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}
60
61impl TableCell {
62    /// Create a new table cell
63    pub fn new(text: &str) -> Self {
64        TableCell {
65            text: text.to_string(),
66            bold: false,
67            italic: false,
68            underline: false,
69            text_color: None,
70            background_color: None,
71            font_size: None,
72            font_family: None,
73            align: CellAlign::Center,
74            valign: CellVAlign::Middle,
75            wrap_text: true,
76        }
77    }
78
79    /// Set cell text as bold
80    pub fn bold(mut self) -> Self {
81        self.bold = true;
82        self
83    }
84
85    /// Set cell text as italic
86    pub fn italic(mut self) -> Self {
87        self.italic = true;
88        self
89    }
90
91    /// Set cell text as underlined
92    pub fn underline(mut self) -> Self {
93        self.underline = true;
94        self
95    }
96
97    /// Set cell text color (RGB hex format, e.g., "FF0000" or "#FF0000")
98    pub fn text_color(mut self, color: &str) -> Self {
99        self.text_color = Some(color.trim_start_matches('#').to_uppercase());
100        self
101    }
102
103    /// Set cell background color (RGB hex format, e.g., "FF0000" or "#FF0000")
104    pub fn background_color(mut self, color: &str) -> Self {
105        self.background_color = Some(color.trim_start_matches('#').to_uppercase());
106        self
107    }
108
109    /// Set font size in points
110    pub fn font_size(mut self, size: u32) -> Self {
111        self.font_size = Some(size);
112        self
113    }
114
115    /// Set font family name
116    pub fn font_family(mut self, family: &str) -> Self {
117        self.font_family = Some(family.to_string());
118        self
119    }
120
121    /// Set horizontal text alignment
122    pub fn align(mut self, align: CellAlign) -> Self {
123        self.align = align;
124        self
125    }
126
127    /// Set horizontal text alignment to left
128    pub fn align_left(mut self) -> Self {
129        self.align = CellAlign::Left;
130        self
131    }
132
133    /// Set horizontal text alignment to right
134    pub fn align_right(mut self) -> Self {
135        self.align = CellAlign::Right;
136        self
137    }
138
139    /// Set horizontal text alignment to center
140    pub fn align_center(mut self) -> Self {
141        self.align = CellAlign::Center;
142        self
143    }
144
145    /// Set vertical text alignment
146    pub fn valign(mut self, valign: CellVAlign) -> Self {
147        self.valign = valign;
148        self
149    }
150
151    /// Set vertical text alignment to top
152    pub fn valign_top(mut self) -> Self {
153        self.valign = CellVAlign::Top;
154        self
155    }
156
157    /// Set vertical text alignment to bottom
158    pub fn valign_bottom(mut self) -> Self {
159        self.valign = CellVAlign::Bottom;
160        self
161    }
162
163    /// Enable or disable text wrapping
164    pub fn wrap(mut self, wrap: bool) -> Self {
165        self.wrap_text = wrap;
166        self
167    }
168}
169
170/// Table row
171#[derive(Clone, Debug)]
172pub struct TableRow {
173    pub cells: Vec<TableCell>,
174    pub height: Option<u32>, // in EMU
175}
176
177impl TableRow {
178    /// Create a new table row
179    pub fn new(cells: Vec<TableCell>) -> Self {
180        TableRow {
181            cells,
182            height: None,
183        }
184    }
185
186    /// Set row height
187    pub fn with_height(mut self, height: u32) -> Self {
188        self.height = Some(height);
189        self
190    }
191}
192
193/// Table definition
194#[derive(Clone, Debug)]
195pub struct Table {
196    pub rows: Vec<TableRow>,
197    pub column_widths: Vec<u32>, // in EMU
198    pub x: u32,                  // Position X in EMU
199    pub y: u32,                  // Position Y in EMU
200}
201
202impl Table {
203    /// Create a new table
204    pub fn new(rows: Vec<TableRow>, column_widths: Vec<u32>, x: u32, y: u32) -> Self {
205        Table {
206            rows,
207            column_widths,
208            x,
209            y,
210        }
211    }
212
213    /// Get number of columns
214    pub fn column_count(&self) -> usize {
215        self.column_widths.len()
216    }
217
218    /// Get number of rows
219    pub fn row_count(&self) -> usize {
220        self.rows.len()
221    }
222
223    /// Get total table width (sum of column widths)
224    pub fn width(&self) -> u32 {
225        self.column_widths.iter().sum()
226    }
227
228    /// Get total table height (sum of row heights)
229    pub fn height(&self) -> u32 {
230        self.rows
231            .iter()
232            .map(|r| r.height.unwrap_or(400000))
233            .sum()
234    }
235
236    /// Create a simple table from 2D data
237    pub fn from_data(data: Vec<Vec<&str>>, column_widths: Vec<u32>, x: u32, y: u32) -> Self {
238        let rows = data
239            .into_iter()
240            .map(|row| {
241                let cells = row
242                    .into_iter()
243                    .map(|cell| TableCell::new(cell))
244                    .collect();
245                TableRow::new(cells)
246            })
247            .collect();
248
249        Table {
250            rows,
251            column_widths,
252            x,
253            y,
254        }
255    }
256}
257
258/// Table builder for fluent API
259pub struct TableBuilder {
260    rows: Vec<TableRow>,
261    column_widths: Vec<u32>,
262    x: u32,
263    y: u32,
264}
265
266impl TableBuilder {
267    /// Create a new table builder
268    pub fn new(column_widths: Vec<u32>) -> Self {
269        TableBuilder {
270            rows: Vec::new(),
271            column_widths,
272            x: 0,
273            y: 0,
274        }
275    }
276
277    /// Set table position
278    pub fn position(mut self, x: u32, y: u32) -> Self {
279        self.x = x;
280        self.y = y;
281        self
282    }
283
284    /// Add a row
285    pub fn add_row(mut self, row: TableRow) -> Self {
286        self.rows.push(row);
287        self
288    }
289
290    /// Add a simple row from strings
291    pub fn add_simple_row(mut self, cells: Vec<&str>) -> Self {
292        let row = TableRow::new(
293            cells
294                .into_iter()
295                .map(|c| TableCell::new(c))
296                .collect(),
297        );
298        self.rows.push(row);
299        self
300    }
301
302    /// Build the table
303    pub fn build(self) -> Table {
304        Table {
305            rows: self.rows,
306            column_widths: self.column_widths,
307            x: self.x,
308            y: self.y,
309        }
310    }
311}
312
313#[cfg(test)]
314mod tests {
315    use super::*;
316
317    #[test]
318    fn test_table_cell_builder() {
319        let cell = TableCell::new("Header")
320            .bold()
321            .italic()
322            .underline()
323            .text_color("FF0000")
324            .background_color("0000FF")
325            .font_size(24)
326            .font_family("Arial");
327        assert_eq!(cell.text, "Header");
328        assert!(cell.bold);
329        assert!(cell.italic);
330        assert!(cell.underline);
331        assert_eq!(cell.text_color, Some("FF0000".to_string()));
332        assert_eq!(cell.background_color, Some("0000FF".to_string()));
333        assert_eq!(cell.font_size, Some(24));
334        assert_eq!(cell.font_family, Some("Arial".to_string()));
335    }
336
337    #[test]
338    fn test_table_row() {
339        let cells = vec![TableCell::new("A"), TableCell::new("B")];
340        let row = TableRow::new(cells).with_height(500000);
341        assert_eq!(row.cells.len(), 2);
342        assert_eq!(row.height, Some(500000));
343    }
344
345    #[test]
346    fn test_table_from_data() {
347        let data = vec![
348            vec!["Name", "Age"],
349            vec!["Alice", "30"],
350            vec!["Bob", "25"],
351        ];
352        let table = Table::from_data(data, vec![1000000, 1000000], 0, 0);
353        assert_eq!(table.row_count(), 3);
354        assert_eq!(table.column_count(), 2);
355    }
356
357    #[test]
358    fn test_table_builder() {
359        let table = TableBuilder::new(vec![1000000, 1000000])
360            .position(100000, 200000)
361            .add_simple_row(vec!["Header 1", "Header 2"])
362            .add_simple_row(vec!["Cell 1", "Cell 2"])
363            .build();
364
365        assert_eq!(table.row_count(), 2);
366        assert_eq!(table.column_count(), 2);
367        assert_eq!(table.x, 100000);
368        assert_eq!(table.y, 200000);
369    }
370}