Skip to main content

ravenrs_sheet/engine/cmd/format/
cell_styles.rs

1use super::{
2    ACCOUNTING, BorderLine, BorderStyle, Borders, COMMA, Color, Fill, Json, NumFmt, Result, Session, Style, Theme, apply_style, bad, str_param,
3};
4
5/// Built-in cell styles (our own palette).
6pub 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            // AccentN, 20%/40%/60% - AccentN
103            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    // Number-format styles only change the number format.
187    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}