From 77241397d6d803005c25e13d83662ff8dd9cdbfc Mon Sep 17 00:00:00 2001 From: Nigel Ihza Firdausy <106657905+Ni-zav@users.noreply.github.com> Date: Sun, 11 Oct 2026 23:32:17 +0700 Subject: [PATCH] fix(xfdf): combine imported annotation flags with bitwise OR (#931) * fix(xfdf): combine annotation flags as a bitmask * Run cargo fmt --------- Co-authored-by: Brandon Thomas --- crates/xfdf/src/lib.rs | 2 +- crates/xfdf/src/tests.rs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/crates/xfdf/src/lib.rs b/crates/xfdf/src/lib.rs index 91c3d24548..e0dfdf9c18 100644 --- a/crates/xfdf/src/lib.rs +++ b/crates/xfdf/src/lib.rs @@ -590,7 +590,7 @@ fn annot_from_xml(node: roxmltree::Node, subtype: &str, page_ref: ObjRef) -> Opt .unwrap_or("print") .split(',') .filter_map(|f| FLAGS.iter().find(|(_, s)| s.eq_ignore_ascii_case(f.trim())).map(|x| x.0)) - .sum(); + .fold(0, |bits, flag| bits | flag); d.set(b"F".to_vec(), Object::Int(flags)); if let Some(c) = node.attribute("color").and_then(parse_hex) { d.set(b"C".to_vec(), arr(&c)); diff --git a/crates/xfdf/src/tests.rs b/crates/xfdf/src/tests.rs index 33b3a5a883..3e5635a9a8 100644 --- a/crates/xfdf/src/tests.rs +++ b/crates/xfdf/src/tests.rs @@ -445,3 +445,20 @@ fn xfdf_keeps_callout_leader_inset_and_ending() { assert_eq!(want.1.len(), 6, "{want:?}"); assert_eq!(callout_keys(&dst), want, "{xfdf}"); } + +#[test] +fn duplicate_xfdf_annotation_flags_do_not_turn_on_unrelated_bits() { + // Flags are a set of bit masks, not additive integers. A repeated "print" flag + // must not carry into "nozoom", nor repeated "readonly" into "locked". + for (flags, expected) in [("print, PRINT, nozoom", 12), ("readonly,readonly", 64), ("print,print,print", 4)] { + let xfdf = format!( + r#""# + ); + let mut doc = blank(); + assert_eq!(import(&mut doc, xfdf.as_bytes()).unwrap().comments, 1); + let page = pdfcraft_model::pages(&doc)[0].obj; + let annotations = doc.get(page).as_dict().and_then(|p| p.get(b"Annots")).map(|a| doc.resolve(a)).and_then(|a| a.as_array().cloned()).unwrap(); + let square = annotations.iter().filter_map(|a| doc.resolve(a).as_dict().cloned()).find(|d| d.name(b"Subtype") == Some(b"Square")).unwrap(); + assert_eq!(square.get(b"F").and_then(Object::as_int), Some(expected), "flags={flags}"); + } +}