mirror of
https://github.com/storytold/wordcraft.git
synced 2026-11-14 06:22:41 +00:00
Merge PR #2343: Fix numbered list levels that start at 0 or are overridden to 1
crates/doc/src/numbering.rs: kept #3417's per-(num, level) seen map and its single-level override; the override now sets that level's LevelFlags { started: false, first: Some(start) } from #2343 instead of start - 1 (the reviewed integration fix for #2343).
This commit is contained in:
commit
558f6c85af
1 file changed
+56
-9
@@ -291,6 +291,16 @@ pub struct Counters {
|
||||
/// Which levels of which nums have started: a num's start override for a level applies
|
||||
/// once, when that level of that num is first used (ECMA-376 Part 1, 17.9.27 startOverride).
|
||||
seen: HashMap<(u32, u8), bool>,
|
||||
/// Per abstract list: whether each level has counted yet (a value of 0 can be a real count when a
|
||||
/// level starts at 0).
|
||||
flags: HashMap<u32, [LevelFlags; 9]>,
|
||||
}
|
||||
|
||||
/// Whether a level has counted yet, and the value its next item takes if a start override says so.
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
struct LevelFlags {
|
||||
started: bool,
|
||||
first: Option<u32>,
|
||||
}
|
||||
|
||||
impl Counters {
|
||||
@@ -313,6 +323,7 @@ impl Counters {
|
||||
return None;
|
||||
}
|
||||
let st = self.state.entry(abs.id).or_insert([0; 9]);
|
||||
let fl = self.flags.entry(abs.id).or_default();
|
||||
// Only this level's override, not the num's others: the counters are shared by every
|
||||
// num of the abstract list, so restarting its other levels here would renumber
|
||||
// paragraphs of the other nums. A heading style can use one num for levels 1-4 and
|
||||
@@ -321,26 +332,27 @@ impl Counters {
|
||||
if let std::collections::hash_map::Entry::Vacant(e) = self.seen.entry((num, lv as u8)) {
|
||||
e.insert(true);
|
||||
if let Some((_, start)) = n.start_overrides.iter().find(|(l, _)| *l as usize == lv)
|
||||
&& let Some(s) = st.get_mut(lv)
|
||||
&& let (Some(s), Some(f)) = (st.get_mut(lv), fl.get_mut(lv))
|
||||
{
|
||||
*s = start.saturating_sub(1);
|
||||
*s = 0;
|
||||
*f = LevelFlags { started: false, first: Some(*start) };
|
||||
}
|
||||
}
|
||||
if let Some(v) = st.get_mut(lv) {
|
||||
*v = if *v == 0 { def.start.max(if def.start == 0 { 0 } else { 1 }) } else { v.saturating_add(1) };
|
||||
if *v == 0 {
|
||||
*v = def.start;
|
||||
}
|
||||
if let (Some(v), Some(f)) = (st.get_mut(lv), fl.get_mut(lv)) {
|
||||
*v = if f.started { v.saturating_add(1) } else { f.first.unwrap_or(def.start) };
|
||||
*f = LevelFlags { started: true, first: None };
|
||||
}
|
||||
// Deeper levels restart, unless they never restart or only restart after a level above
|
||||
// this one (Word's lvlRestart).
|
||||
for (d, deeper) in st.iter_mut().enumerate().skip(lv + 1) {
|
||||
for (d, (deeper, f)) in st.iter_mut().zip(fl.iter_mut()).enumerate().skip(lv + 1) {
|
||||
let restarts = level_def(d).is_none_or(|l| l.restart && l.restart_after.is_none_or(|k| lv < k as usize));
|
||||
if restarts {
|
||||
*deeper = 0;
|
||||
*f = LevelFlags::default();
|
||||
}
|
||||
}
|
||||
let vals = *st;
|
||||
let started = *fl;
|
||||
if def.format == NumFormat::Bullet {
|
||||
return Some((def.text.clone(), def));
|
||||
}
|
||||
@@ -353,7 +365,8 @@ impl Counters {
|
||||
chars.next();
|
||||
let k = (d as usize).saturating_sub(1).min(8);
|
||||
let val = vals.get(k).copied().unwrap_or(0);
|
||||
let val = if val == 0 { level_def(k).map(|l| l.start).unwrap_or(1) } else { val };
|
||||
let counted = started.get(k).is_some_and(|f| f.started);
|
||||
let val = if counted { val } else { level_def(k).map(|l| l.start).unwrap_or(1) };
|
||||
// Legal numbering (w:isLgl) shows every level in Arabic digits, whatever its own
|
||||
// format (二 of a Chinese outline becomes 2 in "2.1").
|
||||
let fmt = match level_def(k).map(|l| l.format).unwrap_or(NumFormat::Decimal) {
|
||||
@@ -440,6 +453,40 @@ mod tests {
|
||||
assert_eq!(l(2), "(1)", "level 1 restarts level 3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_level_starting_at_zero_counts_up() {
|
||||
let mut n = Numbering::default();
|
||||
let id = n.add_list(ListKind::Numbered);
|
||||
if let Some(a) = n.abstracts.first_mut() {
|
||||
a.levels[0].start = 0;
|
||||
}
|
||||
let mut c = Counters::default();
|
||||
let labels: Vec<String> = (0..4).map(|_| c.next_label(&n, id, 0).unwrap().0).collect();
|
||||
assert_eq!(labels, ["0.", "1.", "2.", "3."]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_start_override_of_one_or_zero_is_honoured() {
|
||||
let mut n = Numbering::default();
|
||||
let id = n.add_list(ListKind::Numbered);
|
||||
if let Some(a) = n.abstracts.first_mut() {
|
||||
a.levels[0].start = 5;
|
||||
}
|
||||
if let Some(num) = n.nums.iter_mut().find(|x| x.id == id) {
|
||||
num.start_overrides = vec![(0, 1)];
|
||||
}
|
||||
let mut c = Counters::default();
|
||||
let labels: Vec<String> = (0..2).map(|_| c.next_label(&n, id, 0).unwrap().0).collect();
|
||||
assert_eq!(labels, ["1.", "2."]);
|
||||
|
||||
if let Some(num) = n.nums.iter_mut().find(|x| x.id == id) {
|
||||
num.start_overrides = vec![(0, 0)];
|
||||
}
|
||||
let mut c = Counters::default();
|
||||
let labels: Vec<String> = (0..3).map(|_| c.next_label(&n, id, 0).unwrap().0).collect();
|
||||
assert_eq!(labels, ["0.", "1.", "2."]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn numbered_labels() {
|
||||
let mut n = Numbering::default();
|
||||
|
||||
Reference in new issue
Block a user