Commit Graph
4058 Commits
Author SHA1 Message Date
vangel76andClaude Sonnet 5.5 0871543fd8 M7: rotate an existing page image by any angle (#1940)
A selected image gets a rotate knob (Shift: 15 degree steps); image_edit
gains rotate_by (degrees). The turn is one extra cm about the visible centre.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 23:14:59 +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
Charan Rathore 0d35035e07 Delete crates/render/src/inspect.rs 2026-10-16 02:37:47 +05:30
Mex 14c3d951e7 M6: preserve complete plain XML form values 2026-10-15 23:04:14 +02:00
Tanish MankuandClaude Opus 5 99941daa5a docs(roadmap): record the day's two changes
`CLAUDE.md` asks for a progress-log line and a status line at the end of a session.

- The Annex D.2 alias codes, in both tables (`13e3154a`).
- Reading order in the text oracle (`50cbf5fa`), with gap 12's revision history.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-10-15 13:34:38 -07:00
Muhammad Saad c9956bf77f Replace Text: give the caret its own /NM (#2666)
add_text_replacement built the StrikeOut and the Caret from one Meta, and
add_annotation stamps /NM from meta.id, so the pair shared a single name. XFDF
names a reply by the /NM of what it replies to, so the exporter wrote the
caret's inreplyto as the caret's own name. Importing that file found a comment
replying to itself, and the Replace Text comment did not arrive: the reporter's
second document listed none. FDF was unaffected because it keeps the pair by
object structure rather than by name.

Take a second Meta for the caret. The ids stay supplied by the caller, as the
rest of the crate does so edits remain deterministic, and the one production
caller already has a generator and calls it twice. /IRT still points at the
strikeout, so the group still moves, lists and deletes as one.

Tests: the annotation test now asserts the two carry different names and that
the caret's /IRT resolves to the strikeout's. A new XFDF test exports a Replace
Text pair and imports it into another document, asserting both comments arrive
with their thread intact; it fails if the two are given one id again.
2026-10-16 00:34:21 +04:00
Tanish MankuandClaude Opus 5 50cbf5fa19 feat(oracle): score reading order, which word F1 cannot see
`cargo xtask text-oracle` reported one number: word F1 against `pdftotext`. F1 is a bag of
words. It catches a character read wrong, a glyph not read at all, a font we cannot map -- and
it is blind to order, so a page emitted back to front scores a perfect 1.000. Reading order is
most of what text extraction has left to get wrong: a column read into the middle of its
neighbour (#2387), a figure's labels threaded through a paragraph (#2586), a form's value parted
from its label.

The hole is not hypothetical. Reviewing #2431, a change that moves text and alters nothing else,
**word F1 called 26 of the 29 files whose output changed identical**, while scoring the word
sequence separated 18 improvements from 6 regressions and named the shape of the regressions
(forms: `prefilled_f1040.pdf` 0.672 -> 0.481, a checkbox's number parted from its label). The
harness could not see the only thing that change does.

So a second score per file: the longest common subsequence of the two word sequences, as a share
of the longer one. It shows up in the first three corpus files -- `Test-plusminus.pdf` scores F1
1.000 and reading order 0.875, the same forty words in a title block that we read down the
column and `pdftotext` reads across.

Both sides are cut to the first 3,000 words, because the comparison is quadratic and three pages
is plenty of signal; the run says how many files that bit, so the cap cannot quietly become the
measurement. It reports; it does not gate, and like the pixel score it is a *disagreement* with
Poppler rather than a verdict -- in the #2431 measurement the same split scored as a win on one
file and a loss on another purely because `pdftotext` makes it in one place and not the other.
The function's doc comment, `crates/oracle/README.md` and `docs/gaps.md` gap 12 all say so.

`words` now takes the sequence rather than re-splitting the text, so both scores read the same
tokens. `docs/file-format-parity.md` claimed plain-text export was good "(word-F1 0.98)" in a row
whose own column is reading order; it now says the two are scored separately.

Verified: `cargo xtask text-oracle --limit 60` end to end; `cargo test -p xtask` 49 passed; fmt
and clippy clean. The unit test was watched failing with the score blinded to order (sorting both
sides made it 1.000 where it must be below 0.7). The corpus-wide first reading is not in this
commit: the machine is shared with a parallel build and the full sweep was stopped twice for
memory.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-10-15 13:33:22 -07:00
Dimitar KlaturovandClaude Opus 5.5 1dac21c508 Print: PPD localization helpers are CUPS-only outside tests (Windows dead-code error)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 23:31:47 +03:00
Charan Rathore 53425fc606 test(annot): fix final-line indentation
Corrects the closing-brace indentation on the last line so cargo fmt --check stays clean.

Signed-off-by: Charan Rathore <180254320+charan-rathore@users.noreply.github.com>
2026-10-16 01:57:38 +05:30
Charan Rathore 8947465af7 test(annot): cover 7-bit ASCII percent-encoding of link /URI
Regression test for the /URI percent-encoding fix: round-trips a non-ASCII URL and an already-escaped URL through save/reload and asserts the stored URIs are pure ASCII with existing escapes preserved.

Signed-off-by: Charan Rathore <180254320+charan-rathore@users.noreply.github.com>
2026-10-16 01:56:26 +05:30
Charan Rathore 46ebc6dcf7 fix(annot): percent-encode non-ASCII bytes written into link /URI
A link annotation whose target contains non-ASCII characters (for example https://example.com/ação) was written into the PDF /URI string as raw UTF-8 bytes, but URIs in PDF are 7-bit ASCII strings (ISO 32000 §12.6.4.8). The saved file then carried an out-of-spec URI that strict readers can reject or misparse.

Percent-encode every byte outside the printable ASCII range when writing the /URI entry. Bytes already inside 0x21..=0x7E pass through unchanged, so existing percent-escapes like %20 are preserved.

Adds a regression test that round-trips a non-ASCII URL and an already-escaped URL through save/reload and asserts the stored URIs are pure ASCII.

Signed-off-by: Charan Rathore <180254320+charan-rathore@users.noreply.github.com>
2026-10-16 01:55:31 +05:30
NetterSilverandClaude Haiku 5.5 bafb948226 Round FormCalc Str and Format exact ties away from zero
Found while testing crates/js/src/formcalc.rs:2169 (Str) and :2258 (Format number picture)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 22:19:58 +02:00
Mex f1177bb287 M6: refresh XML whitespace fix and record parity evidence 2026-10-15 22:01:42 +02:00
Tanish MankuandClaude Opus 5 13e3154ab3 fix(fonts): read the Annex D.2 alias codes as the ink they draw
#2305 fixed WinAnsi 0xAD in `crates/fonts`, and closed with two loose ends it named: the
user-visible extraction path goes through hayro, not that table, so it still reported the
invisible character; and the same Annex D.2 note makes 0xA0 the `space` glyph. This finishes
both, and makes the two tables in the product agree about the same byte.

WinAnsiEncoding gives codes 0xA0 and 0xAD the glyph names `space` and `hyphen` -- the names it
also gives 0x20 and 0x2D -- and MacRomanEncoding gives 0xCA `space`. The tables called them
`nbspace` and `sfthyphen`, whose Adobe Glyph List values U+00A0 and U+00AD are invisible
formatting characters, so extraction hid ink the reader can see: "well-known" drawn with the
0xAD byte matched no search and no redaction pattern, and a non-breaking space did not match the
space a reader typed.

Evidence. Two sources transcribe the table the same way (an Annex D implementation's docs, and
the WinAnsi.enc encoding vector R ships, which gives `/space` at octal 240 and `/hyphen` at
255), and a black-box check settles the rest: on a synthetic fixture pdftotext reads the
base-encoded codes as `a-b a b`, and keeps U+00AD for a code an explicit `/Differences` names
`sfthyphen`. We now do both. Over the 989-file pdf.js corpus the soft hyphens we extract fall
from 117 to 13 and the non-breaking spaces from 30 to 26; everything left comes from `/ToUnicode`
maps, which say what they mean and are not touched.

Scope. The base table only, in the text direction only.

- A `/Differences` entry naming `sfthyphen` or `nbspace` is a deliberate statement about one
  code and keeps its glyph list value, as pdftotext does.
- Glyph lookup is untouched, so a font program's own `sfthyphen` program is still found first.
  In hayro that is why the alias sits in the Unicode path rather than in the generated encoding
  table: `code_to_ps_name` is shared with lookup.
- Writing is unaffected: `encode` takes the lowest code showing a character, so ' ' and '-'
  still write 0x20 and 0x2D, and a caller holding a literal U+00A0 still writes 0xA0.

Tests. `fonts` pins both tables and the whole decode/encode path including the `/Differences`
case; `render` pins the extraction a reader actually gets, over WinAnsi, MacRoman and
`/Differences`. Each was watched failing with each cell and each match arm reverted in turn, so
no assertion is passing by accident. fonts 70, render 296+4, redact 49, a11y 43 and edit 156 are
green, with fmt, clippy, `xtask layers` (37 crates) and `xtask assets` clean.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-10-15 12:52:53 -07:00
Mex 87e69d2c72 M4: merge current upstream into indirect rotation fix 2026-10-15 21:48:28 +02:00
Mex 7bb2d49467 M1: merge current upstream into Flate fallback fix 2026-10-15 21:40:21 +02:00
PerchAnton ff62683ff8 M12: skip PDF container scripts during active XFA layout (#2589) 2026-10-15 22:18:58 +03:00
Dimitar KlaturovandClaude Opus 5.5 a9320efd7a Print ▸ Properties…: the driver's options in the UI language
CUPS drivers ship their names in other languages as PPD localization lines
(`*cupsLanguages`, `*de.Translation KMDuplex/Druckart: ""`, `*de.KMDuplex
Double/2-seitig: ""`), which the system print dialog uses. PdfCraft showed the
PPD's default (English) names in every language. `spool::printer_options_in`
now applies the lines for the UI language (`pt-br` → `pt_BR`, then `pt`;
`zh-hans` → `zh_CN`) to option, group and choice names. Each line is decoded as
UTF-8 on its own, as drivers write them in otherwise Latin-1 files; missing or
empty translations keep the driver's name. `printer_options` (automation) is
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 22:13:51 +03:00
alehelandClaude Opus 5.5 fb10540990 fix(ui): dialog buttons hug their content; no page cursors under modals
The shared dialog footer laid its buttons out right-to-left in all the
height left, centred. After a taller Document Properties tab the modal
kept that tab's height, so Close floated far below a shorter tab's last
row. The footer is now one row high.

Page hovers (form fields, comments, the link tool, image editing) set the
cursor from the raw pointer position, so a hand or text cursor showed
over fields under a dialog's backdrop, where nothing takes clicks. While
a modal is open the cursor starts from the arrow each frame and only the
modal's own widgets change it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:59:29 +02:00
alehelandClaude Opus 5.5 1646fc7817 docs(claude): check for existing issues and PRs before starting a fix
Agents fixed bugs that were already reported, or already fixed on main,
because nothing told them to look first. Say to search issues and PRs
(open and closed), check whether a merged fix is on main, and build on
an open PR rather than opening a competing one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:59:29 +02:00
alehelandClaude Opus 5.5 d48ce4ae1b fix(ui): Document Properties gains a Space Audit tab
Properties ▸ Space Audit shows the same bytes-by-kind table as the PDF
Optimizer's Audit Space Usage, which now shares its table code. The
audit walks the whole file, so it runs when the tab opens and again only
after an edit (keyed on the document and its edit generation). The tab
is also reachable as `--dialog space-audit`.

The audit table no longer overflows its dialog: egui's default minimum
column width stretched the narrow Bytes column past the computed widths,
widening the card by about 5 points.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:59:29 +02:00
alehelandClaude Opus 5.5 b349ef480f fix(ui): Document Properties values line up and long fields wrap
The Description tab's plain values (File, Application, PDF producer) now
start where the text inside the Title/Author fields does, the fields use
the dialog's full width instead of a fixed 420 points, and Subject and
Keywords wrap onto more lines instead of clipping mid-word.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:59:29 +02:00
outragedevs 4a991b03b5 M7: Let the page range select page 1 alone
Seitenbereich „1 bis 1“ springt nicht mehr auf die letzte Seite zurück.
2026-10-15 20:21:57 +02:00
Mateus BessaandClaude Opus 5.5 64ab6b67ec fix(ui): Copy in the selection action bar closes the bar
The bar beside selected text closed after Highlight, Underline, Strikethrough,
Add comment and Replace text because those consume the selection. Copy kept the
selection, so the bar stayed open. Remember the copied selection and hide the
bar until the selection changes; the words stay selected for ⌘C or another
action.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 15:14:30 -03:00
FredApps 1d5c20c5c4 Support typed font sizes and Fill and Sign text colours (#2444, #2496) 2026-10-15 20:02:36 +02:00
mtlcl410andClaude Opus 5.5 294fea2fb8 fix(ui): Reveal in File Explorer selects files whose path has a space (#2579)
Explorer reads /select,"<path>". The path went in as one ordinary
argument, which Rust quotes whole when it contains a space
("/select,C:\a b.pdf"), and Explorer answers that by opening Documents.
A file saved to a folder or name with a space (often after Save As) was
therefore never shown. reveal_command now quotes the path after the
comma and the Windows runner passes the arguments as written; a path
containing a quote, which Windows doesn't allow, is refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
2026-10-16 01:34:32 +08:00
PerchAnton 97d286b2b9 M12: reflow growable XFA text after form edits (#2569) 2026-10-15 20:33:03 +03:00
fanyzp 1c3cb9d14d feat(forms): nudge the selected field with the arrow keys in Prepare a form (#2608) 2026-10-16 01:15:06 +08:00
PerchAnton f06bebd520 M12: dispatch repeated XFA print buttons by their own field (#2572) 2026-10-15 20:08:05 +03:00
PerchAnton a146648648 M12: enforce XFA rich-text maxLength (#2570) 2026-10-15 19:52:42 +03:00
fanyzp 963609d7d6 fix(edit): draw the Cyrillic the Japanese faces lack with the bundled Inter face (#2593) 2026-10-16 00:31:49 +08:00
magdale76andClaude Opus 5.5 67d35b70a0 M14: no PdfCraft lion over PDF thumbnails and the preview pane
With PdfCraft as the default PDF program, Windows painted the program's
icon, the lion, over the lower-right corner of every PDF thumbnail and of
the preview pane, covering part of the page. Thumbnail overlays come from
the ProgID's TypeOverlay value. When it is absent, Windows uses the default
program's icon, and an empty string means no overlay ("Thumbnail Handlers",
Microsoft Learn). The installer now writes TypeOverlay = "" on
PdfCraft.Document.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 18:09:16 +02:00
alehelandClaude Opus 5.5 b9e1b06b94 M3: name the All tools expand and collapse buttons for what they do
The sidebar's expand button was labelled "Expand", the same as the Combine
grid's per-file Expand on its hover bar. With both on screen, the Combine
grid tests' lookup by that label found two buttons and 13 of them failed.
The buttons are now "Expand all tools" and "Collapse all tools", translated
in all 22 catalogues, and the labels are tl! literals the coverage scan sees.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 17:58:11 +02:00
Krzysztof Cieślik 7e68ef44d5 fix(combine): let the cards zoom to 500% (#2531)
The Combine grid stopped at 200%, too small to read a value off an invoice
while reordering the pages of a statement. The main viewer already reached
6400%, so only the card grid was capped.

- The range is 60–500%, and the buttons and keys step
  60/80/100/125/150/175/200/250/300/400/500%.
- MAX_SIDE, the thumbnail texture cap, goes 768 → 1536, so a card near the top
  re-renders sharper instead of upscaling a 768px texture. It only binds near the
  top: a card below it asks for less and costs no more than it did. Up there one
  thumbnail is a few megabytes, so TEXTURE_BUDGET and UPLOAD_BYTES_PER_FRAME keep
  the cache at a screenful plus one frame's uploads — and a screenful at the top
  of the range is a card or two, because a card is wider than most windows.
- The control channel's range message is derived from the constant rather than
  repeating "60 and 200", so it cannot drift from the range again.
2026-10-15 17:43:46 +02:00
NetterSilverandClaude Haiku 5.5 db4157099a Don't treat a date ending in "Sep" as PM in AFDate/AFTime parsing
Found while testing crates/forms/src/af.rs:590

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 15:43:14 +00:00
NetterSilverandClaude Haiku 5.5 7cc073ab89 Shorten over-long part file names in doc_split by bookmarks
Found while testing crates/automation/src/lib.rs:1747

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 17:42:18 +02:00
oracola91 f59320b1a0 fix(web): host mode needs a parent page on the same origin
With ?host=parent, saves are posted to the parent with the app's own
origin as the target. A framing page on another origin never gets them:
the browser drops each message without an error, post() returns Ok, and
the document is marked saved and the leave-page warning cleared (#2167).

Bridge::new now returns None unless the parent's origin is the app's
own (reading another origin's location throws, which is None too). The
app then runs as if unframed and saves download, and a console warning
says why. Same-origin embedding is unchanged.
2026-10-15 18:36:56 +03:00
Codex b05a06e8e2 Merge main CI repairs and preserve button export translations 2026-10-15 17:29:00 +02:00
Verix bdb943dc3d M3: a page jumped to in single-page view no longer hops into place
The jump scrolls to MARGIN - GAP above the page. A page that fits has
nothing to scroll, and egui clamps the offset only after drawing the
frame, so the page showed 14 pt too high for a frame and then hopped
down. Clamp the jump to the scroll range before handing it to egui.
2026-10-15 17:28:46 +02:00
jonathanwxh-cell 279712769d fix(edit): keep overlapping watermarks and distinct paragraphs on text edit (#1756) 2026-10-15 23:22:11 +08:00
komo1iddinandClaude Opus 5.5 8ebe4f7a76 M7: write a full font program compressed and find it again on any page
A text edit that needs glyphs the source font's embedding lacks writes
the face's full program once. It was written uncompressed, and the
document-wide search that lets another page reuse it compared the
stream's raw bytes, so it only matched an uncompressed copy.

Write the program with Flate, and compare its decoded bytes (after the
/Length1 check) both on the page and across the document.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:21:02 +05:00
Verix ff526a7896 M3: single-page view turns pages without a blank frame
At Fit Page in single-page view, every wheel turn forward drew one
blank frame before the new page appeared.

- Lay out the shown row again after the wheel is read. The jump to the
  new page was measured from the old row's place and scrolled both out
  of view; going back it came out negative and was clamped, so only
  forward turns flickered. The edge-turn deferral that worked around
  this goes.
- Prefetch the rows either side of the shown one, so the next page is
  rendered before the turn instead of showing blank paper.
2026-10-15 17:19:56 +02:00
Osvaldo Duran 96afca8fd2 Merge main (dc41044b) into pr/attachments: the Vietnamese and Lithuanian catalogs' new rows beside this change's 2026-10-15 11:09:41 -04:00
oracola91 00fb10cf1e fix(crypt): /Perms limits what an edited /P allows
AES-256 files (R5 and R6) store the permissions twice: in clear in /P,
and in /Perms, encrypted with the file key (ISO 32000-2, Algorithm 13).
We wrote /Perms but never read it, so a document opened with the user
password got whatever /P said, even when /P no longer matched /Perms.

permissions() now decrypts /Perms and, when it holds a valid block
(marked "adb"), counts a permission only if both /P and /Perms grant
it. Without a readable /Perms, /P applies as before. R2 to R4 need no
check: /P is part of their file key, so an edited /P does not open.
2026-10-15 17:56:45 +03:00
alehel cd16644a64 Merge remote-tracking branch 'origin/main' into feat/all-tools-expand-view
# Conflicts:
#	ATTRIBUTION.toml
#	crates/ui-egui/src/i18n/lt.tsv
#	crates/ui-egui/src/i18n/vi.tsv
2026-10-15 16:54:41 +02:00
komo1iddinandClaude Opus 5.5 398ad77077 M7: rewrap a paragraph at a slant at the width its rows have
A turned paragraph's width came from its rows' upright bounding boxes,
turned into the baseline's frame. On a slant that box is wider than the
row by the row's height times |sin 2θ| (about 12 pt for 12 pt text at
30°), so a rewrite let a word that doesn't fit stay on its row.

Each run now also records its box along and across its baseline, from
the same corners as its upright box, and a line unions those. Grouping
and the rewrite's width read that box. Upright and quarter-turned text
are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 19:51:57 +05:00
Guillem VelaandClaude Sonnet 5.5 aad803caae M14: add Catalan interface and date localization
Register Catalan (`ca`) as an interface and date language with a
2,592-entry contributor-original catalog written from the meaning of the
English labels. Plurals use the one/other rule; every `ca-*` locale,
including Valencian (`ca_ES@valencia`), resolves to this catalog.

Tests cover registration and persistence, every command, menu, tool group
and section, every `tl!` literal in the UI source, and every key in the
other bundled catalogs. The Latin-script glyph test in tests/fonts.rs now
includes Catalan (the interpunct of l·l is covered by the bundled faces).

Catalan dates elide the preposition before a vowel (7 de març, 7 d'abril,
8 d'octubre), so the date language sets months_with_day, as Czech and Russian
do; a unit test pins it.

Update localization docs, the parity table and the roadmap log, and add the
ATTRIBUTION.toml entry (ATTRIBUTION.md regenerated). The contributor, a
native Catalan speaker, has reviewed the translation.

Validation: cargo fmt --check; clippy -D warnings and tests on
pdfcraft-ui-egui (i18n, fonts); pdfcraft-engine dates tests;
cargo xtask assets; cargo xtask layers.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 16:51:02 +02:00
FredAppsandCodex dc41044ba7 Fill Vietnamese and Lithuanian catalog coverage gaps (#2510)
Co-authored-by: Codex <codex@openai.com>
2026-10-15 16:50:50 +02:00
FredAppsandCodex 609a724235 Fix main UI regressions in paging, fonts and test expectations (#2502)
* Fix main UI regressions in paging, fonts and test expectations

* Wait for asynchronous dropped-document preparation in regression

---------

Co-authored-by: Codex <codex@openai.com>
2026-10-15 16:50:45 +02:00