Skip to main content

pdf_oxide/writer/
table_renderer.rs

1//! Table rendering for PDF generation.
2//!
3//! This module provides support for creating tables in PDF documents,
4//! including layout calculation, borders, backgrounds, and text wrapping.
5//!
6//! # Example
7//!
8//! ```ignore
9//! use pdf_oxide::writer::{Table, TableCell, TableStyle};
10//!
11//! let table = Table::new(vec![
12//!     vec![TableCell::text("Name"), TableCell::text("Age")],
13//!     vec![TableCell::text("Alice"), TableCell::text("30")],
14//!     vec![TableCell::text("Bob"), TableCell::text("25")],
15//! ])
16//! .with_header_row()
17//! .with_borders(BorderStyle::all(0.5));
18//! ```
19
20use super::content_stream::ContentStreamBuilder;
21use crate::error::Result;
22
23/// Horizontal alignment for cell content.
24#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
25pub enum CellAlign {
26    /// Align to the left
27    #[default]
28    Left,
29    /// Center horizontally
30    Center,
31    /// Align to the right
32    Right,
33}
34
35/// Vertical alignment for cell content.
36#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
37pub enum CellVAlign {
38    /// Align to the top
39    #[default]
40    Top,
41    /// Center vertically
42    Middle,
43    /// Align to the bottom
44    Bottom,
45}
46
47/// Column width specification.
48#[derive(Debug, Clone, Copy, Default)]
49pub enum ColumnWidth {
50    /// Automatic width based on content
51    #[default]
52    Auto,
53    /// Fixed width in points
54    Fixed(f32),
55    /// Percentage of table width
56    Percent(f32),
57    /// Proportional weight (flex)
58    Weight(f32),
59}
60
61/// Border style for tables.
62#[derive(Debug, Clone, Copy)]
63pub struct TableBorderStyle {
64    /// Border width in points
65    pub width: f32,
66    /// Border color (RGB, 0.0-1.0)
67    pub color: (f32, f32, f32),
68}
69
70impl Default for TableBorderStyle {
71    fn default() -> Self {
72        Self {
73            width: 0.5,
74            color: (0.0, 0.0, 0.0), // Black
75        }
76    }
77}
78
79impl TableBorderStyle {
80    /// Create a new border style.
81    pub fn new(width: f32) -> Self {
82        Self {
83            width,
84            ..Default::default()
85        }
86    }
87
88    /// Create a border with specific color.
89    pub fn with_color(mut self, r: f32, g: f32, b: f32) -> Self {
90        self.color = (r, g, b);
91        self
92    }
93
94    /// Create a thin border (0.25pt).
95    pub fn thin() -> Self {
96        Self::new(0.25)
97    }
98
99    /// Create a medium border (0.5pt).
100    pub fn medium() -> Self {
101        Self::new(0.5)
102    }
103
104    /// Create a thick border (1.0pt).
105    pub fn thick() -> Self {
106        Self::new(1.0)
107    }
108
109    /// No border.
110    pub fn none() -> Self {
111        Self::new(0.0)
112    }
113}
114
115/// Border configuration for a cell or table.
116#[derive(Debug, Clone, Copy, Default)]
117pub struct Borders {
118    /// Top border
119    pub top: Option<TableBorderStyle>,
120    /// Right border
121    pub right: Option<TableBorderStyle>,
122    /// Bottom border
123    pub bottom: Option<TableBorderStyle>,
124    /// Left border
125    pub left: Option<TableBorderStyle>,
126}
127
128impl Borders {
129    /// No borders.
130    pub fn none() -> Self {
131        Self::default()
132    }
133
134    /// All borders with the same style.
135    pub fn all(style: TableBorderStyle) -> Self {
136        Self {
137            top: Some(style),
138            right: Some(style),
139            bottom: Some(style),
140            left: Some(style),
141        }
142    }
143
144    /// Horizontal borders only (top and bottom).
145    pub fn horizontal(style: TableBorderStyle) -> Self {
146        Self {
147            top: Some(style),
148            bottom: Some(style),
149            ..Default::default()
150        }
151    }
152
153    /// Vertical borders only (left and right).
154    pub fn vertical(style: TableBorderStyle) -> Self {
155        Self {
156            left: Some(style),
157            right: Some(style),
158            ..Default::default()
159        }
160    }
161
162    /// Set top border.
163    pub fn with_top(mut self, style: TableBorderStyle) -> Self {
164        self.top = Some(style);
165        self
166    }
167
168    /// Set bottom border.
169    pub fn with_bottom(mut self, style: TableBorderStyle) -> Self {
170        self.bottom = Some(style);
171        self
172    }
173
174    /// Set left border.
175    pub fn with_left(mut self, style: TableBorderStyle) -> Self {
176        self.left = Some(style);
177        self
178    }
179
180    /// Set right border.
181    pub fn with_right(mut self, style: TableBorderStyle) -> Self {
182        self.right = Some(style);
183        self
184    }
185}
186
187/// Cell padding configuration.
188#[derive(Debug, Clone, Copy)]
189pub struct CellPadding {
190    /// Top padding in points
191    pub top: f32,
192    /// Right padding in points
193    pub right: f32,
194    /// Bottom padding in points
195    pub bottom: f32,
196    /// Left padding in points
197    pub left: f32,
198}
199
200impl Default for CellPadding {
201    fn default() -> Self {
202        Self {
203            top: 4.0,
204            right: 4.0,
205            bottom: 4.0,
206            left: 4.0,
207        }
208    }
209}
210
211impl CellPadding {
212    /// Create uniform padding.
213    pub fn uniform(padding: f32) -> Self {
214        Self {
215            top: padding,
216            right: padding,
217            bottom: padding,
218            left: padding,
219        }
220    }
221
222    /// Create padding with horizontal and vertical values.
223    pub fn symmetric(horizontal: f32, vertical: f32) -> Self {
224        Self {
225            top: vertical,
226            right: horizontal,
227            bottom: vertical,
228            left: horizontal,
229        }
230    }
231
232    /// No padding.
233    pub fn none() -> Self {
234        Self::uniform(0.0)
235    }
236
237    /// Total horizontal padding.
238    pub fn horizontal(&self) -> f32 {
239        self.left + self.right
240    }
241
242    /// Total vertical padding.
243    pub fn vertical(&self) -> f32 {
244        self.top + self.bottom
245    }
246}
247
248/// A single table cell.
249#[derive(Debug, Clone)]
250pub struct TableCell {
251    /// Cell content (text)
252    pub content: String,
253    /// Number of columns this cell spans
254    pub colspan: usize,
255    /// Number of rows this cell spans
256    pub rowspan: usize,
257    /// Horizontal alignment
258    pub align: CellAlign,
259    /// Vertical alignment
260    pub valign: CellVAlign,
261    /// Cell-specific padding (overrides table default)
262    pub padding: Option<CellPadding>,
263    /// Cell-specific borders (overrides table default)
264    pub borders: Option<Borders>,
265    /// Background color (RGB, 0.0-1.0)
266    pub background: Option<(f32, f32, f32)>,
267    /// Font name override
268    pub font_name: Option<String>,
269    /// Font size override
270    pub font_size: Option<f32>,
271    /// Bold text
272    pub bold: bool,
273    /// Italic text
274    pub italic: bool,
275}
276
277impl TableCell {
278    /// Create a new text cell.
279    pub fn text(content: impl Into<String>) -> Self {
280        Self {
281            content: content.into(),
282            colspan: 1,
283            rowspan: 1,
284            align: CellAlign::default(),
285            valign: CellVAlign::default(),
286            padding: None,
287            borders: None,
288            background: None,
289            font_name: None,
290            font_size: None,
291            bold: false,
292            italic: false,
293        }
294    }
295
296    /// Create an empty cell.
297    pub fn empty() -> Self {
298        Self::text("")
299    }
300
301    /// Set column span.
302    pub fn colspan(mut self, span: usize) -> Self {
303        self.colspan = span.max(1);
304        self
305    }
306
307    /// Set row span.
308    pub fn rowspan(mut self, span: usize) -> Self {
309        self.rowspan = span.max(1);
310        self
311    }
312
313    /// Set horizontal alignment.
314    pub fn align(mut self, align: CellAlign) -> Self {
315        self.align = align;
316        self
317    }
318
319    /// Set vertical alignment.
320    pub fn valign(mut self, valign: CellVAlign) -> Self {
321        self.valign = valign;
322        self
323    }
324
325    /// Set cell padding.
326    pub fn padding(mut self, padding: CellPadding) -> Self {
327        self.padding = Some(padding);
328        self
329    }
330
331    /// Set cell borders.
332    pub fn borders(mut self, borders: Borders) -> Self {
333        self.borders = Some(borders);
334        self
335    }
336
337    /// Set background color.
338    pub fn background(mut self, r: f32, g: f32, b: f32) -> Self {
339        self.background = Some((r, g, b));
340        self
341    }
342
343    /// Set font.
344    pub fn font(mut self, name: impl Into<String>, size: f32) -> Self {
345        self.font_name = Some(name.into());
346        self.font_size = Some(size);
347        self
348    }
349
350    /// Set bold style.
351    pub fn bold(mut self) -> Self {
352        self.bold = true;
353        self
354    }
355
356    /// Set italic style.
357    pub fn italic(mut self) -> Self {
358        self.italic = true;
359        self
360    }
361
362    /// Create a header cell (centered, bold).
363    pub fn header(content: impl Into<String>) -> Self {
364        Self::text(content).align(CellAlign::Center).bold()
365    }
366
367    /// Create a numeric cell (right-aligned).
368    pub fn number(content: impl Into<String>) -> Self {
369        Self::text(content).align(CellAlign::Right)
370    }
371}
372
373/// A table row.
374#[derive(Debug, Clone)]
375pub struct TableRow {
376    /// Cells in this row
377    pub cells: Vec<TableCell>,
378    /// Minimum row height
379    pub min_height: Option<f32>,
380    /// Row background color (applied to all cells without explicit background)
381    pub background: Option<(f32, f32, f32)>,
382    /// Whether this is a header row
383    pub is_header: bool,
384}
385
386impl TableRow {
387    /// Create a new row from cells.
388    pub fn new(cells: Vec<TableCell>) -> Self {
389        Self {
390            cells,
391            min_height: None,
392            background: None,
393            is_header: false,
394        }
395    }
396
397    /// Create a header row.
398    pub fn header(cells: Vec<TableCell>) -> Self {
399        Self {
400            cells,
401            min_height: None,
402            background: None,
403            is_header: true,
404        }
405    }
406
407    /// Set minimum height.
408    pub fn min_height(mut self, height: f32) -> Self {
409        self.min_height = Some(height);
410        self
411    }
412
413    /// Set row background.
414    pub fn background(mut self, r: f32, g: f32, b: f32) -> Self {
415        self.background = Some((r, g, b));
416        self
417    }
418
419    /// Mark as header row.
420    pub fn as_header(mut self) -> Self {
421        self.is_header = true;
422        self
423    }
424}
425
426/// Table style configuration.
427#[derive(Debug, Clone)]
428pub struct TableStyle {
429    /// Default cell padding
430    pub cell_padding: CellPadding,
431    /// Default cell borders
432    pub cell_borders: Borders,
433    /// Table outer border
434    pub outer_border: Option<TableBorderStyle>,
435    /// Default font name
436    pub font_name: String,
437    /// Default font size
438    pub font_size: f32,
439    /// Header row background color
440    pub header_background: Option<(f32, f32, f32)>,
441    /// Alternating row colors (even rows)
442    pub stripe_color: Option<(f32, f32, f32)>,
443    /// Space between rows
444    pub row_spacing: f32,
445}
446
447impl Default for TableStyle {
448    fn default() -> Self {
449        Self {
450            cell_padding: CellPadding::default(),
451            cell_borders: Borders::all(TableBorderStyle::thin()),
452            outer_border: None,
453            font_name: "Helvetica".to_string(),
454            font_size: 10.0,
455            header_background: Some((0.9, 0.9, 0.9)), // Light gray
456            stripe_color: None,
457            row_spacing: 0.0,
458        }
459    }
460}
461
462impl TableStyle {
463    /// Create a new default style.
464    pub fn new() -> Self {
465        Self::default()
466    }
467
468    /// Set cell padding.
469    pub fn cell_padding(mut self, padding: CellPadding) -> Self {
470        self.cell_padding = padding;
471        self
472    }
473
474    /// Set cell borders.
475    pub fn cell_borders(mut self, borders: Borders) -> Self {
476        self.cell_borders = borders;
477        self
478    }
479
480    /// Set outer border.
481    pub fn outer_border(mut self, border: TableBorderStyle) -> Self {
482        self.outer_border = Some(border);
483        self
484    }
485
486    /// Set default font.
487    pub fn font(mut self, name: impl Into<String>, size: f32) -> Self {
488        self.font_name = name.into();
489        self.font_size = size;
490        self
491    }
492
493    /// Set header background color.
494    pub fn header_background(mut self, r: f32, g: f32, b: f32) -> Self {
495        self.header_background = Some((r, g, b));
496        self
497    }
498
499    /// Enable striped rows.
500    pub fn striped(mut self, r: f32, g: f32, b: f32) -> Self {
501        self.stripe_color = Some((r, g, b));
502        self
503    }
504
505    /// Create a minimal style (no borders).
506    pub fn minimal() -> Self {
507        Self {
508            cell_borders: Borders::none(),
509            outer_border: None,
510            header_background: None,
511            ..Default::default()
512        }
513    }
514
515    /// Create a bordered style.
516    pub fn bordered() -> Self {
517        Self {
518            cell_borders: Borders::all(TableBorderStyle::medium()),
519            outer_border: Some(TableBorderStyle::thick()),
520            ..Default::default()
521        }
522    }
523}
524
525/// A complete table.
526#[derive(Debug, Clone)]
527pub struct Table {
528    /// Table rows
529    pub rows: Vec<TableRow>,
530    /// Column widths
531    pub column_widths: Vec<ColumnWidth>,
532    /// Table style
533    pub style: TableStyle,
534    /// Total table width (None = auto)
535    pub width: Option<f32>,
536    /// Column alignments (default for cells in column)
537    pub column_aligns: Vec<CellAlign>,
538}
539
540impl Table {
541    /// Create a new table from rows of cells.
542    pub fn new(rows: Vec<Vec<TableCell>>) -> Self {
543        let rows: Vec<TableRow> = rows.into_iter().map(TableRow::new).collect();
544        Self::from_rows(rows)
545    }
546
547    /// Create a table from TableRow objects.
548    pub fn from_rows(rows: Vec<TableRow>) -> Self {
549        let num_cols = rows
550            .iter()
551            .map(|r| r.cells.iter().map(|c| c.colspan).sum::<usize>())
552            .max()
553            .unwrap_or(0);
554
555        Self {
556            rows,
557            column_widths: vec![ColumnWidth::Auto; num_cols],
558            style: TableStyle::default(),
559            width: None,
560            column_aligns: vec![CellAlign::Left; num_cols],
561        }
562    }
563
564    /// Create an empty table.
565    pub fn empty() -> Self {
566        Self {
567            rows: Vec::new(),
568            column_widths: Vec::new(),
569            style: TableStyle::default(),
570            width: None,
571            column_aligns: Vec::new(),
572        }
573    }
574
575    /// Add a row to the table.
576    pub fn add_row(&mut self, row: TableRow) {
577        self.rows.push(row);
578    }
579
580    /// Set the first row as header.
581    pub fn with_header_row(mut self) -> Self {
582        if let Some(row) = self.rows.first_mut() {
583            row.is_header = true;
584        }
585        self
586    }
587
588    /// Set table style.
589    pub fn with_style(mut self, style: TableStyle) -> Self {
590        self.style = style;
591        self
592    }
593
594    /// Set total table width.
595    pub fn with_width(mut self, width: f32) -> Self {
596        self.width = Some(width);
597        self
598    }
599
600    /// Set column widths.
601    pub fn with_column_widths(mut self, widths: Vec<ColumnWidth>) -> Self {
602        self.column_widths = widths;
603        self
604    }
605
606    /// Set column alignments.
607    pub fn with_column_aligns(mut self, aligns: Vec<CellAlign>) -> Self {
608        self.column_aligns = aligns;
609        self
610    }
611
612    /// Get the number of columns.
613    pub fn num_columns(&self) -> usize {
614        self.rows
615            .iter()
616            .map(|r| r.cells.iter().map(|c| c.colspan).sum::<usize>())
617            .max()
618            .unwrap_or(0)
619    }
620
621    /// Get the number of rows.
622    pub fn num_rows(&self) -> usize {
623        self.rows.len()
624    }
625
626    /// Check if the table is empty.
627    pub fn is_empty(&self) -> bool {
628        self.rows.is_empty()
629    }
630}
631
632/// Calculated layout for a table.
633#[derive(Debug, Clone)]
634pub struct TableLayout {
635    /// Calculated column widths in points
636    pub column_widths: Vec<f32>,
637    /// Calculated row heights in points
638    pub row_heights: Vec<f32>,
639    /// Total table width
640    pub total_width: f32,
641    /// Total table height
642    pub total_height: f32,
643    /// Cell positions (row, col) -> (x, y, width, height)
644    pub cell_positions: Vec<Vec<CellPosition>>,
645}
646
647/// Position and size of a cell.
648#[derive(Debug, Clone, Copy)]
649pub struct CellPosition {
650    /// X position (left edge)
651    pub x: f32,
652    /// Y position (top edge, relative to table top)
653    pub y: f32,
654    /// Cell width
655    pub width: f32,
656    /// Cell height
657    pub height: f32,
658}
659
660impl Table {
661    /// Calculate the layout for this table.
662    pub fn calculate_layout(
663        &self,
664        available_width: f32,
665        font_metrics: &dyn FontMetrics,
666    ) -> TableLayout {
667        let num_cols = self.num_columns();
668        if num_cols == 0 || self.rows.is_empty() {
669            return TableLayout {
670                column_widths: vec![],
671                row_heights: vec![],
672                total_width: 0.0,
673                total_height: 0.0,
674                cell_positions: vec![],
675            };
676        }
677
678        let table_width = self.width.unwrap_or(available_width);
679
680        // Calculate column widths
681        let column_widths = self.calculate_column_widths(table_width, num_cols, font_metrics);
682
683        // Calculate row heights
684        let row_heights = self.calculate_row_heights(&column_widths, font_metrics);
685
686        // Calculate cell positions
687        let cell_positions = self.calculate_cell_positions(&column_widths, &row_heights);
688
689        let total_width: f32 = column_widths.iter().sum();
690        let total_height: f32 = row_heights.iter().sum();
691
692        TableLayout {
693            column_widths,
694            row_heights,
695            total_width,
696            total_height,
697            cell_positions,
698        }
699    }
700
701    fn calculate_column_widths(
702        &self,
703        table_width: f32,
704        num_cols: usize,
705        font_metrics: &dyn FontMetrics,
706    ) -> Vec<f32> {
707        let padding = &self.style.cell_padding;
708        let mut widths = vec![0.0f32; num_cols];
709        let mut _fixed_width = 0.0f32;
710        let mut weight_total = 0.0f32;
711        let mut _percent_total = 0.0f32;
712
713        // First pass: calculate minimum widths and collect constraints
714        for (col, spec) in self.column_widths.iter().take(num_cols).enumerate() {
715            match spec {
716                ColumnWidth::Fixed(w) => {
717                    widths[col] = *w;
718                    _fixed_width += *w;
719                },
720                ColumnWidth::Percent(p) => {
721                    let w = table_width * (*p / 100.0);
722                    widths[col] = w;
723                    _percent_total += *p;
724                },
725                ColumnWidth::Weight(w) => {
726                    weight_total += *w;
727                },
728                ColumnWidth::Auto => {
729                    // Calculate based on content
730                    let mut max_width = 0.0f32;
731                    for row in &self.rows {
732                        let mut col_idx = 0;
733                        for cell in &row.cells {
734                            if col_idx == col && cell.colspan == 1 {
735                                let font_size = cell.font_size.unwrap_or(self.style.font_size);
736                                let text_width = font_metrics.text_width(&cell.content, font_size);
737                                let cell_padding = cell.padding.as_ref().unwrap_or(padding);
738                                max_width = max_width.max(text_width + cell_padding.horizontal());
739                            }
740                            col_idx += cell.colspan;
741                        }
742                    }
743                    widths[col] = max_width.max(20.0); // Minimum 20pt
744                },
745            }
746        }
747
748        // Handle remaining auto columns with default width
749        for col in self.column_widths.len()..num_cols {
750            let mut max_width = 0.0f32;
751            for row in &self.rows {
752                let mut col_idx = 0;
753                for cell in &row.cells {
754                    if col_idx == col && cell.colspan == 1 {
755                        let font_size = cell.font_size.unwrap_or(self.style.font_size);
756                        let text_width = font_metrics.text_width(&cell.content, font_size);
757                        let cell_padding = cell.padding.as_ref().unwrap_or(padding);
758                        max_width = max_width.max(text_width + cell_padding.horizontal());
759                    }
760                    col_idx += cell.colspan;
761                }
762            }
763            widths[col] = max_width.max(20.0);
764        }
765
766        // Distribute remaining space to weighted columns
767        let used_width: f32 = widths.iter().sum();
768        let remaining = (table_width - used_width).max(0.0);
769
770        if weight_total > 0.0 && remaining > 0.0 {
771            for (col, spec) in self.column_widths.iter().take(num_cols).enumerate() {
772                if let ColumnWidth::Weight(w) = spec {
773                    widths[col] = remaining * (*w / weight_total);
774                }
775            }
776        }
777
778        // Scale if total exceeds table width
779        let total: f32 = widths.iter().sum();
780        if total > table_width && total > 0.0 {
781            let scale = table_width / total;
782            for w in &mut widths {
783                *w *= scale;
784            }
785        }
786
787        widths
788    }
789
790    fn calculate_row_heights(
791        &self,
792        column_widths: &[f32],
793        font_metrics: &dyn FontMetrics,
794    ) -> Vec<f32> {
795        let padding = &self.style.cell_padding;
796        let mut heights = Vec::with_capacity(self.rows.len());
797
798        for row in &self.rows {
799            let mut max_height = 0.0f32;
800            let mut col_idx = 0;
801
802            for cell in &row.cells {
803                if cell.rowspan == 1 {
804                    let cell_width = if cell.colspan == 1 {
805                        column_widths.get(col_idx).copied().unwrap_or(100.0)
806                    } else {
807                        column_widths[col_idx..col_idx + cell.colspan].iter().sum()
808                    };
809
810                    let cell_padding = cell.padding.as_ref().unwrap_or(padding);
811                    let content_width = cell_width - cell_padding.horizontal();
812
813                    let font_size = cell.font_size.unwrap_or(self.style.font_size);
814                    let line_height = font_size * 1.2;
815
816                    // Calculate wrapped text height
817                    let lines = wrap_text(&cell.content, content_width, font_size, font_metrics);
818                    let text_height = lines.len() as f32 * line_height;
819
820                    let cell_height = text_height + cell_padding.vertical();
821                    max_height = max_height.max(cell_height);
822                }
823                col_idx += cell.colspan;
824            }
825
826            // Apply minimum height if specified
827            if let Some(min_h) = row.min_height {
828                max_height = max_height.max(min_h);
829            }
830
831            heights.push(max_height.max(self.style.font_size * 1.5));
832        }
833
834        heights
835    }
836
837    fn calculate_cell_positions(
838        &self,
839        column_widths: &[f32],
840        row_heights: &[f32],
841    ) -> Vec<Vec<CellPosition>> {
842        let mut positions = Vec::with_capacity(self.rows.len());
843        let mut y = 0.0;
844
845        for (row_idx, row) in self.rows.iter().enumerate() {
846            let mut row_positions = Vec::with_capacity(row.cells.len());
847            let mut x = 0.0;
848            let mut col_idx = 0;
849
850            for cell in &row.cells {
851                let width: f32 = column_widths[col_idx..col_idx + cell.colspan].iter().sum();
852                let height: f32 = row_heights[row_idx..row_idx + cell.rowspan].iter().sum();
853
854                row_positions.push(CellPosition {
855                    x,
856                    y,
857                    width,
858                    height,
859                });
860
861                x += width;
862                col_idx += cell.colspan;
863            }
864
865            positions.push(row_positions);
866            y += row_heights[row_idx];
867        }
868
869        positions
870    }
871
872    /// Render the table to a content stream.
873    pub fn render(
874        &self,
875        builder: &mut ContentStreamBuilder,
876        x: f32,
877        y: f32,
878        layout: &TableLayout,
879    ) -> Result<()> {
880        // Y is top of table, PDF coordinates are bottom-up
881        let table_top = y;
882
883        // Draw backgrounds and borders
884        for (row_idx, row) in self.rows.iter().enumerate() {
885            for (cell_idx, cell) in row.cells.iter().enumerate() {
886                let pos = &layout.cell_positions[row_idx][cell_idx];
887                let cell_x = x + pos.x;
888                let cell_y = table_top - pos.y - pos.height;
889
890                // Determine background color
891                let bg = cell.background.or({
892                    if row.is_header {
893                        self.style.header_background
894                    } else if let Some(stripe) = self.style.stripe_color {
895                        if row_idx % 2 == 1 {
896                            Some(stripe)
897                        } else {
898                            row.background
899                        }
900                    } else {
901                        row.background
902                    }
903                });
904
905                // Draw background
906                if let Some((r, g, b)) = bg {
907                    builder.set_fill_color(r, g, b);
908                    builder.rect(cell_x, cell_y, pos.width, pos.height);
909                    builder.fill();
910                }
911
912                // Draw borders
913                let borders = cell.borders.as_ref().unwrap_or(&self.style.cell_borders);
914                self.draw_cell_borders(builder, cell_x, cell_y, pos.width, pos.height, borders);
915            }
916        }
917
918        // Draw text
919        for (row_idx, row) in self.rows.iter().enumerate() {
920            for (cell_idx, cell) in row.cells.iter().enumerate() {
921                if cell.content.is_empty() {
922                    continue;
923                }
924
925                let pos = &layout.cell_positions[row_idx][cell_idx];
926                let padding = cell.padding.as_ref().unwrap_or(&self.style.cell_padding);
927
928                let cell_x = x + pos.x + padding.left;
929                let cell_y = table_top - pos.y - padding.top;
930                let content_width = pos.width - padding.horizontal();
931
932                // Get alignment (cell -> column -> default)
933                let align = if cell.align != CellAlign::Left {
934                    cell.align
935                } else {
936                    self.column_aligns
937                        .get(cell_idx)
938                        .copied()
939                        .unwrap_or(CellAlign::Left)
940                };
941
942                let font_name = cell.font_name.as_deref().unwrap_or(&self.style.font_name);
943                let font_size = cell.font_size.unwrap_or(self.style.font_size);
944
945                // Adjust font name for bold/italic
946                let actual_font = if cell.bold && cell.italic {
947                    format!("{}-BoldOblique", font_name)
948                } else if cell.bold || row.is_header {
949                    format!("{}-Bold", font_name)
950                } else if cell.italic {
951                    format!("{}-Oblique", font_name)
952                } else {
953                    font_name.to_string()
954                };
955
956                builder.begin_text().set_font(&actual_font, font_size);
957
958                // Calculate text position based on alignment
959                let text_x = match align {
960                    CellAlign::Left => cell_x,
961                    CellAlign::Center => cell_x + content_width / 2.0,
962                    CellAlign::Right => cell_x + content_width,
963                };
964
965                let _line_height = font_size * 1.2;
966                let text_y = cell_y - font_size;
967
968                // Simple single-line rendering for now
969                builder.text(&cell.content, text_x, text_y);
970                builder.end_text();
971            }
972        }
973
974        // Draw outer border
975        if let Some(outer) = &self.style.outer_border {
976            if outer.width > 0.0 {
977                builder.set_stroke_color(outer.color.0, outer.color.1, outer.color.2);
978                builder.set_line_width(outer.width);
979                builder.rect(
980                    x,
981                    table_top - layout.total_height,
982                    layout.total_width,
983                    layout.total_height,
984                );
985                builder.stroke();
986            }
987        }
988
989        Ok(())
990    }
991
992    fn draw_cell_borders(
993        &self,
994        builder: &mut ContentStreamBuilder,
995        x: f32,
996        y: f32,
997        width: f32,
998        height: f32,
999        borders: &Borders,
1000    ) {
1001        // Top border
1002        if let Some(border) = &borders.top {
1003            if border.width > 0.0 {
1004                builder.set_stroke_color(border.color.0, border.color.1, border.color.2);
1005                builder.set_line_width(border.width);
1006                builder.move_to(x, y + height);
1007                builder.line_to(x + width, y + height);
1008                builder.stroke();
1009            }
1010        }
1011
1012        // Bottom border
1013        if let Some(border) = &borders.bottom {
1014            if border.width > 0.0 {
1015                builder.set_stroke_color(border.color.0, border.color.1, border.color.2);
1016                builder.set_line_width(border.width);
1017                builder.move_to(x, y);
1018                builder.line_to(x + width, y);
1019                builder.stroke();
1020            }
1021        }
1022
1023        // Left border
1024        if let Some(border) = &borders.left {
1025            if border.width > 0.0 {
1026                builder.set_stroke_color(border.color.0, border.color.1, border.color.2);
1027                builder.set_line_width(border.width);
1028                builder.move_to(x, y);
1029                builder.line_to(x, y + height);
1030                builder.stroke();
1031            }
1032        }
1033
1034        // Right border
1035        if let Some(border) = &borders.right {
1036            if border.width > 0.0 {
1037                builder.set_stroke_color(border.color.0, border.color.1, border.color.2);
1038                builder.set_line_width(border.width);
1039                builder.move_to(x + width, y);
1040                builder.line_to(x + width, y + height);
1041                builder.stroke();
1042            }
1043        }
1044    }
1045}
1046
1047/// Trait for font metrics needed for layout.
1048pub trait FontMetrics {
1049    /// Calculate the width of text in points.
1050    fn text_width(&self, text: &str, font_size: f32) -> f32;
1051}
1052
1053/// Simple font metrics using average character width.
1054#[derive(Debug, Clone, Copy)]
1055pub struct SimpleFontMetrics {
1056    /// Average character width as proportion of font size
1057    pub char_width_ratio: f32,
1058}
1059
1060impl Default for SimpleFontMetrics {
1061    fn default() -> Self {
1062        Self {
1063            char_width_ratio: 0.5, // Typical for proportional fonts
1064        }
1065    }
1066}
1067
1068impl SimpleFontMetrics {
1069    /// Create metrics for monospace fonts.
1070    pub fn monospace() -> Self {
1071        Self {
1072            char_width_ratio: 0.6,
1073        }
1074    }
1075}
1076
1077impl FontMetrics for SimpleFontMetrics {
1078    fn text_width(&self, text: &str, font_size: f32) -> f32 {
1079        text.chars().count() as f32 * font_size * self.char_width_ratio
1080    }
1081}
1082
1083/// Wrap text to fit within a given width.
1084fn wrap_text(text: &str, max_width: f32, font_size: f32, metrics: &dyn FontMetrics) -> Vec<String> {
1085    if text.is_empty() {
1086        return vec![String::new()];
1087    }
1088
1089    let mut lines = Vec::new();
1090    let mut current_line = String::new();
1091
1092    for word in text.split_whitespace() {
1093        let test_line = if current_line.is_empty() {
1094            word.to_string()
1095        } else {
1096            format!("{} {}", current_line, word)
1097        };
1098
1099        let width = metrics.text_width(&test_line, font_size);
1100
1101        if width <= max_width || current_line.is_empty() {
1102            current_line = test_line;
1103        } else {
1104            lines.push(current_line);
1105            current_line = word.to_string();
1106        }
1107    }
1108
1109    if !current_line.is_empty() {
1110        lines.push(current_line);
1111    }
1112
1113    if lines.is_empty() {
1114        lines.push(String::new());
1115    }
1116
1117    lines
1118}
1119
1120#[cfg(test)]
1121mod tests {
1122    use super::*;
1123
1124    #[test]
1125    fn test_table_cell_creation() {
1126        let cell = TableCell::text("Hello");
1127        assert_eq!(cell.content, "Hello");
1128        assert_eq!(cell.colspan, 1);
1129        assert_eq!(cell.rowspan, 1);
1130    }
1131
1132    #[test]
1133    fn test_table_cell_header() {
1134        let cell = TableCell::header("Title");
1135        assert!(cell.bold);
1136        assert_eq!(cell.align, CellAlign::Center);
1137    }
1138
1139    #[test]
1140    fn test_table_cell_spanning() {
1141        let cell = TableCell::text("Wide").colspan(2).rowspan(3);
1142        assert_eq!(cell.colspan, 2);
1143        assert_eq!(cell.rowspan, 3);
1144    }
1145
1146    #[test]
1147    fn test_table_creation() {
1148        let table = Table::new(vec![
1149            vec![TableCell::text("A"), TableCell::text("B")],
1150            vec![TableCell::text("C"), TableCell::text("D")],
1151        ]);
1152
1153        assert_eq!(table.num_columns(), 2);
1154        assert_eq!(table.num_rows(), 2);
1155    }
1156
1157    #[test]
1158    fn test_table_with_header() {
1159        let table = Table::new(vec![
1160            vec![TableCell::text("Name"), TableCell::text("Age")],
1161            vec![TableCell::text("Alice"), TableCell::text("30")],
1162        ])
1163        .with_header_row();
1164
1165        assert!(table.rows[0].is_header);
1166    }
1167
1168    #[test]
1169    fn test_cell_padding() {
1170        let padding = CellPadding::uniform(10.0);
1171        assert_eq!(padding.horizontal(), 20.0);
1172        assert_eq!(padding.vertical(), 20.0);
1173
1174        let asym = CellPadding::symmetric(5.0, 10.0);
1175        assert_eq!(asym.horizontal(), 10.0);
1176        assert_eq!(asym.vertical(), 20.0);
1177    }
1178
1179    #[test]
1180    fn test_borders() {
1181        let borders = Borders::all(TableBorderStyle::medium());
1182        assert!(borders.top.is_some());
1183        assert!(borders.right.is_some());
1184        assert!(borders.bottom.is_some());
1185        assert!(borders.left.is_some());
1186
1187        let horiz = Borders::horizontal(TableBorderStyle::thin());
1188        assert!(horiz.top.is_some());
1189        assert!(horiz.bottom.is_some());
1190        assert!(horiz.left.is_none());
1191        assert!(horiz.right.is_none());
1192    }
1193
1194    #[test]
1195    fn test_column_width_types() {
1196        let _auto = ColumnWidth::Auto;
1197        let _fixed = ColumnWidth::Fixed(100.0);
1198        let _percent = ColumnWidth::Percent(25.0);
1199        let _weight = ColumnWidth::Weight(1.0);
1200    }
1201
1202    #[test]
1203    fn test_table_style_presets() {
1204        let minimal = TableStyle::minimal();
1205        assert!(minimal.cell_borders.top.is_none());
1206        assert!(minimal.outer_border.is_none());
1207
1208        let bordered = TableStyle::bordered();
1209        assert!(bordered.outer_border.is_some());
1210    }
1211
1212    #[test]
1213    fn test_table_layout_calculation() {
1214        let table = Table::new(vec![
1215            vec![TableCell::text("Name"), TableCell::text("Value")],
1216            vec![TableCell::text("Test"), TableCell::text("123")],
1217        ]);
1218
1219        let metrics = SimpleFontMetrics::default();
1220        let layout = table.calculate_layout(400.0, &metrics);
1221
1222        assert_eq!(layout.column_widths.len(), 2);
1223        assert_eq!(layout.row_heights.len(), 2);
1224        assert!(layout.total_width > 0.0);
1225        assert!(layout.total_height > 0.0);
1226    }
1227
1228    #[test]
1229    fn test_text_wrapping() {
1230        let metrics = SimpleFontMetrics::default();
1231        let lines = wrap_text("Hello World", 100.0, 12.0, &metrics);
1232        assert!(!lines.is_empty());
1233    }
1234
1235    #[test]
1236    fn test_empty_table() {
1237        let table = Table::empty();
1238        assert!(table.is_empty());
1239        assert_eq!(table.num_columns(), 0);
1240        assert_eq!(table.num_rows(), 0);
1241    }
1242
1243    #[test]
1244    fn test_cell_alignments() {
1245        let left = TableCell::text("Left").align(CellAlign::Left);
1246        let center = TableCell::text("Center").align(CellAlign::Center);
1247        let right = TableCell::number("123");
1248
1249        assert_eq!(left.align, CellAlign::Left);
1250        assert_eq!(center.align, CellAlign::Center);
1251        assert_eq!(right.align, CellAlign::Right);
1252    }
1253
1254    #[test]
1255    fn test_row_creation() {
1256        let row = TableRow::new(vec![TableCell::text("A"), TableCell::text("B")]);
1257        assert_eq!(row.cells.len(), 2);
1258        assert!(!row.is_header);
1259
1260        let header = TableRow::header(vec![TableCell::text("Name"), TableCell::text("Value")]);
1261        assert!(header.is_header);
1262    }
1263
1264    #[test]
1265    fn test_striped_table() {
1266        let style = TableStyle::new().striped(0.95, 0.95, 0.95);
1267        assert!(style.stripe_color.is_some());
1268    }
1269}