Commit Graph
1189 Commits
Author SHA1 Message Date
Márk Bartalos b89cbd0044 M4: keep the last saved revision across undo (#1310, #1311, #1312) (#1435)
* M4: keep the last saved revision across undo (#1310, #1311, #1312)

Undo treated a return to the opened file as unsaved work, and after a save it followed the restored snapshot's older base. Revert and the next save now stay on the last saved file, so that revision remains in the PDF.

* Run a tool after a background open of the file it asked for

A tool chosen with no document opened the picked file on the worker, but only ran when that open finished inside the picker callback. Remember the command on the pending open, and let the command test wait for the worker.

* Wait for background opens in the command tests

Opening a picked file or a recent file finishes on the worker, so a fixed handful of frames can assert before the tab exists.

* Compare batch copies to the PDF that was written

A new text PDF stamps the current second, so a later pdf() call is a different file. The numbered copy and the source must match the bytes that were saved.

* Drop the unused batch fixture left by the merge

Main already keeps the written PDF in original, so the older source binding is unread and fails -D warnings.
2026-10-15 02:17:06 -04:00
Sir Clangington III.andLabDennis 8a04b6d5c2 M8: Bound and reuse repeated redaction overlays (#1426)
Consolidate the reviewed change and its follow-ups on current upstream.

Original change notes:
M8: Bound and reuse repeated redaction overlays

Prepare appearances within a shared 8 MiB budget before changing the document. Reuse one measured and encoded row, reject excessive geometry, and preserve document and undo state on refusal.

Fixes #1326

M8: Preserve upstream font metrics in bounded redaction overlays

M8: reuse ordered redaction mark groups (#1326)

Co-authored-by: LabDennis <341377186+LabDennis@users.noreply.github.com>
2026-10-15 02:16:56 -04:00
Earl Co fc601b89e1 Tests: use a 300 DPI scan for OCR searchability (#1197) 2026-10-15 02:16:45 -04:00
Jules Kreuer 039706c437 Clear image leftovers (#1151)
* M8: Remove image redaction leftovers

* M8: Move roadmap note
2026-10-15 02:16:35 -04:00
Alexis e75082c9d6 Printing: a layer that is off in the document stays off on the sheets (#1135)
The print-ready PDF that `pdfcraft_print::impose` builds starts from a new
catalog with only /Type and /Pages, so the document's /OCProperties was not
carried over. The pages' marked content still refers to its optional-content
groups, but nothing said a group was off, so a layer that is switched off in
the document (and not drawn on screen) was printed anyway.

Keep /OCProperties in the new catalog, so the sheets hide the same layers the
document hides. Test: a one-page document whose red square is in a layer; with
the layer off by default it is not drawn on screen and, now, not on the
printed sheet; with the layer on it is drawn in both.

Printing still follows the layers' default state only; applying the Print
event of /AS (a layer shown only on screen or only in print) is not done here.
2026-10-15 02:16:23 -04:00
dbef872ed7 M2: preserve precision when writing page transforms (#946)
Co-authored-by: sanyamsingh001 <314051937+sanyamsingh001@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
2026-10-15 02:16:13 -04:00
d3a91c5c5f M2: use fixed-size pixel chunks in oracle decoder (#2342)
Keep RGBA decoding unchanged while satisfying the current Clippy lint.
Include the separately verified Rustls 0.23.46 lock prerequisite.

Co-authored-by: sanyamsingh001 <314051937+sanyamsingh001@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
2026-10-15 05:44:02 +02:00
Tanish MankuandClaude Opus 5 f802257be6 docs(fonts): the README still called the standard-14 widths an estimate
F1 replaced the character-class approximation with the published standard-14 metrics, and
`helvetica_width` has delegated to `Std14::Helvetica` ever since. The README kept describing it as
PdfCraft's own estimate and, more importantly, said "No metrics file or font program from any
vendor is bundled" -- which stopped being true when the generated tables landed.

They are Adobe-authored non-visual technical data, the item AGENTS.md 1.1 permits, carried in
std14_metrics.rs with adobe_data = true in ATTRIBUTION.toml. A contributor reading the README
would have concluded there was no vendor data here at all, which is the one kind of error that
matters in a provenance claim. Says what is bundled now, and what still is not: widths and
encodings, never a font program or a typeface.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-14 15:32:36 -07:00
Tanish MankuandClaude Opus 5 e0c63546bb test(render): pin that every JBIG2 conformance fixture decodes to the same picture
The render oracle put four JBIG2 fixtures at the top of its disagreement list against Poppler, and
the obvious reading was that hayro-jbig2, which carries local patches, had a bug. Investigating it
found the reverse.

The 96 `bitmap-*.pdf` corpus files are a conformance suite: one small drawing encoded through every
corner of JBIG2 -- each generic-region template, custom AT pixels, TPGDON and TPGRON, refinement,
halftone with and without skip, symbol dictionaries with Huffman tables, MMR, the composition
operators, random access, unknown-height segments. Those features change how the bytes are
compressed, not what the picture is, so a correct decoder renders all 96 identically. That is a
real test with no reference image and no external oracle: the suite checks itself.

We render 1 distinct image across all 96. Poppler renders 8: it draws a blank page for the seven
halftone-skip and symbol fixtures, and its output changes when TPGRON is switched on, which a
correct decoder's cannot. So roughly a fifth of Poppler's JBIG2 output is wrong, the top of the
oracle's worst list is its bug rather than ours, and Poppler is not a usable oracle for JBIG2.

Watched failing by admitting one unrelated file to the set: the test fails and names it as the odd
one out rather than merely reporting a count.

The expectation holds of the pinned corpus. Whoever re-pins it to a newer pdf.js should re-check
this rather than assume it still holds, which the test's own documentation says.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-14 15:12:52 -07:00
Tanish MankuandClaude Opus 5 5e08aa8719 fix(oracle): say what the render-oracle score does and does not mean
Investigating the worst file of the first 250 turned the headline finding around. bug1552113.pdf
scores 84%, and the cause is Poppler: it paints the link's `/Border [0 0 112]` as a 112-unit band
across a 250x50 page, hiding the two lines of text. The fixture is named "this text should be
visible", and in our rendering it is. The oracle is not ground truth, and the top of its worst
list needs a person to look at both images before it is read as a bug of ours.

That led to a plausible change that the measurement rejected. Poppler synthesises appearances for
annotations that have none, where we draw only what the document carries -- `/AP`-less link borders
are already recorded as not covered under view.render-annotation-appearances -- so hiding
annotations looked like the way to measure page content alone. It is worse: it then scores Poppler
for every appearance we do draw. Over the same 250 files, shown gives mean 2.16%, p90 5.35% and 25
files within 0.1%; hidden gives 2.18%, 5.55% and 22, with annotation-tx3.pdf jumping to 45.64% and
annotation-tx2.pdf to 18.79%. The two biases nearly cancel.

So the default is unchanged and `--hide-annotations` is an opt-in for explaining a single file,
with the numbers written down so the experiment is not repeated. Verified that the default
reproduces the merged baseline exactly and that the flag moves it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-14 14:59:26 -07:00
Tanish MankuandClaude Opus 5 f988cf87ed feat(oracle): measure rendering against Poppler, and turn the pixel test on
Gap 12 of docs/gaps.md: until rendering is measured, every claim about its quality is a judgement.
Text extraction had an oracle and written output had qpdf; rendering had nothing.

`cargo xtask render-oracle` renders page 1 of every corpus file with our CLI and with Poppler's
pdftoppm and reports how far apart they are. The metric is the point. A raw pixel comparison is
useless here -- two correct renderers disagree on every glyph edge, so it calls a perfect page
badly wrong and says nothing about whether a shading is missing. `pixels::compare` counts a pixel
as differing only when it differs from every pixel in the other image's 3x3 neighbourhood by more
than a tolerance, in both directions, taking the worse. A one-pixel shift is forgiven; absent or
miscoloured content is not.

First reading, 250 files at 72 dpi, tolerance 8: 229 compared, median 0.89%, mean 2.16%, p90
5.35%, 25 within 0.1%. It reports rather than gates -- the corpus is not on CI, and a first
reading of a borrowed renderer is a baseline to improve on, not a bar to clear. The worst results
cluster on JBIG2 `bitmap-*` files and on ZapfDingbats, which aims the renderer work at something
specific for the first time. 15 files could not be compared because the two renderers round the
page box to different pixel sizes; they are reported as unmeasurable rather than scored.

Verified against the knob's bounds end to end: tolerance 255 scores every file 0.00%, tolerance 0
takes the median to 0.95% and p90 to 26.36%.

Also turns on `extract_type3_shared_xobjects_matches_poppler_pixels`, which had never run anywhere.
Poppler is now installed on the Linux runner, and the test is version-robust by construction --
both sides of its comparison go through the same pdftoppm, so it is about our two documents rather
than about Poppler's output. Verified in all three states: runs and passes with pdftoppm present,
skips without it, fails without it when PDFCRAFT_ORACLES_REQUIRED is set.

`run_capture` keeps its exact behaviour, including leaving the buffer empty on non-UTF-8 output;
the byte-oriented capture an image needs is a second entry point on the same timeout logic.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-14 14:28:19 -07:00
Tanish MankuandClaude Opus 5 50629eb6bf feat(testkit): one way to write a synthetic PDF fixture
55 files hand-rolled the same serialiser -- number the objects, record each one's offset, emit a
classic cross-reference table and a trailer -- 111 times over. The object bodies are properly the
business of the crate under test; the envelope around them is not, and every copy is a place for a
fixture to be quietly wrong in a way that makes a test prove less than it appears to.

`pdfcraft-testkit` holds that envelope. It has no workspace dependencies on purpose, so the crates
at the bottom of the layering can use it without a cycle, and it returns bytes rather than a parsed
document for the same reason.

The builder is byte-identical to the serialiser it replaces, asserted against a verbatim copy of
the old code across versions and trailer variations, because a migration that changes a fixture's
bytes silently changes what the test using it proves. Watched failing by breaking the free-entry
generation: three tests caught it. The one intended difference is that an empty stream dictionary
gives `<< /Length N >>` rather than a doubled space.

Migrates cos, measure and redact: 111 copies in 55 files becomes 108 in 53. The rest are
follow-ups rather than one sweep -- ui-egui alone is 27 of those files and is the most contested
area in the repository. Some copies should stay: a test about a malformed cross-reference table
needs to write its own bytes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-14 13:57:56 -07:00
Tanish MankuandClaude Opus 5 0754740fd9 feat(oracle): make a missing external oracle loud instead of a silent pass
Every test that wanted an external oracle probed for it by hand and chose for itself what to do
when it was missing. The common choice -- print a line and return -- reports as a pass, so a suite
with no oracles installed looked exactly like one in which every oracle agreed.

That was not hypothetical. No CI runner installed qpdf, so `qpdf_agrees` had never executed on a
runner, while README.md said saved output was "independently checked with qpdf". For R2-R4 that
test is the only check of owner-password authentication (Algorithm 7) that does not go through our
own code.

`pdfcraft-oracle` holds the probes and one rule: a missing oracle is a declared skip or a failure,
never a silent pass. `PDFCRAFT_ORACLES_REQUIRED` picks which, on the same convention as
`CRAFT_FONTS_REQUIRED`. CI installs qpdf and sets it, so a skip notice there is now a red build.
The crate has no panicking path of its own; `or_skip!` expands its assert into the caller, which
is test code.

Verified by running the qpdf tests against qpdf 12.4.2 for the first time -- they pass, so the
README claim is now both enforced and true. Both gate directions were watched: absent and
unrequired skips with a notice, absent and required fails with the install command in the message.

Only qpdf is installed in CI, because it is the only tool the test job uses. The Poppler pixel
comparison in crates/organize stays ignored until a change turns it on and verifies it there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-14 13:56:00 -07:00
Mex 893cb7430e M1: preserve output-limit errors in raw Flate fallback 2026-10-14 21:58:05 +02:00
Marco Arnulfo c1a41cc5c7 fix(save): validate PDF output before replacing files
Validate serialized PDF bytes and page count before replacing the destination in desktop and automation saves. Reject unreadable or page-losing output before file replacement and saved-state updates.

Keep staging writes synced before publication and retain the regression for a repaired page tree that cannot survive a full rewrite.

Fixes #1698
2026-10-14 00:25:22 -06:00
skylar taylor-barrick 3ef63cb4e7 M2: retain open regressions and deterministic test fixtures 2026-10-13 23:14:03 -06:00
hpcgridandClaude Fable 5.1 e4028b3c77 fix(ui): reject stale file watcher results
Carry the watched path and epoch through queued file reads, and reject stale results before changing the document. Preserve newer saved contents and undo history across Save and Save As while retaining ordinary external reloads.

Fixes #1802

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-13 22:40:52 -06:00
Sir Clangington III. dc4d86ba40 fix(automation): sync staged output before publication
Sync staged output after writing all bytes and before replacing its destination, closing the handle before rename. Return sync failures while keeping the old destination and ordinary save state available for retry.

Fixes #1804
2026-10-13 21:44:45 -06:00
Ibn ZwanckaertandClaude Opus 5.5 5e12f5fd6d feat(ui): reorder and cycle document tabs
Reorder document tabs by dragging while keeping the active document and its view state. Add Ctrl+Tab and Ctrl+Shift+Tab cycling in visible order, including Combine files, and translate the menu labels.

Keep timestamped batch test expectations tied to the originally generated PDF bytes.

Closes #1537
Closes #1542

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:06:20 -06:00
Tanish MankuandClaude d9607ec3be F3: one documented substitution chain, and report what it chose and why
A font the document does not embed was replaced by whatever the caller
happened to pick, and nothing told the user. `doc_info` said only
`embedded: false`, which is why "my Chinese PDF shows nothing" has been
a mystery rather than a sentence.

`crates/fonts/src/fallback.rs` applies one chain, in a documented order,
and returns what it chose with a reason:

1. embedded -> nothing substituted;
2. /BaseFont names a standard-14 face or a known alias (exact metrics);
3. craft-fonts holds that exact family;
4. /CIDSystemInfo /Ordering names a script we have a face for;
5. the ordering names a script we have no licensed face for -- report
   none, so nothing is drawn rather than the wrong glyphs;
6. otherwise the nearest of the fourteen by descriptor flags.

It delegates to the existing per-script selectors instead of restating
their order, so each keeps one source of truth. It answers the
dictionary question only; "which face has a glyph for this character?"
depends on the string being drawn and stays where the drawing happens.

`doc_info` gains `substitute` and `substitution` per font. Both null
means the document's own glyphs are shown; a null `substitute` with a
reason means no face was available at all. That is Acrobat's Actual
Font, minus the interface, which `parity/` now records as still missing.

Two findings on the way:

`Std14::from_base_font` matches loosely, so "SomeHouseSerif" resolved to
Times by its name containing "serif". The face is a fair guess but
reporting it as "names one of the standard 14" would have been untrue of
a font nobody has heard of. `Std14::recognise` now says whether a name
matched an alias or only a family hint, and the two report differently.
`from_base_font` is unchanged.

The generated table moved to `crates/fonts/src/std14_metrics.rs`, data
alone, and the ATTRIBUTION entry and AGENTS.md 1.1 line follow it. F1
put the hash on a file holding both the table and the code reading it,
so this commit's ordinary edits tripped the gate -- and a hash that
churns on unrelated changes gets re-attested reflexively, which is the
protection gone. Verified both directions after the split: editing
std14.rs passes the gate, changing one width in std14_metrics.rs fails
it.

Every new test was watched failing first: removing the script step
breaks the two unit tests and the doc_info integration test.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-13 16:42:10 -07:00
3f044b6263 fix(print): adopt native Windows spooler
Adopt the canonical craft-print revision from craft-libs #28 so Windows printing uses the in-process Windows.Data.Pdf/XPS backend instead of PowerShell. Keep exact paper, orientation and scaling validation and the existing PDFCraft composition and permission checks.

Pin the reviewed winprint fork, scope its compile-time licence/advisory exceptions, and distinguish the earlier WDAC Sandbox exercise from the final pinned revision's Windows CI coverage. Physical printer output remains unverified.

Add worker-barrier regressions for Home-selected tool handoff and startup document ordering.

Fixes #1107

Co-authored-by: durtyrobbin <76844303+durtyrobbin@users.noreply.github.com>
Co-authored-by: Dimitar Klaturov <7221384+bulgariamitko@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: skylar taylor-barrick <hi@skylar.cc>
2026-10-13 14:46:24 -06:00
skylar taylor-barrick b5467d93a0 fix(ui): preserve native open actions and password prompts
Carry selected tool actions through native file preparation, password retries and conversion, and defer startup intake until pending native opens finish.

Await observable completion in native opening, password, recovery and linked-PDF tests while retaining their content, identity, dirty-state, cancellation and destination checks. Verify the expected validation error and preserve the original document after refused consent targets.

M3; refs #1914.
2026-10-13 12:18:41 -06:00
MMexicannandCodex b85d519993 M6: preserve whitespace in XML and XFDF attributes
Encode tabs and line feeds as character references so XML normalization cannot change field names, comment identities or metadata. Add failing-before-fix saved-PDF round-trip regressions.

Co-Authored-By: Codex <noreply@openai.com>
2026-10-13 14:16:44 +00:00
MMexicannandCodex 2a218f3420 M4: resolve indirect page rotations before editing
Read the effective rotation through its PDF integer object so inherited and
page-local indirect values retain their orientation when rotated.

Co-Authored-By: Codex <noreply@openai.com>
2026-10-13 14:13:38 +00:00
7437bde378 M3: draw a GNOME-style title bar on Wayland (#553) (#1530)
GNOME's Wayland compositor draws no window frames (it offers no
zxdg_decoration_manager_v1), so winit drew its own fallback title bar,
which ignores GNOME's theme, dark mode and button layout. PdfCraft now
asks the compositor at launch (apps/pdfcraft/src/decorations.rs) and,
where it draws no frames, turns winit's frame off and makes the tab strip
the header bar, as in GNOME's own apps
(crates/ui-egui/src/window_controls.rs):

- Minimize, maximize/restore and close in Adwaita's style (round buttons,
  symbolic glyphs drawn in code, no new assets), in the order and on the
  side named by org.gnome.desktop.wm.preferences button-layout, read
  through gsettings; Fedora's default `appmenu:close` shows only close.
  Hidden in full screen.
- Resize zones along the window's edges and L-shaped corners, above the
  rest of the UI so a press there resizes instead of reaching a
  scrollbar; a hairline outlines the window while it isn't maximized.
- Preferences > Use system title bar (shown only where the app draws the
  frame) gives the window back winit's frame at once, without a restart
  (part of #1001). Named as in VectorCraft and PhotoCraft, but GNOME draws
  no title bars for apps, so on GNOME this is winit's basic frame, not
  GNOME's own; the tooltip says so. Saved as `system_title_bar`;
  `system-title-bar` on the control channel.
- PDFCRAFT_TITLEBAR=app|system overrides the detection.

KDE, Sway, Hyprland, COSMIC and X11 offer server-side decorations and
keep the frame their desktop draws; macOS and Windows are unchanged.

Not done: right-clicking the header bar doesn't open GNOME's window menu
(egui has no command for it), the window has no shadow or rounded
corners, the resize zones lie just inside the window rather than outside
it, and Windows still uses its system frame (the rest of #1001).

The new labels (Minimize, Maximize, Restore, Use system title bar and its
tooltip) were added to all 21 translation catalogs. These translations
have not been checked by anyone fluent in those languages and should be
reviewed. The catalogs' hashes in ATTRIBUTION.toml are updated.

Made with Claude Code (Claude Opus 5.5, medium effort). It passes the
workspace quality gates (fmt, clippy, cargo test --workspace, xtask
assets) and was checked by hand on Fedora 44 GNOME (light and dark,
maximized and not, the preference toggled live), but it has not been
exhaustively regression tested.

Co-authored-by: bmh129 <11478682+bmh129@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 03:04:13 -04:00
RidwanandBrandon Thomas 7739685796 Issue #1396: remove an imported digital ID (#1515)
Digital IDs added through Sign with a Digital ID (imported .p12/.pfx files
or new self-signed ones) are saved with the app settings, but nothing ever
took one off the list, so an old or mistakenly imported certificate stayed
there for good (#1396).

Sign with a Digital ID now has Remove next to Configure New Digital ID. It
acts on the selected file-based ID, asks first ("Remove "<name>" from this
list? The file itself is not deleted."), and takes the entry off the saved
list. The .p12 file stays on disk. Keychain and Windows store IDs can't be
removed here: the OS manages them and they are listed again on every
signing.

- sign_ui: PdfCraftApp::remove_digital_id refuses OS store IDs and stale
  indexes, keeps the dialog's selection on the same ID (or the first
  usable one), and goes to Configure when no IDs are left.
- SignDraft::confirm_remove holds the entry awaiting confirmation; clicking
  another row cancels the question.
- i18n: the confirmation is translated in all 21 catalogs; ATTRIBUTION.toml
  hashes updated.
- Tests: a unit test for the list, the selection and persist/restore, and a
  real-shell test that imports a .p12, cancels, then removes it.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 02:49:38 -04:00
5ebb53a4a4 M4: preserve shared Type 3 glyph resources during extraction (#1500)
Co-authored-by: MMexicann <MMexicann@users.noreply.github.com>
Co-authored-by: OpenAI Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 02:34:12 -04:00
13a921ae4a M2: keep byte boundaries in packed TIFF-predictor rows (#1499)
Co-authored-by: MMexicann <MMexicann@users.noreply.github.com>
Co-authored-by: OpenAI Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 02:27:26 -04:00
4cb803038d M3: confirm before opening absolute GoToR paths (#1364) (#1489)
* M3: confirm before opening absolute GoToR paths (#1364)

* fix(i18n): translate GoToR confirmation dialog labels

---------

Co-authored-by: srt8hellcat2-ui <225485253+srt8hellcat2-ui@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 02:16:21 -04:00
Marco ArnulfoandBrandon Thomas 685fd7eeea fix(text): read a TrueType font without an encoding as WinAnsi (#1477)
* fix(text): read a TrueType font without an encoding as WinAnsi (fixes #1468)

* fix(text): describe the WinAnsi reading by pdftotext's output

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 02:04:39 -04:00
Yousef MojahidandBrandon Thomas 0dd5fdd5bd M7: keep Arabic ActualText ligatures in their word (#1473)
Normalize single-glyph RTL replacements before assembling editable lines. Preserve authoritative multi-glyph spans and cover lam-alef variants, marks, CID codes, replacement, copy/search and save/reopen.

Assisted-by: Codex

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 01:52:22 -04:00
Márk BartalosandBrandon Thomas fff985f30d M5: keep stamps upright on rotated pages (#1398) (#1460)
A stamp placed on a page with /Rotate was drawn in user space, so the
label or picture turned with the page. Counterrotate the appearance so
it reads upright as the page is displayed.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 01:42:57 -04:00
dfa43beb9f M10: BigTIFF and strict GIF detection in Create PDF; XDP data in format parity (#1453)
- Create PDF detects BigTIFF (II+/MM+) as well as classic TIFF; the tiff
  decoder already reads it, so these scans now convert instead of being
  refused.
- Image detection accepts only GIF87a/GIF89a, the headers conversion
  decodes, so a malformed GIF header is no longer called an image and then
  rejected with a misleading error.
- file-format-parity.md: XDP split into XFA data import/export (shipped
  under form.xfa-data) and opening .xdp as a document (none).

Tests: bigtiff_images_become_pages; malformed GIF headers in
source_kind_tells_pdfs_images_and_text_apart. Both fail without the fix.


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

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 01:19:48 -04:00
Márk BartalosandBrandon Thomas 9ed3abe490 Keep undrawn comments and accurate search hits (#1317, #1327, #1302, #1315, #1340) (#1442)
Flattening deleted a cloudy comment it could not draw, and a refused text-box restyle or resize left the new properties in the file. Arrow defaults were stored on plain lines. A custom match ending on a separator included the next glyph, and the vertical-text redaction error claimed an overlap it had not checked.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 01:09:36 -04:00
Earl CoandBrandon Thomas 890b83c4c4 Add reviewable text-only PDF auto-tagging (#1440)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 01:02:05 -04:00
Marco Arnulfo 0372243ef2 fix(ui): the PDF/A dialog shows a declared PDF/UA (#1309) (#1438)
The declaration line was built from the PDF/A identification alone, so a
document that declares only PDF/UA-1 read "Declared conformance: none".
It now lists PDF/UA next to PDF/A, and still says none when neither is
declared. pdfa_dialog_shows_a_declared_pdfua fails before the change.
2026-10-13 00:49:39 -04:00
Marco Arnulfo 251917f685 fix(ui): the accessibility check keeps a saved page range inside the document (#1300) (#1437)
With the options dialog turned off, run_accessibility_check used the range
saved on an earlier, longer document as it was: a page past the end
selected no page, and the page rules came out Passed without looking at
anything. The range is now clamped to the active document's pages, as the
dialog already does. a_saved_page_range_past_the_document_still_checks_its_pages
fails before the change with Passed for Tagged content.
2026-10-13 00:47:16 -04:00
Filip PranklinandClaude Opus 5.5 4f375bf45c fix(print): custom pages-per-sheet counts no longer overfill sheets (#1336) (#1430)
Layout::multiple(n) maps counts without a fixed grid to a near-square
grid (3 -> 2x2, 5 -> 3x2), but Layout::Multiple kept only the grid, so
layout() filled every cell: three per sheet put four pages on a sheet.

Layout::Multiple now carries per_sheet, capped by the grid; spare cells
stay blank and cut-stack piles use the same count. The doc_print tool
and the print dialog pass the count through.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:41:45 -04:00
Marco Arnulfo fc56888567 fix(text): keep doubled narrow letters in condensed text (fixes #1467) (#1474) 2026-10-13 00:39:07 -04:00
Marco Arnulfo 7eac105c00 fix(ui): Combine flags the same converted file added twice (#1303) (#1458)
Follow-up to #1420.

#1420 compares the bytes of each entry to tell a file added twice from a
different file with the same name and size. An image or text file is
converted to PDF each time it is added, with its own date, so those bytes
never match and the same .txt or image added twice was no longer flagged.

Each entry now keeps the file as it was added (the same bytes for a PDF,
the image or text otherwise), and that is what is compared.

Adds the_same_image_or_text_file_added_twice_is_a_duplicate in
combine_ui.rs, which fails before this change.
2026-10-13 00:36:44 -04:00
Marco Arnulfo 595026aa69 fix(ui): ui.click and ui.drag refuse coordinates that overflow (#1306) (#1447)
ui.move refuses a coordinate that isn't finite once narrowed to a
pointer position; ui.click {x, y} and ui.drag did not, so 1e39 became
an infinite position and was queued. With debug assertions on, the
next idle frame panicked in emath comparing NaN vectors.

Both now refuse such a coordinate with an error, as ui.move does, and
ui.drag also refuses ends too far apart for the way between them to
stay finite.

Adds click_and_drag_refuse_coordinates_that_overflow_like_move in
tests/control.rs, which panics before this change.
2026-10-13 00:36:25 -04:00
Marco Arnulfo cced834190 fix(ui): a rule skipped by hand stays with its document (#1301) (#1445)
Rules skipped in the accessibility results were kept in one set for
the whole app, and every check applied it. With the options dialog
turned off, skipping Tagged PDF in one document made the next
document's check report it as Skipped, its findings cleared, in the
panel and the saved report.

The set is now keyed by document: Skip Rule records the rule for the
document whose report is shown, and a check applies only that
document's skips. Start Checking in the options dialog still starts
over, for the document it checks only.

Adds a_rule_skipped_in_one_document_is_not_skipped_in_another in
tests/accessibility.rs, which fails before this change.
2026-10-13 00:36:15 -04:00
Sir Clangington III.andLabDennis 082768ac29 M13: Bound cached page text and keep partial search results (#1492)
Use a session-wide byte-accounted LRU instead of retaining every visited
page. Share the renderer's PageText Arc without cloning all glyphs and
avoid retaining obsolete document bytes through the cache.

Preserve per-page extraction results so text_find can continue after a
failed page, reporting bounded warnings alongside readable matches and
normal continuation cursors. Other text consumers retain strict errors.

Add deterministic coverage for cache budgets across documents, eviction,
revision changes, close, partial searches and bounded warnings.

Fixes #1483.

Co-authored-by: LabDennis <341377186+LabDennis@users.noreply.github.com>
2026-10-13 00:19:59 -04:00
AlexandClaude Opus 5.5 9324afaa4f fix(organize): Document Properties also change the XMP title and author (#1658)
`set_info` changed only `/Info`. Files that carry XMP (LibreOffice and Word
exports, PDF/A) kept the old dc:title and dc:creator, so viewers that read
XMP first kept showing the old title, and PDF/A preflight reported the two
as different (6.6.3).

The matching XMP property (Title dc:title, Author dc:creator, Subject
dc:description, Keywords pdf:Keywords, Creator xmp:CreatorTool, Producer
pdf:Producer) is now replaced where it stands, added in a description of
its own, or removed with an empty value. The packet is edited as text and
the rest of it stays byte for byte; files without XMP get none, and a
packet that isn't UTF-8 is left as it was.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:18:35 -04:00
Marco Arnulfo e8fcf76c33 Render: a soft mask shrunk below its size is filtered, so thin strokes drawn through it survive (fixes #1649) (#1654) 2026-10-13 00:18:27 -04:00
SteamSmithandClaude Opus 5.5 f9d7aa72b6 control: cap line length before auth (#1653)
Follow-up to #1259. Before a connection authenticates, the control server
read request lines with BufRead::lines, which buffers a whole line before
anything looks at it, so a client that never sends `auth` could make it
buffer an arbitrarily long line.

Read lines with a bounded read instead. Until `auth` succeeds a line may be
at most MAX_AUTH_LINE (4 KiB; the auth request is about a hundred bytes);
a longer one gets a -32600 error and the connection is closed, after
reading at most MAX_AUTH_LINE + 1 bytes of it. After `auth`, lines are read
whole as before. End of stream, read errors and non-UTF-8 input end the
connection as they did with BufRead::lines.

Tests: lines_are_read_up_to_the_limit, an_overlong_line_before_auth_is_refused,
long_lines_after_auth_are_still_read.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:18:19 -04:00
Marco Arnulfo a0bb1f08a4 Export all images: CMYK JPEGs keep their colours instead of coming out as negatives (fixes #1640) (#1642) 2026-10-13 00:16:45 -04:00
Marco Arnulfo a6aa05cd7b XFA: place positioned objects by their anchorType (fixes #1637) (#1639) 2026-10-13 00:16:34 -04:00
FredAppsandCodex 03d9900990 M3: preserve IME composition in command search (#1252) (#1638)
Co-authored-by: Codex <noreply@openai.com>
2026-10-13 00:16:26 -04:00
Marco Arnulfo da9cefa33c M4: copied pages keep the resources they list only up the page tree (part of #1628) (#1635) 2026-10-13 00:16:19 -04:00