oxidize_pdf/text/
table_advanced.rs

1//! Advanced table rendering for PDF documents
2//!
3//! This module provides enhanced table functionality with additional features:
4//! - Cell-specific styling (background color, borders, padding)
5//! - Row and column spanning
6//! - Automatic column width calculation
7//! - Table headers and footers that repeat on page breaks
8//! - Alternating row colors
9//! - Cell content wrapping
10//! - Vertical alignment in cells
11
12use crate::error::PdfError;
13use crate::graphics::{Color, GraphicsContext, LineDashPattern};
14use crate::text::{Font, TextAlign};
15
16/// Advanced table with enhanced features
17#[derive(Debug, Clone)]
18pub struct AdvancedTable {
19    /// Table rows
20    rows: Vec<TableRow>,
21    /// Column definitions
22    columns: Vec<ColumnDefinition>,
23    /// Table position (x, y)
24    position: (f64, f64),
25    /// Table options
26    options: AdvancedTableOptions,
27    /// Header rows (repeated on each page)
28    header_rows: Vec<TableRow>,
29    /// Footer rows (repeated on each page)
30    footer_rows: Vec<TableRow>,
31}
32
33/// Advanced options for table rendering
34#[derive(Debug, Clone)]
35pub struct AdvancedTableOptions {
36    /// Default border style
37    pub border_style: BorderStyle,
38    /// Default cell padding
39    pub cell_padding: CellPadding,
40    /// Default row height (0 for auto)
41    pub row_height: f64,
42    /// Default font
43    pub font: Font,
44    /// Default font size
45    pub font_size: f64,
46    /// Default text color
47    pub text_color: Color,
48    /// Alternating row colors
49    pub alternating_rows: Option<AlternatingRowColors>,
50    /// Table-wide background color
51    pub background_color: Option<Color>,
52    /// Maximum table height before breaking
53    pub max_height: Option<f64>,
54    /// Spacing between cells
55    pub cell_spacing: f64,
56    /// Whether to draw outer border
57    pub draw_outer_border: bool,
58}
59
60/// Column definition with width and default alignment
61#[derive(Debug, Clone)]
62pub struct ColumnDefinition {
63    /// Column width (absolute or relative)
64    pub width: ColumnWidth,
65    /// Default alignment for cells in this column
66    pub default_align: TextAlign,
67    /// Minimum width (for auto-width columns)
68    pub min_width: Option<f64>,
69    /// Maximum width (for auto-width columns)
70    pub max_width: Option<f64>,
71}
72
73/// Column width specification
74#[derive(Debug, Clone)]
75pub enum ColumnWidth {
76    /// Fixed width in points
77    Fixed(f64),
78    /// Relative width (percentage of available space)
79    Relative(f64),
80    /// Automatic width based on content
81    Auto,
82}
83
84/// Border style for cells
85#[derive(Debug, Clone)]
86pub struct BorderStyle {
87    /// Top border
88    pub top: Option<BorderLine>,
89    /// Right border
90    pub right: Option<BorderLine>,
91    /// Bottom border
92    pub bottom: Option<BorderLine>,
93    /// Left border
94    pub left: Option<BorderLine>,
95}
96
97/// Individual border line style
98#[derive(Debug, Clone)]
99pub struct BorderLine {
100    /// Line width
101    pub width: f64,
102    /// Line color
103    pub color: Color,
104    /// Line style
105    pub style: LineStyle,
106}
107
108/// Line style for borders
109#[derive(Debug, Clone, Copy, PartialEq)]
110pub enum LineStyle {
111    /// Solid line
112    Solid,
113    /// Dashed line
114    Dashed,
115    /// Dotted line
116    Dotted,
117}
118
119/// Cell padding specification
120#[derive(Debug, Clone, Copy)]
121pub struct CellPadding {
122    pub top: f64,
123    pub right: f64,
124    pub bottom: f64,
125    pub left: f64,
126}
127
128/// Alternating row color configuration
129#[derive(Debug, Clone)]
130pub struct AlternatingRowColors {
131    /// Color for even rows
132    pub even_color: Color,
133    /// Color for odd rows
134    pub odd_color: Color,
135    /// Whether to include header rows in alternation
136    pub include_header: bool,
137}
138
139/// Table row with advanced features
140#[derive(Debug, Clone)]
141pub struct TableRow {
142    /// Cells in this row
143    cells: Vec<AdvancedTableCell>,
144    /// Row-specific height (overrides default)
145    height: Option<f64>,
146    /// Row-specific background color
147    background_color: Option<Color>,
148    /// Whether this row can be split across pages
149    can_split: bool,
150}
151
152/// Advanced table cell with styling options
153#[derive(Debug, Clone)]
154pub struct AdvancedTableCell {
155    /// Cell content
156    content: CellContent,
157    /// Text alignment
158    align: TextAlign,
159    /// Vertical alignment
160    vertical_align: VerticalAlign,
161    /// Column span
162    colspan: usize,
163    /// Row span
164    rowspan: usize,
165    /// Cell-specific background color
166    background_color: Option<Color>,
167    /// Cell-specific border style
168    border_style: Option<BorderStyle>,
169    /// Cell-specific padding
170    padding: Option<CellPadding>,
171    /// Cell-specific font
172    font: Option<Font>,
173    /// Cell-specific font size
174    font_size: Option<f64>,
175    /// Cell-specific text color
176    text_color: Option<Color>,
177}
178
179/// Cell content types
180#[derive(Debug, Clone)]
181pub enum CellContent {
182    /// Simple text content
183    Text(String),
184    /// Multiple paragraphs
185    Paragraphs(Vec<String>),
186}
187
188/// Vertical alignment in cells
189#[derive(Debug, Clone, Copy, PartialEq)]
190pub enum VerticalAlign {
191    Top,
192    Middle,
193    Bottom,
194}
195
196impl Default for AdvancedTableOptions {
197    fn default() -> Self {
198        Self {
199            border_style: BorderStyle::default(),
200            cell_padding: CellPadding::uniform(5.0),
201            row_height: 0.0,
202            font: Font::Helvetica,
203            font_size: 10.0,
204            text_color: Color::black(),
205            alternating_rows: None,
206            background_color: None,
207            max_height: None,
208            cell_spacing: 0.0,
209            draw_outer_border: true,
210        }
211    }
212}
213
214impl Default for BorderStyle {
215    fn default() -> Self {
216        let default_line = BorderLine {
217            width: 1.0,
218            color: Color::black(),
219            style: LineStyle::Solid,
220        };
221        Self {
222            top: Some(default_line.clone()),
223            right: Some(default_line.clone()),
224            bottom: Some(default_line.clone()),
225            left: Some(default_line),
226        }
227    }
228}
229
230impl BorderStyle {
231    /// Create a border style with no borders
232    pub fn none() -> Self {
233        Self {
234            top: None,
235            right: None,
236            bottom: None,
237            left: None,
238        }
239    }
240
241    /// Create a border style with only horizontal lines
242    pub fn horizontal_only(width: f64, color: Color) -> Self {
243        let line = BorderLine {
244            width,
245            color,
246            style: LineStyle::Solid,
247        };
248        Self {
249            top: Some(line.clone()),
250            right: None,
251            bottom: Some(line),
252            left: None,
253        }
254    }
255
256    /// Create a border style with only vertical lines
257    pub fn vertical_only(width: f64, color: Color) -> Self {
258        let line = BorderLine {
259            width,
260            color,
261            style: LineStyle::Solid,
262        };
263        Self {
264            top: None,
265            right: Some(line.clone()),
266            bottom: None,
267            left: Some(line),
268        }
269    }
270}
271
272impl CellPadding {
273    /// Create uniform padding on all sides
274    pub fn uniform(padding: f64) -> Self {
275        Self {
276            top: padding,
277            right: padding,
278            bottom: padding,
279            left: padding,
280        }
281    }
282
283    /// Create padding with horizontal and vertical values
284    pub fn symmetric(horizontal: f64, vertical: f64) -> Self {
285        Self {
286            top: vertical,
287            right: horizontal,
288            bottom: vertical,
289            left: horizontal,
290        }
291    }
292}
293
294impl AdvancedTable {
295    /// Create a new advanced table with column definitions
296    pub fn new(columns: Vec<ColumnDefinition>) -> Self {
297        Self {
298            rows: Vec::new(),
299            columns,
300            position: (0.0, 0.0),
301            options: AdvancedTableOptions::default(),
302            header_rows: Vec::new(),
303            footer_rows: Vec::new(),
304        }
305    }
306
307    /// Create a table with equal-width columns
308    pub fn with_equal_columns(num_columns: usize, total_width: f64) -> Self {
309        let column_width = total_width / num_columns as f64;
310        let columns = (0..num_columns)
311            .map(|_| ColumnDefinition {
312                width: ColumnWidth::Fixed(column_width),
313                default_align: TextAlign::Left,
314                min_width: None,
315                max_width: None,
316            })
317            .collect();
318        Self::new(columns)
319    }
320
321    /// Set table position
322    pub fn set_position(&mut self, x: f64, y: f64) -> &mut Self {
323        self.position = (x, y);
324        self
325    }
326
327    /// Set table options
328    pub fn set_options(&mut self, options: AdvancedTableOptions) -> &mut Self {
329        self.options = options;
330        self
331    }
332
333    /// Add a header row (will be repeated on each page)
334    pub fn add_header_row(&mut self, cells: Vec<AdvancedTableCell>) -> Result<&mut Self, PdfError> {
335        self.validate_row_cells(&cells)?;
336        self.header_rows.push(TableRow {
337            cells,
338            height: None,
339            background_color: None,
340            can_split: false,
341        });
342        Ok(self)
343    }
344
345    /// Add a footer row (will be repeated on each page)
346    pub fn add_footer_row(&mut self, cells: Vec<AdvancedTableCell>) -> Result<&mut Self, PdfError> {
347        self.validate_row_cells(&cells)?;
348        self.footer_rows.push(TableRow {
349            cells,
350            height: None,
351            background_color: None,
352            can_split: false,
353        });
354        Ok(self)
355    }
356
357    /// Add a data row
358    pub fn add_row(&mut self, row: TableRow) -> Result<&mut Self, PdfError> {
359        self.validate_row_cells(&row.cells)?;
360        self.rows.push(row);
361        Ok(self)
362    }
363
364    /// Add a simple text row
365    pub fn add_text_row(&mut self, texts: Vec<String>) -> Result<&mut Self, PdfError> {
366        if texts.len() != self.columns.len() {
367            return Err(PdfError::InvalidStructure(
368                "Text count doesn't match column count".to_string(),
369            ));
370        }
371
372        let cells: Vec<AdvancedTableCell> = texts
373            .into_iter()
374            .enumerate()
375            .map(|(i, text)| AdvancedTableCell {
376                content: CellContent::Text(text),
377                align: self.columns[i].default_align,
378                vertical_align: VerticalAlign::Middle,
379                colspan: 1,
380                rowspan: 1,
381                background_color: None,
382                border_style: None,
383                padding: None,
384                font: None,
385                font_size: None,
386                text_color: None,
387            })
388            .collect();
389
390        self.add_row(TableRow {
391            cells,
392            height: None,
393            background_color: None,
394            can_split: true,
395        })
396    }
397
398    /// Validate that cells match column structure
399    fn validate_row_cells(&self, cells: &[AdvancedTableCell]) -> Result<(), PdfError> {
400        let total_colspan: usize = cells.iter().map(|c| c.colspan).sum();
401        if total_colspan != self.columns.len() {
402            return Err(PdfError::InvalidStructure(format!(
403                "Total colspan {} doesn't match column count {}",
404                total_colspan,
405                self.columns.len()
406            )));
407        }
408        Ok(())
409    }
410
411    /// Calculate actual column widths based on table width and content
412    pub fn calculate_column_widths(&self, available_width: f64) -> Vec<f64> {
413        let mut widths = vec![0.0; self.columns.len()];
414        let mut fixed_width = 0.0;
415        let mut relative_total = 0.0;
416        let mut auto_columns = Vec::new();
417
418        // First pass: calculate fixed widths and relative totals
419        for (i, col) in self.columns.iter().enumerate() {
420            match &col.width {
421                ColumnWidth::Fixed(w) => {
422                    widths[i] = *w;
423                    fixed_width += *w;
424                }
425                ColumnWidth::Relative(pct) => {
426                    relative_total += *pct;
427                }
428                ColumnWidth::Auto => {
429                    auto_columns.push(i);
430                }
431            }
432        }
433
434        // Calculate remaining width for relative and auto columns
435        let remaining_width = available_width - fixed_width;
436
437        // Second pass: calculate relative widths
438        for (i, col) in self.columns.iter().enumerate() {
439            if let ColumnWidth::Relative(pct) = col.width {
440                widths[i] = remaining_width * (pct / relative_total);
441            }
442        }
443
444        // Third pass: distribute remaining width to auto columns
445        if !auto_columns.is_empty() {
446            let auto_width = remaining_width / auto_columns.len() as f64;
447            for &i in &auto_columns {
448                widths[i] = auto_width;
449
450                // Apply min/max constraints
451                if let Some(min) = self.columns[i].min_width {
452                    widths[i] = widths[i].max(min);
453                }
454                if let Some(max) = self.columns[i].max_width {
455                    widths[i] = widths[i].min(max);
456                }
457            }
458        }
459
460        widths
461    }
462
463    /// Get total table width
464    pub fn get_width(&self, available_width: f64) -> f64 {
465        self.calculate_column_widths(available_width).iter().sum()
466    }
467
468    /// Calculate row height based on content
469    fn calculate_row_height(&self, row: &TableRow, _column_widths: &[f64]) -> f64 {
470        if let Some(height) = row.height {
471            return height;
472        }
473
474        if self.options.row_height > 0.0 {
475            return self.options.row_height;
476        }
477
478        // Calculate based on content
479        let padding = self.options.cell_padding;
480        let font_size = self.options.font_size;
481
482        // For now, simple calculation - can be enhanced with text wrapping
483        font_size + padding.top + padding.bottom
484    }
485
486    /// Render the table to a graphics context
487    pub fn render(
488        &self,
489        graphics: &mut GraphicsContext,
490        available_width: f64,
491    ) -> Result<(), PdfError> {
492        let column_widths = self.calculate_column_widths(available_width);
493        let (start_x, mut current_y) = self.position;
494
495        // Draw table background if specified
496        if let Some(bg_color) = self.options.background_color {
497            let table_width: f64 = column_widths.iter().sum();
498            let table_height = self.calculate_total_height(&column_widths);
499
500            graphics.save_state();
501            graphics.set_fill_color(bg_color);
502            graphics.rectangle(start_x, current_y, table_width, table_height);
503            graphics.fill();
504            graphics.restore_state();
505        }
506
507        // Render header rows
508        for header_row in &self.header_rows {
509            self.render_row(
510                graphics,
511                header_row,
512                &column_widths,
513                start_x,
514                &mut current_y,
515                None,
516            )?;
517        }
518
519        // Render data rows
520        for (row_index, row) in self.rows.iter().enumerate() {
521            let row_color = self.get_row_background_color(row, row_index);
522            self.render_row(
523                graphics,
524                row,
525                &column_widths,
526                start_x,
527                &mut current_y,
528                row_color,
529            )?;
530        }
531
532        // Render footer rows
533        for footer_row in &self.footer_rows {
534            self.render_row(
535                graphics,
536                footer_row,
537                &column_widths,
538                start_x,
539                &mut current_y,
540                None,
541            )?;
542        }
543
544        Ok(())
545    }
546
547    /// Render a single row
548    fn render_row(
549        &self,
550        graphics: &mut GraphicsContext,
551        row: &TableRow,
552        column_widths: &[f64],
553        start_x: f64,
554        current_y: &mut f64,
555        row_background: Option<Color>,
556    ) -> Result<(), PdfError> {
557        let row_height = self.calculate_row_height(row, column_widths);
558        let mut current_x = start_x;
559
560        // Draw row background
561        if let Some(color) = row_background.or(row.background_color) {
562            let row_width: f64 = column_widths.iter().sum();
563            graphics.save_state();
564            graphics.set_fill_color(color);
565            graphics.rectangle(start_x, *current_y, row_width, row_height);
566            graphics.fill();
567            graphics.restore_state();
568        }
569
570        // Draw cells
571        let mut col_index = 0;
572        for cell in &row.cells {
573            let mut cell_width = 0.0;
574            for i in 0..cell.colspan {
575                if col_index + i < column_widths.len() {
576                    cell_width += column_widths[col_index + i];
577                }
578            }
579
580            self.render_cell(
581                graphics, cell, current_x, *current_y, cell_width, row_height,
582            )?;
583
584            current_x += cell_width;
585            col_index += cell.colspan;
586        }
587
588        *current_y += row_height;
589        Ok(())
590    }
591
592    /// Render a single cell
593    fn render_cell(
594        &self,
595        graphics: &mut GraphicsContext,
596        cell: &AdvancedTableCell,
597        x: f64,
598        y: f64,
599        width: f64,
600        height: f64,
601    ) -> Result<(), PdfError> {
602        // Draw cell background
603        if let Some(bg_color) = cell.background_color {
604            graphics.save_state();
605            graphics.set_fill_color(bg_color);
606            graphics.rectangle(x, y, width, height);
607            graphics.fill();
608            graphics.restore_state();
609        }
610
611        // Draw cell borders
612        let border_style = cell
613            .border_style
614            .as_ref()
615            .unwrap_or(&self.options.border_style);
616        self.draw_cell_borders(graphics, border_style, x, y, width, height)?;
617
618        // Draw cell content
619        let padding = cell.padding.unwrap_or(self.options.cell_padding);
620        let content_x = x + padding.left;
621        let content_y = y + padding.bottom;
622        let content_width = width - padding.left - padding.right;
623        let content_height = height - padding.top - padding.bottom;
624
625        match &cell.content {
626            CellContent::Text(text) => {
627                self.render_text_content(
628                    graphics,
629                    text,
630                    cell,
631                    content_x,
632                    content_y,
633                    content_width,
634                    content_height,
635                )?;
636            }
637            CellContent::Paragraphs(paragraphs) => {
638                self.render_paragraphs_content(
639                    graphics,
640                    paragraphs,
641                    cell,
642                    content_x,
643                    content_y,
644                    content_width,
645                    content_height,
646                )?;
647            }
648        }
649
650        Ok(())
651    }
652
653    /// Draw borders for a cell
654    fn draw_cell_borders(
655        &self,
656        graphics: &mut GraphicsContext,
657        border_style: &BorderStyle,
658        x: f64,
659        y: f64,
660        width: f64,
661        height: f64,
662    ) -> Result<(), PdfError> {
663        graphics.save_state();
664
665        // Top border
666        if let Some(border) = &border_style.top {
667            self.draw_border_line(graphics, border, x, y, x + width, y)?;
668        }
669
670        // Right border
671        if let Some(border) = &border_style.right {
672            self.draw_border_line(graphics, border, x + width, y, x + width, y + height)?;
673        }
674
675        // Bottom border
676        if let Some(border) = &border_style.bottom {
677            self.draw_border_line(graphics, border, x, y + height, x + width, y + height)?;
678        }
679
680        // Left border
681        if let Some(border) = &border_style.left {
682            self.draw_border_line(graphics, border, x, y, x, y + height)?;
683        }
684
685        graphics.restore_state();
686        Ok(())
687    }
688
689    /// Draw a single border line
690    fn draw_border_line(
691        &self,
692        graphics: &mut GraphicsContext,
693        border: &BorderLine,
694        x1: f64,
695        y1: f64,
696        x2: f64,
697        y2: f64,
698    ) -> Result<(), PdfError> {
699        graphics.set_stroke_color(border.color);
700        graphics.set_line_width(border.width);
701
702        match border.style {
703            LineStyle::Solid => {
704                graphics.move_to(x1, y1);
705                graphics.line_to(x2, y2);
706                graphics.stroke();
707            }
708            LineStyle::Dashed => {
709                graphics.set_line_dash_pattern(LineDashPattern::dashed(3.0, 3.0));
710                graphics.move_to(x1, y1);
711                graphics.line_to(x2, y2);
712                graphics.stroke();
713                graphics.set_line_solid(); // Reset
714            }
715            LineStyle::Dotted => {
716                graphics.set_line_dash_pattern(LineDashPattern::dotted(1.0, 2.0));
717                graphics.move_to(x1, y1);
718                graphics.line_to(x2, y2);
719                graphics.stroke();
720                graphics.set_line_solid(); // Reset
721            }
722        }
723
724        Ok(())
725    }
726
727    /// Render text content in a cell
728    #[allow(clippy::too_many_arguments)]
729    fn render_text_content(
730        &self,
731        graphics: &mut GraphicsContext,
732        text: &str,
733        cell: &AdvancedTableCell,
734        x: f64,
735        y: f64,
736        width: f64,
737        height: f64,
738    ) -> Result<(), PdfError> {
739        graphics.save_state();
740
741        // Set font and color
742        let font = cell.font.clone().unwrap_or(self.options.font.clone());
743        let font_size = cell.font_size.unwrap_or(self.options.font_size);
744        let text_color = cell.text_color.unwrap_or(self.options.text_color);
745
746        graphics.set_font(font, font_size);
747        graphics.set_fill_color(text_color);
748
749        // Calculate text position based on alignment
750        let text_y = match cell.vertical_align {
751            VerticalAlign::Top => y + height - font_size,
752            VerticalAlign::Middle => y + (height - font_size) / 2.0,
753            VerticalAlign::Bottom => y + font_size,
754        };
755
756        // Draw text with horizontal alignment
757        match cell.align {
758            TextAlign::Left => {
759                graphics.begin_text();
760                graphics.set_text_position(x, text_y);
761                graphics.show_text(text)?;
762                graphics.end_text();
763            }
764            TextAlign::Center => {
765                let text_width = text.len() as f64 * font_size * 0.5; // Approximate
766                let centered_x = x + (width - text_width) / 2.0;
767                graphics.begin_text();
768                graphics.set_text_position(centered_x, text_y);
769                graphics.show_text(text)?;
770                graphics.end_text();
771            }
772            TextAlign::Right => {
773                let text_width = text.len() as f64 * font_size * 0.5; // Approximate
774                let right_x = x + width - text_width;
775                graphics.begin_text();
776                graphics.set_text_position(right_x, text_y);
777                graphics.show_text(text)?;
778                graphics.end_text();
779            }
780            TextAlign::Justified => {
781                // For now, treat as left-aligned
782                graphics.begin_text();
783                graphics.set_text_position(x, text_y);
784                graphics.show_text(text)?;
785                graphics.end_text();
786            }
787        }
788
789        graphics.restore_state();
790        Ok(())
791    }
792
793    /// Render multiple paragraphs in a cell
794    #[allow(clippy::too_many_arguments)]
795    fn render_paragraphs_content(
796        &self,
797        graphics: &mut GraphicsContext,
798        paragraphs: &[String],
799        cell: &AdvancedTableCell,
800        x: f64,
801        y: f64,
802        width: f64,
803        height: f64,
804    ) -> Result<(), PdfError> {
805        let font_size = cell.font_size.unwrap_or(self.options.font_size);
806        let line_height = font_size * 1.2;
807        let mut current_y = y + height - font_size;
808
809        for paragraph in paragraphs {
810            if current_y < y {
811                break; // No more space
812            }
813
814            self.render_text_content(
815                graphics,
816                paragraph,
817                cell,
818                x,
819                current_y - (height - font_size),
820                width,
821                font_size,
822            )?;
823
824            current_y -= line_height;
825        }
826
827        Ok(())
828    }
829
830    /// Calculate total table height
831    fn calculate_total_height(&self, column_widths: &[f64]) -> f64 {
832        let mut height = 0.0;
833
834        // Header rows
835        for row in &self.header_rows {
836            height += self.calculate_row_height(row, column_widths);
837        }
838
839        // Data rows
840        for row in &self.rows {
841            height += self.calculate_row_height(row, column_widths);
842        }
843
844        // Footer rows
845        for row in &self.footer_rows {
846            height += self.calculate_row_height(row, column_widths);
847        }
848
849        height
850    }
851
852    /// Get background color for a row considering alternating colors
853    fn get_row_background_color(&self, row: &TableRow, row_index: usize) -> Option<Color> {
854        if row.background_color.is_some() {
855            return row.background_color;
856        }
857
858        if let Some(alt_colors) = &self.options.alternating_rows {
859            let index = if alt_colors.include_header {
860                row_index + self.header_rows.len()
861            } else {
862                row_index
863            };
864
865            if index % 2 == 0 {
866                Some(alt_colors.even_color)
867            } else {
868                Some(alt_colors.odd_color)
869            }
870        } else {
871            None
872        }
873    }
874}
875
876impl AdvancedTableCell {
877    /// Create a simple text cell
878    pub fn text(content: String) -> Self {
879        Self {
880            content: CellContent::Text(content),
881            align: TextAlign::Left,
882            vertical_align: VerticalAlign::Middle,
883            colspan: 1,
884            rowspan: 1,
885            background_color: None,
886            border_style: None,
887            padding: None,
888            font: None,
889            font_size: None,
890            text_color: None,
891        }
892    }
893
894    /// Create a cell with paragraphs
895    pub fn paragraphs(paragraphs: Vec<String>) -> Self {
896        Self {
897            content: CellContent::Paragraphs(paragraphs),
898            align: TextAlign::Left,
899            vertical_align: VerticalAlign::Top,
900            colspan: 1,
901            rowspan: 1,
902            background_color: None,
903            border_style: None,
904            padding: None,
905            font: None,
906            font_size: None,
907            text_color: None,
908        }
909    }
910
911    /// Set cell alignment
912    pub fn with_align(mut self, align: TextAlign) -> Self {
913        self.align = align;
914        self
915    }
916
917    /// Set vertical alignment
918    pub fn with_vertical_align(mut self, align: VerticalAlign) -> Self {
919        self.vertical_align = align;
920        self
921    }
922
923    /// Set column span
924    pub fn with_colspan(mut self, colspan: usize) -> Self {
925        self.colspan = colspan;
926        self
927    }
928
929    /// Set row span
930    pub fn with_rowspan(mut self, rowspan: usize) -> Self {
931        self.rowspan = rowspan;
932        self
933    }
934
935    /// Set background color
936    pub fn with_background(mut self, color: Color) -> Self {
937        self.background_color = Some(color);
938        self
939    }
940
941    /// Set custom padding
942    pub fn with_padding(mut self, padding: CellPadding) -> Self {
943        self.padding = Some(padding);
944        self
945    }
946
947    /// Set custom font
948    pub fn with_font(mut self, font: Font, size: f64) -> Self {
949        self.font = Some(font);
950        self.font_size = Some(size);
951        self
952    }
953
954    /// Set text color
955    pub fn with_text_color(mut self, color: Color) -> Self {
956        self.text_color = Some(color);
957        self
958    }
959}
960
961impl TableRow {
962    /// Create a new row with cells
963    pub fn new(cells: Vec<AdvancedTableCell>) -> Self {
964        Self {
965            cells,
966            height: None,
967            background_color: None,
968            can_split: true,
969        }
970    }
971
972    /// Set row height
973    pub fn with_height(mut self, height: f64) -> Self {
974        self.height = Some(height);
975        self
976    }
977
978    /// Set row background color
979    pub fn with_background(mut self, color: Color) -> Self {
980        self.background_color = Some(color);
981        self
982    }
983
984    /// Set whether row can be split across pages
985    pub fn with_can_split(mut self, can_split: bool) -> Self {
986        self.can_split = can_split;
987        self
988    }
989}
990
991#[cfg(test)]
992mod tests {
993    use super::*;
994
995    #[test]
996    fn test_advanced_table_creation() {
997        let columns = vec![
998            ColumnDefinition {
999                width: ColumnWidth::Fixed(100.0),
1000                default_align: TextAlign::Left,
1001                min_width: None,
1002                max_width: None,
1003            },
1004            ColumnDefinition {
1005                width: ColumnWidth::Relative(0.5),
1006                default_align: TextAlign::Center,
1007                min_width: None,
1008                max_width: None,
1009            },
1010            ColumnDefinition {
1011                width: ColumnWidth::Auto,
1012                default_align: TextAlign::Right,
1013                min_width: Some(50.0),
1014                max_width: Some(200.0),
1015            },
1016        ];
1017
1018        let table = AdvancedTable::new(columns);
1019        assert_eq!(table.columns.len(), 3);
1020        assert_eq!(table.rows.len(), 0);
1021    }
1022
1023    #[test]
1024    fn test_column_width_calculation() {
1025        let columns = vec![
1026            ColumnDefinition {
1027                width: ColumnWidth::Fixed(100.0),
1028                default_align: TextAlign::Left,
1029                min_width: None,
1030                max_width: None,
1031            },
1032            ColumnDefinition {
1033                width: ColumnWidth::Relative(0.6),
1034                default_align: TextAlign::Center,
1035                min_width: None,
1036                max_width: None,
1037            },
1038            ColumnDefinition {
1039                width: ColumnWidth::Relative(0.4),
1040                default_align: TextAlign::Right,
1041                min_width: None,
1042                max_width: None,
1043            },
1044        ];
1045
1046        let table = AdvancedTable::new(columns);
1047        let widths = table.calculate_column_widths(500.0);
1048
1049        assert_eq!(widths[0], 100.0); // Fixed
1050        assert_eq!(widths[1], 240.0); // 60% of remaining 400
1051        assert_eq!(widths[2], 160.0); // 40% of remaining 400
1052    }
1053
1054    #[test]
1055    fn test_cell_padding() {
1056        let uniform = CellPadding::uniform(10.0);
1057        assert_eq!(uniform.top, 10.0);
1058        assert_eq!(uniform.right, 10.0);
1059        assert_eq!(uniform.bottom, 10.0);
1060        assert_eq!(uniform.left, 10.0);
1061
1062        let symmetric = CellPadding::symmetric(5.0, 15.0);
1063        assert_eq!(symmetric.top, 15.0);
1064        assert_eq!(symmetric.right, 5.0);
1065        assert_eq!(symmetric.bottom, 15.0);
1066        assert_eq!(symmetric.left, 5.0);
1067    }
1068
1069    #[test]
1070    fn test_border_styles() {
1071        let none = BorderStyle::none();
1072        assert!(none.top.is_none());
1073        assert!(none.right.is_none());
1074        assert!(none.bottom.is_none());
1075        assert!(none.left.is_none());
1076
1077        let horizontal = BorderStyle::horizontal_only(2.0, Color::red());
1078        assert!(horizontal.top.is_some());
1079        assert!(horizontal.right.is_none());
1080        assert!(horizontal.bottom.is_some());
1081        assert!(horizontal.left.is_none());
1082
1083        let vertical = BorderStyle::vertical_only(1.5, Color::blue());
1084        assert!(vertical.top.is_none());
1085        assert!(vertical.right.is_some());
1086        assert!(vertical.bottom.is_none());
1087        assert!(vertical.left.is_some());
1088    }
1089
1090    #[test]
1091    fn test_advanced_cell_creation() {
1092        let cell = AdvancedTableCell::text("Hello".to_string())
1093            .with_align(TextAlign::Center)
1094            .with_vertical_align(VerticalAlign::Top)
1095            .with_colspan(2)
1096            .with_background(Color::gray(0.9))
1097            .with_font(Font::HelveticaBold, 12.0)
1098            .with_text_color(Color::blue());
1099
1100        match &cell.content {
1101            CellContent::Text(text) => assert_eq!(text, "Hello"),
1102            _ => panic!("Expected text content"),
1103        }
1104        assert_eq!(cell.align, TextAlign::Center);
1105        assert_eq!(cell.vertical_align, VerticalAlign::Top);
1106        assert_eq!(cell.colspan, 2);
1107        assert!(cell.background_color.is_some());
1108        assert!(cell.font.is_some());
1109        assert!(cell.font_size.is_some());
1110        assert!(cell.text_color.is_some());
1111    }
1112
1113    #[test]
1114    fn test_table_row_creation() {
1115        let cells = vec![
1116            AdvancedTableCell::text("Cell 1".to_string()),
1117            AdvancedTableCell::text("Cell 2".to_string()),
1118        ];
1119
1120        let row = TableRow::new(cells)
1121            .with_height(30.0)
1122            .with_background(Color::yellow())
1123            .with_can_split(false);
1124
1125        assert_eq!(row.cells.len(), 2);
1126        assert_eq!(row.height, Some(30.0));
1127        assert!(row.background_color.is_some());
1128        assert!(!row.can_split);
1129    }
1130
1131    #[test]
1132    fn test_add_text_row() {
1133        let mut table = AdvancedTable::with_equal_columns(3, 300.0);
1134        let result = table.add_text_row(vec![
1135            "Name".to_string(),
1136            "Age".to_string(),
1137            "City".to_string(),
1138        ]);
1139
1140        assert!(result.is_ok());
1141        assert_eq!(table.rows.len(), 1);
1142    }
1143
1144    #[test]
1145    fn test_add_text_row_mismatch() {
1146        let mut table = AdvancedTable::with_equal_columns(2, 200.0);
1147        let result = table.add_text_row(vec![
1148            "One".to_string(),
1149            "Two".to_string(),
1150            "Three".to_string(),
1151        ]);
1152
1153        assert!(result.is_err());
1154    }
1155
1156    #[test]
1157    fn test_validate_row_cells() {
1158        let table = AdvancedTable::with_equal_columns(3, 300.0);
1159
1160        // Valid: 3 cells with colspan 1 each
1161        let cells1 = vec![
1162            AdvancedTableCell::text("A".to_string()),
1163            AdvancedTableCell::text("B".to_string()),
1164            AdvancedTableCell::text("C".to_string()),
1165        ];
1166        assert!(table.validate_row_cells(&cells1).is_ok());
1167
1168        // Valid: 1 cell with colspan 2, 1 cell with colspan 1
1169        let cells2 = vec![
1170            AdvancedTableCell::text("AB".to_string()).with_colspan(2),
1171            AdvancedTableCell::text("C".to_string()),
1172        ];
1173        assert!(table.validate_row_cells(&cells2).is_ok());
1174
1175        // Invalid: total colspan is 4
1176        let cells3 = vec![
1177            AdvancedTableCell::text("A".to_string()).with_colspan(2),
1178            AdvancedTableCell::text("B".to_string()).with_colspan(2),
1179        ];
1180        assert!(table.validate_row_cells(&cells3).is_err());
1181    }
1182
1183    #[test]
1184    fn test_alternating_row_colors() {
1185        let alt_colors = AlternatingRowColors {
1186            even_color: Color::gray(0.95),
1187            odd_color: Color::white(),
1188            include_header: false,
1189        };
1190
1191        let mut options = AdvancedTableOptions::default();
1192        options.alternating_rows = Some(alt_colors);
1193
1194        let mut table = AdvancedTable::with_equal_columns(2, 200.0);
1195        table.set_options(options);
1196
1197        // Add some rows
1198        table
1199            .add_text_row(vec!["Row 0".to_string(), "Data".to_string()])
1200            .unwrap();
1201        table
1202            .add_text_row(vec!["Row 1".to_string(), "Data".to_string()])
1203            .unwrap();
1204
1205        // Test color calculation
1206        let row = &table.rows[0];
1207        let color = table.get_row_background_color(row, 0);
1208        assert!(color.is_some());
1209        assert_eq!(color.unwrap(), Color::gray(0.95)); // Even row
1210
1211        let row = &table.rows[1];
1212        let color = table.get_row_background_color(row, 1);
1213        assert!(color.is_some());
1214        assert_eq!(color.unwrap(), Color::white()); // Odd row
1215    }
1216
1217    #[test]
1218    fn test_cell_content_types() {
1219        // Text content
1220        let text_cell = AdvancedTableCell::text("Simple text".to_string());
1221        match text_cell.content {
1222            CellContent::Text(ref t) => assert_eq!(t, "Simple text"),
1223            _ => panic!("Expected text content"),
1224        }
1225
1226        // Paragraphs content
1227        let para_cell =
1228            AdvancedTableCell::paragraphs(vec!["Line 1".to_string(), "Line 2".to_string()]);
1229        match para_cell.content {
1230            CellContent::Paragraphs(ref p) => assert_eq!(p.len(), 2),
1231            _ => panic!("Expected paragraphs content"),
1232        }
1233    }
1234
1235    #[test]
1236    fn test_line_styles() {
1237        assert_eq!(LineStyle::Solid, LineStyle::Solid);
1238        assert_ne!(LineStyle::Solid, LineStyle::Dashed);
1239        assert_ne!(LineStyle::Dashed, LineStyle::Dotted);
1240    }
1241
1242    #[test]
1243    fn test_vertical_alignment() {
1244        assert_eq!(VerticalAlign::Top, VerticalAlign::Top);
1245        assert_ne!(VerticalAlign::Top, VerticalAlign::Middle);
1246        assert_ne!(VerticalAlign::Middle, VerticalAlign::Bottom);
1247    }
1248
1249    #[test]
1250    fn test_table_dimensions() {
1251        let mut table = AdvancedTable::with_equal_columns(3, 300.0);
1252
1253        // Set fixed row height
1254        table.options.row_height = 25.0;
1255
1256        // Add some rows
1257        table
1258            .add_text_row(vec!["A".to_string(), "B".to_string(), "C".to_string()])
1259            .unwrap();
1260        table
1261            .add_text_row(vec!["D".to_string(), "E".to_string(), "F".to_string()])
1262            .unwrap();
1263
1264        let widths = table.calculate_column_widths(300.0);
1265        let total_width: f64 = widths.iter().sum();
1266        assert_eq!(total_width, 300.0);
1267
1268        let total_height = table.calculate_total_height(&widths);
1269        assert_eq!(total_height, 50.0); // 2 rows * 25.0 height
1270    }
1271
1272    #[test]
1273    fn test_auto_column_constraints() {
1274        let columns = vec![
1275            ColumnDefinition {
1276                width: ColumnWidth::Fixed(100.0),
1277                default_align: TextAlign::Left,
1278                min_width: None,
1279                max_width: None,
1280            },
1281            ColumnDefinition {
1282                width: ColumnWidth::Auto,
1283                default_align: TextAlign::Center,
1284                min_width: Some(80.0),
1285                max_width: Some(120.0),
1286            },
1287            ColumnDefinition {
1288                width: ColumnWidth::Auto,
1289                default_align: TextAlign::Right,
1290                min_width: Some(50.0),
1291                max_width: None,
1292            },
1293        ];
1294
1295        let table = AdvancedTable::new(columns);
1296        let widths = table.calculate_column_widths(400.0);
1297
1298        assert_eq!(widths[0], 100.0); // Fixed
1299        assert!(widths[1] >= 80.0 && widths[1] <= 120.0); // Auto with constraints
1300        assert!(widths[2] >= 50.0); // Auto with min constraint
1301    }
1302
1303    #[test]
1304    fn test_header_footer_rows() {
1305        let mut table = AdvancedTable::with_equal_columns(2, 200.0);
1306
1307        // Add header
1308        let header_cells = vec![
1309            AdvancedTableCell::text("Header 1".to_string()),
1310            AdvancedTableCell::text("Header 2".to_string()),
1311        ];
1312        assert!(table.add_header_row(header_cells).is_ok());
1313        assert_eq!(table.header_rows.len(), 1);
1314
1315        // Add footer
1316        let footer_cells = vec![
1317            AdvancedTableCell::text("Footer 1".to_string()),
1318            AdvancedTableCell::text("Footer 2".to_string()),
1319        ];
1320        assert!(table.add_footer_row(footer_cells).is_ok());
1321        assert_eq!(table.footer_rows.len(), 1);
1322
1323        // Add data row
1324        assert!(table
1325            .add_text_row(vec!["Data 1".to_string(), "Data 2".to_string()])
1326            .is_ok());
1327        assert_eq!(table.rows.len(), 1);
1328    }
1329}