Skip to main content

oxidize_pdf/advanced_tables/
table_builder.rs

1//! Advanced table builder for creating complex tables with professional styling
2
3use super::cell_style::CellStyle;
4use super::error::TableError;
5use super::header_builder::HeaderBuilder;
6use crate::graphics::Color;
7use std::collections::HashMap;
8
9/// Column definition for advanced tables
10#[derive(Debug, Clone)]
11pub struct Column {
12    /// Column header text
13    pub header: String,
14    /// Column width in points
15    pub width: f64,
16    /// Default style for cells in this column
17    pub default_style: Option<CellStyle>,
18    /// Whether this column can resize automatically
19    pub auto_resize: bool,
20    /// Minimum width for auto-resizing columns
21    pub min_width: Option<f64>,
22    /// Maximum width for auto-resizing columns
23    pub max_width: Option<f64>,
24}
25
26impl Column {
27    /// Create a new column
28    pub fn new<S: Into<String>>(header: S, width: f64) -> Self {
29        Self {
30            header: header.into(),
31            width,
32            default_style: None,
33            auto_resize: false,
34            min_width: None,
35            max_width: None,
36        }
37    }
38
39    /// Set default style for this column
40    pub fn with_style(mut self, style: CellStyle) -> Self {
41        self.default_style = Some(style);
42        self
43    }
44
45    /// Enable auto-resizing for this column
46    pub fn auto_resize(mut self, min_width: Option<f64>, max_width: Option<f64>) -> Self {
47        self.auto_resize = true;
48        self.min_width = min_width;
49        self.max_width = max_width;
50        self
51    }
52}
53
54/// Cell data with optional styling and spanning
55#[derive(Debug, Clone)]
56pub struct CellData {
57    /// Cell content
58    pub content: String,
59    /// Optional custom style for this cell
60    pub style: Option<CellStyle>,
61    /// Number of columns this cell spans (1 = no spanning)
62    pub colspan: usize,
63    /// Number of rows this cell spans (1 = no spanning)
64    pub rowspan: usize,
65}
66
67impl CellData {
68    /// Create a new cell with text content
69    pub fn new<S: Into<String>>(content: S) -> Self {
70        Self {
71            content: content.into(),
72            style: None,
73            colspan: 1,
74            rowspan: 1,
75        }
76    }
77
78    /// Set custom style for this cell
79    pub fn with_style(mut self, style: CellStyle) -> Self {
80        self.style = Some(style);
81        self
82    }
83
84    /// Set column span for this cell
85    pub fn colspan(mut self, span: usize) -> Self {
86        self.colspan = span.max(1);
87        self
88    }
89
90    /// Set row span for this cell
91    pub fn rowspan(mut self, span: usize) -> Self {
92        self.rowspan = span.max(1);
93        self
94    }
95}
96
97/// Row data with optional styling
98#[derive(Debug, Clone)]
99pub struct RowData {
100    /// Cells in this row
101    pub cells: Vec<CellData>,
102    /// Optional style for the entire row
103    pub style: Option<CellStyle>,
104    /// Minimum height for this row
105    pub min_height: Option<f64>,
106}
107
108impl RowData {
109    /// Create a new row from string content
110    pub fn from_strings(content: Vec<&str>) -> Self {
111        let cells = content.into_iter().map(CellData::new).collect();
112
113        Self {
114            cells,
115            style: None,
116            min_height: None,
117        }
118    }
119
120    /// Create a new row from cell data
121    pub fn from_cells(cells: Vec<CellData>) -> Self {
122        Self {
123            cells,
124            style: None,
125            min_height: None,
126        }
127    }
128
129    /// Set style for the entire row
130    pub fn with_style(mut self, style: CellStyle) -> Self {
131        self.style = Some(style);
132        self
133    }
134
135    /// Set minimum height for this row
136    pub fn min_height(mut self, height: f64) -> Self {
137        self.min_height = Some(height);
138        self
139    }
140}
141
142/// Complete advanced table configuration
143#[derive(Debug, Clone)]
144pub struct AdvancedTable {
145    /// Table title
146    pub title: Option<String>,
147    /// X position on page
148    pub x: f64,
149    /// Y position on page
150    pub y: f64,
151    /// Table columns definition
152    pub columns: Vec<Column>,
153    /// Table rows data
154    pub rows: Vec<RowData>,
155    /// Header configuration
156    pub header: Option<HeaderBuilder>,
157    /// Whether to show the table header
158    pub show_header: bool,
159    /// Default cell style
160    pub default_style: CellStyle,
161    /// Header style
162    pub header_style: CellStyle,
163    /// Zebra striping configuration
164    pub zebra_striping: Option<ZebraConfig>,
165    /// Table-wide border style
166    pub table_border: bool,
167    /// Spacing between cells
168    pub cell_spacing: f64,
169    /// Total table width (auto-calculated if None)
170    pub total_width: Option<f64>,
171    /// Whether to repeat headers on page breaks
172    pub repeat_headers: bool,
173    /// Styles for specific cells (row, col) -> style
174    pub cell_styles: HashMap<(usize, usize), CellStyle>,
175}
176
177/// Zebra striping configuration
178#[derive(Debug, Clone)]
179pub struct ZebraConfig {
180    /// Background color for odd rows
181    pub odd_color: Option<Color>,
182    /// Background color for even rows
183    pub even_color: Option<Color>,
184    /// Start with odd or even row
185    pub start_with_odd: bool,
186}
187
188impl ZebraConfig {
189    /// Create zebra striping with alternating colors
190    pub fn new(odd_color: Option<Color>, even_color: Option<Color>) -> Self {
191        Self {
192            odd_color,
193            even_color,
194            start_with_odd: true,
195        }
196    }
197
198    /// Simple zebra striping with one alternating color
199    pub fn simple(color: Color) -> Self {
200        Self::new(Some(color), None)
201    }
202
203    /// Get color for a specific row
204    pub fn get_color_for_row(&self, row_index: usize) -> Option<Color> {
205        let is_odd = (row_index % 2) == (if self.start_with_odd { 1 } else { 0 });
206        if is_odd {
207            self.odd_color
208        } else {
209            self.even_color
210        }
211    }
212}
213
214/// Builder for creating advanced tables with fluent API
215pub struct AdvancedTableBuilder {
216    table: AdvancedTable,
217}
218
219impl AdvancedTableBuilder {
220    /// Create a new table builder
221    pub fn new() -> Self {
222        Self {
223            table: AdvancedTable {
224                title: None,
225                x: 0.0,
226                y: 0.0,
227                columns: Vec::new(),
228                rows: Vec::new(),
229                header: None,
230                show_header: true,
231                default_style: CellStyle::data(),
232                header_style: CellStyle::header(),
233                zebra_striping: None,
234                table_border: true,
235                cell_spacing: 0.0,
236                total_width: None,
237                repeat_headers: false,
238                cell_styles: HashMap::new(),
239            },
240        }
241    }
242
243    /// Add a column to the table
244    pub fn add_column<S: Into<String>>(mut self, header: S, width: f64) -> Self {
245        self.table.columns.push(Column::new(header, width));
246        self
247    }
248
249    /// Add a column with custom styling
250    pub fn add_styled_column<S: Into<String>>(
251        mut self,
252        header: S,
253        width: f64,
254        style: CellStyle,
255    ) -> Self {
256        self.table
257            .columns
258            .push(Column::new(header, width).with_style(style));
259        self
260    }
261
262    /// Set columns from a list of headers with equal widths
263    pub fn columns_equal_width(mut self, headers: Vec<&str>, total_width: f64) -> Self {
264        let column_width = total_width / headers.len() as f64;
265        self.table.columns = headers
266            .into_iter()
267            .map(|header| Column::new(header, column_width))
268            .collect();
269        self.table.total_width = Some(total_width);
270        self
271    }
272
273    /// Add a simple row from string content
274    pub fn add_row(mut self, content: Vec<&str>) -> Self {
275        self.table.rows.push(RowData::from_strings(content));
276        self
277    }
278
279    pub fn add_row_with_min_height(mut self, content: Vec<&str>, min_height: f64) -> Self {
280        self.table
281            .rows
282            .push(RowData::from_strings(content).min_height(min_height));
283        self
284    }
285
286    /// Add a row with cell data
287    pub fn add_row_cells(mut self, cells: Vec<CellData>) -> Self {
288        self.table.rows.push(RowData::from_cells(cells));
289        self
290    }
291
292    /// Add a styled row
293    pub fn add_styled_row(mut self, content: Vec<&str>, style: CellStyle) -> Self {
294        self.table
295            .rows
296            .push(RowData::from_strings(content).with_style(style));
297        self
298    }
299
300    /// Set default cell style
301    pub fn default_style(mut self, style: CellStyle) -> Self {
302        self.table.default_style = style;
303        self
304    }
305
306    /// Set data cell style (alias for default_style)
307    pub fn data_style(mut self, style: CellStyle) -> Self {
308        self.table.default_style = style;
309        self
310    }
311
312    /// Set header style
313    pub fn header_style(mut self, style: CellStyle) -> Self {
314        self.table.header_style = style;
315        self
316    }
317
318    /// Control header visibility
319    pub fn show_header(mut self, show: bool) -> Self {
320        self.table.show_header = show;
321        self
322    }
323
324    /// Set table title
325    pub fn title<S: Into<String>>(mut self, title: S) -> Self {
326        self.table.title = Some(title.into());
327        self
328    }
329
330    /// Set table columns from (header, width) tuples
331    pub fn columns(mut self, column_specs: Vec<(&str, f64)>) -> Self {
332        self.table.columns = column_specs
333            .into_iter()
334            .map(|(header, width)| Column::new(header, width))
335            .collect();
336        self
337    }
338
339    /// Set table position on page
340    pub fn position(mut self, x: f64, y: f64) -> Self {
341        self.table.x = x;
342        self.table.y = y;
343        self
344    }
345
346    /// Add a complex header using HeaderBuilder
347    pub fn complex_header(mut self, header: HeaderBuilder) -> Self {
348        // Auto-generate columns from header if table has no columns
349        if self.table.columns.is_empty() {
350            let column_count = header.total_columns;
351            for i in 0..column_count {
352                self.table.columns.push(Column::new(
353                    format!("Column {}", i + 1),
354                    100.0, // Default width
355                ));
356            }
357        }
358        self.table.header = Some(header);
359        self
360    }
361
362    /// Enable zebra stripes
363    pub fn zebra_stripes(mut self, enabled: bool, color: Color) -> Self {
364        if enabled {
365            self.table.zebra_striping = Some(ZebraConfig::simple(color));
366        } else {
367            self.table.zebra_striping = None;
368        }
369        self
370    }
371
372    /// Add row with custom style
373    pub fn add_row_with_style(mut self, content: Vec<&str>, style: CellStyle) -> Self {
374        let mut row = RowData::from_strings(content);
375        row = row.with_style(style);
376        self.table.rows.push(row);
377        self
378    }
379
380    /// Add row with mixed cell styles
381    pub fn add_row_with_mixed_styles(mut self, cells: Vec<(CellStyle, &str)>) -> Self {
382        let cell_data: Vec<CellData> = cells
383            .into_iter()
384            .map(|(style, content)| CellData::new(content.to_string()).with_style(style))
385            .collect();
386        self.table.rows.push(RowData::from_cells(cell_data));
387        self
388    }
389
390    /// Build with error handling (for compatibility with tests)
391    pub fn build(self) -> Result<AdvancedTable, TableError> {
392        if self.table.columns.is_empty() {
393            return Err(TableError::NoColumns);
394        }
395        Ok(self.table)
396    }
397
398    /// Enable zebra striping
399    pub fn zebra_striping(mut self, color: Color) -> Self {
400        self.table.zebra_striping = Some(ZebraConfig::simple(color));
401        self
402    }
403
404    /// Enable custom zebra striping
405    pub fn zebra_striping_custom(mut self, config: ZebraConfig) -> Self {
406        self.table.zebra_striping = Some(config);
407        self
408    }
409
410    /// Enable or disable table border
411    pub fn table_border(mut self, enabled: bool) -> Self {
412        self.table.table_border = enabled;
413        self
414    }
415
416    /// Set cell spacing
417    pub fn cell_spacing(mut self, spacing: f64) -> Self {
418        self.table.cell_spacing = spacing;
419        self
420    }
421
422    /// Set total table width
423    pub fn total_width(mut self, width: f64) -> Self {
424        self.table.total_width = Some(width);
425        self
426    }
427
428    /// Enable header repetition on page breaks
429    pub fn repeat_headers(mut self, repeat: bool) -> Self {
430        self.table.repeat_headers = repeat;
431        self
432    }
433
434    /// Set style for a specific cell
435    pub fn set_cell_style(mut self, row: usize, col: usize, style: CellStyle) -> Self {
436        self.table.cell_styles.insert((row, col), style);
437        self
438    }
439
440    /// Add bulk data from a 2D vector
441    pub fn add_data(mut self, data: Vec<Vec<&str>>) -> Self {
442        for row in data {
443            self = self.add_row(row);
444        }
445        self
446    }
447
448    /// Create a financial table with common styling
449    pub fn financial_table(self) -> Self {
450        self.header_style(
451            CellStyle::header()
452                .background_color(Color::rgb(0.2, 0.4, 0.8))
453                .text_color(Color::white()),
454        )
455        .default_style(CellStyle::data())
456        .zebra_striping(Color::rgb(0.97, 0.97, 0.97))
457        .table_border(true)
458    }
459
460    /// Create a data table with minimal styling
461    pub fn minimal_table(self) -> Self {
462        self.header_style(
463            CellStyle::new()
464                .font_size(12.0)
465                .background_color(Color::rgb(0.95, 0.95, 0.95)),
466        )
467        .default_style(CellStyle::data())
468        .table_border(false)
469        .cell_spacing(2.0)
470    }
471}
472
473impl Default for AdvancedTableBuilder {
474    fn default() -> Self {
475        Self::new()
476    }
477}
478
479impl AdvancedTable {
480    /// Get the total calculated width of the table
481    pub fn calculate_width(&self) -> f64 {
482        if let Some(width) = self.total_width {
483            width
484        } else {
485            self.columns.iter().map(|col| col.width).sum()
486        }
487    }
488
489    /// Get the number of rows (excluding headers)
490    pub fn row_count(&self) -> usize {
491        self.rows.len()
492    }
493
494    /// Get the number of columns
495    pub fn column_count(&self) -> usize {
496        self.columns.len()
497    }
498
499    /// Get style for a specific cell, considering row/column defaults and zebra striping
500    pub fn get_cell_style(&self, row: usize, col: usize) -> CellStyle {
501        // Priority: specific cell style > row style > column style > zebra striping > default
502
503        // Check specific cell style
504        if let Some(cell_style) = self.cell_styles.get(&(row, col)) {
505            return cell_style.clone();
506        }
507
508        // Check row style
509        if let Some(row_data) = self.rows.get(row) {
510            if let Some(row_style) = &row_data.style {
511                return row_style.clone();
512            }
513        }
514
515        // Check column style
516        if let Some(column) = self.columns.get(col) {
517            if let Some(column_style) = &column.default_style {
518                let mut style = column_style.clone();
519
520                // Apply zebra striping if configured
521                if let Some(zebra) = &self.zebra_striping {
522                    if let Some(color) = zebra.get_color_for_row(row) {
523                        style.background_color = Some(color);
524                    }
525                }
526
527                return style;
528            }
529        }
530
531        // Apply zebra striping to default style
532        let mut style = self.default_style.clone();
533        if let Some(zebra) = &self.zebra_striping {
534            if let Some(color) = zebra.get_color_for_row(row) {
535                style.background_color = Some(color);
536            }
537        }
538
539        style
540    }
541
542    /// Get a reference to the style for a specific cell, considering row/column defaults.
543    ///
544    /// Returns a reference for the common cases (cell-specific, row, or column style).
545    /// When zebra striping would modify the style, falls back to the base column or default style
546    /// without applying the zebra background. Use [`get_cell_style`] when zebra colors are needed.
547    pub fn get_cell_style_ref(&self, row: usize, col: usize) -> &CellStyle {
548        // Priority: specific cell style > row style > column style > default
549
550        if let Some(cell_style) = self.cell_styles.get(&(row, col)) {
551            return cell_style;
552        }
553
554        if let Some(row_data) = self.rows.get(row) {
555            if let Some(row_style) = &row_data.style {
556                return row_style;
557            }
558        }
559
560        if let Some(column) = self.columns.get(col) {
561            if let Some(column_style) = &column.default_style {
562                // Return the base column style without zebra mutation.
563                // Callers needing zebra background should use `get_cell_style`.
564                return column_style;
565            }
566        }
567
568        &self.default_style
569    }
570
571    /// Validate table structure (e.g., consistent column counts with colspan/rowspan)
572    pub fn validate(&self) -> Result<(), TableError> {
573        let expected_cols = self.columns.len();
574        // Track the last row each column is occupied through (exclusive upper bound)
575        let mut rowspan_end: Vec<usize> = vec![0; expected_cols];
576
577        for (row_idx, row) in self.rows.iter().enumerate() {
578            // Count columns occupied by rowspan from previous rows
579            let occupied_by_rowspan: usize =
580                rowspan_end.iter().filter(|&&end| end > row_idx).count();
581
582            // Sum of cell colspans + occupied columns must equal total columns
583            let total_colspan: usize = row.cells.iter().map(|c| c.colspan).sum();
584            if total_colspan + occupied_by_rowspan != expected_cols {
585                return Err(TableError::ColumnMismatch {
586                    row: row_idx,
587                    found: total_colspan + occupied_by_rowspan,
588                    expected: expected_cols,
589                });
590            }
591
592            // Record new rowspans from this row's cells
593            let mut actual_col = 0;
594            for cell in &row.cells {
595                // Skip columns occupied by rowspan
596                while actual_col < expected_cols && rowspan_end[actual_col] > row_idx {
597                    actual_col += 1;
598                }
599                if cell.rowspan > 1 {
600                    for c in actual_col..(actual_col + cell.colspan).min(expected_cols) {
601                        rowspan_end[c] = row_idx + cell.rowspan;
602                    }
603                }
604                actual_col += cell.colspan;
605            }
606        }
607
608        Ok(())
609    }
610}
611
612#[cfg(test)]
613mod tests {
614    use super::*;
615
616    // =============================================================================
617    // Column tests
618    // =============================================================================
619
620    #[test]
621    fn test_column_new() {
622        let col = Column::new("Header", 100.0);
623        assert_eq!(col.header, "Header");
624        assert_eq!(col.width, 100.0);
625        assert!(col.default_style.is_none());
626        assert!(!col.auto_resize);
627        assert!(col.min_width.is_none());
628        assert!(col.max_width.is_none());
629    }
630
631    #[test]
632    fn test_column_with_style() {
633        let style = CellStyle::data();
634        let col = Column::new("Header", 100.0).with_style(style.clone());
635        assert!(col.default_style.is_some());
636        // font_size is Option<f64>
637        assert_eq!(col.default_style.unwrap().font_size, style.font_size);
638    }
639
640    #[test]
641    fn test_column_auto_resize() {
642        let col = Column::new("Header", 100.0).auto_resize(Some(50.0), Some(200.0));
643        assert!(col.auto_resize);
644        assert_eq!(col.min_width, Some(50.0));
645        assert_eq!(col.max_width, Some(200.0));
646    }
647
648    #[test]
649    fn test_column_auto_resize_no_limits() {
650        let col = Column::new("Header", 100.0).auto_resize(None, None);
651        assert!(col.auto_resize);
652        assert!(col.min_width.is_none());
653        assert!(col.max_width.is_none());
654    }
655
656    // =============================================================================
657    // CellData tests
658    // =============================================================================
659
660    #[test]
661    fn test_cell_data_new() {
662        let cell = CellData::new("Content");
663        assert_eq!(cell.content, "Content");
664        assert!(cell.style.is_none());
665        assert_eq!(cell.colspan, 1);
666        assert_eq!(cell.rowspan, 1);
667    }
668
669    #[test]
670    fn test_cell_data_with_style() {
671        let style = CellStyle::header();
672        let cell = CellData::new("Content").with_style(style);
673        assert!(cell.style.is_some());
674    }
675
676    #[test]
677    fn test_cell_data_colspan() {
678        let cell = CellData::new("Content").colspan(3);
679        assert_eq!(cell.colspan, 3);
680    }
681
682    #[test]
683    fn test_cell_data_colspan_min_is_one() {
684        // colspan(0) should be clamped to 1
685        let cell = CellData::new("Content").colspan(0);
686        assert_eq!(cell.colspan, 1);
687    }
688
689    #[test]
690    fn test_cell_data_rowspan() {
691        let cell = CellData::new("Content").rowspan(2);
692        assert_eq!(cell.rowspan, 2);
693    }
694
695    #[test]
696    fn test_cell_data_rowspan_min_is_one() {
697        // rowspan(0) should be clamped to 1
698        let cell = CellData::new("Content").rowspan(0);
699        assert_eq!(cell.rowspan, 1);
700    }
701
702    #[test]
703    fn test_cell_data_combined_span() {
704        let cell = CellData::new("Merged").colspan(2).rowspan(3);
705        assert_eq!(cell.colspan, 2);
706        assert_eq!(cell.rowspan, 3);
707    }
708
709    // =============================================================================
710    // RowData tests
711    // =============================================================================
712
713    #[test]
714    fn test_row_data_from_strings() {
715        let row = RowData::from_strings(vec!["A", "B", "C"]);
716        assert_eq!(row.cells.len(), 3);
717        assert_eq!(row.cells[0].content, "A");
718        assert_eq!(row.cells[1].content, "B");
719        assert_eq!(row.cells[2].content, "C");
720        assert!(row.style.is_none());
721        assert!(row.min_height.is_none());
722    }
723
724    #[test]
725    fn test_row_data_from_cells() {
726        let cells = vec![CellData::new("Cell1"), CellData::new("Cell2").colspan(2)];
727        let row = RowData::from_cells(cells);
728        assert_eq!(row.cells.len(), 2);
729        assert_eq!(row.cells[1].colspan, 2);
730    }
731
732    #[test]
733    fn test_row_data_with_style() {
734        let style = CellStyle::header();
735        let row = RowData::from_strings(vec!["A"]).with_style(style);
736        assert!(row.style.is_some());
737    }
738
739    #[test]
740    fn test_row_data_min_height() {
741        let row = RowData::from_strings(vec!["A"]).min_height(50.0);
742        assert_eq!(row.min_height, Some(50.0));
743    }
744
745    // =============================================================================
746    // ZebraConfig tests
747    // =============================================================================
748
749    #[test]
750    fn test_zebra_config_new() {
751        let odd = Color::rgb(0.9, 0.9, 0.9);
752        let even = Color::rgb(1.0, 1.0, 1.0);
753        let config = ZebraConfig::new(Some(odd), Some(even));
754        assert!(config.odd_color.is_some());
755        assert!(config.even_color.is_some());
756        assert!(config.start_with_odd);
757    }
758
759    #[test]
760    fn test_zebra_config_simple() {
761        let color = Color::rgb(0.95, 0.95, 0.95);
762        let config = ZebraConfig::simple(color);
763        assert!(config.odd_color.is_some());
764        assert!(config.even_color.is_none());
765    }
766
767    #[test]
768    fn test_zebra_config_get_color_for_row() {
769        let odd_color = Color::rgb(0.9, 0.9, 0.9);
770        let config = ZebraConfig::simple(odd_color);
771
772        // Row 0 is even (start_with_odd=true means odd rows get color)
773        assert!(config.get_color_for_row(0).is_none()); // even row
774        assert!(config.get_color_for_row(1).is_some()); // odd row
775        assert!(config.get_color_for_row(2).is_none()); // even row
776        assert!(config.get_color_for_row(3).is_some()); // odd row
777    }
778
779    #[test]
780    fn test_zebra_config_alternating() {
781        let odd = Color::rgb(0.9, 0.9, 0.9);
782        let even = Color::rgb(0.95, 0.95, 0.95);
783        let config = ZebraConfig::new(Some(odd), Some(even));
784
785        // Both even and odd rows should have colors
786        assert!(config.get_color_for_row(0).is_some()); // even row
787        assert!(config.get_color_for_row(1).is_some()); // odd row
788    }
789
790    // =============================================================================
791    // AdvancedTableBuilder tests
792    // =============================================================================
793
794    #[test]
795    fn test_builder_new() {
796        let builder = AdvancedTableBuilder::new();
797        let table = builder.add_column("Col1", 100.0).build().unwrap();
798        assert_eq!(table.columns.len(), 1);
799        assert!(table.rows.is_empty());
800    }
801
802    #[test]
803    fn test_builder_default() {
804        let builder = AdvancedTableBuilder::default();
805        assert!(builder.table.columns.is_empty());
806    }
807
808    #[test]
809    fn test_builder_add_column() {
810        let table = AdvancedTableBuilder::new()
811            .add_column("A", 50.0)
812            .add_column("B", 75.0)
813            .build()
814            .unwrap();
815        assert_eq!(table.columns.len(), 2);
816        assert_eq!(table.columns[0].width, 50.0);
817        assert_eq!(table.columns[1].width, 75.0);
818    }
819
820    #[test]
821    fn test_builder_add_styled_column() {
822        let style = CellStyle::header();
823        let table = AdvancedTableBuilder::new()
824            .add_styled_column("Header", 100.0, style)
825            .build()
826            .unwrap();
827        assert!(table.columns[0].default_style.is_some());
828    }
829
830    #[test]
831    fn test_builder_columns_equal_width() {
832        let table = AdvancedTableBuilder::new()
833            .columns_equal_width(vec!["A", "B", "C", "D"], 400.0)
834            .build()
835            .unwrap();
836        assert_eq!(table.columns.len(), 4);
837        assert_eq!(table.columns[0].width, 100.0);
838        assert_eq!(table.total_width, Some(400.0));
839    }
840
841    #[test]
842    fn test_builder_add_row() {
843        let table = AdvancedTableBuilder::new()
844            .add_column("A", 50.0)
845            .add_row(vec!["Value"])
846            .build()
847            .unwrap();
848        assert_eq!(table.rows.len(), 1);
849        assert_eq!(table.rows[0].cells[0].content, "Value");
850    }
851
852    #[test]
853    fn test_builder_add_row_with_min_height() {
854        let table = AdvancedTableBuilder::new()
855            .add_column("A", 50.0)
856            .add_row_with_min_height(vec!["Value"], 30.0)
857            .build()
858            .unwrap();
859        assert_eq!(table.rows[0].min_height, Some(30.0));
860    }
861
862    #[test]
863    fn test_builder_add_row_cells() {
864        let cells = vec![CellData::new("Cell1").colspan(2), CellData::new("Cell2")];
865        let table = AdvancedTableBuilder::new()
866            .add_column("A", 50.0)
867            .add_column("B", 50.0)
868            .add_column("C", 50.0)
869            .add_row_cells(cells)
870            .build()
871            .unwrap();
872        assert_eq!(table.rows[0].cells[0].colspan, 2);
873    }
874
875    #[test]
876    fn test_builder_add_styled_row() {
877        let style = CellStyle::header();
878        let table = AdvancedTableBuilder::new()
879            .add_column("A", 50.0)
880            .add_styled_row(vec!["Value"], style)
881            .build()
882            .unwrap();
883        assert!(table.rows[0].style.is_some());
884    }
885
886    #[test]
887    fn test_builder_default_style() {
888        let style = CellStyle::new().font_size(14.0);
889        let table = AdvancedTableBuilder::new()
890            .add_column("A", 50.0)
891            .default_style(style.clone())
892            .build()
893            .unwrap();
894        assert_eq!(table.default_style.font_size, Some(14.0));
895    }
896
897    #[test]
898    fn test_builder_data_style() {
899        let style = CellStyle::new().font_size(16.0);
900        let table = AdvancedTableBuilder::new()
901            .add_column("A", 50.0)
902            .data_style(style)
903            .build()
904            .unwrap();
905        assert_eq!(table.default_style.font_size, Some(16.0));
906    }
907
908    #[test]
909    fn test_builder_header_style() {
910        let style = CellStyle::new().font_size(18.0);
911        let table = AdvancedTableBuilder::new()
912            .add_column("A", 50.0)
913            .header_style(style)
914            .build()
915            .unwrap();
916        assert_eq!(table.header_style.font_size, Some(18.0));
917    }
918
919    #[test]
920    fn test_builder_show_header() {
921        let table = AdvancedTableBuilder::new()
922            .add_column("A", 50.0)
923            .show_header(false)
924            .build()
925            .unwrap();
926        assert!(!table.show_header);
927    }
928
929    #[test]
930    fn test_builder_title() {
931        let table = AdvancedTableBuilder::new()
932            .add_column("A", 50.0)
933            .title("My Table")
934            .build()
935            .unwrap();
936        assert_eq!(table.title, Some("My Table".to_string()));
937    }
938
939    #[test]
940    fn test_builder_columns() {
941        let table = AdvancedTableBuilder::new()
942            .columns(vec![("X", 30.0), ("Y", 40.0)])
943            .build()
944            .unwrap();
945        assert_eq!(table.columns.len(), 2);
946        assert_eq!(table.columns[0].header, "X");
947        assert_eq!(table.columns[1].header, "Y");
948    }
949
950    #[test]
951    fn test_builder_position() {
952        let table = AdvancedTableBuilder::new()
953            .add_column("A", 50.0)
954            .position(100.0, 200.0)
955            .build()
956            .unwrap();
957        assert_eq!(table.x, 100.0);
958        assert_eq!(table.y, 200.0);
959    }
960
961    #[test]
962    fn test_builder_zebra_stripes() {
963        let color = Color::rgb(0.95, 0.95, 0.95);
964        let table = AdvancedTableBuilder::new()
965            .add_column("A", 50.0)
966            .zebra_stripes(true, color)
967            .build()
968            .unwrap();
969        assert!(table.zebra_striping.is_some());
970        // Verify the ZebraConfig was set with the correct color
971        let zebra = table.zebra_striping.as_ref().unwrap();
972        assert_eq!(zebra.odd_color, Some(color));
973    }
974
975    #[test]
976    fn test_builder_zebra_stripes_disabled() {
977        let color = Color::rgb(0.95, 0.95, 0.95);
978        let table = AdvancedTableBuilder::new()
979            .add_column("A", 50.0)
980            .zebra_stripes(false, color)
981            .build()
982            .unwrap();
983        assert!(table.zebra_striping.is_none());
984    }
985
986    #[test]
987    fn test_builder_zebra_striping() {
988        let color = Color::rgb(0.9, 0.9, 0.9);
989        let table = AdvancedTableBuilder::new()
990            .add_column("A", 50.0)
991            .zebra_striping(color)
992            .build()
993            .unwrap();
994        assert!(table.zebra_striping.is_some());
995    }
996
997    #[test]
998    fn test_builder_zebra_striping_custom() {
999        let config = ZebraConfig::new(
1000            Some(Color::rgb(0.9, 0.9, 0.9)),
1001            Some(Color::rgb(1.0, 1.0, 1.0)),
1002        );
1003        let table = AdvancedTableBuilder::new()
1004            .add_column("A", 50.0)
1005            .zebra_striping_custom(config)
1006            .build()
1007            .unwrap();
1008        assert!(table.zebra_striping.is_some());
1009    }
1010
1011    #[test]
1012    fn test_builder_add_row_with_style() {
1013        let style = CellStyle::data();
1014        let table = AdvancedTableBuilder::new()
1015            .add_column("A", 50.0)
1016            .add_row_with_style(vec!["Value"], style)
1017            .build()
1018            .unwrap();
1019        assert!(table.rows[0].style.is_some());
1020    }
1021
1022    #[test]
1023    fn test_builder_add_row_with_mixed_styles() {
1024        let style1 = CellStyle::header();
1025        let style2 = CellStyle::data();
1026        let table = AdvancedTableBuilder::new()
1027            .add_column("A", 50.0)
1028            .add_column("B", 50.0)
1029            .add_row_with_mixed_styles(vec![(style1, "Header"), (style2, "Data")])
1030            .build()
1031            .unwrap();
1032        assert!(table.rows[0].cells[0].style.is_some());
1033        assert!(table.rows[0].cells[1].style.is_some());
1034    }
1035
1036    #[test]
1037    fn test_builder_table_border() {
1038        let table = AdvancedTableBuilder::new()
1039            .add_column("A", 50.0)
1040            .table_border(false)
1041            .build()
1042            .unwrap();
1043        assert!(!table.table_border);
1044    }
1045
1046    #[test]
1047    fn test_builder_cell_spacing() {
1048        let table = AdvancedTableBuilder::new()
1049            .add_column("A", 50.0)
1050            .cell_spacing(5.0)
1051            .build()
1052            .unwrap();
1053        assert_eq!(table.cell_spacing, 5.0);
1054    }
1055
1056    #[test]
1057    fn test_builder_total_width() {
1058        let table = AdvancedTableBuilder::new()
1059            .add_column("A", 50.0)
1060            .total_width(500.0)
1061            .build()
1062            .unwrap();
1063        assert_eq!(table.total_width, Some(500.0));
1064    }
1065
1066    #[test]
1067    fn test_builder_repeat_headers() {
1068        let table = AdvancedTableBuilder::new()
1069            .add_column("A", 50.0)
1070            .repeat_headers(true)
1071            .build()
1072            .unwrap();
1073        assert!(table.repeat_headers);
1074    }
1075
1076    #[test]
1077    fn test_builder_set_cell_style() {
1078        let style = CellStyle::header();
1079        let table = AdvancedTableBuilder::new()
1080            .add_column("A", 50.0)
1081            .add_row(vec!["Value"])
1082            .set_cell_style(0, 0, style)
1083            .build()
1084            .unwrap();
1085        assert!(table.cell_styles.contains_key(&(0, 0)));
1086    }
1087
1088    #[test]
1089    fn test_builder_add_data() {
1090        let table = AdvancedTableBuilder::new()
1091            .add_column("A", 50.0)
1092            .add_column("B", 50.0)
1093            .add_data(vec![vec!["A1", "B1"], vec!["A2", "B2"], vec!["A3", "B3"]])
1094            .build()
1095            .unwrap();
1096        assert_eq!(table.rows.len(), 3);
1097    }
1098
1099    #[test]
1100    fn test_builder_financial_table() {
1101        let table = AdvancedTableBuilder::new()
1102            .add_column("A", 50.0)
1103            .financial_table()
1104            .build()
1105            .unwrap();
1106        // Financial table sets zebra striping
1107        assert!(table.zebra_striping.is_some());
1108        assert!(table.table_border);
1109    }
1110
1111    #[test]
1112    fn test_builder_minimal_table() {
1113        let table = AdvancedTableBuilder::new()
1114            .add_column("A", 50.0)
1115            .minimal_table()
1116            .build()
1117            .unwrap();
1118        assert!(!table.table_border);
1119        assert_eq!(table.cell_spacing, 2.0);
1120    }
1121
1122    #[test]
1123    fn test_builder_build_fails_without_columns() {
1124        let result = AdvancedTableBuilder::new().build();
1125        assert!(result.is_err());
1126        match result {
1127            Err(TableError::NoColumns) => {}
1128            _ => panic!("Expected NoColumns error"),
1129        }
1130    }
1131
1132    // =============================================================================
1133    // AdvancedTable tests
1134    // =============================================================================
1135
1136    #[test]
1137    fn test_table_calculate_width_explicit() {
1138        let table = AdvancedTableBuilder::new()
1139            .add_column("A", 50.0)
1140            .add_column("B", 75.0)
1141            .total_width(300.0)
1142            .build()
1143            .unwrap();
1144        assert_eq!(table.calculate_width(), 300.0);
1145    }
1146
1147    #[test]
1148    fn test_table_calculate_width_from_columns() {
1149        let table = AdvancedTableBuilder::new()
1150            .add_column("A", 50.0)
1151            .add_column("B", 75.0)
1152            .build()
1153            .unwrap();
1154        assert_eq!(table.calculate_width(), 125.0);
1155    }
1156
1157    #[test]
1158    fn test_table_row_count() {
1159        let table = AdvancedTableBuilder::new()
1160            .add_column("A", 50.0)
1161            .add_row(vec!["1"])
1162            .add_row(vec!["2"])
1163            .add_row(vec!["3"])
1164            .build()
1165            .unwrap();
1166        assert_eq!(table.row_count(), 3);
1167    }
1168
1169    #[test]
1170    fn test_table_column_count() {
1171        let table = AdvancedTableBuilder::new()
1172            .add_column("A", 50.0)
1173            .add_column("B", 50.0)
1174            .build()
1175            .unwrap();
1176        assert_eq!(table.column_count(), 2);
1177    }
1178
1179    #[test]
1180    fn test_table_get_cell_style_specific() {
1181        let specific_style = CellStyle::header();
1182        let table = AdvancedTableBuilder::new()
1183            .add_column("A", 50.0)
1184            .add_row(vec!["Value"])
1185            .set_cell_style(0, 0, specific_style.clone())
1186            .build()
1187            .unwrap();
1188        let style = table.get_cell_style(0, 0);
1189        assert_eq!(style.font_size, specific_style.font_size);
1190    }
1191
1192    #[test]
1193    fn test_table_get_cell_style_row() {
1194        let row_style = CellStyle::header();
1195        let table = AdvancedTableBuilder::new()
1196            .add_column("A", 50.0)
1197            .add_styled_row(vec!["Value"], row_style.clone())
1198            .build()
1199            .unwrap();
1200        let style = table.get_cell_style(0, 0);
1201        assert_eq!(style.font_size, row_style.font_size);
1202    }
1203
1204    #[test]
1205    fn test_table_get_cell_style_column() {
1206        let col_style = CellStyle::new().font_size(20.0);
1207        let table = AdvancedTableBuilder::new()
1208            .add_styled_column("A", 50.0, col_style.clone())
1209            .add_row(vec!["Value"])
1210            .build()
1211            .unwrap();
1212        let style = table.get_cell_style(0, 0);
1213        assert_eq!(style.font_size, Some(20.0));
1214    }
1215
1216    #[test]
1217    fn test_table_get_cell_style_zebra() {
1218        let zebra_color = Color::rgb(0.9, 0.9, 0.9);
1219        let table = AdvancedTableBuilder::new()
1220            .add_column("A", 50.0)
1221            .add_row(vec!["Row0"])
1222            .add_row(vec!["Row1"])
1223            .zebra_striping(zebra_color)
1224            .build()
1225            .unwrap();
1226
1227        // Zebra applies to odd rows (row 1)
1228        let style_row1 = table.get_cell_style(1, 0);
1229        assert!(style_row1.background_color.is_some());
1230    }
1231
1232    #[test]
1233    fn test_table_get_cell_style_column_with_zebra() {
1234        let col_style = CellStyle::new().font_size(20.0);
1235        let zebra_color = Color::rgb(0.9, 0.9, 0.9);
1236        let table = AdvancedTableBuilder::new()
1237            .add_styled_column("A", 50.0, col_style)
1238            .add_row(vec!["Row0"])
1239            .add_row(vec!["Row1"])
1240            .zebra_striping(zebra_color)
1241            .build()
1242            .unwrap();
1243
1244        // Row 1 (odd) should have column style with zebra background
1245        let style = table.get_cell_style(1, 0);
1246        assert_eq!(style.font_size, Some(20.0));
1247        assert!(style.background_color.is_some());
1248    }
1249
1250    #[test]
1251    fn test_table_validate_success() {
1252        let table = AdvancedTableBuilder::new()
1253            .add_column("A", 50.0)
1254            .add_column("B", 50.0)
1255            .add_row(vec!["1", "2"])
1256            .add_row(vec!["3", "4"])
1257            .build()
1258            .unwrap();
1259        assert!(table.validate().is_ok());
1260    }
1261
1262    #[test]
1263    fn test_table_validate_column_mismatch() {
1264        let mut table = AdvancedTableBuilder::new()
1265            .add_column("A", 50.0)
1266            .add_column("B", 50.0)
1267            .build()
1268            .unwrap();
1269
1270        // Manually add a row with wrong number of cells
1271        table.rows.push(RowData::from_strings(vec!["1", "2", "3"]));
1272
1273        let result = table.validate();
1274        assert!(result.is_err());
1275        match result {
1276            Err(TableError::ColumnMismatch {
1277                row,
1278                found,
1279                expected,
1280            }) => {
1281                assert_eq!(row, 0);
1282                assert_eq!(found, 3);
1283                assert_eq!(expected, 2);
1284            }
1285            _ => panic!("Expected ColumnMismatch error"),
1286        }
1287    }
1288
1289    #[test]
1290    fn test_table_get_cell_style_default() {
1291        let default_style = CellStyle::new().font_size(12.0);
1292        let table = AdvancedTableBuilder::new()
1293            .add_column("A", 50.0)
1294            .add_row(vec!["Value"])
1295            .default_style(default_style.clone())
1296            .build()
1297            .unwrap();
1298
1299        let style = table.get_cell_style(0, 0);
1300        assert_eq!(style.font_size, Some(12.0));
1301    }
1302
1303    #[test]
1304    fn test_table_get_cell_style_invalid_row() {
1305        let table = AdvancedTableBuilder::new()
1306            .add_column("A", 50.0)
1307            .add_row(vec!["Value"])
1308            .build()
1309            .unwrap();
1310
1311        // Getting style for non-existent row should return default
1312        let style = table.get_cell_style(100, 0);
1313        assert_eq!(style.font_size, table.default_style.font_size);
1314    }
1315
1316    #[test]
1317    fn test_table_get_cell_style_invalid_column() {
1318        let table = AdvancedTableBuilder::new()
1319            .add_column("A", 50.0)
1320            .add_row(vec!["Value"])
1321            .build()
1322            .unwrap();
1323
1324        // Getting style for non-existent column should return default
1325        let style = table.get_cell_style(0, 100);
1326        assert_eq!(style.font_size, table.default_style.font_size);
1327    }
1328
1329    #[test]
1330    fn test_get_cell_style_ref_returns_correct_style() {
1331        use crate::graphics::Color;
1332
1333        // Case 1: cell-specific style returned by reference
1334        let cell_style = CellStyle::new().font_size(18.0).text_color(Color::red());
1335        let table = AdvancedTableBuilder::new()
1336            .add_column("A", 100.0)
1337            .add_row(vec!["Value"])
1338            .set_cell_style(0, 0, cell_style.clone())
1339            .build()
1340            .unwrap();
1341
1342        let style_ref = table.get_cell_style_ref(0, 0);
1343        assert!(
1344            (style_ref.font_size.unwrap_or(12.0) - 18.0).abs() < f64::EPSILON,
1345            "Cell-specific style should have font_size 18.0"
1346        );
1347
1348        // Case 2: returns default_style when no cell/row/column style is set
1349        let table_default = AdvancedTableBuilder::new()
1350            .add_column("A", 100.0)
1351            .add_row(vec!["Value"])
1352            .build()
1353            .unwrap();
1354
1355        let style_ref_default = table_default.get_cell_style_ref(0, 0);
1356        assert_eq!(
1357            style_ref_default.font_size, table_default.default_style.font_size,
1358            "Should fall back to default_style when no overrides are set"
1359        );
1360
1361        // Case 3: reference is the same font_size as what get_cell_style returns for the non-zebra path
1362        let style_owned = table.get_cell_style(0, 0);
1363        let style_ref2 = table.get_cell_style_ref(0, 0);
1364        assert_eq!(
1365            style_owned.font_size, style_ref2.font_size,
1366            "get_cell_style and get_cell_style_ref should agree when no zebra striping"
1367        );
1368    }
1369}