diff --git a/ROADMAP.md b/ROADMAP.md index 7dfc00e76e..80b3687175 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,5 +1,6 @@ # PdfCraft roadmap +> **Last reviewed:** 2026-10-31 | **Last updated:** 2026-10-31 | **Change:** patch (M6, PR #4166/#4088: integrate main f137a289 at 0.16.1; retain automatic-field Print suppression, explicit button/console printing and document-error provenance; 761 scoped tests and verification gates passed; 13-step CLI exercise and render inspection passed; existing warnings retained; estimates unchanged) > **Last reviewed:** 2026-11-09 | **Last updated:** 2026-11-09 | **Change:** patch (M10 #5130: preserve manual duplex defaults; validate DocumentDuplex review fix and CI formatting; estimates unchanged) > > **Last reviewed:** 2026-11-08 | **Last updated:** 2026-11-08 | **Change:** patch (M10 #6514: refresh craft-print to 7ee35a9f, PDF loader to 51934db3 and public GDI kept at 64e7afd4 after #605 merged; preserve crash fix and stream-test repairs; new-graph CI pending; estimates unchanged) @@ -39,6 +40,10 @@ > **Last reviewed:** 2026-10-15 · **Last updated:** 2026-10-15 · **Change:** minor (M1: refresh object-generation reference identity against current upstream; estimates unchanged) **Stage: alpha** · next: beta, ~25% (ready for real work 50% → 75%) and ~400–700 h away +> **Last reviewed:** 2026-10-29 | **Last updated:** 2026-10-29 | **Change:** minor (M6, PR #4166/#4088: preserve automatic-field Print suppression, explicit script printing and document-error provenance on upstream 0.15; retain the integrated craft-libs revision; estimates unchanged) +> +> +> **Last reviewed:** 2026-10-29 | **Last updated:** 2026-10-29 | **Change:** minor (M7 #2517: a narrowed then widened paragraph keeps the typed spaces the wrap consumed, after save and reopen; estimates unchanged) > **Last reviewed:** 2026-10-27 | **Last updated:** 2026-10-27 | **Change:** minor (M10: vector-print appearance transforms retain small coefficients; estimates unchanged) > **Last reviewed:** 2026-10-29 | **Last updated:** 2026-10-29 | **Change:** minor (M2: scans keep their strokes at screen size and are smoothed when zoomed; M7 #2517: a narrowed then widened paragraph keeps the typed spaces the wrap consumed, after save and reopen; estimates unchanged) > **Last reviewed:** 2026-10-28 | **Last updated:** 2026-10-28 | **Change:** minor (M10, #4119 partial: Enhance scanned file cleans scanned pages in place: background removal, edge shadow removal, despeckle; estimates unchanged) @@ -244,6 +249,9 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md ## Progress log - **2026-11-09 (M10, #5130):** Windows Printer default sends native two-sided bindings only when automatic duplex support is confirmed, preserving manual driver defaults (#5130/#5772); physical output remains unqualified. Both Windows submission paths preserve DLL guards and current spooler fixes. Review follow-up validates both duplex scopes through craft-print 9fe50c94 and corrects CI formatting. Estimates unchanged. +- **2026-10-31 (M6, PR #4166/#4088 integration):** Integrated main f137a289 (0.16.1), preserving upstream progress entries and automatic-field Print suppression with its console diagnostic. ShowPanel/SaveAs filtering, explicit button/console printing, field effects and document-error provenance remain intact, together with all three engine regressions and the headless automation regression. Manifests, lockfile and CI match incoming main. Windows x64 / Rust 1.95 validation: 761 engine/automation tests passed, zero failed and four existing ignored; formatting, scoped all-target Clippy with -D warnings, layers/assets/parity and the engine/automation WebAssembly check passed. A fresh CLI build and the 13-step synthetic form exercise passed; the inspected render shows Qty=4 and Total=8, and explicit button/console Print requests are retained. Main's 100 headless parity notices, five renderer WebAssembly warnings and three optional egui patch notices remain. Estimates unchanged. + +- **2026-10-29 (M6, PR #4166/#4088 integration):** Retained automatic-field Print suppression, ShowPanel/SaveAs filtering, explicit button/console printing and document-error provenance while integrating upstream 0.15. All three engine regressions and the headless regression remain in their current test modules, with matching parity evidence. Dependency manifests retain main's integrated craft-libs revision, which includes #366. Estimates unchanged. - **2026-10-27 (M10, annotation print precision):** Vector printing preserves small placement coefficients for existing form, comment and stamp appearances with large bounding boxes or matrices. Synthetic render and save/reopen regressions cover the change; estimates unchanged. @@ -340,6 +348,8 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md The regression checks two distinct appearances, inherited resources, print/reopen pixels and unchanged source data. Estimates unchanged. - **2026-10-21 (M3, window resize and move on Wayland):** With PdfCraft's own title bar on GNOME/Wayland, a second resize by a window edge, or a second move by the empty tab strip, works straight after the first; it waited for a click inside the window, because the compositor keeps the button's release from egui. A unit test covers the release given back; checked by hand in a test build. Estimates unchanged. +- **2026-10-22 (M6, #4088):** Automatic AcroForm Keystroke, Validate, Calculate and Format scripts cannot open the print dialog; ignored requests are reported in the JavaScript console. Explicit button and console printing remain available, including when they trigger automatic calculations. Engine and headless automation regressions cover the boundary. Estimates unchanged. + - **2026-10-24 (M5, move preview):** A comment being moved is drawn where it goes while it is dragged (a rectangle or oval showed only its blue frame): the comment is rendered alone once as the drag starts and laid over the page, which shows it where it was until the move is applied. Engine `Document::comment_layer`; synthetic engine and UI regressions. Estimates unchanged. - **2026-10-22 (M10, #3132):** Windows printing no longer swaps the letters of embedded TrueType subsets. A subset renumbers its glyphs (ReportLab's Arial space is glyph 11, the installed Arial's is 3), and the Windows print path drew those numbers with the installed font of the same name: `Revisión` printed as `Wj{nxn`, the bold `z` as `Ä`, on Brother queues and the Adobe PDF printer alike. The copy sent to the Windows spooler now names every embedded TrueType/OpenType program `PdfCraftPrint`, in its `name` table and in the PDF; glyphs, encodings and `/ToUnicode` are untouched and a synthetic test renders the page identically before and after. Not yet checked on a physical Windows printer. Estimates unchanged. - **2026-10-24 (M5, border shape):** Text boxes and Fill & Sign's typed text take an oval or a circle for their border (Comment Properties ▸ Border shape, Fill & Sign's toolbar): the text box keeps its place and the shape goes around it, the text centred, a circle snug around the text (its box no wider than the text needs); stored as /RD and /PCBorderShape and drawn in the appearance. comment_add / comment_edit / fill_sign_add `border_shape`. One new label in every catalog. Synthetic annot, UI and automation regressions. Estimates unchanged. @@ -836,6 +846,9 @@ Newest first: the date, what moved, and the new overall estimate. ## Revision history +**2026-10-31 · patch:** M6/PR #4166 integrated main f137a289 (0.16.1), preserving both roadmap histories. Automatic Keystroke, Validate, Calculate and Format scripts still suppress Print alongside ShowPanel/SaveAs; explicit button and console Print requests, field effects and document-error provenance are retained. The three engine regressions and headless automation regression remain in their current modules. Manifests, lockfile and CI match incoming main. Windows x64 / Rust 1.95 validation: 761 engine/automation tests passed, zero failed and four existing ignored; formatting, scoped all-target Clippy with -D warnings, layers/assets/parity and the engine/automation WebAssembly check passed. A fresh CLI build and the 13-step synthetic form exercise passed; the inspected render shows Qty=4 and Total=8, and explicit button/console Print requests are retained. Main's 100 headless parity notices, five renderer WebAssembly warnings and three optional egui patch notices remain. Estimates unchanged. + +**2026-10-29 · minor:** M6/PR #4166 refreshed against upstream 0.15. Automatic field scripts still suppress Print with a console diagnostic alongside ShowPanel/SaveAs filtering, and explicit button/console scripts retain their requests and document-error provenance. The three engine regressions, headless regression and parity evidence remain in the current modules. Dependency manifests retain main's integrated craft-libs revision, including #366. Estimates unchanged. **2026-10-27 · minor:** M10: vector printing retains small appearance placement coefficients. Synthetic rendering and save/reopen regressions cover the change. Estimates unchanged. diff --git a/contributors/people.toml b/contributors/people.toml index a22c884ccb..06c98f6464 100644 --- a/contributors/people.toml +++ b/contributors/people.toml @@ -191,3 +191,6 @@ real_name = "Helen Kwok" real_name = "Valentin Klein" display_name = "vangel76" sync_github_name = false + +[people.enriquemelzer-bit] +sync_github_name = true diff --git a/crates/automation/src/tests.rs b/crates/automation/src/tests.rs index 1b857b6c29..1f3a13cbf0 100644 --- a/crates/automation/src/tests.rs +++ b/crates/automation/src/tests.rs @@ -38,6 +38,29 @@ fn search_document(a: &mut Automation, pages: usize) -> DocId { a.session.open("synthetic.pdf", None, Arc::new(numbered_pages(pages)), None).unwrap() } +#[test] +fn form_fill_cannot_print_but_js_run_can() { + let mut a = Automation::new(); + let doc = search_document(&mut a, 1); + json(&mut a, "form_add_field", json!({ "doc": doc.0, "page": 1, "type": "text", "rect": [20, 20, 180, 42], "name": "Qty" })); + json( + &mut a, + "form_set_script", + json!({ "doc": doc.0, "field": "Qty", "event": "validate", "script": "this.print(); console.println('validated');" }), + ); + json(&mut a, "form_fill", json!({ "doc": doc.0, "values": { "Qty": "4" } })); + let output = a.session.take_js_output(doc); + assert!(output.requests.is_empty(), "{output:?}"); + assert!(output.console.contains(&"validated".to_string()), "{output:?}"); + assert!(output.console.iter().any(|line| line.contains("Print") && line.contains("ignored")), "{output:?}"); + assert!(output.errors.is_empty(), "{output:?}"); + let fields = json(&mut a, "form_fields", json!({ "doc": doc.0 })); + let qty = fields["fields"].as_array().unwrap().iter().find(|f| f["name"] == "Qty").unwrap(); + assert_eq!(qty["value"], "4"); + let explicit = json(&mut a, "js_run", json!({ "doc": doc.0, "script": "this.print();" })); + assert_eq!(explicit["requests"], json!([{ "print": true }])); +} + #[test] fn search_cache_stays_bounded_reextracts_and_follows_edits_and_close() { let mut a = Automation::new(); diff --git a/crates/engine/README.md b/crates/engine/README.md index 3962afc50d..25216ba46a 100644 --- a/crates/engine/README.md +++ b/crates/engine/README.md @@ -82,6 +82,11 @@ family (`sign`, `sign_with_timestamp`, `timestamp_document`, `embed_ltv`). | `js` | The bridge to `pdfcraft-js`. `forms` never depends on `js` directly; the engine injects it, so a build without JavaScript is possible | | `xfa`, `ocr`, `compare`, `export` | Façades over the L3/L4 crates of the same names | +AcroForm Keystroke, Validate, Calculate and Format scripts run automatically. Their Print, +ShowPanel and SaveAs requests are ignored with a JavaScript console diagnostic; field values, +alerts and other requests are retained. Explicit button and console scripts can still request +printing (#4088). + ## Not done here - **Rendering is still borrowed.** `pdfcraft-render` wraps `hayro` and inspects through `lopdf` diff --git a/crates/engine/src/js.rs b/crates/engine/src/js.rs index d2b698a370..1d46f97786 100644 --- a/crates/engine/src/js.rs +++ b/crates/engine/src/js.rs @@ -81,18 +81,21 @@ impl JsOutput { } /// Adds what a field's own script produced: its alerts, console lines, errors and requests, - /// except a request to show a side panel, open Save As, or open or save an attached file, - /// which is noted on the console and dropped. + /// except a request to print, show a side panel, open Save As, or open or save an attached + /// file, which is noted on the console and dropped. fn absorb(&mut self, o: &Outcome) { self.alert(o.alerts.iter().cloned()); self.say(o.console.iter().cloned()); // A field's own scripts (keystroke, format, validate, calculate) run unasked: only a - // button's may show or hide a side panel, open Save As, or open or save an attached file. + // button's or the console's may print, show or hide a side panel, open Save As, or open + // or save an attached file (#4088). // The warnings go through `say` like any other console line, so they count against the // line and byte caps instead of growing the console past them. let mut notes = Vec::new(); for r in &o.requests { match r { + Request::Print => notes + .push("Print requested by an automatic field script was ignored; use a button or the JavaScript console to print".to_string()), Request::ShowPanel(_) => notes.push("A field script asked to show a side panel; only a button may show a side panel".to_string()), Request::SaveAs => notes.push("A field script asked to save the document as a new file; only a button may open Save As".to_string()), Request::OpenAttachment { name, .. } => { diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index c18117ba42..15e868bd61 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -6753,6 +6753,90 @@ fn inactive_xfa_and_ordinary_pdfs_keep_document_helpers_and_alerts() { } } +#[test] +fn automatic_field_scripts_cannot_print() { + use pdfcraft_forms::{FieldEvent, Scripts}; + let cos = pdfcraft_cos::Document::open(Arc::new(scripted_form())).unwrap(); + let fields = pdfcraft_forms::fields(&cos); + let target = fields.iter().find(|f| f.name == "qty").unwrap(); + for event in [FieldEvent::Keystroke, FieldEvent::Validate, FieldEvent::Calculate, FieldEvent::Format] { + let mut runner = js::JsRunner::new(&cos, "order.pdf", false); + let result = runner.run( + event, + "console.println('before'); this.print(); app.execMenuItem('SaveAs'); app.execMenuItem('ShowHideFileAttachment'); \ + this.pageNum = 0; this.print(); app.alert('ok'); event.value = '4';", + target, + "2", + &fields, + ); + assert!(result.rc, "{event:?}: {result:?}"); + assert_eq!(result.value, "4"); + assert_eq!(runner.output.requests, [js::Request::GoToPage(0)], "{event:?}: {:?}", runner.output); + assert_eq!(runner.output.alerts, [js::Message::plain("ok")]); + assert!(runner.output.console.contains(&"before".to_string())); + assert!(runner.output.console.iter().any(|line| line.contains("Print") && line.contains("ignored")), "{:?}", runner.output); + assert_eq!(runner.output.console.iter().filter(|line| line.contains("only a button may")).count(), 2, "{:?}", runner.output); + assert!(runner.output.errors.is_empty()); + + runner.output = js::JsOutput::default(); + runner.run(event, "this.print(); throw new Error('broken script');", target, "2", &fields); + assert!(runner.output.requests.is_empty(), "{event:?}: {:?}", runner.output); + assert!(runner.output.errors.iter().any(|e| e.contains("broken script"))); + } +} + +#[test] +fn automatic_print_suppression_preserves_document_errors() { + use pdfcraft_forms::{FieldEvent, Scripts}; + let mut cos = pdfcraft_cos::Document::open(Arc::new(scripted_form())).unwrap(); + pdfcraft_forms::set_document_script(&mut cos, "failure", Some("throw new Error('document failure');")).unwrap(); + let fields = pdfcraft_forms::fields(&cos); + let target = fields.iter().find(|f| f.name == "qty").unwrap(); + let mut runner = js::JsRunner::new(&cos, "order.pdf", false); + for event in [FieldEvent::Keystroke, FieldEvent::Validate, FieldEvent::Calculate, FieldEvent::Format] { + runner.run(event, "this.print(); throw new Error('field failure');", target, "2", &fields); + } + let out = runner.output; + assert!(out.requests.is_empty(), "{out:?}"); + assert_eq!(out.console.iter().filter(|line| line.contains("Print") && line.contains("ignored")).count(), 4, "{out:?}"); + assert_eq!(out.doc_errors.len(), 1, "the document script's error is reported once: {out:?}"); + assert!(out.doc_errors[0].contains("document failure"), "{out:?}"); + assert_eq!(out.errors.iter().filter(|e| e.contains("document failure")).count(), 1, "{out:?}"); + assert_eq!(out.errors.iter().filter(|e| e.contains("field failure")).count(), 4, "each field script's error is retained: {out:?}"); +} + +#[test] +fn form_calculations_cannot_print_but_buttons_and_console_can() { + let mut s = Session::new().with_clock(|| 1_700_000_000); + let id = s.open("order.pdf", None, Arc::new(scripted_form()), None).unwrap(); + s.apply( + id, + Edit::SetFieldScript { + name: "total".into(), + event: "calculate".into(), + script: Some("this.print(); event.value = times(getField('price').value, getField('qty').value);".into()), + }, + ) + .unwrap(); + s.take_js_output(id); + s.apply(id, Edit::SetFieldValue { name: "qty".into(), value: FieldValue::Text("4".into()) }).unwrap(); + let out = s.take_js_output(id); + assert!(out.requests.is_empty(), "{out:?}"); + assert!(out.console.iter().any(|line| line.contains("Print") && line.contains("ignored")), "{out:?}"); + assert!(out.errors.is_empty(), "{out:?}"); + assert!(page_texts(&s, id)[0].contains("USD 10.00")); + + for (target, qty, formatted) in [(Some("go"), 6, "USD 15.00"), (None, 8, "USD 20.00")] { + let out = s.run_javascript(id, &format!("getField('qty').value = {qty}; this.print();"), target).unwrap(); + assert_eq!(out.requests, [js::Request::Print]); + let automatic = s.take_js_output(id); + assert!(automatic.requests.is_empty(), "{automatic:?}"); + assert!(automatic.console.iter().any(|line| line.contains("Print") && line.contains("ignored")), "{automatic:?}"); + assert!(automatic.errors.is_empty(), "{automatic:?}"); + assert!(page_texts(&s, id)[0].contains(formatted)); + } +} + #[test] fn edit_history_does_not_replay_field_script_side_effects() { let mut s = Session::new().with_clock(|| 1_700_000_000); diff --git a/docs/roadmap.md b/docs/roadmap.md index fa1b5e5c97..b2674a9776 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,5 +1,9 @@ # PdfCraft roadmap: milestones and what's next +> **Last reviewed:** 2026-10-31 | **Last updated:** 2026-10-31 | **Change:** patch (M6, PR #4166/#4088: integrate main f137a289 at 0.16.1; retain automatic-field Print suppression, explicit button/console printing and document-error provenance; 761 scoped tests and verification gates passed; 13-step CLI exercise and render inspection passed; existing warnings retained; estimates unchanged) +> **Last reviewed:** 2026-10-29 | **Last updated:** 2026-10-29 | **Change:** minor (M6, PR #4166/#4088: preserve automatic-field Print suppression, explicit script printing and document-error provenance on upstream 0.15; retain the integrated craft-libs revision; estimates unchanged) +> +> > **Last reviewed:** 2026-10-27 | **Last updated:** 2026-10-27 | **Change:** minor (M10: vector-print appearance transforms retain small coefficients; estimates unchanged) > **Last reviewed:** 2026-11-09 | **Last updated:** 2026-11-09 | **Change:** patch (M10 #5130: preserve manual duplex defaults; validate DocumentDuplex review fix and CI formatting; estimates unchanged) > @@ -48,6 +52,8 @@ > **Last reviewed:** 2026-10-23 | **Last updated:** 2026-10-23 | **Change:** minor (M10: table spans retain text from covered slots in Office exports; estimates unchanged) > **Last reviewed:** 2026-10-23 · **Last updated:** 2026-10-23 · **Change:** minor (M10: image export preserves soft-mask Decode opacity ranges; estimates unchanged) > **Last reviewed:** 2026-10-23 · **Last updated:** 2026-10-23 · **Change:** minor (M10: vector printing preserves existing page XObjects when adding annotation appearances; estimates unchanged) +> **Last reviewed:** 2026-10-22 · **Last updated:** 2026-10-22 · **Change:** minor (M6, #4088: automatic AcroForm field scripts cannot request printing; explicit button and console printing are retained; estimates unchanged) +> > > **Last reviewed:** 2026-10-23 · **Last updated:** 2026-10-23 · **Change:** minor (M2, PR #4172: CJK fallback in Combine thumbnails and figure previews; FangSong and subset-prefixed Founder family matching; estimates unchanged) > @@ -250,7 +256,7 @@ total matches the parity estimate. | M7 | Content editing (text, images, header/footer, watermark) | 250–500 | 20% | 100–180 | Done: header & footer, watermarks, backgrounds, Bates; added text and images that stay editable (move, resize, format, rotate, flip, crop, replace); links (Link tool, Link Properties, create from URLs, remove all). Done since: Edit text preserves Arabic/Hebrew `/ActualText` typing order, with font-independent regressions. Done since (#1116): editable text regions grouped across compatible words and subset fonts, one rich-text editor over a background with the source text removed, embedded sfnt faces reused as editor faces, and visible (clipped) bounds for cropped-image selection and resize. Exclusive crops now resize across nested states and content streams, with shared clips fixed.
Done since: #594 ordinary paragraph drags preserve overlapping source text; UI/engine regressions, native save/reopen, independent-reader checks and full CI passed in the Linux test environment. #1200 candidate: local GoToR links open in desktop tabs and through root-confined automation; appearance-only edits preserve existing actions. Named destinations remain unapplied and `/NewWindow` is a tab hint. #1316: replacing a line or paragraph keeps a neighbouring line whose glyph box overlaps when the baselines differ; a second copy on the same baseline is still replaced. Missing: editing existing text and images in place (the longest pole), image/PDF watermarks | | M7 | Content editing (text, images, header/footer, watermark) | 250–500 | 20% | 100–180 | Done: header & footer, watermarks, backgrounds, Bates; added text and images that stay editable (move, resize, format, rotate, flip, crop, replace); links (Link tool, Link Properties, create from URLs, remove all). #3212: Headers, Bates numbers, watermarks and added text stay visible after unfinished marked content, without revealing hidden content. Tracked editable text distinguishes repeated glyph spacing from word gaps, with synthetic paragraph/object regressions. Missing: editing existing text and images in place (the longest pole), watermark print/screen visibility and repeat | | M6 | Forms + JavaScript | 160–320 | 50% | 40–75 | Done: filling all field types with regenerated appearances; Prepare a form (every field type, multi-select/shared properties, move/resize/delete, Field Properties General/Appearance/Position/Options/Format/Validate/Calculate); Acrobat's AF format/keystroke/validate/calculate functions and simplified field notation run natively in Acrobat's event order; tab order; push-button actions; flatten; agent tools. Form data exchange (FDF, XFDF, XML, CSV, text) done; XFDF values retain text across XML comments and processing instructions.
Done since: JavaScript engine (boa, sandboxed) running custom keystroke/validate/calculate/format and button scripts, document JavaScripts, console. Missing: Actions tab, wider object model (annotations, layers, dialogs), XFA
Done since: Existing simple field-font encodings and Differences are respected when generating appearances; an unencodable value uses a separate Helvetica appearance font without changing the source font. Synthetic reopen regressions cover punctuation, subset fallback and multiline text. Exact font metrics/layout and broader Unicode fallback remain incomplete. | -| M6 | Forms + JavaScript | 160–320 | 50% | 40–75 | Done: filling all field types with regenerated appearances; Prepare a form (every field type, multi-select/shared properties, move/resize/delete, Field Properties General/Appearance/Position/Options/Format/Validate/Calculate); Acrobat's AF format/keystroke/validate/calculate functions and simplified field notation run natively in Acrobat's event order; tab order; push-button actions; flatten; agent tools. Form data exchange (FDF, XFDF, XML, CSV, text) done; XFDF values retain text across XML comments and processing instructions.
Done since: JavaScript engine (boa, sandboxed) running custom keystroke/validate/calculate/format and button scripts, document JavaScripts, console. Missing: Actions tab, wider object model (annotations, layers, dialogs), XFA
Done since: Tab and Shift-Tab wrapping to the same field or another widget of that field refresh the newly opened editor from the accepted value before Save. Synthetic UI regressions cover both directions, same-frame Save, validation refusal and undo/redo. Renaming a field renames it in the calculations that read it (field lists, notation, literal `getField` names), in one undo step, and says how many changed. Prepare a form ▸ Calculation order, a page of its own: each calculated field's formula and value, what is wrong (fields that calculate too early, loops, fields missing from `/CO`) and Fix order, Up/Down, Alt+↑/↓ and drag, and on the page each field's step and arrows to and from the field in focus (`form_calculation_order` headlessly). | +| M6 | Forms + JavaScript | 160–320 | 50% | 40–75 | Done: filling all field types with regenerated appearances; Prepare a form (every field type, multi-select/shared properties, move/resize/delete, Field Properties General/Appearance/Position/Options/Format/Validate/Calculate); Acrobat's AF format/keystroke/validate/calculate functions and simplified field notation run natively in Acrobat's event order; tab order; push-button actions; flatten; agent tools. Form data exchange (FDF, XFDF, XML, CSV, text) done; XFDF values retain text across XML comments and processing instructions.
Done since: JavaScript engine (boa, sandboxed) running custom keystroke/validate/calculate/format and button scripts, document JavaScripts, console. Missing: Actions tab, wider object model (annotations, layers, dialogs), XFA
Done since: Tab and Shift-Tab wrapping to the same field or another widget of that field refresh the newly opened editor from the accepted value before Save. Synthetic UI regressions cover both directions, same-frame Save, validation refusal and undo/redo. Renaming a field renames it in the calculations that read it (field lists, notation, literal `getField` names), in one undo step, and says how many changed. Prepare a form ▸ Calculation order, a page of its own: each calculated field's formula and value, what is wrong (fields that calculate too early, loops, fields missing from `/CO`) and Fix order, Up/Down, Alt+↑/↓ and drag, and on the page each field's step and arrows to and from the field in focus (`form_calculation_order` headlessly).
Done since (#4088): automatic AcroForm field scripts ignore Print with a console diagnostic; explicit button and console printing remain available. Integrated main f137a289 (0.16.1) on 2026-10-31; ShowPanel/SaveAs filtering, all three engine regressions, the headless regression, field effects and document-error provenance are retained. Windows x64 / Rust 1.95 validation: 761 engine/automation tests passed, zero failed and four existing ignored; formatting, scoped all-target Clippy with -D warnings, layers/assets/parity and the engine/automation WebAssembly check passed. A fresh CLI build and the 13-step synthetic form exercise passed; the inspected render shows Qty=4 and Total=8, and explicit button/console Print requests are retained. Main's 100 headless parity notices, five renderer WebAssembly warnings and three optional egui patch notices remain. Estimates unchanged. | | M7 | Content editing (text, images, header/footer, watermark) | 250–500 | 20% | 100–180 | Done: header & footer, watermarks, backgrounds, Bates; added text and images that stay editable (move, resize, format, rotate, flip, crop, replace); links (Link tool, Link Properties, create from URLs, remove all). Missing: editing existing text and images in place (the longest pole), image/PDF watermarks | | M8 | Security + redaction | 100–180 | 68% | 15–30 | Done: opening protected documents, permissions, Protect Using Password, Remove security; redaction (mark text/areas/pages, Search & Redact with patterns, apply removing glyphs, image pixels, vectors, XObject content, annotations and fields, verification, full rewrite on save); Remove hidden information and Sanitize.
Done since: redaction codes (U.S. FOIA and Privacy Act sets as overlay text, in Redaction properties and `redact_mark`). Redaction cleans indirect and form structure references, preserving surviving form tags. Redaction retires inherited resource dictionaries, so a parent `/Pages` can't keep a removed XObject reachable; unredacted pages are left untouched. Image redaction removes alternate images and unsupported masks only on affected images. Changed marked-content spans lose replacement descriptions; untouched spans keep theirs. Hidden-text cleanup retires unused named descriptions across inherited/shared page and form resources (64 focused tests and scoped Rust 1.96 Clippy/wasm checks pass; publication CI pending). Retained text/image-bearing or unreadable tiling-pattern cells now make Apply refuse atomically after a bounded check of rewritten resources; fully removed cells still apply, and cross-page retention is checked. The redaction audit counts a filled cover that hides half of any one word of a long line (#2662), applied or not. Phone-pattern redaction also matches labelled Italian geographic numbers with `+39` and a slash after the area code (#3205 follow-up); unlabelled international slash numbers remain unsupported. Missing: certificate security, custom redaction code sets and pattern locales, DCT re-encoding | | M9 | Signatures (PAdES, validation) | 160–280 | 60% | 40–70 | Done: new `sign` crate (DER, X.509, CMS, PKCS #12 on RustCrypto; aws-lc-rs for RSA private keys); PAdES B-B signing (visible, invisible, existing fields, certification with DocMDP); validation with trust store and changes-after-signing classification; self-signed digital IDs; Signatures panel, message bar, sign dialogs; agent tools. Checked with pdfsig and OpenSSL.
Done since: macOS Keychain and Windows Current User Personal store signing (software-backed CNG keys), certificate viewer, signed documents protected from rewrites, page validation overlays separate from original signature artwork, placed in the widget's bottom-right corner as it lies on the page, at a quarter of its shorter side and 48 screen points at most, so they annotate the artwork from a corner instead of covering the middle of it and turn with the page and the view (a field falling under 32 screen points, as a page does when zoomed out to fit, keeps its outline), snapshot tabs that reuse instead of multiplying, named-layer signature appearances redrawn for their real state (#79; display, the CLI's render, exports and printing, never saves; captions in the interface language, Chinese in English; the badge stays). Missing: timestamps (B-T), LTV (DSS, OCSP, CRL), FieldMDP, smart cards, PKCS #11 | @@ -303,6 +309,9 @@ M2 (renderer), M7 (content editing), M10 (OCR and Office), M11 (Preflight and st ## Revision history +**2026-10-31 · patch:** M6/PR #4166 integrated main f137a289 (0.16.1), preserving both roadmap histories. Automatic Keystroke, Validate, Calculate and Format scripts still suppress Print alongside ShowPanel/SaveAs; explicit button and console Print requests, field effects and document-error provenance are retained. The three engine regressions and headless automation regression remain in their current modules. Manifests, lockfile and CI match incoming main. Windows x64 / Rust 1.95 validation: 761 engine/automation tests passed, zero failed and four existing ignored; formatting, scoped all-target Clippy with -D warnings, layers/assets/parity and the engine/automation WebAssembly check passed. A fresh CLI build and the 13-step synthetic form exercise passed; the inspected render shows Qty=4 and Total=8, and explicit button/console Print requests are retained. Main's 100 headless parity notices, five renderer WebAssembly warnings and three optional egui patch notices remain. Estimates unchanged. + +**2026-10-29 · minor:** M6/PR #4166 refreshed against upstream 0.15. Automatic field scripts still suppress Print with a console diagnostic alongside ShowPanel/SaveAs filtering, and explicit button/console scripts retain their requests and document-error provenance. The three engine regressions, headless regression and parity evidence remain in the current modules. Dependency manifests retain main's integrated craft-libs revision, including #366. Estimates unchanged. **2026-10-27 · minor:** M10: vector printing retains small appearance placement coefficients. Synthetic rendering and save/reopen regressions cover the change. Estimates unchanged. diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml index 8902c1787b..c198e6f4e1 100644 --- a/parity/acrobat-features.toml +++ b/parity/acrobat-features.toml @@ -5411,8 +5411,8 @@ milestone = "M6" status = "shipped" commands = ["app.preferences"] tools = ["js_enabled"] -evidence = ["crates/js/src/tests.rs::the_sandbox_has_no_host_access", "crates/js/src/tests.rs::errors_and_runaway_scripts_are_contained", "crates/ui-egui/tests/it/javascript.rs::document_scripts_and_preferences", "crates/ui-egui/tests/it/document_links.rs::a_script_asks_first_and_only_once", "crates/ui-egui/tests/it/document_links.rs::a_script_cannot_open_other_kinds_of_address"] -notes = "Preferences \u25b8 JavaScript \u25b8 Enable Acrobat JavaScript (remembered); no host access; loop and recursion limits; app.launchURL asks before opening and only offers http, https and mailto addresses (#90, #91)." +evidence = ["crates/js/src/tests.rs::the_sandbox_has_no_host_access", "crates/js/src/tests.rs::errors_and_runaway_scripts_are_contained", "crates/ui-egui/tests/it/javascript.rs::document_scripts_and_preferences", "crates/ui-egui/tests/it/document_links.rs::a_script_asks_first_and_only_once", "crates/ui-egui/tests/it/document_links.rs::a_script_cannot_open_other_kinds_of_address", "crates/engine/src/tests.rs::automatic_field_scripts_cannot_print", "crates/engine/src/tests.rs::automatic_print_suppression_preserves_document_errors", "crates/engine/src/tests.rs::form_calculations_cannot_print_but_buttons_and_console_can", "crates/automation/src/tests.rs::form_fill_cannot_print_but_js_run_can"] +notes = "Preferences \u25b8 JavaScript \u25b8 Enable Acrobat JavaScript (remembered); no host access; loop and recursion limits; app.launchURL asks before opening and only offers http, https and mailto addresses (#90, #91). Automatic AcroForm Keystroke, Validate, Calculate and Format scripts cannot request printing; an ignored Print request is reported in the JavaScript console. Explicit button and console printing remain available (#4088). Integration with main f137a289 (0.16.1), validated on 2026-10-31 on Windows x64 / Rust 1.95: 761 engine/automation tests passed, 0 failed, 4 existing ignored; formatting, scoped all-target Clippy, layers/assets/parity, scoped engine/automation WebAssembly and a fresh CLI build passed. The 13-step synthetic CLI exercise preserved Qty=4, Total=8 and explicit button/console Print requests through save/render; the rendered values were inspected. Existing main warnings remain (100 headless parity notices, 5 renderer WebAssembly warnings, 3 optional egui patch notices). Native printer transport remains untested." [[feature]] id = "form.folder-level-js"