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
7
Checksum
f8871632f38084ae53dd9bf16e9ea90c80a209bf6cd234b6767630c5470dc305
Dependencies
None

main.rs

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
use lina::font::{self, Font};
use lina::{escape, render_svg};


const DEFAULT_FONT: &str = "font/latinmodern-math.otf";


const SAMPLES: &[(&str, &str)] = &[
    ("H-O-H", "Water. Bonds stop at the edge of the letters"),
    ("O=C=O", "Carbon dioxide. Double bonds"),
    ("N~N", "Nitrogen. A triple bond"),
    ("H_3C-CH_3", "Ethane. The bond stops at the last C of H_3C and the first C of CH_3"),
    ("H_3C-CH_2-OH", "Ethanol"),
    ("H_3C-[:30]CH_2-[:-30]CH_2-[:30]CH_3", "Butane. The angles alternate"),
    ("-[:30]-[:-30]-[:30]-[:-30]", "The skeleton of pentane, with no labels at all"),
    ("H-[:30]C(-[:90]H)(-[:-90]H)-[:-30]H", "Methane. Two branches"),
    ("H_3C-C(=[:90]O)-OH", "Acetic acid. A double bond in a branch"),
    ("H_2N-CH_2-C(=[:90]O)-OH", "Glycine"),
    ("H_3C-CH_2-C(=[:90]O)-CH_2-CH_3", "3-Pentanone"),
    ("\\chemfig{H_3C-CH_2-OH}", "The LaTeX wrapper is taken off"),
    ("\\chemname{\\chemfig{O=C=O}}{carbon dioxide}", "The name goes under the figure"),
    ("Na^+", "A charge as a superscript"),
    ("SO_4^{2-}", "A subscript and a grouped superscript"),
    ("H_2SO_4", "Sulfuric acid. Two subscripts"),
    ("*6(------)", "Cyclohexane. A six-membered ring"),
    ("*5(-----)", "Cyclopentane"),
    ("*3(---)", "Cyclopropane"),
    ("*6(-=-=-=)", "Benzene. Double bonds in a ring move inward and are shortened at both ends"),
    ("**6(------)", "Benzene. The circle of an aromatic ring"),
    ("H_3C-*6(-=-=-=)", "Toluene. A ring extends along the bond that reaches it"),
    ("*6(-N=-=-=)", "Pyridine"),
    ("*6(-(-OH)-----)", "Cyclohexanol. A branch with no angle points out of the ring"),
    ("*6(--*5(-----)----)", "A ring on a ring vertex"),
    ("HO-*6(-=-(-OH)=-=)", "Hydroquinone. A branch on a ring reached from a chain"),
    ("?[a]-[:0]-[:-90]-[:180]?[a]", "Two marks with the same name draw a bond between them"),
    ("*6(--?[a]-(-[:0]-[:60]-[:120]-[:180]?[a])---)", "The skeleton of decalin, fused with marks"),
    ("*6(-=?[a]-(=[:0]-[:60]=[:120]-[:180]?[a,=])=-=)", "Naphthalene"),
    ("X(>[:0]a)(<[:60]b)(>:[:120]c)(<:[:180]d)(>|[:240]e)(<|[:300]f)", "The six wedges"),
    ("H_3C-C(>[:90]OH)(<:[:-90]H)-CH_2-CH_3", "2-Butanol. Filled toward the viewer, hashed away"),
    ("A-[::30]B-[::30]C-[::30]D-[::30]E", "Repeated relative angles make an arc"),
    ("A-[,0.5]B-[,1]C-[,2]D", "Bond length factors"),
    ("C-[,,1]C", "A part number on the leaving side"),
    ("A-[45]B", "A mistake. Degrees are written as :45"),
];


const PAGE_STYLE: &str = "body{margin:0;padding:56px 40px 96px;color:#1f1f1f;background:#fff;\
font-family:system-ui,-apple-system,sans-serif;font-size:14px;line-height:1.9}
h1{font-size:19px;font-weight:500;margin:0 0 8px}
p{max-width:46em;margin:0 0 40px;color:#767676}
.item{margin-bottom:44px}
.source{color:#767676;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;\
line-height:1.6;margin-bottom:6px;word-break:break-all}
.note{color:#767676;font-size:12px;margin-bottom:14px}
.figure{font-size:16px;overflow-x:auto;max-width:100%}
.wrong{color:#a33}";


fn main() {
    let mut argument: Vec<String> = std::env::args().skip(1).collect();
    let mut path = DEFAULT_FONT.to_string();

    if let Some(index) = argument.iter().position(|value| value == "--font") {
        if index + 1 >= argument.len() {
            eprintln!("give a font file after --font");
            std::process::exit(1);
        }

        path = argument.remove(index + 1);
        argument.remove(index);
    }

    let data = match std::fs::read(&path) {
        Ok(data) => data,
        Err(reason) => {
            eprintln!("cannot read the font: {path}: {reason}");
            std::process::exit(1);
        }
    };
    let font = match font::load(data) {
        Ok(font) => font,
        Err(reason) => {
            eprintln!("cannot load the font: {path}: {reason}");
            std::process::exit(1);
        }
    };

    let shown: Vec<(&str, &str)> = if argument.is_empty() {
        SAMPLES.to_vec()
    } else {
        argument.iter().map(|value| (value.as_str(), "")).collect()
    };

    write_page(&font, &shown);
}


fn write_page(font: &Font, shown: &[(&str, &str)]) {
    println!("<!DOCTYPE html>");
    println!("<html lang=\"en\">");
    println!("<head>");
    println!("<meta charset=\"utf-8\">");
    println!("<title>lina</title>");
    println!("<style>{PAGE_STYLE}</style>");
    println!("</head>");
    println!("<body>");
    println!("<h1>lina</h1>");
    println!(
        "<p>The typeface is {}. Glyphs are drawn as outlines, so the reader needs no font. \
Sizes are written in em, so the figures scale with the text around them.</p>",
        escape(font::family(font))
    );

    for (source, note) in shown {
        println!("<div class=\"item\">");
        println!("<div class=\"source\">{}</div>", escape(source));

        if !note.is_empty() {
            println!("<div class=\"note\">{}</div>", escape(note));
        }

        match render_svg(source, font) {
            Ok(markup) => println!("<div class=\"figure\">{markup}</div>"),
            Err(reason) => {
                println!("<div class=\"note wrong\">{}</div>", escape(&reason.to_string()))
            }
        }

        println!("</div>");
    }

    println!("</body>");
    println!("</html>");
}