laser_pdf/
lib.rs

1pub mod elements;
2pub mod flex;
3pub mod fonts;
4pub mod image;
5pub mod serde_elements;
6pub mod test_utils;
7pub mod text;
8pub mod utils;
9
10use elements::padding::Padding;
11use pdf_writer::{Content, Name, Rect, Ref};
12// use elements::padding::Padding;
13use fonts::Font;
14// use printpdf::{CurTransMat, Mm, PdfDocumentReference, PdfLayerReference};
15use serde::{Deserialize, Serialize};
16
17pub const EMPTY_FIELD: &str = "—";
18
19#[derive(Debug)]
20pub struct FontSet<'a, F: Font> {
21    pub regular: &'a F,
22    pub bold: &'a F,
23    pub italic: &'a F,
24    pub bold_italic: &'a F,
25}
26
27impl<'a, F: Font> Clone for FontSet<'a, F> {
28    fn clone(&self) -> Self {
29        *self
30    }
31}
32
33impl<'a, F: Font> Copy for FontSet<'a, F> {}
34
35#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
36pub enum VAlign {
37    Top,
38    Center,
39    Bottom,
40}
41
42pub type Color = u32;
43
44/// ISO 32000-1:2008 8.4.3.3
45///
46/// The line cap style shall specify the shape that shall be used at the ends of
47/// open subpaths (and dashes, if any) when they are stroked.
48#[derive(Copy, Clone, Serialize, Deserialize)]
49pub enum LineCapStyle {
50    /// 0: Butt cap. The stroke shall be squared off at the endpoint of the
51    /// path. There shall be no projection beyond the end of the path.
52    Butt,
53
54    /// 1: Round cap. A semicircular arc with a diameter equal to the line width
55    /// shall be drawn around the endpoint and shall be filled in.
56    Round,
57
58    /// 2: Projecting square cap. The stroke shall continue beyond the endpoint
59    /// of the path for a distance equal to half the line width and shall be
60    /// squared off.
61    ProjectingSquare,
62}
63
64impl Into<pdf_writer::types::LineCapStyle> for LineCapStyle {
65    fn into(self) -> pdf_writer::types::LineCapStyle {
66        match self {
67            LineCapStyle::Butt => pdf_writer::types::LineCapStyle::ButtCap,
68            LineCapStyle::Round => pdf_writer::types::LineCapStyle::RoundCap,
69            LineCapStyle::ProjectingSquare => pdf_writer::types::LineCapStyle::ProjectingSquareCap,
70        }
71    }
72}
73
74/// ISO 32000-1:2008 8.4.3.6
75///
76/// The line dash pattern shall control the pattern of dashes and gaps used to
77/// stroke paths.
78#[derive(Copy, Clone, Serialize, Deserialize)]
79pub struct LineDashPattern {
80    /// The dash phase shall specify the distance into the dash pattern at which
81    /// to start the dash.
82    pub offset: u16,
83
84    /// The dash array’s elements shall be numbers that specify the lengths of
85    /// alternating dashes and gaps; the numbers shall be nonnegative and not
86    /// all zero.
87    pub dashes: [u16; 2],
88}
89
90#[derive(Copy, Clone, Serialize, Deserialize)]
91pub struct LineStyle {
92    pub thickness: f32,
93    pub color: Color,
94    pub dash_pattern: Option<LineDashPattern>,
95    pub cap_style: LineCapStyle,
96}
97
98pub struct Layer {
99    pub content: Content,
100    pub graphics_state_restore_required: bool,
101}
102
103pub struct Page {
104    pub ext_g_states: Vec<Ref>, // all objects must be indirect for now
105    pub x_objects: Vec<Ref>,
106    pub layers: Vec<Layer>,
107    pub size: (f32, f32),
108}
109
110impl Page {
111    pub fn add_ext_g_state(&mut self, resource: Ref) -> usize {
112        self.ext_g_states.push(resource);
113        self.ext_g_states.len() - 1
114    }
115
116    pub fn add_x_object(&mut self, resource: Ref) -> String {
117        self.x_objects.push(resource);
118        (self.x_objects.len() - 1).to_string()
119    }
120}
121
122pub struct Pdf {
123    pub alloc: Ref,
124    pub pdf: pdf_writer::Pdf,
125    pub pages: Vec<Page>,
126    pub fonts: Vec<Ref>,
127    truetype_fonts: Vec<fonts::truetype::TruetypeFontState>,
128}
129
130impl Pdf {
131    pub fn new() -> Self {
132        let pdf = pdf_writer::Pdf::new();
133
134        Pdf {
135            alloc: pdf_writer::Ref::new(1),
136            pdf,
137            pages: Vec::new(),
138            fonts: Vec::new(),
139            truetype_fonts: Vec::new(),
140        }
141    }
142
143    pub fn alloc(&mut self) -> Ref {
144        self.alloc.bump()
145    }
146
147    pub fn add_page(&mut self, size: (f32, f32)) -> Location {
148        self.pages.push(Page {
149            ext_g_states: Vec::new(),
150            x_objects: Vec::new(),
151            layers: vec![Layer {
152                content: Content::new(),
153                graphics_state_restore_required: false,
154            }],
155            size,
156        });
157
158        Location {
159            page_idx: self.pages.len() - 1,
160            layer_idx: 0,
161            pos: (0., size.1),
162            scale_factor: 1.,
163        }
164    }
165
166    pub fn finish(mut self) -> Vec<u8> {
167        let catalog_ref = self.alloc();
168        let page_tree_ref = self.alloc();
169
170        self.pdf.catalog(catalog_ref).pages(page_tree_ref);
171
172        for mut truetype_font in self.truetype_fonts {
173            truetype_font.finish(&mut self.pdf, &mut self.alloc);
174        }
175
176        let pages = self
177            .pages
178            .iter()
179            .scan(self.alloc, |state, _| Some(state.bump()));
180
181        self.pdf
182            .pages(page_tree_ref)
183            .kids(pages)
184            .count(self.pages.len() as i32);
185
186        let mut page_alloc = self.alloc;
187        self.alloc = Ref::new(self.alloc.get() + self.pages.len() as i32);
188
189        for page in self.pages {
190            let mut page_writer = self.pdf.page(page_alloc.bump());
191
192            page_writer
193                .parent(page_tree_ref)
194                .media_box(Rect::new(
195                    0.,
196                    0.,
197                    (page.size.0 * 72. / 25.4) as f32,
198                    (page.size.1 * 72. / 25.4) as f32,
199                ))
200                .contents_array(
201                    page.layers
202                        .iter()
203                        .scan(self.alloc, |state, _| Some(state.bump())),
204                );
205
206            let mut resources = page_writer.resources();
207
208            let mut ext_g_states = resources.ext_g_states();
209            for (i, ext_g_state) in page.ext_g_states.iter().enumerate() {
210                ext_g_states.pair(Name(format!("{i}").as_bytes()), ext_g_state);
211            }
212            drop(ext_g_states);
213
214            if !page.x_objects.is_empty() {
215                let mut x_objects = resources.x_objects();
216                for (i, x_object) in page.x_objects.iter().enumerate() {
217                    x_objects.pair(Name(format!("{i}").as_bytes()), x_object);
218                }
219            }
220
221            let mut fonts = resources.fonts();
222
223            for (i, &font) in self.fonts.iter().enumerate() {
224                // TODO: inherit or make an indirect object
225                fonts.pair(Name(&format!("F{}", i).as_bytes()), font);
226            }
227
228            drop(fonts);
229            drop(resources);
230            drop(page_writer);
231
232            for mut layer in page.layers {
233                if layer.graphics_state_restore_required {
234                    layer.content.restore_state();
235                }
236
237                // This adds up as long as it's not bumped between the contents_array call and here.
238                self.pdf.stream(self.alloc.bump(), &layer.content.finish());
239            }
240        }
241
242        self.pdf.finish()
243    }
244}
245
246/// A position for an element to render at.
247/// This doesn't include the width at the moment, as this would make things much more complicated.
248/// The line breaking iterator wouldn't work in its current form for example.
249/// Things are much easier if an element can make width related calculations in the beginning an
250/// doesn't have to recalculate them on a page break.
251#[derive(Clone, Debug)]
252pub struct Location {
253    pub page_idx: usize,
254    pub layer_idx: usize,
255    pub pos: (f32, f32),
256    pub scale_factor: f32,
257}
258
259impl Location {
260    pub fn layer<'a>(&self, pdf: &'a mut Pdf) -> &'a mut Content {
261        &mut pdf.pages[self.page_idx].layers[self.layer_idx].content
262    }
263
264    pub fn next_layer(&self, pdf: &mut Pdf) -> Location {
265        let page = &mut pdf.pages[self.page_idx];
266
267        let mut content = Content::new();
268
269        let graphics_state_restore_required = if self.scale_factor != 1. {
270            content
271                .save_state()
272                .transform(utils::scale(self.scale_factor));
273            true
274        } else {
275            false
276        };
277
278        // The issue is some of the layers are scaled. That's why we currently can't reuse them.
279        // TODO: Find a better solution that doesn't require adding so many layers, but also doesn't
280        // lead to unbalances saves/restores (which is not allowed by the spec).
281        page.layers.push(Layer {
282            content,
283            graphics_state_restore_required,
284        });
285
286        Location {
287            layer_idx: page.layers.len() - 1,
288            ..*self
289        }
290    }
291}
292
293#[derive(Clone, Copy, Debug, PartialEq)]
294pub struct WidthConstraint {
295    pub max: f32,
296    pub expand: bool,
297}
298
299impl WidthConstraint {
300    pub fn constrain(&self, width: f32) -> f32 {
301        if self.expand {
302            self.max
303        } else {
304            width.min(self.max)
305        }
306    }
307}
308
309pub type Pos = (f32, f32);
310pub type Size = (f32, f32);
311
312/// This returns a new [Location] because some collection elements need to keep multiple
313/// [Location]s at once (e.g. for page breaking inside of a horizontal list)
314///
315/// The second parameter is which location the break is occurring from. This number
316/// must be counted up for sequential page breaks. This allows the same page break to be
317/// performed twice in a row.
318///
319/// The third parameter is the height of the location.
320pub type Break<'a> = &'a mut dyn FnMut(&mut Pdf, u32, Option<f32>) -> Location;
321
322#[derive(Clone, Copy, PartialEq, Eq, Debug)]
323pub enum FirstLocationUsage {
324    /// This means the element has no height at all. Meaning it doesn't break either. If the element
325    /// breaks, but has a height of None for the first location it should use
326    /// [FirstLocationUsage::WillUse] or [FirstLocationUsage::WillSkip] if appropriate.
327    NoneHeight,
328    WillUse,
329    WillSkip,
330}
331
332pub struct FirstLocationUsageCtx {
333    pub width: WidthConstraint,
334    pub first_height: f32,
335
336    // is this needed?
337    // one could argue that the parent should know to not even ask if full height isn't more
338    // on the other hand a text element could have a behavior of printing one line at a time if
339    // full-height is less than the height needed, but available-height might still be even less
340    // than that and in that case text might still use the first one (though the correctness of that
341    // is also questionable)
342    pub full_height: f32,
343}
344
345impl FirstLocationUsageCtx {
346    pub fn break_appropriate_for_min_height(&self, height: f32) -> bool {
347        height > self.first_height && self.full_height > self.first_height
348    }
349}
350
351pub struct BreakableMeasure<'a> {
352    pub full_height: f32,
353    pub break_count: &'a mut u32,
354
355    /// The minimum height required for any extra locations added to the end. If, for example,
356    /// there's a flex with a text element that gets repeated for each location and other flex
357    /// elements use more locations than this one, the text element will still be drawn on the last
358    /// location via `preferred_break_count` and `preferred_height`. The flex needs to be able to
359    /// predict the height of the last page so that there isn't a single element that is higher than
360    /// the other ones.
361    /// `None` here means the element does not use extra locations. This means it is not possible
362    /// to have an element that does use extra locations, but returns a `None` height on the last
363    /// one. Should that ever become necessary we'll probably have to change this to an
364    /// `Option<Option<f32>>`.
365    pub extra_location_min_height: &'a mut Option<f32>,
366}
367
368pub struct MeasureCtx<'a> {
369    pub width: WidthConstraint,
370    pub first_height: f32,
371    pub breakable: Option<BreakableMeasure<'a>>,
372}
373
374impl<'a> MeasureCtx<'a> {
375    pub fn break_if_appropriate_for_min_height(&mut self, height: f32) -> bool {
376        if let Some(ref mut breakable) = self.breakable {
377            if height > self.first_height && breakable.full_height > self.first_height {
378                *breakable.break_count = 1;
379                return true;
380            }
381        }
382
383        false
384    }
385}
386
387pub struct BreakableDraw<'a> {
388    pub full_height: f32,
389    pub preferred_height_break_count: u32,
390    pub do_break: Break<'a>,
391}
392
393pub struct DrawCtx<'a, 'b> {
394    pub pdf: &'a mut Pdf,
395    pub location: Location,
396
397    pub width: WidthConstraint,
398    pub first_height: f32,
399
400    pub preferred_height: Option<f32>,
401
402    pub breakable: Option<BreakableDraw<'b>>,
403}
404
405impl<'a, 'b> DrawCtx<'a, 'b> {
406    pub fn break_if_appropriate_for_min_height(&mut self, height: f32) -> bool {
407        if let Some(ref mut breakable) = self.breakable {
408            if height > self.first_height && breakable.full_height > self.first_height {
409                // TODO: Make sure this is correct. Maybe this function needs to be renamed to make
410                // clear what this actually does.
411                self.location = (breakable.do_break)(self.pdf, 0, None);
412                return true;
413            }
414        }
415
416        false
417    }
418}
419
420#[derive(Copy, Clone, Debug, PartialEq)]
421pub struct ElementSize {
422    pub width: Option<f32>,
423
424    /// None here means that this element doesn't need any space on it's last page. This is useful
425    /// for things like collapsing gaps after a forced break. This in combination with no breaks
426    /// means the element is completely hidden. This can be used to trigger collapsing of gaps even
427    /// hiding certain parent containers, like titled, in turn.
428    pub height: Option<f32>,
429}
430
431impl ElementSize {
432    pub fn new(width: Option<f32>, height: Option<f32>) -> Self {
433        ElementSize { width, height }
434    }
435}
436
437/// Rules:
438/// Width returned from measure has to be matched in draw given the same
439/// constraint (even if there's some preferred height).
440pub trait Element {
441    #[allow(unused_variables)]
442    fn first_location_usage(&self, ctx: FirstLocationUsageCtx) -> FirstLocationUsage {
443        FirstLocationUsage::WillUse
444    }
445
446    fn measure(&self, ctx: MeasureCtx) -> ElementSize;
447
448    fn draw(&self, ctx: DrawCtx) -> ElementSize;
449
450    fn with_padding_top(self, padding: f32) -> Padding<Self>
451    where
452        Self: Sized,
453    {
454        Padding {
455            left: 0.,
456            right: 0.,
457            top: padding,
458            bottom: 0.,
459            element: self,
460        }
461    }
462
463    fn with_vertical_padding(self, padding: f32) -> Padding<Self>
464    where
465        Self: Sized,
466    {
467        Padding {
468            left: 0.,
469            right: 0.,
470            top: padding,
471            bottom: padding,
472            element: self,
473        }
474    }
475
476    fn debug(self, color: u8) -> elements::debug::Debug<Self>
477    where
478        Self: Sized,
479    {
480        elements::debug::Debug {
481            element: self,
482            color,
483            show_max_width: false,
484            show_last_location_max_height: false,
485        }
486    }
487}
488
489pub trait CompositeElementCallback {
490    fn call(self, element: &impl Element);
491}
492
493pub trait CompositeElement {
494    fn element(&self, callback: impl CompositeElementCallback);
495}
496
497impl<C: CompositeElement> Element for C {
498    fn first_location_usage(&self, ctx: FirstLocationUsageCtx) -> FirstLocationUsage {
499        struct Callback<'a> {
500            ctx: FirstLocationUsageCtx,
501            ret: &'a mut FirstLocationUsage,
502        }
503
504        impl<'a> CompositeElementCallback for Callback<'a> {
505            fn call(self, element: &impl Element) {
506                *self.ret = element.first_location_usage(self.ctx);
507            }
508        }
509
510        let mut ret = FirstLocationUsage::NoneHeight;
511
512        self.element(Callback { ctx, ret: &mut ret });
513
514        ret
515    }
516
517    fn measure(&self, ctx: MeasureCtx) -> ElementSize {
518        struct Callback<'a> {
519            ctx: MeasureCtx<'a>,
520            ret: &'a mut ElementSize,
521        }
522
523        impl<'a> CompositeElementCallback for Callback<'a> {
524            fn call(self, element: &impl Element) {
525                *self.ret = element.measure(self.ctx);
526            }
527        }
528
529        let mut ret = ElementSize {
530            width: None,
531            height: None,
532        };
533
534        self.element(Callback { ctx, ret: &mut ret });
535
536        ret
537    }
538
539    fn draw(&self, ctx: DrawCtx) -> ElementSize {
540        struct Callback<'pdf, 'a, 'r> {
541            ctx: DrawCtx<'pdf, 'a>,
542            ret: &'r mut ElementSize,
543        }
544
545        impl<'pdf, 'a, 'r> CompositeElementCallback for Callback<'pdf, 'a, 'r> {
546            fn call(self, element: &impl Element) {
547                *self.ret = element.draw(self.ctx);
548            }
549        }
550
551        let mut ret = ElementSize {
552            width: None,
553            height: None,
554        };
555
556        self.element(Callback { ctx, ret: &mut ret });
557
558        ret
559    }
560}
561
562// -------------------------------------------------------------------------------------------------
563
564// pub trait BuildElement<'a, F: 'static> {
565//     type R: Element + 'a;
566
567//     fn call(self, fonts: &'a F) -> Self::R;
568// }
569
570// impl<'a, F: 'static, R: Element + 'a, O: FnOnce(&'a F) -> R> BuildElement<'a, F> for O {
571//     type R = R;
572
573//     #[inline]
574//     fn call(self, fonts: &'a F) -> Self::R {
575//         self(fonts)
576//     }
577// }
578
579// pub fn build_pdf<F: 'static>(
580//     name: &str,
581//     page_size: (f32, f32),
582//     build_fonts: impl FnOnce(&PdfDocumentReference) -> F,
583//     build_element: impl for<'a> BuildElement<'a, F>,
584// ) -> printpdf::PdfDocumentReference {
585//     use printpdf::{
586//         indices::{PdfLayerIndex, PdfPageIndex},
587//         PdfDocument,
588//     };
589
590//     let (doc, page, layer) = PdfDocument::new(name, Mm(page_size.0), Mm(page_size.1), "Layer 0");
591//     let mut page_idx = 0;
592
593//     let mut pdf = Pdf {
594//         document: doc,
595//         page_size,
596//     };
597
598//     let do_break = &mut |pdf: &mut Pdf, location_idx, size| {
599//         while page_idx <= location_idx {
600//             pdf.document
601//                 .add_page(Mm(page_size.0), Mm(page_size.1), "Layer 0");
602//             page_idx += 1;
603//         }
604
605//         let layer = pdf
606//             .document
607//             .get_page(PdfPageIndex((location_idx + 1) as usize))
608//             .get_layer(PdfLayerIndex(0));
609
610//         Location {
611//             layer,
612//             pos: (0., page_size.1),
613//             scale_factor: 1.,
614//         }
615//     };
616
617//     let layer = pdf.document.get_page(page).get_layer(layer);
618
619//     let fonts = build_fonts(&pdf.document);
620
621//     let element = build_element.call(&fonts);
622
623//     let ctx = DrawCtx {
624//         pdf: &mut pdf,
625//         width: WidthConstraint {
626//             max: page_size.0,
627//             expand: true,
628//         },
629//         location: Location {
630//             layer,
631//             pos: (0., page_size.1),
632//             scale_factor: 1.,
633//         },
634
635//         first_height: page_size.1,
636//         preferred_height: None,
637
638//         breakable: Some(BreakableDraw {
639//             full_height: page_size.1,
640//             preferred_height_break_count: 0,
641//             do_break,
642//         }),
643//     };
644
645//     element.draw(ctx);
646
647//     pdf.document
648// }