From 1efd80e94e7b79657faef6e8edc6f99e29c44a53 Mon Sep 17 00:00:00 2001 From: mtlcl410 <2368091+mtlcl410@users.noreply.github.com> Date: Sun, 25 Oct 2026 01:09:09 +0800 Subject: [PATCH] M6: form_fields names a check box's options by export only when each export ticks its own state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit form_fill reads a name as an on state before an /Opt export, so an export that equals another kid's on state would tick that kid instead (ISO 32000-2 §12.7.5.2.3, Table 230). Fall back to the on-state names then. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC --- crates/automation/src/forms.rs | 9 +++++++-- crates/automation/tests/it/automation.rs | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/automation/src/forms.rs b/crates/automation/src/forms.rs index 3e15c2f4e3..c9f758de31 100644 --- a/crates/automation/src/forms.rs +++ b/crates/automation/src/forms.rs @@ -299,13 +299,18 @@ impl Automation { // which one is ticked, and the names form_fill takes to tick each. One option // per distinct on state: kids sharing a state tick together (EXAMPLE 2), so // their `/Opt` exports are not separate choices. Exports name the options - // unless two states share one (Table 230 allows it); then the states do. + // unless two states share one (Table 230 allows it) or an export would tick + // another state (form_fill reads a name as an on state first); then the + // states do. FormFieldKind::CheckBox => { let mut seen = std::collections::HashSet::new(); let states: Vec<&str> = f.widgets.iter().filter_map(|w| w.on_state.as_deref()).filter(|s| seen.insert(*s)).collect(); if states.len() > 1 { let mut exports = std::collections::HashSet::new(); - let by_export = states.iter().all(|s| exports.insert(f.export_for_state(s))); + let by_export = states.iter().all(|s| { + let e = f.export_for_state(s); + exports.insert(e) && f.state_for(e) == Some(*s) + }); let name = |s: &str| if by_export { f.export_for_state(s).to_owned() } else { s.to_owned() }; // The ticked kid is the one whose `/AS` is its on state: §12.7.5.2.3 // makes `/AS` decide where it and `/V` differ. diff --git a/crates/automation/tests/it/automation.rs b/crates/automation/tests/it/automation.rs index 3f095e2f3b..34bcf7bc20 100644 --- a/crates/automation/tests/it/automation.rs +++ b/crates/automation/tests/it/automation.rs @@ -2699,7 +2699,11 @@ fn form_fields_lists_a_check_box_by_on_state_and_reports_the_kid_that_is_on() { // Two on states with one export: the states name the options. let same = listed("check-box-same-export", "<< /FT /Btn /T (pair) /V /B /Opt [(x) (x)] /Kids [6 0 R 7 0 R] >>", ("A", "Off"), ("B", "B")); assert_eq!(same, (json!(["A", "B"]), json!("B"))); + // B's export is A's on state, so naming it would tick A: the states name the options. + let crossed = listed("check-box-crossed-export", "<< /FT /Btn /T (pair) /V /B /Opt [(x) (A)] /Kids [6 0 R 7 0 R] >>", ("A", "Off"), ("B", "B")); + assert_eq!(crossed, (json!(["A", "B"]), json!("B"))); } + /// A form whose fields are only page widgets (the `/Fields` list is empty) still lists, fills and /// resets through the tools, and the leniency shows up as a repair note. #[test]