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.4.0
Copyright
Rollpie, Irohabook
Publisher
math
Published
2026-10-16
Size
1085.8 KB
Downloads
19
Checksum
df10d3447b809454aba0a9fd252d07428b819520aff22324461ba2dcbf1e43fb
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
//! Turns placed marks into PDF drawing operators
//!
//! Typesetting puts the origin at the top left and counts y downward, but PDF puts the origin
//! at the bottom left and counts y upward. The conversion between them is done only here.

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


/// For a circle drawn with 4 curves, the distance to the control points as a ratio to the radius
const CIRCLE_ARM: f64 = 0.5523;


/// The drawing operators for one page
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()
}


/// Writes one placed mark
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)
            ));
        }
    }
}


/// Draws a circle with 4 cubic curves
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");
}


/// Joins letters placed one after another into one run. This makes the PDF operators shorter
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
}


/// The total advance of a run of letters
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()
}