Skip to main content

visi_core/core/
style.rs

1use serde::{Deserialize, Serialize};
2
3/// Cell formatting style attributes
4/// (font color, background color, font styles, font family, font size)
5#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
6pub struct CellStyle {
7    /// Font color as Hex (e.g. "#FF0000" or "FF0000")
8    /// or standard color name ("red", "blue", etc.)
9    pub font_color: Option<String>,
10    /// Background fill color as Hex or color name
11    pub bg_color: Option<String>,
12    /// Bold text style flag
13    pub bold: Option<bool>,
14    /// Italic text style flag
15    pub italic: Option<bool>,
16    /// Underline text style flag
17    pub underline: Option<bool>,
18    /// Font family name (e.g. "Arial", "Calibri", "Courier New")
19    pub font_family: Option<String>,
20    /// Font size in points (e.g. 11, 12, 14.5)
21    pub font_size: Option<f64>,
22    /// Excel number-format code (e.g. `0.00`, `0.0"x"`, `m/d/yy`, `yyyy-mm-dd`)
23    pub num_format: Option<String>,
24}
25
26impl CellStyle {
27    /// A style with nothing set.
28    pub fn new() -> Self {
29        Self::default()
30    }
31
32    /// Whether no attribute is set. An empty style is stored as no style at
33    /// all rather than kept around.
34    pub fn is_empty(&self) -> bool {
35        self.font_color.is_none()
36            && self.bg_color.is_none()
37            && self.bold.is_none()
38            && self.italic.is_none()
39            && self.underline.is_none()
40            && self.font_family.is_none()
41            && self.font_size.is_none()
42            && self.num_format.is_none()
43    }
44
45    /// Overlays `other` onto this style, attribute by attribute.
46    ///
47    /// Only the attributes `other` actually sets are copied, so merging a
48    /// style that just sets `bold` leaves an existing font color alone.
49    pub fn merge(&mut self, other: &CellStyle) {
50        if other.font_color.is_some() {
51            self.font_color = other.font_color.clone();
52        }
53        if other.bg_color.is_some() {
54            self.bg_color = other.bg_color.clone();
55        }
56        if other.bold.is_some() {
57            self.bold = other.bold;
58        }
59        if other.italic.is_some() {
60            self.italic = other.italic;
61        }
62        if other.underline.is_some() {
63            self.underline = other.underline;
64        }
65        if other.font_family.is_some() {
66            self.font_family = other.font_family.clone();
67        }
68        if other.font_size.is_some() {
69            self.font_size = other.font_size;
70        }
71        if other.num_format.is_some() {
72            self.num_format = other.num_format.clone();
73        }
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn test_cell_style_is_empty() {
83        let style = CellStyle::new();
84        assert!(style.is_empty());
85
86        let style_bold = CellStyle {
87            bold: Some(true),
88            ..Default::default()
89        };
90        assert!(!style_bold.is_empty());
91    }
92
93    #[test]
94    fn test_cell_style_merge() {
95        let mut style1 = CellStyle {
96            font_color: Some("#FF0000".to_string()),
97            bold: Some(true),
98            ..Default::default()
99        };
100        let style2 = CellStyle {
101            bg_color: Some("#00FF00".to_string()),
102            font_color: Some("#0000FF".to_string()),
103            font_size: Some(14.0),
104            ..Default::default()
105        };
106
107        style1.merge(&style2);
108        assert_eq!(style1.font_color, Some("#0000FF".to_string()));
109        assert_eq!(style1.bg_color, Some("#00FF00".to_string()));
110        assert_eq!(style1.bold, Some(true));
111        assert_eq!(style1.font_size, Some(14.0));
112    }
113}