diff --git a/crates/pdf/src/export.rs b/crates/pdf/src/export.rs index 480c1eee9..30802d8ab 100644 --- a/crates/pdf/src/export.rs +++ b/crates/pdf/src/export.rs @@ -244,12 +244,16 @@ pub fn export_pdf_with_report(doc: &Document, cache: &Cache, opts: &PdfOptions) // group that got past it, with its page. crate::pdfx::cmyk_group_spaces(&mut bytes); } - // Form fields (Buttons and Forms) and, for interactive PDF, video and sound; not in PDF/X. + // Form fields (Buttons and Forms) and, for interactive PDF, video and sound; not in PDF/X + // or PDF/A: this update comes after krilla's validator ran, and its NeedAppearances fields and + // Screen annotations aren't allowed in PDF/A. let fields = crate::forms::collect(doc, &sheets); let media = if opts.media { crate::forms::collect_media(doc, &sheets) } else { Vec::new() }; if !fields.is_empty() || !media.is_empty() { if opts.standard == Standard::PdfX4 { warnings.push("PDF/X-4: form fields and media were left out".into()); + } else if opts.standard == Standard::PdfA2b { + warnings.push("PDF/A-2b: form fields and media were left out".into()); } else { match crate::forms::add_fields(&bytes, &fields, &media) { Some(b) => bytes = b, diff --git a/crates/pdf/src/tests.rs b/crates/pdf/src/tests.rs index c2318dc3b..aa5b3a5f3 100644 --- a/crates/pdf/src/tests.rs +++ b/crates/pdf/src/tests.rs @@ -478,6 +478,32 @@ fn stroke_behind_fill_strokes_the_run_before_filling_it() { assert_eq!(text.trim(), "I", "the text is not duplicated"); } +#[test] +fn pdfa_leaves_out_form_fields_added_after_validation() { + let mut d = doc_with_text("Archive me"); + let lid = d.default_layer(); + let id = ItemId(d.alloc()); + let mut b = Item::new(id, lid, designcraft_doc::Shape::Rectangle, designcraft_geom::shapes::rectangle(Rect::new(36.0, 400.0, 200.0, 430.0))); + b.form_field = Some(designcraft_doc::FormField { + kind: designcraft_doc::FieldKind::TextField, + name: "name".into(), + value: String::new(), + options: Vec::new(), + multiline: false, + required: false, + font_size: 0.0, + }); + d.insert_item(SpreadRef::Doc(0), b, None).unwrap(); + let plain = export_pdf(&d, &Cache::new(), &PdfOptions::default()).unwrap(); + assert!(String::from_utf8_lossy(&plain).contains("/AcroForm"), "control: a plain export has the field"); + let opts = PdfOptions { standard: Standard::PdfA2b, created: Some(1_700_000_000), ..Default::default() }; + let a = export_pdf_with_report(&d, &Cache::new(), &opts).unwrap(); + let text = String::from_utf8_lossy(&a.bytes).to_string(); + assert!(text.contains("pdfaid")); + assert!(!text.contains("/AcroForm") && !text.contains("NeedAppearances"), "PDF/A must not get the form update"); + assert!(a.warnings.iter().any(|w| w.contains("PDF/A-2b") && w.contains("left out")), "{:?}", a.warnings); +} + #[test] fn missing_glyphs_print_as_boxes() { // Source Serif 4 lacks 語: its .notdef box is drawn as a path, not as a .notdef glyph diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 9120b7d8f..118fbddbf 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -365,7 +365,7 @@ It is evidence of breadth: each item exists. It is not a claim of InDesign-level - Cell strokes: with cells selected (or the cursor in a cell) the Stroke panel edits the chosen cell edges, picked on a drawn edge proxy (top, bottom, left, right, inner horizontal, inner vertical); Table › Cell Options › Strokes and Fills… does the same with the cell fill. Weight, every stroke type, colour, tint, gap colour and gap tint draw on screen and in PDF and round-trip through IDML (`table.setCell` `stroke.edges`, `table.getCellStroke`). - Distribute Rows Evenly (`table.distributeRows`, Table menu and panel): selected rows receive equal fixed heights preserving their composed total height, with undo/redo. Row-spanning merges and rows outside the composed frames are rejected; shrinking tall cells can overset their text. - Object States (`states.create/show/rename/release/list`, Object States panel): multi-state objects show one state on screen and in PDF. -- Buttons (`button.set/clear/list`, Buttons and Forms panel): go to page / next / previous / first / last / URL as PDF link annotations. Form fields too (`form.set/clear/list`: text fields, check boxes, combo and list boxes, signature fields; required, multiline, defaults) exported as AcroForm widgets through an incremental update (not in PDF/X). +- Buttons (`button.set/clear/list`, Buttons and Forms panel): go to page / next / previous / first / last / URL as PDF link annotations. Form fields too (`form.set/clear/list`: text fields, check boxes, combo and list boxes, signature fields; required, multiline, defaults) exported as AcroForm widgets through an incremental update (not in PDF/X or PDF/A). - Power Zoom (Hand tool: press and hold still for half a second, or Alt-press): zoom out to the spread, aim the red view rectangle, release to zoom back in there. - Graphic cells (`table.placeGraphic`, `table.textCell`, Table › Convert Cell Type, Table panel): images in table cells, fitted or filled, clipped to the cell on screen and in PDF. - Live Distribute (Space while dragging a selection handle; `transform.resize {distribute}`): objects keep their size and spread with the bounds.