1use crate::error::PdfError;
6use crate::graphics::Color;
7use crate::page::Page;
8use crate::text::{Font, HeaderStyle, Table, TableOptions};
9
10pub trait PageTables {
12 fn add_simple_table(&mut self, table: &Table, x: f64, y: f64) -> Result<&mut Self, PdfError>;
14
15 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 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#[derive(Debug, Clone)]
39pub struct TableStyle {
40 pub header_background: Option<Color>,
42 pub header_text_color: Option<Color>,
44 pub font_size: f64,
46}
47
48impl TableStyle {
49 pub fn minimal() -> Self {
51 Self {
52 header_background: None,
53 header_text_color: None,
54 font_size: 10.0,
55 }
56 }
57
58 pub fn simple() -> Self {
60 Self {
61 header_background: None,
62 header_text_color: None,
63 font_size: 10.0,
64 }
65 }
66
67 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 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 let mut table = Table::with_equal_columns(num_columns, width);
136
137 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 table.add_row(headers)?;
160
161 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]
178 fn test_table_style_minimal() {
179 let style = TableStyle::minimal();
180 assert_eq!(style.header_background, None);
181 assert_eq!(style.header_text_color, None);
182 assert_eq!(style.font_size, 10.0);
183 }
184
185 #[test]
186 fn test_table_style_simple() {
187 let style = TableStyle::simple();
188 assert_eq!(style.header_background, None);
189 assert_eq!(style.header_text_color, None);
190 assert_eq!(style.font_size, 10.0);
191 }
192
193 #[test]
194 fn test_table_style_professional() {
195 let style = TableStyle::professional();
196 assert!(style.header_background.is_some());
197 assert!(style.header_text_color.is_some());
198 assert_eq!(style.font_size, 10.0);
199
200 if let Some(bg) = style.header_background {
202 assert!(bg.r() < 0.2, "Professional header should be dark");
203 }
204
205 if let Some(text) = style.header_text_color {
207 assert_eq!(text, Color::white());
208 }
209 }
210
211 #[test]
212 fn test_table_style_colorful() {
213 let style = TableStyle::colorful();
214 assert!(style.header_background.is_some());
215 assert!(style.header_text_color.is_some());
216 assert_eq!(style.font_size, 10.0);
217
218 if let Some(bg) = style.header_background {
220 assert!(bg.b() > bg.r(), "Colorful header should be blue-ish");
221 assert!(bg.b() > bg.g(), "Colorful header should be blue-ish");
222 }
223
224 if let Some(text) = style.header_text_color {
226 assert_eq!(text, Color::white());
227 }
228 }
229
230 #[test]
231 fn test_table_style_clone() {
232 let original = TableStyle::professional();
233 let cloned = original.clone();
234
235 assert_eq!(cloned.header_background, original.header_background);
236 assert_eq!(cloned.header_text_color, original.header_text_color);
237 assert_eq!(cloned.font_size, original.font_size);
238 }
239
240 #[test]
241 fn test_table_style_debug() {
242 let style = TableStyle::minimal();
243 let debug_str = format!("{:?}", style);
244 assert!(debug_str.contains("TableStyle"));
245 }
246
247 #[test]
248 fn test_table_style_mutability() {
249 let mut style = TableStyle::minimal();
250
251 style.header_background = Some(Color::red());
252 style.header_text_color = Some(Color::blue());
253 style.font_size = 14.0;
254
255 assert_eq!(style.header_background, Some(Color::red()));
256 assert_eq!(style.header_text_color, Some(Color::blue()));
257 assert_eq!(style.font_size, 14.0);
258 }
259
260 #[test]
261 fn test_table_styles() {
262 let minimal = TableStyle::minimal();
263 assert_eq!(minimal.font_size, 10.0);
264
265 let simple = TableStyle::simple();
266 assert_eq!(simple.font_size, 10.0);
267
268 let professional = TableStyle::professional();
269 assert!(professional.header_background.is_some());
270
271 let colorful = TableStyle::colorful();
272 assert!(colorful.header_background.is_some());
273 }
274
275 #[test]
278 fn test_page_tables_trait() {
279 let mut page = Page::a4();
280
281 let data = vec![
283 vec!["Name".to_string(), "Age".to_string()],
284 vec!["John".to_string(), "30".to_string()],
285 ];
286
287 let result = page.add_quick_table(data, 50.0, 700.0, 400.0, None);
288 assert!(result.is_ok());
289 }
290
291 #[test]
292 fn test_quick_table_with_options() {
293 let mut page = Page::a4();
294
295 let data = vec![
296 vec!["A".to_string(), "B".to_string()],
297 vec!["C".to_string(), "D".to_string()],
298 ];
299
300 let options = TableOptions {
301 font_size: 12.0,
302 ..Default::default()
303 };
304
305 let result = page.add_quick_table(data, 50.0, 700.0, 400.0, Some(options));
306 assert!(result.is_ok());
307 }
308
309 #[test]
310 fn test_styled_table() {
311 let mut page = Page::a4();
312
313 let headers = vec!["Column 1".to_string(), "Column 2".to_string()];
314 let data = vec![
315 vec!["Data 1".to_string(), "Data 2".to_string()],
316 vec!["Data 3".to_string(), "Data 4".to_string()],
317 ];
318
319 let result = page.add_styled_table(
320 headers,
321 data,
322 50.0,
323 700.0,
324 500.0,
325 TableStyle::professional(),
326 );
327
328 assert!(result.is_ok());
329 }
330
331 #[test]
332 fn test_styled_table_minimal() {
333 let mut page = Page::a4();
334
335 let headers = vec!["H1".to_string(), "H2".to_string()];
336 let data = vec![vec!["V1".to_string(), "V2".to_string()]];
337
338 let result =
339 page.add_styled_table(headers, data, 50.0, 700.0, 400.0, TableStyle::minimal());
340 assert!(result.is_ok());
341 }
342
343 #[test]
344 fn test_styled_table_colorful() {
345 let mut page = Page::a4();
346
347 let headers = vec!["Header".to_string()];
348 let data = vec![vec!["Value".to_string()]];
349
350 let result =
351 page.add_styled_table(headers, data, 50.0, 700.0, 300.0, TableStyle::colorful());
352 assert!(result.is_ok());
353 }
354
355 #[test]
356 fn test_styled_table_empty_headers() {
357 let mut page = Page::a4();
358
359 let headers: Vec<String> = vec![];
360 let data = vec![vec!["Data".to_string()]];
361
362 let result = page.add_styled_table(headers, data, 50.0, 700.0, 400.0, TableStyle::simple());
364 assert!(result.is_ok());
365 }
366
367 #[test]
368 fn test_styled_table_empty_data() {
369 let mut page = Page::a4();
370
371 let headers = vec!["H1".to_string(), "H2".to_string()];
372 let data: Vec<Vec<String>> = vec![];
373
374 let result = page.add_styled_table(
376 headers,
377 data,
378 50.0,
379 700.0,
380 400.0,
381 TableStyle::professional(),
382 );
383 assert!(result.is_ok());
384 }
385
386 #[test]
387 fn test_empty_table() {
388 let mut page = Page::a4();
389
390 let data: Vec<Vec<String>> = vec![];
391 let result = page.add_quick_table(data, 50.0, 700.0, 400.0, None);
392 assert!(result.is_ok());
393 }
394
395 #[test]
396 fn test_single_cell_table() {
397 let mut page = Page::a4();
398
399 let data = vec![vec!["Single".to_string()]];
400 let result = page.add_quick_table(data, 50.0, 700.0, 200.0, None);
401 assert!(result.is_ok());
402 }
403
404 #[test]
405 fn test_single_row_table() {
406 let mut page = Page::a4();
407
408 let data = vec![vec![
409 "A".to_string(),
410 "B".to_string(),
411 "C".to_string(),
412 "D".to_string(),
413 ]];
414 let result = page.add_quick_table(data, 50.0, 700.0, 500.0, None);
415 assert!(result.is_ok());
416 }
417
418 #[test]
419 fn test_single_column_table() {
420 let mut page = Page::a4();
421
422 let data = vec![
423 vec!["Row 1".to_string()],
424 vec!["Row 2".to_string()],
425 vec!["Row 3".to_string()],
426 ];
427 let result = page.add_quick_table(data, 50.0, 700.0, 150.0, None);
428 assert!(result.is_ok());
429 }
430
431 #[test]
432 fn test_many_rows_table() {
433 let mut page = Page::a4();
434
435 let data: Vec<Vec<String>> = (0..50)
436 .map(|i| vec![format!("Row {}", i), format!("Value {}", i)])
437 .collect();
438
439 let result = page.add_quick_table(data, 50.0, 700.0, 400.0, None);
440 assert!(result.is_ok());
441 }
442
443 #[test]
444 fn test_many_columns_table() {
445 let mut page = Page::a4();
446
447 let headers: Vec<String> = (0..10).map(|i| format!("Col {}", i)).collect();
448 let data = vec![(0..10).map(|i| format!("V{}", i)).collect()];
449
450 let result = page.add_styled_table(headers, data, 50.0, 700.0, 550.0, TableStyle::simple());
451 assert!(result.is_ok());
452 }
453
454 #[test]
455 fn test_table_at_different_positions() {
456 let mut page = Page::a4();
457
458 let data = vec![vec!["Test".to_string()]];
459
460 let result = page.add_quick_table(data.clone(), 0.0, 800.0, 100.0, None);
462 assert!(result.is_ok());
463
464 let result = page.add_quick_table(data.clone(), 200.0, 400.0, 100.0, None);
466 assert!(result.is_ok());
467
468 let result = page.add_quick_table(data, 400.0, 100.0, 100.0, None);
470 assert!(result.is_ok());
471 }
472
473 #[test]
474 fn test_styled_table_with_only_header_background() {
475 let mut page = Page::a4();
476
477 let mut style = TableStyle::minimal();
478 style.header_background = Some(Color::green());
479 let headers = vec!["Test".to_string()];
482 let data = vec![vec!["Data".to_string()]];
483
484 let result = page.add_styled_table(headers, data, 50.0, 700.0, 200.0, style);
485 assert!(result.is_ok());
486 }
487
488 #[test]
489 fn test_styled_table_with_only_header_text_color() {
490 let mut page = Page::a4();
491
492 let mut style = TableStyle::minimal();
493 style.header_text_color = Some(Color::red());
494 let headers = vec!["Test".to_string()];
497 let data = vec![vec!["Data".to_string()]];
498
499 let result = page.add_styled_table(headers, data, 50.0, 700.0, 200.0, style);
500 assert!(result.is_ok());
501 }
502
503 #[test]
504 fn test_styled_table_custom_font_size() {
505 let mut page = Page::a4();
506
507 let mut style = TableStyle::professional();
508 style.font_size = 16.0;
509
510 let headers = vec!["Big".to_string(), "Text".to_string()];
511 let data = vec![vec!["Large".to_string(), "Font".to_string()]];
512
513 let result = page.add_styled_table(headers, data, 50.0, 700.0, 300.0, style);
514 assert!(result.is_ok());
515 }
516
517 #[test]
518 fn test_all_styles_integration() {
519 let mut page = Page::a4();
520
521 let headers = vec!["A".to_string(), "B".to_string()];
522 let data = vec![vec!["1".to_string(), "2".to_string()]];
523
524 let styles = vec![
525 TableStyle::minimal(),
526 TableStyle::simple(),
527 TableStyle::professional(),
528 TableStyle::colorful(),
529 ];
530
531 for (i, style) in styles.into_iter().enumerate() {
532 let y = 700.0 - (i as f64 * 100.0);
533 let result =
534 page.add_styled_table(headers.clone(), data.clone(), 50.0, y, 200.0, style);
535 assert!(result.is_ok(), "Failed for style index {}", i);
536 }
537 }
538}