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.
This commit is contained in:
dotCooCoo
2026-10-24 19:52:32 -07:00
parent d9d61ee8da
commit 52c88567b8
2 changed files with 29 additions and 7 deletions
+17 -2
View File
@@ -1144,10 +1144,13 @@ fn new_property_value(instr: &str) -> Option<String> {
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<String> = 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() {
+12 -5
View File
@@ -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::<Vec<_>>().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