Skip to main content

visi_core/core/
style.rs

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