Registry / RepositoryTypeScriptRustPython
Rollpie
ReadmeFiles
Versions
Info
Download
0.2.0528.5 KB2026-10-160.1.1542.8 KB2026-10-150.1.0541.1 KB2026-10-15
Version
0.2.0
Copyright
Rollpie, Irohabook
Publisher
math
Published
2026-10-16
Size
528.5 KB
Downloads
5
Checksum
f8871632f38084ae53dd9bf16e9ea90c80a209bf6cd234b6767630c5470dc305
Dependencies
None

lina

iroha get rust/lina

lina

A Rust library that typesets molecular structures written in chemfig notation into SVG. It uses neither LaTeX nor TikZ, and it has no dependencies.

The font is read directly from an OpenType file. lina takes the advances, the glyph outlines and the cap height from it, and uses them to decide where each bond stops and at what height it runs. Bonds stop at the edge of an atom's ink, so a longer label makes the bond shorter.

Usage

cargo run > sample.html                                # write the built-in samples
cargo run -- 'H_3C-CH_2-OH' '*6(-=-=-=)' > out.html    # write the molecules given as arguments
cargo run -- --font /path/to/other.otf > out.html      # use another typeface
cargo test

examples/ holds four commands for checking the output.

cargo run --example check > check.html       # a page to inspect by eye
cargo run --example show -- 'O=C=O'          # print the coordinates of atoms and lines
cargo run --example probe                    # see whether the typeface has the characters chemistry needs
cargo run --example probe -- /path/to.otf    # check another typeface
cargo run --release --example bench          # measure loading and layout

The page from check lists representative molecules under six headings. Next to each figure it says what to look at to tell that it is right. If a figure does not look as described, that is the mistake. At the end come figures placed in running text, figures with a different font size and color, and figures with changed Style dimensions. Three figures are given malformed input on purpose; the right behavior is that the reason and the position appear in red.

As a library:

let data = std::fs::read("font/latinmodern-math.otf")?;
let font = lina::load_font(data)?;
let svg = lina::render_svg("H_3C-CH_2-OH", &font)?;

To change the dimensions or how glyphs are written, pass a Style and a Shape.

let style = lina::Style {bond_length: 2.4, ..lina::Style::default()};
let svg = lina::render_svg_with("*6(-=-=-=)", &font, &style, lina::Shape::Outline)?;

You can paste straight from a LaTeX source. \chemfig{...} and \chemname{\chemfig{...}}{name} are taken off as outer wrappers. The name is placed centered under the figure.

let svg = lina::render_svg(r"\chemname{\chemfig{H_3C-CH_2-OH}}{ethanol}", &font)?;

Use parse_wrapped when you also need to know which wrapper was used. Even after the wrapper is taken off, error positions are still counted in the original string.

On failure you get a lina::Error. It carries a message and the character position in the source where the parse stopped. When loading a font fails, in_font is set and the position counts bytes of the font file.

Supported notation

The outermost \chemfig{...} and \chemname{\chemfig{...}}{name} are taken off as wrappers. No other backslash command is read; lina refuses it and says where it was.

An atom is the string as written. _ makes a subscript and ^ a superscript, and {} groups. Write H_2O, SO_4^{2-} or CH_3. A hyphen inside a label is set as the minus sign of a negative charge. Nested subscripts and superscripts get smaller, but stop at script_floor in Style.

A little space goes after a script. Scripts are small, so the next letter looks cramped when it follows at once; the 2 and the S in H_2SO_4 are an example. No space goes between scripts that follow each other (the 4 and the 2 in O_4^{2-}).

+, −, ± and ∓ inside a superscript are raised. Digits sit on the baseline, but these signs are drawn around the math axis, so set as they are they look lower than the digits. They are raised until their center meets the center of the digits, so the 2 and the + in Ca^{2+} sit at the same height.

An atom is split into parts at each capital letter: CH_3 is the two parts C and H_3. By default a bond stops at the box of the whole label, but if you give a part number, it stops at the box of that part.

There are nine kinds of bond.

  • - single, = double, ~ triple
  • > wedge (wide at the end it goes to), < wedge (wide at the end it comes from)
  • >: <: hashed wedges
  • >| <| hollow wedges

Square brackets after a bond give its direction, its length and the parts it connects. The order is <angle>,<factor>,<from part>,<to part>, and every item can be left out.

An angle can be written three ways. :45 is an absolute angle with east as 0 degrees, ::45 is relative to the direction of the previous bond, and 0 to 7 count steps of 45 degrees. With nothing written, the bond goes straight east. A bond leaving a ring vertex, though, points out of the ring when nothing is written.

Branches go in parentheses. A molecule can have as many as you like, as in C(-[:90]OH)(-[:-90]H)-CH_3. A branch always starts with a bond.

A ring is written like *6(------): its size from 3 to 100, then as many bonds as it has sides. With six sides there are five atoms, and the last side returns to the first atom. **6(------) draws a circle inside, for aromatic rings.

A ring is placed as a regular polygon. The incoming bond bisects the exterior angle at the first vertex, so the ring extends straight along the incoming direction. Double bonds inside a ring move toward the inside and are shortened slightly at both ends. A ring written at a vertex of another ring points out of that ring and shares only that vertex.

A ring side takes neither an angle nor a length, because the size of the ring decides its direction. A part number can be given. To start a chain from a ring vertex, put it in parentheses as a branch. A bond outside parentheses becomes a side of the ring.

? closes a ring. When a mark with the same name appears twice, a bond is drawn between those two atoms. Write the name as ?[a]; without one it is a. ?[a,=] chooses the kind of bond to draw. The kind is the bond symbol itself (-, =, ~, > and so on) or a number from 1 to 3.

Fused rings are written this way. Naphthalene looks like this:

*6(-=?[a]-(=[:0]-[:60]=[:120]-[:180]?[a,=])=-=)

Write one ring, put a mark and a branch on two adjacent vertices, and close the ring at the end of the branch. A double bond in a ring closed with marks does not belong to a ring placed as a polygon, so it does not move inward; its two lines are drawn on either side of the center line.

Unsupported notation

Defining and calling submolecules (chemfig's \definesubmol and !) is not supported. Neither are charges and lone pairs (\charge and \lewis), notes above and below an atom (\chemabove), or reaction schemes (\schemestart and \arrow). No command other than \chemfig and \chemname is read. Nesting rings so that they share a side is not supported either; write fused rings with ?.

Element symbols

Element symbols are set upright. chemfig sets atoms as math, so they come out italic, but IUPAC uses upright type. Style cannot switch this. To get italic, write the italic characters themselves in the label.

Layout

src/
├── lib.rs      public API
├── error.rs    the error type
├── opentype.rs OpenType tables (head hhea maxp hmtx cmap name)
├── cff.rs      CFF glyphs and Type 2 charstrings
├── glyf.rs     TrueType glyphs
├── outline.rs  outlines and their bounding boxes
├── font.rs     one font's metrics and glyphs
├── token.rs    tokenizer
├── node.rs     syntax tree types
├── parse.rs    parser
├── label.rs    sets atom labels
├── ring.rs     ring geometry
├── bond.rs     how bonds are drawn
├── layout.rs   gives the syntax tree coordinates
├── style.rs    figure dimensions
├── svg.rs      writes SVG
├── text.rs     escaping and number formatting
└── main.rs     the command that writes the sample page

layout decides coordinates in em, with the font size as 1. Positive x points right and positive y points down. Angles take east as 0 degrees and counterclockwise as positive, so their sign flips because y points down.

A label is placed so that the horizontal center of its ink box sits on the node. Vertically, its baseline is lowered by half the cap height. That height does not depend on what the label says, so bonds stay at the same height however long a chain grows.

A bond is drawn from node to node, and then both ends are trimmed at the atoms' ink boxes. A node with no label has no box, so nothing is trimmed and no space is left there.

Dimensions

The defaults follow what chemfig has used: a bond length of 3em, a double bond gap of 0.2em, and 0.2em between an atom and the end of a bond. All of them can be changed through Style.

Every length is in em. The SVG also gives its width and height in em, so a figure scales with the surrounding font size. The color is left to currentColor.

Figures carry vertical-align. Placed in running text, the baseline of the letters in the figure then sits on the baseline of the surrounding text. Without it, the bottom of the figure would sit on the baseline and the figure would look as if it floated.

Fonts

OpenType is what lina reads. Both CFF outlines (usually .otf) and glyf outlines (.ttf) work. For a font collection (.ttc) the first font is used.

Glyphs can be written in two ways. Shape::Outline draws outlines: the reader needs no font and the figure looks the same anywhere, but it cannot be selected and copied. Shape::Text places characters: they can be selected and copied and the output is smaller, but the reader needs the same font. lina::font::family returns the family name to write in @font-face.

Even with Shape::Text, lina writes the position of every character instead of leaving the advances to the browser. Runs of characters with the same size and height are grouped into one <text>, so they can be selected and copied together.

Characters the typeface lacks (Japanese, for example) have no outlines to draw, so they are placed as text with either shape.

font/latinmodern-math.otf is here for tests and samples. The library itself never reads it. lina reads the font the caller passes, so to use another font, pass that file.

Speed

Measured on an Apple silicon machine with a release build, loading the font takes 27 ms and setting one molecule into SVG takes 70 µs. Loading happens once, and that is when the vertical extent of every glyph is measured. Layout does no loading.

Relation to suki

suki typesets LaTeX math and is a separate library from lina. The code that reads fonts was copied from suki and then rewritten as lina's own. lina does not refer to suki, and its Cargo.toml has no dependencies.

License

Dual licensed under MIT and Apache-2.0. Take whichever you prefer.

The bundled font is licensed separately. font/latinmodern-math.otf is Latin Modern Math 1.959 from GUST, the Polish TeX Users Group, under the GUST Font License, which follows the LaTeX Project Public License 1.3c. The text is in font/GUST-FONT-LICENSE.txt. The font is bundled for tests and samples only; the library itself never reads it.