1use crate::graphics::Color;
4use crate::text::Font;
5use crate::CoordinateSystem;
6
7#[derive(Debug, Clone, Copy, PartialEq, Default)]
9pub enum BorderStyle {
10 None,
12 #[default]
14 Solid,
15 Dashed,
17 Dotted,
19 Double,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Default)]
25pub enum CellAlignment {
26 #[default]
28 Left,
29 Center,
31 Right,
33 Justify,
35}
36
37#[derive(Debug, Clone, Copy)]
39pub struct Padding {
40 pub top: f64,
42 pub right: f64,
44 pub bottom: f64,
46 pub left: f64,
48}
49
50impl Padding {
51 pub fn new(top: f64, right: f64, bottom: f64, left: f64) -> Self {
53 Self {
54 top,
55 right,
56 bottom,
57 left,
58 }
59 }
60
61 pub fn uniform(padding: f64) -> Self {
63 Self {
64 top: padding,
65 right: padding,
66 bottom: padding,
67 left: padding,
68 }
69 }
70
71 pub fn symmetric(horizontal: f64, vertical: f64) -> Self {
73 Self {
74 top: vertical,
75 right: horizontal,
76 bottom: vertical,
77 left: horizontal,
78 }
79 }
80
81 pub fn individual(top: f64, right: f64, bottom: f64, left: f64) -> Self {
83 Self {
84 top,
85 right,
86 bottom,
87 left,
88 }
89 }
90
91 pub fn horizontal_total(&self) -> f64 {
93 self.left + self.right
94 }
95
96 pub fn vertical_total(&self) -> f64 {
98 self.top + self.bottom
99 }
100
101 pub fn pad_vertically(&self, coordinate_system: &CoordinateSystem, y: f64) -> f64 {
102 let mut padded = y;
103 match coordinate_system {
104 CoordinateSystem::PdfStandard | CoordinateSystem::Custom(_) => {
105 padded -= self.top;
106 padded += self.bottom;
107 }
108 CoordinateSystem::ScreenSpace => {
109 padded += self.top;
110 padded -= self.bottom;
111 }
112 }
113
114 padded
115 }
116
117 pub fn pad_horizontally(&self, x: f64) -> f64 {
118 let mut padded = x;
119 padded -= self.right;
120 padded += self.left;
121
122 padded
123 }
124}
125
126impl Default for Padding {
127 fn default() -> Self {
128 Self::uniform(4.0)
129 }
130}
131
132#[derive(Debug, Clone)]
134pub struct CellStyle {
135 pub background_color: Option<Color>,
137 pub text_color: Option<Color>,
139 pub font: Option<Font>,
141 pub font_size: Option<f64>,
143 pub padding: Padding,
145 pub alignment: CellAlignment,
147 pub border: BorderConfiguration,
149 pub border_style: BorderStyle,
151 pub text_wrap: bool,
153 pub min_height: Option<f64>,
155 pub max_height: Option<f64>,
157}
158
159#[derive(Debug, Clone)]
161pub struct BorderConfiguration {
162 pub top: BorderEdge,
164 pub right: BorderEdge,
166 pub bottom: BorderEdge,
168 pub left: BorderEdge,
170}
171
172#[derive(Debug, Clone)]
174pub struct BorderEdge {
175 pub style: BorderStyle,
177 pub width: f64,
179 pub color: Color,
181}
182
183impl BorderEdge {
184 pub fn new(style: BorderStyle, width: f64, color: Color) -> Self {
186 Self {
187 style,
188 width,
189 color,
190 }
191 }
192
193 pub fn solid(width: f64) -> Self {
195 Self::new(BorderStyle::Solid, width, Color::black())
196 }
197
198 pub fn dashed(width: f64, color: Color) -> Self {
200 Self::new(BorderStyle::Dashed, width, color)
201 }
202
203 pub fn dotted(width: f64, color: Color) -> Self {
205 Self::new(BorderStyle::Dotted, width, color)
206 }
207
208 pub fn none() -> Self {
210 Self::new(BorderStyle::None, 0.0, Color::black())
211 }
212}
213
214impl Default for BorderEdge {
215 fn default() -> Self {
216 Self::solid(1.0)
217 }
218}
219
220impl BorderConfiguration {
221 pub fn new() -> Self {
223 Self {
224 top: BorderEdge::default(),
225 right: BorderEdge::default(),
226 bottom: BorderEdge::default(),
227 left: BorderEdge::default(),
228 }
229 }
230
231 pub fn uniform(edge: BorderEdge) -> Self {
233 Self {
234 top: edge.clone(),
235 right: edge.clone(),
236 bottom: edge.clone(),
237 left: edge,
238 }
239 }
240
241 pub fn edges(top: bool, right: bool, bottom: bool, left: bool) -> Self {
243 let solid_edge = BorderEdge::solid(1.0);
244 let no_edge = BorderEdge::none();
245
246 Self {
247 top: if top {
248 solid_edge.clone()
249 } else {
250 no_edge.clone()
251 },
252 right: if right {
253 solid_edge.clone()
254 } else {
255 no_edge.clone()
256 },
257 bottom: if bottom {
258 solid_edge.clone()
259 } else {
260 no_edge.clone()
261 },
262 left: if left { solid_edge } else { no_edge },
263 }
264 }
265
266 pub fn none() -> Self {
268 let no_edge = BorderEdge::none();
269 Self {
270 top: no_edge.clone(),
271 right: no_edge.clone(),
272 bottom: no_edge.clone(),
273 left: no_edge,
274 }
275 }
276}
277
278impl Default for BorderConfiguration {
279 fn default() -> Self {
280 Self::new()
281 }
282}
283
284impl CellStyle {
285 pub fn new() -> Self {
287 Self {
288 background_color: None,
289 text_color: Some(Color::black()),
290 font: Some(Font::Helvetica),
291 font_size: Some(12.0),
292 padding: Padding::default(),
293 alignment: CellAlignment::Left,
294 border: BorderConfiguration::default(),
295 border_style: BorderStyle::Solid,
296 text_wrap: true,
297 min_height: None,
298 max_height: None,
299 }
300 }
301
302 pub fn background_color(mut self, color: Color) -> Self {
304 self.background_color = Some(color);
305 self
306 }
307
308 pub fn text_color(mut self, color: Color) -> Self {
310 self.text_color = Some(color);
311 self
312 }
313
314 pub fn font(mut self, font: Font) -> Self {
316 self.font = Some(font);
317 self
318 }
319
320 pub fn font_size(mut self, size: f64) -> Self {
322 self.font_size = Some(size);
323 self
324 }
325
326 pub fn padding(mut self, padding: Padding) -> Self {
328 self.padding = padding;
329 self
330 }
331
332 pub fn alignment(mut self, alignment: CellAlignment) -> Self {
334 self.alignment = alignment;
335 self
336 }
337
338 pub fn border_config(mut self, border: BorderConfiguration) -> Self {
340 self.border = border;
341 self
342 }
343
344 pub fn border(mut self, style: BorderStyle, width: f64, color: Color) -> Self {
346 self.border_style = style;
347 self.border = BorderConfiguration::uniform(BorderEdge::new(style, width, color));
348 self
349 }
350
351 pub fn text_wrap(mut self, wrap: bool) -> Self {
353 self.text_wrap = wrap;
354 self
355 }
356
357 pub fn min_height(mut self, height: f64) -> Self {
359 self.min_height = Some(height);
360 self
361 }
362
363 pub fn max_height(mut self, height: f64) -> Self {
365 self.max_height = Some(height);
366 self
367 }
368
369 pub fn header() -> Self {
371 Self::new()
372 .font(Font::HelveticaBold)
373 .font_size(14.0)
374 .alignment(CellAlignment::Center)
375 .background_color(Color::rgb(0.9, 0.9, 0.9))
376 .padding(Padding::uniform(8.0))
377 }
378
379 pub fn data() -> Self {
381 Self::new()
382 .font(Font::Helvetica)
383 .font_size(12.0)
384 .alignment(CellAlignment::Left)
385 .padding(Padding::uniform(6.0))
386 }
387
388 pub fn numeric() -> Self {
390 Self::new()
391 .font(Font::Courier)
392 .font_size(11.0)
393 .alignment(CellAlignment::Right)
394 .padding(Padding::uniform(6.0))
395 }
396
397 pub fn alternating() -> Self {
399 Self::data().background_color(Color::rgb(0.97, 0.97, 0.97))
400 }
401}
402
403impl Default for CellStyle {
404 fn default() -> Self {
405 Self::new()
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412
413 #[test]
414 fn test_pad_vertically() {
415 let cs = CoordinateSystem::PdfStandard;
416 let padding = Padding::new(6.0, 0.0, 2.0, 2.0);
417
418 assert_eq!(padding.pad_vertically(&cs, 100.0), 96.0);
419 assert_eq!(padding.pad_vertically(&cs, 50.0), 46.0);
420
421 let cs = CoordinateSystem::ScreenSpace;
422 let padding = Padding::new(6.0, 0.0, 2.0, 2.0);
423
424 assert_eq!(padding.pad_vertically(&cs, 100.0), 104.0);
425 assert_eq!(padding.pad_vertically(&cs, 50.0), 54.0);
426 }
427
428 #[test]
429 fn test_pad_horizontally() {
430 let padding = Padding::new(6.0, 12.0, 2.0, 2.0);
431
432 assert_eq!(padding.pad_horizontally(100.0), 90.0);
433 assert_eq!(padding.pad_horizontally(50.0), 40.0);
434 }
435
436 #[test]
437 fn test_border_style_default() {
438 let style = BorderStyle::default();
439 assert_eq!(style, BorderStyle::Solid);
440 }
441
442 #[test]
443 fn test_cell_alignment_default() {
444 let alignment = CellAlignment::default();
445 assert_eq!(alignment, CellAlignment::Left);
446 }
447
448 #[test]
449 fn test_padding_uniform() {
450 let padding = Padding::uniform(10.0);
451 assert_eq!(padding.top, 10.0);
452 assert_eq!(padding.right, 10.0);
453 assert_eq!(padding.bottom, 10.0);
454 assert_eq!(padding.left, 10.0);
455 }
456
457 #[test]
458 fn test_padding_symmetric() {
459 let padding = Padding::symmetric(5.0, 10.0);
460 assert_eq!(padding.top, 10.0);
461 assert_eq!(padding.right, 5.0);
462 assert_eq!(padding.bottom, 10.0);
463 assert_eq!(padding.left, 5.0);
464 }
465
466 #[test]
467 fn test_padding_individual() {
468 let padding = Padding::individual(1.0, 2.0, 3.0, 4.0);
469 assert_eq!(padding.top, 1.0);
470 assert_eq!(padding.right, 2.0);
471 assert_eq!(padding.bottom, 3.0);
472 assert_eq!(padding.left, 4.0);
473 }
474
475 #[test]
476 fn test_padding_totals() {
477 let padding = Padding::new(5.0, 10.0, 15.0, 20.0);
478 assert_eq!(padding.horizontal_total(), 30.0);
479 assert_eq!(padding.vertical_total(), 20.0);
480 }
481
482 #[test]
483 fn test_padding_default() {
484 let padding = Padding::default();
485 assert_eq!(padding.top, 4.0);
486 assert_eq!(padding.horizontal_total(), 8.0);
487 }
488
489 #[test]
490 fn test_border_edge_new() {
491 let edge = BorderEdge::new(BorderStyle::Dashed, 2.0, Color::red());
492 assert_eq!(edge.style, BorderStyle::Dashed);
493 assert_eq!(edge.width, 2.0);
494 }
495
496 #[test]
497 fn test_border_edge_solid() {
498 let edge = BorderEdge::solid(1.5);
499 assert_eq!(edge.style, BorderStyle::Solid);
500 assert_eq!(edge.width, 1.5);
501 }
502
503 #[test]
504 fn test_border_edge_dashed() {
505 let edge = BorderEdge::dashed(1.0, Color::blue());
506 assert_eq!(edge.style, BorderStyle::Dashed);
507 }
508
509 #[test]
510 fn test_border_edge_dotted() {
511 let edge = BorderEdge::dotted(0.5, Color::green());
512 assert_eq!(edge.style, BorderStyle::Dotted);
513 }
514
515 #[test]
516 fn test_border_edge_none() {
517 let edge = BorderEdge::none();
518 assert_eq!(edge.style, BorderStyle::None);
519 assert_eq!(edge.width, 0.0);
520 }
521
522 #[test]
523 fn test_border_edge_default() {
524 let edge = BorderEdge::default();
525 assert_eq!(edge.style, BorderStyle::Solid);
526 assert_eq!(edge.width, 1.0);
527 }
528
529 #[test]
530 fn test_border_configuration_new() {
531 let config = BorderConfiguration::new();
532 assert_eq!(config.top.style, BorderStyle::Solid);
533 assert_eq!(config.right.style, BorderStyle::Solid);
534 assert_eq!(config.bottom.style, BorderStyle::Solid);
535 assert_eq!(config.left.style, BorderStyle::Solid);
536 }
537
538 #[test]
539 fn test_border_configuration_uniform() {
540 let edge = BorderEdge::dashed(2.0, Color::red());
541 let config = BorderConfiguration::uniform(edge);
542 assert_eq!(config.top.style, BorderStyle::Dashed);
543 assert_eq!(config.right.width, 2.0);
544 assert_eq!(config.bottom.style, BorderStyle::Dashed);
545 }
546
547 #[test]
548 fn test_border_configuration_edges() {
549 let config = BorderConfiguration::edges(true, false, true, false);
550 assert_eq!(config.top.style, BorderStyle::Solid);
551 assert_eq!(config.right.style, BorderStyle::None);
552 assert_eq!(config.bottom.style, BorderStyle::Solid);
553 assert_eq!(config.left.style, BorderStyle::None);
554 }
555
556 #[test]
557 fn test_border_configuration_none() {
558 let config = BorderConfiguration::none();
559 assert_eq!(config.top.style, BorderStyle::None);
560 assert_eq!(config.right.style, BorderStyle::None);
561 assert_eq!(config.bottom.style, BorderStyle::None);
562 assert_eq!(config.left.style, BorderStyle::None);
563 }
564
565 #[test]
566 fn test_border_configuration_default() {
567 let config = BorderConfiguration::default();
568 assert_eq!(config.top.style, BorderStyle::Solid);
569 }
570
571 #[test]
572 fn test_cell_style_new() {
573 let style = CellStyle::new();
574 assert!(style.background_color.is_none());
575 assert!(style.text_color.is_some());
576 assert_eq!(style.font_size, Some(12.0));
577 assert!(style.text_wrap);
578 }
579
580 #[test]
581 fn test_cell_style_background_color() {
582 let style = CellStyle::new().background_color(Color::yellow());
583 assert!(style.background_color.is_some());
584 }
585
586 #[test]
587 fn test_cell_style_text_color() {
588 let style = CellStyle::new().text_color(Color::blue());
589 assert!(style.text_color.is_some());
590 }
591
592 #[test]
593 fn test_cell_style_font() {
594 let style = CellStyle::new().font(Font::CourierBold);
595 assert_eq!(style.font, Some(Font::CourierBold));
596 }
597
598 #[test]
599 fn test_cell_style_font_size() {
600 let style = CellStyle::new().font_size(18.0);
601 assert_eq!(style.font_size, Some(18.0));
602 }
603
604 #[test]
605 fn test_cell_style_padding() {
606 let style = CellStyle::new().padding(Padding::uniform(20.0));
607 assert_eq!(style.padding.top, 20.0);
608 }
609
610 #[test]
611 fn test_cell_style_alignment() {
612 let style = CellStyle::new().alignment(CellAlignment::Center);
613 assert_eq!(style.alignment, CellAlignment::Center);
614 }
615
616 #[test]
617 fn test_cell_style_border_config() {
618 let config = BorderConfiguration::none();
619 let style = CellStyle::new().border_config(config);
620 assert_eq!(style.border.top.style, BorderStyle::None);
621 }
622
623 #[test]
624 fn test_cell_style_border() {
625 let style = CellStyle::new().border(BorderStyle::Dashed, 2.0, Color::red());
626 assert_eq!(style.border_style, BorderStyle::Dashed);
627 assert_eq!(style.border.top.width, 2.0);
628 }
629
630 #[test]
631 fn test_cell_style_text_wrap() {
632 let style = CellStyle::new().text_wrap(false);
633 assert!(!style.text_wrap);
634 }
635
636 #[test]
637 fn test_cell_style_min_height() {
638 let style = CellStyle::new().min_height(50.0);
639 assert_eq!(style.min_height, Some(50.0));
640 }
641
642 #[test]
643 fn test_cell_style_max_height() {
644 let style = CellStyle::new().max_height(100.0);
645 assert_eq!(style.max_height, Some(100.0));
646 }
647
648 #[test]
649 fn test_cell_style_header() {
650 let style = CellStyle::header();
651 assert_eq!(style.font, Some(Font::HelveticaBold));
652 assert_eq!(style.font_size, Some(14.0));
653 assert_eq!(style.alignment, CellAlignment::Center);
654 assert!(style.background_color.is_some());
655 }
656
657 #[test]
658 fn test_cell_style_data() {
659 let style = CellStyle::data();
660 assert_eq!(style.font, Some(Font::Helvetica));
661 assert_eq!(style.font_size, Some(12.0));
662 assert_eq!(style.alignment, CellAlignment::Left);
663 }
664
665 #[test]
666 fn test_cell_style_numeric() {
667 let style = CellStyle::numeric();
668 assert_eq!(style.font, Some(Font::Courier));
669 assert_eq!(style.font_size, Some(11.0));
670 assert_eq!(style.alignment, CellAlignment::Right);
671 }
672
673 #[test]
674 fn test_cell_style_alternating() {
675 let style = CellStyle::alternating();
676 assert!(style.background_color.is_some());
677 assert_eq!(style.alignment, CellAlignment::Left);
678 }
679
680 #[test]
681 fn test_cell_style_default() {
682 let style = CellStyle::default();
683 assert_eq!(style.font_size, Some(12.0));
684 }
685
686 #[test]
687 fn test_border_style_variants() {
688 let styles = vec![
689 BorderStyle::None,
690 BorderStyle::Solid,
691 BorderStyle::Dashed,
692 BorderStyle::Dotted,
693 BorderStyle::Double,
694 ];
695
696 for style in styles {
697 let cloned = style;
698 assert_eq!(style, cloned);
699 }
700 }
701
702 #[test]
703 fn test_cell_alignment_variants() {
704 let alignments = vec![
705 CellAlignment::Left,
706 CellAlignment::Center,
707 CellAlignment::Right,
708 CellAlignment::Justify,
709 ];
710
711 for alignment in alignments {
712 let cloned = alignment;
713 assert_eq!(alignment, cloned);
714 }
715 }
716}