oxidize_pdf/
page_tables.rs

1//! Page extension for table rendering
2//!
3//! This module provides traits and implementations to easily add tables to PDF pages.
4
5use crate::error::PdfError;
6use crate::graphics::Color;
7use crate::page::Page;
8use crate::text::{Font, HeaderStyle, Table, TableOptions};
9
10/// Extension trait for adding tables to pages
11pub trait PageTables {
12    /// Add a simple table to the page
13    fn add_simple_table(&mut self, table: &Table, x: f64, y: f64) -> Result<&mut Self, PdfError>;
14
15    /// Create and add a quick table with equal columns
16    fn add_quick_table(
17        &mut self,
18        data: Vec<Vec<String>>,
19        x: f64,
20        y: f64,
21        width: f64,
22        options: Option<TableOptions>,
23    ) -> Result<&mut Self, PdfError>;
24
25    /// Create and add an advanced table with custom styling
26    fn add_styled_table(
27        &mut self,
28        headers: Vec<String>,
29        data: Vec<Vec<String>>,
30        x: f64,
31        y: f64,
32        width: f64,
33        style: TableStyle,
34    ) -> Result<&mut Self, PdfError>;
35}
36
37/// Predefined table styles
38#[derive(Debug, Clone)]
39pub struct TableStyle {
40    /// Header background color
41    pub header_background: Option<Color>,
42    /// Header text color
43    pub header_text_color: Option<Color>,
44    /// Default font size
45    pub font_size: f64,
46}
47
48impl TableStyle {
49    /// Create a minimal table style (no borders)
50    pub fn minimal() -> Self {
51        Self {
52            header_background: None,
53            header_text_color: None,
54            font_size: 10.0,
55        }
56    }
57
58    /// Create a simple table style with borders
59    pub fn simple() -> Self {
60        Self {
61            header_background: None,
62            header_text_color: None,
63            font_size: 10.0,
64        }
65    }
66
67    /// Create a professional table style
68    pub fn professional() -> Self {
69        Self {
70            header_background: Some(Color::gray(0.1)),
71            header_text_color: Some(Color::white()),
72            font_size: 10.0,
73        }
74    }
75
76    /// Create a colorful table style
77    pub fn colorful() -> Self {
78        Self {
79            header_background: Some(Color::rgb(0.2, 0.4, 0.8)),
80            header_text_color: Some(Color::white()),
81            font_size: 10.0,
82        }
83    }
84}
85
86impl PageTables for Page {
87    fn add_simple_table(&mut self, table: &Table, x: f64, y: f64) -> Result<&mut Self, PdfError> {
88        let mut table_clone = table.clone();
89        table_clone.set_position(x, y);
90        table_clone.render(self.graphics())?;
91        Ok(self)
92    }
93
94    fn add_quick_table(
95        &mut self,
96        data: Vec<Vec<String>>,
97        x: f64,
98        y: f64,
99        width: f64,
100        options: Option<TableOptions>,
101    ) -> Result<&mut Self, PdfError> {
102        if data.is_empty() {
103            return Ok(self);
104        }
105
106        let num_columns = data[0].len();
107        let mut table = Table::with_equal_columns(num_columns, width);
108
109        if let Some(opts) = options {
110            table.set_options(opts);
111        }
112
113        for row in data {
114            table.add_row(row)?;
115        }
116
117        self.add_simple_table(&table, x, y)
118    }
119
120    fn add_styled_table(
121        &mut self,
122        headers: Vec<String>,
123        data: Vec<Vec<String>>,
124        x: f64,
125        y: f64,
126        width: f64,
127        style: TableStyle,
128    ) -> Result<&mut Self, PdfError> {
129        let num_columns = headers.len();
130        if num_columns == 0 {
131            return Ok(self);
132        }
133
134        // Create a simple table with the given style
135        let mut table = Table::with_equal_columns(num_columns, width);
136
137        // Create table options based on style
138        let header_style = if style.header_background.is_some() || style.header_text_color.is_some()
139        {
140            Some(HeaderStyle {
141                background_color: style.header_background.unwrap_or(Color::white()),
142                text_color: style.header_text_color.unwrap_or(Color::black()),
143                font: Font::Helvetica,
144                bold: true,
145            })
146        } else {
147            None
148        };
149
150        let options = TableOptions {
151            font_size: style.font_size,
152            header_style,
153            ..Default::default()
154        };
155
156        table.set_options(options);
157
158        // Add header row
159        table.add_row(headers)?;
160
161        // Add data rows
162        for row_data in data {
163            table.add_row(row_data)?;
164        }
165
166        self.add_simple_table(&table, x, y)
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173    use crate::page::Page;
174
175    #[test]
176    fn test_page_tables_trait() {
177        let mut page = Page::a4();
178
179        // Test quick table
180        let data = vec![
181            vec!["Name".to_string(), "Age".to_string()],
182            vec!["John".to_string(), "30".to_string()],
183        ];
184
185        let result = page.add_quick_table(data, 50.0, 700.0, 400.0, None);
186        assert!(result.is_ok());
187    }
188
189    #[test]
190    fn test_table_styles() {
191        let minimal = TableStyle::minimal();
192        assert_eq!(minimal.font_size, 10.0);
193
194        let simple = TableStyle::simple();
195        assert_eq!(simple.font_size, 10.0);
196
197        let professional = TableStyle::professional();
198        assert!(professional.header_background.is_some());
199
200        let colorful = TableStyle::colorful();
201        assert!(colorful.header_background.is_some());
202    }
203
204    #[test]
205    fn test_styled_table() {
206        let mut page = Page::a4();
207
208        let headers = vec!["Column 1".to_string(), "Column 2".to_string()];
209        let data = vec![
210            vec!["Data 1".to_string(), "Data 2".to_string()],
211            vec!["Data 3".to_string(), "Data 4".to_string()],
212        ];
213
214        let result = page.add_styled_table(
215            headers,
216            data,
217            50.0,
218            700.0,
219            500.0,
220            TableStyle::professional(),
221        );
222
223        assert!(result.is_ok());
224    }
225
226    #[test]
227    fn test_empty_table() {
228        let mut page = Page::a4();
229
230        let data: Vec<Vec<String>> = vec![];
231        let result = page.add_quick_table(data, 50.0, 700.0, 400.0, None);
232        assert!(result.is_ok());
233    }
234}