From 5716a95f8da3f8ab70ba36f8077dd42abd53e314 Mon Sep 17 00:00:00 2001 From: KenM76 Date: Tue, 27 Oct 2026 22:53:55 -0400 Subject: [PATCH] C010: joining a group redraws only the buttons that moved redraw_field after the join redrew every button of the target group, so a button with its own appearance states lost them to the default marks. Only the moved buttons are now redrawn under the group's name. Test: joining_a_group_leaves_its_own_buttons_appearances_as_they_are (fails before: the group's own button gets new /AP streams). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9 --- crates/forms/src/author/edit.rs | 15 ++++++++++++--- crates/forms/src/tests.rs | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/crates/forms/src/author/edit.rs b/crates/forms/src/author/edit.rs index 0ffb8eb3e0..dcb306e4a2 100644 --- a/crates/forms/src/author/edit.rs +++ b/crates/forms/src/author/edit.rs @@ -1,5 +1,5 @@ use super::join::{check_join, join_radio_group}; -use super::look::{BorderStyle, da_string, patch_da, patch_widget_look, redraw_field, redraw_widgets}; +use super::look::{BorderStyle, da_string, patch_da, patch_widget_look, redraw_widgets}; use super::tree::{attach, check_place, child_named, clash, detach, ensure_form, inherited, name_parts, own_inherited}; use super::{Dict, Document, Field, FieldKind, FieldProps, FormError, MIN_FIELD_SIDE, ObjRef, Object, PdfString, fields, flags, invalid}; @@ -376,12 +376,21 @@ fn apply_widget_style_props(doc: &mut Document, f: &Field, name: &str, props: &F } /// Redraw the edited widgets. A radio button joining another group (`join`) is redrawn under its -/// old name `name` first, then its buttons move and the whole group is redrawn as `new_name`. +/// old name `name` first; then its buttons move, and only they are redrawn as `new_name`, so the +/// group's own buttons keep their appearances. fn redraw_or_join(doc: &mut Document, f: &Field, name: &str, new_name: &str, join: Option<&Field>, only: Option) -> Result<(), FormError> { if let Some(group) = join { redraw_widgets(doc, name, only)?; join_radio_group(doc, f, group)?; - redraw_field(doc, new_name) + let moved: Vec = fields(doc) + .into_iter() + .find(|g| g.name == new_name) + .map(|g| g.widgets.iter().enumerate().filter(|(_, w)| f.widgets.iter().any(|m| m.obj == w.obj)).map(|(i, _)| i).collect()) + .unwrap_or_default(); + for index in moved { + redraw_widgets(doc, new_name, Some(index))?; + } + Ok(()) } else { redraw_widgets(doc, new_name, only) } diff --git a/crates/forms/src/tests.rs b/crates/forms/src/tests.rs index 77d8c62648..62427f1259 100644 --- a/crates/forms/src/tests.rs +++ b/crates/forms/src/tests.rs @@ -4663,6 +4663,21 @@ fn a_radio_button_renamed_to_a_groups_name_joins_the_group() { assert_eq!(group.widgets.iter().map(|w| w.state.clone()).collect::>(), [Some("Off".into()), Some("No".into())]); } +/// Joining redraws only the buttons that moved: the group's own keep their appearances, which may +/// be custom ones a redraw would replace. +#[test] +fn joining_a_group_leaves_its_own_buttons_appearances_as_they_are() { + let mut doc = two_radio_groups("Yes", "No"); + let own = field(&fields(&doc), "Group1").widgets[0].obj; + let look = |doc: &Document| doc.get(own).as_dict().and_then(|d| d.get(b"AP").cloned()); + let before = look(&doc); + assert!(before.is_some()); + rename_to(&mut doc, "Group2", "Group1").unwrap(); + assert_eq!(look(&doc), before); + let group = field(&fields(&doc), "Group1").clone(); + assert!(group.widgets[1].on_state.as_deref() == Some("No"), "the moved button is drawn with its states"); +} + /// A joining button whose export value a button of the group already has takes the next free /// "ChoiceN", in its appearance states too, so the two can be chosen apart. #[test]