1pub mod elements;
2pub mod flex;
3pub mod fonts;
4pub mod image;
5pub mod serde_elements;
6pub mod test_utils;
7mod text;
8pub mod utils;
9
10use chrono::{Datelike, Timelike, Utc};
11use elements::padding::Padding;
12use fonts::Font;
13use pdf_writer::{Content, Date, Name, Rect, Ref, TextStr};
14use serde::{Deserialize, Serialize};
15use uuid::Uuid;
16use xmp_writer::{DateTime, LangId, Timezone, XmpWriter};
17
18pub use crate::text::TextPiecesCache;
19
20pub type Color = u32;
21
22#[derive(Copy, Clone, Serialize, Deserialize)]
27pub enum LineCapStyle {
28 Butt,
31
32 Round,
35
36 ProjectingSquare,
40}
41
42impl Into<pdf_writer::types::LineCapStyle> for LineCapStyle {
43 fn into(self) -> pdf_writer::types::LineCapStyle {
44 match self {
45 LineCapStyle::Butt => pdf_writer::types::LineCapStyle::ButtCap,
46 LineCapStyle::Round => pdf_writer::types::LineCapStyle::RoundCap,
47 LineCapStyle::ProjectingSquare => pdf_writer::types::LineCapStyle::ProjectingSquareCap,
48 }
49 }
50}
51
52#[derive(Copy, Clone, Serialize, Deserialize)]
57pub struct LineDashPattern {
58 pub offset: u16,
61
62 pub dashes: [u16; 2],
66}
67
68#[derive(Copy, Clone, Serialize, Deserialize)]
69pub struct LineStyle {
70 pub thickness: f32,
71 pub color: Color,
72 pub dash_pattern: Option<LineDashPattern>,
73 pub cap_style: LineCapStyle,
74}
75
76#[derive(Copy, Clone, Debug, PartialEq, Eq)]
77#[non_exhaustive]
78pub enum LinkTarget<'a> {
79 Uri(&'a str),
80}
81
82pub struct Layer {
83 pub content: Content,
84 pub graphics_state_restore_required: bool,
85}
86
87pub struct Page {
88 pub annotations: Vec<Ref>,
89 pub ext_g_states: Vec<Ref>, pub x_objects: Vec<Ref>,
91 pub layers: Vec<Layer>,
92 pub size: (f32, f32),
93}
94
95impl Page {
96 pub fn add_ext_g_state(&mut self, resource: Ref) -> usize {
97 self.ext_g_states.push(resource);
98 self.ext_g_states.len() - 1
99 }
100
101 pub fn add_x_object(&mut self, resource: Ref) -> String {
102 self.x_objects.push(resource);
103 (self.x_objects.len() - 1).to_string()
104 }
105}
106
107#[derive(Clone)]
109pub struct Metadata {
110 pub title: String,
111 pub language: String,
113 pub keywords: Option<String>,
114 pub producer: Option<String>,
115 pub creation_date: chrono::DateTime<Utc>,
116 pub identifier: String,
118}
119
120impl Metadata {
121 pub fn new() -> Self {
122 Metadata {
123 title: "".to_string(),
124 language: "en".to_string(),
125 keywords: None,
126 producer: None,
127 creation_date: Utc::now(),
128 identifier: Uuid::new_v4().to_string(),
129 }
130 }
131
132 fn fixed() -> Self {
133 Metadata {
134 title: "".to_string(),
135 language: "en".to_string(),
136 keywords: None,
137 producer: None,
138 creation_date: chrono::DateTime::UNIX_EPOCH,
139 identifier: "00000000-0000-0000-0000-000000000000".to_string(),
140 }
141 }
142}
143
144pub struct Pdf {
145 pub alloc: Ref,
146 pub pdf: pdf_writer::Pdf,
147 pub pages: Vec<Page>,
148 pub fonts: Vec<Ref>,
149 pub metadata: Metadata,
150 truetype_fonts: Vec<fonts::truetype::TruetypeFontState>,
151}
152
153impl Pdf {
154 pub fn new(metadata: Metadata) -> Self {
155 let pdf = pdf_writer::Pdf::new();
156
157 Pdf {
158 alloc: pdf_writer::Ref::new(1),
159 pdf,
160 pages: Vec::new(),
161 fonts: Vec::new(),
162 metadata,
163 truetype_fonts: Vec::new(),
164 }
165 }
166
167 pub fn alloc(&mut self) -> Ref {
168 self.alloc.bump()
169 }
170
171 pub fn add_page(&mut self, size: (f32, f32)) -> Location {
172 self.pages.push(Page {
173 ext_g_states: Vec::new(),
174 x_objects: Vec::new(),
175 annotations: Vec::new(),
176 layers: vec![Layer {
177 content: Content::new(),
178 graphics_state_restore_required: false,
179 }],
180 size,
181 });
182
183 Location {
184 page_idx: self.pages.len() - 1,
185 layer_idx: 0,
186 pos: (0., size.1),
187 scale_factor: 1.,
188 }
189 }
190
191 pub fn add_element(&mut self, page_size: (f32, f32), element: impl Element) {
194 let text_pieces_cache = TextPiecesCache::new();
195
196 self.add_element_with_text_pieces_cache(page_size, &text_pieces_cache, element);
197 }
198
199 pub fn add_element_with_text_pieces_cache(
202 &mut self,
203 page_size: (f32, f32),
204 text_pieces_cache: &TextPiecesCache,
205 element: impl Element,
206 ) {
207 let mut page_idx = self.pages.len() as u32;
208
209 let location = self.add_page((page_size.0, page_size.1));
210
211 let entry_page = page_idx;
212
213 let do_break = &mut |pdf: &mut Pdf, location_idx, _height| {
214 while page_idx <= entry_page + location_idx {
215 pdf.add_page((page_size.0, page_size.1));
216 page_idx += 1;
217 }
218
219 Location {
220 page_idx: (entry_page + location_idx + 1) as usize,
221 layer_idx: 0,
222 pos: (0., page_size.1),
223 scale_factor: 1.,
224 }
225 };
226
227 let ctx = DrawCtx {
228 pdf: self,
229 text_pieces_cache,
230 width: WidthConstraint {
231 max: page_size.0,
232 expand: true,
233 },
234 location,
235
236 first_height: page_size.1,
237 preferred_height: None,
238
239 breakable: Some(BreakableDraw {
240 full_height: page_size.1,
241 preferred_height_break_count: 0,
242 do_break,
243 }),
244 };
245
246 element.draw(ctx);
247 }
248
249 pub fn finish(mut self) -> Vec<u8> {
250 let catalog_ref = self.alloc();
251 let page_tree_ref = self.alloc();
252
253 {
255 let mut writer = XmpWriter::new();
259
260 let identifier: Vec<u8> = self.metadata.identifier.clone().into();
264 self.pdf.set_file_id((identifier.clone(), identifier));
265
266 {
267 let id = self.alloc();
268 let mut document_info = self.pdf.document_info(id);
269 document_info.title(TextStr(self.metadata.title.clone().as_str()));
270 if let Some(keywords) = &self.metadata.keywords {
271 document_info.keywords(TextStr(keywords));
272 }
273 if let Some(producer) = &self.metadata.producer {
274 document_info.producer(TextStr(producer));
275 }
276 document_info.creation_date(
277 Date::new(self.metadata.creation_date.year() as u16)
278 .month(self.metadata.creation_date.month() as u8)
279 .day(self.metadata.creation_date.day() as u8)
280 .hour(self.metadata.creation_date.hour() as u8)
281 .minute(self.metadata.creation_date.minute() as u8)
282 .second(self.metadata.creation_date.second() as u8),
283 );
284 }
285 writer.title([(None, self.metadata.title.as_str())]);
286
287 writer.language([LangId(&self.metadata.language.as_str())]);
288
289 if let Some(ref keywords) = self.metadata.keywords {
290 writer.pdf_keywords(keywords);
291 }
292
293 if let Some(producer) = &self.metadata.producer {
294 writer.producer(producer);
295 }
296
297 writer.create_date(DateTime::new(
298 self.metadata.creation_date.year() as u16,
299 self.metadata.creation_date.month() as u8,
300 self.metadata.creation_date.day() as u8,
301 self.metadata.creation_date.hour() as u8,
302 self.metadata.creation_date.minute() as u8,
303 self.metadata.creation_date.second() as u8,
304 Timezone::Utc,
305 ));
306
307 writer.xmp_identifier([self.metadata.identifier.as_str()]);
308
309 writer.pdfa_part(2);
310 writer.pdfa_conformance("U");
312 writer.pdf_version("1.7");
313
314 let finished = writer.finish(None);
315
316 let id = self.alloc();
317 let icc_profile_ref = self.alloc();
318 self.pdf.metadata(id, finished.as_bytes());
319 self.pdf
320 .icc_profile(
321 icc_profile_ref,
322 include_bytes!("../assets/icc_profiles/sRGB-v4.icc"),
323 )
324 .n(3);
326 let mut catalog = self.pdf.catalog(catalog_ref);
327 catalog.metadata(id).pages(page_tree_ref);
328 catalog
334 .output_intents()
335 .push()
336 .subtype(pdf_writer::types::OutputIntentSubtype::PDFA)
337 .dest_output_profile(icc_profile_ref)
338 .output_condition_identifier(TextStr("sRGB-v4"));
339 }
340
341 for mut truetype_font in self.truetype_fonts {
342 truetype_font.finish(&mut self.pdf, &mut self.alloc);
343 }
344
345 let pages = self
346 .pages
347 .iter()
348 .scan(self.alloc, |state, _| Some(state.bump()));
349
350 self.pdf
351 .pages(page_tree_ref)
352 .kids(pages)
353 .count(self.pages.len() as i32);
354
355 let mut page_alloc = self.alloc;
356 self.alloc = Ref::new(self.alloc.get() + self.pages.len() as i32);
357
358 for page in self.pages {
359 let mut page_writer = self.pdf.page(page_alloc.bump());
360
361 page_writer
362 .parent(page_tree_ref)
363 .media_box(Rect::new(
364 0.,
365 0.,
366 (page.size.0 * 72. / 25.4) as f32,
367 (page.size.1 * 72. / 25.4) as f32,
368 ))
369 .contents_array(
370 page.layers
371 .iter()
372 .scan(self.alloc, |state, _| Some(state.bump())),
373 );
374
375 if !page.annotations.is_empty() {
376 page_writer.annotations(page.annotations);
377 }
378
379 let mut resources = page_writer.resources();
380
381 let mut ext_g_states = resources.ext_g_states();
382 for (i, ext_g_state) in page.ext_g_states.iter().enumerate() {
383 ext_g_states.pair(Name(format!("{i}").as_bytes()), ext_g_state);
384 }
385 drop(ext_g_states);
386
387 if !page.x_objects.is_empty() {
388 let mut x_objects = resources.x_objects();
389 for (i, x_object) in page.x_objects.iter().enumerate() {
390 x_objects.pair(Name(format!("{i}").as_bytes()), x_object);
391 }
392 }
393
394 let mut fonts = resources.fonts();
395
396 for (i, &font) in self.fonts.iter().enumerate() {
397 fonts.pair(Name(&format!("F{}", i).as_bytes()), font);
399 }
400
401 drop(fonts);
402 drop(resources);
403 drop(page_writer);
404
405 for mut layer in page.layers {
406 if layer.graphics_state_restore_required {
407 layer.content.restore_state();
408 }
409
410 self.pdf.stream(self.alloc.bump(), &layer.content.finish());
412 }
413 }
414
415 self.pdf.finish()
416 }
417}
418
419#[derive(Clone, Debug)]
425pub struct Location {
426 pub page_idx: usize,
427 pub layer_idx: usize,
428 pub pos: (f32, f32),
429 pub scale_factor: f32,
430}
431
432impl Location {
433 pub fn layer<'a>(&self, pdf: &'a mut Pdf) -> &'a mut Content {
434 &mut pdf.pages[self.page_idx].layers[self.layer_idx].content
435 }
436
437 pub fn next_layer(&self, pdf: &mut Pdf) -> Location {
438 let page = &mut pdf.pages[self.page_idx];
439
440 let mut content = Content::new();
441
442 let graphics_state_restore_required = if self.scale_factor != 1. {
443 content
444 .save_state()
445 .transform(utils::scale(self.scale_factor));
446 true
447 } else {
448 false
449 };
450
451 page.layers.push(Layer {
455 content,
456 graphics_state_restore_required,
457 });
458
459 Location {
460 layer_idx: page.layers.len() - 1,
461 ..*self
462 }
463 }
464}
465
466#[derive(Clone, Copy, Debug, PartialEq)]
467pub struct WidthConstraint {
468 pub max: f32,
469 pub expand: bool,
470}
471
472impl WidthConstraint {
473 pub fn constrain(&self, width: f32) -> f32 {
474 if self.expand {
475 self.max
476 } else {
477 width.min(self.max)
478 }
479 }
480
481 pub fn max(&self, width: f32) -> f32 {
482 if self.expand {
483 width.max(self.max)
484 } else {
485 width
486 }
487 }
488}
489
490pub type Pos = (f32, f32);
491pub type Size = (f32, f32);
492
493pub type Break<'a> = &'a mut dyn FnMut(&mut Pdf, u32, Option<f32>) -> Location;
502
503#[derive(Clone, Copy, PartialEq, Eq, Debug)]
504pub enum FirstLocationUsage {
505 NoneHeight,
509 WillUse,
510 WillSkip,
511}
512
513pub struct FirstLocationUsageCtx<'a> {
514 pub text_pieces_cache: &'a TextPiecesCache,
515 pub width: WidthConstraint,
516 pub first_height: f32,
517
518 pub full_height: f32,
525}
526
527impl<'a> FirstLocationUsageCtx<'a> {
528 pub fn break_appropriate_for_min_height(&self, height: f32) -> bool {
529 height > self.first_height && self.full_height > self.first_height
530 }
531}
532
533pub struct BreakableMeasure<'a> {
534 pub full_height: f32,
535 pub break_count: &'a mut u32,
536
537 pub extra_location_min_height: &'a mut Option<f32>,
548}
549
550pub struct MeasureCtx<'a> {
551 pub text_pieces_cache: &'a TextPiecesCache,
552 pub width: WidthConstraint,
553 pub first_height: f32,
554 pub breakable: Option<BreakableMeasure<'a>>,
555}
556
557impl<'a> MeasureCtx<'a> {
558 pub fn break_if_appropriate_for_min_height(&mut self, height: f32) -> bool {
559 if let Some(ref mut breakable) = self.breakable {
560 if height > self.first_height && breakable.full_height > self.first_height {
561 *breakable.break_count = 1;
562 return true;
563 }
564 }
565
566 false
567 }
568}
569
570pub struct BreakableDraw<'a> {
571 pub full_height: f32,
572 pub preferred_height_break_count: u32,
573 pub do_break: Break<'a>,
574}
575
576pub struct DrawCtx<'a, 'b> {
577 pub pdf: &'a mut Pdf,
578 pub text_pieces_cache: &'a TextPiecesCache,
579 pub location: Location,
580
581 pub width: WidthConstraint,
582 pub first_height: f32,
583
584 pub preferred_height: Option<f32>,
585
586 pub breakable: Option<BreakableDraw<'b>>,
587}
588
589impl<'a, 'b> DrawCtx<'a, 'b> {
590 pub fn break_if_appropriate_for_min_height(&mut self, height: f32) -> bool {
591 if let Some(ref mut breakable) = self.breakable {
592 if height > self.first_height && breakable.full_height > self.first_height {
593 self.location = (breakable.do_break)(self.pdf, 0, None);
596 return true;
597 }
598 }
599
600 false
601 }
602}
603
604#[derive(Copy, Clone, Debug, PartialEq)]
605pub struct ElementSize {
606 pub width: Option<f32>,
607
608 pub height: Option<f32>,
613}
614
615impl ElementSize {
616 pub fn new(width: Option<f32>, height: Option<f32>) -> Self {
617 ElementSize { width, height }
618 }
619}
620
621pub trait Element {
625 #[allow(unused_variables)]
626 fn first_location_usage(&self, ctx: FirstLocationUsageCtx) -> FirstLocationUsage {
627 FirstLocationUsage::WillUse
628 }
629
630 fn measure(&self, ctx: MeasureCtx) -> ElementSize;
631
632 fn draw(&self, ctx: DrawCtx) -> ElementSize;
633
634 fn with_padding_top(self, padding: f32) -> Padding<Self>
635 where
636 Self: Sized,
637 {
638 Padding {
639 left: 0.,
640 right: 0.,
641 top: padding,
642 bottom: 0.,
643 element: self,
644 }
645 }
646
647 fn with_padding_bottom(self, padding: f32) -> Padding<Self>
648 where
649 Self: Sized,
650 {
651 Padding {
652 left: 0.,
653 right: 0.,
654 top: 0.,
655 bottom: padding,
656 element: self,
657 }
658 }
659
660 fn with_vertical_padding(self, padding: f32) -> Padding<Self>
661 where
662 Self: Sized,
663 {
664 Padding {
665 left: 0.,
666 right: 0.,
667 top: padding,
668 bottom: padding,
669 element: self,
670 }
671 }
672
673 fn with_padding_left(self, padding: f32) -> Padding<Self>
674 where
675 Self: Sized,
676 {
677 Padding {
678 left: padding,
679 right: 0.,
680 top: 0.,
681 bottom: 0.,
682 element: self,
683 }
684 }
685
686 fn with_padding_right(self, padding: f32) -> Padding<Self>
687 where
688 Self: Sized,
689 {
690 Padding {
691 left: 0.,
692 right: padding,
693 top: 0.,
694 bottom: 0.,
695 element: self,
696 }
697 }
698
699 fn with_horizontal_padding(self, padding: f32) -> Padding<Self>
700 where
701 Self: Sized,
702 {
703 Padding {
704 left: padding,
705 right: padding,
706 top: 0.,
707 bottom: 0.,
708 element: self,
709 }
710 }
711
712 fn debug(self, color: u8) -> elements::debug::Debug<Self>
713 where
714 Self: Sized,
715 {
716 elements::debug::Debug {
717 element: self,
718 color,
719 show_max_width: false,
720 show_last_location_max_height: false,
721 }
722 }
723}
724
725pub trait CompositeElementCallback {
726 fn call(self, element: &impl Element);
727}
728
729pub trait CompositeElement {
730 fn element(&self, callback: impl CompositeElementCallback);
731}
732
733impl<C: CompositeElement> Element for C {
734 fn first_location_usage(&self, ctx: FirstLocationUsageCtx) -> FirstLocationUsage {
735 struct Callback<'a> {
736 ctx: FirstLocationUsageCtx<'a>,
737 ret: &'a mut FirstLocationUsage,
738 }
739
740 impl<'a> CompositeElementCallback for Callback<'a> {
741 fn call(self, element: &impl Element) {
742 *self.ret = element.first_location_usage(self.ctx);
743 }
744 }
745
746 let mut ret = FirstLocationUsage::NoneHeight;
747
748 self.element(Callback { ctx, ret: &mut ret });
749
750 ret
751 }
752
753 fn measure(&self, ctx: MeasureCtx) -> ElementSize {
754 struct Callback<'a> {
755 ctx: MeasureCtx<'a>,
756 ret: &'a mut ElementSize,
757 }
758
759 impl<'a> CompositeElementCallback for Callback<'a> {
760 fn call(self, element: &impl Element) {
761 *self.ret = element.measure(self.ctx);
762 }
763 }
764
765 let mut ret = ElementSize {
766 width: None,
767 height: None,
768 };
769
770 self.element(Callback { ctx, ret: &mut ret });
771
772 ret
773 }
774
775 fn draw(&self, ctx: DrawCtx) -> ElementSize {
776 struct Callback<'pdf, 'a, 'r> {
777 ctx: DrawCtx<'pdf, 'a>,
778 ret: &'r mut ElementSize,
779 }
780
781 impl<'pdf, 'a, 'r> CompositeElementCallback for Callback<'pdf, 'a, 'r> {
782 fn call(self, element: &impl Element) {
783 *self.ret = element.draw(self.ctx);
784 }
785 }
786
787 let mut ret = ElementSize {
788 width: None,
789 height: None,
790 };
791
792 self.element(Callback { ctx, ret: &mut ret });
793
794 ret
795 }
796}