fix(engine): Keep section prefixes to InDesign's rules

A section prefix is at most eight characters and contains no plus or
comma (page references and ranges use them); `layout.section` refuses
others and leaves the section unchanged. Symbols, a footnote style,
no longer number pages. The parameter documentation lists the 01, 001
and 0001 styles, and a test pins the fields the generated dialogs read
from both numbering commands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
moppeeer
2026-10-10 15:39:38 +02:00
co-authored by Claude Opus 5.5
parent f061be9b3c
commit 498e3d401c
2 changed files with 32 additions and 2 deletions
+25 -2
View File
@@ -385,7 +385,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Numbering & Section Options…",
["Layout"],
None,
"{page (1-based; starts a section there), startNumber?: n|null (continue), style?: arabic|upperRoman|lowerRoman|upperLetters|lowerLetters, prefix?, includePrefix?, marker?, remove?: bool}",
"{page (1-based; starts a section there), startNumber?: n|null (continue), style?: arabic|upperRoman|lowerRoman|upperLetters|lowerLetters|arabicLeadingZero|arabicThreeDigits|arabicFourDigits, prefix? (up to 8 characters; no + or comma), includePrefix?, marker?, remove?: bool}",
has_doc,
|s, p| {
let page = p.get("page").and_then(Value::as_u64).ok_or_else(|| bad("layout.section", "missing page"))? as usize;
@@ -394,6 +394,11 @@ pub fn specs() -> Vec<CommandSpec> {
return Err(bad("layout.section", format!("no page {page}")));
}
let start = page - 1;
if let Some(prefix) = p.get("prefix").and_then(Value::as_str)
&& (prefix.chars().count() > 8 || prefix.contains(['+', ',']))
{
return Err(bad("layout.section", "section prefix: up to 8 characters, without + or ,"));
}
let p = p.clone();
s.edit(|d, _| {
d.sections.retain(|x| x.start != start || start == 0);
@@ -414,7 +419,11 @@ pub fn specs() -> Vec<CommandSpec> {
None => {}
}
if let Some(v) = p.get("style") {
sec.style = serde_json::from_value(v.clone()).map_err(|e| bad("layout.section", e.to_string()))?;
sec.style = match serde_json::from_value(v.clone()) {
Ok(designcraft_doc::NumberStyle::Symbols) => return Err(bad("layout.section", "symbols don't number pages")),
Ok(style) => style,
Err(e) => return Err(bad("layout.section", e.to_string())),
};
}
if let Some(v) = p.get("prefix").and_then(Value::as_str) {
sec.prefix = v.into();
@@ -986,6 +995,20 @@ mod page_numbering_view_tests {
assert_eq!(s.resolve_page("1"), Some(0));
assert_eq!(s.resolve_page("9"), None);
}
#[test]
fn section_prefixes_are_short_and_avoid_plus_and_comma() {
let mut s = crate::Session::new();
s.execute("file.new", &json!({"pages": 2})).unwrap();
for prefix in ["A-", "Appendix", "§ 1"] {
s.execute("layout.section", &json!({"page": 2, "prefix": prefix})).unwrap();
}
for prefix in ["Appendix1", "A+", "1,2"] {
assert!(s.execute("layout.section", &json!({"page": 2, "prefix": prefix})).is_err(), "{prefix}");
}
assert!(s.execute("layout.section", &json!({"page": 2, "style": "symbols"})).is_err());
assert_eq!(s.doc().unwrap().doc.sections.iter().find(|x| x.start == 1).unwrap().prefix, "§ 1", "a refused prefix changes nothing");
}
}
#[cfg(test)]
+7
View File
@@ -4104,6 +4104,13 @@ mod tests {
}
}
#[test]
fn numbering_commands_document_their_fields() {
let names = |id: &str| command_fields(designcraft_engine::find_command(id).unwrap().params).into_iter().map(|f| f.key).collect::<Vec<_>>();
assert_eq!(names("layout.section"), ["page", "startNumber", "style", "prefix", "includePrefix", "marker", "remove"]);
assert_eq!(names("layout.chapterNumbering"), ["style", "start", "source"]);
}
#[test]
fn generic_command_preserves_array_geometry() {
let mut app = command_app("file.new", json!({}));