1use sheetkit_xml::theme::ThemeColors;
4
5pub fn resolve_theme_color(theme: &ThemeColors, index: u32, tint: Option<f64>) -> Option<String> {
8 let base = theme.get(index as usize)?;
9 if base.is_empty() {
10 return None;
11 }
12 match tint {
13 Some(t) if t != 0.0 => Some(apply_tint(base, t)),
14 _ => Some(base.to_string()),
15 }
16}
17
18fn apply_tint(argb: &str, tint: f64) -> String {
21 if argb.len() < 8 {
22 return argb.to_string();
23 }
24 let r = u8::from_str_radix(&argb[2..4], 16).unwrap_or(0);
25 let g = u8::from_str_radix(&argb[4..6], 16).unwrap_or(0);
26 let b = u8::from_str_radix(&argb[6..8], 16).unwrap_or(0);
27
28 let (r, g, b) = if tint < 0.0 {
29 let factor = 1.0 + tint;
30 (
31 (r as f64 * factor) as u8,
32 (g as f64 * factor) as u8,
33 (b as f64 * factor) as u8,
34 )
35 } else {
36 (
37 (r as f64 + (255.0 - r as f64) * tint) as u8,
38 (g as f64 + (255.0 - g as f64) * tint) as u8,
39 (b as f64 + (255.0 - b as f64) * tint) as u8,
40 )
41 };
42
43 format!("FF{:02X}{:02X}{:02X}", r, g, b)
44}
45
46pub fn default_theme_colors() -> ThemeColors {
48 ThemeColors {
49 colors: [
50 "FF000000".to_string(),
51 "FFFFFFFF".to_string(),
52 "FF44546A".to_string(),
53 "FFE7E6E6".to_string(),
54 "FF4472C4".to_string(),
55 "FFED7D31".to_string(),
56 "FFA5A5A5".to_string(),
57 "FFFFC000".to_string(),
58 "FF5B9BD5".to_string(),
59 "FF70AD47".to_string(),
60 "FF0563C1".to_string(),
61 "FF954F72".to_string(),
62 ],
63 }
64}
65
66pub fn default_theme_xml() -> Vec<u8> {
68 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
69<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme">
70 <a:themeElements>
71 <a:clrScheme name="Office">
72 <a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1>
73 <a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1>
74 <a:dk2><a:srgbClr val="44546A"/></a:dk2>
75 <a:lt2><a:srgbClr val="E7E6E6"/></a:lt2>
76 <a:accent1><a:srgbClr val="4472C4"/></a:accent1>
77 <a:accent2><a:srgbClr val="ED7D31"/></a:accent2>
78 <a:accent3><a:srgbClr val="A5A5A5"/></a:accent3>
79 <a:accent4><a:srgbClr val="FFC000"/></a:accent4>
80 <a:accent5><a:srgbClr val="5B9BD5"/></a:accent5>
81 <a:accent6><a:srgbClr val="70AD47"/></a:accent6>
82 <a:hlink><a:srgbClr val="0563C1"/></a:hlink>
83 <a:folHlink><a:srgbClr val="954F72"/></a:folHlink>
84 </a:clrScheme>
85 <a:fontScheme name="Office">
86 <a:majorFont><a:latin typeface="Calibri Light"/><a:ea typeface=""/><a:cs typeface=""/></a:majorFont>
87 <a:minorFont><a:latin typeface="Calibri"/><a:ea typeface=""/><a:cs typeface=""/></a:minorFont>
88 </a:fontScheme>
89 <a:fmtScheme name="Office">
90 <a:fillStyleLst>
91 <a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
92 <a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
93 <a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
94 </a:fillStyleLst>
95 <a:lnStyleLst>
96 <a:ln w="6350"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:ln>
97 <a:ln w="12700"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:ln>
98 <a:ln w="19050"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:ln>
99 </a:lnStyleLst>
100 <a:effectStyleLst>
101 <a:effectStyle><a:effectLst/></a:effectStyle>
102 <a:effectStyle><a:effectLst/></a:effectStyle>
103 <a:effectStyle><a:effectLst/></a:effectStyle>
104 </a:effectStyleLst>
105 <a:bgFillStyleLst>
106 <a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
107 <a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
108 <a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
109 </a:bgFillStyleLst>
110 </a:fmtScheme>
111 </a:themeElements>
112</a:theme>"#;
113 xml.as_bytes().to_vec()
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn test_resolve_theme_color_no_tint() {
122 let theme = default_theme_colors();
123 let color = resolve_theme_color(&theme, 0, None);
124 assert_eq!(color, Some("FF000000".to_string()));
125 }
126
127 #[test]
128 fn test_resolve_theme_color_with_positive_tint() {
129 let theme = default_theme_colors();
130 let color = resolve_theme_color(&theme, 0, Some(0.5));
131 assert!(color.is_some());
132 let c = color.unwrap();
133 assert_eq!(&c[0..2], "FF");
134 }
135
136 #[test]
137 fn test_resolve_invalid_index() {
138 let theme = default_theme_colors();
139 assert!(resolve_theme_color(&theme, 99, None).is_none());
140 }
141
142 #[test]
143 fn test_apply_tint_lighten() {
144 let result = apply_tint("FF000000", 0.5);
145 assert_eq!(result, "FF7F7F7F");
146 }
147
148 #[test]
149 fn test_apply_tint_darken() {
150 let result = apply_tint("FFFFFFFF", -0.5);
151 assert_eq!(result, "FF7F7F7F");
152 }
153
154 #[test]
155 fn test_apply_tint_zero() {
156 let theme = default_theme_colors();
157 let color = resolve_theme_color(&theme, 4, Some(0.0));
158 assert_eq!(color, Some("FF4472C4".to_string()));
159 }
160
161 #[test]
162 fn test_default_theme_has_all_colors() {
163 let theme = default_theme_colors();
164 for i in 0..12 {
165 assert!(!theme.colors[i].is_empty());
166 }
167 }
168
169 #[test]
170 fn test_default_theme_xml_parseable() {
171 let xml_bytes = default_theme_xml();
172 let colors = sheetkit_xml::theme::parse_theme_colors(&xml_bytes);
173 assert_eq!(colors.colors[0], "FF000000");
174 assert_eq!(colors.colors[1], "FFFFFFFF");
175 assert_eq!(colors.colors[4], "FF4472C4");
176 }
177}