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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
This commit is contained in:
KenM76
2026-10-27 22:53:55 -04:00
co-authored by Claude Opus 5.5
parent 969cad099d
commit 5716a95f8d
2 changed files with 27 additions and 3 deletions
+12 -3
View File
@@ -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<usize>) -> 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<usize> = 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)
}
+15
View File
@@ -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::<Vec<_>>(), [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]