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.1
Copyright
Rollpie, Irohabook
Publisher
math
Published
2026-10-15
Size
106.1 KB
Downloads
24
Checksum
2dc69758bdc87c93f18bd70b188794e42b99dd6ed36955a65b7bdf8109030321
Dependencies
rust/[email protected]
rust/[email protected]

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