logisheets_controller/controller/
style.rs

1use crate::style_manager::RawStyle;
2use crate::theme_manager::ThemeManager;
3use logisheets_workbook::prelude::{
4    CtBorder, CtBorderPr, CtCellAlignment, CtCellProtection, CtColor, CtFill, CtFont, CtFontFamily,
5    CtFontName, CtFontScheme, CtUnderlineProperty, CtVerticalAlignFontProperty, StBorderStyle,
6    StGradientType, StPatternType,
7};
8
9#[derive(Debug, Clone)]
10#[cfg_attr(feature = "gents", gents_derives::gents_header(file_name = "style.ts"))]
11pub struct Style {
12    pub font: Font,
13    pub fill: Fill,
14    pub border: Border,
15    pub alignment: Option<CtCellAlignment>,
16    pub protection: Option<CtCellProtection>,
17    pub formatter: String,
18}
19
20#[derive(Debug, Clone)]
21#[cfg_attr(feature = "gents", gents_derives::gents_header(file_name = "font.ts"))]
22pub struct Font {
23    pub bold: bool,
24    pub italic: bool,
25    pub underline: Option<CtUnderlineProperty>,
26    pub color: Option<Color>,
27    pub sz: Option<f64>,
28    pub name: Option<CtFontName>,
29    pub charset: Option<i32>,
30    pub family: Option<CtFontFamily>,
31    pub strike: bool,
32    pub outline: bool,
33    pub shadow: bool,
34    pub condense: bool,
35    pub extend: bool,
36    pub vert_align: Option<CtVerticalAlignFontProperty>,
37    pub scheme: Option<CtFontScheme>,
38}
39
40#[derive(Debug, Clone)]
41#[cfg_attr(feature = "gents", gents_derives::gents_header(file_name = "fill.ts"))]
42pub enum Fill {
43    PatternFill(PatternFill),
44    GradientFill(GradientFill),
45}
46
47#[derive(Debug, Clone)]
48#[cfg_attr(
49    feature = "gents",
50    gents_derives::gents_header(file_name = "pattern_fill.ts")
51)]
52pub struct PatternFill {
53    pub fg_color: Option<Color>,
54    pub bg_color: Option<Color>,
55    pub pattern_type: Option<StPatternType>,
56}
57
58#[derive(Debug, Clone)]
59#[cfg_attr(
60    feature = "gents",
61    gents_derives::gents_header(file_name = "gradient_fill.ts")
62)]
63pub struct GradientFill {
64    pub stops: Vec<GradientStop>,
65    pub ty: StGradientType,
66    pub degree: f64,
67    pub left: f64,
68    pub right: f64,
69    pub top: f64,
70    pub bottom: f64,
71}
72
73#[derive(Debug, Clone)]
74#[cfg_attr(
75    feature = "gents",
76    gents_derives::gents_header(file_name = "gradient_stop.ts")
77)]
78pub struct GradientStop {
79    pub color: Color,
80    pub position: f64,
81}
82
83#[derive(Debug, Clone)]
84#[cfg_attr(
85    feature = "gents",
86    gents_derives::gents_header(file_name = "border_pr.ts")
87)]
88pub struct BorderPr {
89    pub color: Option<Color>,
90    pub style: StBorderStyle,
91}
92
93#[derive(Debug, Clone)]
94#[cfg_attr(
95    feature = "gents",
96    gents_derives::gents_header(file_name = "border.ts")
97)]
98pub struct Border {
99    pub left: Option<BorderPr>,
100    pub right: Option<BorderPr>,
101    pub top: Option<BorderPr>,
102    pub bottom: Option<BorderPr>,
103    pub diagonal: Option<BorderPr>,
104    pub vertical: Option<BorderPr>,
105    pub horizontal: Option<BorderPr>,
106    pub diagonal_up: Option<bool>,
107    pub diagonal_down: Option<bool>,
108    pub outline: bool,
109}
110
111#[derive(Debug, Clone)]
112#[cfg_attr(feature = "gents", gents_derives::gents_header(file_name = "color.ts"))]
113pub struct Color {
114    pub red: Option<f64>,
115    pub green: Option<f64>,
116    pub blue: Option<f64>,
117    pub alpha: Option<f64>,
118}
119
120pub struct StyleConverter<'a> {
121    pub theme_manager: &'a ThemeManager,
122}
123
124impl<'a> StyleConverter<'a> {
125    pub fn convert_style(&self, raw_style: RawStyle) -> Style {
126        Style {
127            font: self.convert_font(raw_style.font),
128            fill: self.convert_fill(raw_style.fill),
129            border: self.convert_border(raw_style.border),
130            alignment: raw_style.alignment,
131            protection: raw_style.protection,
132            formatter: raw_style.formatter,
133        }
134    }
135
136    fn convert_font(&self, font: CtFont) -> Font {
137        Font {
138            bold: font.bold,
139            italic: font.italic,
140            underline: font.underline.clone(),
141            color: font.color.map_or(None, |c| Some(self.convert_color(c))),
142            sz: font.sz.as_ref().map_or(None, |s| Some(s.val)),
143            name: font.name.clone(),
144            charset: font.charset.as_ref().map_or(None, |s| Some(s.val)),
145            family: font.family.clone(),
146            strike: font.strike,
147            outline: font.outline,
148            shadow: font.shadow,
149            condense: font.condense,
150            extend: font.extend,
151            vert_align: font.vert_align.clone(),
152            scheme: font.scheme.clone(),
153        }
154    }
155
156    fn convert_fill(&self, fill: CtFill) -> Fill {
157        match fill {
158            CtFill::PatternFill(pf) => Fill::PatternFill(PatternFill {
159                fg_color: pf.fg_color.map_or(None, |c| Some(self.convert_color(c))),
160                bg_color: pf.bg_color.map_or(None, |c| Some(self.convert_color(c))),
161                pattern_type: pf.pattern_type.clone(),
162            }),
163            CtFill::GradientFill(gf) => Fill::GradientFill(GradientFill {
164                stops: gf
165                    .stops
166                    .into_iter()
167                    .map(|stop| GradientStop {
168                        color: self.convert_color(stop.color),
169                        position: stop.position,
170                    })
171                    .collect(),
172                ty: gf.ty.clone(),
173                degree: gf.degree,
174                left: gf.left,
175                right: gf.right,
176                top: gf.top,
177                bottom: gf.bottom,
178            }),
179        }
180    }
181
182    fn convert_border(&self, border: CtBorder) -> Border {
183        Border {
184            left: border
185                .left
186                .map_or(None, |pr| Some(self.convert_border_pr(pr))),
187            right: border
188                .right
189                .map_or(None, |pr| Some(self.convert_border_pr(pr))),
190            top: border
191                .top
192                .map_or(None, |pr| Some(self.convert_border_pr(pr))),
193            bottom: border
194                .bottom
195                .map_or(None, |pr| Some(self.convert_border_pr(pr))),
196            diagonal: border
197                .diagonal
198                .map_or(None, |pr| Some(self.convert_border_pr(pr))),
199            vertical: border
200                .vertical
201                .map_or(None, |pr| Some(self.convert_border_pr(pr))),
202            horizontal: border
203                .horizontal
204                .map_or(None, |pr| Some(self.convert_border_pr(pr))),
205            diagonal_up: border.diagonal_up,
206            diagonal_down: border.diagonal_down,
207            outline: border.outline,
208        }
209    }
210
211    fn convert_border_pr(&self, border_pr: CtBorderPr) -> BorderPr {
212        BorderPr {
213            color: border_pr
214                .color
215                .map_or(None, |c| Some(self.convert_color(c))),
216            style: border_pr.style.clone(),
217        }
218    }
219
220    fn convert_color(&self, color: CtColor) -> Color {
221        let tint = color.tint;
222        let rgb = {
223            if let Some(rgb) = &color.rgb {
224                rgb.clone()
225            } else if let Some(indexed) = color.indexed {
226                match indexed {
227                    0 => String::from("FF000000"),
228                    1 => String::from("FFFFFFFF"),
229                    2 => String::from("FFFF0000"),
230                    3 => String::from("FF00FF00"),
231                    4 => String::from("FF0000FF"),
232                    5 => String::from("FFFFFF00"),
233                    6 => String::from("FFFF00FF"),
234                    7 => String::from("FF00FFFF"),
235                    8 => String::from("FF000000"),
236                    9 => String::from("FFFFFFFF"),
237                    10 => String::from("FFFF0000"),
238                    11 => String::from("FF00FF00"),
239                    12 => String::from("FF0000FF"),
240                    13 => String::from("FFFFFF00"),
241                    14 => String::from("FFFF00FF"),
242                    15 => String::from("FF00FFFF"),
243                    16 => String::from("FF800000"),
244                    17 => String::from("FF008000"),
245                    18 => String::from("FF000080"),
246                    19 => String::from("FF808000"),
247                    20 => String::from("FF800080"),
248                    21 => String::from("FF008080"),
249                    22 => String::from("FFC0C0C0"),
250                    23 => String::from("FF808080"),
251                    24 => String::from("FF9999FF"),
252                    25 => String::from("FF993366"),
253                    26 => String::from("FFFFFFCC"),
254                    27 => String::from("FFCCFFFF"),
255                    28 => String::from("FF660066"),
256                    29 => String::from("FFFF8080"),
257                    30 => String::from("FF0066CC"),
258                    31 => String::from("FFCCCCFF"),
259                    32 => String::from("FF000080"),
260                    33 => String::from("FFFF00FF"),
261                    34 => String::from("FFFFFF00"),
262                    35 => String::from("FF00FFFF"),
263                    36 => String::from("FF800080"),
264                    37 => String::from("FF800000"),
265                    38 => String::from("FF008080"),
266                    39 => String::from("FF0000FF"),
267                    40 => String::from("FF00CCFF"),
268                    41 => String::from("FFCCFFFF"),
269                    42 => String::from("FFCCFFCC"),
270                    43 => String::from("FFFFFF99"),
271                    44 => String::from("FF99CCFF"),
272                    45 => String::from("FFFF99CC"),
273                    46 => String::from("FFCC99FF"),
274                    47 => String::from("FFFFCC99"),
275                    48 => String::from("FF3366FF"),
276                    49 => String::from("FF33CCCC"),
277                    50 => String::from("FF99CC00"),
278                    51 => String::from("FFFFCC00"),
279                    52 => String::from("FFFF9900"),
280                    53 => String::from("FFFF6600"),
281                    54 => String::from("FF666699"),
282                    55 => String::from("FF969696"),
283                    56 => String::from("FF003366"),
284                    57 => String::from("FF339966"),
285                    58 => String::from("FF003300"),
286                    59 => String::from("FF333300"),
287                    60 => String::from("FF993300"),
288                    61 => String::from("FF993366"),
289                    62 => String::from("FF333399"),
290                    63 => String::from("FF333333"),
291                    _ => String::from("FF000000"),
292                }
293            } else if let Some(theme) = color.theme {
294                self.theme_manager.get_color(theme)
295            } else {
296                // auto is true.
297                // TODO: Figure out what auto means.
298                String::from("FF000000")
299            }
300        };
301        from_hex_str(rgb, tint)
302    }
303}
304
305// Convert ARGB hex str and apply the tint to it.
306fn from_hex_str(argb: String, tint: f64) -> Color {
307    use colorsys::{Hsl, Rgb};
308    if argb.len() < 8 {
309        return Color {
310            red: None,
311            green: None,
312            blue: None,
313            alpha: None,
314        };
315    }
316    let a = u32::from_str_radix(&argb[0..2], 16).unwrap_or(0) as f64;
317    let r = u32::from_str_radix(&argb[2..4], 16).unwrap_or(0) as f64;
318    let g = u32::from_str_radix(&argb[4..6], 16).unwrap_or(0) as f64;
319    let b = u32::from_str_radix(&argb[6..8], 16).unwrap_or(0) as f64;
320    let rgb = Rgb::new(r, g, b, None);
321    let mut hsl = Hsl::from(rgb);
322    let lum = {
323        let lum_max = 100.;
324        let lum = hsl.lightness();
325        if tint < 0. {
326            lum * (1. + tint)
327        } else if tint == 0. {
328            lum
329        } else {
330            lum * (1. - tint) + lum_max - lum_max * (1. - tint)
331        }
332    };
333    hsl.set_lightness(lum);
334    let rgb = Rgb::from(hsl);
335    Color {
336        red: Some(rgb.red()),
337        green: Some(rgb.green()),
338        blue: Some(rgb.blue()),
339        alpha: Some(a),
340    }
341}
342
343#[cfg(test)]
344mod tests {
345    use super::from_hex_str;
346
347    #[test]
348    fn test_from_hex_str() {
349        let argb = "FF2F4B1A".to_string();
350        let tint = 0.1;
351        let color = from_hex_str(argb, tint);
352        println!("{:?}", color);
353    }
354}