M6: form_fields names a check box's options by export only when each export ticks its own state

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
This commit is contained in:
mtlcl410
2026-10-31 13:59:31 +08:00
co-authored by Claude Opus 5.5
parent d16cbfb96b
commit 1efd80e94e
2 changed files with 11 additions and 2 deletions
+7 -2
View File
@@ -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.
+4
View File
@@ -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]