Commit Graph
960 Commits
Author SHA1 Message Date
FredApps 6b38a5ab47 Reconcile typed font sizes and Fill & Sign colours with current UI
Use the unified Format Text panel and current page-input context instead of restoring old monolithic UI code. Preserve main's pointer routing, editor accessibility and preferences; retain colour, save and input regressions. Hosted CI must validate this refreshed head.
2026-10-30 08:47:42 +01:00
FredApps e34cb1d1cc Integrate KenM76 PR #4991
# Conflicts:
#	crates/automation/tests/it/automation.rs
#	crates/forms/src/author/edit.rs
#	crates/forms/src/tests.rs
#	crates/forms/src/values.rs
#	parity/acrobat-features.toml
2026-10-29 02:38:25 +01:00
FredApps 07edd05aea Integrate KenM76 PR #4987
# Conflicts:
#	crates/forms/src/author/edit.rs
#	crates/forms/src/tests.rs
2026-10-29 02:38:03 +01:00
FredApps 9e30913d6e Integrate KenM76 PR #4977 2026-10-29 02:36:20 +01:00
FredApps a66686cb0d Integrate KenM76 PR #4862 2026-10-29 02:34:57 +01:00
KenM76andClaude Opus 5.5 3aeafeb09e Merge origin/main into fix/cleared-default-stays-cleared
Resolves test-file conflicts where main and this branch both appended tests:
main's file plus this branch's appended tests, unchanged.
fmt, clippy -p pdfcraft-forms -p pdfcraft-automation, and both crates' tests pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-10-28 19:18:37 -04:00
KenM76andClaude Opus 5.5 304f27fd0b Merge origin/main into fix/choice-index-follows-options
Resolves test-file conflicts where main and this branch both appended tests:
main's file plus this branch's appended tests, unchanged.
fmt, clippy -p pdfcraft-forms -p pdfcraft-automation, and both crates' tests pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-10-28 19:18:35 -04:00
KenM76andClaude Opus 5.5 2a26479cbd Merge origin/main into fix/radio-join-group-by-name
Resolves test-file conflicts where main and this branch both appended tests:
main's file plus this branch's appended tests, unchanged.
fmt, clippy -p pdfcraft-forms -p pdfcraft-automation, and both crates' tests pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-10-28 19:18:32 -04:00
aiongg dc943eeb13 Merge pull request #4718 from alehel/fix/marks-update-preview-without-old-marks
fix(ui): Update previews the page without the marks it replaces
2026-10-28 17:59:41 -04:00
aiongg 7cde655cc2 Merge pull request #4709 from alehel/fix/page-cursor-writer
fix(ui): the page sets its cursor once, from the top claim the tool reaches
2026-10-28 17:57:29 -04:00
aiongg f824475202 Merge pull request #4631 from Lcoding15/fix/delete-one-radio-button
M6: Delete removes one selected radio button, not its whole group (#4609)
2026-10-28 17:55:17 -04:00
.CooCoo 222a124650 M5: Import comments takes another PDF and copies its comments onto the same pages (#4595)
* M5: Import comments takes another PDF and copies its comments onto the same pages

Import comments read only XFDF, FDF, XML, XDP, CSV and text data files.
It now also takes a PDF: each comment of that PDF is copied onto the
page with the same number, at the same place, with its appearance
stream, its pop-up, its replies and its layer. Comments on pages past
the document's last page are left out, together with replies to them.
Links, form fields and redaction marks are not copied. A comment named
like one already on its page replaces it, as an XFDF or FDF import
does. A PDF without comments is refused with "nothing in the file
matches this document", and a PDF that needs a password is refused.

- organize: import_comments copies the comments with the page copier
  that Insert Pages uses, and registers the layers they use.
- engine: Edit::ImportData reads a file that starts with %PDF- as a PDF.
  Any other file goes to the data importer first, and is read as a PDF
  when that fails, %PDF- occurs in its first 1,024 bytes and the PDF
  reader opens it.
- ui: Comments > Import comments lists .pdf files; form.import_data
  lists the same files as before.
- automation: the doc_import_data description names the PDF case.
- parity: comment.import-from-pdf is shipped.

* Import comments: an annotation that is no comment keeps its name

A comment imported from a PDF replaced any annotation of the same /NM
on its page that was not locked, a redaction mark or a widget, so a
link, screen or 3D annotation of that name was overwritten. Only an
annotation whose subtype is one of the copied comment subtypes is
replaced now; for any other, the copy is added beside it without the
name. The subtype is read through an indirect reference.

* Import comments: index each page's annotations instead of scanning them per comment

* Import comments: a layer that BaseState OFF hides stays hidden

* Import comments: a PDF with binary bytes before its header imports on an XFA form, and two comments with one name both land

On an XFA form, a decode failure no longer stops the import when a PDF
header is in the first 1,024 bytes; the PDF's comments are copied. Two
incoming comments on one page with the same /NM no longer replace the
same comment: the first replaces it, and the second is added without
the name.

* Import comments: an inline annotation on the page is replaced by name

An annotation written inline in a page's /Annots is made an indirect
object before names are matched, so an incoming comment with its /NM
replaces it instead of being added beside it.

* Import comments: two new comments with one name keep it once

When no comment on the page has the name, the first incoming comment
keeps its /NM and each later one with the same name is added without it.
2026-10-28 17:53:03 -04:00
aiongg 4d8dc3a1b7 Merge pull request #4560 from Lcoding15/feat/bookmarks-from-text-styles
M4: New bookmarks from text styles, for headings that aren't tagged (#1693)
2026-10-28 17:50:47 -04:00
.CooCoo 8f3a4f18bd M6: Organize pages ▸ Page properties sets the selected pages' tab order (#4516)
* M6: Organize pages ▸ Page properties sets the selected pages' tab order

Page properties in the Organize pages panel was listed as planned. In
the app, a page's tab order could only be set for every page at once,
with the form.tab_order commands, which offer no Unspecified. (The
form_tab_order tool already set chosen pages, Unspecified included.)

- Page properties (page.properties) opens a Tab Order dialog for the
  selected pages, with Use Row Order, Use Column Order, Use Document
  Structure and Unspecified. OK writes Edit::SetTabOrder for those pages
  as one undo step. The dialog opens on the pages' order, or with no
  choice made when they differ, and then OK is off until one is chosen.
  OK on the order the pages already have adds no undo step.
- Rows and columns run right to left in a document bound on the right,
  as Use Row Order and Use Column Order are described. PdfCraft reads a
  right-to-left binding from /ViewerPreferences /Direction /R2L
  (ISO 32000-1, 12.2, Table 150). Before, rank_tabs ranked them left to
  right.
- A page whose /Tabs is an indirect object now takes its row, column or
  structure order. Before, rank_tabs read only a direct name and ranked
  the page in annotation order.
- pdfcraft_forms::tab_orders and Document::tab_orders read each page's
  /Tabs. form_tab_order returns them as page_tabs, and reads without
  changing anything when it is given no order or field.
- set_option("dialog", "page-properties") opens the dialog.
- Parity: organize.page-tab-order is shipped, and its notes say the
  Actions tab of Page Properties (organize.page-actions) is still
  planned; the Organize pages grid notes list Page properties.

* Page properties: OK applies only to the document the dialog was opened on

The draft keeps the document's id. When another document is active by
OK, the dialog closes without changing either document.

* Menus: list Page properties… in the Pages menu

The menu bar now lists its commands explicitly, and every command with a menu group must appear in it.

* Page properties: apply the tab order only to an unchanged document

An edit, undo or redo made while the dialog is open can move the pages
at the stored indices. OK now closes the dialog without changing a page
when the document's edit generation differs from when it opened.

* Page properties: say when a document change cancels the tab order

OK shows a notice when the document changed or another document became
active while the dialog was open, so the user knows to open it again.
2026-10-28 17:48:29 -04:00
Eric Kaden 21f6c29d3e M6: form script messages keep their title, icon and buttons, and every one is shown (#4293, #4294) (#4306)
* M6: form script messages keep their title, icon and buttons, and every one is shown (#4293, #4294)

* M6: a form script message waits for a notice the toast already shows

* M6: form script messages past the queue's room are counted and reported

* M6: a sentence break in the merged parity note

* M6: an object that names no parameter is a message's text, as a String object or a caught error

* M6: the count of left-out script messages is said again if another notice replaces it before its time

* M6: a full stop between two sentences of the FormCalc parity note

* chore: re-run CI (a frame-budget timing test missed on a shared runner)
2026-10-28 17:46:11 -04:00
Lucas Sampaio Dias 03996b1025 Improve reading-colour preferences and add a settings shortcut (#2627)
* M3: improve reading-colour preferences and add settings shortcut

* M3: clarify the disabled reading-colour preview

* M3: adapt rebased preferences to current UI dependencies
2026-10-28 17:41:29 -04:00
alehelandClaude Opus 5.5 871a3e1e96 Merge origin/main into fix/page-cursor-writer
main split canvas.rs, comments.rs, edit_text_ui.rs and moved the page
modules under canvas/ and tools/. The cursor claims are re-applied where
that code now lives:

- page_cursor.rs moves to canvas/, beside page_pointer.rs, re-exported
  from lib.rs the same way.
- canvas.rs: claims in the stamp, hand, text-cursor and link code;
  over_field and badge_hover are gone; the cursor is resolved once after
  the page loop in draw_pages. annotation_hover_text (canvas/area.rs) and
  its test give way to comments::describe and hover_tip.
- comments: describe, hover_tip and on_link go to tools/comments/input.rs,
  the hover_tip test to tools/comments/tests.rs.
- Edit text's picture claims go to tools/edit_text_ui/image.rs.
- The cursor guard test goes to canvas/page_pointer/tests.rs, with
  NOT_PAGE_CURSOR on main's paths.
- Tools that took ui only to set the cursor stop binding it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 10:16:13 +01:00
alehelandClaude Opus 5.5 b3cbb77681 Merge origin/main into fix/marks-update-preview-without-old-marks
main split lib.rs: the unmarked_page field stays on PdfCraftApp in lib.rs,
its init and the render_pending check moved to app.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:50:04 +01:00
KenM76andClaude Opus 5.5 969cad099d Merge origin/main into fix/radio-join-group-by-name
Adopt upstream's split set_props: rename_field returns the group a renamed
radio button joins, and redraw_or_join keeps set_props under the new
cognitive_complexity limit. join.rs imports explicitly (wildcard_imports).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-10-27 21:57:42 -04:00
KenM76andClaude Opus 5.5 d95ea8710d Merge origin/main into fix/cleared-default-stays-cleared
Adopt upstream's explicit import lists in author/edit.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-10-27 21:35:31 -04:00
KenM76andClaude Opus 5.5 68e78e7886 Merge origin/main into fix/choice-index-follows-options
Adopt upstream's split set_props and explicit import lists. The list box
scroll after a property edit moves into scroll_list, keeping set_props under
the new cognitive_complexity limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-10-27 21:17:29 -04:00
Claude d915af5ed5 Merge remote-tracking branch 'upstream/main' into feat/bookmarks-from-text-styles 2026-10-27 22:27:02 +00:00
Claude 65d8385616 Merge remote-tracking branch 'upstream/main' into feat/bookmarks-from-text-styles
# Conflicts:
#	crates/automation/src/lib.rs
#	crates/engine/src/lib.rs
#	crates/ui-egui/src/lib.rs
#	crates/ui-egui/src/panels.rs

Main's module layout (#4889) moved what this branch changed: the tools to
automation's dispatch.rs (the levels parser to json.rs), the edit to
engine's edit.rs, edit/labels.rs and edit/run.rs, Document::text_styles
to document.rs, the dialog state to state.rs and app.rs, and the menu
item to panels/right.rs and panels/bookmarks.rs. The dialog module joins
the others in dialogs/. Main's craft-menu bar (#4752) lists its commands
by hand, so bookmark.from_text_styles is added after
bookmark.from_structure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbuSKAS2k5248LGFEapMUR
2026-10-27 20:46:00 +00:00
Kyle Kassen da8cea2b0b M10: reuse verified OCR model and packaging prerequisites 2026-10-27 18:54:41 +00:00
KenM76andClaude Opus 5.5 58c50f60d6 M6: a list box's top row is chosen after the edit's new font size
set_props chose /TI inside the field-dictionary update, from the field
read before the edit, so a font size or look changed in the same call
was not counted: at a larger size fewer rows fit and the selection
could end up below the visible rows. The field and widget top rows are
now chosen after every layout change, from the field read again,
before the redraw.

Test: a_list_boxs_top_row_follows_a_font_size_changed_with_its_items
(fails with the old ordering: rows 1..2 shown, selection on row 3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-10-27 12:32:57 -04:00
KenM76andClaude Opus 5.5 7a6177759c M6: each list box widget with its own top row scrolls by itself
A widget's own /TI is read before the field's, so each one scrolls by its
own top row and size (ListRows::top_showing). Before, the widgets were only
moved, all to the first widget's new row, when the first widget itself
scrolled: a second widget scrolled past the remaining options, or away from
a new value the first already showed, kept its stale row.

Test each_widget_with_its_own_top_row_scrolls_by_itself covers both
set_props (options shrink) and set_value; restoring the first-widget gate
in either caller makes it fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-10-27 11:13:16 -04:00
KenM76andClaude Opus 5.5 12c9051fd8 M6: clearing a field's value leaves it empty, not its parent field's
/V is inheritable like /DV (ISO 32000-2 §12.7.4.1). Clearing a text or
choice field's value removed its own /V, so a parent field's /V showed
through: Reset Form on a kid with no default refilled it with the parent's
value. When an ancestor sets /V, an empty value is now written instead of
removing the key. Check boxes and radio buttons already write /Off.

Test clearing_a_kids_value_does_not_bring_back_its_parents covers a text
kid and a list box kid through reset and through set_value; each half of
the fix was switched off in turn and the test failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-10-27 11:01:12 -04:00
KenM76andClaude Opus 5.5 d39fe5b233 M6: clearing a field's default leaves it with none, not its parent field's
/DV is inheritable (ISO 32000-2 §12.7.4.1). set_props removed a cleared
/DV, which brought back a parent field's, so Reset Form restored the
parent's default. When an ancestor sets /DV, a cleared default is now
written empty (/Off for buttons, () otherwise); with none it is removed
as before. New tree::ancestor_sets, cycle-safe and capped.

Adapted from pdfcer (MIT, same author) 6af2773ccf599b416fab379673119c6b37c55a2c.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-10-27 10:03:43 -04:00
KenM76andClaude Opus 5.5 3ed4f2379e M6: a list box's top row follows its options when they change
Changing a list box's options could leave /TI (the first visible row)
pointing at a row that moved or no longer exists. A widget's own /TI is
read before the field's, and one past the end draws no options at all.

set_props now runs the same ListRows::top_showing rule set_value uses
(ISO 32000-2 12.7.5.4): /TI stays put while the selected rows are still
visible, otherwise it scrolls to them, clamped to the last full page.
The field's /TI is updated, and so is every widget that carries its own.
The scroll helpers move out of set_value so both paths share them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-10-27 09:51:32 -04:00
KenM76andClaude Opus 5.5 0ea9a23466 M6: editing or sorting a list's items keeps its selected indices on its items
set_props rewrote /Opt (an items edit or "Sort items") but left /I, the
selected positions in /Opt (ISO 32000-2 §12.7.5.4), so /I named whatever
now sat at the old positions. It now re-points /I at the selected values'
new positions, by set_value's rule, and removes it when none is offered;
a field /TI past the end of the new list is removed.

Adapted from pdfcer (MIT, same author) 149db0662ecfe5b23a050cdb7513821da918d7cb.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-10-27 09:16:35 -04:00
KenM76andClaude Opus 5.5 a9ddf79a19 M6: a radio button renamed to a radio group's name joins that group (#4949)
Renaming a radio button to an existing radio group's name was refused as
a duplicate, so buttons placed separately could never become one group.
set_props now moves the renamed field's buttons into the named group
(ISO 32000-2 §12.7.5.2.4: a group is one field whose widgets are its
buttons). A clashing export value is renamed to the first free ChoiceN
in /AP /N, /D and /R; a moved button is on only if its value is the
group's value; merged field+widgets are handled on both sides (the
target split via split_merged, now shared with duplicate_field).
Refused when either group has /Opt, has no button, or the target is
locked.

Adapted from pdfcer (MIT, same author) 79870b56 and 116f48d3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-10-27 08:05:28 -04:00
Claude f595a9677b Merge remote-tracking branch 'upstream/main' into fix/delete-one-radio-button
# Conflicts:
#	crates/engine/src/lib.rs
#	crates/forms/src/author.rs
#	crates/ui-egui/src/prepare.rs
#	parity/acrobat-features.toml
2026-10-27 06:03:23 +00:00
KenM76andClaude Opus 5.5 cef9908343 Merge origin/main into fix/auto-size-follows-field-height
main deleted crates/engine/src/reexports.rs and moved its re-exports
into crates/engine/src/lib.rs; the comb_auto_text_size re-export this
branch added now sits in lib.rs. forms_ui.rs moved to
crates/ui-egui/src/tools/; the branch's hunks carried over unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-10-26 23:11:08 -04:00
aiongg 3dbea4e7a0 docs(parity): point evidence at the ui-egui files' new directories 2026-10-27 00:34:32 +00:00
aiongg 91aaa4fdab docs(parity): point evidence at inspect's moved tests 2026-10-26 22:18:50 +00:00
aiongg daba10be67 docs(parity): point evidence at panels' moved tests 2026-10-26 22:18:50 +00:00
aiongg f64ee67181 docs(parity): point evidence at combine_ui's moved tests 2026-10-26 22:18:50 +00:00
aiongg b1a09ee08d docs(parity): point evidence at the tests' new files 2026-10-26 20:51:19 +00:00
KenM76andClaude Opus 5.5 ccb2565036 fix(forms): auto-sized single-line text fields fill their height, not 12 pt
An auto-sized (`0 Tf`) single-line text field drew at most 12 pt however
tall the box. Acrobat Reader draws it at the size the height gives (checked
black-box on a synthetic form: 30 pt and 60 pt fields); ISO 32000-2
§12.7.4.3 leaves the size implementation-defined. auto_text_size_by now
keeps the existing height / 1.15 size without the 12 pt cap, still shrinks
to the width and never goes under 4 pt, with the height bounded at 14,400
(MAX_AUTO_HEIGHT, the largest page side ISO 32000-1 Annex C recommends).

Multiline and comb auto-size keep their 12 pt cap: not yet checked against
Acrobat.

Tests: the forms unit tests now pin a tall box's size (they pinned 12 pt),
and an automation test fills a 30 pt field through form_fill and measures
the drawn text with text_find. All three fail with the cap restored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-10-26 16:29:44 -04:00
alehelandClaude Opus 5.5 24eb327a80 fix(ui): the page sets its cursor once, from the top claim the tool reaches
Every page module set egui's cursor itself and the last write of a frame won, so the
order of document_area's per-page chain decided the cursor, not what a press there does.
The link hover loop ran last and showed the hand over any link for every tool but Fill &
Sign, though only the Select tool follows links: a click with the stamp tool placed a
stamp, a drag with the rectangle tool drew, and in Edit text a drag from linked words
moved the paragraph, all under the hand. The text layer ran after the comments and showed
the I-beam over a movable comment lying on text, which a drag there moves.

Page modules now claim the cursor instead (src/page_cursor.rs): a layer, an icon and
optionally a tip. The layers are the review's priority table, Editor > Handle > Placed >
Field > Comment > Link > Text > tool area, with Drag on top for a drag in progress, whose
owner names the cursor until the release. document_area resolves the claims once per
frame, after the pages, and calls set_cursor_icon once: the claim on the highest layer
the active tool reaches wins. Links count only for the Select tool (follow) and the Link
tool (edit), comments only for the Select tool; the other layers are claimed only by the
modules of the tools that act on them. The claims go into a per-frame collector on
DocView, which every page module already takes, so the calls in the dispatch block keep
their arguments (camera, content and redact lose a `ui` they took only for the cursor).

A link's tip goes with its claim, so it shows only where the link wins: no longer with a
stamp, drawing or other tool that does not follow links. A comment's tip is a description
instead (comments::describe), shown with any tool beside that tool's cursor unless the
winning claim has a tip of its own: Acrobat shows a comment's tooltip with every tool
(observed with Select, Hand, Add comment, Highlight, Rectangle and Stamp). It keeps the old
exceptions (Fill & Sign, freehand drawing for #429, a comment gesture in progress) and
comments. A signed field's validation badge (#1895) says its verdict the same way, with any
tool, ranked below a comment's description as it was below the comment's tip; the topmost
of overlapping badges still speaks.

FieldPointer::Over existed only to keep the text layer from overwriting a field's cursor
(#2099); the Field layer above Text does that now, so forms_ui::page_input returns whether
a field owns the press. Who acts on a press is unchanged (step 3 of the router).

The picture's rotate knob claims too: Grab as a handle, Grabbing as a drag while it turns,
which dragging_the_rotate_knob_turns_a_page_image now checks.
The contains_pointer checks #2755 put before the cursor in content_ui, edit_text_ui,
forms_ui and the text layer go: the pages' pointer has no position where the mode bar or a
side panel covers a page, so nothing is claimed there, and #2755's tests pass unchanged.
During a press the pages hold, those four now keep their cursor over the chrome too, as
the other page modules and a text selection being dragged already did.

Un-ignores the four step-2 probes in tests/it/pointer.rs, each seen failing on the
previous commit at its cursor assertion, after its press assertions passed. A new test,
also seen failing there: a link's tip shows only with its hand. Another pins a comment's
tip with every tool beside its cursor, and its exceptions; it was seen failing with comment
tips tied to the cursor's winner instead, which shows them with the Select tool only. In
tests/it/signing.rs, a signed field's badge says its verdict with the Select tool and with a
stamp; it fails when the badge is left out of the resolver, as resolving after the page
loop would otherwise do. A new ignored probe pins a step-3 bug: over stacked comments the
tip names the one drawn on top, but a click selects the one underneath, because
PageCx::hit takes the last in inspection's top-edge order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 21:54:55 +01:00
alehelandClaude Opus 5.5 2b83ae6a7a fix(ui): Update previews the page without the marks it replaces
Update Background, Update Watermark and Update Header and Footer
previewed the new marks on the page's thumbnail, which still has the
marks Update replaces. A pale yellow background updated to light blue
showed a muddy teal (the blue multiplied onto the yellow), and a new
watermark or header was drawn over the old one. The applied result was
right; only the preview was wrong.

In Update, the preview now draws the page without its marks of that
kind. The engine removes them on a copy of the document
(Document::page_without_marks, read-only like the other preview layers),
and the dialog renders that page off the UI thread at the preview's
size, showing a blank sheet until it arrives. Add, and pages without
marks of that kind, keep the thumbnail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 21:23:17 +01:00
Dylan M. Taylor 833cd63c8a proposal(redact): prevent retention of confidential vector paths behind clipping (F-008) (#4577)
Partial vector redaction previously hid covered areas with clipping
(`clip_out`, `W/W*`) while retaining the complete original drawing instructions
and painting operators in the content stream. This allowed recipients
to recover confidential outlined text or other vector drawings from
supposedly redacted documents.

This change removes vector paths that intersect redaction marks completely
and refuses partial clip retention, ensuring no confidential geometry is
preserved behind clipping. Report counts are updated to record removed paths.
2026-10-25 16:14:06 -04:00
Dylan M. Taylor 86a4120ae0 feat: startup update prompt modal and direct self-updating (#4654)
* feat: startup update prompt modal and direct self-updating

Help ▸ Check for updates and the check at start run through craft-update's Updater, which replaces the old GitHub fetcher and update source. The check at start is opt-in for the desktop app (test harnesses never reach the network), an install holds back while documents are unsaved, and a finished update closes through the app's quit path. craft-update stays out of the wasm build.

Ref: storytold/craft-libs#319

* test(updates): the prompt's buttons are DOWNLOAD and LATER

* build: pin craft-update to craft-libs main (db19c37)

* fix(updates): run the Windows update helper before argument parsing

* fix(updates): point the parity evidence at the current update tests
2026-10-25 16:00:23 -04:00
skylar taylor-barrick e90f7882f1 Migrate PdfCraft to egui 0.37 (#4591)
* Record successful PdfCraft web application check

* E21: bound startup requests and validate font and license inputs

* E21: separate pending control completion and gesture regression phases

* M-egui037: enforce control admission and per-family font notices

* M-egui037: qualify capture fixes and document migration behavior

* egui-037: identify current and historical qualification results
2026-10-25 14:30:31 -04:00
Claude 5a1b9065bc Merge remote-tracking branch 'upstream/main' into fix/delete-one-radio-button
# Conflicts:
#	crates/forms/src/tests.rs
2026-10-25 15:03:09 +00:00
71b47b519d M3: a Page tools menu in the Pages panel (#4415) (#4583)
* M3: a Page tools menu in the Pages panel (#4415)

The Pages panel's header gets a Page tools menu beside Close: insert a
blank page or pages from a file after the selection, rotate either way,
extract and delete. Each runs the registered command on the selected
thumbnails (the current page when none is) and is greyed out where the
document's security withholds page changes. A menu rather than a row of
buttons, so the thumbnails stay where they were and it fits any width.

* M3: tests drive the Page tools menu's file items too (#4415)

CodeRabbit on #4583: the parity evidence covered rotate, delete, blank
page and permissions through the menu, not Insert pages from a file or
Extract. A test now inserts a picked file's pages and extracts the
selection from the menu; the parity entry cites it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LymhVaGZCWwB9HNEbLzeMM

* M3: the Page tools menu in every catalog (#4415)

Page tools and five of its items, in each language (AI-assisted, not
separately native-reviewed, like the other recent entries). Insert a
blank page after the selection was in the catalogs already and keeps
its translation there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LymhVaGZCWwB9HNEbLzeMM

* chore(ui): keep thumbnail_size's doc line on thumbnail_size

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-25 10:11:08 -04:00
8708f35b37 M6: with NeedAppearances, check boxes and radio buttons draw their /MK caption (ISO 32000-2 Table 224) (#4454)
* M6: with NeedAppearances, check boxes and radio buttons draw their /MK caption (ISO 32000-2 Table 224)

A form that sets /NeedAppearances asks the viewer to construct the
appearances of its widgets (ISO 32000-2 Table 224; deprecated in PDF 2.0).
The display copy already redraws text and choice fields for it, but check
boxes and radio buttons kept the file's own appearance streams, so a box
whose /MK /CA is ZapfDingbats 4 (a check mark) showed the filled square
its /AP carried.

With /NeedAppearances, every check box and radio button that has an /MK
is now drawn in its current state through button_state_shown (merged in
#4368): /MK /BG and /BC, and when on the /MK /CA ZapfDingbats character
(Table 192) in the /DA colour, sized to the box. A widget without /MK
keeps its own appearance, and without /NeedAppearances nothing changes.
A radio button with no /AS now takes its state from /V when /V names its
own on state. Display and print only: the file is saved as it was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC

* chore(forms): wrap button_state_shown's doc comment

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-25 10:11:04 -04:00
58d368f1f0 M10: Word export keeps an italic run inside its sentence's paragraph (#4158) (#4411)
The reading blocks keep style boundaries, so "Before Chevron after." with an
italic case name reached the Office exporters as three blocks, and each writer
emitted one paragraph per block. A block that continues another on the same
line (the test `continuations` already applies to keep such blocks out of
table cells) is now a run in its own size, bold/italic and colour inside the
paragraph of the block it continues, in Word, HTML, RTF and PowerPoint.

- `continued_from` names the block each one continues (nearest end);
  `continuations` is its `is_some`, so table detection is unchanged.
- `paragraphs` groups blocks left to right, joining only a block that starts
  strictly further left, with the same rotation, and never a table cell or the
  slide title.
- Block text is trimmed, so the engine records the source's spaces at its ends
  (`Block::space_around`, from the reading block's editor text); runs keep that
  space, or one where the page shows a gap.
- A paragraph is a heading only if its runs are (a bold lead-in no longer makes
  its line a heading); a smaller footnote marker does not demote a heading.

Style changes that wrap onto the next line still split paragraphs; that needs
the reading-block grouping change and is left for later.


Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-25 10:11:00 -04:00
Livio OswaldandClaude Opus 5.5 0c124edc3e M3: an attachment's row opens on a double-click and has a right-click menu (#3466) (#4513)
* M3: an attachment's row opens on a double-click and has a right-click menu (#3466)

In Acrobat, a right-click on an attachment offers Open and Save, and a
double-click opens it. PdfCraft's Attachments panel had only the buttons
at the end of each row.

Each row now senses clicks beneath its buttons (`UiBuilder::sense`, which
egui registers under the row's children, so the buttons keep their own
clicks):
- A double-click opens a PDF in a new tab, as the Open button does, and
  saves any other file, since PdfCraft opens only PDFs.
- A right-click offers the row's own actions with the buttons' labels:
  Open in a new tab (PDFs), Save attachment… and Delete attachment (the
  document's own attachments, when it may be modified).

The name and details labels are no longer selectable, so a click on them
reaches the row.

* M3: a right-click on an attachment row's button opens the row's menu too (#3466)

CodeRabbit on #4513: the menu hung on the row's response only, so a
right-click on its Save, Open or Delete button opened nothing. The menu
now answers the union of the row and its buttons (one menu, the row's id).
Regression test right-clicks a button (fails without the fix).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LymhVaGZCWwB9HNEbLzeMM

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-25 10:02:53 -04:00
mtlcl410andClaude Opus 5.5 7dd44301db M2: a luminosity soft mask in a DeviceCMYK group converts as ISO 32000-2 10.4.2 says, so 100% K masks fully (#4423)
A luminosity soft mask is drawn into an RGB buffer and its value read
as the luminance of that buffer. A /DeviceCMYK colour reached the
buffer through the CMYK display profile, where 100% K is a dark grey,
so a mask painted 100% K in a DeviceCMYK group let about an eighth of
the masked paint through (mask value 0.125 instead of 0).

ISO 32000-2 11.5.3 converts a device-space group colour to gray "with
no compensation for gamma or other colour calibration", and 10.4.2.3
gives DeviceCMYK to DeviceGray as
gray = 1 - min(1, 0.3 c + 0.59 m + 0.11 y + k).

hayro-interpret patch (63): when the group colour space of a luminosity
soft mask is /DeviceCMYK itself (not an ICC-based or calibrated space),
the mask's context paints /DeviceCMYK fill and stroke colours as that
gray, and a four-component /BC becomes the same gray. A gray reads back
as itself through any luminance weights that sum to one. CMYK on the
page, CMYK in DeviceGray/DeviceRGB mask groups, and CMYK images,
shadings and tiling patterns inside such a mask keep the profile.

Tests: color::device_cmyk_gray_tests (hayro-interpret) and
it::luminosity_group_space (pdfcraft-render); the three DeviceCMYK
render tests fail with the patch reverted.


Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 10:02:49 -04:00