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.1.0
Copyright
Rollpie, Irohabook
Publisher
math
Published
2026-10-15
Size
541.1 KB
Downloads
7
Checksum
67f9c361d04f05b0299dd8e9ef5e2a0120d67be36205a76945eb5f7ef90443be
Dependencies
None

README.md

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# 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`](src/style.rs) 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.

## 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, 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 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](https://rollpie.com/rust/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.

- [LICENSE-MIT](./LICENSE-MIT)
- [LICENSE-APACHE](./LICENSE-APACHE)

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](./font/GUST-FONT-LICENSE.txt). The font is bundled for tests and
samples only; the library itself never reads it.