fix(forms): a whole look keeps the rest of an indirect /MK (#1322) (#1392)

set_props with a complete look read the widget's /MK without resolving
it, so an indirect one started from an empty dictionary: a check box
lost its Cross mark, its 90-degree rotation and any unknown entry, and
the loss survived a save. The /MK is now resolved first, as the
check_style branch already does; a direct /MK behaves as before.

Adds tests::a_whole_look_keeps_the_rest_of_an_indirect_mk, which fails
before this change.
This commit is contained in:
Marco Arnulfo
2026-10-12 07:19:42 -04:00
committed by GitHub
parent 0584425e67
commit 3e2877cbbb
2 changed files with 33 additions and 1 deletions
+2 -1
View File
@@ -1033,8 +1033,9 @@ pub fn set_props(doc: &mut Document, name: &str, props: &FieldProps) -> Result<S
if let Some(l) = &props.look {
let arr = |c: [f64; 3]| Object::Array(c.iter().map(|v| Object::Real(v.clamp(0.0, 1.0))).collect());
for w in &f.widgets {
// Resolved first: an indirect /MK keeps its mark, rotation and other entries.
let mut mk = doc.get(w.obj).as_dict().and_then(|d| d.get(b"MK").map(|m| doc.resolve(m).as_dict().cloned())).flatten().unwrap_or_default();
doc.update_dict(w.obj, |d| {
let mut mk = d.get(b"MK").and_then(|m| m.as_dict().cloned()).unwrap_or_default();
match l.border {
Some(c) => mk.set(b"BC".to_vec(), arr(c)),
None => {
+31
View File
@@ -1790,6 +1790,37 @@ fn partial_appearance_preserves_each_widgets_indirect_colours_and_border_extensi
}
}
/// A whole look sets the colours of an indirect /MK and keeps its mark, rotation and unknown
/// entries (#1322), as it already did for a direct one.
#[test]
fn a_whole_look_keeps_the_rest_of_an_indirect_mk() {
let mut doc = fixture();
let widget = field(&fields(&doc), "agree").widgets[0].obj;
let mut mk = pdfcraft_cos::Dict::new();
mk.set(b"CA".to_vec(), pdfcraft_cos::PdfString::text("8"));
mk.set(b"R".to_vec(), Object::Int(90));
mk.set(b"VendorKey".to_vec(), Object::Int(71));
let mk_ref = doc.add(Object::Dict(mk));
doc.update_dict(widget, |d| d.set(b"MK".to_vec(), Object::Ref(mk_ref))).unwrap();
let new = Look {
border: Some([1.0, 0.0, 0.0]),
fill: Some([1.0, 1.0, 0.8]),
width: 2.0,
style: BorderStyle::Solid,
text: [0.0; 3],
font: FieldFont::Helvetica,
};
set_props(&mut doc, "agree", &FieldProps { look: Some(new), ..FieldProps::default() }).unwrap();
let doc = reopen(&doc);
let f = field(&fields(&doc), "agree").clone();
assert_eq!((check_style(&doc, &f), f.widgets[0].rotation), (CheckStyle::Cross, 90));
let mk = doc.get(widget).as_dict().unwrap().get(b"MK").map(|v| doc.resolve(v)).unwrap();
let mk = mk.as_dict().unwrap();
assert_eq!(mk.int(b"VendorKey"), Some(71));
assert_eq!(look(&doc, &f).border, Some([1.0, 0.0, 0.0]));
assert_eq!(look(&doc, &f).fill, Some([1.0, 1.0, 0.8]));
}
#[test]
fn invalid_appearance_patches_are_refused_before_anything_is_written() {
let mut doc = fixture();