oxidize_pdf/text/
table.rs

1//! Simple table rendering for PDF documents
2//!
3//! This module provides basic table functionality without CSS styling,
4//! suitable for structured data presentation in PDF documents.
5
6use crate::error::PdfError;
7use crate::graphics::{Color, GraphicsContext, LineDashPattern};
8use crate::text::{measure_text, Font, TextAlign};
9
10/// Represents a simple table in a PDF document
11#[derive(Debug, Clone)]
12pub struct Table {
13    /// Table rows
14    rows: Vec<TableRow>,
15    /// Column widths (in points)
16    column_widths: Vec<f64>,
17    /// Table position (x, y)
18    position: (f64, f64),
19    /// Table options
20    options: TableOptions,
21}
22
23/// Options for table rendering
24#[derive(Debug, Clone)]
25pub struct TableOptions {
26    /// Border width in points
27    pub border_width: f64,
28    /// Border color
29    pub border_color: Color,
30    /// Cell padding in points
31    pub cell_padding: f64,
32    /// Row height in points (0 for auto)
33    pub row_height: f64,
34    /// Font for table text
35    pub font: Font,
36    /// Font size in points
37    pub font_size: f64,
38    /// Text color
39    pub text_color: Color,
40    /// Header row styling
41    pub header_style: Option<HeaderStyle>,
42    /// Grid layout options
43    pub grid_style: GridStyle,
44    /// Cell border style
45    pub cell_border_style: CellBorderStyle,
46    /// Alternating row colors
47    pub alternating_row_colors: Option<(Color, Color)>,
48    /// Table background color
49    pub background_color: Option<Color>,
50}
51
52/// Header row styling options
53#[derive(Debug, Clone)]
54pub struct HeaderStyle {
55    /// Background color for header cells
56    pub background_color: Color,
57    /// Text color for header cells
58    pub text_color: Color,
59    /// Font for header text
60    pub font: Font,
61    /// Make header text bold
62    pub bold: bool,
63}
64
65/// Represents a row in the table
66#[derive(Debug, Clone)]
67pub struct TableRow {
68    /// Cells in this row
69    cells: Vec<TableCell>,
70    /// Whether this is a header row
71    is_header: bool,
72}
73
74/// Represents a cell in the table
75#[derive(Debug, Clone)]
76pub struct TableCell {
77    /// Cell content
78    content: String,
79    /// Text alignment
80    align: TextAlign,
81    /// Column span (default 1)
82    colspan: usize,
83    /// Row span (default 1)
84    rowspan: usize,
85    /// Cell background color (overrides row color)
86    background_color: Option<Color>,
87    /// Cell border style (overrides table default)
88    border_style: Option<CellBorderStyle>,
89}
90
91/// Grid layout style for tables
92#[derive(Debug, Clone, Copy, PartialEq)]
93pub enum GridStyle {
94    /// No grid lines
95    None,
96    /// Only horizontal lines
97    Horizontal,
98    /// Only vertical lines
99    Vertical,
100    /// Full grid with all lines
101    Full,
102    /// Only outer borders
103    Outline,
104}
105
106/// Cell border style options
107#[derive(Debug, Clone)]
108pub struct CellBorderStyle {
109    /// Border width
110    pub width: f64,
111    /// Border color
112    pub color: Color,
113    /// Dash pattern (None for solid)
114    pub dash_pattern: Option<LineDashPattern>,
115}
116
117impl Default for CellBorderStyle {
118    fn default() -> Self {
119        Self {
120            width: 1.0,
121            color: Color::black(),
122            dash_pattern: None,
123        }
124    }
125}
126
127impl Default for TableOptions {
128    fn default() -> Self {
129        Self {
130            border_width: 1.0,
131            border_color: Color::black(),
132            cell_padding: 5.0,
133            row_height: 0.0, // Auto
134            font: Font::Helvetica,
135            font_size: 10.0,
136            text_color: Color::black(),
137            header_style: None,
138            grid_style: GridStyle::Full,
139            cell_border_style: CellBorderStyle::default(),
140            alternating_row_colors: None,
141            background_color: None,
142        }
143    }
144}
145
146impl Table {
147    /// Create a new table with specified column widths
148    pub fn new(column_widths: Vec<f64>) -> Self {
149        Self {
150            rows: Vec::new(),
151            column_widths,
152            position: (0.0, 0.0),
153            options: TableOptions::default(),
154        }
155    }
156
157    /// Create a table with equal column widths
158    pub fn with_equal_columns(num_columns: usize, total_width: f64) -> Self {
159        let column_width = total_width / num_columns as f64;
160        let column_widths = vec![column_width; num_columns];
161        Self::new(column_widths)
162    }
163
164    /// Set table position
165    pub fn set_position(&mut self, x: f64, y: f64) -> &mut Self {
166        self.position = (x, y);
167        self
168    }
169
170    /// Set table options
171    pub fn set_options(&mut self, options: TableOptions) -> &mut Self {
172        self.options = options;
173        self
174    }
175
176    /// Add a header row
177    pub fn add_header_row(&mut self, cells: Vec<String>) -> Result<&mut Self, PdfError> {
178        if cells.len() != self.column_widths.len() {
179            return Err(PdfError::InvalidStructure(
180                "Header cells count doesn't match column count".to_string(),
181            ));
182        }
183
184        let row_cells: Vec<TableCell> = cells
185            .into_iter()
186            .map(|content| TableCell {
187                content,
188                align: TextAlign::Center,
189                colspan: 1,
190                rowspan: 1,
191                background_color: None,
192                border_style: None,
193            })
194            .collect();
195
196        self.rows.push(TableRow {
197            cells: row_cells,
198            is_header: true,
199        });
200
201        Ok(self)
202    }
203
204    /// Add a data row
205    pub fn add_row(&mut self, cells: Vec<String>) -> Result<&mut Self, PdfError> {
206        self.add_row_with_alignment(cells, TextAlign::Left)
207    }
208
209    /// Add a data row with specific alignment
210    pub fn add_row_with_alignment(
211        &mut self,
212        cells: Vec<String>,
213        align: TextAlign,
214    ) -> Result<&mut Self, PdfError> {
215        if cells.len() != self.column_widths.len() {
216            return Err(PdfError::InvalidStructure(
217                "Row cells count doesn't match column count".to_string(),
218            ));
219        }
220
221        let row_cells: Vec<TableCell> = cells
222            .into_iter()
223            .map(|content| TableCell {
224                content,
225                align,
226                colspan: 1,
227                rowspan: 1,
228                background_color: None,
229                border_style: None,
230            })
231            .collect();
232
233        self.rows.push(TableRow {
234            cells: row_cells,
235            is_header: false,
236        });
237
238        Ok(self)
239    }
240
241    /// Add a row with custom cells (allows colspan)
242    pub fn add_custom_row(&mut self, cells: Vec<TableCell>) -> Result<&mut Self, PdfError> {
243        // Validate total colspan matches column count
244        let total_colspan: usize = cells.iter().map(|c| c.colspan).sum();
245        if total_colspan != self.column_widths.len() {
246            return Err(PdfError::InvalidStructure(
247                "Total colspan doesn't match column count".to_string(),
248            ));
249        }
250
251        self.rows.push(TableRow {
252            cells,
253            is_header: false,
254        });
255
256        Ok(self)
257    }
258
259    /// Calculate the height of a row
260    fn calculate_row_height(&self, _row: &TableRow) -> f64 {
261        if self.options.row_height > 0.0 {
262            self.options.row_height
263        } else {
264            // Auto height: font size + padding
265            self.options.font_size + (self.options.cell_padding * 2.0)
266        }
267    }
268
269    /// Get total table height
270    pub fn get_height(&self) -> f64 {
271        self.rows
272            .iter()
273            .map(|row| self.calculate_row_height(row))
274            .sum()
275    }
276
277    /// Get total table width
278    pub fn get_width(&self) -> f64 {
279        self.column_widths.iter().sum()
280    }
281
282    /// Render the table to a graphics context
283    pub fn render(&self, graphics: &mut GraphicsContext) -> Result<(), PdfError> {
284        let (start_x, start_y) = self.position;
285        let mut current_y = start_y;
286
287        // Draw table background if specified
288        if let Some(bg_color) = self.options.background_color {
289            graphics.save_state();
290            graphics.set_fill_color(bg_color);
291            graphics.rectangle(start_x, start_y, self.get_width(), self.get_height());
292            graphics.fill();
293            graphics.restore_state();
294        }
295
296        // Draw each row
297        for (row_index, row) in self.rows.iter().enumerate() {
298            let row_height = self.calculate_row_height(row);
299            let mut current_x = start_x;
300
301            // Determine if we should use header styling
302            let use_header_style = row.is_header && self.options.header_style.is_some();
303            let header_style = self.options.header_style.as_ref();
304
305            // Draw cells
306            let mut col_index = 0;
307            for cell in &row.cells {
308                // Calculate cell width (considering colspan)
309                let mut cell_width = 0.0;
310                for i in 0..cell.colspan {
311                    if col_index + i < self.column_widths.len() {
312                        cell_width += self.column_widths[col_index + i];
313                    }
314                }
315
316                // Draw cell background
317                // First priority: cell-specific background
318                if let Some(cell_bg) = cell.background_color {
319                    graphics.save_state();
320                    graphics.set_fill_color(cell_bg);
321                    graphics.rectangle(current_x, current_y, cell_width, row_height);
322                    graphics.fill();
323                    graphics.restore_state();
324                }
325                // Second priority: header style background
326                else if use_header_style {
327                    if let Some(style) = header_style {
328                        graphics.save_state();
329                        graphics.set_fill_color(style.background_color);
330                        graphics.rectangle(current_x, current_y, cell_width, row_height);
331                        graphics.fill();
332                        graphics.restore_state();
333                    }
334                }
335                // Third priority: alternating row colors
336                else if let Some((even_color, odd_color)) = self.options.alternating_row_colors {
337                    if !row.is_header {
338                        let color = if row_index % 2 == 0 {
339                            even_color
340                        } else {
341                            odd_color
342                        };
343                        graphics.save_state();
344                        graphics.set_fill_color(color);
345                        graphics.rectangle(current_x, current_y, cell_width, row_height);
346                        graphics.fill();
347                        graphics.restore_state();
348                    }
349                }
350
351                // Draw cell border based on grid style
352                let should_draw_border = match self.options.grid_style {
353                    GridStyle::None => false,
354                    GridStyle::Full => true,
355                    GridStyle::Horizontal => {
356                        // Draw top and bottom borders only
357                        true
358                    }
359                    GridStyle::Vertical => {
360                        // Draw left and right borders only
361                        true
362                    }
363                    GridStyle::Outline => {
364                        // Only draw if it's an edge cell
365                        col_index == 0
366                            || col_index + cell.colspan >= self.column_widths.len()
367                            || row_index == 0
368                            || row_index == self.rows.len() - 1
369                    }
370                };
371
372                if should_draw_border {
373                    graphics.save_state();
374
375                    // Use cell-specific border style if available
376                    let border_style = cell
377                        .border_style
378                        .as_ref()
379                        .unwrap_or(&self.options.cell_border_style);
380
381                    graphics.set_stroke_color(border_style.color);
382                    graphics.set_line_width(border_style.width);
383
384                    // Apply dash pattern if specified
385                    if let Some(dash_pattern) = &border_style.dash_pattern {
386                        graphics.set_line_dash_pattern(dash_pattern.clone());
387                    }
388
389                    // Draw borders based on grid style
390                    match self.options.grid_style {
391                        GridStyle::Full | GridStyle::Outline => {
392                            graphics.rectangle(current_x, current_y, cell_width, row_height);
393                            graphics.stroke();
394                        }
395                        GridStyle::Horizontal => {
396                            // Top border
397                            graphics.move_to(current_x, current_y);
398                            graphics.line_to(current_x + cell_width, current_y);
399                            // Bottom border
400                            graphics.move_to(current_x, current_y + row_height);
401                            graphics.line_to(current_x + cell_width, current_y + row_height);
402                            graphics.stroke();
403                        }
404                        GridStyle::Vertical => {
405                            // Left border
406                            graphics.move_to(current_x, current_y);
407                            graphics.line_to(current_x, current_y + row_height);
408                            // Right border
409                            graphics.move_to(current_x + cell_width, current_y);
410                            graphics.line_to(current_x + cell_width, current_y + row_height);
411                            graphics.stroke();
412                        }
413                        GridStyle::None => {}
414                    }
415
416                    graphics.restore_state();
417                }
418
419                // Draw cell text
420                let text_x = current_x + self.options.cell_padding;
421                let text_y =
422                    current_y + row_height - self.options.cell_padding - self.options.font_size;
423                let text_width = cell_width - (2.0 * self.options.cell_padding);
424
425                graphics.save_state();
426
427                // Set font and color
428                if use_header_style {
429                    if let Some(style) = header_style {
430                        let font = if style.bold {
431                            match style.font {
432                                Font::Helvetica => Font::HelveticaBold,
433                                Font::TimesRoman => Font::TimesBold,
434                                Font::Courier => Font::CourierBold,
435                                _ => style.font.clone(),
436                            }
437                        } else {
438                            style.font.clone()
439                        };
440                        graphics.set_font(font, self.options.font_size);
441                        graphics.set_fill_color(style.text_color);
442                    }
443                } else {
444                    graphics.set_font(self.options.font.clone(), self.options.font_size);
445                    graphics.set_fill_color(self.options.text_color);
446                }
447
448                // Draw text with alignment
449                match cell.align {
450                    TextAlign::Left => {
451                        graphics.begin_text();
452                        graphics.set_text_position(text_x, text_y);
453                        graphics.show_text(&cell.content)?;
454                        graphics.end_text();
455                    }
456                    TextAlign::Center => {
457                        // Determine which font to use based on header style
458                        let font_to_measure = if use_header_style {
459                            if let Some(style) = header_style {
460                                if style.bold {
461                                    match style.font {
462                                        Font::Helvetica => Font::HelveticaBold,
463                                        Font::TimesRoman => Font::TimesBold,
464                                        Font::Courier => Font::CourierBold,
465                                        _ => style.font.clone(),
466                                    }
467                                } else {
468                                    style.font.clone()
469                                }
470                            } else {
471                                self.options.font.clone()
472                            }
473                        } else {
474                            self.options.font.clone()
475                        };
476
477                        let text_width_measured =
478                            measure_text(&cell.content, font_to_measure, self.options.font_size);
479                        let centered_x = text_x + (text_width - text_width_measured) / 2.0;
480                        graphics.begin_text();
481                        graphics.set_text_position(centered_x, text_y);
482                        graphics.show_text(&cell.content)?;
483                        graphics.end_text();
484                    }
485                    TextAlign::Right => {
486                        // Determine which font to use based on header style
487                        let font_to_measure = if use_header_style {
488                            if let Some(style) = header_style {
489                                if style.bold {
490                                    match style.font {
491                                        Font::Helvetica => Font::HelveticaBold,
492                                        Font::TimesRoman => Font::TimesBold,
493                                        Font::Courier => Font::CourierBold,
494                                        _ => style.font.clone(),
495                                    }
496                                } else {
497                                    style.font.clone()
498                                }
499                            } else {
500                                self.options.font.clone()
501                            }
502                        } else {
503                            self.options.font.clone()
504                        };
505
506                        let text_width_measured =
507                            measure_text(&cell.content, font_to_measure, self.options.font_size);
508                        let right_x = text_x + text_width - text_width_measured;
509                        graphics.begin_text();
510                        graphics.set_text_position(right_x, text_y);
511                        graphics.show_text(&cell.content)?;
512                        graphics.end_text();
513                    }
514                    TextAlign::Justified => {
515                        // For simple tables, treat justified as left-aligned
516                        graphics.begin_text();
517                        graphics.set_text_position(text_x, text_y);
518                        graphics.show_text(&cell.content)?;
519                        graphics.end_text();
520                    }
521                }
522
523                graphics.restore_state();
524
525                current_x += cell_width;
526                col_index += cell.colspan;
527            }
528
529            current_y += row_height;
530        }
531
532        Ok(())
533    }
534}
535
536impl TableRow {
537    /// Create a new row with cells
538    #[allow(dead_code)]
539    pub fn new(cells: Vec<TableCell>) -> Self {
540        Self {
541            cells,
542            is_header: false,
543        }
544    }
545
546    /// Create a header row
547    #[allow(dead_code)]
548    pub fn header(cells: Vec<TableCell>) -> Self {
549        Self {
550            cells,
551            is_header: true,
552        }
553    }
554}
555
556impl TableCell {
557    /// Create a new cell with content
558    pub fn new(content: String) -> Self {
559        Self {
560            content,
561            align: TextAlign::Left,
562            colspan: 1,
563            rowspan: 1,
564            background_color: None,
565            border_style: None,
566        }
567    }
568
569    /// Create a cell with specific alignment
570    pub fn with_align(content: String, align: TextAlign) -> Self {
571        Self {
572            content,
573            align,
574            colspan: 1,
575            rowspan: 1,
576            background_color: None,
577            border_style: None,
578        }
579    }
580
581    /// Create a cell with colspan
582    pub fn with_colspan(content: String, colspan: usize) -> Self {
583        Self {
584            content,
585            align: TextAlign::Left,
586            colspan,
587            rowspan: 1,
588            background_color: None,
589            border_style: None,
590        }
591    }
592
593    /// Set cell background color
594    pub fn set_background_color(&mut self, color: Color) -> &mut Self {
595        self.background_color = Some(color);
596        self
597    }
598
599    /// Set cell border style
600    pub fn set_border_style(&mut self, style: CellBorderStyle) -> &mut Self {
601        self.border_style = Some(style);
602        self
603    }
604
605    /// Set rowspan
606    pub fn set_rowspan(&mut self, rowspan: usize) -> &mut Self {
607        self.rowspan = rowspan;
608        self
609    }
610
611    /// Set cell alignment
612    pub fn set_align(&mut self, align: TextAlign) -> &mut Self {
613        self.align = align;
614        self
615    }
616
617    /// Set cell colspan
618    pub fn set_colspan(&mut self, colspan: usize) -> &mut Self {
619        self.colspan = colspan;
620        self
621    }
622}
623
624#[cfg(test)]
625mod tests {
626    use super::*;
627
628    #[test]
629    fn test_table_creation() {
630        let table = Table::new(vec![100.0, 150.0, 200.0]);
631        assert_eq!(table.column_widths.len(), 3);
632        assert_eq!(table.rows.len(), 0);
633    }
634
635    #[test]
636    fn test_table_equal_columns() {
637        let table = Table::with_equal_columns(4, 400.0);
638        assert_eq!(table.column_widths.len(), 4);
639        assert_eq!(table.column_widths[0], 100.0);
640        assert_eq!(table.get_width(), 400.0);
641    }
642
643    #[test]
644    fn test_add_header_row() {
645        let mut table = Table::new(vec![100.0, 100.0, 100.0]);
646        let result = table.add_header_row(vec![
647            "Name".to_string(),
648            "Age".to_string(),
649            "City".to_string(),
650        ]);
651        assert!(result.is_ok());
652        assert_eq!(table.rows.len(), 1);
653        assert!(table.rows[0].is_header);
654    }
655
656    #[test]
657    fn test_add_row_mismatch() {
658        let mut table = Table::new(vec![100.0, 100.0]);
659        let result = table.add_row(vec![
660            "John".to_string(),
661            "25".to_string(),
662            "NYC".to_string(),
663        ]);
664        assert!(result.is_err());
665    }
666
667    #[test]
668    fn test_table_cell_creation() {
669        let cell = TableCell::new("Test".to_string());
670        assert_eq!(cell.content, "Test");
671        assert_eq!(cell.align, TextAlign::Left);
672        assert_eq!(cell.colspan, 1);
673    }
674
675    #[test]
676    fn test_table_cell_with_colspan() {
677        let cell = TableCell::with_colspan("Merged".to_string(), 3);
678        assert_eq!(cell.content, "Merged");
679        assert_eq!(cell.colspan, 3);
680    }
681
682    #[test]
683    fn test_custom_row_colspan_validation() {
684        let mut table = Table::new(vec![100.0, 100.0, 100.0]);
685        let cells = vec![
686            TableCell::new("Normal".to_string()),
687            TableCell::with_colspan("Merged".to_string(), 2),
688        ];
689        let result = table.add_custom_row(cells);
690        assert!(result.is_ok());
691        assert_eq!(table.rows.len(), 1);
692    }
693
694    #[test]
695    fn test_custom_row_invalid_colspan() {
696        let mut table = Table::new(vec![100.0, 100.0, 100.0]);
697        let cells = vec![
698            TableCell::new("Normal".to_string()),
699            TableCell::with_colspan("Merged".to_string(), 3), // Total would be 4
700        ];
701        let result = table.add_custom_row(cells);
702        assert!(result.is_err());
703    }
704
705    #[test]
706    fn test_table_options_default() {
707        let options = TableOptions::default();
708        assert_eq!(options.border_width, 1.0);
709        assert_eq!(options.border_color, Color::black());
710        assert_eq!(options.cell_padding, 5.0);
711        assert_eq!(options.font_size, 10.0);
712        assert_eq!(options.grid_style, GridStyle::Full);
713        assert!(options.alternating_row_colors.is_none());
714        assert!(options.background_color.is_none());
715    }
716
717    #[test]
718    fn test_header_style() {
719        let style = HeaderStyle {
720            background_color: Color::gray(0.9),
721            text_color: Color::black(),
722            font: Font::HelveticaBold,
723            bold: true,
724        };
725        assert_eq!(style.background_color, Color::gray(0.9));
726        assert!(style.bold);
727    }
728
729    #[test]
730    fn test_table_dimensions() {
731        let mut table = Table::new(vec![100.0, 150.0, 200.0]);
732        table.options.row_height = 20.0;
733
734        table
735            .add_row(vec!["A".to_string(), "B".to_string(), "C".to_string()])
736            .unwrap();
737        table
738            .add_row(vec!["D".to_string(), "E".to_string(), "F".to_string()])
739            .unwrap();
740
741        assert_eq!(table.get_width(), 450.0);
742        assert_eq!(table.get_height(), 40.0);
743    }
744
745    #[test]
746    fn test_table_position() {
747        let mut table = Table::new(vec![100.0]);
748        table.set_position(50.0, 100.0);
749        assert_eq!(table.position, (50.0, 100.0));
750    }
751
752    #[test]
753    fn test_row_with_alignment() {
754        let mut table = Table::new(vec![100.0, 100.0]);
755        let result = table.add_row_with_alignment(
756            vec!["Left".to_string(), "Right".to_string()],
757            TextAlign::Right,
758        );
759        assert!(result.is_ok());
760        assert_eq!(table.rows[0].cells[0].align, TextAlign::Right);
761    }
762
763    #[test]
764    fn test_table_cell_setters() {
765        let mut cell = TableCell::new("Test".to_string());
766        cell.set_align(TextAlign::Center).set_colspan(2);
767        assert_eq!(cell.align, TextAlign::Center);
768        assert_eq!(cell.colspan, 2);
769    }
770
771    #[test]
772    fn test_auto_row_height() {
773        let table = Table::new(vec![100.0]);
774        let row = TableRow::new(vec![TableCell::new("Test".to_string())]);
775        let height = table.calculate_row_height(&row);
776        assert_eq!(height, 20.0); // font_size (10) + padding*2 (5*2)
777    }
778
779    #[test]
780    fn test_fixed_row_height() {
781        let mut table = Table::new(vec![100.0]);
782        table.options.row_height = 30.0;
783        let row = TableRow::new(vec![TableCell::new("Test".to_string())]);
784        let height = table.calculate_row_height(&row);
785        assert_eq!(height, 30.0);
786    }
787
788    #[test]
789    fn test_grid_styles() {
790        let mut options = TableOptions::default();
791
792        options.grid_style = GridStyle::None;
793        assert_eq!(options.grid_style, GridStyle::None);
794
795        options.grid_style = GridStyle::Horizontal;
796        assert_eq!(options.grid_style, GridStyle::Horizontal);
797
798        options.grid_style = GridStyle::Vertical;
799        assert_eq!(options.grid_style, GridStyle::Vertical);
800
801        options.grid_style = GridStyle::Outline;
802        assert_eq!(options.grid_style, GridStyle::Outline);
803    }
804
805    #[test]
806    fn test_cell_border_style() {
807        let style = CellBorderStyle::default();
808        assert_eq!(style.width, 1.0);
809        assert_eq!(style.color, Color::black());
810        assert!(style.dash_pattern.is_none());
811
812        let custom_style = CellBorderStyle {
813            width: 2.0,
814            color: Color::rgb(1.0, 0.0, 0.0),
815            dash_pattern: Some(LineDashPattern::new(vec![5.0, 3.0], 0.0)),
816        };
817        assert_eq!(custom_style.width, 2.0);
818        assert!(custom_style.dash_pattern.is_some());
819    }
820
821    #[test]
822    fn test_table_with_alternating_colors() {
823        let mut table = Table::new(vec![100.0, 100.0]);
824        table.options.alternating_row_colors = Some((Color::gray(0.95), Color::gray(0.9)));
825
826        table
827            .add_row(vec!["Row 1".to_string(), "Data 1".to_string()])
828            .unwrap();
829        table
830            .add_row(vec!["Row 2".to_string(), "Data 2".to_string()])
831            .unwrap();
832
833        assert_eq!(table.rows.len(), 2);
834        assert!(table.options.alternating_row_colors.is_some());
835    }
836
837    #[test]
838    fn test_cell_with_background() {
839        let mut cell = TableCell::new("Test".to_string());
840        cell.set_background_color(Color::rgb(0.0, 1.0, 0.0));
841
842        assert!(cell.background_color.is_some());
843        assert_eq!(cell.background_color.unwrap(), Color::rgb(0.0, 1.0, 0.0));
844    }
845
846    #[test]
847    fn test_cell_with_custom_border() {
848        let mut cell = TableCell::new("Test".to_string());
849        let border_style = CellBorderStyle {
850            width: 2.0,
851            color: Color::rgb(0.0, 0.0, 1.0),
852            dash_pattern: None,
853        };
854        cell.set_border_style(border_style);
855
856        assert!(cell.border_style.is_some());
857        let style = cell.border_style.as_ref().unwrap();
858        assert_eq!(style.width, 2.0);
859        assert_eq!(style.color, Color::rgb(0.0, 0.0, 1.0));
860    }
861}