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
//! 分子構造式の見本を 1 枚の HTML ページにして標準出力へ書き出す
//!
//! 使い方:
//! cargo run > sample.html
//! cargo run -- 'H_3C-CH_2-OH' 'O=C=O' > out.html
//! cargo run -- --font /path/to/other.otf > out.html
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", "水。結合は字の縁で止まる"),
("O=C=O", "二酸化炭素。二重結合"),
("N~N", "窒素。三重結合"),
("H_3C-CH_3", "エタン。左は最後の C、右は最初の C で結合が止まる"),
("H_3C-CH_2-OH", "エタノール"),
("H_3C-[:30]CH_2-[:-30]CH_2-[:30]CH_3", "ブタン。角度を交互に振る"),
("-[:30]-[:-30]-[:30]-[:-30]", "ペンタンの骨格。印を置かない結び目だけで書く"),
("H-[:30]C(-[:90]H)(-[:-90]H)-[:-30]H", "メタン。枝を 2 本出す"),
("H_3C-C(=[:90]O)-OH", "酢酸。枝に二重結合"),
("H_2N-CH_2-C(=[:90]O)-OH", "グリシン"),
("H_3C-CH_2-C(=[:90]O)-CH_2-CH_3", "3-ペンタノン"),
("\\chemfig{H_3C-CH_2-OH}", "LaTeX の囲みは外して読む"),
("\\chemname{\\chemfig{O=C=O}}{二酸化炭素}", "名前は図の下に置く"),
("Na^+", "上付きの電荷"),
("SO_4^{2-}", "下付きと、まとまりの上付き"),
("H_2SO_4", "硫酸。下付きが 2 つ"),
("*6(------)", "シクロヘキサン。6 員環"),
("*5(-----)", "シクロペンタン"),
("*3(---)", "シクロプロパン"),
("*6(-=-=-=)", "ベンゼン。環の二重結合は内側へ寄り、両端を詰める"),
("**6(------)", "ベンゼン。芳香環の丸"),
("H_3C-*6(-=-=-=)", "トルエン。環は入ってきた結合の向きへ伸びる"),
("*6(-N=-=-=)", "ピリジン"),
("*6(-(-OH)-----)", "シクロヘキサノール。向きを書かない枝は環の外を向く"),
("*6(--*5(-----)----)", "環の頂点から出る環"),
("HO-*6(-=-(-OH)=-=)", "ヒドロキノン。鎖からつないだ環に枝を足す"),
("?[a]-[:0]-[:-90]-[:180]?[a]", "同じ名前の印を 2 つ置くと、そのあいだに結合が引かれる"),
("*6(--?[a]-(-[:0]-[:60]-[:120]-[:180]?[a])---)", "デカリンの骨格。印で縮合環にする"),
("*6(-=?[a]-(=[:0]-[:60]=[:120]-[:180]?[a,=])=-=)", "ナフタレン"),
("X(>[:0]a)(<[:60]b)(>:[:120]c)(<:[:180]d)(>|[:240]e)(<|[:300]f)", "くさび 6 種"),
("H_3C-C(>[:90]OH)(<:[:-90]H)-CH_2-CH_3", "2-ブタノール。手前が塗り、奥が破線"),
("A-[::30]B-[::30]C-[::30]D-[::30]E", "相対角度を重ねると弧になる"),
("A-[,0.5]B-[,1]C-[,2]D", "結合長の係数"),
("C-[,,1]C", "出る側の部分の番号を書く"),
("A-[45]B", "誤り。度で書くときは :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!("--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);
}
};
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=\"ja\">");
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>書体は {} です。字形は輪郭で描いているので、開く側にフォントは要りません。\
図の大きさは em で書いてあるので、まわりの文字サイズに合わせて伸び縮みします。</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>");
}