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]

stream.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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
//! 置いたものを PDF の描画命令にする
//!
//! 組み上げは左上を原点にして y を下向きに数えるが、PDF は左下を原点にして y を
//! 上向きに数える。その付け替えはここだけで行う。

use crate::draw::{Letter, Mark, Page};
use crate::font::{self, Library};
use crate::text::{hex_number, number};


/// 円を 4 本の曲線で描くときの、制御点までの割合
const CIRCLE_ARM: f64 = 0.5523;


/// 1 ページぶんの描画命令
pub fn render(page: &Page, library: &Library) -> Vec<u8> {
    let mut out = String::from("0 g 0 G\n");

    for one in join(&page.mark, library) {
        write_mark(&mut out, &one, page.height);
    }

    out.into_bytes()
}


/// 置いたもの 1 つを書く
fn write_mark(out: &mut String, mark: &Mark, height: f64) {
    match mark {
        Mark::Text {face, size, stretch, x, y, letter} => {
            out.push_str("BT\n");
            out.push_str(&format!("/F{face} {} Tf\n", number(*size)));
            out.push_str(&format!(
                "1 0 0 {} {} {} Tm\n",
                number(*stretch),
                number(*x),
                number(height - y)
            ));
            out.push('<');

            for one in letter {
                out.push_str(&hex_number(one.glyph));
            }

            out.push_str("> Tj\nET\n");
        }
        Mark::Rule {x, y, width, height: thickness} => {
            out.push_str(&format!(
                "{} {} {} {} re f\n",
                number(*x),
                number(height - y - thickness),
                number(*width),
                number(*thickness)
            ));
        }
        Mark::Stroke {point, width} => {
            if point.len() < 2 {
                return;
            }

            out.push_str(&format!("{} w\n", number(*width)));

            for (index, place) in point.iter().enumerate() {
                let mark = if index == 0 {"m"} else {"l"};
                out.push_str(&format!(
                    "{} {} {mark}\n",
                    number(place.0),
                    number(height - place.1)
                ));
            }

            out.push_str("S\n");
        }
        Mark::Fill {point} => {
            if point.len() < 3 {
                return;
            }

            for (index, place) in point.iter().enumerate() {
                let mark = if index == 0 {"m"} else {"l"};
                out.push_str(&format!(
                    "{} {} {mark}\n",
                    number(place.0),
                    number(height - place.1)
                ));
            }

            out.push_str("h f\n");
        }
        Mark::Circle {x, y, radius, width} => {
            write_circle(out, *x, height - y, *radius, *width);
        }
        Mark::Image {index, x, y, width, height: tall} => {
            out.push_str(&format!(
                "q {} 0 0 {} {} {} cm /Im{index} Do Q\n",
                number(*width),
                number(*tall),
                number(*x),
                number(height - y - tall)
            ));
        }
    }
}


/// 円を 4 本の 3 次曲線で描く
fn write_circle(out: &mut String, x: f64, y: f64, radius: f64, width: f64) {
    let arm = radius * CIRCLE_ARM;
    out.push_str(&format!("{} w\n", number(width)));
    out.push_str(&format!("{} {} m\n", number(x + radius), number(y)));

    let corner = [
        (x + radius, y + arm, x + arm, y + radius, x, y + radius),
        (x - arm, y + radius, x - radius, y + arm, x - radius, y),
        (x - radius, y - arm, x - arm, y - radius, x, y - radius),
        (x + arm, y - radius, x + radius, y - arm, x + radius, y),
    ];

    for (x1, y1, x2, y2, x3, y3) in corner {
        out.push_str(&format!(
            "{} {} {} {} {} {} c\n",
            number(x1),
            number(y1),
            number(x2),
            number(y2),
            number(x3),
            number(y3)
        ));
    }

    out.push_str("S\n");
}


/// 続けて置いた字をひとまとめにする。PDF の命令が短くなる
fn join(mark: &[Mark], library: &Library) -> Vec<Mark> {
    let mut out: Vec<Mark> = Vec::new();

    for one in mark {
        if let Mark::Text {face, size, stretch, x, y, letter} = one {
            if let Some(Mark::Text {
                face: last_face,
                size: last_size,
                stretch: last_stretch,
                x: last_x,
                y: last_y,
                letter: last_letter,
            }) = out.last_mut()
            {
                let same = last_face == face
                    && (*last_size - size).abs() < 1e-9
                    && (*last_stretch - stretch).abs() < 1e-9
                    && (*last_y - y).abs() < 1e-9;
                let end = *last_x + run_width(library, *last_face, *last_size, last_letter);

                if same && (end - x).abs() < 0.02 {
                    last_letter.extend(letter.iter().cloned());

                    continue;
                }
            }
        }

        out.push(one.clone());
    }

    out
}


/// 字の並びの送り幅
fn run_width(library: &Library, face: usize, size: f64, letter: &[Letter]) -> f64 {
    let found = match library.face.get(face) {
        Some(found) => found,
        None => return 0.0,
    };

    letter
        .iter()
        .map(|one| font::advance(found, one.glyph) * size)
        .sum()
}