Commit Graph

1189 Commits

Author SHA1 Message Date
aiongg bb695240f0 Merge PR #2525: fix(cos): an incremental save of a file with bytes before its header counts from the header 2026-10-16 14:02:08 +00:00
aiongg 9477e2a3fd Merge PR #2793: M10: preserve component Decode in lossless image export 2026-10-16 14:02:08 +00:00
aiongg 405ad489c2 Merge PR #2681: Replace Text: give the caret its own /NM (#2666) 2026-10-16 14:02:08 +00:00
aiongg eb403f84ec Merge PR #2702: Remove security also from files with /Encrypt inline in the trailer 2026-10-16 14:02:08 +00:00
aiongg fae7f4451c Merge PR #2532: fix(redact): removed links and comments leave the structure tree too (#2447) 2026-10-16 14:02:08 +00:00
aiongg c2a634ec4c Merge PR #2816: M10: running text is not a table row in Word, HTML and PowerPoint export (#2806) 2026-10-16 14:02:08 +00:00
aiongg a17794f170 Merge PR #2698: M8: the redaction audit finds a cover over one word of a long line (#2662) 2026-10-16 14:02:08 +00:00
aiongg 5ed4b67bab Merge PR #2754: Keep leading spaces and tabs when wrapping plain text into a PDF 2026-10-16 14:02:08 +00:00
aiongg 8c5074b982 Merge PR #2600: Shorten over-long part file names in doc_split by bookmarks 2026-10-16 14:02:08 +00:00
aiongg 0b68d0e3c6 Merge PR #2679: fix(annot): percent-encode non-ASCII bytes written into link /URI 2026-10-16 14:02:08 +00:00
aiongg e902b975fa Merge PR #2678: Round FormCalc Str and Format exact ties away from zero 2026-10-16 14:02:07 +00:00
aiongg 624e6343fb Merge PR #2587: M3: single-page view turns pages without a blank frame 2026-10-16 14:02:07 +00:00
aiongg 938d171652 Merge PR #2730: M3: closing PdfCraft while minimized no longer saves a 0 x 0 window 2026-10-16 14:02:07 +00:00
aiongg c859bf2aac Merge PR #2602: Don't treat a date ending in "Sep" as PM in AFDate/AFTime parsing 2026-10-16 14:02:07 +00:00
aiongg ca443792d3 Merge PR #2810: fix(select): a picked picture that is edited away no longer leaves its highlight behind 2026-10-16 14:02:07 +00:00
aiongg 77f2b295cc Merge PR #2687: fix(inspect): report /OpenAction JavaScript in document info 2026-10-16 14:02:07 +00:00
aiongg 17b7effd6f Merge PR #2704: M3: ⌘G and ⇧⌘G step through matches from the find field (#2669) 2026-10-16 14:02:07 +00:00
aiongg 3eaff53a94 Merge PR #2642: Let the page range select page 1 alone 2026-10-16 14:02:07 +00:00
aiongg 2b3bbd0690 Merge PR #2625: fix(ui): Reveal in File Explorer selects files whose path has a space (#2579) 2026-10-16 14:02:07 +00:00
mtlcl410 30c174be9e M3: closing PdfCraft while minimized no longer saves a 0 x 0 window
eframe stores the window's size and place just before App::save. A
minimized window has none: Windows reports 0 x 0 at (-32000, -32000),
and the next start opened at egui's 64-point floor in the screen's
corner (seen on a 6K display at 225%: a 144 x 206 px window). While the
window is minimized the app now puts back the last settings it was
shown with (or none), and a window that opens at that floor from
settings saved before this fix is given a usable size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
2026-10-16 19:20:47 +08:00
hade0ntema 37a9346629 fix(select): clear the picked picture in the edit itself, not on the next frame
The first fix dropped a stale pick while painting the next frame. The window only redraws on
the next input, so after deleting a picture in Edit text & images its highlight stayed on screen
until the user clicked. Clear the pick in apply_edit, in the same step as the edit, and ask for a
repaint. drop_stale stays for the paths that change the document without apply_edit (Undo, Redo).

An edit-level test checks the pick right after apply_edit with no frame in between; it fails
without the change. The earlier one-frame test could not tell, the test harness redraws more
often than the app does.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 11:49:06 +02:00
mtlcl410 96478f325d fix(ui): test that the Explorer argument reaches the process as written (#2579)
Review of #2625: the test checked the argument reveal_command builds but
not how it is passed, so it still passed with raw_arg replaced by args.
The process is now built by reveal_process, and a Windows test runs it
on `cmd /c echo`, which prints its command line back: with Rust's own
quoting the argument comes back wrapped in quotes with the inner ones
escaped, and the test fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
2026-10-16 17:40:55 +08:00
mtlcl410 f9db1e31fd M10: running text is not a table row in Word, HTML and PowerPoint export (#2806)
Table detection took a sentence whose link was set in another colour
and font, split into blocks that run on into each other, for two table
columns, and took a paragraph's lines, whose box overlaps the line
before, for a cell beside it. The table it built put the paragraph's
first line in one cell, a fragment of the link in another, and dropped
the paragraph's later lines. A block that starts where another on the
same line ends now adds no column of its own, and a row whose cells
overlap is not a table row; a superscript beside a number stays in its
table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
2026-10-16 16:47:49 +08:00
hade0ntema da48b84b8c fix(select): a picked picture that is edited away no longer leaves its highlight behind
With the Select tool a click picks a picture and draws a blue highlight over it. The pick was
only cleared by Escape, a drag or another click. Picking a picture, then entering Edit text &
images and deleting it left the highlight on the empty spot, with nothing left to click to
remove it.

The pick now remembers the document's edit generation and is dropped once the document has been
edited since (a delete, a move, Undo), the same condition under which the copy shortcut already
refused it as stale.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 10:33:24 +02:00
Enrico Sita c66384f92b fix(ui): Read mode hides the quick toolbar (#2646)
Read mode hides the tools, not the navigation panes, as Acrobat's does
and as switch_mode already says, but the floating quick toolbar was
still drawn over the page there. It is now hidden in Read mode, and
entering Read mode puts the current tool down (back to Select), so no
markup tool stays active without a toolbar to show it. The right rail,
the page navigation and the V/H shortcuts are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 10:27:59 +02:00
Ahmad Mujahid e447749109 Fix wrapped revision controls overlapping Binding (#2739) 2026-10-16 11:22:54 +04:00
Ahmad Mujahid 9409265c35 M10: apply component Decode during lossless image export 2026-10-16 11:06:55 +04:00
Marco Arnulfo c736697680 fix(ui): Save asks where to keep an Apple Mail attachment (fixes #2751) 2026-10-16 06:13:19 +02:00
NetterSilver fdc8099ac2 Keep leading spaces and tabs when wrapping plain text into a PDF
Found while testing crates/fonts/src/lib.rs:79 (wrap_with), called from crates/create/src/lib.rs:set_text

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 04:08:29 +00:00
skylar taylor-barrick d43b9a1f86 test: refresh open regression coverage
Refs #2067. Preserve the original regression commit while incorporating upstream changes.
2026-10-15 21:28:11 -06:00
hcchung1 31716e7058 fix(xfa): a page area without <occur> has no limit (fixes #2574)
A pageArea with no <occur> was given a subform's default (at most one use). Once the
first body page was out, the layout fell back to the last page area, here one with
occur max="1", and drew its content over every body page, with a copy of its fields
per page. A page area with no <occur> now repeats as long as pages are needed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 10:46:25 +08:00
Christoph Ortenburg c0a8a1a10f test(cos): qpdf can't check a prefixed file whose offsets include the prefix
qpdf counts every offset from the header in a file with bytes before it, so
it already reports the input of
`incremental_saves_of_a_file_whose_offsets_count_from_its_first_byte_keep_counting_so`
as damaged ("xref not found") and reconstructs it, before anything is
appended. The qpdf check there could never pass; the test's own reader checks
stay. The header-relative test keeps its qpdf check, which passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:46:45 -04:00
Chiranjeevi U 5fdd820bdb fix(ui): validated signature display, snapshot tab reuse (#1895)
* fix(ui): validated signature display, snapshot tab reuse

Signature widgets with layered appearances baked the unverified state
into the page, so a valid signature kept reading 'Signature Not Verified'
while the banner and panel said valid. Validated verdicts are now drawn
into the icon and caption layers in the display copy only: the signed
bytes are untouched, saving keeps the original appearance, and files
without those layers or without a valid verdict render exactly as before.

Other valid signatures still get the verdict mark over their widget,
sharing one style with the Signatures panel, and hide it again once the
appearance itself shows the verdict.

View signed version and View revision opened a new tab on every click,
and every tab showed the document title, so repeats piled up identical
tabs. Repeats now reuse the snapshot tab, snapshot tabs read like their
source plus the suffix in the tab strip, window title and Combine picker,
and carry a notice bar naming the revision with a Close that asks about
saving first.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* Address review comments on validated appearances and trust refresh

Count every field when checking shared appearance streams, so a
caption shared with an unvalidated field is never rewritten for the
valid one alone.

Pick the icon slot by drawing instead of length, and only when a
single layer qualifies. Bare rectangles never count as the icon, and
ambiguous or missing slots fall back to the overlay badge.

Revalidate and invalidate together through one helper, so trusting a
certificate always drops rendered pages. Trust alone still marks
nothing dirty.

Tests: mixed-verdict sharing, longer decorative borders, competing
and doubled layers, capture-before preservation, trust invalidation
without zooming, and signed Session round trips with rendering,
saving and passwords.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* Prove the icon slot geometrically and cover real layered signatures

A lone line drawing is still not the icon: frames and borders span the
box while the status mark lives in a corner of it. The icon now has to
sit strictly inside the appearance frame and cover at most half of it;
anything else falls back to the overlay badge. Widgets without a
measurable frame veto the rewrite the same way.

Tests: parsed extents and inhabitant checks as units, both reported
border repros plus a frameless widget, and real layered signatures
built with cms::sign_detached through Session open, trust, rendering,
save and reopen, plain and password-protected.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* Identify the icon by shape instead of geometry

A lone line drawing is still not the icon, but size and position were
the wrong signals: the real status mark is large, and geometry blocked
it. Frames, borders and panels are rectangles by nature while the mark
is not, so a drawing made only of axis-aligned rectangles is decoration
however it is stroked. Singularity, shared-stream and template checks
are unchanged; anything ambiguous still falls back to the overlay badge.

Tests: rectangularity as a unit, nested line-drawn frames, and a
triangular fixture mark in place of the rectangular one.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* Budget the appearance walk and keep the marks with the bytes

The walk that finds a signature's icon and caption capped depth,
layers and fields, but not the work: a dictionary whose entries all
point at the next one is visited once per entry per level, so a small
hostile file never finishes opening. The walk now counts its visits
and gives up, and a field whose tree cannot be read to the end stops
the whole pass, because a layer we never read may be the one two
fields share and a rewritten shared layer puts one field's verdict on
another field's page. The sharing count is exact again with that: two
widgets of one field pointing at the same appearance claim it once,
or the field vetoes its own rewrite.

A mark used to outlive the bytes it described. A failed display write
left the page showing the file's own caption while the verdict still
claimed its state was drawn; a metadata edit dropped the mark while
the page kept drawing the state, so the badge landed on top of it. The
write fallback now drops the marks and logs the loss, and metadata
carries them over.

Tests: a self-referential appearance tree, which never finished before
the budget, two widgets sharing one appearance, a metadata edit that
keeps its mark, a save that goes through the writer instead of
short-circuiting on the opened file, and the snapshot bar's Close
button pressed for real.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* Cite the new regressions in parity, restore main's installer note

The appearance entry now cites the bounded walk and the save through
the writer, and says what a tree that cannot be read to the end does.
The installer entry carried main's older text, which still claims
signed installers are planned; main's newer note is taken verbatim.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: preserve rounded decoration beside raster status glyphs

* M9: place appearance progress beside signature history

* M9: retain appearances with ambiguous curved layers

* M9: keep signature verdicts in viewer overlays

Defer generic appearance rewriting: caption text and a unique drawing do not establish an icon role. Preserve original artwork for saves, image and text exports, and both print modes while retaining snapshot reuse and trust refresh. Cover chamfered borders and mixed signer/caption streams through signed Session round trips.

---------

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>
Co-authored-by: skylar taylor-barrick <hi@skylar.cc>
2026-10-15 19:16:48 -06:00
mtlcl410 271cb6161f M1: read an xref table numbered from 1 that begins with object 0 (#2676)
Some producers head a classic xref table "1 n" and then write object 0's
free entry first, so every row is one number off and object 1 reads as
free. The renderer coped, but cos did not: printing a scanned invoice
built from cos dropped object 1 (page 1's image) and printed the page
blank. Such a subsection is now read from 0 when its second row locates
an object that is numbered 1, in both xref readers, and the repair is
logged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
2026-10-16 08:35:35 +08:00
jonathanwxh-cell 9229e7af36 fix(edit): wrap single turned rows at the page margin (#2578) 2026-10-16 08:13:01 +08:00
Claude 33fe488061 fix(cos): an incremental save of a file with bytes before its header counts from the header
Some files carry bytes before `%PDF-`, and their offsets count from the header rather than from
the first byte. We read those, retrying a section relative to the header when the absolute
offset misses, but the incremental writer then appended a section whose offsets and `startxref`
were absolute while its `/Prev` kept the header-relative value it was given. qpdf reported "xref
not found" and reconstructed the file, so a save that reported success left a damaged-looking
update behind (#2364, found with KillerPDF's reserve3.pdf and a Subject edit).

The reader now records on each `Revision` whether it was found counting from the header, and the
writer counts the new section the same way as the one its `/Prev` points to. A file whose
offsets already include the prefix keeps absolute offsets, which the second test pins so the fix
cannot overcorrect.

Seen red with the writer change reverted: the appended section's `startxref` missed its `xref`.

Fixes storytold/pdfcraft#2364

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DsT1y8xBvkXeBxEUQqdDtj
2026-10-15 19:54:17 -04:00
Piohrun f65f733041 fix(render): faster sampled functions, image decodes across zoom levels and patch meshes (vendored hayro-interpret patches 31–33) (#2436)
* fix(render): sampled functions index a flat sample array (vendored hayro-interpret patch 31)

A Type 0 sampled function kept its samples in a HashMap keyed by each point of
its /Size grid (two SmallVecs per key, SipHash), and the renderer builds a
shading's functions again every time it is painted, so every paint hashed one
insert per sample. A 2,097,152-sample ramp painted 30 times took 38 s; the
pdf.js corpus's bug1721218 (InDesign) spent most of a 1.5 s page on it.

The samples now stay in stream order in one Vec, and a grid point is found by
strides (first input fastest). The interpolation keeps the same recursion and
arithmetic, so results are bit for bit the same, and a point the stream ended
before still gives no value. The repeated-paint page draws in well under a
second. The maximum sample value no longer computes 2^32 in a u32 (a panic with
overflow checks on at 32 bits per sample).

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

* fix(render): one decode of an image serves every size it is drawn at (vendored hayro-interpret patch 32)

The decoded-image cache (patch 24) keyed each decode by the size the image is
drawn at, but only the JPX filter decodes a reduced resolution for a size:
every other filter decodes the whole image. So a JPEG or Flate scan missed the
cache at every zoom step, was decoded again, and was kept once per zoom level.

The key now carries the target size only when JPX decodes the image, its
/SMask or its /Mask (read from /Filter or /F, erring towards keeping the
target). `InterpreterCache::decoded_image_decodes` counts the cache's decodes;
the new test draws a Flate image at five sizes and sees one decode, and a
copy whose soft mask is JPX-encoded still decodes per size. Both assertions
fail with the old keying.

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

* fix(render): patch meshes copy their vertices' colours less (vendored hayro-interpret patch 33)

A Coons or tensor-product patch is drawn as 722 triangles over a 20 x 20 grid
of points, and every vertex's colours (a SmallVec) were produced or copied up
to five times: each grid cell interpolated its four corners itself (1,444
interpolations a patch for 400 points), cloned all six vertices of its two
triangles, and the mesh sampler cloned each vertex again to move the triangle
into device space. Profiling page 8 of the annual report in #1584 put a
quarter of its time in those clones.

Each grid point's colours are now interpolated once, a cell's second triangle
takes the vertices its first copied, and the sampler moves a patch's freshly
made triangles instead of copying them (`TriangleSampler::sample_owned`; a
free-form triangle mesh, which the shading keeps, is still copied). The same
inputs give the same values: rendering the first three pages of every pdf.js
corpus file is byte for byte the same. Release build, Linux: report page 8
773 -> 696 ms, personwithdog.pdf 149 -> 129 ms, issue13520.pdf 104 -> 87 ms.

The new test pins the colours near each corner, at the centre and between them
for a Coons and a tensor-product patch (taken before the change); swapping two
vertices' grid points makes it fail.

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

* docs: roadmap entries for faster sampled functions, image decodes and patch meshes

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 17:50:29 -06:00
jonathanwxh-cell 6f348589bf fix(edit): emit rewritten paragraph after backgrounds between its rows (#2377) 2026-10-16 07:24:05 +08:00
mtlcl410 bf1ce2a07c M3: ⌘G and ⇧⌘G step through matches from the find field (#2669)
The find-step shortcuts sat after the canvas's "egui wants keyboard
input" check, so they did nothing while the find field had the focus
(Enter worked). They now run before it, while a search is open, as in
Acrobat and Preview.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
2026-10-16 07:18:35 +08:00
mtlcl410 2e2b5e7bf4 fix(redact): removed links and comments leave the structure tree too (#2447)
Remove Hidden Information and Sanitize took a link or comment off its
page's /Annots, but a tagged PDF's structure tree still referenced it
through an /OBJR, so the full rewrite kept the annotation, its URL or
text included, while link_list reported none. The annotations removed
from every page are now collected, and tags::drop_object_refs drops the
structure elements' object references to them (direct or indirect,
array or single kid), with the same seen-set bound as tags::clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
2026-10-16 07:01:43 +08:00
Marco Arnulfo b78724be05 Go to page keeps the side gutter clear of the quick-action bar (part of #2665) 2026-10-16 00:57:01 +02:00
Marco Arnulfo 2dd976b901 Go to page brings the page into view horizontally too (part of #2665) 2026-10-16 00:36:35 +02:00
Marco Arnulfo bb651792a9 Remove security also from files with /Encrypt inline in the trailer (fixes #2661) 2026-10-16 00:33:39 +02:00
mtlcl410 f0576408ee M1: saving refuses to replace a read-only file (#2658)
Saves write a staging file and rename it over the target. On Unix the
rename ignores the target's permissions, so a read-only file was
overwritten without a word and came out writable. The shared atomic
writer now refuses a read-only target before staging anything, with an
error that points to Save As; the edits stay unsaved in the document.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
2026-10-16 06:30:57 +08:00
mtlcl410 9dfc91e0e8 M8: the redaction audit finds a cover over one word of a long line (#2662)
A cover counted only when it hid 40% of the line's words or box, so a
black box over one number in a long line, and an unapplied redaction
mark over it, audited clean while the number was still extractable. A
cover that hides half of any one word now counts as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
2026-10-16 06:25:35 +08:00
Mex 6e44acf8c7 Merge current main into PR #1917
Resolve independent progress-document conflicts while retaining upstream updates and the existing regression-tested fix. Refresh the relevant milestone notes; feature scores remain unchanged.

Fresh checks: cargo fmt --all -- --check, git diff --check, and layering/assets/parity gates. Focused production/test files and lockfiles match the prior green CI head. Full CI will rerun on this merge.
2026-10-15 23:15:55 +02:00
Mex 9ed71d98d6 Merge current main into PR #1919
Resolve independent progress-document conflicts while retaining upstream updates and the existing regression-tested fix. Refresh the relevant milestone notes and rotation regression evidence; feature scores remain unchanged.

Fresh checks: cargo fmt --all -- --check, git diff --check, and layering/assets/parity gates. Focused production/test files and lockfiles match the prior green CI head. Full CI will rerun on this merge.
2026-10-15 23:15:49 +02:00
Mex d093e34e43 Merge current main into PR #2267
Resolve independent progress-document conflicts while retaining upstream updates and the existing regression-tested fix. Refresh the relevant milestone notes; feature scores remain unchanged.

Fresh checks: cargo fmt --all -- --check, git diff --check, and layering/assets/parity gates. Focused production/test files and lockfiles match the prior green CI head. Full CI will rerun on this merge.
2026-10-15 23:15:42 +02:00
Mex c30fce5d3e M6: distinguish CSV header tabs from TSV delimiters 2026-10-15 23:13:27 +02:00
Charan Rathore b4af74dd47 fix(inspect): report /OpenAction JavaScript in document info 2026-10-16 02:40:29 +05:30