ppt_rs/generator/
tables.rs

1//! Table creation support for PPTX generation
2
3/// Horizontal text alignment
4#[derive(Clone, Debug, Default, PartialEq)]
5pub enum CellAlign {
6    Left,
7    #[default]
8    Center,
9    Right,
10    Justify,
11}
12
13impl CellAlign {
14    /// Get the OOXML alignment value
15    pub fn as_str(&self) -> &'static str {
16        match self {
17            CellAlign::Left => "l",
18            CellAlign::Center => "ctr",
19            CellAlign::Right => "r",
20            CellAlign::Justify => "just",
21        }
22    }
23}
24
25/// Vertical text alignment
26#[derive(Clone, Debug, Default, PartialEq)]
27pub enum CellVAlign {
28    Top,
29    #[default]
30    Middle,
31    Bottom,
32}
33
34impl CellVAlign {
35    /// Get the OOXML anchor value
36    pub fn as_str(&self) -> &'static str {
37        match self {
38            CellVAlign::Top => "t",
39            CellVAlign::Middle => "ctr",
40            CellVAlign::Bottom => "b",
41        }
42    }
43}
44
45/// Table cell content
46#[derive(Clone, Debug)]
47pub struct TableCell {
48    pub text: String,
49    pub bold: bool,
50    pub italic: bool,
51    pub underline: bool,
52    pub text_color: Option<String>,      // RGB hex color for text
53    pub background_color: Option<String>, // RGB hex color for background
54    pub font_size: Option<u32>,          // Font size in points
55    pub font_family: Option<String>,     // Font family name
56    pub align: CellAlign,                // Horizontal alignment
57    pub valign: CellVAlign,              // Vertical alignment
58    pub wrap_text: bool,                 // Text wrapping
59    pub row_span: u32,
60    pub col_span: u32,
61    pub v_merge: bool,
62    pub h_merge: bool,
63}
64
65impl TableCell {
66    /// Create a new table cell
67    pub fn new(text: &str) -> Self {
68        TableCell {
69            text: text.to_string(),
70            bold: false,
71            italic: false,
72            underline: false,
73            text_color: None,
74            background_color: None,
75            font_size: None,
76            font_family: None,
77            align: CellAlign::Center,
78            valign: CellVAlign::Middle,
79            wrap_text: true,
80            row_span: 1,
81            col_span: 1,
82            v_merge: false,
83            h_merge: false,
84        }
85    }
86
87    /// Set cell text as bold
88    pub fn bold(mut self) -> Self {
89        self.bold = true;
90        self
91    }
92
93    /// Set cell text as italic
94    pub fn italic(mut self) -> Self {
95        self.italic = true;
96        self
97    }
98
99    /// Set cell text as underlined
100    pub fn underline(mut self) -> Self {
101        self.underline = true;
102        self
103    }
104
105
106    /// Set cell text color (RGB hex format, e.g., "FF0000" or "#FF0000")
107    pub fn text_color(mut self, color: &str) -> Self {
108        self.text_color = Some(color.trim_start_matches('#').to_uppercase());
109        self
110    }
111
112    /// Set cell background color (RGB hex format, e.g., "FF0000" or "#FF0000")
113    pub fn background_color(mut self, color: &str) -> Self {
114        self.background_color = Some(color.trim_start_matches('#').to_uppercase());
115        self
116    }
117
118    /// Set font size in points
119    pub fn font_size(mut self, size: u32) -> Self {
120        self.font_size = Some(size);
121        self
122    }
123
124    /// Set font family name
125    pub fn font_family(mut self, family: &str) -> Self {
126        self.font_family = Some(family.to_string());
127        self
128    }
129
130    /// Set horizontal text alignment
131    pub fn align(mut self, align: CellAlign) -> Self {
132        self.align = align;
133        self
134    }
135
136    /// Set horizontal text alignment to left
137    pub fn align_left(mut self) -> Self {
138        self.align = CellAlign::Left;
139        self
140    }
141
142    /// Set horizontal text alignment to right
143    pub fn align_right(mut self) -> Self {
144        self.align = CellAlign::Right;
145        self
146    }
147
148    /// Set horizontal text alignment to center
149    pub fn align_center(mut self) -> Self {
150        self.align = CellAlign::Center;
151        self
152    }
153
154    /// Set vertical text alignment
155    pub fn valign(mut self, valign: CellVAlign) -> Self {
156        self.valign = valign;
157        self
158    }
159
160    /// Set vertical text alignment to top
161    pub fn valign_top(mut self) -> Self {
162        self.valign = CellVAlign::Top;
163        self
164    }
165
166    /// Set vertical text alignment to bottom
167    pub fn valign_bottom(mut self) -> Self {
168        self.valign = CellVAlign::Bottom;
169        self
170    }
171
172    /// Enable or disable text wrapping
173    pub fn wrap(mut self, wrap: bool) -> Self {
174        self.wrap_text = wrap;
175        self
176    }
177
178    /// Set row span (number of rows to merge down)
179    pub fn with_row_span(mut self, span: u32) -> Self {
180        self.row_span = span;
181        self
182    }
183
184    /// Set column span (number of columns to merge right)
185    pub fn with_col_span(mut self, span: u32) -> Self {
186        self.col_span = span;
187        self
188    }
189
190    /// Set vertical merge flag (for cells covered by row span)
191    pub fn with_v_merge(mut self) -> Self {
192        self.v_merge = true;
193        self
194    }
195
196    /// Set horizontal merge flag (for cells covered by col span)
197    pub fn with_h_merge(mut self) -> Self {
198        self.h_merge = true;
199        self
200    }
201}
202
203/// Table row
204#[derive(Clone, Debug)]
205pub struct TableRow {
206    pub cells: Vec<TableCell>,
207    pub height: Option<u32>, // in EMU
208}
209
210impl TableRow {
211    /// Create a new table row
212    pub fn new(cells: Vec<TableCell>) -> Self {
213        TableRow {
214            cells,
215            height: None,
216        }
217    }
218
219    /// Set row height
220    pub fn with_height(mut self, height: u32) -> Self {
221        self.height = Some(height);
222        self
223    }
224}
225
226/// Table definition
227#[derive(Clone, Debug)]
228pub struct Table {
229    pub rows: Vec<TableRow>,
230    pub column_widths: Vec<u32>, // in EMU
231    pub x: u32,                  // Position X in EMU
232    pub y: u32,                  // Position Y in EMU
233}
234
235impl Table {
236    /// Create a new table
237    pub fn new(rows: Vec<TableRow>, column_widths: Vec<u32>, x: u32, y: u32) -> Self {
238        Table {
239            rows,
240            column_widths,
241            x,
242            y,
243        }
244    }
245
246    /// Get number of columns
247    pub fn column_count(&self) -> usize {
248        self.column_widths.len()
249    }
250
251    /// Get number of rows
252    pub fn row_count(&self) -> usize {
253        self.rows.len()
254    }
255
256    /// Get total table width (sum of column widths)
257    pub fn width(&self) -> u32 {
258        self.column_widths.iter().sum()
259    }
260
261    /// Get total table height (sum of row heights)
262    pub fn height(&self) -> u32 {
263        self.rows
264            .iter()
265            .map(|r| r.height.unwrap_or(400000))
266            .sum()
267    }
268
269    /// Create a simple table from 2D data
270    pub fn from_data(data: Vec<Vec<&str>>, column_widths: Vec<u32>, x: u32, y: u32) -> Self {
271        let rows = data
272            .into_iter()
273            .map(|row| {
274                let cells = row
275                    .into_iter()
276                    .map(|cell| TableCell::new(cell))
277                    .collect();
278                TableRow::new(cells)
279            })
280            .collect();
281
282        Table {
283            rows,
284            column_widths,
285            x,
286            y,
287        }
288    }
289}
290
291/// Table builder for fluent API
292pub struct TableBuilder {
293    rows: Vec<TableRow>,
294    column_widths: Vec<u32>,
295    x: u32,
296    y: u32,
297}
298
299impl TableBuilder {
300    /// Create a new table builder
301    pub fn new(column_widths: Vec<u32>) -> Self {
302        TableBuilder {
303            rows: Vec::new(),
304            column_widths,
305            x: 0,
306            y: 0,
307        }
308    }
309
310    /// Set table position
311    pub fn position(mut self, x: u32, y: u32) -> Self {
312        self.x = x;
313        self.y = y;
314        self
315    }
316
317    /// Add a row
318    pub fn add_row(mut self, row: TableRow) -> Self {
319        self.rows.push(row);
320        self
321    }
322
323    /// Add a simple row from strings
324    pub fn add_simple_row(mut self, cells: Vec<&str>) -> Self {
325        let row = TableRow::new(
326            cells
327                .into_iter()
328                .map(|c| TableCell::new(c))
329                .collect(),
330        );
331        self.rows.push(row);
332        self
333    }
334
335    /// Build the table
336    pub fn build(self) -> Table {
337        Table {
338            rows: self.rows,
339            column_widths: self.column_widths,
340            x: self.x,
341            y: self.y,
342        }
343    }
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349
350    #[test]
351    fn test_table_cell_builder() {
352        let cell = TableCell::new("Header")
353            .bold()
354            .italic()
355            .underline()
356            .text_color("FF0000")
357            .background_color("0000FF")
358            .font_size(24)
359            .font_family("Arial");
360        assert_eq!(cell.text, "Header");
361        assert!(cell.bold);
362        assert!(cell.italic);
363        assert!(cell.underline);
364        assert_eq!(cell.text_color, Some("FF0000".to_string()));
365        assert_eq!(cell.background_color, Some("0000FF".to_string()));
366        assert_eq!(cell.font_size, Some(24));
367        assert_eq!(cell.font_family, Some("Arial".to_string()));
368    }
369
370    #[test]
371    fn test_table_row() {
372        let cells = vec![TableCell::new("A"), TableCell::new("B")];
373        let row = TableRow::new(cells).with_height(500000);
374        assert_eq!(row.cells.len(), 2);
375        assert_eq!(row.height, Some(500000));
376    }
377
378    #[test]
379    fn test_table_from_data() {
380        let data = vec![
381            vec!["Name", "Age"],
382            vec!["Alice", "30"],
383            vec!["Bob", "25"],
384        ];
385        let table = Table::from_data(data, vec![1000000, 1000000], 0, 0);
386        assert_eq!(table.row_count(), 3);
387        assert_eq!(table.column_count(), 2);
388    }
389
390    #[test]
391    fn test_table_builder() {
392        let table = TableBuilder::new(vec![1000000, 1000000])
393            .position(100000, 200000)
394            .add_simple_row(vec!["Header 1", "Header 2"])
395            .add_simple_row(vec!["Cell 1", "Cell 2"])
396            .build();
397
398        assert_eq!(table.row_count(), 2);
399        assert_eq!(table.column_count(), 2);
400        assert_eq!(table.x, 100000);
401        assert_eq!(table.y, 200000);
402    }
403}