From e72147c210e4901e09201e523327a6c5ce39cba5 Mon Sep 17 00:00:00 2001
From: Eric Kaden <608341+EricKaden@users.noreply.github.com>
Date: Fri, 23 Oct 2026 19:12:42 -0400
Subject: [PATCH 1/5] M12: screen-only and print-only XFA objects are shown and
printed as the form says
---
crates/engine/src/tests.rs | 39 +++++++
crates/xfa/src/layout.rs | 109 ++++++++++++++++--
crates/xfa/src/model.rs | 45 ++++++++
crates/xfa/src/parse.rs | 23 ++++
crates/xfa/src/pdf.rs | 218 ++++++++++++++++++++++++++++++++++-
crates/xfa/src/tests.rs | 186 ++++++++++++++++++++++++++++++
parity/acrobat-features.toml | 4 +-
7 files changed, 612 insertions(+), 12 deletions(-)
diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs
index ed4e6efa1c..ce69daf5a0 100644
--- a/crates/engine/src/tests.rs
+++ b/crates/engine/src/tests.rs
@@ -2593,6 +2593,45 @@ fn printing_lays_out_sheets_that_render() {
assert_eq!(s4.print_pdf(id4, &print::Settings { pages: vec![0], ..Default::default() }), Err(EditError::NotPermitted("printing")));
}
+/// A dynamic XFA form's screen-only and print-only objects (`relevant`): on screen, the
+/// screen-only field and draw show and the print-only ones don't; printed, the screen-only field
+/// is left off and the print-only one is on paper. (Draws follow the layers' state on screen
+/// when printed until printing applies the layers' print usage; other viewers read it from the
+/// file.)
+#[test]
+fn xfa_screen_only_and_print_only_objects_show_and_print_where_the_form_says() {
+ let field = |name: &str, attrs: &str| {
+ format!(
+ r#"{name} value"#
+ )
+ };
+ let draw = |name: &str, attrs: &str| {
+ format!(r#"{name} text"#)
+ };
+ let xdp = format!(
+ r##"
+
+
+ {}{}{}{}
+"##,
+ field("screenField", r#"relevant="-print""#),
+ field("paperField", r#"presence="invisible" relevant="+print""#),
+ draw("screenDraw", r#"relevant="-print""#),
+ draw("paperDraw", r#"presence="invisible" relevant="+print""#),
+ );
+ let mut s = Session::new();
+ let id = s.open("form.pdf", None, Arc::new(pdfcraft_xfa::fixtures::shell(&xdp)), None).expect("opens");
+ let screen = page_texts(&s, id).join("\n");
+ assert!(screen.contains("screenField value") && screen.contains("screenDraw text"), "on screen: {screen}");
+ assert!(!screen.contains("paperField value") && !screen.contains("paperDraw text"), "on screen: {screen}");
+ let sheet = s.print_pdf(id, &print::Settings { pages: vec![0], ..Default::default() }).unwrap();
+ let mut out = Session::new();
+ let printed = out.open("print.pdf", None, Arc::new(sheet), None).unwrap();
+ let paper = page_texts(&out, printed).join("\n");
+ assert!(!paper.contains("screenField value"), "printed: {paper}");
+ assert!(paper.contains("paperField value"), "printed: {paper}");
+}
+
/// A one-page document whose red square is in a layer; `off` switches the layer off by default.
fn red_layer_pdf(off: bool) -> Vec {
let content = "/OC /L1 BDC 1 0 0 rg 0 0 100 100 re f EMC";
diff --git a/crates/xfa/src/layout.rs b/crates/xfa/src/layout.rs
index 23342168e7..c5f2449bd0 100644
--- a/crates/xfa/src/layout.rs
+++ b/crates/xfa/src/layout.rs
@@ -39,11 +39,30 @@ const PAGE_COUNT_MARK: char = '\u{E000}';
#[derive(Clone, Debug, PartialEq)]
pub enum Item {
- Fill { rect: Rect, color: [f64; 3] },
- Line { from: (f64, f64), to: (f64, f64), width: f64, color: [f64; 3], dashed: bool },
+ Fill {
+ rect: Rect,
+ color: [f64; 3],
+ },
+ Line {
+ from: (f64, f64),
+ to: (f64, f64),
+ width: f64,
+ color: [f64; 3],
+ dashed: bool,
+ },
Text(Span),
- Image { rect: Rect, data: Arc>, content_type: String },
+ Image {
+ rect: Rect,
+ data: Arc>,
+ content_type: String,
+ },
Widget(Box),
+ /// An item drawn only on screen or only in print (XFA `relevant`, `presence`): written in an
+ /// optional content group whose usage says so.
+ Only {
+ views: Views,
+ item: Box- ,
+ },
}
#[derive(Clone, Debug, PartialEq)]
@@ -126,6 +145,8 @@ pub struct Widget {
pub action: Option,
/// Check boxes: the on and off values of the data; radio buttons: the on value.
pub items: Vec,
+ /// Where the field is shown: on screen, in print, or both (its annotation flags).
+ pub views: Views,
}
#[derive(Clone, Debug, Default, PartialEq)]
@@ -211,6 +232,8 @@ struct Layouter<'t> {
/// The fields and draws of the block being placed: (first item, one past the last, top,
/// bottom) on the last page. A page break keeps each whole (see `split_block`).
leaves: Vec<(usize, usize, f64, f64)>,
+ /// Where what is being drawn shows, as the objects it is inside say ([`Common::views`]).
+ views: Views,
}
/// How many instances of a repeating subform to lay out: what the data holds once it holds
@@ -256,6 +279,7 @@ pub fn layout(tpl: &Template, data: Option<&DataNode>) -> Result