From 280f2439ec34dd82f6feb8416a2fc0898c14261f Mon Sep 17 00:00:00 2001 From: vangel76 Date: Wed, 14 Oct 2026 23:03:14 +0200 Subject: [PATCH 1/3] Preferences: field highlights on by default, with a toggle in Appearance (#2273) New settings start with Highlight fields on. A stored choice is kept; a missing or malformed value falls back to on. Preferences > Appearance gets the checkbox. Co-Authored-By: Claude Sonnet 5.5 --- crates/ui-egui/src/appearance.rs | 9 ++++++++ crates/ui-egui/src/canvas.rs | 4 ++-- crates/ui-egui/tests/view.rs | 38 +++++++++++++++++++++++++------- parity/acrobat-features.toml | 4 ++-- 4 files changed, 43 insertions(+), 12 deletions(-) diff --git a/crates/ui-egui/src/appearance.rs b/crates/ui-egui/src/appearance.rs index 0d607cc91..536d8e4d9 100644 --- a/crates/ui-egui/src/appearance.rs +++ b/crates/ui-egui/src/appearance.rs @@ -139,6 +139,9 @@ pub fn preferences_section(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) app.execute(mode_command(ThemePreference::Dark)); } }); + ui.add_space(4.0); + // Acrobat's "Highlight existing fields": shared with the toggle in the form bar. + ui.add(crate::widgets::checkbox(&mut app.view_defaults.highlight_fields, tl!("Highlight fields"))); } #[cfg(test)] @@ -176,6 +179,12 @@ mod tests { h.render().unwrap().save(path).unwrap(); } + // The field-highlight preference is on for new settings, and the box turns it off. + assert!(h.state().view_defaults.highlight_fields); + h.get_by_label("Highlight fields").click(); + h.run(); + assert!(!h.state().view_defaults.highlight_fields); + let combo = egui::accesskit::Role::ComboBox; h.get_by_role(combo).click(); h.run(); diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index eb9244a28..bd408f401 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -174,14 +174,14 @@ pub struct ViewDefaults { /// The zoom with [`Fit::None`] (1.0 = 100%). pub zoom: f32, /// Highlight existing fields: a preference in Acrobat, so it carries over to the next - /// document and the next session. + /// document and the next session. On for new settings; a stored choice wins. pub highlight_fields: bool, } impl Default for ViewDefaults { fn default() -> Self { // Single page, scrolling continuously at fit width: the earlier "continuous" default. - Self { layout: PageLayout::Single, continuous: true, fit: Fit::Width, zoom: 1.0, highlight_fields: false } + Self { layout: PageLayout::Single, continuous: true, fit: Fit::Width, zoom: 1.0, highlight_fields: true } } } diff --git a/crates/ui-egui/tests/view.rs b/crates/ui-egui/tests/view.rs index 9cf99021a..713702b13 100644 --- a/crates/ui-egui/tests/view.rs +++ b/crates/ui-egui/tests/view.rs @@ -1059,6 +1059,8 @@ fn highlight_fields_tints_the_field_area() { let _gpu = gpu(); let mut h = form_harness(); h.state_mut().set_option("panel", "none").unwrap(); + // Highlighting is on by default; start from plain fields. + h.state_mut().set_option("fields", "off").unwrap(); for _ in 0..100 { h.run_steps(2); if !h.state().render_pending() { @@ -1136,27 +1138,47 @@ fn required_radio_buttons_get_a_round_red_border() { assert!(!stroke(at(50.3, 339.7)), "the widget's corner, outside the circle, is not: {:?}", at(50.3, 339.7)); } -/// Acrobat's Highlight existing fields is a preference, not a per-document choice: turned on -/// once, the next document (and the next session) opens with fields highlighted. +/// Acrobat's Highlight existing fields is a preference, not a per-document choice: turned off +/// once, the next document (and the next session) opens with fields plain. New settings start +/// with it on. #[test] -fn field_highlighting_is_remembered() { +fn field_highlighting_is_on_by_default_and_remembered() { use egui_kittest::kittest::Queryable; let mut h = form_harness(); h.state_mut().set_option("panel", "none").unwrap(); h.run_steps(4); - assert!(!h.state().views[0].highlight_fields, "off by default"); - h.get_by_label("Highlight fields").click(); + assert!(h.state().views[0].highlight_fields, "on by default"); + h.get_by_label("Hide field highlights").click(); h.run_steps(4); - assert!(h.state().views[0].highlight_fields); + assert!(!h.state().views[0].highlight_fields); h.state_mut().open_bytes("radios.pdf", None, RADIOS.to_vec()).expect("opens"); h.run_steps(4); - assert!(h.state().views[1].highlight_fields, "the next document opens highlighted"); + assert!(!h.state().views[1].highlight_fields, "the next document opens plain"); let saved = h.state().persist(); let mut next = PdfCraftApp::new(); next.set_option("language", "en").unwrap(); next.restore(&saved); next.open_bytes("form.pdf", None, FORM.to_vec()).expect("opens"); - assert!(next.views[0].highlight_fields, "and so does the next session"); + assert!(!next.views[0].highlight_fields, "and so does the next session"); +} + +/// Settings saved before the default changed keep their stored choice; a missing or malformed +/// value falls back to the default instead of failing. +#[test] +fn stored_field_highlight_choice_wins_over_the_default() { + for (stored, expected) in [ + (r#"{"highlight_fields": false}"#, false), + (r#"{"highlight_fields": true}"#, true), + (r#"{}"#, true), + (r#"{"highlight_fields": "no"}"#, true), + (r#"{"highlight_fields": null}"#, true), + ] { + let mut app = PdfCraftApp::new(); + app.set_option("language", "en").unwrap(); + app.restore(stored); + app.open_bytes("form.pdf", None, FORM.to_vec()).expect("opens"); + assert_eq!(app.views[0].highlight_fields, expected, "{stored}"); + } } #[test] diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml index 7aca7d5c8..7d95c7098 100644 --- a/parity/acrobat-features.toml +++ b/parity/acrobat-features.toml @@ -4392,8 +4392,8 @@ milestone = "M3" status = "shipped" commands = [] tools = [] -evidence = ["crates/ui-egui/tests/view.rs::highlight_fields_tints_the_field_area", "crates/ui-egui/tests/view.rs::required_radio_buttons_get_a_round_red_border", "crates/ui-egui/tests/view.rs::field_highlighting_is_remembered", "crates/ui-egui/tests/editing.rs::a_secured_form_shows_its_form_notice_and_its_security"] -notes = "offered on secured forms too; remembered for the next document and session; required fields get a red border (round for radio buttons); the tint is drawn over the field, where Acrobat paints the highlight colour behind it" +evidence = ["crates/ui-egui/tests/view.rs::highlight_fields_tints_the_field_area", "crates/ui-egui/tests/view.rs::required_radio_buttons_get_a_round_red_border", "crates/ui-egui/tests/view.rs::field_highlighting_is_on_by_default_and_remembered", "crates/ui-egui/tests/view.rs::stored_field_highlight_choice_wins_over_the_default", "crates/ui-egui/tests/editing.rs::a_secured_form_shows_its_form_notice_and_its_security"] +notes = "offered on secured forms too; on by default for new settings (a stored choice is kept) and remembered for the next document and session; toggled from the form bar and from Preferences > Appearance, where it sets the default for documents opened afterwards; required fields get a red border (round for radio buttons); the tint is drawn over the field, where Acrobat paints the highlight colour behind it" [[feature]] id = "form.field-appearance-display" From f295be1e057a84d9273a523ce78b86aabed034c6 Mon Sep 17 00:00:00 2001 From: vangel76 Date: Thu, 15 Oct 2026 23:29:41 +0200 Subject: [PATCH 2/3] Tests: field highlights are on by default, so the toggle reads "Hide field highlights" (#2273) Co-Authored-By: Claude Sonnet 5.5 --- crates/ui-egui/tests/ui_group/animate.rs | 3 ++- crates/ui-egui/tests/ui_group/xfa.rs | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/ui-egui/tests/ui_group/animate.rs b/crates/ui-egui/tests/ui_group/animate.rs index 20008edd6..d01bf6de1 100644 --- a/crates/ui-egui/tests/ui_group/animate.rs +++ b/crates/ui-egui/tests/ui_group/animate.rs @@ -67,6 +67,7 @@ fn push_buttons_alone_show_no_form_bar_and_a_screen_annotation_is_not_a_comment( } assert!(h.query_by_label_contains("interactive form fields").is_none(), "nothing to fill in, so no form bar"); assert!(h.query_by_label("Highlight fields").is_none()); + assert!(h.query_by_label("Hide field highlights").is_none()); } #[test] @@ -75,5 +76,5 @@ fn a_fillable_field_beside_push_buttons_still_shows_the_form_bar() { let text = "<< /Type /Annot /Subtype /Widget /FT /Tx /T (name) /Rect [20 160 200 180] /P 3 0 R >>"; let h = open(pdf(&[&button, text, SCREEN])); h.get_by_label_contains("This document contains 2 interactive form fields."); - h.get_by_label("Highlight fields"); + h.get_by_label("Hide field highlights"); } diff --git a/crates/ui-egui/tests/ui_group/xfa.rs b/crates/ui-egui/tests/ui_group/xfa.rs index 6565a9816..ee44ec823 100644 --- a/crates/ui-egui/tests/ui_group/xfa.rs +++ b/crates/ui-egui/tests/ui_group/xfa.rs @@ -58,8 +58,8 @@ fn a_dynamic_xfa_form_is_laid_out_and_filled() { let doc = s.session.get(s.views[0].id).unwrap(); assert_eq!(doc.info.pages.len(), 2); assert!(doc.form.iter().any(|f| f.name == "familyName")); - // The notice offers to highlight the fields, like any form. - h.get_by_label("Highlight fields"); + // The notice offers the highlight toggle, like any form; highlights are on by default. + h.get_by_label("Hide field highlights"); } #[test] @@ -87,8 +87,8 @@ fn a_static_xfa_form_is_filled_from_its_data_and_says_so() { assert_eq!(doc.form.iter().find(|f| f.name == "form1[0].page1[0].name[0]").unwrap().value, vec!["Ada".to_string()]); assert_eq!(doc.form.iter().find(|f| f.name == "form1[0].page1[0].agree[0]").unwrap().value, vec!["1".to_string()]); assert!(!doc.dirty); - // The fields can still be highlighted from the notice. - h.get_by_label("Highlight fields"); + // The highlight toggle (on by default, so it reads "Hide") is still in the notice. + h.get_by_label("Hide field highlights"); } #[test] From 7504799e059c579d3def2a9b6076f57c4cd405dd Mon Sep 17 00:00:00 2001 From: vangel76 Date: Tue, 20 Oct 2026 01:11:01 +0200 Subject: [PATCH 3/3] M6: align secured-form highlight tests after rebase (#2273) --- ROADMAP.md | 2 ++ crates/ui-egui/tests/editing.rs | 2 +- crates/ui-egui/tests/ui_group/xfa.rs | 2 +- docs/roadmap.md | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index b1283e775..7b6650e82 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -172,6 +172,8 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md ## Progress log +- **2026-10-20 (M6, #2273 / PR #2279):** Rebased the field-highlight default and Appearance preference onto main, retaining secured-form parity evidence and updating secured AcroForm/XFA toggle expectations. Build and test validation delegated to GitHub CI. Estimates unchanged. + - **2026-10-19 (M14, #3373):** The main menu and its long submenus scroll in short windows, keeping their commands reachable. Estimates unchanged. - **2026-10-19 (M3, #3387):** Save-prompt shortcuts yield to later consent and utility dialogs; a synthetic UI regression checks that an edited document stays open. Estimates unchanged. diff --git a/crates/ui-egui/tests/editing.rs b/crates/ui-egui/tests/editing.rs index d41041e52..a72ac29be 100644 --- a/crates/ui-egui/tests/editing.rs +++ b/crates/ui-egui/tests/editing.rs @@ -2165,7 +2165,7 @@ fn a_secured_form_shows_its_form_notice_and_its_security() { }); h.run_steps(4); h.get_by_label_contains("interactive form fields. This document is secured. Some changes are restricted by its security settings."); - h.get_by_label("Highlight fields"); + h.get_by_label("Hide field highlights"); h.get_by_label("Security settings"); h.get_by_label("Unlock…"); } diff --git a/crates/ui-egui/tests/ui_group/xfa.rs b/crates/ui-egui/tests/ui_group/xfa.rs index ee44ec823..e66658b0e 100644 --- a/crates/ui-egui/tests/ui_group/xfa.rs +++ b/crates/ui-egui/tests/ui_group/xfa.rs @@ -228,6 +228,6 @@ fn a_secured_dynamic_xfa_form_shows_its_layout_and_its_security() { .unwrap(); let h = open(pdfcraft_cos::write_full(&doc, &Default::default()).unwrap()); h.get_by_label_contains("laid out from its template: 2 pages, 11 fields. This document is secured."); - h.get_by_label("Highlight fields"); + h.get_by_label("Hide field highlights"); h.get_by_label("Security settings"); } diff --git a/docs/roadmap.md b/docs/roadmap.md index 5d73511e9..b0513ce40 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -133,7 +133,7 @@ total matches the parity estimate. | M4 | Engine, history, save, organize | 100–180 | 95% | 8–15 | Done:
- command registry (menus, shortcuts and palette all use it);
- undo/redo; incremental, atomic and encrypted saves;
- autosave and crash recovery;
- organize, combine, extract, split and insert-from-file, with identical fonts and images stored once;
- CLI `edit/combine/extract/split`.
Done since: Combine files in its own tab (sortable, resizable columns; folders; early warnings; unlocking protected files), bookmark editing, page labels (Number pages), CLI `run`, Set Page Boxes, Crop tool, Duplicate pages, Ctrl+A / Cmd+A to select all pages in Organize, Replace pages, bookmarks from tagged headings. Missing: page transitions, recovery on the web M4: Insert-from-file preserves source bookmarks; integration tests retained with current main. | | M5 | Comments (all annotation types, XFDF) | 120–200 | 88% | 12–25 | Done: notes, highlight/underline/strikeout/squiggly, text boxes, ink, lines, arrows, rectangles, ovals, stamps (dynamic, Sign Here, Standard Business), with appearance streams; replies, status, checkmarks, locking, move/resize/restyle/delete; properties dialog; panel filter and sort; hide all; summaries (comments only); XFDF/FDF import and export (partial imports preserve existing reply and review-status links); flatten; Fill & Sign with adjustable text/date size; agent tools.
Done since: stamps placed on a rotated page read upright (#1398); polygons, connected lines, clouds, callouts, inserted text, colour and checkmark filters; image signature pointer previews, selection handles and live image movement/resizing with proportional corners and independent edge handles. Missing: custom stamps, replace-text proposals, summary layouts with the page | | M5 | Comments (all annotation types, XFDF) | 120–200 | 88% | 12–25 | Done: notes, highlight/underline/strikeout/squiggly, text boxes, ink, lines, arrows, rectangles, ovals, stamps (dynamic, Sign Here, Standard Business), with appearance streams; replies, status, checkmarks, locking, move/resize/restyle/delete; properties dialog; panel filter and sort; hide all; summaries (comments only); XFDF/FDF import and export (partial imports preserve existing reply and review-status links; nested FDF export uses lazy array owners and annotation positions, with retained entries budgeted before cloning under copied-graph limits and source `/Page` and `/Pages` dictionaries replaced by null at every copy depth); flatten; Fill & Sign with adjustable text/date size; agent tools.
Done since: polygons, connected lines, clouds, callouts, inserted text, colour and checkmark filters; image signature pointer previews, selection handles and live image movement/resizing with proportional corners and independent edge handles. A refused restyle or resize of a text box another program drew leaves that comment unchanged (#1327). Make Current Properties Default on an arrow updates arrows, not plain lines (#1302). Flattening keeps a visible comment it cannot draw (#1317). Missing: custom stamps, replace-text proposals, summary layouts with the page | -| 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. Plain AcroForm XML imports retain all direct text around comments and processing instructions, including CDATA and character references. CSV export and spreadsheet merging quote tabs, and import distinguishes quoted tabs from TSV delimiters without changing field identities. XML/XFDF exports preserve tabs and line breaks in names and comment metadata. Refreshed against upstream `99941daa` (2026-10-15).
Done since: JavaScript engine (boa, sandboxed) running custom keystroke/validate/calculate/format and button scripts, document JavaScripts, console. Done since: Push-button normal labels are editable per widget through Options and form_set_props, independently of field names; rendering, undo/redo and incremental/full save/reopen are covered. (#2892). Missing: Actions tab, wider object model (annotations, layers, dialogs), XFA
Done since: Multi-select choice fields write ascending `/I` option indices while preserving `/V` selection order; synthetic forms/engine regressions cover reverse selection and save/reopen. CSV export preserves carriage returns in names and values. | +| 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. Plain AcroForm XML imports retain all direct text around comments and processing instructions, including CDATA and character references. CSV export and spreadsheet merging quote tabs, and import distinguishes quoted tabs from TSV delimiters without changing field identities. XML/XFDF exports preserve tabs and line breaks in names and comment metadata. Refreshed against upstream `99941daa` (2026-10-15).
Done since: JavaScript engine (boa, sandboxed) running custom keystroke/validate/calculate/format and button scripts, document JavaScripts, console. Done since: Push-button normal labels are editable per widget through Options and form_set_props, independently of field names; rendering, undo/redo and incremental/full save/reopen are covered. (#2892). Missing: Actions tab, wider object model (annotations, layers, dialogs), XFA
Done since: Multi-select choice fields write ascending `/I` option indices while preserving `/V` selection order; synthetic forms/engine regressions cover reverse selection and save/reopen. CSV export preserves carriage returns in names and values. PR #2279 refreshed on main: field highlighting defaults on, stored choices are retained; secured-form toggle expectations updated. CI validation pending; estimates unchanged. | | 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. Plain AcroForm XML imports retain all direct text around comments and processing instructions, including CDATA and character references.
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: Multi-select choice fields write ascending `/I` option indices while preserving `/V` selection order; synthetic forms/engine regressions cover reverse selection and save/reopen. CSV export preserves carriage returns in names and values. | | 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. XML/XFDF exports preserve tabs and line breaks in names and comment metadata. Refreshed against upstream `99941daa` (2026-10-15).
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: Multi-select choice fields write ascending `/I` option indices while preserving `/V` selection order; synthetic forms/engine regressions cover reverse selection and save/reopen. CSV export preserves carriage returns in names and values. | | 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. XML/XFDF exports preserve tabs and line breaks in names and comment metadata. Plain AcroForm XML imports retain all direct text around comments and processing instructions, including CDATA and character references. CSV export and spreadsheet merging quote tabs, and import distinguishes quoted tabs from TSV delimiters without changing field identities.
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: Multi-select choice fields write ascending `/I` option indices while preserving `/V` selection order; synthetic forms/engine regressions cover reverse selection and save/reopen. CSV export preserves carriage returns in names and values. |