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 - a library that turns molecular structural formulas written in chemfig notation into SVG
//!
//! Reading (token, parse), placement (label, layout), and output (svg) are kept separate.
//! Positions and sizes are set only once, by layout, and svg only writes them out.
//!
//! It uses neither LaTeX nor TikZ. It has no dependency crates. It reads an OpenType font file
//! directly and takes the advances, the glyph outlines, and the cap height from it.
//!
//! ```
//! 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();
//! ```
//!
//! A bond stops at the edge of the atom's ink. Where it stops is set by the glyph boxes
//! measured from the font, so a longer label makes the bond shorter by that amount.
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;
/// Turns a molecule string into SVG. Glyphs are drawn as outlines
pub fn render_svg(source: &str, font: &Font) -> Result<String, Error> {
render_svg_with(source, font, &Style::default(), Shape::Outline)
}
/// Turns a molecule string into SVG with chosen sizes and a chosen way of writing glyphs
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))
}
/// Turns a syntax tree into SVG. label becomes the alternative text
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))
}
/// Gives coordinates to a molecule string. Use this when only the positions are needed, not 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=\"二酸化炭素\""));
}
}