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