1use pdf_writer::types::{ActionType, AnnotationType, FunctionShadingType};
5use pdf_writer::{Filter, Finish, Pdf, Rect as PdfRect, Ref, Str};
6use zenith_core::{AssetProvider, FontProvider};
7use zenith_scene::Scene;
8
9use super::content::{
10 ALPHA_PREFIX, FONT_PREFIX, IMAGE_PREFIX, LinkAnnot, PageResources, SHADING_PREFIX, name,
11 translate,
12};
13use super::font::{self, FontPlan};
14use super::gradient::AxialGradient;
15
16#[derive(Clone, Copy)]
18pub struct PdfOptions {
19 pub subset: bool,
23}
24
25impl Default for PdfOptions {
26 fn default() -> Self {
27 Self { subset: true }
28 }
29}
30
31#[must_use]
46pub fn render_pdf(scene: &Scene, fonts: &dyn FontProvider, assets: &dyn AssetProvider) -> Vec<u8> {
47 render_pdf_multi(std::slice::from_ref(scene), fonts, assets)
48}
49
50#[must_use]
52pub fn render_pdf_with(
53 scene: &Scene,
54 fonts: &dyn FontProvider,
55 assets: &dyn AssetProvider,
56 options: PdfOptions,
57) -> Vec<u8> {
58 render_pdf_multi_with(std::slice::from_ref(scene), fonts, assets, options)
59}
60
61#[must_use]
74pub fn render_pdf_multi(
75 scenes: &[Scene],
76 fonts: &dyn FontProvider,
77 assets: &dyn AssetProvider,
78) -> Vec<u8> {
79 render_pdf_multi_with(scenes, fonts, assets, PdfOptions::default())
80}
81
82#[must_use]
91pub fn render_pdf_multi_with(
92 scenes: &[Scene],
93 fonts: &dyn FontProvider,
94 assets: &dyn AssetProvider,
95 options: PdfOptions,
96) -> Vec<u8> {
97 let mut pdf = Pdf::new();
98
99 let catalog_id = Ref::new(1);
100 let page_tree_id = Ref::new(2);
101
102 let usage = font::collect_usage(scenes);
105 let font_plan = font::build_plan(&usage, fonts, options.subset);
106 let font_base: i32 = 3;
107 let mut next: i32 = font_base + (font_plan.fonts.len() as i32) * font::REFS_PER_FONT;
108 let mut alloc = || {
109 let r = Ref::new(next);
110 next += 1;
111 r
112 };
113
114 let mut pages: Vec<PreparedPage<'_>> = Vec::with_capacity(scenes.len());
117 for scene in scenes {
118 pages.push(prepare_page(scene, fonts, assets, &font_plan, &mut alloc));
119 }
120
121 pdf.catalog(catalog_id).pages(page_tree_id);
123 pdf.pages(page_tree_id)
124 .kids(pages.iter().map(|p| p.page_id))
125 .count(pages.len() as i32);
126
127 for (idx, font) in font_plan.fonts.iter().enumerate() {
129 let refs = font::font_refs_at(font_base, idx);
130 font::write_font(&mut pdf, font, &refs);
131 }
132
133 for prepared in pages {
135 write_prepared_page(&mut pdf, page_tree_id, prepared);
136 }
137
138 pdf.finish()
139}
140
141struct PreparedPage<'a> {
145 page_id: Ref,
146 content_id: Ref,
147 content: Vec<u8>,
148 scene: &'a Scene,
149 res: PageResources,
150 annot_ids: Vec<Ref>,
152 alpha_ids: Vec<Ref>,
153 gradient_refs: Vec<GradientRefs>,
154 image_refs: Vec<ImageRefs>,
155}
156
157fn prepare_page<'a>(
162 scene: &'a Scene,
163 fonts: &dyn FontProvider,
164 assets: &dyn AssetProvider,
165 font_plan: &FontPlan,
166 alloc: &mut impl FnMut() -> Ref,
167) -> PreparedPage<'a> {
168 let page_id = alloc();
169 let content_id = alloc();
170
171 let (content, res) = translate(scene, fonts, assets, font_plan);
173
174 let annot_ids: Vec<Ref> = res.links.iter().map(|_| alloc()).collect();
177
178 let alpha_ids: Vec<Ref> = res.alphas.iter().map(|_| alloc()).collect();
182 let gradient_refs: Vec<GradientRefs> = res
183 .gradients
184 .iter()
185 .map(|g| {
186 let shading = alloc();
187 let function = alloc();
188 let seg_count = g.stops.len().saturating_sub(1);
192 let sub_functions = if g.stops.len() > 2 {
193 (0..seg_count).map(|_| alloc()).collect()
194 } else {
195 Vec::new()
196 };
197 GradientRefs {
198 shading,
199 function,
200 sub_functions,
201 }
202 })
203 .collect();
204 let image_refs: Vec<ImageRefs> = res
205 .images
206 .iter()
207 .map(|img| ImageRefs {
208 image: alloc(),
209 smask: if img.alpha_flate.is_some() {
210 Some(alloc())
211 } else {
212 None
213 },
214 })
215 .collect();
216
217 PreparedPage {
218 page_id,
219 content_id,
220 content: content.finish().into_vec(),
221 scene,
222 res,
223 annot_ids,
224 alpha_ids,
225 gradient_refs,
226 image_refs,
227 }
228}
229
230fn write_prepared_page(pdf: &mut Pdf, page_tree_id: Ref, prepared: PreparedPage<'_>) {
234 let PreparedPage {
235 page_id,
236 content_id,
237 content,
238 scene,
239 res,
240 annot_ids,
241 alpha_ids,
242 gradient_refs,
243 image_refs,
244 } = prepared;
245
246 write_page(
248 pdf,
249 PageWrite {
250 page_id,
251 page_tree_id,
252 content_id,
253 scene,
254 res: &res,
255 annot_ids: &annot_ids,
256 alpha_ids: &alpha_ids,
257 gradient_refs: &gradient_refs,
258 image_refs: &image_refs,
259 },
260 );
261
262 pdf.stream(content_id, &content);
264
265 write_link_annotations(pdf, scene, &res.links, &annot_ids);
267
268 write_alpha_states(pdf, &res, &alpha_ids);
270 write_gradients(pdf, &res, &gradient_refs);
271 write_images(pdf, &res, &image_refs);
272}
273
274fn write_link_annotations(pdf: &mut Pdf, scene: &Scene, links: &[LinkAnnot], annot_ids: &[Ref]) {
279 let h = scene.height as f32;
280 for (link, id) in links.iter().zip(annot_ids) {
281 let x0 = link.x0 as f32;
282 let x1 = link.x1 as f32;
283 let y_top = h - link.y0 as f32;
285 let y_bottom = h - link.y1 as f32;
286 let mut annot = pdf.annotation(*id);
287 annot.subtype(AnnotationType::Link);
288 annot.rect(PdfRect::new(x0, y_bottom, x1, y_top));
289 annot.border(0.0, 0.0, 0.0, None);
291 annot
292 .action()
293 .action_type(ActionType::Uri)
294 .uri(Str(link.url.as_bytes()));
295 annot.finish();
296 }
297}
298
299struct GradientRefs {
302 shading: Ref,
303 function: Ref,
304 sub_functions: Vec<Ref>,
307}
308
309struct ImageRefs {
312 image: Ref,
313 smask: Option<Ref>,
314}
315
316#[derive(Clone, Copy)]
321struct PageWrite<'a> {
322 page_id: Ref,
323 page_tree_id: Ref,
324 content_id: Ref,
325 scene: &'a Scene,
326 res: &'a PageResources,
327 annot_ids: &'a [Ref],
328 alpha_ids: &'a [Ref],
329 gradient_refs: &'a [GradientRefs],
330 image_refs: &'a [ImageRefs],
331}
332
333fn write_page(pdf: &mut Pdf, ctx: PageWrite<'_>) {
334 let PageWrite {
335 page_id,
336 page_tree_id,
337 content_id,
338 scene,
339 res,
340 annot_ids,
341 alpha_ids,
342 gradient_refs,
343 image_refs,
344 } = ctx;
345 let w = scene.width as f32;
346 let h = scene.height as f32;
347 let media = PdfRect::new(0.0, 0.0, w, h);
348
349 let mut page = pdf.page(page_id);
350 page.parent(page_tree_id);
351 page.media_box(media);
352
353 match scene.trim {
359 Some(t) => {
360 let x0 = t.x as f32;
361 let x1 = (t.x + t.w) as f32;
362 let y0 = (scene.height - (t.y + t.h)) as f32;
363 let y1 = (scene.height - t.y) as f32;
364 page.trim_box(PdfRect::new(x0, y0, x1, y1));
365 page.bleed_box(media);
366 page.crop_box(media);
367 }
368 None => {
369 page.trim_box(media);
370 page.bleed_box(media);
371 page.crop_box(media);
372 }
373 }
374
375 page.contents(content_id);
376
377 if !annot_ids.is_empty() {
380 page.annotations(annot_ids.iter().copied());
381 }
382
383 let mut resources = page.resources();
386 if !res.font_indices.is_empty() {
387 let mut fonts = resources.fonts();
388 for &idx in &res.font_indices {
389 let nm = name(FONT_PREFIX, idx);
390 fonts.pair(nm.as_name(), font::font_refs_at(3, idx).type0_ref());
391 }
392 fonts.finish();
393 }
394 if !res.alphas.is_empty() {
395 let mut gs = resources.ext_g_states();
396 for (i, r) in alpha_ids.iter().enumerate() {
397 let nm = name(ALPHA_PREFIX, i);
398 gs.pair(nm.as_name(), *r);
399 }
400 gs.finish();
401 }
402 if !res.gradients.is_empty() {
403 let mut sh = resources.shadings();
404 for (i, gr) in gradient_refs.iter().enumerate() {
405 let nm = name(SHADING_PREFIX, i);
406 sh.pair(nm.as_name(), gr.shading);
407 }
408 sh.finish();
409 }
410 if !res.images.is_empty() {
411 let mut xo = resources.x_objects();
412 for (i, ir) in image_refs.iter().enumerate() {
413 let nm = name(IMAGE_PREFIX, i);
414 xo.pair(nm.as_name(), ir.image);
415 }
416 xo.finish();
417 }
418 resources.finish();
419 page.finish();
420}
421
422fn write_alpha_states(pdf: &mut Pdf, res: &PageResources, alpha_ids: &[Ref]) {
425 for (a, r) in res.alphas.iter().zip(alpha_ids) {
426 let factor = f32::from(*a) / 255.0;
427 let mut gs = pdf.ext_graphics(*r);
428 gs.non_stroking_alpha(factor);
429 gs.stroking_alpha(factor);
430 gs.finish();
431 }
432}
433
434fn write_gradients(pdf: &mut Pdf, res: &PageResources, refs: &[GradientRefs]) {
438 for (g, gr) in res.gradients.iter().zip(refs) {
439 write_gradient_function(pdf, gr, g);
440
441 let mut shading = pdf.function_shading(gr.shading);
442 shading.shading_type(FunctionShadingType::Axial);
443 shading.color_space().device_rgb();
444 shading.coords(g.coords);
445 shading.function(gr.function);
446 shading.extend([true, true]);
449 shading.finish();
450 }
451}
452
453fn write_gradient_function(pdf: &mut Pdf, gr: &GradientRefs, g: &AxialGradient) {
458 if g.stops.len() <= 2 {
460 let c0 = g.stops.first().map(|s| s.1).unwrap_or([0.0, 0.0, 0.0]);
461 let c1 = g.stops.get(1).map(|s| s.1).unwrap_or(c0);
462 write_linear_segment(pdf, gr.function, c0, c1);
463 return;
464 }
465
466 for (k, sub) in gr.sub_functions.iter().enumerate() {
468 let c0 = g.stops.get(k).map(|s| s.1).unwrap_or([0.0, 0.0, 0.0]);
469 let c1 = g.stops.get(k + 1).map(|s| s.1).unwrap_or(c0);
470 write_linear_segment(pdf, *sub, c0, c1);
471 }
472
473 let last = g.stops.len() - 1;
476 let bounds: Vec<f32> = g
477 .stops
478 .get(1..last)
479 .unwrap_or(&[])
480 .iter()
481 .map(|s| s.0)
482 .collect();
483 let mut encode: Vec<f32> = Vec::with_capacity(gr.sub_functions.len() * 2);
484 for _ in &gr.sub_functions {
485 encode.push(0.0);
486 encode.push(1.0);
487 }
488
489 let mut stitch = pdf.stitching_function(gr.function);
490 stitch.domain([0.0, 1.0]);
491 stitch.range([0.0, 1.0, 0.0, 1.0, 0.0, 1.0]);
492 stitch.functions(gr.sub_functions.iter().copied());
493 stitch.bounds(bounds);
494 stitch.encode(encode);
495 stitch.finish();
496}
497
498fn write_linear_segment(pdf: &mut Pdf, id: Ref, c0: [f32; 3], c1: [f32; 3]) {
501 let mut f = pdf.exponential_function(id);
502 f.domain([0.0, 1.0]);
503 f.range([0.0, 1.0, 0.0, 1.0, 0.0, 1.0]);
504 f.c0(c0);
505 f.c1(c1);
506 f.n(1.0);
507 f.finish();
508}
509
510fn write_images(pdf: &mut Pdf, res: &PageResources, refs: &[ImageRefs]) {
513 for (img, ir) in res.images.iter().zip(refs) {
514 let w = img.width as i32;
515 let h = img.height as i32;
516
517 let mut xobj = pdf.image_xobject(ir.image, &img.rgb_flate);
518 xobj.filter(Filter::FlateDecode);
519 xobj.width(w);
520 xobj.height(h);
521 xobj.color_space().device_rgb();
522 xobj.bits_per_component(8);
523 if let Some(smask) = ir.smask {
524 xobj.s_mask(smask);
525 }
526 xobj.finish();
527
528 if let (Some(smask), Some(alpha)) = (ir.smask, &img.alpha_flate) {
529 let mut sm = pdf.image_xobject(smask, alpha);
530 sm.filter(Filter::FlateDecode);
531 sm.width(w);
532 sm.height(h);
533 sm.color_space().device_gray();
534 sm.bits_per_component(8);
535 sm.finish();
536 }
537 }
538}