Commit Graph
4058 Commits
Author SHA1 Message Date
KenM76andClaude Opus 5.5 bac061e473 fix(render): a form's stray Q no longer pops the page's saved states
A form XObject or annotation appearance with more `Q` than `q` popped
graphics states saved by the content that drew it, so later page text,
paint and clip lost the page's `cm` and clip, and search redaction of that
text failed closed at `redact_apply`.

A content stream balances its own `q` and `Q` (ISO 32000-2 §8.4.2,
§8.10.1). Vendored hayro-interpret patch (68): `interpret()` records the
state depth at which its stream starts (`Context::enter_stream`) and
restores the outer floor on exit; a `Q` at the floor is ignored with a
warning. Page content (`interpret_content`) is unchanged.

Tests: pdfcraft-render `it::stray_q_in_a_form` (three behaviour tests fail
with the fix reverted, one guard) and pdfcraft-automation
`it::redact_after_stray_q_form` (end to end; fails at `redact_apply` with
the fix reverted).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-10-26 11:34:17 -04:00
KenM76andClaude Opus 5.5 086339b7d7 fix(render): keep the text matrix across Q inside a text object
The vendored hayro-interpret kept the text and text line matrices in the
saved graphics state, so a `q … Q` inside BT … ET put the pen back where it
was before `q`. Text after the `Q` was drawn, extracted and found at the
wrong place, and search redaction refused to apply because its find boxes
(from the render text layer) disagreed with the redactor's own measurement.

Tm and Tlm are not graphics state (ISO 32000-2 §8.4.1 Table 51, §9.4.1);
q/Q inside a text object is allowed in PDF 2.0 (§8.2). Patch (67) carries
them across the restore; font and the other text parameters are still
restored. Forms and annotations save and restore around their own content,
and Type 3 glyphs, patterns and soft masks run in a context of their own, so
a stream's pen does not reach its caller unless the stream pops past its own
saves (two or more stray Q in a form already corrupt the page state on main;
unchanged here).

Tests: pdfcraft-render it::text_matrix_q (three behaviour tests that fail
with the fix reverted, two guards) and pdfcraft-automation
it::redact_text_after_q (end to end, fails at redact_apply when reverted).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-10-26 10:57:16 -04:00
Combiler01 8f3151d4e7 chore(ui): mark image_menu_id with #[doc(hidden)] 2026-10-26 16:33:05 +05:30
RiceDee a85a6c7211 fix(ui): restyling an open added-text box restarts its typing history
restyle_draft applies a format change to an open added-text box in
place, so the page then has the box's text. Unlike a paragraph's
applied(), it left typing_from as it was: when the restyle left the
text as it was, the box kept its history, and Redo put typing undone
before the restyle back on top of it, to be saved on finishing. It now
resets typing_from after a successful edit, so the next claim clears
the history (and the rows' alignments by text with it).
2026-10-26 21:22:57 +11:00
RiceDee a33a8bba38 Merge main into feat/align-selected-lines
Main moved to egui 0.37 (#4591) and took the menus change (#4752).
Conflicts:
- edit_text_ui.rs: the paragraph box keeps the shared input id, now
  Id::unique; the added-text box's shared id and the unit tests' ids
  follow.
- content_ui.rs: the added-text box keeps its per-paragraph layouter
  (which places each line itself, so main's .align(halign) is left out)
  and takes main's inset, margin(4, 2), and its "Added text"
  accessibility label.
2026-10-26 21:02:08 +11:00
jonathanwxh-cellandjonathanwxh-cell 0a6d0f3689 fix(crypt): derive a new file id from the encryption seed
/ID is SHA-256 of the seed under the label id, not an unkeyed DefaultHasher digest. The salts test is a characterization; failing closed when randomness is missing stays the regression guard.

Co-authored-by: jonathanwxh-cell <jonathanwxh-cell@users.noreply.github.com>
2026-10-26 10:00:14 +00:00
RiceDee c779488c79 Merge main into fix/undo-in-open-text-editor
Main moved to egui 0.37 (#4591) and renamed string ids to Id::unique.
Conflict in edit_text_ui.rs: the paragraph box keeps the shared input
id, now Id::unique; the added-text box's shared id follows.
2026-10-26 20:41:38 +11:00
songzhihui 3994c2ca9d M2: document installed CJK font resolution contracts 2026-10-26 16:39:20 +08:00
songzhihui 9745b7c5cc M2: match print-preview fonts to output policy 2026-10-26 16:03:04 +08:00
Combiler01 fb0fb54f50 refactor(ui): expose and use image_menu_id helper in tests 2026-10-26 12:22:08 +05:30
songzhihui d865886d39 M2: sync validated CJK viewer PR with latest main 2026-10-26 13:55:39 +08:00
songzhihui 9107ada7e7 M2: resolve CJK viewer PR conflicts with main 2026-10-26 13:37:48 +08:00
Combiler01 bf2c246a68 fix(ui): adapt custom controls to egui 0.37 API and deprecations 2026-10-26 10:40:41 +05:30
aiongg 64e0868b84 feat(ui): menus from craft-menu, native on macOS (#4752)
* feat(ui): menus from craft-menu, native on macOS

One menu definition (crates/ui-egui/src/menus.rs) built from the command
registry drives the native macOS menu bar (craft-menu's muda backend) and
an in-window egui bar on Windows, Linux and the web, which takes the place
of the "Menu" button at the start of the top bar.

- File, Edit, View, Sign, Window and Help, with separators and submenus.
  Edit gains Cut, Paste, Delete and Select All (they act as their keys
  do) and a Pages submenu; View gains a Tools submenu built from the All
  tools groups; Sign holds Fill & Sign and the certificate commands;
  Window lists the open documents.
- Toggles are check items and the page layouts, side panels and themes
  radio items, with their state from the app.
- File > Open Recent lists the recent files (file.open_recent with an
  index).
- A menu click and its shortcut run the same dispatch, including the
  form-field deferral (#166) and macOS Cmd+W closing the window (#869).
  Dialogs and the palette grey the menus through craft-menu's gate.
- Quit goes through craft-quit, so the unsaved-changes prompt runs.
- craft-ui, craft-quit, craft-menu and craft-keymap pinned to
  storytold/craft-libs 70ade1fb.

Menu state and dispatch follow #2610 by @hajdukste; the menu tree and the
view and panel items follow #3870 by @RivanJarjes.

* test(ui): the menu model, native dispatch and the in-window bar

- Unit tests for the menu definition: every registry menu command is
  listed once with its registry shortcut; check and radio items follow
  the app; a dialog greys document commands and drops their clicks; the
  recent files and open documents are listed once each; a native menu
  click (through a fake craft-menu backend) runs its command once.
- The kittests that opened the old "Menu" button use the menu bar, and
  the ones that look up a label the bar's titles share (Edit, File,
  Sign) skip the bar.
- The in-window bar takes its titles' width, so the mode tabs, search
  and buttons keep their place in the top bar.
- Edit menu clicks that stand for keys (Copy, Cut, Delete, Select All)
  reach this frame's input from `logic`, where test hosts see them too.

* test(ui): the language switch test opens File in the menu bar

Translated titles and rows carry their mnemonic ("ファイル(F)", "開く(O)…").

* feat(ui): the menu bars run Quit themselves, the bar shrinks to its titles, and AppKit's window list stays off

craft-libs main edc41f6 (craft-libs#354): MenuBarUi runs Quit and Minimize, shrink_to_fit sizes the in-window bar, Key::parse reads the registry's arrow and minus glyphs, and mac::install_with(.., window_list(false)) keeps the Window menu to PdfCraft's own document list.

* fix(ui): the menu command filter is an if, for clippy

* test(ui): drop a needless borrow in the edit-tab helper

* test(ui): two tests follow the menu bar

The tests were wrong, not the app. The Preferences language test clicked the old Menu button; it opens Edit in the menu bar and its Preferences row. The prepare Actions test counted the Edit labels: the narrower menu bar leaves room for the Edit tab, so the rows' Edit buttons come third and fourth.
2026-10-26 00:57:46 -04:00
Combiler01 3dfcff3884 Merge remote-tracking branch 'upstream/main' into main 2026-10-26 10:24:50 +05:30
Combiler01 2c0588eaf6 Self Reviewed Bug Fixes 2026-10-26 10:12:01 +05:30
aiongg 01b696df2c test(ui): CJK interface tests skip without craft-fonts (#4706)
* test(ui): CJK interface tests skip without craft-fonts

* test(ui): a build that requires craft-fonts never skips the CJK interface tests
2026-10-25 23:11:45 -04:00
songzhihui 46fbb73d1a M2: address CJK viewer review and sync main 2026-10-26 09:03:41 +08:00
Dylan M. Taylor 13bfbd0761 Merge pull request #4737 from storytold/chore/deps-safe-refresh
chore(deps): bump 11 safe dependency updates
2026-10-25 19:03:52 -04:00
skylar fc578a0b02 chore(deps): bump 11 safe dependency updates 2026-10-25 16:36:22 -06:00
alehelandClaude Opus 5.5 323f569b37 docs: point form-performance at the engine's session.rs and edit.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:15:03 +01:00
alehelandClaude Opus 5.5 00596b69a6 refactor(engine): keep the moved docs' intra-doc links resolving (document, fill_sign)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:12:15 +01:00
alehelandClaude Opus 5.5 dc224884cb refactor(engine): gather lib.rs's imports and re-exports under the module list
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:10:38 +01:00
alehelandClaude Opus 5.5 51fbed293b refactor(engine): move the test-only hooks to test_hooks.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:09:57 +01:00
alehelandClaude Opus 5.5 8ca4bf641f refactor(engine): move the page filters to page_filter.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:09:38 +01:00
alehelandClaude Opus 5.5 fc6072154d refactor(engine): move the Fill & Sign geometry helpers to fill_sign.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:09:23 +01:00
alehelandClaude Opus 5.5 94b6110819 refactor(engine): move placed pictures and mark sources to pictures.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:09:11 +01:00
alehelandClaude Opus 5.5 8473403a5d refactor(engine): move layer visibility to layers.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:08:38 +01:00
alehelandClaude Opus 5.5 4972a4e1eb refactor(engine): move the bookmark list rows to bookmarks.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:08:14 +01:00
alehelandClaude Opus 5.5 20974e7822 refactor(engine): move the combine and page-source helpers to sources.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:08:01 +01:00
alehelandClaude Opus 5.5 71dccdda50 refactor(engine): move the comment list and summary to comments.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:07:23 +01:00
alehelandClaude Opus 5.5 748b20abb9 refactor(engine): move the XFA form helpers to xfa_form.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:05:58 +01:00
alehelandClaude Opus 5.5 ff361fe138 refactor(engine): move Document and its display helpers to document.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:05:07 +01:00
alehelandClaude Opus 5.5 c4dc7e9126 refactor(engine): keep the moved docs' intra-doc links resolving
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:02:51 +01:00
alehelandClaude Opus 5.5 5b75180759 refactor(engine): move Session and its operations to session.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:02:51 +01:00
alehelandClaude Opus 5.5 f148915730 refactor(engine): move the other crates' re-exports to reexports.rs
The doc comment for SplitPart had drifted above the LabelStyle re-export; it is back on SplitPart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:02:51 +01:00
alehelandClaude Opus 5.5 05098de542 refactor(engine): move the open pipeline to open.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:02:51 +01:00
alehelandClaude Opus 5.5 631fc13f13 refactor(engine): move protection and the security summary to protection.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:02:51 +01:00
alehelandClaude Opus 5.5 58e7e36717 refactor(engine): move the Edit enum, its labels, permission check and runner to edit.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:02:50 +01:00
alehelandClaude Opus 5.5 7e72b583ed refactor(engine): move EditError and SourceProblem to error.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:02:50 +01:00
alehelandClaude Opus 5.5 ecf6231416 refactor(engine): move undo/redo state and labels to history.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:02:50 +01:00
Dylan M. Taylor 83ba28680f build: pin craft-update to craft-libs main (495e597) (#4728)
Picks up the startup prompt without the glyphs a font may lack (storytold/craft-libs#343, #344): the close mark is painted and the version pill is ASCII, so apps that panic on a missing glyph no longer take the prompt down.

Part of tracking issue storytold/craft-libs#319
2026-10-25 17:54:18 -04:00
Dylan M. TaylorandCodebuff cda2d019e3 build: pin craft-update to craft-libs main (495e597) (#4721)
Picks up the update prompt fix that draws its close mark and version pill
without glyphs a font may lack, so the prompt no longer takes the app down
when a bundled face has no ✕ or →.

Part of tracking issue storytold/craft-libs#319

🤖 Generated with Codebuff

Co-authored-by: Codebuff <noreply@codebuff.com>
2026-10-25 17:37:06 -04:00
alehelandClaude Opus 5.5 5815dbfb79 docs: page modules claim the cursor, never set it
A "Page cursor" non-negotiable in CLAUDE.md, under "Page input": page modules claim the
cursor for what is under the pointer (view.cursor.claim, crates/ui-egui/src/page_cursor.rs)
and never call set_cursor_icon, write cursor_icon or use on_hover_cursor; document_area
sets it once, from the claim on the highest layer the active tool reaches. It names the
test that enforces it, page_pointer::tests::page_modules_claim_the_cursor_and_never_set_it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 21:54:55 +01:00
alehelandClaude Opus 5.5 61f92a1d0c test(ui): page modules claim the cursor and never set it
A sibling of page_modules_read_the_pointer_only_through_page_pointer in page_pointer.rs,
sharing its scan of the page modules (now `misuses`, which both tests call): it fails on
set_cursor_icon, a `.cursor_icon = ` write to egui's output, on_hover_cursor and
on_hover_and_drag_cursor in a page module, naming the file and line and pointing to the
claims in src/page_cursor.rs. Only the resolver, which is not a page module, sets the
pages' cursor. NOT_PAGE_CURSOR allows the three writers that are not the pages' cursor:
the signature dialog's Scan tab (fill_sign::scan_tab), the paragraph editor's frame
(edit_text_ui::overlay, an Area of its own drawn after the pages' cursor is set) and the
Calculation order panel, whose rows are dragged to reorder them (calc_order_ui::panel). An
entry that sets no cursor any more fails too.

Seen failing with a set_cursor_icon put back into canvas.rs's link loop, an output_mut
cursor write in forms_ui, an on_hover_cursor in comments and the Scan tab's write
removed: one message each. The pointer guard still fails on a hover_pos() read put
into crop.rs. Rebased onto main's newer page modules, it named one write before the panel's
entry was added, calc_order_ui.rs's Grabbing: main's other new cursors are claims now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 21:54:55 +01: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
alehelandClaude Opus 5.5 42da7f2473 fix(comments): delete or edit one reply from its own menu, keeping the thread
In the Comments panel a reply had no menu, and the card's Delete removes
the whole thread, so one wrong reply couldn't go without the comment and
every other reply. On the selected card each reply now has its own "…"
(Edit, Copy text, Delete), as Adobe's help describes. Delete takes that
reply with its pop-up and the replies to it, as comment_delete already
did headlessly. The comment and its other replies stay, in one undo step.
A locked reply's Delete is greyed out. Status badges and a Replace Text
caret get no menu.

Deleting moves what follows it up in /Annots. Before, any delete cleared
the selection, and the panel's inline editor stayed keyed to an index
that could now be another comment. CommentView::annotation_deleted now
finds both where they moved, from their place among what stays and what
they read, or clears them when they went too or the page changed
unexpectedly.

The new string is in every catalogue. Parity notes, the roadmap row and
comment_delete's description say a reply can be deleted on its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 21:22:15 +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