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
9
Checksum
67f9c361d04f05b0299dd8e9ef5e2a0120d67be36205a76945eb5f7ef90443be
Dependencies
None

check.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
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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
//! ブラウザで目で見て確かめるページを書き出す
//!
//! 使い方:
//!   cargo run --example check > check.html
//!   cargo run --example check -- --font /path/to/other.otf > check.html
//!
//! 図ごとに、どこを見れば正しいと言えるかを書いてある。
//! 書いてあるとおりに見えていなければ、そこが誤りである。

use lina::font::{self, Font};
use lina::{Shape, Style, escape, render_svg, render_svg_with};


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


/// 見出しと、その下に並べる(分子, 見るところ)の組
const GROUPS: &[(&str, &[(&str, &str)])] = &[
    ("結合", &[
        ("H-O-H", "結合が H と O の墨の縁で止まり、字に触れていない。線は水平で、字の胴を通る"),
        ("O=C=O", "2 本の線が同じ長さで、真ん中をはさんで上下に等しく離れている"),
        ("N~N", "3 本の線が等しい間隔で、真ん中の 1 本が両端の中央にある"),
        ("C-C", "H-O-H と同じ長さの結合。原子の中心どうしは 3em 離れている"),
        ("H_3C-CH_3", "結合が左は最後の C、右は最初の C の縁で止まる。C-C より短い"),
    ]),
    ("角度と枝", &[
        ("A-[:90]B", "B が A の真上にある"),
        ("A-[2]B", "[2] は 45 度 x 2 で真上。上の図と同じ向き"),
        ("A-[:30]B-[::60]C", "C は B の真上。相対角度が直前の 30 度に足されている"),
        ("A-[::30]B-[::30]C-[::30]D-[::30]E", "同じ角度を重ねると弧を描く。5 つの間隔が等しい"),
        ("A-[,0.5]B-[,1]C-[,2]D", "結合の長さが 0.5 倍、1 倍、2 倍。C-D は A-B の 4 倍"),
        ("H-[:30]C(-[:90]H)(-[:-90]H)-[:-30]H", "枝が 2 本。4 本の結合がすべて C の縁から出ている"),
        ("-[:30]-[:-30]-[:30]-[:-30]", "印を置かない結び目。折れ線に切れ目も空きもない"),
    ]),
    ("環", &[
        ("*6(------)", "正六角形。上下の辺が水平で、左右の頂点がとがる。6 辺が同じ長さ"),
        ("*5(-----)", "正五角形。5 辺が同じ長さ"),
        ("*3(---)", "正三角形"),
        ("*6(-=-=-=)", "二重結合の 2 本目が環の内側にあり、両端が角の手前で止まっている"),
        ("**6(------)", "丸が六角形の内側に収まり、どの辺にも触れていない"),
        ("H_3C-*6(-=-=-=)", "環が結合の向きへまっすぐ伸びる。CH_3 は環の左の頂点につながる"),
        ("*6(-N=-=-=)", "N が左上の頂点にあり、両隣の辺が N の縁で止まっている"),
        ("*6(-(-OH)-----)", "向きを書かない枝が環の外を向く。OH は環に重ならない"),
        ("*6(--*5(-----)----)", "六角形と五角形が頂点 1 つを共有する"),
    ]),
    ("環の閉じ", &[
        ("?[a]-[:0]-[:-90]-[:180]?[a]", "3 本の結合と、印が引いた 1 本で四角が閉じている"),
        ("*6(--?[a]-(-[:0]-[:60]-[:120]-[:180]?[a])---)",
         "六角形 2 つが辺 1 本を共有する。デカリンの骨格"),
        ("*6(-=?[a]-(=[:0]-[:60]=[:120]-[:180]?[a,=])=-=)",
         "ナフタレン。二重結合が両方の環で交互に並ぶ"),
        ("A?[b,=]-[:60]B-[:-60]C?[b]",
         "印が引いた A と C のあいだの結合が二重になり、三角形の底辺になっている"),
    ]),
    ("くさび", &[
        ("X(>[:0]a)(<[:60]b)(>:[:120]c)(<:[:180]d)(>|[:240]e)(<|[:300]f)",
         "a と b が塗った三角、c と d が破線、e と f が塗らない三角。\
          a c e は X 側が細く、b d f は X 側が太い"),
        ("H_3C-C(>[:90]OH)(<:[:-90]H)-CH_2-CH_3",
         "OH へ向かう手前の結合が塗った三角、H へ向かう奥の結合が破線"),
    ]),
    ("LaTeX の囲み", &[
        ("\\chemfig{H_3C-CH_2-OH}", "囲みを外して中身を組む。囲みなしで書いたものと同じ図になる"),
        ("\\chemname{\\chemfig{H-O-H}}{water}", "名前が図の下の真ん中に置かれる"),
        ("\\chemfig{A-[45]B}", "囲みがあっても、誤りの場所は元の文字列で数える。13 文字目"),
    ]),
    ("ラベル", &[
        ("H_2O", "2 が小さく、ベースラインより下がっている"),
        ("H_{2_{2_{2}}}", "入れ子の添字が 3 段目で縮むのをやめる。2 つめと 3 つめが同じ大きさ"),
        ("Na^+", "+ が小さく上がり、上下の端が数字の上付きとそろっている"),
        ("Ca^{2+}", "2 と + の上下がそろっている。+ は数式の軸で描かれた字なので、\
                     数字の高さまで持ち上げてある"),
        ("SO_4^{2-}", "2- がまとまりとして上がり、負号が 2 の高さの真ん中を通っている"),
        ("H_2SO_4", "下付きが 2 つとも同じ高さ、同じ大きさ。2 と S のあいだに少し空きがある"),
        ("H_2N-CH_2-C(=[:90]O)-OH", "グリシン。3 本の水平な結合が同じ高さを通る"),
    ]),
    ("誤りの知らせ", &[
        ("A-[45]B", "赤い文字で、何が誤りで何文字目かを言う"),
        ("*6(-----)", "6 員環に結合が 5 本しかないことを言う"),
        ("A?[a]-B", "印が 1 つしかないことを言う"),
    ]),
];


/// 文中に置いて、まわりの文字と高さが合うかを見る
const INLINE: &str = "H_3C-CH_2-OH";

/// 大きさと色を変えて見る
const VARIED: &str = "*6(-=-(-OH)=-=)";

/// 輪郭と文字の 2 通りで見る
const SHAPED: &str = "H_2SO_4";


const PAGE_STYLE: &str = "body{margin:0;padding:56px 40px 120px;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}
h2{font-size:15px;font-weight:500;margin:64px 0 20px;color:#1f1f1f}
p{max-width:46em;margin:0 0 32px;color:#767676}
.item{display:grid;grid-template-columns:minmax(0,20em) minmax(0,22em);column-gap:32px;\
align-items:start;margin-bottom:36px}
.source{grid-column:1 / -1;color:#767676;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;\
font-size:12px;line-height:1.6;margin-bottom:10px;word-break:break-all}
.figure{font-size:16px;overflow-x:auto;max-width:100%}
.note{color:#767676;font-size:12px;line-height:1.8}
.wrong{color:#a33;font-size:12px}
.inline{max-width:46em;color:#1f1f1f;font-size:16px;line-height:2.4}
.row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:40px}
.dark{background:#1f1f1f;color:#fff;padding:24px 28px;display:inline-block;font-size:16px}
.tint{color:#1b81e0;font-size:16px}
.tone{color:#a33;font-size:16px}";


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!("--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!("フォントを読めません: {path}: {reason}");
            std::process::exit(1);
        }
    };
    let font = match font::load(data) {
        Ok(font) => font,
        Err(reason) => {
            eprintln!("フォントを読み込めません: {path}: {reason}");
            std::process::exit(1);
        }
    };

    write_page(&font, &path);
}


fn write_page(font: &Font, path: &str) {
    println!("<!DOCTYPE html>");
    println!("<html lang=\"ja\">");
    println!("<head>");
    println!("<meta charset=\"utf-8\">");
    println!("<title>lina を確かめる</title>");
    // 文字として書き出した図には、表示する側にも同じフォントが要る
    println!("<style>");
    println!(
        "@font-face{{font-family:\"{}\";src:url(\"{}\")}}",
        escape(font::family(font)),
        escape(path)
    );
    println!("{PAGE_STYLE}</style>");
    println!("</head>");
    println!("<body>");
    println!("<h1>lina を確かめる</h1>");
    println!(
        "<p>書体は {} です。図ごとに、右へ「どこを見れば正しいと言えるか」を書いてあります。\
書いてあるとおりに見えていなければ、そこが誤りです。最後の 3 つは、わざと誤った書き方を\
渡したものです。赤い文字で理由と場所が出れば正しい動きです。</p>",
        escape(font::family(font))
    );

    for (title, items) in GROUPS {
        println!("<h2>{}</h2>", escape(title));

        for (source, note) in *items {
            write_item(font, source, note);
        }
    }

    write_shape(font);
    write_inline(font);
    write_varied(font);

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


/// 輪郭で描いたものと、文字として置いたもの
fn write_shape(font: &Font) {
    println!("<h2>輪郭と文字</h2>");
    println!(
        "<p>同じ分子を 2 通りで書き出します。左が字形を輪郭で描いたもの、\
右が文字として置いたものです。2 つが同じ形に見え、右だけを選んで写せれば正しい動きです。\
写すと {} が取り出せます。右はこのページに読ませたフォントで描いているので、\
表示する側にフォントがないと形が変わります。</p>",
        escape(SHAPED)
    );
    println!("<div class=\"source\">{}</div>", escape(SHAPED));
    println!("<div class=\"row\">");
    println!("<div class=\"figure\">{}</div>", draw(font, SHAPED));

    let style = Style::default();

    match render_svg_with(SHAPED, font, &style, Shape::Text) {
        Ok(markup) => println!("<div class=\"figure\">{markup}</div>"),
        Err(reason) => println!("<div class=\"wrong\">{}</div>", escape(&reason.to_string())),
    }

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


/// 分子 1 つと、その見どころ
fn write_item(font: &Font, source: &str, note: &str) {
    println!("<div class=\"item\">");
    println!("<div class=\"source\">{}</div>", escape(source));

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

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


/// 文中に置いたところ
fn write_inline(font: &Font) {
    let figure = match render_svg(INLINE, font) {
        Ok(markup) => markup,
        Err(reason) => escape(&reason.to_string()),
    };

    println!("<h2>文中に置く</h2>");
    println!(
        "<p>図はまわりの文字と同じ大きさの単位で書き出します。\
下の行で、図の結合の高さが前後の文字のなかほどにあり、行の高さを不自然に押し広げて\
いなければ正しい動きです。</p>"
    );
    println!("<div class=\"inline\">エタノール {figure} は水にもよく溶けます。</div>");
}


/// 大きさと色を変えたところ
fn write_varied(font: &Font) {
    println!("<h2>大きさと色</h2>");
    println!(
        "<p>同じ分子を、文字サイズと色だけ変えて並べます。3 つの図が同じ形のまま\
大きさだけ変わり、色が地の文の色に従っていれば正しい動きです。\
最後は暗い背景に置いたもので、線も字も白く出れば正しい動きです。</p>"
    );
    println!("<div class=\"source\">{}</div>", escape(VARIED));
    println!("<div class=\"row\">");

    for size in [13, 18, 26] {
        let figure = draw(font, VARIED);
        println!("<div style=\"font-size:{size}px\">{figure}</div>");
    }

    println!("</div>");
    println!("<div class=\"row\" style=\"margin-top:40px\">");
    println!("<div class=\"tint\">{}</div>", draw(font, VARIED));
    println!("<div class=\"tone\">{}</div>", draw(font, VARIED));
    println!("<div class=\"dark\">{}</div>", draw(font, VARIED));
    println!("</div>");

    println!("<h2>寸法を変える</h2>");
    println!(
        "<p>結合長と線の太さを Style で変えたものです。左が既定、\
真ん中が結合長を 2em に縮めたもの、右が線を太くしたものです。\
形が崩れず、指定したところだけが変わっていれば正しい動きです。</p>"
    );
    println!("<div class=\"row\">");
    println!("<div class=\"figure\">{}</div>", draw(font, VARIED));

    let narrow = Style {bond_length: 2.0, ..Style::default()};
    println!("<div class=\"figure\">{}</div>", draw_with(font, VARIED, &narrow));

    let bold = Style {line_width: 0.1, double_gap: 0.26, ..Style::default()};
    println!("<div class=\"figure\">{}</div>", draw_with(font, VARIED, &bold));
    println!("</div>");
}


fn draw(font: &Font, source: &str) -> String {
    match render_svg(source, font) {
        Ok(markup) => markup,
        Err(reason) => escape(&reason.to_string()),
    }
}


fn draw_with(font: &Font, source: &str, style: &Style) -> String {
    match render_svg_with(source, font, style, Shape::Outline) {
        Ok(markup) => markup,
        Err(reason) => escape(&reason.to_string()),
    }
}