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
//! Lays out math with suki and turns it into marks to place on the page
//!
//! suki returns positions and sizes in em. Here they are multiplied by the font size to get
//! points, and the glyphs are passed to the PDF as they are. The glyph IDs are those of the
//! font, so they match the embedded font.
//!
//! Characters that the math typeface does not have (Japanese text inside `\text{}`) are set in
//! the typeface for Japanese text.
use crate::draw::{Letter, Mark};
use crate::error::Error;
use crate::font::{self, Library};
use suki::layout::{Content, Layout};
/// The typeset result
#[derive(Debug, Clone, PartialEq)]
pub struct Built {
/// The marks, with the origin at the left end, on the baseline
pub mark: Vec<Mark>,
pub width: f64,
/// The height above the baseline
pub height: f64,
/// The depth below the baseline
pub depth: f64,
}
/// Lays out math. size is the font size in points
pub fn build(source: &str, library: &Library, size: f64, display: bool) -> Result<Built, Error> {
let node = suki::parse(source)
.map_err(|reason| Error::new(format!("数式が読めません: ${source}$: {reason}"), 0))?;
let face = &library.face[library.role.math];
let placed = suki::layout::layout(&node, &face.font, 1.0, suki::Style::Auto, display);
let mut mark = Vec::new();
write(&placed, library, size, 0.0, 0.0, &mut mark);
Ok(Built {
mark,
width: placed.width * size,
height: placed.height * size,
depth: placed.depth * size,
})
}
/// Goes through the typeset result and turns it into marks
fn write(placed: &Layout, library: &Library, size: f64, x: f64, y: f64, out: &mut Vec<Mark>) {
match &placed.content {
Content::Glyph {text, glyph, size: em, stretch, ..} => {
let (face, found) = choose(library, *glyph, text);
if found == 0 {
return;
}
// The stretch factor is applied only to glyphs that a character maps to.
// Glyphs that no character maps to can stay as they are, because the font has
// variants of them in other sizes
let vertical = if text.is_empty() {1.0} else {*stretch};
out.push(Mark::Text {
face,
size: em * size,
stretch: vertical,
x: x * size,
y: y * size,
letter: vec![Letter {glyph: found, text: text.clone()}],
});
}
Content::Rule => out.push(Mark::Rule {
x: x * size,
y: (y - placed.height) * size,
width: placed.width * size,
height: (placed.height + placed.depth) * size,
}),
Content::Group(children) => {
for child in children {
write(&child.layout, library, size, x + child.x, y + child.y, out);
}
}
}
}
/// If the glyph is not in the math typeface, look for it in the typeface for Japanese text or
/// the typeface for body text
fn choose(library: &Library, glyph: u16, text: &str) -> (usize, u16) {
if glyph != 0 {
return (library.role.math, glyph);
}
let character = match text.chars().next() {
Some(character) => character,
None => return (library.role.math, 0),
};
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)
}