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 String::from("FF000000")
299 }
300 };
301 from_hex_str(rgb, tint)
302 }
303}
304
305fn 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}