1mod image;
4mod paint;
5mod shape;
6mod text;
7
8use tiny_skia as sk;
9use typst_library::layout::{
10 Abs, Axes, Frame, FrameItem, FrameKind, GroupItem, Page, PagedDocument, Point, Size,
11 Transform,
12};
13use typst_library::visualize::{Color, Geometry, Paint};
14
15#[typst_macros::time(name = "render")]
20pub fn render(page: &Page, pixel_per_pt: f32) -> sk::Pixmap {
21 let size = page.frame.size();
22 let pxw = (pixel_per_pt * size.x.to_f32()).round().max(1.0) as u32;
23 let pxh = (pixel_per_pt * size.y.to_f32()).round().max(1.0) as u32;
24
25 let ts = sk::Transform::from_scale(pixel_per_pt, pixel_per_pt);
26 let state = State::new(size, ts, pixel_per_pt);
27
28 let mut canvas = sk::Pixmap::new(pxw, pxh).unwrap();
29
30 if let Some(fill) = page.fill_or_white() {
31 if let Paint::Solid(color) = fill {
32 canvas.fill(paint::to_sk_color(color));
33 } else {
34 let rect = Geometry::Rect(page.frame.size()).filled(fill);
35 shape::render_shape(&mut canvas, state, &rect);
36 }
37 }
38
39 render_frame(&mut canvas, state, &page.frame);
40
41 canvas
42}
43
44pub fn render_merged(
46 document: &PagedDocument,
47 pixel_per_pt: f32,
48 gap: Abs,
49 fill: Option<Color>,
50) -> sk::Pixmap {
51 let pixmaps: Vec<_> =
52 document.pages.iter().map(|page| render(page, pixel_per_pt)).collect();
53
54 let gap = (pixel_per_pt * gap.to_f32()).round() as u32;
55 let pxw = pixmaps.iter().map(sk::Pixmap::width).max().unwrap_or_default();
56 let pxh = pixmaps.iter().map(|pixmap| pixmap.height()).sum::<u32>()
57 + gap * pixmaps.len().saturating_sub(1) as u32;
58
59 let mut canvas = sk::Pixmap::new(pxw, pxh).unwrap();
60 if let Some(fill) = fill {
61 canvas.fill(paint::to_sk_color(fill));
62 }
63
64 let mut y = 0;
65 for pixmap in pixmaps {
66 canvas.draw_pixmap(
67 0,
68 y as i32,
69 pixmap.as_ref(),
70 &sk::PixmapPaint::default(),
71 sk::Transform::identity(),
72 None,
73 );
74
75 y += pixmap.height() + gap;
76 }
77
78 canvas
79}
80
81#[derive(Clone, Copy, Default)]
83struct State<'a> {
84 transform: sk::Transform,
86 container_transform: sk::Transform,
88 mask: Option<&'a sk::Mask>,
90 pixel_per_pt: f32,
92 size: Size,
94}
95
96impl State<'_> {
97 fn new(size: Size, transform: sk::Transform, pixel_per_pt: f32) -> Self {
98 Self {
99 size,
100 transform,
101 container_transform: transform,
102 pixel_per_pt,
103 ..Default::default()
104 }
105 }
106
107 fn pre_translate(self, pos: Point) -> Self {
109 Self {
110 transform: self.transform.pre_translate(pos.x.to_f32(), pos.y.to_f32()),
111 ..self
112 }
113 }
114
115 fn pre_scale(self, scale: Axes<Abs>) -> Self {
116 Self {
117 transform: self.transform.pre_scale(scale.x.to_f32(), scale.y.to_f32()),
118 ..self
119 }
120 }
121
122 fn pre_concat(self, transform: sk::Transform) -> Self {
124 Self {
125 transform: self.transform.pre_concat(transform),
126 ..self
127 }
128 }
129
130 fn with_mask(self, mask: Option<&sk::Mask>) -> State<'_> {
132 if mask.is_some() {
134 State { mask, ..self }
135 } else {
136 State { mask: None, ..self }
137 }
138 }
139
140 fn with_size(self, size: Size) -> Self {
142 Self { size, ..self }
143 }
144
145 fn pre_concat_container(self, transform: sk::Transform) -> Self {
147 Self {
148 container_transform: self.container_transform.pre_concat(transform),
149 ..self
150 }
151 }
152}
153
154fn render_frame(canvas: &mut sk::Pixmap, state: State, frame: &Frame) {
156 for (pos, item) in frame.items() {
157 match item {
158 FrameItem::Group(group) => {
159 render_group(canvas, state, *pos, group);
160 }
161 FrameItem::Text(text) => {
162 text::render_text(canvas, state.pre_translate(*pos), text);
163 }
164 FrameItem::Shape(shape, _) => {
165 shape::render_shape(canvas, state.pre_translate(*pos), shape);
166 }
167 FrameItem::Image(image, size, _) => {
168 image::render_image(canvas, state.pre_translate(*pos), image, *size);
169 }
170 FrameItem::Link(_, _) => {}
171 FrameItem::Tag(_) => {}
172 }
173 }
174}
175
176fn render_group(canvas: &mut sk::Pixmap, state: State, pos: Point, group: &GroupItem) {
178 let sk_transform = to_sk_transform(&group.transform);
179 let state = match group.frame.kind() {
180 FrameKind::Soft => state.pre_translate(pos).pre_concat(sk_transform),
181 FrameKind::Hard => state
182 .pre_translate(pos)
183 .pre_concat(sk_transform)
184 .pre_concat_container(
185 state
186 .transform
187 .post_concat(state.container_transform.invert().unwrap()),
188 )
189 .pre_concat_container(to_sk_transform(&Transform::translate(pos.x, pos.y)))
190 .pre_concat_container(sk_transform)
191 .with_size(group.frame.size()),
192 };
193
194 let mut mask = state.mask;
195 let storage;
196 if let Some(clip_curve) = group.clip.as_ref() {
197 if let Some(path) = shape::convert_curve(clip_curve)
198 .and_then(|path| path.transform(state.transform))
199 {
200 if let Some(mask) = mask {
201 let mut mask = mask.clone();
202 mask.intersect_path(
203 &path,
204 sk::FillRule::default(),
205 false,
206 sk::Transform::default(),
207 );
208 storage = mask;
209 } else {
210 let pxw = canvas.width();
211 let pxh = canvas.height();
212 let Some(mut mask) = sk::Mask::new(pxw, pxh) else {
213 return;
216 };
217
218 mask.fill_path(
219 &path,
220 sk::FillRule::default(),
221 false,
222 sk::Transform::default(),
223 );
224 storage = mask;
225 };
226
227 mask = Some(&storage);
228 }
229 }
230
231 render_frame(canvas, state.with_mask(mask), &group.frame);
232}
233
234fn to_sk_transform(transform: &Transform) -> sk::Transform {
235 let Transform { sx, ky, kx, sy, tx, ty } = *transform;
236 sk::Transform::from_row(
237 sx.get() as _,
238 ky.get() as _,
239 kx.get() as _,
240 sy.get() as _,
241 tx.to_f32(),
242 ty.to_f32(),
243 )
244}
245
246trait AbsExt {
248 fn to_f32(self) -> f32;
250}
251
252impl AbsExt for Abs {
253 fn to_f32(self) -> f32 {
254 self.to_pt() as f32
255 }
256}