Registry / RepositoryTypeScriptRustPython
Rollpie
ReadmeFiles
Versions
Info
Download
0.4.01085.8 KB2026-10-160.3.21087.6 KB2026-10-160.3.11087.5 KB2026-10-160.3.0117.8 KB2026-10-160.2.1106.1 KB2026-10-150.2.0105.5 KB2026-10-15
Version
0.3.0
Copyright
Rollpie, Irohabook
Publisher
math
Published
2026-10-16
Size
117.8 KB
Downloads
24
Checksum
91f9899559d38e059baa788310473f349f3d218d16f6afbd045fc9d087ba00c3
Dependencies
rust/[email protected]
rust/[email protected]

math.rs

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)
}