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.2.0
Copyright
Rollpie, Irohabook
Publisher
math
Published
2026-10-15
Size
105.5 KB
Downloads
24
Checksum
f438a48f6f2fc61d892f930eb4fb56fb5a8e302e733a8c3a6283d0ff7350cd08
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
//! 数式を suki で組んで、ページに置くものに直す
//!
//! suki は位置と寸法を em で返す。ここでは字の大きさを掛けて点に直し、
//! 字形をそのまま PDF へ渡す。字形の番号はフォントのものなので、埋め込みと合う。
//!
//! 数式の書体が持たない字(`\text{}` の中の日本語)は和文の書体で置く。

use crate::draw::{Letter, Mark};
use crate::error::Error;
use crate::font::{self, Library};
use suki::layout::{Content, Layout};


/// 組み上がったもの
#[derive(Debug, Clone, PartialEq)]
pub struct Built {
    /// 左端とベースラインを原点として置いたもの
    pub mark: Vec<Mark>,
    pub width: f64,
    /// ベースラインから上
    pub height: f64,
    /// ベースラインから下
    pub depth: f64,
}


/// 数式を組む。size は字の大きさ(点)
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,
    })
}


/// 組み上がったものをたどって、置くものに直す
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;
            }

            // 伸ばす率は、文字の当たっている字形にだけ掛ける。
            // 当たっていない字形は、フォントが大きさ違いを持っているのでそのままでよい
            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);
            }
        }
    }
}


/// 字形が数式の書体にないときは、和文か地の文の書体から探す
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)
}