1use std::collections::BTreeMap;
8
9use crate::model::{LineInfo, Sheet};
10
11pub const MAX_LEVEL: u8 = 7;
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub struct OutlineGroup {
18 pub level: u8,
19 pub start: u32,
20 pub end: u32,
21 pub collapsed: bool,
23 pub summary_before: bool,
25}
26
27impl OutlineGroup {
28 pub fn summary(&self) -> u32 {
30 if self.summary_before { self.start.saturating_sub(1) } else { self.end.saturating_add(1) }
31 }
32 pub fn has_summary(&self) -> bool {
35 !self.contains(self.summary())
36 }
37 pub fn contains(&self, line: u32) -> bool {
38 line >= self.start && line <= self.end
39 }
40}
41
42pub fn summary_before(sh: &Sheet, rows: bool) -> bool {
44 if rows { !sh.summary_below } else { !sh.summary_right }
45}
46
47pub fn max_level(lines: &BTreeMap<u32, LineInfo>) -> u8 {
49 lines.values().map(|l| l.outline.min(MAX_LEVEL)).max().unwrap_or(0)
50}
51
52pub fn groups(lines: &BTreeMap<u32, LineInfo>) -> Vec<OutlineGroup> {
54 groups_with(lines, false)
55}
56
57pub fn groups_with(lines: &BTreeMap<u32, LineInfo>, summary_before: bool) -> Vec<OutlineGroup> {
60 let mut out = Vec::new();
61 let mut open: [Option<(u32, bool)>; MAX_LEVEL as usize] = [None; MAX_LEVEL as usize];
63 let mut prev: Option<u32> = None;
64 let close_from = |open: &mut [Option<(u32, bool)>; MAX_LEVEL as usize], from: u8, end: u32, out: &mut Vec<OutlineGroup>| {
65 for level in (from..=MAX_LEVEL).rev() {
66 if let Some(slot) = open.get_mut(level as usize - 1)
67 && let Some((start, hidden)) = slot.take()
68 {
69 out.push(OutlineGroup { level, start, end, collapsed: hidden, summary_before });
70 }
71 }
72 };
73 for (&i, info) in lines.iter().filter(|(_, l)| l.outline > 0) {
74 let level = info.outline.min(MAX_LEVEL);
75 match prev {
76 Some(p) if p.checked_add(1) != Some(i) => close_from(&mut open, 1, p, &mut out),
78 Some(p) if level < MAX_LEVEL => close_from(&mut open, level + 1, p, &mut out),
79 _ => {}
80 }
81 for slot in open.iter_mut().take(level as usize) {
82 match slot {
83 Some((_, hidden)) => *hidden &= info.hidden,
84 None => *slot = Some((i, info.hidden)),
85 }
86 }
87 prev = Some(i);
88 }
89 if let Some(p) = prev {
90 close_from(&mut open, 1, p, &mut out);
91 }
92 out
93}
94
95pub fn group_at(lines: &BTreeMap<u32, LineInfo>, line: u32, level: u8) -> Option<OutlineGroup> {
97 groups(lines).into_iter().find(|g| g.level == level && g.contains(line))
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 fn lines(spec: &[(u32, u8, bool)]) -> BTreeMap<u32, LineInfo> {
105 spec.iter().map(|&(i, outline, hidden)| (i, LineInfo { outline, hidden, ..Default::default() })).collect()
106 }
107
108 #[test]
109 fn no_outline_has_no_groups() {
110 let l = lines(&[(3, 0, true)]);
111 assert_eq!(max_level(&l), 0);
112 assert!(groups(&l).is_empty());
113 }
114
115 #[test]
116 fn nested_and_separate_groups() {
117 let l = lines(&[(4, 1, false), (5, 2, false), (6, 2, false), (7, 1, false), (11, 1, true), (12, 1, true)]);
119 assert_eq!(max_level(&l), 2);
120 let g = groups(&l);
121 assert_eq!(
122 g,
123 vec![
124 OutlineGroup { level: 2, start: 5, end: 6, collapsed: false, summary_before: false },
125 OutlineGroup { level: 1, start: 4, end: 7, collapsed: false, summary_before: false },
126 OutlineGroup { level: 1, start: 11, end: 12, collapsed: true, summary_before: false },
127 ]
128 );
129 assert_eq!(g[1].summary(), 8);
130 assert_eq!(group_at(&l, 6, 1).map(|g| g.start), Some(4));
131 assert_eq!(group_at(&l, 4, 2), None);
132 }
133
134 #[test]
135 fn adjacent_groups_at_one_level_split_by_a_shallower_line() {
136 let l = lines(&[(0, 2, true), (1, 2, true), (2, 1, false), (3, 2, false)]);
138 let g = groups(&l);
139 assert_eq!(g.iter().filter(|g| g.level == 2).count(), 2);
140 assert_eq!(g.iter().find(|g| g.level == 2 && g.start == 0).map(|g| g.collapsed), Some(true));
141 assert_eq!(g.iter().find(|g| g.level == 1).map(|g| (g.start, g.end, g.collapsed)), Some((0, 3, false)));
142 }
143
144 #[test]
145 fn deepest_level_and_last_line_do_not_overflow() {
146 let l = lines(&[(u32::MAX - 1, 9, false), (u32::MAX, 7, false)]);
147 let g = groups(&l);
148 assert_eq!(g.len(), 7);
149 assert!(g.iter().all(|g| g.start == u32::MAX - 1 && g.end == u32::MAX));
150 assert_eq!(g[0].summary(), u32::MAX);
151 assert!(!g[0].has_summary());
152 }
153
154 #[test]
155 fn summary_before_the_detail() {
156 let l = lines(&[(4, 1, false), (5, 2, false), (6, 2, false), (7, 1, false), (0, 1, false)]);
158 let g = groups_with(&l, true);
159 let at = |level: u8, start: u32| g.iter().find(|g| g.level == level && g.start == start).copied();
160 assert_eq!(at(1, 4).map(|g| (g.summary(), g.has_summary())), Some((3, true)));
161 assert_eq!(at(2, 5).map(|g| (g.summary(), g.has_summary())), Some((4, true)));
162 assert_eq!(at(1, 0).map(|g| (g.summary(), g.has_summary())), Some((0, false)));
164 let mut sh = Sheet::new("S");
165 assert!(!summary_before(&sh, true) && !summary_before(&sh, false));
166 sh.summary_below = false;
167 assert!(summary_before(&sh, true) && !summary_before(&sh, false));
168 }
169}