Skip to main content

laser_pdf/
lib.rs

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