Skip to main content

lightweight_pdf_core/
element.rs

1//! Element catalog (V1 subset through concept Phase 5, see
2//! `plan/02-elementcatalog-and-features.md`).
3
4use crate::image::Image;
5use crate::list::List;
6use crate::style::{Align, Border, Color, Common, FontKey, Overflow, TextStyle};
7use crate::table::Table;
8
9/// One element in the document tree. Enum-based (not `Box<dyn Layoutable>`)
10/// — a closed, small set of primitives.
11#[derive(Clone, Debug)]
12pub enum Element {
13    Text(Text),
14    Row(Row),
15    Column(Column),
16    Spacer(Spacer),
17    Line(Line),
18    Rect(Rect),
19    Table(Table),
20    Image(Image),
21    List(List),
22    /// Forces a page break at this point in the enclosing flow, regardless
23    /// of whether the remaining content would still fit (Phase 2).
24    PageBreak,
25}
26
27impl Element {
28    /// Shared style properties, where applicable. `Spacer` and `PageBreak`
29    /// carry no `Common` (nothing to size/clip/keep-with-next).
30    pub fn common(&self) -> Option<&Common> {
31        match self {
32            Element::Text(t) => Some(&t.common),
33            Element::Row(r) => Some(&r.common),
34            Element::Column(c) => Some(&c.common),
35            Element::Line(l) => Some(&l.common),
36            Element::Rect(r) => Some(&r.common),
37            Element::Table(t) => Some(&t.common),
38            Element::Image(i) => Some(&i.common),
39            Element::List(l) => Some(&l.common),
40            Element::Spacer(_) | Element::PageBreak => None,
41        }
42    }
43
44    /// Mutable counterpart to [`Self::common`] — used by `List`'s layout
45    /// translation to make item content fill the remaining row width
46    /// (`flex(1.0)`) without needing a bespoke setter per element variant.
47    pub fn common_mut(&mut self) -> Option<&mut Common> {
48        match self {
49            Element::Text(t) => Some(&mut t.common),
50            Element::Row(r) => Some(&mut r.common),
51            Element::Column(c) => Some(&mut c.common),
52            Element::Line(l) => Some(&mut l.common),
53            Element::Rect(r) => Some(&mut r.common),
54            Element::Table(t) => Some(&mut t.common),
55            Element::Image(i) => Some(&mut i.common),
56            Element::List(l) => Some(&mut l.common),
57            Element::Spacer(_) | Element::PageBreak => None,
58        }
59    }
60}
61
62/// Generates the shared `Common`-backed builder methods for a wrapper type
63/// that has a `pub common: Common` field. Avoids repeating five setters on
64/// every element type (Text, Row, Column, Line, Rect).
65macro_rules! common_builder_methods {
66    () => {
67        pub fn width(mut self, width: f32) -> Self {
68            self.common.width = Some(width);
69            self
70        }
71
72        pub fn height(mut self, height: f32) -> Self {
73            self.common.height = Some(height);
74            self
75        }
76
77        pub fn flex(mut self, factor: f32) -> Self {
78            self.common.flex = Some(factor);
79            self
80        }
81
82        pub fn padding(mut self, padding: f32) -> Self {
83            self.common.padding = padding;
84            self
85        }
86
87        pub fn overflow(mut self, overflow: Overflow) -> Self {
88            self.common.overflow = overflow;
89            self
90        }
91
92        pub fn background(mut self, color: Color) -> Self {
93            self.common.background = Some(color);
94            self
95        }
96
97        pub fn border(mut self, border: Border) -> Self {
98            self.common.border = Some(border);
99            self
100        }
101
102        /// See `plan/05-overflow-and-robustness.md` Grundprinzip 9: only
103        /// placed on a page if the following sibling also still fits.
104        pub fn keep_with_next(mut self) -> Self {
105            self.common.keep_with_next = true;
106            self
107        }
108    };
109}
110
111// ---------------------------------------------------------------------
112// Text
113// ---------------------------------------------------------------------
114
115#[derive(Clone, Debug, Default)]
116pub struct Text {
117    pub content: String,
118    pub style: TextStyle,
119    pub common: Common,
120}
121
122impl Text {
123    pub fn new(content: impl Into<String>) -> Self {
124        Text {
125            content: content.into(),
126            style: TextStyle::default(),
127            common: Common::default(),
128        }
129    }
130
131    pub fn size(mut self, size: f32) -> Self {
132        self.style.size = size;
133        self
134    }
135
136    pub fn bold(mut self) -> Self {
137        self.style.font = FontKey::SANS_BOLD;
138        self
139    }
140
141    pub fn font(mut self, font: FontKey) -> Self {
142        self.style.font = font;
143        self
144    }
145
146    pub fn color(mut self, color: Color) -> Self {
147        self.style.color = color;
148        self
149    }
150
151    pub fn align(mut self, align: Align) -> Self {
152        self.style.align = align;
153        self
154    }
155
156    pub fn line_height(mut self, line_height: f32) -> Self {
157        self.style.line_height = line_height;
158        self
159    }
160
161    /// Heading presets (Phase 6, `plan/02-elementcatalog-and-features.md`):
162    /// thin wrappers over `.size()`/`.bold()`, additionally setting
163    /// `keep_with_next` so a heading never ends up alone at the bottom of
164    /// a page without its following content
165    /// (`plan/05-overflow-and-robustness.md` Grundprinzip 9).
166    pub fn heading1(self) -> Self {
167        self.size(24.0).bold().keep_with_next()
168    }
169
170    pub fn heading2(self) -> Self {
171        self.size(18.0).bold().keep_with_next()
172    }
173
174    pub fn heading3(self) -> Self {
175        self.size(14.0).bold().keep_with_next()
176    }
177
178    common_builder_methods!();
179}
180
181impl From<&str> for Text {
182    fn from(value: &str) -> Self {
183        Text::new(value)
184    }
185}
186
187impl From<String> for Text {
188    fn from(value: String) -> Self {
189        Text::new(value)
190    }
191}
192
193// ---------------------------------------------------------------------
194// Row / Column
195// ---------------------------------------------------------------------
196
197#[derive(Clone, Debug, Default)]
198pub struct Row {
199    pub children: Vec<Element>,
200    pub gap: f32,
201    pub align: Align,
202    pub common: Common,
203}
204
205#[derive(Clone, Debug, Default)]
206pub struct Column {
207    pub children: Vec<Element>,
208    pub gap: f32,
209    pub align: Align,
210    pub common: Common,
211}
212
213macro_rules! container_impl {
214    ($ty:ident) => {
215        impl $ty {
216            pub fn new() -> Self {
217                Self::default()
218            }
219
220            pub fn child(mut self, child: impl Into<Element>) -> Self {
221                self.children.push(child.into());
222                self
223            }
224
225            pub fn children(mut self, children: impl IntoIterator<Item = impl Into<Element>>) -> Self {
226                self.children.extend(children.into_iter().map(Into::into));
227                self
228            }
229
230            pub fn gap(mut self, gap: f32) -> Self {
231                self.gap = gap;
232                self
233            }
234
235            pub fn align(mut self, align: Align) -> Self {
236                self.align = align;
237                self
238            }
239
240            common_builder_methods!();
241        }
242    };
243}
244
245container_impl!(Row);
246container_impl!(Column);
247
248// ---------------------------------------------------------------------
249// Spacer
250// ---------------------------------------------------------------------
251
252#[derive(Clone, Copy, Debug)]
253pub struct Spacer {
254    pub size: f32,
255}
256
257impl Spacer {
258    pub fn new(size: f32) -> Self {
259        Spacer { size }
260    }
261}
262
263// ---------------------------------------------------------------------
264// Line
265// ---------------------------------------------------------------------
266
267#[derive(Clone, Debug)]
268pub struct Line {
269    pub thickness: f32,
270    pub color: Color,
271    pub common: Common,
272}
273
274impl Default for Line {
275    fn default() -> Self {
276        Line {
277            thickness: 1.0,
278            color: Color::BLACK,
279            common: Common::default(),
280        }
281    }
282}
283
284impl Line {
285    pub fn new() -> Self {
286        Self::default()
287    }
288
289    pub fn thickness(mut self, thickness: f32) -> Self {
290        self.thickness = thickness;
291        self
292    }
293
294    pub fn color(mut self, color: Color) -> Self {
295        self.color = color;
296        self
297    }
298
299    common_builder_methods!();
300}
301
302// ---------------------------------------------------------------------
303// Rect
304// ---------------------------------------------------------------------
305
306#[derive(Clone, Debug, Default)]
307pub struct Rect {
308    pub common: Common,
309}
310
311impl Rect {
312    pub fn new() -> Self {
313        Self::default()
314    }
315
316    common_builder_methods!();
317}
318
319// ---------------------------------------------------------------------
320// Element From-impls (ADR/03-builder-api-design.md point 3)
321// ---------------------------------------------------------------------
322
323macro_rules! element_from {
324    ($ty:ident) => {
325        impl From<$ty> for Element {
326            fn from(value: $ty) -> Self {
327                Element::$ty(value)
328            }
329        }
330    };
331}
332
333element_from!(Text);
334element_from!(Row);
335element_from!(Column);
336element_from!(Spacer);
337element_from!(Line);
338element_from!(Rect);
339element_from!(Table);
340element_from!(Image);
341element_from!(List);
342
343impl From<&str> for Element {
344    fn from(value: &str) -> Self {
345        Element::Text(Text::new(value))
346    }
347}
348
349impl From<String> for Element {
350    fn from(value: String) -> Self {
351        Element::Text(Text::new(value))
352    }
353}