1use crate::error::{ensure_finite, PdfError};
7use crate::graphics::{Color, GraphicsContext, LineDashPattern};
8use crate::text::{measure_text, Font, TextAlign};
9
10#[derive(Debug, Clone)]
12pub struct Table {
13 rows: Vec<TableRow>,
15 column_widths: Vec<f64>,
17 position: (f64, f64),
19 options: TableOptions,
21}
22
23#[derive(Debug, Clone)]
25pub struct TableOptions {
26 pub border_width: f64,
28 pub border_color: Color,
30 pub cell_padding: f64,
32 pub row_height: f64,
34 pub font: Font,
36 pub font_size: f64,
38 pub text_color: Color,
40 pub header_style: Option<HeaderStyle>,
42 pub grid_style: GridStyle,
44 pub cell_border_style: CellBorderStyle,
46 pub alternating_row_colors: Option<(Color, Color)>,
48 pub background_color: Option<Color>,
50 pub repeat_header_on_split: bool,
53}
54
55#[derive(Debug, Clone)]
57pub struct HeaderStyle {
58 pub background_color: Color,
60 pub text_color: Color,
62 pub font: Font,
64 pub bold: bool,
66}
67
68#[derive(Debug, Clone)]
70pub struct TableRow {
71 cells: Vec<TableCell>,
73 is_header: bool,
75 row_height: Option<f64>,
77}
78
79#[derive(Debug, Clone)]
81pub struct TableCell {
82 content: String,
84 align: TextAlign,
86 colspan: usize,
88 rowspan: usize,
90 background_color: Option<Color>,
92 border_style: Option<CellBorderStyle>,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq)]
98pub enum GridStyle {
99 None,
101 Horizontal,
103 Vertical,
105 Full,
107 Outline,
109}
110
111#[derive(Debug, Clone)]
113pub struct CellBorderStyle {
114 pub width: f64,
116 pub color: Color,
118 pub dash_pattern: Option<LineDashPattern>,
120}
121
122impl Default for CellBorderStyle {
123 fn default() -> Self {
124 Self {
125 width: 1.0,
126 color: Color::black(),
127 dash_pattern: None,
128 }
129 }
130}
131
132impl Default for TableOptions {
133 fn default() -> Self {
134 Self {
135 border_width: 1.0,
136 border_color: Color::black(),
137 cell_padding: 5.0,
138 row_height: 0.0, font: Font::Helvetica,
140 font_size: 10.0,
141 text_color: Color::black(),
142 header_style: None,
143 grid_style: GridStyle::Full,
144 cell_border_style: CellBorderStyle::default(),
145 alternating_row_colors: None,
146 background_color: None,
147 repeat_header_on_split: true,
148 }
149 }
150}
151
152impl Table {
153 pub fn new(column_widths: Vec<f64>) -> Self {
155 Self {
156 rows: Vec::new(),
157 column_widths,
158 position: (0.0, 0.0),
159 options: TableOptions::default(),
160 }
161 }
162
163 pub fn with_equal_columns(num_columns: usize, total_width: f64) -> Self {
165 let column_width = total_width / num_columns as f64;
166 let column_widths = vec![column_width; num_columns];
167 Self::new(column_widths)
168 }
169
170 pub fn set_position(&mut self, x: f64, y: f64) -> &mut Self {
172 self.position = (x, y);
173 self
174 }
175
176 pub fn set_options(&mut self, options: TableOptions) -> &mut Self {
178 self.options = options;
179 self
180 }
181
182 pub fn options(&self) -> &TableOptions {
184 &self.options
185 }
186
187 pub fn add_header_row(&mut self, cells: Vec<String>) -> Result<&mut Self, PdfError> {
189 if cells.len() != self.column_widths.len() {
190 return Err(PdfError::InvalidStructure(
191 "Header cells count doesn't match column count".to_string(),
192 ));
193 }
194
195 let row_cells: Vec<TableCell> = cells
196 .into_iter()
197 .map(|content| TableCell {
198 content,
199 align: TextAlign::Center,
200 colspan: 1,
201 rowspan: 1,
202 background_color: None,
203 border_style: None,
204 })
205 .collect();
206
207 self.rows.push(TableRow {
208 cells: row_cells,
209 is_header: true,
210 row_height: None,
211 });
212
213 Ok(self)
214 }
215
216 pub fn set_last_row_height(&mut self, height: f64) -> &mut Self {
218 if let Some(row) = self.rows.last_mut() {
219 row.row_height = Some(height);
220 }
221 self
222 }
223
224 pub fn add_row(&mut self, cells: Vec<String>) -> Result<&mut Self, PdfError> {
226 self.add_row_with_alignment(cells, TextAlign::Left)
227 }
228
229 pub fn add_row_with_alignment(
231 &mut self,
232 cells: Vec<String>,
233 align: TextAlign,
234 ) -> Result<&mut Self, PdfError> {
235 if cells.len() != self.column_widths.len() {
236 return Err(PdfError::InvalidStructure(
237 "Row cells count doesn't match column count".to_string(),
238 ));
239 }
240
241 let row_cells: Vec<TableCell> = cells
242 .into_iter()
243 .map(|content| TableCell {
244 content,
245 align,
246 colspan: 1,
247 rowspan: 1,
248 background_color: None,
249 border_style: None,
250 })
251 .collect();
252
253 self.rows.push(TableRow {
254 cells: row_cells,
255 is_header: false,
256 row_height: None,
257 });
258
259 Ok(self)
260 }
261
262 pub fn add_custom_row(&mut self, cells: Vec<TableCell>) -> Result<&mut Self, PdfError> {
264 let total_colspan: usize = cells.iter().map(|c| c.colspan).sum();
266 if total_colspan != self.column_widths.len() {
267 return Err(PdfError::InvalidStructure(
268 "Total colspan doesn't match column count".to_string(),
269 ));
270 }
271
272 self.rows.push(TableRow {
273 cells,
274 is_header: false,
275 row_height: None,
276 });
277
278 Ok(self)
279 }
280
281 fn calculate_row_height(&self, row: &TableRow) -> f64 {
283 if let Some(h) = row.row_height {
285 return h;
286 }
287 if self.options.row_height > 0.0 {
288 return self.options.row_height;
289 }
290
291 let line_height = self.options.font_size * 1.2;
293 let max_lines = row
294 .cells
295 .iter()
296 .map(|cell| cell.content.split('\n').count())
297 .max()
298 .unwrap_or(1);
299
300 if max_lines <= 1 {
301 self.options.font_size + (self.options.cell_padding * 2.0)
303 } else {
304 self.options.font_size
306 + ((max_lines - 1) as f64 * line_height)
307 + (self.options.cell_padding * 2.0)
308 }
309 }
310
311 pub fn get_height(&self) -> f64 {
313 self.rows
314 .iter()
315 .map(|row| self.calculate_row_height(row))
316 .sum()
317 }
318
319 pub fn get_width(&self) -> f64 {
321 self.column_widths.iter().sum()
322 }
323
324 pub fn row_count(&self) -> usize {
326 self.rows.len()
327 }
328
329 pub fn header_count(&self) -> usize {
331 self.rows.iter().take_while(|r| r.is_header).count()
332 }
333
334 pub fn position(&self) -> (f64, f64) {
336 self.position
337 }
338
339 pub(crate) fn prepend_headers_from(&mut self, source: &Table) {
349 let header_count = source.header_count();
350 if header_count == 0 {
351 return;
352 }
353 let mut new_rows: Vec<TableRow> = source.rows[..header_count].to_vec();
354 new_rows.extend(self.rows.drain(..));
355 self.rows = new_rows;
356 }
357
358 pub fn render(&self, graphics: &mut GraphicsContext) -> Result<(), PdfError> {
365 self.render_rows_slice(graphics, &self.rows, self.get_height())
369 }
370
371 pub fn render_with_split(
388 &self,
389 graphics: &mut GraphicsContext,
390 bottom_y: f64,
391 ) -> Result<Option<Table>, PdfError> {
392 ensure_finite("bottom_y", bottom_y)?;
393 let (start_x, _start_y) = self.position;
394
395 let rendered_count = self.fit_count(bottom_y);
397 let rendered_height = self.rows[..rendered_count]
398 .iter()
399 .map(|r| self.calculate_row_height(r))
400 .sum::<f64>();
401
402 if rendered_count > 0 {
403 self.render_rows_slice(graphics, &self.rows[..rendered_count], rendered_height)?;
404 }
405
406 if rendered_count == self.rows.len() {
407 Ok(None)
408 } else {
409 let mut tail = self.clone();
410 tail.rows = self.rows[rendered_count..].to_vec();
411 tail.position = (start_x, 0.0);
412 Ok(Some(tail))
413 }
414 }
415
416 pub fn render_strict(
424 &self,
425 graphics: &mut GraphicsContext,
426 bottom_y: f64,
427 ) -> Result<(), PdfError> {
428 ensure_finite("bottom_y", bottom_y)?;
429 let rendered = self.fit_count(bottom_y);
430 if rendered < self.rows.len() {
431 return Err(PdfError::TableOverflow {
432 rendered,
433 dropped: self.rows.len() - rendered,
434 bottom_y,
435 });
436 }
437 self.render_rows_slice(graphics, &self.rows, self.get_height())
440 }
441
442 fn fit_count(&self, bottom_y: f64) -> usize {
444 let (_start_x, start_y) = self.position;
445 let mut current_y = start_y;
446 let mut count = 0usize;
447 for row in &self.rows {
448 let row_height = self.calculate_row_height(row);
449 let next_y = current_y - row_height;
450 if next_y < bottom_y {
451 break;
452 }
453 count += 1;
454 current_y = next_y;
455 }
456 count
457 }
458
459 fn render_rows_slice(
462 &self,
463 graphics: &mut GraphicsContext,
464 rows: &[TableRow],
465 rendered_height: f64,
466 ) -> Result<(), PdfError> {
467 let (start_x, start_y) = self.position;
468 let mut current_y = start_y;
469
470 if let Some(bg_color) = self.options.background_color {
472 graphics.save_state();
473 graphics.set_fill_color(bg_color);
474 graphics.rectangle(
475 start_x,
476 start_y - rendered_height,
477 self.get_width(),
478 rendered_height,
479 );
480 graphics.fill();
481 graphics.restore_state();
482 }
483
484 let mut data_row_index: usize = 0; for (row_index, row) in rows.iter().enumerate() {
487 let row_height = self.calculate_row_height(row);
488 let mut current_x = start_x;
489
490 let use_header_style = row.is_header && self.options.header_style.is_some();
492 let header_style = self.options.header_style.as_ref();
493
494 let mut col_index = 0;
496 for cell in &row.cells {
497 let mut cell_width = 0.0;
499 for i in 0..cell.colspan {
500 if col_index + i < self.column_widths.len() {
501 cell_width += self.column_widths[col_index + i];
502 }
503 }
504
505 let cell_rect_y = current_y - row_height;
507
508 if let Some(cell_bg) = cell.background_color {
511 graphics.save_state();
512 graphics.set_fill_color(cell_bg);
513 graphics.rectangle(current_x, cell_rect_y, cell_width, row_height);
514 graphics.fill();
515 graphics.restore_state();
516 }
517 else if use_header_style {
519 if let Some(style) = header_style {
520 graphics.save_state();
521 graphics.set_fill_color(style.background_color);
522 graphics.rectangle(current_x, cell_rect_y, cell_width, row_height);
523 graphics.fill();
524 graphics.restore_state();
525 }
526 }
527 else if let Some((even_color, odd_color)) = self.options.alternating_row_colors {
529 if !row.is_header {
530 let color = if data_row_index % 2 == 0 {
531 even_color
532 } else {
533 odd_color
534 };
535 graphics.save_state();
536 graphics.set_fill_color(color);
537 graphics.rectangle(current_x, cell_rect_y, cell_width, row_height);
538 graphics.fill();
539 graphics.restore_state();
540 }
541 }
542
543 let should_draw_border = match self.options.grid_style {
545 GridStyle::None => false,
546 GridStyle::Full => true,
547 GridStyle::Horizontal => {
548 true
550 }
551 GridStyle::Vertical => {
552 true
554 }
555 GridStyle::Outline => {
556 col_index == 0
560 || col_index + cell.colspan >= self.column_widths.len()
561 || row_index == 0
562 || row_index == rows.len() - 1
563 }
564 };
565
566 if should_draw_border {
567 graphics.save_state();
568
569 let border_style = cell
571 .border_style
572 .as_ref()
573 .unwrap_or(&self.options.cell_border_style);
574
575 graphics.set_stroke_color(border_style.color);
576 graphics.set_line_width(border_style.width);
577
578 if let Some(dash_pattern) = &border_style.dash_pattern {
580 graphics.set_line_dash_pattern(dash_pattern.clone());
581 }
582
583 match self.options.grid_style {
585 GridStyle::Full | GridStyle::Outline => {
586 graphics.rectangle(current_x, cell_rect_y, cell_width, row_height);
587 graphics.stroke();
588 }
589 GridStyle::Horizontal => {
590 graphics.move_to(current_x, current_y);
592 graphics.line_to(current_x + cell_width, current_y);
593 graphics.move_to(current_x, cell_rect_y);
595 graphics.line_to(current_x + cell_width, cell_rect_y);
596 graphics.stroke();
597 }
598 GridStyle::Vertical => {
599 graphics.move_to(current_x, current_y);
601 graphics.line_to(current_x, cell_rect_y);
602 graphics.move_to(current_x + cell_width, current_y);
604 graphics.line_to(current_x + cell_width, cell_rect_y);
605 graphics.stroke();
606 }
607 GridStyle::None => {}
608 }
609
610 graphics.restore_state();
611 }
612
613 let text_x = current_x + self.options.cell_padding;
616 let text_y = current_y - self.options.cell_padding - self.options.font_size;
617 let text_width = cell_width - (2.0 * self.options.cell_padding);
618
619 graphics.save_state();
620
621 if use_header_style {
623 if let Some(style) = header_style {
624 let font = if style.bold {
625 match style.font {
626 Font::Helvetica => Font::HelveticaBold,
627 Font::TimesRoman => Font::TimesBold,
628 Font::Courier => Font::CourierBold,
629 _ => style.font.clone(),
630 }
631 } else {
632 style.font.clone()
633 };
634 graphics.set_font(font, self.options.font_size);
635 graphics.set_fill_color(style.text_color);
636 }
637 } else {
638 graphics.set_font(self.options.font.clone(), self.options.font_size);
639 graphics.set_fill_color(self.options.text_color);
640 }
641
642 let lines: Vec<&str> = cell.content.split('\n').collect();
644 let line_height = self.options.font_size * 1.2;
645
646 let font_to_measure = if use_header_style {
648 if let Some(style) = header_style {
649 if style.bold {
650 match style.font {
651 Font::Helvetica => Font::HelveticaBold,
652 Font::TimesRoman => Font::TimesBold,
653 Font::Courier => Font::CourierBold,
654 _ => style.font.clone(),
655 }
656 } else {
657 style.font.clone()
658 }
659 } else {
660 self.options.font.clone()
661 }
662 } else {
663 self.options.font.clone()
664 };
665
666 for (line_idx, line) in lines.iter().enumerate() {
668 let line_y = text_y - (line_idx as f64 * line_height);
669
670 let line_x = match cell.align {
671 TextAlign::Center => {
672 let measured =
673 measure_text(line, &font_to_measure, self.options.font_size);
674 text_x + (text_width - measured) / 2.0
675 }
676 TextAlign::Right => {
677 let measured =
678 measure_text(line, &font_to_measure, self.options.font_size);
679 text_x + text_width - measured
680 }
681 TextAlign::Left | TextAlign::Justified => text_x,
682 };
683
684 graphics.begin_text();
685 graphics.set_text_position(line_x, line_y);
686 graphics.show_text(line)?;
687 graphics.end_text();
688 }
689
690 graphics.restore_state();
691
692 current_x += cell_width;
693 col_index += cell.colspan;
694 }
695
696 if !row.is_header {
697 data_row_index += 1;
698 }
699 current_y -= row_height;
700 }
701
702 Ok(())
703 }
704}
705
706impl TableRow {
707 #[allow(dead_code)]
709 pub fn new(cells: Vec<TableCell>) -> Self {
710 Self {
711 cells,
712 is_header: false,
713 row_height: None,
714 }
715 }
716
717 #[allow(dead_code)]
719 pub fn header(cells: Vec<TableCell>) -> Self {
720 Self {
721 cells,
722 is_header: true,
723 row_height: None,
724 }
725 }
726
727 pub fn set_row_height(&mut self, height: f64) -> &mut Self {
729 self.row_height = Some(height);
730 self
731 }
732}
733
734impl TableCell {
735 pub fn new(content: String) -> Self {
737 Self {
738 content,
739 align: TextAlign::Left,
740 colspan: 1,
741 rowspan: 1,
742 background_color: None,
743 border_style: None,
744 }
745 }
746
747 pub fn with_align(content: String, align: TextAlign) -> Self {
749 Self {
750 content,
751 align,
752 colspan: 1,
753 rowspan: 1,
754 background_color: None,
755 border_style: None,
756 }
757 }
758
759 pub fn with_colspan(content: String, colspan: usize) -> Self {
761 Self {
762 content,
763 align: TextAlign::Left,
764 colspan,
765 rowspan: 1,
766 background_color: None,
767 border_style: None,
768 }
769 }
770
771 pub fn set_background_color(&mut self, color: Color) -> &mut Self {
773 self.background_color = Some(color);
774 self
775 }
776
777 pub fn set_border_style(&mut self, style: CellBorderStyle) -> &mut Self {
779 self.border_style = Some(style);
780 self
781 }
782
783 pub fn set_rowspan(&mut self, rowspan: usize) -> &mut Self {
785 self.rowspan = rowspan;
786 self
787 }
788
789 pub fn set_align(&mut self, align: TextAlign) -> &mut Self {
791 self.align = align;
792 self
793 }
794
795 pub fn set_colspan(&mut self, colspan: usize) -> &mut Self {
797 self.colspan = colspan;
798 self
799 }
800}
801
802#[cfg(test)]
803mod tests {
804 use super::*;
805
806 #[test]
807 fn test_table_creation() {
808 let table = Table::new(vec![100.0, 150.0, 200.0]);
809 assert_eq!(table.column_widths.len(), 3);
810 assert_eq!(table.rows.len(), 0);
811 }
812
813 #[test]
814 fn test_table_equal_columns() {
815 let table = Table::with_equal_columns(4, 400.0);
816 assert_eq!(table.column_widths.len(), 4);
817 assert_eq!(table.column_widths[0], 100.0);
818 assert_eq!(table.get_width(), 400.0);
819 }
820
821 #[test]
822 fn test_add_header_row() {
823 let mut table = Table::new(vec![100.0, 100.0, 100.0]);
824 let result = table.add_header_row(vec![
825 "Name".to_string(),
826 "Age".to_string(),
827 "City".to_string(),
828 ]);
829 assert!(result.is_ok());
830 assert_eq!(table.rows.len(), 1);
831 assert!(table.rows[0].is_header);
832 }
833
834 #[test]
835 fn test_add_row_mismatch() {
836 let mut table = Table::new(vec![100.0, 100.0]);
837 let result = table.add_row(vec![
838 "John".to_string(),
839 "25".to_string(),
840 "NYC".to_string(),
841 ]);
842 assert!(result.is_err());
843 }
844
845 #[test]
846 fn test_table_cell_creation() {
847 let cell = TableCell::new("Test".to_string());
848 assert_eq!(cell.content, "Test");
849 assert_eq!(cell.align, TextAlign::Left);
850 assert_eq!(cell.colspan, 1);
851 }
852
853 #[test]
854 fn test_table_cell_with_colspan() {
855 let cell = TableCell::with_colspan("Merged".to_string(), 3);
856 assert_eq!(cell.content, "Merged");
857 assert_eq!(cell.colspan, 3);
858 }
859
860 #[test]
861 fn test_custom_row_colspan_validation() {
862 let mut table = Table::new(vec![100.0, 100.0, 100.0]);
863 let cells = vec![
864 TableCell::new("Normal".to_string()),
865 TableCell::with_colspan("Merged".to_string(), 2),
866 ];
867 let result = table.add_custom_row(cells);
868 assert!(result.is_ok());
869 assert_eq!(table.rows.len(), 1);
870 }
871
872 #[test]
873 fn test_custom_row_invalid_colspan() {
874 let mut table = Table::new(vec![100.0, 100.0, 100.0]);
875 let cells = vec![
876 TableCell::new("Normal".to_string()),
877 TableCell::with_colspan("Merged".to_string(), 3), ];
879 let result = table.add_custom_row(cells);
880 assert!(result.is_err());
881 }
882
883 #[test]
884 fn test_table_options_default() {
885 let options = TableOptions::default();
886 assert_eq!(options.border_width, 1.0);
887 assert_eq!(options.border_color, Color::black());
888 assert_eq!(options.cell_padding, 5.0);
889 assert_eq!(options.font_size, 10.0);
890 assert_eq!(options.grid_style, GridStyle::Full);
891 assert!(options.alternating_row_colors.is_none());
892 assert!(options.background_color.is_none());
893 }
894
895 #[test]
896 fn test_header_style() {
897 let style = HeaderStyle {
898 background_color: Color::gray(0.9),
899 text_color: Color::black(),
900 font: Font::HelveticaBold,
901 bold: true,
902 };
903 assert_eq!(style.background_color, Color::gray(0.9));
904 assert!(style.bold);
905 }
906
907 #[test]
908 fn test_table_dimensions() {
909 let mut table = Table::new(vec![100.0, 150.0, 200.0]);
910 table.options.row_height = 20.0;
911
912 table
913 .add_row(vec!["A".to_string(), "B".to_string(), "C".to_string()])
914 .unwrap();
915 table
916 .add_row(vec!["D".to_string(), "E".to_string(), "F".to_string()])
917 .unwrap();
918
919 assert_eq!(table.get_width(), 450.0);
920 assert_eq!(table.get_height(), 40.0);
921 }
922
923 #[test]
924 fn test_table_position() {
925 let mut table = Table::new(vec![100.0]);
926 table.set_position(50.0, 100.0);
927 assert_eq!(table.position, (50.0, 100.0));
928 }
929
930 #[test]
931 fn test_row_with_alignment() {
932 let mut table = Table::new(vec![100.0, 100.0]);
933 let result = table.add_row_with_alignment(
934 vec!["Left".to_string(), "Right".to_string()],
935 TextAlign::Right,
936 );
937 assert!(result.is_ok());
938 assert_eq!(table.rows[0].cells[0].align, TextAlign::Right);
939 }
940
941 #[test]
942 fn test_table_cell_setters() {
943 let mut cell = TableCell::new("Test".to_string());
944 cell.set_align(TextAlign::Center).set_colspan(2);
945 assert_eq!(cell.align, TextAlign::Center);
946 assert_eq!(cell.colspan, 2);
947 }
948
949 #[test]
950 fn test_auto_row_height() {
951 let table = Table::new(vec![100.0]);
952 let row = TableRow::new(vec![TableCell::new("Test".to_string())]);
953 let height = table.calculate_row_height(&row);
954 assert_eq!(height, 20.0); }
956
957 #[test]
958 fn test_fixed_row_height() {
959 let mut table = Table::new(vec![100.0]);
960 table.options.row_height = 30.0;
961 let row = TableRow::new(vec![TableCell::new("Test".to_string())]);
962 let height = table.calculate_row_height(&row);
963 assert_eq!(height, 30.0);
964 }
965
966 #[test]
967 fn test_grid_styles() {
968 let mut options = TableOptions::default();
969
970 options.grid_style = GridStyle::None;
971 assert_eq!(options.grid_style, GridStyle::None);
972
973 options.grid_style = GridStyle::Horizontal;
974 assert_eq!(options.grid_style, GridStyle::Horizontal);
975
976 options.grid_style = GridStyle::Vertical;
977 assert_eq!(options.grid_style, GridStyle::Vertical);
978
979 options.grid_style = GridStyle::Outline;
980 assert_eq!(options.grid_style, GridStyle::Outline);
981 }
982
983 #[test]
984 fn test_cell_border_style() {
985 let style = CellBorderStyle::default();
986 assert_eq!(style.width, 1.0);
987 assert_eq!(style.color, Color::black());
988 assert!(style.dash_pattern.is_none());
989
990 let custom_style = CellBorderStyle {
991 width: 2.0,
992 color: Color::rgb(1.0, 0.0, 0.0),
993 dash_pattern: Some(LineDashPattern::new(vec![5.0, 3.0], 0.0)),
994 };
995 assert_eq!(custom_style.width, 2.0);
996 assert!(custom_style.dash_pattern.is_some());
997 }
998
999 #[test]
1000 fn test_table_with_alternating_colors() {
1001 let mut table = Table::new(vec![100.0, 100.0]);
1002 table.options.alternating_row_colors = Some((Color::gray(0.95), Color::gray(0.9)));
1003
1004 table
1005 .add_row(vec!["Row 1".to_string(), "Data 1".to_string()])
1006 .unwrap();
1007 table
1008 .add_row(vec!["Row 2".to_string(), "Data 2".to_string()])
1009 .unwrap();
1010
1011 assert_eq!(table.rows.len(), 2);
1012 assert!(table.options.alternating_row_colors.is_some());
1013 }
1014
1015 #[test]
1016 fn test_cell_with_background() {
1017 let mut cell = TableCell::new("Test".to_string());
1018 cell.set_background_color(Color::rgb(0.0, 1.0, 0.0));
1019
1020 assert!(cell.background_color.is_some());
1021 assert_eq!(cell.background_color.unwrap(), Color::rgb(0.0, 1.0, 0.0));
1022 }
1023
1024 #[test]
1025 fn test_cell_with_custom_border() {
1026 let mut cell = TableCell::new("Test".to_string());
1027 let border_style = CellBorderStyle {
1028 width: 2.0,
1029 color: Color::rgb(0.0, 0.0, 1.0),
1030 dash_pattern: None,
1031 };
1032 cell.set_border_style(border_style);
1033
1034 assert!(cell.border_style.is_some());
1035 let style = cell.border_style.as_ref().unwrap();
1036 assert_eq!(style.width, 2.0);
1037 assert_eq!(style.color, Color::rgb(0.0, 0.0, 1.0));
1038 }
1039}