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
//! Lays out a chemical structure with lina and turns it into marks to place on the page
//!
//! lina returns positions and sizes in em. The origin is the first atom, so the figure is
//! shifted to start at its left edge when it is placed. Bonds become lines, wedges become
//! triangles, and the circle in an aromatic ring becomes a circle.
use crate::draw::{Letter, Mark};
use crate::error::Error;
use crate::font::{self, Library};
use crate::math::Built;
/// Lays out a chemical structure. size is the font size in points
pub fn build(source: &str, library: &Library, size: f64) -> Result<Built, Error> {
let style = lina::Style::default();
let (node, wrapper) = lina::parse_wrapped(source)
.map_err(|reason| Error::new(format!("化学式が読めません: {source}: {reason}"), 0))?;
let mut figure = lina::layout::layout(&node, &library.chem, &style)
.map_err(|reason| Error::new(format!("化学式を組めません: {source}: {reason}"), 0))?;
if let lina::Wrapper::Named(name) = &wrapper {
lina::layout::place_name(&mut figure, name, &library.chem, &style);
}
let extent = figure.extent.grow(style.margin);
let left = extent.left;
let width = extent.right - extent.left;
// lina puts the nodes at y = 0. The baseline of a label is half the cap height below
// that, so we raise the figure by that amount to put it on the line of the body text
let base = lina::font::cap_height(&library.chem) / 2.0;
let mut mark = Vec::new();
let thickness = style.line_width * size;
for line in &figure.line {
mark.push(Mark::Stroke {
point: vec![
((line.x1 - left) * size, (line.y1 - base) * size),
((line.x2 - left) * size, (line.y2 - base) * size),
],
width: thickness,
});
}
for wedge in &figure.wedge {
let point = vec![
((wedge.tip.0 - left) * size, (wedge.tip.1 - base) * size),
((wedge.base[0].0 - left) * size, (wedge.base[0].1 - base) * size),
((wedge.base[1].0 - left) * size, (wedge.base[1].1 - base) * size),
];
if wedge.filled {
mark.push(Mark::Fill {point});
} else {
let mut closed = point.clone();
closed.push(point[0]);
mark.push(Mark::Stroke {point: closed, width: thickness});
}
}
for circle in &figure.circle {
mark.push(Mark::Circle {
x: (circle.x - left) * size,
y: (circle.y - base) * size,
radius: circle.radius * size,
width: thickness,
});
}
for atom in figure.atom.iter().chain(figure.name.iter()) {
for placed in &atom.label.placed {
let (face, found) = choose(library, placed.glyph, placed.character);
if found == 0 {
continue;
}
mark.push(Mark::Text {
face,
size: placed.size * size,
stretch: 1.0,
x: (atom.origin_x + placed.x - left) * size,
y: (atom.origin_y + placed.y - base) * size,
letter: vec![Letter {glyph: found, text: placed.character.to_string()}],
});
}
}
Ok(Built {
mark,
width: width * size,
height: (base - extent.top) * size,
depth: (extent.bottom - base) * size,
})
}
/// If the glyph is not in the typeface for chemical structures, look for it in the typeface for
/// Japanese text or the typeface for body text
fn choose(library: &Library, glyph: Option<u16>, character: char) -> (usize, u16) {
if let Some(found) = glyph {
return (library.role.math, found);
}
for face in [library.role.japanese, library.role.roman] {
if let Some(found) = font::glyph(&library.face[face], character) {
return (face, found);
}
}
(library.role.math, 0)
}