Commit Graph
4058 Commits
Author SHA1 Message Date
Dylan M. Taylor 90bd6660f7 Merge pull request #4220 from Jayesh-Prajapati9/chore/ctutils-rustsec-2026-0385
M14: update ctutils to 0.4.5 for RUSTSEC-2026-0385
2026-10-23 10:11:03 -04:00
eliets2 4d451834ed Update ctutils to fix RUSTSEC-2026-0385 2026-10-23 16:47:45 +03:00
eliets2 1481eb1dda Update ctutils to fix RUSTSEC-2026-0385 2026-10-23 16:39:36 +03:00
Guillem Hermida cc6fa0402f deps: ctutils 0.4.5 for RUSTSEC-2026-0385 2026-10-23 15:27:28 +02:00
Jayesh-Prajapati9 866a28a93e M14: update ctutils to 0.4.5 for RUSTSEC-2026-0385
The dependency-advisory gate rejects ctutils 0.4.2 (non-constant-time
mask generation and u128 comparisons, fixed in 0.4.4). It comes in
through crypto-bigint for the signing crates. Only its version and
checksum change in Cargo.lock.

(cherry picked from commit 1f8811761a)
2026-10-23 18:41:11 +05:30
Jayesh-Prajapati9 3b9cfe1d52 Tests: UI tests wait for background work by time, not frame count (#4186)
A frame count is not a time: on a busy runner the worker had not finished
the open, or the page text had not arrived, within 30 frames or 50 hover
rounds. support::step_until (#[must_use]) steps frames until a condition
holds or 30 s pass, sleeping briefly between frames, and says whether it
held; each test asserts on that with its own message.

- control_open_replies_pending_while_the_worker_opens_and_focuses_once_it_has
  and mode_tabs_keep_the_hand_over_page_text_scrolled_under_them wait with it,
  and so does control.rs call() for its reply.
- support::settle waits for every pending render and fails if the pages are
  not rendered within 30 s. It replaces all the render-pending settle loops
  (40 to 3000 frames) and the local settle/wait_for_pages wrappers, except
  text_select's double-click wait, which is held to 8 frames on purpose to
  stay inside egui's multi-click window, and view.rs's loops that already
  wait by a deadline.
- wait_until is built on step_until, keeping its diagnostic message.
- tabbing_back_to_the_same_field_then_saving_keeps_the_committed_value already
  waited 30 s for its file. It failed because its "same-frame" case shared
  its scratch directory with typing_and_saving_in_the_same_frame_saves_the_last_keystrokes,
  which runs in the same test process (since ee1ee704, #1045) and deleted
  the directory mid-save. forms' and pickers' scratch() now give every call
  a directory of its own.

Fixes #4186
2026-10-23 18:40:53 +05:30
mtlcl410andClaude Opus 5.5 9efc1727a1 M6: default names skip empty parents; a full name under the same parents is a plain rename (#2696)
Review follow-up on #2957:
- free_name picked a default name (Text1) that a parent field left empty
  by a move still held at the top of /Fields, and attach then refused it
  for every new field of that kind. The own names (/T) of the top-level
  /Fields nodes are now skipped too.
- the editor sends nested fields' full names, so renaming address.city to
  address.town took the move path: it copied the inherited keys onto the
  field and moved it to the end of the parent's /Kids. A full name whose
  parents are the field's current parents now renames only its own /T.

Field tree per ISO 32000-2 12.7.4 (Arlington Field*.tsv: /Parent, /Kids,
/T; FT, Ff, V, DV, DA, MaxLen inheritable).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
2026-10-23 20:48:22 +08:00
Jayesh-Prajapati9 1f8811761a M14: update ctutils to 0.4.5 for RUSTSEC-2026-0385
The dependency-advisory gate rejects ctutils 0.4.2 (non-constant-time
mask generation and u128 comparisons, fixed in 0.4.4). It comes in
through crypto-bigint for the signing crates. Only its version and
checksum change in Cargo.lock.
2026-10-23 18:15:48 +05:30
Jayesh-Prajapati9 3225b030f1 M14: update ctutils to 0.4.5 for RUSTSEC-2026-0385
The dependency-advisory gate rejects ctutils 0.4.2 (non-constant-time
mask generation and u128 comparisons, fixed in 0.4.4). It comes in
through crypto-bigint for the signing crates. Only its version and
checksum change in Cargo.lock.

(cherry picked from commit 8a687ee2c6)
2026-10-23 18:15:26 +05:30
Guillem Hermida afb25864cd deps: ctutils 0.4.5 for RUSTSEC-2026-0385 2026-10-23 14:36:43 +02:00
Guillem Hermida bde5c37058 deps: ctutils 0.4.5 for RUSTSEC-2026-0385 2026-10-23 13:11:16 +02:00
Combiler01 30e6cb3f24 Test Fixes 2026-10-23 16:34:22 +05:30
Combiler01 45fe40e1d7 Dependencies Fix 2026-10-23 16:08:07 +05:30
Combiler01 fa11f650cd Fixes Pasting Images having broken Coordinates 2026-10-23 15:44:04 +05:30
Guillem Hermida 07f0faf4d5 tests: the NoRotate test's annotation has main's points field too 2026-10-23 12:13:07 +02:00
mtlcl410andClaude Opus 5.5 eed2afa165 fix(forms): an inherited signature value and a direct /AcroForm are handled when filling (#3441)
Review follow-ups on filling a field converting inline /DR fonts:
- A signature field whose /V sits on its parent counted as unsigned, so
  filling another field rewrote /DR in a signed form. The check now
  follows the /Parent chain (bounded at 64, like the field walk).
- A catalog holding /AcroForm as a direct dictionary was skipped; its
  inline fonts are now converted and written back into the catalog.

Both tests failed before the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
2026-10-23 18:00:42 +08:00
Osvaldo DuranandClaude Sonnet 5.5 e1b6401475 test(ui): the attachments panel snapshot lists the Attach and Delete buttons
The panel now has an "Attach a file…" button and a "Delete attachment" button; the side-panel
snapshot test (every_panel_matches_its_snapshot) still expected the read-only panel.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 05:26:51 -04:00
Guillem Hermida cefbefbef9 deps: ctutils 0.4.5 for RUSTSEC-2026-0385 2026-10-23 11:25:36 +02:00
Combiler01 3641801f13 Fixes: Refactored System not opening for Added/Pasted Images 2026-10-23 14:49:01 +05:30
eliets2 64d8c20584 Leave the page grid when the Crop tool is chosen
Crop is only listed in the Organize pages panel, which is shown together with the thumbnail grid; activating it there must bring the document view back, otherwise the tool is selected but no page is on screen to drag a crop rectangle on.

In the "page.crop" command arm, close the organize grid of the active view before arming the Crop tool, the way opening a page from the grid does. The left panel then falls back to the workspace panel on the next frame, as it already does when the grid closes by other means.

Test: the_crop_tool_leaves_the_page_grid_so_a_page_can_be_cropped failed before the change and passes after.
2026-10-23 12:05:49 +03:00
Guillem Hermida 33aea965ee style: rustfmt the cancellation test 2026-10-23 10:53:16 +02:00
Jayesh-Prajapati9 3e9988954a M7: CR and CRLF break rows on the styled rewrite path too (#4199)
A multi-line or multi-style draft is laid out by rewrite_styled_block, which broke rows only at \n: a CRLF left a stray CR before the break and a lone CR broke nothing. The CR of a CRLF is now skipped and a lone CR breaks the row, keeping the draft's character positions, and a draft whose only break is a lone CR takes the same path as one with \n.
2026-10-23 14:15:17 +05:30
luis.pereiraandClaude Opus 5.5 60985e119c Edit text: a dash or line cap partway through an outlined line starts a run (#3508)
Review follow-up for #3551: two ExtGStates at the same opacity are the same
Paint, so a dashed outline after a solid one joined its line and was
rewritten with the first run's selections. Ts tracks the effective dash
pattern and line cap, from d and J and from an ExtGState's /D and /LC, and
same_paint compares them as values for stroked text, so states with the
same settings under other names still continue a line. A dash array longer
than 32 entries, or with an operand that isn't a finite number, and a cap
outside 0-2 are ignored.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 09:35:03 +01:00
luis.pereiraandClaude Opus 5.5 9163f29090 Edit text: per-run q/Q, /LW from gs, and a Tr partway through a line starts a run (#3508, #3509)
Review follow-up for #3551, rebased on the gs grouping of #3310:
- The styled rewrite draws each run in its own q/Q from the state where
  the rewrite is drawn: a gs has no inverse, so one run's ExtGState stayed
  in force for the runs after it. Two ExtGStates at the same opacity share
  a paragraph (Paint), but either may set what Paint doesn't compare.
- The gs arm applies an ExtGState's /LW to the line width; paint_ops
  restates the effective width after any replayed gs.
- A run whose visible Tr (or, for stroked text, stroke colour and width)
  differs no longer joins the line before it, so a line is not rewritten
  in its first run's state. Clipping and invisible modes still join, as
  the #3447 refusal reads the whole line; gs changes are already kept
  apart by Paint.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 09:27:47 +01:00
cadelassandClaude Opus 5.5 6d767cc6fc Edit text: keep Tr, stroke, line width and gs set inside BT when rewriting a paragraph (#3508, #3509)
A rewritten paragraph is drawn before its text object and restated only the
font, spacing and fill. Anything the text object set before its first show was
lost: `1 Tr 1 0 0 RG 2 w` inside BT turned red outline text into filled black
text, and `/GS1 gs` inside BT made semi-transparent text opaque, even when the
text was unchanged.

The interpreter now tracks stroke colour, line width and the ExtGStates
selected (bounded to 32 distinct names), and the rewrite restates whatever
differs from the state in force where it is drawn. Styled runs restate it per
run, too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 09:27:46 +01:00
Jayesh-Prajapati9 d9115bb90b M7: edited text keeps every space typed (#4199, #3797)
Rewriting a paragraph in one style split its text at white space and
joined the words with one space, so a run of spaces, an indent or a
trailing space typed in the editor was saved as one space or none. The
text is now kept as typed (other white space becomes a space, and CR or
CRLF become line breaks), and the wrapper keeps each run of spaces,
dropping only the spaces where a row wraps.

A justified (stretched) row still drops its indent and the spaces after
its last word, as before, so word spacing reaches only the spaces
between its words; keeping an indent on a justified row is a possible
follow-up. Centred and right-aligned rows, and the editor's draft box,
are measured to the last word, and a paragraph at 0 Tz is not justified.

Fixes #4199
Fixes #3797
2026-10-23 13:50:17 +05:30
Guillem Hermida 558fa0c995 Merge remote-tracking branch 'origin/main' into fix/2583-note-icon-no-rotate
# Conflicts:
#	ROADMAP.md
#	crates/annot/src/lib.rs
#	crates/automation/src/comments.rs
#	crates/engine/src/lib.rs
#	crates/engine/src/tests.rs
#	crates/render/src/inspect.rs
#	crates/ui-egui/src/canvas.rs
#	crates/ui-egui/src/comments_panel.rs
#	crates/ui-egui/tests/it/comments.rs
#	parity/acrobat-features.toml
#	vendor/README.md
2026-10-23 10:19:47 +02:00
eliets2 39918c466a Describe the shared-output refusal in the batch OCR tool description
Agents read the tool description to plan a call. The description of
ocr_recognize_files now says that two inputs which would write the
same output file are refused before anything is read or written, so
an agent does not expect a partial batch.

Checked: cargo test -p pdfcraft-automation --test automation -- tools
(86 passed, 0 failed). The text is the only change in this commit.
2026-10-23 11:17:45 +03:00
eliets2 4bd1e72788 Refuse an OCR batch whose inputs would write one output file
The ocr_recognize_files tool wrote every input's searchable copy to
the same name in the output folder. Two inputs with one file name
(a/scan.pdf and b/scan.pdf) therefore wrote the same output: the
second replaced the first while both results reported success.
Reproduced on the unchanged tree with the OCR models installed: both
results pointed at out/scan.pdf, and the folder held one file.

The whole batch is now refused before anything happens. The output
folder is not created, nothing is read, and the OCR engines are not
loaded. The error names the colliding file names and says what to do.
Names are compared the way the default filesystem compares them:
without regard to case on Windows and macOS, exactly elsewhere, and on
Windows a trailing dot or space is not part of the name. The compared
name is the one the output is written under, after the separator
replacement the writer already applies. A request without collisions
runs as before.

Not changed here: the batch dialog in crates/ui-egui (ocr_ui.rs) writes
its outputs the same way and needs its own change.

Checked: the three ocr_recognize_files tests in
crates/automation/tests/automation.rs and the unit test
shared_output_names_follow_the_default_filesystem, run with the models
installed and without them. Each pinned line was reverted in turn and
the test that pins it failed; the file was restored after each step.
2026-10-23 11:17:45 +03:00
Guillem Hermida b9db0fdb76 Merge remote-tracking branch 'origin/main' into fix/2837-preview-page-content
# Conflicts:
#	crates/ui-egui/src/marks_ui.rs
#	crates/ui-egui/tests/it/marks.rs
#	parity/acrobat-features.toml
2026-10-23 10:15:05 +02:00
Guillem Hermida 5514756a63 Merge remote-tracking branch 'origin/main' into fix/back-arrows-leave-edit-text 2026-10-23 10:14:01 +02:00
Guillem Hermida c9c6c85948 Merge remote-tracking branch 'origin/main' into fix/1201-optimize-keeps-jpeg-colortransform
# Conflicts:
#	ROADMAP.md
#	crates/optimize/src/images.rs
2026-10-23 10:13:47 +02:00
Osvaldo DuranandClaude Sonnet 5.5 d09ccb2154 i18n(ui): carry this PR's new wordings in the catalogues that must cover them
Catalan, Finnish, Hindi, Korean, Indonesian, Romanian and Albanian get the strings this PR adds
(AI-assisted translations from English, not separately native-reviewed), so the catalogue coverage
tests pass. ATTRIBUTION.toml's sha256 for each file is refreshed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 03:55:12 -04:00
Marco Arnulfo 35b4677019 M11: LZW to Flate leaves streams whose decode parameters aren't direct integers 2026-10-23 09:41:41 +02:00
Guillem Hermida 833d6546f0 optimize: the JPEG header scan stops at the first stray byte (#1201 review)
Optimize reads a JPEG's header again for every group of fragments it tries,
and one image can be drawn many times. The scan skipped stray bytes between
segments one at a time, so a JPEG with megabytes of them after SOI cost a
full walk on every call: 64 references to one such image delayed the first
progress call by seconds.

The scan now treats a stray byte as an unreadable header, which leaves the
image as it is, and stops after 4096 markers and fill bytes. Each step jumps
over a whole segment, so the work no longer grows with the stream's size.
2026-10-23 09:40:50 +02:00
Combiler01 e4476b007d Fix:Added Image don't follow the Refactored Edit_image 2026-10-23 13:08:57 +05:30
so-sai 12b5e670e4 feat(ui): add 'Split document...' to page thumbnail context menus 2026-10-23 14:17:19 +07:00
Gabriel Luís da Silva 52d49c301e fix(audit): charge a line's runs to the comparison bound
The covered text walks every run of a covered line, and a run can draw
no words (`( ) Tj`), so a line now costs its word boxes or its runs,
whichever are more. A word followed by many empty runs can no longer
be walked once per cover outside the 50-million-comparison bound.

Review on #4198 (CodeRabbit).
2026-10-23 04:15:41 -03:00
Osvaldo DuranandClaude Opus 5.5 4ebf287e61 Merge upstream main 0dc77fe4 into pr/attachments
crates/organize/src/import.rs: upstream's name_tree_entries (seen-node set, each node read once) kept whole; this PR's only change to it, pub(crate) for attachments.rs, applied on top.
crates/organize/src/attachments.rs: its name_tree_entries call passes the new seen set.
crates/automation/tests/attachment_key.rs, crates/ui-egui/tests/attach_picker_target.rs: moved into tests/it/ and registered in main.rs, following upstream's one-binary layout (organize keeps its single test file, as upstream's rule says).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 03:15:10 -04:00
Gabriel Luís da Silva cd2b26d492 fix(audit): join covered runs the way the line joins them
TextLine::runs also says whether joining put a space before each run,
and covered_words uses it: a word shown in two runs with nothing
between them (`SE` `CRET`) is reported as one word, not `SE CRET`.
Runs with uncovered runs left out between them are still spaced.
The regression test writes its content streams on one line each.

Review on #4198 (CodeRabbit).
2026-10-23 03:53:31 -03:00
mtlcl410andClaude Opus 5.5 9ebc237ecf M6: skip forms without a Password field quickly, and find ancestors once (#3926)
Review on #3943, point 1. has_password_values ran a full fields() walk
on every save and recovery snapshot, and clear_password_values walked
every field's ancestors again for each ancestor of each target.

has_password_flag now answers first, reading only /Ff, /Kids and /Parent:
the field tree from /Fields (seen set, 32 levels) and, as fields() does,
the widgets on the pages with the fields above them. A form without such
a field returns before any Field is built. clear_password_values finds
each field's ancestors once.

The test puts a page-only widget, a flag inherited from an unlisted
parent and a nested field whose widget is on no page each alone in a
document. Mutants that drop the page scan, the parent chain, the tree
walk or the descent into kids each fail it, as does treating every
ancestor as exclusive. Dropping the seen set or the early-out changes
only speed (equivalent).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:38:29 +08:00
mtlcl410andClaude Opus 5.5 adc8307c0b M6: a saved Password field returns to its default value and is redrawn from it (#3926)
Review on #3943. A black-box test of Acrobat (synthetic form, Save As and
Ctrl+S, every decoded object searched) showed how it saves a Password
field: the typed value is nowhere in the file; a field with /DV is saved
holding /DV, one without is left empty; the asterisks are drawn from the
saved value (5 for a 5-letter default, not 13 for the typed password);
/DV and /AA are kept.

clear_password_values now does the same: it restores the (inherited)
/DV as the value, or leaves the field empty, and redraws the widgets, so
the appearance no longer shows how long the typed password was. A field
already at its default no longer counts as holding a password, so such
a form needs no cleaned copy. The parity note for form.fill-text says so
and lists the two limits Acrobat shares (a script copying the password
into an ordinary field, and an earlier revision kept by an incremental
save).

Mutants, each failing a test: no /DV restore, no redraw, the default
ignored in password_fields, emptiness ignored (an empty string in an
array). Dropping the empty-default filter was equivalent, so the filter
is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:38:29 +08:00
mtlcl410andClaude Opus 5.5 64c5fd8ef0 M6: signing, timestamping and DSS writes carry no Password-field values (#3926)
CodeRabbit on #3943: sign, sign_with_timestamp, timestamp_document and dss::embed
each write a new incremental file from editor.cos, outside the writable() boundary
saves use, so a typed Password value was signed into the file. Each now writes from
the writable() copy. The new test signs a form with a typed value and checks the
signed file holds the masked appearance but not the value; the three TSA/DSS paths
need a timestamp server or validation data, and use the same one-line change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:38:29 +08:00
mtlcl410andClaude Opus 5.5 048cf14d31 M6: Extract, Split and Combine write no Password-field values either (#3926)
CodeRabbit on #3943: Extract Pages copies the fields of the pages it takes, and
write_new (Combine, Extract, Split, Create) wrote that copy without the writable()
boundary saves use, so the new file kept a typed Password value. write_unchecked, which
write_new and Split's size measuring both go through, now writes the sanitized copy. The
other write_full/write_incremental calls in the engine build in-memory bytes (display,
previews, the working copy, print rasters), not files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:38:29 +08:00
mtlcl410andClaude Opus 5.5 509b5d5cf3 M6: clear Password values inherited from an ancestor or held by a widget (#3926)
Review follow-up: the helpers checked only the field dictionary's own /V. A value
inherited through /Parent or carried by a separate widget was missed, so the
document could be written as it was. Detection now uses the resolved value and
the widgets; clearing removes /V from the field, its widgets and every ancestor
only password fields inherit from, and gives the field an empty value of its own
where a shared ancestor value has to stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:38:28 +08:00
mtlcl410andClaude Opus 5.5 0e883c44a0 M6: values typed into Password fields are written to no file (#3926)
A text field with the Password flag kept its typed value in /V, so Save, a full
rewrite, Reduce File Size and the recovery snapshots all wrote it in plain text.
ISO 32000-2 §12.7.5.3, Table 231 (note to the Password flag): PDF processors
should never store the value in the PDF file. Each of those paths now writes a
copy without the values; the open document keeps them in memory, and the masked
appearance is kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:38:16 +08:00
Combiler01 9df61340be Fixed Double copy when used win + v to paste image 2026-10-23 11:52:43 +05:30
Gabriel Luís da Silva e7dd758d33 fix(audit): covered_text reports the words under the cover, not the whole line
A text line joins runs painted on one baseline, and the audit reported
the line's whole text for a cover over part of it, including words
painted beside the cover that stay visible (`SECRETtail` for a cover
over `SECRET`).

TextLine now keeps each joined run's text with the end of its word
boxes (TextLine::runs), and the audit reports the runs whose words
the cover overlaps. A single run, a line reordered for reading
(right-to-left or visual-order Indic), or a cover that overlaps the
line's box but none of its words still reports the whole line.

Fixes #4164
2026-10-23 03:14:49 -03:00
mmmaarian03-cryptoandClaude Opus 5.5 cdc0834f34 Comments: the composer refocuses after its page was off screen (#4021)
When the whole page left the screen, `composer` returned before it set
`focus`, so the editor didn't take the keyboard again when the page came
back (review on #4095). That path now asks for the focus too.

Test `the_composer_takes_the_keyboard_back_after_its_page_was_off_screen`
scrolls page 1 off the screen and back; it fails without the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 08:57:40 +03:00
mmmaarian03-cryptoandClaude Opus 5.5 d6ba2413ba Comments: the composer hides while its comment is scrolled away (#4021)
The floating comment card (new note, text box, edit comment) was an egui
Area constrained to the whole window. When the page scrolled its comment
under the mode bar, the card was pushed back into the window and sat over
the mode bar and toolbar until the whole page left the screen.

It now shows only while its anchor is inside the document view, and is
kept inside that view. While the comment is out of sight the draft is
kept, and the editor takes the focus again when it comes back.

Test: tests/comments.rs the_composer_hides_while_its_comment_is_scrolled_away
scrolls a sticky note's anchor above the document with its page still on
screen; it fails without the change (the card sat at y=43, over the
toolbar, with the document starting at y=134) and passes with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 08:57:40 +03:00