1use super::{
2 ACCOUNTING, BorderLine, BorderStyle, Borders, COMMA, Color, Fill, Json, NumFmt, Result, Session, Style, Theme, apply_style, bad, str_param,
3};
4
5pub fn builtin_cell_style(name: &str, theme: &Theme) -> Option<Style> {
7 let _ = theme;
8 let mut s = Style::default();
9 let accent = |i: u8, tint: i16| Color::Theme(i, tint);
10 match name {
11 "Normal" => {}
12 "Good" => {
13 s.fill = Fill::solid(Color::rgb(0xC6, 0xEF, 0xCE));
14 s.font.color = Color::rgb(0x00, 0x61, 0x00);
15 }
16 "Bad" => {
17 s.fill = Fill::solid(Color::rgb(0xFF, 0xC7, 0xCE));
18 s.font.color = Color::rgb(0x9C, 0x00, 0x06);
19 }
20 "Neutral" => {
21 s.fill = Fill::solid(Color::rgb(0xFF, 0xEB, 0x9C));
22 s.font.color = Color::rgb(0x9C, 0x57, 0x00);
23 }
24 "Calculation" => {
25 s.fill = Fill::solid(Color::rgb(0xF2, 0xF2, 0xF2));
26 s.font.color = Color::rgb(0xFA, 0x7D, 0x00);
27 s.font.bold = true;
28 let l = BorderLine { style: BorderStyle::Thin, color: Color::rgb(0x7F, 0x7F, 0x7F) };
29 s.borders = Borders { left: l, right: l, top: l, bottom: l, ..Default::default() };
30 }
31 "Check Cell" => {
32 s.fill = Fill::solid(Color::rgb(0xA5, 0xA5, 0xA5));
33 s.font.color = Color::rgb(0xFF, 0xFF, 0xFF);
34 s.font.bold = true;
35 let l = BorderLine { style: BorderStyle::Double, color: Color::rgb(0x3F, 0x3F, 0x3F) };
36 s.borders = Borders { left: l, right: l, top: l, bottom: l, ..Default::default() };
37 }
38 "Explanatory Text" => {
39 s.font.italic = true;
40 s.font.color = Color::rgb(0x7F, 0x7F, 0x7F);
41 }
42 "Input" => {
43 s.fill = Fill::solid(Color::rgb(0xFF, 0xCC, 0x99));
44 s.font.color = Color::rgb(0x3F, 0x3F, 0x76);
45 let l = BorderLine { style: BorderStyle::Thin, color: Color::rgb(0x7F, 0x7F, 0x7F) };
46 s.borders = Borders { left: l, right: l, top: l, bottom: l, ..Default::default() };
47 }
48 "Linked Cell" => {
49 s.font.color = Color::rgb(0xFA, 0x7D, 0x00);
50 s.borders.bottom = BorderLine { style: BorderStyle::Double, color: Color::rgb(0xFF, 0x80, 0x01) };
51 }
52 "Note" => {
53 s.fill = Fill::solid(Color::rgb(0xFF, 0xFF, 0xCC));
54 let l = BorderLine { style: BorderStyle::Thin, color: Color::rgb(0xB2, 0xB2, 0xB2) };
55 s.borders = Borders { left: l, right: l, top: l, bottom: l, ..Default::default() };
56 }
57 "Output" => {
58 s.fill = Fill::solid(Color::rgb(0xF2, 0xF2, 0xF2));
59 s.font.color = Color::rgb(0x3F, 0x3F, 0x3F);
60 s.font.bold = true;
61 let l = BorderLine { style: BorderStyle::Thin, color: Color::rgb(0x3F, 0x3F, 0x3F) };
62 s.borders = Borders { left: l, right: l, top: l, bottom: l, ..Default::default() };
63 }
64 "Warning Text" => s.font.color = Color::rgb(0xFF, 0x00, 0x00),
65 "Title" => {
66 s.font.size = 18.0;
67 s.font.color = Color::Theme(3, 0);
68 s.font.name = theme.major_font.clone();
69 }
70 "Heading 1" => {
71 s.font.size = 15.0;
72 s.font.bold = true;
73 s.font.color = Color::Theme(3, 0);
74 s.borders.bottom = BorderLine { style: BorderStyle::Thick, color: accent(4, 0) };
75 }
76 "Heading 2" => {
77 s.font.size = 13.0;
78 s.font.bold = true;
79 s.font.color = Color::Theme(3, 0);
80 s.borders.bottom = BorderLine { style: BorderStyle::Thick, color: accent(4, 500) };
81 }
82 "Heading 3" => {
83 s.font.bold = true;
84 s.font.color = Color::Theme(3, 0);
85 s.borders.bottom = BorderLine { style: BorderStyle::Medium, color: accent(4, 400) };
86 }
87 "Heading 4" => {
88 s.font.bold = true;
89 s.font.color = Color::Theme(3, 0);
90 }
91 "Total" => {
92 s.font.bold = true;
93 s.borders.top = BorderLine { style: BorderStyle::Thin, color: accent(4, 0) };
94 s.borders.bottom = BorderLine { style: BorderStyle::Double, color: accent(4, 0) };
95 }
96 "Currency" => s.num_fmt = NumFmt::new(ACCOUNTING),
97 "Comma" => s.num_fmt = NumFmt::new(COMMA),
98 "Percent" => s.num_fmt = NumFmt::new("0%"),
99 "Currency [0]" => s.num_fmt = NumFmt::new("_(\"$\"* #,##0_);_(\"$\"* \\(#,##0\\);_(\"$\"* \"-\"_);_(@_)"),
100 "Comma [0]" => s.num_fmt = NumFmt::new("_(* #,##0_);_(* \\(#,##0\\);_(* \"-\"_);_(@_)"),
101 n => {
102 let (pct, rest) = match n.split_once("% - ") {
104 Some((p, r)) => (p.parse::<i16>().ok(), r),
105 None => (None, n),
106 };
107 let i: u8 = rest.strip_prefix("Accent")?.parse().ok()?;
108 if !(1..=6).contains(&i) {
109 return None;
110 }
111 match pct {
112 None => {
113 s.fill = Fill::solid(accent(3 + i, 0));
114 s.font.color = Color::Theme(0, 0);
115 }
116 Some(p) => {
117 s.fill = Fill::solid(accent(3 + i, (1000 - p * 10).clamp(0, 1000)));
118 s.font.color = if p >= 60 { Color::Theme(0, 0) } else { Color::Theme(1, 0) };
119 }
120 }
121 }
122 }
123 Some(s)
124}
125
126pub const CELL_STYLE_NAMES: &[&str] = &[
127 "Normal",
128 "Bad",
129 "Good",
130 "Neutral",
131 "Calculation",
132 "Check Cell",
133 "Explanatory Text",
134 "Input",
135 "Linked Cell",
136 "Note",
137 "Output",
138 "Warning Text",
139 "Heading 1",
140 "Heading 2",
141 "Heading 3",
142 "Heading 4",
143 "Title",
144 "Total",
145 "20% - Accent1",
146 "20% - Accent2",
147 "20% - Accent3",
148 "20% - Accent4",
149 "20% - Accent5",
150 "20% - Accent6",
151 "40% - Accent1",
152 "40% - Accent2",
153 "40% - Accent3",
154 "40% - Accent4",
155 "40% - Accent5",
156 "40% - Accent6",
157 "60% - Accent1",
158 "60% - Accent2",
159 "60% - Accent3",
160 "60% - Accent4",
161 "60% - Accent5",
162 "60% - Accent6",
163 "Accent1",
164 "Accent2",
165 "Accent3",
166 "Accent4",
167 "Accent5",
168 "Accent6",
169 "Comma",
170 "Comma [0]",
171 "Currency",
172 "Currency [0]",
173 "Percent",
174];
175
176pub(super) fn cell_style(s: &mut Session, p: &Json) -> Result<Json> {
177 let name = str_param(p, "name").unwrap_or("Normal").to_string();
178 let d = s.doc()?;
179 let style =
180 d.wb.cell_styles
181 .iter()
182 .find(|(n, _)| n.eq_ignore_ascii_case(&name))
183 .map(|(_, s)| s.clone())
184 .or_else(|| builtin_cell_style(&name, &d.wb.theme));
185 let Some(style) = style else { return Err(bad("home.cellStyle", format!("unknown cell style `{name}`"))) };
186 let only_fmt = matches!(name.as_str(), "Comma" | "Comma [0]" | "Currency" | "Currency [0]" | "Percent");
188 apply_style(s, p, move |st| {
189 if only_fmt {
190 st.num_fmt = style.num_fmt.clone();
191 } else {
192 let keep_fmt = st.num_fmt.clone();
193 *st = style.clone();
194 if style.num_fmt.as_str() == "General" {
195 st.num_fmt = keep_fmt;
196 }
197 }
198 })
199}