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
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;
pub fn render_svg(source: &str, font: &Font) -> Result<String, Error> {
render_svg_with(source, font, &Style::default(), Shape::Outline)
}
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))
}
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))
}
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("character 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), "the name does not make the figure taller");
}
#[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=\"二酸化炭素\""));
}
}