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
//! lina - chemfig の記法で書いた分子構造式を SVG に組み上げるライブラリ
//!
//! 読み取り (token, parse) と配置 (label, layout) と書き出し (svg) を分けている。
//! 位置と寸法は layout が一度だけ決めて、svg はそれを書き出すだけである。
//!
//! LaTeX も TikZ も使わない。依存クレートもない。フォントは OpenType のファイルを
//! そのまま読み、字送りと字形の輪郭、大文字の高さを取る。
//!
//! ```
//! let data = std::fs::read("font/latinmodern-math.otf").unwrap();
//! let font = lina::load_font(data).unwrap();
//! let svg = lina::render_svg("H_3C-CH_2-OH", &font).unwrap();
//! ```
//!
//! 結合は原子の墨の縁で止まる。止める位置はフォントから測った字形の箱で決まるので、
//! ラベルが長くなれば結合はそのぶん短くなる。
pub mod bond;
pub mod cff;
pub mod error;
pub mod font;
pub mod glyf;
pub mod label;
pub mod layout;
pub mod node;
pub mod opentype;
pub mod outline;
pub mod parse;
pub mod ring;
pub mod style;
pub mod svg;
pub mod text;
pub mod token;
pub use error::Error;
pub use font::{Font, load as load_font};
pub use layout::Figure;
pub use node::Node;
pub use parse::{Wrapper, parse, parse_wrapped};
pub use style::Style;
pub use svg::Shape;
pub use text::escape;
/// 分子の文字列を SVG にする。字形は輪郭で描く
pub fn render_svg(source: &str, font: &Font) -> Result<String, Error> {
render_svg_with(source, font, &Style::default(), Shape::Outline)
}
/// 寸法と字形の出し方を決めて、分子の文字列を SVG にする
pub fn render_svg_with(
source: &str,
font: &Font,
style: &Style,
shape: Shape,
) -> Result<String, Error> {
let (node, wrapper) = parse_wrapped(source)?;
let mut figure = layout::layout(&node, font, style)?;
if let Wrapper::Named(name) = &wrapper {
layout::place_name(&mut figure, name, font, style);
}
Ok(svg::render(&figure, font, style, shape, source))
}
/// 構文木を SVG にする。label は代替テキストになる
pub fn to_svg(
node: &Node,
font: &Font,
style: &Style,
shape: Shape,
label: &str,
) -> Result<String, Error> {
let figure = layout::layout(node, font, style)?;
Ok(svg::render(&figure, font, style, shape, label))
}
/// 分子の文字列に座標を与える。SVG にせず、位置だけがほしいときに使う
pub fn to_figure(source: &str, font: &Font, style: &Style) -> Result<Figure, Error> {
layout::layout(&parse(source)?, font, style)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::font::sample;
#[test]
fn renders_a_molecule_from_one_call() {
let found = render_svg("H_3C-CH_2-OH", &sample()).unwrap();
assert!(found.starts_with("<svg"));
assert!(found.ends_with("</svg>"));
}
#[test]
fn a_mistake_comes_back_with_its_place() {
let found = render_svg("A-[45]B", &sample()).unwrap_err();
assert_eq!(found.position, 3);
assert!(found.to_string().contains("4 文字目"));
}
#[test]
fn the_style_changes_the_size() {
let font = sample();
let style = Style {bond_length: 6.0, ..Style::default()};
let wide = to_figure("C-C", &font, &style).unwrap();
let narrow = to_figure("C-C", &font, &Style::default()).unwrap();
assert!(wide.extent.right > narrow.extent.right);
}
#[test]
fn a_latex_source_can_be_pasted_as_it_is() {
let font = sample();
assert_eq!(
render_svg("\\chemfig{H_3C-CH_2-OH}", &font).unwrap(),
render_svg("\\chemfig{H_3C-CH_2-OH}", &font).unwrap()
);
let wrapped = render_svg("\\chemfig{O=C=O}", &font).unwrap();
assert!(wrapped.contains("aria-label=\"\\chemfig{O=C=O}\""));
assert!(wrapped.contains("viewBox"));
}
#[test]
fn a_name_is_drawn_under_the_molecule() {
let font = sample();
let plain = render_svg("\\chemfig{H-O-H}", &font).unwrap();
let named = render_svg("\\chemname{\\chemfig{H-O-H}}{water}", &font).unwrap();
let height = |markup: &str| {
let at = markup.find("height=\"").unwrap() + "height=\"".len();
markup[at..at + markup[at..].find("em").unwrap()].parse::<f64>().unwrap()
};
assert!(height(&named) > height(&plain), "名前のぶん高くなっていません");
}
#[test]
fn the_letters_can_be_written_as_text() {
let font = sample();
let style = Style::default();
let found = render_svg_with("H_2O", &font, &style, Shape::Text).unwrap();
assert!(found.contains("<text"));
assert!(!found.contains("<path fill=\"currentColor\""));
}
#[test]
fn the_tree_can_be_drawn_on_its_own() {
let font = sample();
let node = parse("O=C=O").unwrap();
let style = Style::default();
let found = to_svg(&node, &font, &style, Shape::Outline, "二酸化炭素").unwrap();
assert!(found.contains("aria-label=\"二酸化炭素\""));
}
}