1use super::items::{Acc, FieldItems, Node, is_collapsed};
2use super::{
3 BorderLine, BorderStyle, Color, Fill, HAlign, HashMap, MAX_COLS, MAX_OUTPUT_CELLS, MAX_ROWS, NumFmt, PivotField, PivotLayout, PivotTable, Style,
4 Value,
5};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
11pub enum CellKind {
12 Empty,
13 Header,
14 RowLabel,
15 ColLabel,
16 Data,
17 Subtotal,
18 GrandTotal,
19 Filter,
20 Placeholder,
21}
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
25pub enum RowKind {
26 Filter,
27 Blank,
28 Header,
29 Item(bool),
31 Subtotal,
32 Grand,
33 Placeholder,
34}
35
36#[derive(Clone, Debug, PartialEq)]
37pub struct OutCell {
38 pub value: Value,
39 pub kind: CellKind,
40 pub indent: u8,
41 pub num_fmt: Option<String>,
42}
43
44impl OutCell {
45 fn empty() -> OutCell {
46 OutCell { value: Value::Empty, kind: CellKind::Empty, indent: 0, num_fmt: None }
47 }
48}
49
50#[derive(Clone, Debug)]
52pub struct Output {
53 pub grid: Vec<Vec<OutCell>>,
54 pub row_kinds: Vec<RowKind>,
55 pub body_offset: u32,
57 pub width: u32,
58 pub chart_rows: Vec<u32>,
60 pub chart_cols: Vec<(u32, String)>,
62 pub label_col: Option<u32>,
64}
65
66impl Output {
67 pub fn height(&self) -> u32 {
68 self.grid.len() as u32
69 }
70 pub fn value(&self, r: usize, c: usize) -> Value {
72 self.grid.get(r).and_then(|row| row.get(c)).map(|x| x.value.clone()).unwrap_or_default()
73 }
74}
75
76pub(super) struct Grid {
77 pub(super) rows: Vec<Vec<OutCell>>,
78 pub(super) kinds: Vec<RowKind>,
79 pub(super) width: usize,
80}
81
82impl Grid {
83 pub(super) fn push_row(&mut self, kind: RowKind) -> Result<usize, String> {
84 if (self.rows.len() as u64 + 1) * self.width.max(1) as u64 > MAX_OUTPUT_CELLS || self.rows.len() as u64 >= MAX_ROWS as u64 {
85 return Err("The PivotTable report is too large.".into());
86 }
87 self.rows.push(vec![OutCell::empty(); self.width]);
88 self.kinds.push(kind);
89 Ok(self.rows.len() - 1)
90 }
91 pub(super) fn set(&mut self, r: usize, c: usize, value: Value, kind: CellKind, indent: u8, fmt: Option<String>) {
92 if let Some(cell) = self.rows.get_mut(r).and_then(|row| row.get_mut(c)) {
93 *cell = OutCell { value, kind, indent, num_fmt: fmt };
94 }
95 }
96}
97
98#[derive(Clone, Copy, PartialEq, Eq)]
99pub(super) enum LineKind {
100 Item(bool),
101 Subtotal,
102 Grand,
103}
104
105pub(super) struct Line {
106 pub(super) kind: LineKind,
107 pub(super) leaf: bool,
109 pub(super) path: Vec<u32>,
110 pub(super) values: bool,
111 pub(super) labels: Vec<(usize, Value, u8, Option<String>)>,
113 pub(super) vi: Option<usize>,
115}
116
117#[derive(Clone, Copy, PartialEq, Eq)]
118pub(super) enum ColKind {
119 Leaf,
120 Subtotal,
121 Grand,
122}
123
124pub(super) struct ColSpec {
125 pub(super) kind: ColKind,
126 pub(super) path: Vec<u32>,
127}
128
129pub(super) struct Ctx<'a> {
130 pub(super) pt: &'a PivotTable,
131 pub(super) rows: Vec<&'a PivotField>,
132 pub(super) row_items: Vec<FieldItems>,
133 pub(super) row_fmts: Vec<String>,
134 pub(super) vpos: Option<usize>,
136 pub(super) val_names: Vec<String>,
138}
139
140impl Ctx<'_> {
141 fn label_cell(&self, depth: usize, item: u32) -> (Value, Option<String>, String) {
142 let Some(it) = self.row_items.get(depth).and_then(|fi| fi.get(item)) else { return (Value::Empty, None, String::new()) };
143 let fmt = if it.formatted { self.row_fmts.get(depth).cloned() } else { None };
144 (it.value.clone(), fmt, it.label.clone())
145 }
146
147 fn level_cell(&self, level: usize) -> (usize, u8) {
149 if self.pt.layout == PivotLayout::Outline { (level, 0) } else { (0, level.min(250) as u8) }
150 }
151
152 fn values_here(&self, depth: usize, vi: Option<usize>) -> bool {
154 vi.is_none() && self.vpos == Some(depth)
155 }
156
157 fn value_subtotals(&self, col: usize, indent: u8, label: &str, prefix: &[u32], out: &mut Vec<Line>) {
159 for (vi, name) in self.val_names.iter().enumerate() {
160 out.push(Line {
161 kind: LineKind::Subtotal,
162 leaf: false,
163 path: prefix.to_vec(),
164 values: true,
165 labels: vec![(col, Value::text(format!("{label} {name}")), indent, None)],
166 vi: Some(vi),
167 });
168 }
169 }
170
171 fn flatten_values(&self, nodes: &[Node], depth: usize, prefix: &mut Vec<u32>, out: &mut Vec<Line>) {
174 let (col, indent) = self.level_cell(depth);
175 for (vi, name) in self.val_names.iter().enumerate() {
176 let labels = vec![(col, Value::text(name.as_str()), indent, None)];
177 let leaf = nodes.is_empty();
178 out.push(Line { kind: LineKind::Item(!leaf), leaf, path: prefix.clone(), values: leaf, labels, vi: Some(vi) });
179 if !leaf {
180 self.flatten(nodes, depth, Some(vi), prefix, out);
181 }
182 }
183 }
184
185 pub(super) fn flatten(&self, nodes: &[Node], depth: usize, vi: Option<usize>, prefix: &mut Vec<u32>, out: &mut Vec<Line>) {
186 if self.values_here(depth, vi) {
187 self.flatten_values(nodes, depth, prefix, out);
188 return;
189 }
190 let n = self.rows.len();
191 let values_below = vi.is_none() && self.vpos.is_some();
193 let (col, indent) = self.level_cell(depth + usize::from(vi.is_some() && self.vpos.is_some()));
194 for node in nodes {
195 prefix.push(node.item);
196 let (v, fmt, label) = self.label_cell(depth, node.item);
197 let collapsed = depth + 1 < n && self.rows.get(depth).is_some_and(|f| is_collapsed(f, &label));
198 let leaf = depth + 1 >= n || collapsed || node.children.is_empty();
199 if leaf && !values_below {
200 out.push(Line {
201 kind: LineKind::Item(depth + 1 < n),
202 leaf: true,
203 path: prefix.clone(),
204 values: true,
205 labels: vec![(col, v, indent, fmt)],
206 vi,
207 });
208 } else if leaf {
209 out.push(Line {
210 kind: LineKind::Item(true),
211 leaf: false,
212 path: prefix.clone(),
213 values: false,
214 labels: vec![(col, v, indent, fmt)],
215 vi,
216 });
217 let level = if self.pt.layout == PivotLayout::Outline { self.vpos.unwrap_or(depth + 1) } else { depth + 1 };
220 self.flatten_values(&[], level, prefix, out);
221 } else if values_below {
222 out.push(Line {
224 kind: LineKind::Item(true),
225 leaf: false,
226 path: prefix.clone(),
227 values: false,
228 labels: vec![(col, v, indent, fmt)],
229 vi,
230 });
231 self.flatten(&node.children, depth + 1, None, prefix, out);
232 if self.pt.subtotals {
233 self.value_subtotals(col, indent, &label, prefix, out);
234 }
235 } else {
236 let top = self.pt.subtotals && self.pt.subtotals_top;
237 out.push(Line {
238 kind: LineKind::Item(true),
239 leaf: false,
240 path: prefix.clone(),
241 values: top,
242 labels: vec![(col, v, indent, fmt)],
243 vi,
244 });
245 self.flatten(&node.children, depth + 1, vi, prefix, out);
246 if self.pt.subtotals && !self.pt.subtotals_top {
247 out.push(Line {
248 kind: LineKind::Subtotal,
249 leaf: false,
250 path: prefix.clone(),
251 values: true,
252 labels: vec![(col, Value::text(format!("{label} Total")), indent, None)],
253 vi,
254 });
255 }
256 }
257 prefix.pop();
258 }
259 }
260
261 fn flatten_tabular_values(
263 &self,
264 nodes: &[Node],
265 depth: usize,
266 prefix: &mut Vec<u32>,
267 pending: &[(usize, Value, u8, Option<String>)],
268 out: &mut Vec<Line>,
269 ) {
270 for (vi, name) in self.val_names.iter().enumerate() {
271 let mut labels: Vec<(usize, Value, u8, Option<String>)> = if vi == 0 { pending.to_vec() } else { vec![] };
272 labels.push((depth, Value::text(name.as_str()), 0, None));
273 if nodes.is_empty() {
274 out.push(Line { kind: LineKind::Item(false), leaf: true, path: prefix.clone(), values: true, labels, vi: Some(vi) });
275 } else {
276 self.flatten_tabular(nodes, depth, Some(vi), prefix, &labels, out);
277 }
278 }
279 }
280
281 pub(super) fn flatten_tabular(
282 &self,
283 nodes: &[Node],
284 depth: usize,
285 vi: Option<usize>,
286 prefix: &mut Vec<u32>,
287 pending: &[(usize, Value, u8, Option<String>)],
288 out: &mut Vec<Line>,
289 ) {
290 if self.values_here(depth, vi) {
291 self.flatten_tabular_values(nodes, depth, prefix, pending, out);
292 return;
293 }
294 let n = self.rows.len();
295 let values_below = vi.is_none() && self.vpos.is_some();
296 let col = depth + usize::from(vi.is_some() && self.vpos.is_some());
297 for (i, node) in nodes.iter().enumerate() {
298 prefix.push(node.item);
299 let (v, fmt, label) = self.label_cell(depth, node.item);
300 let mut labels: Vec<(usize, Value, u8, Option<String>)> = if i == 0 { pending.to_vec() } else { vec![] };
301 labels.push((col, v, 0, fmt));
302 let collapsed = depth + 1 < n && self.rows.get(depth).is_some_and(|f| is_collapsed(f, &label));
303 if depth + 1 >= n || collapsed || node.children.is_empty() {
304 if values_below {
305 self.flatten_tabular_values(&[], self.vpos.unwrap_or(depth + 1), prefix, &labels, out);
306 } else {
307 out.push(Line { kind: LineKind::Item(false), leaf: true, path: prefix.clone(), values: true, labels, vi });
308 }
309 } else {
310 self.flatten_tabular(&node.children, depth + 1, vi, prefix, &labels, out);
311 if self.pt.subtotals && values_below {
312 self.value_subtotals(col, 0, &label, prefix, out);
313 } else if self.pt.subtotals {
314 out.push(Line {
315 kind: LineKind::Subtotal,
316 leaf: false,
317 path: prefix.clone(),
318 values: true,
319 labels: vec![(col, Value::text(format!("{label} Total")), 0, None)],
320 vi,
321 });
322 }
323 }
324 prefix.pop();
325 }
326 }
327}
328
329pub(super) fn flatten_cols(
330 nodes: &[Node],
331 depth: usize,
332 fields: &[&PivotField],
333 items: &[FieldItems],
334 subtotals: bool,
335 prefix: &mut Vec<u32>,
336 out: &mut Vec<ColSpec>,
337) {
338 let n = fields.len();
339 for node in nodes {
340 prefix.push(node.item);
341 let label = items.get(depth).map(|fi| fi.label(node.item)).unwrap_or_default();
342 let collapsed = depth + 1 < n && fields.get(depth).is_some_and(|f| is_collapsed(f, &label));
343 if depth + 1 >= n || collapsed || node.children.is_empty() {
344 out.push(ColSpec { kind: ColKind::Leaf, path: prefix.clone() });
345 } else {
346 flatten_cols(&node.children, depth + 1, fields, items, subtotals, prefix, out);
347 if subtotals {
348 out.push(ColSpec { kind: ColKind::Subtotal, path: prefix.clone() });
349 }
350 }
351 prefix.pop();
352 }
353}
354
355pub(super) type AggMap = HashMap<(Vec<u32>, Vec<u32>), Vec<Acc>>;
356
357#[derive(Clone, Copy)]
359pub(super) enum ValSrc {
360 Col(usize),
362 Calc(usize),
364}
365
366pub(super) fn data_cols_exceed_sheet(col_groups: usize, grand_total: bool, values: usize) -> bool {
369 (col_groups + usize::from(grand_total)).saturating_mul(values.max(1)) > MAX_COLS as usize
370}
371
372pub(super) struct Look {
377 header_fill: Color,
378 header_font: Color,
379 line: Color,
380 filter_fill: Color,
381}
382
383pub(super) fn look(style: &str) -> Look {
384 let rest = style.strip_prefix("PivotStyle").unwrap_or(style);
385 let (fam, n) = ["Light", "Medium", "Dark"]
386 .iter()
387 .find_map(|f| rest.strip_prefix(f).map(|num| (*f, num.parse::<u32>().unwrap_or(16))))
388 .unwrap_or(("Light", 16));
389 let k = n.saturating_sub(1) % 7;
390 let acc = if k == 0 { None } else { Some(3 + k as u8) };
391 let tone = |tint: i16| match acc {
392 Some(a) => Color::Theme(a, tint),
393 None => Color::Theme(1, tint.clamp(-1000, 1000)),
394 };
395 match fam {
396 "Dark" => Look { header_fill: Color::Theme(1, 0), header_font: Color::Theme(0, 0), line: tone(0), filter_fill: tone(600) },
397 "Medium" => Look { header_fill: tone(0), header_font: Color::Theme(0, 0), line: tone(0), filter_fill: tone(800) },
398 _ => Look { header_fill: tone(800), header_font: Color::Auto, line: tone(400), filter_fill: tone(800) },
399 }
400}
401
402pub(super) fn cell_style(lk: &Look, rk: RowKind, cell: &OutCell, last_header: bool) -> Style {
403 let mut s = Style::default();
404 match rk {
405 RowKind::Header => {
406 s.font.bold = true;
407 s.fill = Fill::solid(lk.header_fill);
408 s.font.color = lk.header_font;
409 if last_header {
410 s.borders.bottom = BorderLine { style: BorderStyle::Thin, color: lk.line };
411 }
412 }
413 RowKind::Filter => {
414 s.fill = Fill::solid(lk.filter_fill);
415 s.font.bold = last_header;
416 }
417 RowKind::Item(parent) => s.font.bold = parent,
418 RowKind::Subtotal => s.font.bold = true,
419 RowKind::Grand => {
420 s.font.bold = true;
421 s.borders.top = BorderLine { style: BorderStyle::Thin, color: lk.line };
422 }
423 RowKind::Placeholder => {
424 s.font.italic = true;
425 s.font.color = Color::Theme(1, 500);
426 }
427 RowKind::Blank => {}
428 }
429 if matches!(cell.kind, CellKind::RowLabel | CellKind::ColLabel | CellKind::Subtotal | CellKind::GrandTotal) && !cell.value.is_number() {
430 s.align.h = HAlign::Left;
431 }
432 if matches!(cell.kind, CellKind::RowLabel | CellKind::ColLabel) && cell.value.is_number() {
433 s.align.h = HAlign::Left;
434 }
435 s.align.indent = cell.indent.min(15);
436 if let Some(f) = &cell.num_fmt {
437 s.num_fmt = NumFmt::new(f);
438 }
439 s
440}