Skip to main content

lightweight_pdf_core/
document.rs

1use crate::element::Element;
2use std::rc::Rc;
3
4/// V1 supports a single fixed page size (Phase 0 spike scope: "fixe
5/// A4-Größe"). Dimensions in PDF points (1/72 inch).
6#[derive(Clone, Copy, PartialEq, Debug)]
7pub enum PageFormat {
8    A4,
9}
10
11impl PageFormat {
12    /// (width, height) in points, portrait.
13    pub fn size(&self) -> (f32, f32) {
14        match self {
15            // 210mm x 297mm at 72pt/25.4mm.
16            PageFormat::A4 => (595.2756, 841.8898),
17        }
18    }
19}
20
21#[derive(Clone, Copy, PartialEq, Debug, Default)]
22pub struct Margin {
23    pub top: f32,
24    pub right: f32,
25    pub bottom: f32,
26    pub left: f32,
27}
28
29impl Margin {
30    pub fn symmetric(horizontal: f32, vertical: f32) -> Self {
31        Margin {
32            top: vertical,
33            right: horizontal,
34            bottom: vertical,
35            left: horizontal,
36        }
37    }
38
39    pub fn all(value: f32) -> Self {
40        Margin {
41            top: value,
42            right: value,
43            bottom: value,
44            left: value,
45        }
46    }
47}
48
49/// Passed to `Header`/`Footer` closures on every (re-)evaluation. Plain data
50/// only, so it can live in `lightweight-pdf-core` without pulling in layout/font
51/// knowledge (ADR-010).
52#[derive(Clone, Copy, Debug)]
53pub struct PageContext {
54    pub page: usize,
55    pub total_pages: usize,
56}
57
58type HeaderFooterFn = Rc<dyn Fn(&PageContext) -> Element>;
59
60/// A header band with a fixed, document-creation-time height (ADR-011): the
61/// closure may vary its content per page but never the reserved band size.
62#[derive(Clone)]
63pub struct Header {
64    pub height: f32,
65    pub content: HeaderFooterFn,
66}
67
68impl Header {
69    pub fn new(height: f32, content: impl Fn(&PageContext) -> Element + 'static) -> Self {
70        Header {
71            height,
72            content: Rc::new(content),
73        }
74    }
75}
76
77#[derive(Clone)]
78pub struct Footer {
79    pub height: f32,
80    pub content: HeaderFooterFn,
81}
82
83impl Footer {
84    pub fn new(height: f32, content: impl Fn(&PageContext) -> Element + 'static) -> Self {
85        Footer {
86            height,
87            content: Rc::new(content),
88        }
89    }
90}
91
92#[derive(Clone)]
93pub struct Document {
94    pub page_format: PageFormat,
95    pub margin: Margin,
96    pub header: Option<Header>,
97    pub footer: Option<Footer>,
98    pub header_visible_from: usize,
99    pub footer_visible_from: usize,
100    pub watermark: Option<crate::watermark::Watermark>,
101    pub children: Vec<Element>,
102}
103
104impl Document {
105    pub fn new(page_format: PageFormat) -> Self {
106        Document {
107            page_format,
108            margin: Margin::default(),
109            header: None,
110            footer: None,
111            header_visible_from: 1,
112            footer_visible_from: 1,
113            watermark: None,
114            children: Vec::new(),
115        }
116    }
117
118    pub fn margin(mut self, margin: Margin) -> Self {
119        self.margin = margin;
120        self
121    }
122
123    pub fn header(mut self, header: Header) -> Self {
124        self.header = Some(header);
125        self
126    }
127
128    pub fn footer(mut self, footer: Footer) -> Self {
129        self.footer = Some(footer);
130        self
131    }
132
133    /// First page number (1-based) on which the header is drawn. Cover-page
134    /// convenience, see `plan/02-elementcatalog-and-features.md` ("Deckblatt
135    /// / Titelseite").
136    pub fn header_visible_from(mut self, page: usize) -> Self {
137        self.header_visible_from = page;
138        self
139    }
140
141    pub fn footer_visible_from(mut self, page: usize) -> Self {
142        self.footer_visible_from = page;
143        self
144    }
145
146    /// Sets a document-wide diagonal stamp ("ENTWURF", "STORNIERT") — an
147    /// independent layer, not a normal flow element (Phase 6).
148    pub fn watermark(mut self, watermark: crate::watermark::Watermark) -> Self {
149        self.watermark = Some(watermark);
150        self
151    }
152
153    pub fn add(&mut self, element: impl Into<Element>) -> &mut Self {
154        self.children.push(element.into());
155        self
156    }
157}