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
//! 化学式を lina で組んで、ページに置くものに直す
//!
//! lina は位置と寸法を em で返す。原点は最初の原子なので、置くときは左端に寄せ直す。
//! 結合は線、くさびは三角、芳香環の丸は円になる。
use crate::draw::{Letter, Mark};
use crate::error::Error;
use crate::font::{self, Library};
use crate::math::Built;
/// 化学式を組む。size は字の大きさ(点)
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 は結び目の高さを y = 0 に置く。ラベルのベースラインはそこから
// 大文字の高さの半分だけ下にあるので、そのぶん持ち上げて地の文の行に乗せる
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,
})
}
/// 字形が化学式の書体にないときは、和文か地の文の書体から探す
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)
}