Commit Graph
3518 Commits
Author SHA1 Message Date
Combiler01 b8b07d0a99 Re-resolve image selection by XObject and rect across undo/redo
When undoing or redoing an edit (such as Arrange), re-resolve any active image selection to match the same image's XObject, name, and position rather than leaving the selection stuck on the old index (which could point to another image). If ambiguous or no longer present, clear the selection.
2026-10-25 16:47:24 +05:30
Combiler01 fa213cfad0 fix(ui): show selection font/size in format panel and add missing accessibility labels 2026-10-25 16:16:59 +05:30
Claude f75af23d4e M6: deleting a radio button turns the group off when no button left shows its choice
CodeRabbit on #4631: a twin with the same on-state name that is currently
off does not keep the choice visible, so compare the remaining buttons'
appearance state (/AS), not their on-state name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LymhVaGZCWwB9HNEbLzeMM
2026-10-25 10:26:51 +00:00
RiceDee e6e57a9a07 fix(ui): formatting restarts a text box's history; the menu claims the box first
Three holes in the shared text box's history:

- Formatting applied with the paragraph open left typing_from as it
  was, so when the format left the text unchanged the box kept its
  history, and Redo put typing undone before the format back on top of
  it. applied() now resets typing_from, so the next claim clears it.
- Undo or Redo from the menu in the same frame as a tab switch ran
  before the box was drawn, on the history of the other tab's box: Undo
  put tab B's text into tab A's box, and Redo put A's undone typing into
  B's. undo_in_page_box and redo_in_page_box now make the box claim the
  text box first (claim_typing), as drawing it does.
- A box new to the text box cleared only the last box's history and
  kept its caret and selection; it now starts from a default state.

claim_typing on LineEditor and TextDraft is now the one way a box takes
the text box, for drawing and for the menu.

Carried from #4179 (aa326f37). On this branch the rows' alignments by
text restart with the history, so a Redo past a format can't bring
back an alignment from before it.
2026-10-25 21:22:57 +11:00
RiceDee aa326f37cd fix(ui): formatting restarts a text box's history; the menu claims the box first
Three holes in the shared text box's history:

- Formatting applied with the paragraph open left typing_from as it
  was, so when the format left the text unchanged the box kept its
  history, and Redo put typing undone before the format back on top of
  it. applied() now resets typing_from, so the next claim clears it.
- Undo or Redo from the menu in the same frame as a tab switch ran
  before the box was drawn, on the history of the other tab's box: Undo
  put tab B's text into tab A's box, and Redo put A's undone typing into
  B's. undo_in_page_box and redo_in_page_box now make the box claim the
  text box first (claim_typing), as drawing it does.
- A box new to the text box cleared only the last box's history and
  kept its caret and selection; it now starts from a default state.

claim_typing on LineEditor and TextDraft is now the one way a box takes
the text box, for drawing and for the menu.
2026-10-25 21:15:26 +11:00
Combiler01 42b2a3ebdb fix(fonts): subset added TrueType fonts to used glyphs on document save 2026-10-25 15:35:28 +05:30
Lcoding15 e3662af3a5 M6: Delete removes one selected radio button, not its whole group (#4609)
Prepare Form deletes the selected widget: one radio button of a group or
one copy of a duplicated field, as Acrobat does; a field's last widget
takes the field with it. The widget leaves its page, the field's /Kids
and /CO; a per-widget /Opt loses its entry, and a group that was on only
through it turns Off. form_delete_field takes an optional widget.
2026-10-25 10:56:17 +01:00
Combiler01 4696dd2f42 fix(engine): enforce font discovery total budget before file reads and limit after sort 2026-10-25 15:07:47 +05:30
RiceDee bfc6f8b1b9 fix(ui): a text box keeps its typing history through a trip to another tab
The tabs share one text box, and its Undo history was cleared whenever
another document's box took it: opening a paragraph in tab B wiped tab
A's typing steps, so Edit ▸ Undo back in A found none and closed A's
paragraph instead of taking the typing back. The box being left now
keeps a copy of its history and caret aside, under its document, and
the same box gets it back on its return; a box new to the text box
still starts with none, so typing still never reaches another tab. The
copy is a deep one: a state's history is shared between its clones.

Carried from #4179 (abc378c0). On this branch the rows' alignments by
text go aside and come back with the history, so Undo after the trip
still restores a deleted row's alignment.
2026-10-25 19:19:20 +11:00
Combiler01 338211cdbf fix(edit): preserve clipping and graphics state when rearranging marked content 2026-10-25 13:46:48 +05:30
RiceDee abc378c0bb fix(ui): a text box keeps its typing history through a trip to another tab
The tabs share one text box, and its Undo history was cleared whenever
another document's box took it: opening a paragraph in tab B wiped tab
A's typing steps, so Edit ▸ Undo back in A found none and closed A's
paragraph instead of taking the typing back. The box being left now
keeps a copy of its history and caret aside, under its document, and
the same box gets it back on its return; a box new to the text box
still starts with none, so typing still never reaches another tab. The
copy is a deep one: a state's history is shared between its clones.
2026-10-25 19:10:46 +11:00
alehelandClaude Opus 5.5 e71a946c0f fix(edit): a backdrop is only a fill the graphics state paints plain white
Review of #2815 (FredApps): two ways the detector took real content for a
page's own white backdrop and replaced it by default.

- A `gs` sets only the keys its dictionary has, but each one replaced the
  whole opacity decision and read every key it left out as its default,
  so `/Transparent gs /LineOnly gs 1 g ... re f` (ca 0.5, then only a
  line width) counted as opaque. Fill alpha, soft mask and blend mode are
  now kept one by one, through `gs` and `q`/`Q`.
- A transfer function was never read: TR or TR2 mapping 1 to 0 paints
  `1 g` black, and the fill was still replaced. The transfer function is
  tracked too, TR2 over TR in the same dictionary, and only /Identity
  (and /Default for TR2) counts; anything else leaves the fill alone.

Tests: nine graphics states that must keep the fill (each also checks Add
Background leaves the content as it was) and nine that still find the
backdrop (keys set back, TR2 /Default over a TR function, q/Q restoring),
plus an end-to-end render of the reviewer's translucent case under a red
background. All the keep cases fail on the code before this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 08:31:05 +01:00
RiceDee c9abf15e7c fix(edit): only a row aligned on its own is rewritten
A nonempty aligns counted as a style change, so every row of the
paragraph was rebuilt from the text state: rows left unselected lost
their source operators, and with TJ word gaps their words moved by
about a point and a half while the row's outer width stayed the same.
aligns no longer makes an edit non-plain; a row whose own line has an
alignment override is rewritten, and the others keep their operators.
2026-10-25 16:57:41 +11:00
RiceDee 2f50e1fde7 fix(ui): typing Undo brings back a deleted row's alignment
egui's typing history keeps only text and caret, so Undo bringing back
a deleted row or line (added text, or a paragraph) gave it the
alignment of the row before it, and finishing the box could save that.
Each page box now keeps the texts it has shown through its own typing,
with every row's alignment then (follow_typing): Undo or Redo back to
one of them restores those alignments, and a new text follows the edit
with remap. An alignment picked since makes the older ones stale; they
are dropped, so Undo never takes back a pick. The record is cleared
with the box's typing history and holds 100 texts, as egui's does. It
covers typing, the keys and the menu's Undo and Redo.

On this branch the two-tab test also runs the menu's Redo in the copy,
where the box, with no Redo of its own, passes the step on and closes;
that step no longer requires the editor to stay open.

Also carries #4179's stronger two-tab test (dd47c4f9): the copy's
paragraph must be open before the key Redo and after it.
2026-10-25 15:48:04 +11:00
RiceDee b7f7b27f8f fix(ui): menu Undo takes back typing in the open text box first
Edit ▸ Undo with typing in a paragraph or added text box that the page
doesn't have yet applied the typing (a new edit, which dropped the
document's waiting Redo), closed the box and undid the typing all at
once; the key takes it back a step at a time in the box. Menu Undo now
goes to the box first, as Redo does, and is enabled while the box holds
such typing. Undo and Redo share typing_step for the box's own history.
2026-10-25 15:28:38 +11:00
RiceDee 6901f66876 Merge main into feat/align-selected-lines
Main took integration batch 6 (#4566). Conflict in edit_text_ui.rs: the
overlay keeps this PR's view_id with main's interact_pos comment. The
two TextDraft literals from #4441's tests take selection and
typing_from.
2026-10-25 15:25:11 +11:00
jonathanwxh-cell 522653d6b1 fix(organize,print): join /Contents array pieces with a space (#3502) 2026-10-25 12:03:40 +08:00
aiongg 418ac6a2af fix(ui): the page ignores the pointer while a panel is being dragged (#4581) 2026-10-24 22:12:46 -04:00
Claude 4c09747773 M4: bookmarks from text styles need copying permission and keep columns apart (#1693)
CodeRabbit on #4560:
- The bookmark titles are the document's text, so the edit now needs
  copying permission as well as assembly, and Document::text_styles (the
  dialog's samples) is empty when copying is withheld; the automation
  text_styles tool already checked it.
- A heading went on to the next row whenever it was close below in the
  same style, so same-style headings in two columns on staggered rows ran
  together into one bookmark. A line continues a heading only when it lies
  under it.
- Lines found their style by a scan over every style so far; they are now
  indexed by font, weight, slant and quarter-point size, so a page with
  thousands of styles no longer makes the scan quadratic.

Regression tests for the first two (each fails without its fix).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LymhVaGZCWwB9HNEbLzeMM
2026-10-25 01:14:19 +00:00
jonathanwxh-cell 24edde6563 forms: clear typed XFA password field values from datasets on save (#3944) (#4506) 2026-10-24 21:08:21 -04:00
Dylan M. Taylor aa9a9105fc fix(redact): remove AcroForm on Hidden::FormFields even when field count is zero (#4571)
* fix(redact): remove AcroForm on Hidden::FormFields even when field count is zero

* fix(redact): report AcroForm presence in scan() and format
2026-10-24 21:07:16 -04:00
Dylan M. Taylor 4c69b8cf88 fix(forms): align has_password_flag traversal depth with field enumeration (#4570)
* fix(forms): align has_password_flag traversal depth with field enumeration

* fix(forms): extend field_chain depth to 64 and fix test formatting
2026-10-24 21:07:09 -04:00
Dylan M. Taylor f4f91d0d5c fix(xfa): require full save when stripping XFA packets (#4568) 2026-10-24 21:03:25 -04:00
RiceDee 08ec1db79b Merge main into fix/undo-in-open-text-editor
Main took integration batch 6 (#4566). Conflict in edit_text_ui.rs: the
overlay keeps this PR's view_id and main's switch to interact_pos for
clicks outside the document.
2026-10-25 11:36:47 +11:00
aiongg 0140f1047c Integration: the page input guard also catches egui's per-button pointer reads (#2685) 2026-10-24 23:56:44 +00:00
aiongg 756660f6d2 Integration: the arrow keys leave a reply to an unnamed comment where it is (#4061) 2026-10-24 23:55:10 +00:00
aiongg 80d8c60ac2 Integration: no link or comment tip beside the open palette (#2685) 2026-10-24 23:23:47 +00:00
aiongg bb06649527 Integration: two comments whose /IRTs point at each other stay comments of their own (#4061) 2026-10-24 23:23:47 +00:00
aiongg e0c319d273 Integration: a comment listed through a reference chain keeps its replies after an edit (#4061) 2026-10-24 23:18:59 +00:00
Lcoding15 4ce68f517b M4: New bookmarks from text styles, for headings that aren't tagged (#1693)
Bookmark options > New bookmarks from text styles... lists the styles the
document's text is set in (font without its subset tag, the size shown,
bold, italic, how many lines, a sample), the largest first, and takes one
style per bookmark level. Every line in a level's style becomes a
bookmark of that level under a new first "Untitled" bookmark, as from
structure; a heading set on several rows is one bookmark, and each goes
to the top of its heading.

The detection lives in engine::text_style_bookmarks on top of
edit::text_lines, reading each page top to bottom then left to right, so
two headings on one row stay two. text_styles and
bookmark_from_text_styles reach it headlessly, and the command
bookmark.from_text_styles opens the dialog. OutlineEntry gets an
optional top for the /XYZ destination; structure bookmarks keep theirs.
2026-10-25 01:03:08 +02:00
aiongg 73240d978f Integration: M3: All tools expands to a full, searchable view (#2460) 2026-10-24 22:48:11 +00:00
aiongg e5005f062a Integration: fix(ui): one page pointer per frame, so taps work and nothing over a page lets it through (#2685) 2026-10-24 22:48:11 +00:00
aiongg d821f91001 Integration: fix(comments): thread replies by their /IRT reference, count comments like Acrobat (#4061) 2026-10-24 22:48:11 +00:00
aiongg f94b34adc9 Integration: fix(redact,cos): cleared redaction marks leave nothing behind (#4226) 2026-10-24 22:48:11 +00:00
aiongg 78dbea442e Integration: M3: the community card on Home can be closed for the rest of the run (#2617) 2026-10-24 22:34:42 +00:00
RiceDee dd47c4f9e1 test(ui): the two-tab Redo test checks the copy's paragraph stays open
If the copy's editor had closed before or during Redo, the check for the
other tab's typing passed on an empty text. It now requires the editor
to be open before Redo and after it.
2026-10-25 08:56:55 +11:00
RiceDee f3b61b73b0 style: rustfmt the two TextDraft test literals that took typing_from 2026-10-25 08:41:17 +11:00
RiceDee dd6e86b6cb Merge pull request #1 from aiongg/fix-4179-main
Merge main; set typing_from in two tests from #4441
2026-10-25 08:37:19 +11:00
RiceDee b9035271b0 fix(ui): menu Undo takes back typing in the open text box first
Edit ▸ Undo with typing in a paragraph or added text box that the page
doesn't have yet applied the typing (a new edit, which dropped the
document's waiting Redo), closed the box and undid the typing all at
once; the key takes it back a step at a time in the box. Menu Undo now
goes to the box first, as Redo does, and is enabled while the box holds
such typing. Undo and Redo share typing_step for the box's own history.
2026-10-25 08:36:17 +11:00
alehelandClaude Opus 5.5 120de553ab fix(ui): the Background preview follows the Replace checkbox
The dialog previews a colour as a tint of the page's thumbnail (main's #3009). It tinted every
page, so with Replace unticked a page that keeps its own white backdrop previewed coloured,
though OK leaves it white. A page a picture covers (a scan) previewed coloured too.

background_tint() now shows no colour where the page will still hide the background after OK:
a scan, or a white backdrop with Replace unticked. It reads the page from the dialog's backdrop
check (BackdropCheck::found, for the current version of the document only); a page the check
hasn't reached yet previews coloured, as before.

Tests, both seen failing with the fix reverted:
- marks_ui::tests::the_preview_shows_a_background_only_where_ok_lets_it_show (the rule);
- it::marks::the_background_preview_follows_the_replace_checkbox (what the dialog paints, per
  page, ticked and unticked).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 23:06:34 +02:00
aiongg ac868f6e29 Tests: new text box drafts set typing_from (main's #4441 tests) 2026-10-24 18:58:44 +00:00
aiongg 43708cf5dd Merge remote-tracking branch 'pr/4179' into lead-check-4179 2026-10-24 18:42:33 +00:00
alehel ad8c2f46dd Merge origin/main into fix/comment-threads-by-irt
Conflicts:
- crates/render/src/inspect.rs: this branch's reply-linking methods and main's NoRotate
  rectangles (#3016) both go in the Annotation impl; the annotation reader takes this
  branch's loop with main's no_rotate field.
- crates/automation/src/comments.rs: this branch's HashMap import, main's import list.
- ATTRIBUTION.toml: each catalogue's source note keeps main's secured-document clause and
  this branch's reply-count clause; ATTRIBUTION.md regenerated (cargo xtask assets --write).
- The 33 catalogues: main's new rows, then this branch's reply-count row.

main's #3016 tests build Annotation field by field; they now set parent and grouped.
2026-10-24 20:42:11 +02:00
alehel 4283936293 Merge origin/main into fix/document-properties-layout
Conflicts: optimize_ui.rs imports (this branch's RichText, main's Compatibility; both kept)
and the organize.document-properties parity entry: both evidence lists kept, main's XMP note
kept, and the tab list now names main's Custom tab (#4387) instead of saying there is none.
2026-10-24 20:37:55 +02:00
aiongg a7801dc84e fix(ui): text typed into a new text box counts as unsaved work (#4441)
* fix(ui): text typed into a new text box counts as unsaved work

Closing a tab or quitting while the only unsaved thing was text in a new Add text box skipped the save prompt and lost the text. The box now counts like fill text does: only when it holds non-blank text.

* fix(ui): saving on close keeps text typed into a new text box
2026-10-24 14:25:44 -04:00
alehel bd7dc0fe8c Merge remote-tracking branch 'origin/main' into fix/background-hidden-by-page-backdrop 2026-10-24 17:35:17 +02:00
Lcoding15 80f919977b M2: page_render and render_preview report the render warnings next to the PNG (#4340)
The renderer reports when a page's picture is incomplete or approximate
(content cut at the page's safety budget, a colour painted in a fallback
colour), but only `pdfcraft-cli render` showed it. `page_render` and
`render_preview` returned the PNG alone, so a script or MCP client could
not tell that the image was not the whole page.

Both tools now return a JSON part after the PNG when the page has
warnings: `{ "page": 1, "warnings": [...] }`, with the sentences the CLI
prints. Warnings are strings, like every other tool's `warnings`, so
`command_run` can add its own to the same list. A page without warnings
still returns the PNG alone, so existing callers see no change.

The viewer notice the issue also asks for is left for a separate change.
2026-10-24 17:10:23 +02:00
RiceDee 25da4b1894 Merge main into feat/align-selected-lines
Conflict in automation/tests/it/automation.rs: this PR's two aligns
tests and main's editable combo box test (#4451) were added at the same
place; all three are kept.
2026-10-25 02:08:55 +11:00
Brandon ThomasandClaude Opus 5.5 d445410807 Order the render integration-test modules (cargo fmt) (#4486)
#4430's merge with main left mod type3_text_clip before mod tiling_cell_placement,
so main's fmt check failed.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 10:57:58 -04:00
alehelandClaude Opus 5.5 866de03fd8 fix(ui): a dialog covers the pages from the frame it opens
The page pointer has the pointer only where egui has the pages on top, and egui knows what
is drawn over them from the layers it met in the frame before. Dialogs are drawn after the
pages, so on the frame one opens egui has not met its layer yet. For that frame the pages
kept the pointer, and a press that landed then was theirs: a link's tip showed over the
dialog, and a click placed a stamp or followed a link under it. main's page_covered had
kept the link's tip and click off the page while a dialog was open, from a hand-kept list.

PagePointer::read now also takes whether a dialog is open: PdfCraftApp::modal_open, the
list the page press and the edge scroll already check, the Unlock prompt included. With one
open the pages see no pointer and no press, except a drag they already have, which goes on
to its release. The palette is not a dialog and the page beside it keeps its tools, so it
is left to egui's layers, from the frame after it opens.

Tests: tests/it/pointer.rs hovers a link and opens the Unlock prompt (no tip in that
frame), and clicks with the Stamp tool in the frame the prompt opens (no stamp). A
page_pointer unit test hides the pointer and a click under a dialog, and keeps a drag the
pages have until its release. With the fix reverted, all three fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 16:54:28 +02:00