Skip to main content

lightweight_pdf_core/
table.rs

1//! `Table` element (Phase 3, `plan/phases/phase-3-tables.md`): the element
2//! that matters most for invoices. Cells are plain `Element`s so they reuse
3//! the exact same `Layoutable`/text-wrap machinery as everything else —
4//! no separate cell-content model.
5
6use crate::element::Element;
7use crate::style::{Align, Color, Common};
8
9/// A column's width: `fixed(w)` reserves an exact width, `flex(weight)`
10/// shares the leftover space proportionally (taffy `flex-grow` analogy,
11/// ADR-004 / `03-builder-api-design.md`) — the same distribution step as
12/// `Row`, not a generic flex implementation.
13#[derive(Clone, Copy, Debug)]
14pub enum ColumnWidth {
15    Fixed(f32),
16    Flex(f32),
17}
18
19#[derive(Clone, Copy, Debug)]
20pub struct TableColumn {
21    pub width: ColumnWidth,
22    pub align: Align,
23}
24
25impl TableColumn {
26    pub fn fixed(width: f32) -> Self {
27        TableColumn {
28            width: ColumnWidth::Fixed(width),
29            align: Align::Start,
30        }
31    }
32
33    pub fn flex(weight: f32) -> Self {
34        TableColumn {
35            width: ColumnWidth::Flex(weight),
36            align: Align::Start,
37        }
38    }
39
40    pub fn align(mut self, align: Align) -> Self {
41        self.align = align;
42        self
43    }
44}
45
46#[derive(Clone, Debug, Default)]
47pub struct Table {
48    pub columns: Vec<TableColumn>,
49    pub header: Option<Vec<Element>>,
50    pub rows: Vec<Vec<Element>>,
51    /// Alternating row background ("Zebra-Streifen"), see
52    /// `02-elementcatalog-and-features.md`. Applies to data rows only (a
53    /// striped header would be indistinguishable from a striped data row).
54    pub striped: Option<Color>,
55    /// Inner spacing on every side of each cell's content, same default
56    /// (4pt) header and data rows.
57    pub cell_padding: f32,
58    /// Absolute index of `rows[0]` within the *original*, unsplit table —
59    /// 0 unless this `Table` is itself the remainder produced by a
60    /// previous page's `LayoutResult::Split`. Not part of the public
61    /// builder surface; exists purely so `.striped()` keeps alternating
62    /// correctly across a page break instead of resetting per page.
63    pub row_offset: usize,
64    pub common: Common,
65}
66
67impl Table {
68    pub fn new() -> Self {
69        Table {
70            cell_padding: 4.0,
71            ..Default::default()
72        }
73    }
74
75    pub fn columns(mut self, columns: impl IntoIterator<Item = TableColumn>) -> Self {
76        self.columns = columns.into_iter().collect();
77        self
78    }
79
80    pub fn header(mut self, cells: impl IntoIterator<Item = impl Into<Element>>) -> Self {
81        self.header = Some(cells.into_iter().map(Into::into).collect());
82        self
83    }
84
85    pub fn rows(mut self, rows: impl IntoIterator<Item = impl IntoIterator<Item = impl Into<Element>>>) -> Self {
86        self.rows = rows.into_iter().map(|row| row.into_iter().map(Into::into).collect()).collect();
87        self
88    }
89
90    pub fn striped(mut self, color: Color) -> Self {
91        self.striped = Some(color);
92        self
93    }
94
95    pub fn cell_padding(mut self, padding: f32) -> Self {
96        self.cell_padding = padding;
97        self
98    }
99
100    pub fn width(mut self, width: f32) -> Self {
101        self.common.width = Some(width);
102        self
103    }
104
105    pub fn height(mut self, height: f32) -> Self {
106        self.common.height = Some(height);
107        self
108    }
109
110    pub fn flex(mut self, factor: f32) -> Self {
111        self.common.flex = Some(factor);
112        self
113    }
114
115    pub fn keep_with_next(mut self) -> Self {
116        self.common.keep_with_next = true;
117        self
118    }
119}