From 52c88567b8dcf320c01f7fcfef158abfbb7a07f5 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 24 Oct 2026 19:52:32 -0700 Subject: [PATCH] Fields: a count past its number format's range keeps its digits NUMWORDS, NUMCHARS and REVNUM wrote a count past what the field's \* number format covers as the format's last value, so a revision number of 1,000,000 under \* ROMAN showed 50 M's. Such a count now keeps its digits, as a table formula result does. --- crates/engine/src/cmd/references.rs | 19 +++++++++++++++++-- crates/engine/src/cmd/table_formula.rs | 17 ++++++++++++----- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/crates/engine/src/cmd/references.rs b/crates/engine/src/cmd/references.rs index 518e0a511..ad9fa1581 100644 --- a/crates/engine/src/cmd/references.rs +++ b/crates/engine/src/cmd/references.rs @@ -1144,10 +1144,13 @@ fn new_property_value(instr: &str) -> Option { FieldCode::parse(instr).args.first().map(|a| a.chars().take(255).collect()) } -/// A count (NUMWORDS, NUMCHARS, REVNUM) in the field's `\*` number format, digits without one, +/// A count (NUMWORDS, NUMCHARS, REVNUM) in the field's `\*` number format, digits without one or +/// when the format does not cover the count (see [`super::table_formula::number_format_covers`]), /// with the field's capitalization switches. fn count_text(instr: &str, n: usize) -> String { - let fmt = wordcraft_layout::fields::format_switch(instr).unwrap_or(NumFormat::Decimal); + let fmt = wordcraft_layout::fields::format_switch(instr) + .filter(|f| super::table_formula::number_format_covers(*f, n as f64)) + .unwrap_or(NumFormat::Decimal); wordcraft_layout::fields::field_number(instr, fmt, u32::try_from(n).unwrap_or(u32::MAX)) } @@ -1647,6 +1650,18 @@ mod tests { assert_eq!((s.doc.core.subject.as_str(), s.doc.core.creator.as_str()), ("Old", "Grace")); } + /// A count the field's number format does not cover keeps its digits. + #[test] + fn counts_past_the_number_format_keep_their_digits() { + let mut doc = wordcraft_doc::Document::from_text("one two three"); + doc.core.revision = 1_000_000; + let codes = [r"REVNUM \* ROMAN", r"REVNUM \* CardText", r"REVNUM \* alphabetic", r"NUMWORDS \* ROMAN"]; + let (mut s, hdr) = header_fields_session(doc, &codes); + s.run("references.updateFields", &json!({})).unwrap(); + let got: Vec = story_fields(&s, StoryRef::Part(hdr)).into_iter().map(|(_, r)| r).collect(); + assert_eq!(got, ["1000000", "1000000", "1000000", "III"]); + } + /// #1706: `all: true` (printing) updates the whole document even with the caret in a header. #[test] fn update_fields_all_covers_every_story() { diff --git a/crates/engine/src/cmd/table_formula.rs b/crates/engine/src/cmd/table_formula.rs index df789911c..f20f07df2 100644 --- a/crates/engine/src/cmd/table_formula.rs +++ b/crates/engine/src/cmd/table_formula.rs @@ -116,16 +116,23 @@ fn format_switches(switches: &str, value: f64, text: String) -> String { use wordcraft_doc::section::NumFormat; let unquoted = switches.split('"').step_by(2).collect::>().join(" "); let fmt = wordcraft_layout::fields::format_switch(&unquoted).filter(|f| *f != NumFormat::Decimal); - let covers = |fmt: NumFormat| match fmt { + match fmt { + Some(fmt) if number_format_covers(fmt, value) => wordcraft_layout::fields::field_number(switches, fmt, value as u32), + _ => wordcraft_layout::fields::apply_case_switch(switches, text), + } +} + +/// Whether the `\*` number format `fmt` writes `value`: a whole number up to 999,999 for CardText +/// and OrdText, 1 to 50,000 for roman, 1 to 780 for alphabetic, and 0 to `u32::MAX` otherwise. +pub(crate) fn number_format_covers(fmt: wordcraft_doc::section::NumFormat, value: f64) -> bool { + use wordcraft_doc::section::NumFormat; + let range = match fmt { NumFormat::CardinalText | NumFormat::OrdinalText => 0.0..=999_999.0, NumFormat::LowerRoman | NumFormat::UpperRoman => 1.0..=50_000.0, NumFormat::LowerLetter | NumFormat::UpperLetter => 1.0..=780.0, _ => 0.0..=f64::from(u32::MAX), }; - match fmt { - Some(fmt) if value.fract() == 0.0 && covers(fmt).contains(&value) => wordcraft_layout::fields::field_number(switches, fmt, value as u32), - _ => wordcraft_layout::fields::apply_case_switch(switches, text), - } + value.fract() == 0.0 && range.contains(&value) } /// Splits a field's switches (`\#` and `\*`) off its formula: `=SUM(ABOVE) \# "0.00"` gives the