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