Commit Graph
1092 Commits
Author SHA1 Message Date
3f9cd3b87d M1: every PDF leaves the engine checked, and every writer uses one staging-file writer (#2166) (#2182)
New files from Combine, Extract, Split and Create, signed, timestamped and
LTV-embedded files and the OCR batch output reopen with their pages before
anyone writes them, as saves already did. The desktop app, the automation
tools (with or without --root) and the CLI's edit, combine, extract and
split write through pdfcraft_platform::staging::write_atomically, so a
failed write or --out naming the input never leaves a half-written file.
Special files such as /dev/stdout are still written as given.


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

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:48:09 -04:00
Marco Arnulfo c3e9609bd1 fix(fonts): keep a subset Type 0 font for glyphs /W leaves to /DW (part of #1388) (#2153) 2026-10-15 02:47:44 -04:00
c1146d0d8d feat(ui): improve back and forward page navigation (#2080) (#2151)
Co-authored-by: srt8hellcat2-ui <225485253+srt8hellcat2-ui@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:47:33 -04:00
1af7a38bd0 Fix multi-select choice index ordering (#2144)
* M6: sort multi-select choice indices

Write ascending /I option indices without reordering /V selections.
Add forms and engine regressions for reverse selection and save/reopen.

Co-authored-by: Codex <codex@openai.com>

* M0: update rustls to 0.23.46

Update only the resolved rustls version and registry checksum.
Keep the existing choice-index feature changes and advisory policy.

Co-authored-by: Codex <codex@openai.com>

---------

Co-authored-by: sanyamsingh001 <314051937+sanyamsingh001@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:47:08 -04:00
Marco Arnulfo b008c52291 OCR: Downsample to shrinks a page box given in pixels (fixes #1848) (#2135) 2026-10-15 02:46:44 -04:00
Marco Arnulfo 210fb904cc fix(render): a soft mask fades a gradient fill again (fixes #2128) (#2132) 2026-10-15 02:46:20 -04:00
Larry HymesandLarry Hymes d01f536f72 test(ui): wait for the save to write the file instead of a fixed frame count (#2122)
Co-authored-by: Larry Hymes <19394645+lhymes@users.noreply.github.com>
2026-10-15 02:46:09 -04:00
b369a50b68 fix: Space Audit lines up its columns and counts form fonts as fonts (#2118)
* fix(optimize): count what form fields and comments draw with by kind

The space audit claimed the AcroForm and annotations before any page
resources, so the fonts in /DR and in widget and comment appearance
streams were counted as Forms or Comments. A fillable form with an
embedded field font showed mostly "Forms" and almost no "Fonts".

Resources are now sorted by kind first, wherever they are used: page
resources, the form's default resources and every annotation
appearance (one stream or a dictionary of states). A form XObject's
other entries now count as X Object Forms instead of overhead.

Sorting nested forms' resources recursed once per form, so a long
chain of nested forms overflowed the stack. The recursion is now
bounded at 64 levels; deeper forms are still counted as X Object
Forms through the iterative walk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ui): line up the Space Audit columns and say what it shows

The percentages were drawn at the dialog's far edge, away from their
header and outside the striped rows: egui gives a grid's last column
the rest of the window, and the right-to-left layout used to
right-align the numbers filled all of it. Every cell is now laid out
first and given exactly its column's width; numeric columns and
their headers are right-aligned, and the description column takes
the rest, so the stripes span the dialog.

A caption under the title says the table is what takes up the file's
space now (not what optimizing would save), translated in every
catalogue. The parity notes no longer call the space audit missing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Refresh catalog hashes after merging main

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:45:58 -04:00
716a957186 fix(sign): a signed page's resources are page content whatever keys a later revision writes (#1787) (#2115)
A rewritten resources dictionary, font, XObject or graphics state of a
signed page changes what the page shows. It was judged by the keys its
new version carried, so adding /FT or /Subtype /Widget made it a
permitted form fill. The roles now come from the signed revision's own
object graph, walked from each page's resources with bounds; a category
dictionary that only gained a name is judged as before.


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

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:45:35 -04:00
a7fe7a20c4 M7: Hebrew in Add text (#128) (#2109)
Add text and page_add_text draw Hebrew with the craft-fonts Hebr face
(Noto Sans Hebrew) through the shaped path Arabic and Thai already use:

- pdfcraft-fonts: ShapedScript::Hebrew, shape_hebrew and hebrew_has.
  Grapheme clusters keep points and cantillation marks on their letter.
- pdfcraft-edit: Hebrew paragraphs are laid out with UAX #9 like Arabic;
  spaces and punctuation between Hebrew letters take the Hebrew face.
  first_undrawable refuses Hebrew the face can't draw (or without the
  face) instead of writing "?"; existing items stay movable without it.
- Type 3 glyphs with ToUnicode and per-line ActualText, so extraction,
  search and the editor get typing order back.

Regular style only, as for Arabic and Thai.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:45:12 -04:00
jonathanwxh-cellandClaude Opus 5.5 b655208aaf fix(ui): header, footer and watermark dialogs refuse undrawable text before OK (#2013) (#2107)
The dialog previews in the interface font, which draws Cyrillic, while
applying draws with the standard fonts and refused it after OK.
HeaderFooter::first_undrawable and Watermark::first_undrawable are now
the one check: applying refuses through them, and the dialog shows the
Edit Text warning and disables OK on the same answer.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 02:44:49 -04:00
178d9a5882 fix(a11y): mark_decorative finds MCIDs in named property lists (#1813) (#2106)
A BDC may name its property list in /Resources /Properties instead of
writing it inline. Marking a figure decorative and placing figures only
matched inline lists, so a figure tagged through a named list kept its
MCID content and parent-tree entry while its element was removed.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:44:37 -04:00
3a4de17707 M3: preserve coordinate clicks across slow frames (#2100)
Pair coordinate press and release after hover warmup so frame delays do not turn clicks into holds. Preserve text multi-click selection for coalesced pointer events.

Co-authored-by: sanyamsingh001 <314051937+sanyamsingh001@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:44:14 -04:00
Aleksander HelgakerandClaude Opus 5.5 b0a692c139 fix(ui): check boxes over text show the pointing hand, not the I-beam (#2099)
Hovering a check box or radio button that sits on page text showed the
text-selection I-beam. The form layer set the pointing hand, but on a
plain hover it reported the pointer as not consumed, so the text layer
ran afterwards and replaced the cursor whenever a glyph was under it.
Character sheets hit this on every dot: the boxes are set on printed
"OOOOO", and boxes whose checked look is a font glyph (an "X") count as
text too, since page text includes annotation appearances.

forms_ui::page_input now says whether the pointer is merely over a field
or the field owns the press, and the text layer leaves the cursor alone
while a field is under the pointer. Presses, clicks and drags behave as
before; text fields keep their I-beam.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 02:43:49 -04:00
Marco Arnulfo d09eabd664 fix(organize): keep page labels on their pages when pages are deleted (fixes #2091) (#2093) 2026-10-15 02:43:38 -04:00
mtlcl410andClaude Fable 5.1 2bcf233f22 chore(packaging): give test-msi.ps1 a UTF-8 BOM so Windows PowerShell 5.1 parses it (#2092)
Without the mark, 5.1 reads the file in the system code page and the
ellipsis on line 136 decodes into something that swallows a quote, so the
script does not parse (MissingEndCurlyBrace at line 133) and package.ps1,
which runs it with the shell it was started from, fails after building
the MSI. pwsh reads UTF-8 either way.


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

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-15 02:43:27 -04:00
712ae9c36a M3: a document that asks to open in Full Screen mode asks first (/PageMode /FullScreen) (#2086)
* M3: a document that asks to open in Full Screen mode asks first (/PageMode /FullScreen)

The Initial View option "Open in Full Screen mode" was written and read
but never acted on when opening. Now the app asks, as Acrobat's alert
does: Full Screen enters it, Not now leaves the window as it is. A
document cannot take the screen on its own.

Refs #379, #1459.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz

* Refresh catalog hashes after merging main

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:43:17 -04:00
53267c66d5 M12: the geometry of page transitions (ISO 32000-2 table 164) (#2084)
pdfcraft_render::transition gives the picture of a page transition at a
moment of its run: which parts of the outgoing and incoming page to paint,
where, and how opaque, in page fractions, for all twelve styles and every
direction. Pure and tested; the full-screen presentation will paint from
it when it plays transitions.

Refs #379.


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

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:42:51 -04:00
c0f76fc73a M4: set page transitions and display times (/Trans, /Dur) through the engine and the tools (#2083)
* M4: set page transitions and display times (/Trans, /Dur) through the engine and the tools

organize::set_transition writes or removes a page's /Trans dictionary
(only the entries the style reads and that differ from the defaults) and
/Dur, with every value checked before any page changes; Edit::SetTransition
is one undo step ("Set page transitions", translated in every catalogue);
page_transition_set is the tool. The counterpart of reading them (#2074);
playing them and the Organize dialog stay planned.

Refs #379.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz

* Refresh catalog hashes after merging main

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:42:28 -04:00
mtlcl410andClaude Fable 5.1 8772db0a6e M4: read page transitions and display times (/Trans, /Dur) into DocInfo (#2074)
Each page's presentation settings of ISO 32000-2 s12.4.4 are read with the
spec's defaults for what is missing or out of range: the transition style,
duration, dimension, motion, direction, Fly scale and backdrop, and the
/Dur display time. doc_info lists them; pdfcraft-cli info counts them.
This is the data full screen needs to play transitions and auto-advance;
playing and editing them stay planned.

Refs #379, #2070.


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

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-15 02:42:04 -04:00
f00a84121b chore(contributors): credit mtlcl410 by display name (#2065)
* chore(contributors): credit hpcgrid as mtlcl410

My own people.toml entry: display name only, no real name, no GitHub
profile sync.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz

* chore(contributors): the entry's key follows the renamed account

The GitHub account was renamed from hpcgrid to mtlcl410 after the first
commit, and people.toml keys are GitHub usernames.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:41:53 -04:00
Lucas Sampaio DiasandBrandon Thomas aa050612c5 M3: preserve Ctrl+Alt+Q input during digital ID creation (#1980) (#2055)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:41:32 -04:00
bbc99126db i18n(es): translate the new print and Add text strings, Type on the signature pad, tú register (#2049)
* i18n(es): translate the new print and Add text strings, Type on the signature pad, tú register

Seven tl! literals reached the Spanish UI in English: the PDF page size,
Match page at 100%, PDF scale and the two scale hints in the print dialog,
and the Done / Discard buttons of Add text. The signature pad's Type
button read "Tipo" (the noun) because Spanish had no `signature pad`
entry; it now reads "Escribir", beside Dibujar, Imagen and Escanear.

Four rows used the formal usted register the catalog header rules out
(Elija, Cámbielo, the signature-photo hint, the no-signature notice);
they now use tú like the rest.

Spanish gains `spanish_covers_ui_literals`, the check Japanese, Italian,
French, German and others already have, and its command test checks
menu names too, so a new string can't ship in English by accident.
`contextual_labels_fall_back_to_the_plain_translation` used Spanish's
missing `signature pad` entry as its example; it now uses a context no
catalog defines, so it keeps testing the fallback as catalogs fill in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Refresh catalog hashes after merging main

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:41:10 -04:00
Omar SaidahmedandBrandon Thomas 8c4e633a6e make xfa som index lookup linear (#2047)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:40:48 -04:00
cyfung1031andClaude Haiku 5.5 0bf49607bf M2: heavy-sheet generator and page timing harness (item 8 of storytold/pdfcraft#623) (#2042)
* M2: heavy-sheet generator and page timing harness (item 8 of storytold/pdfcraft#623)

Adds two examples to pdfcraft-render. synth_sheet writes a synthetic A1 sheet whose content
follows the sample's layout, so timings on it can be compared with the sample. page_timing
replays the canvas's requests for page 1 and prints times, counts and an output hash. The
renderer is unchanged. Budgets are reported, not enforced.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>

* M2: sort sweep results by tile size too (item 8 of storytold/pdfcraft#623)

Tiles at the same origin but different sizes compared equal under the sort key, so the
sweep hash depended on completion order. Including the size makes the key unique.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-15 02:40:26 -04:00
0b4a5e44c5 M3: wait for asynchronous remote-link refusal notices (#2041)
Wait for the existing notification before checking that invalid relative
link targets open no tab and report the expected refusal.

Co-authored-by: sanyamsingh001 <314051937+sanyamsingh001@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
2026-10-15 02:40:16 -04:00
Leo CamusandBrandon Thomas 15ca7e13ac fix(forms): read each JavaScript name tree node once (#2040)
document_scripts_named walked the catalog's /Names /JavaScript tree by
resolving and deep-cloning every kid onto its stack before looking at it.
The 10,000 cap counted nodes taken off the stack, not what went on it, so a
node whose /Kids lists itself k times pushed k full copies of itself (each
holding its k-entry /Kids array) per visit. A 6.5 KB file with k = 1000 made
`pdfcraft-cli run` (doc_open, js_document_scripts) exhaust an 8 GB address
space in 8 seconds and abort; the same walk runs when a field edit sets up
the script runner. A tree that lists a leaf twice returned its script twice.

Keep kids unresolved on the stack and skip references already read, as the
/Dests walker in organize does. The cap stays.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:40:06 -04:00
tydude001andClaude Opus 5.5 32a397f27a fix(linux): open files from later launches in the running PdfCraft (#1726) (#2037)
The single-instance handoff (#282, #715) ran only on Windows; every other platform returned
Claim::Alone, so on Linux each xdg-open or file-manager open started a second process and window.
Linux and the BSDs now use the same Unix-socket exchange through std's UnixListener/UnixStream,
with the socket in $XDG_RUNTIME_DIR/pdfcraft (never /tmp) and named after WAYLAND_DISPLAY or
DISPLAY, so a launch on another display opens its own window. No XDG_RUNTIME_DIR: as before.
macOS is unchanged (Apple events).

winit ignores Focus on Wayland, so on a native Wayland window the handoff also requests user
attention (xdg-activation), which desktops show in the taskbar.

The hand-off, together and stale-socket tests now run on Linux/BSD too; a new test covers the
display socket names.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 02:39:44 -04:00
Benno Evers d8f32e767c M7: list Edit text & images first under Modify page (#2028)
Edit text & images changes what is already on the page rather than
adding content, so it belongs under Modify page, the first section.
2026-10-15 02:39:27 -04:00
15d82b5b9c fix(render): a Multiply highlight inside a transparency group keeps the text (#1968) (#2027)
Highlight appearances written by other editors are a transparency group whose
content is filled with `/BM /Multiply`. Every `/Group` form got a layer of its
own, so the Multiply blended with that layer's transparent backdrop and the
layer then covered the page as an opaque block.

Vendored hayro-interpret patch (28): a group that is neither isolated nor
knockout composites its elements with the backdrop (ISO 32000-2 §11.4.7), so
when it is entered at full opacity with no soft mask and Normal blending there
is nothing for a layer to add, and its content is drawn straight onto the page.
`/I` and `/K` are read from the group dictionary. An isolated group keeps its
blank backdrop; a knockout group or one entered at partial opacity, under a
soft mask or with a blend mode keeps its layer.

Test: `raster::tests::a_multiply_highlight_inside_a_transparency_group_keeps_the_text`
(the appearance drawn as an annotation and from page content, with `/I false
/K false` spelled out and with no group at all; an isolated group as a control;
a group entered at half opacity still composited as one). It fails before the
fix with the text pixel yellow instead of black. parity: view.render-transparency
cites it and says what is still approximated.


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

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:39:16 -04:00
572870eca8 M2: clip intersection starts its row walk at the first shared row (#2026)
* M2: clip intersection starts its row walk at the first shared row

clip::intersect in vello_common, the step every clipped path passes
through, began its walk over strip rows at the earlier of the two paths'
first rows. Each path therefore visited every row above it, although rows
where one path has no strips produce no output. Every page is clipped (the
crop box is a clip), so this ran for every stroke of every render: on the
A1 engineering sample it was the hottest function in a full-page render.

Start at the later first row, as vello_common 0.1 does. The strips and the
pixels are unchanged; a test counts the rows the walk visits and fails
without the change.

vello_common 0.0.8 is vendored with that one-line PdfCraft patch (marked
in the source), its unit test, the vendor/README.md row and an
ATTRIBUTION.toml entry for its reference image. Cargo.lock records it as a
path crate.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>

* Refresh catalog hashes after merging main

---------

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:38:54 -04:00
osaym e6e65d0292 fix(ui): wrap long filenames and metadata in Document Properties (#2011) (#2012)
Wrap text in Document Properties rows within the 420 pt content column so documents with long filenames or metadata do not stretch the modal dialog card beyond the viewport boundaries.

Fixes #2011.
2026-10-15 02:38:32 -04:00
8da1d3efce Redaction Tool Properties: the mark's appearance, and Make properties default (#2008)
* Redaction Tool Properties: the mark's appearance, and Make properties default

Acrobat's third group in the Redaction Tool Properties dialog: the mark's
outline colour (/C), a fill colour shown while the pointer is over a mark (a
viewing preference, never written), and the mark's opacity (/CA). Make
properties default: ticked, OK keeps the values for the next run (saved with
the settings as redact_prefs); unticked, they hold for this run only and the
saved defaults stay; Cancel puts back what the dialog opened with. The dialog
is laid out in two columns so it fits a laptop screen. Rows in every
language, their checksums in ATTRIBUTION.toml, parity
protect.redaction-properties shipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Refresh catalog hashes after merging main

---------

Co-authored-by: Osvaldo Duran <338149330+regendorf-ux@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:38:22 -04:00
6c22c78a72 Edit: crop an existing page image with its handles (#2002)
* Edit: crop an existing page image with its handles

ImageChange::Crop(rect) keeps the part of a page image inside rect (user space)
with a clipping path placed just inside the scope that holds the image (or a
new q … Q around its Do); a clipping path already at the scope's start, the
picture's earlier crop, is replaced, so a crop can grow back. The pixels are
never scaled, and a later move or resize wraps the scope, so the crop moves
with the picture. ImageEdit::Crop (undo label "Crop image"), image_edit
action crop. In the window, right-click ▸ Crop Image puts the selected
picture in crop mode: eight handles trim (the inside pans), the part that goes
is dimmed, Enter or Done applies, Esc or Cancel leaves; Remove Crop shows the
whole picture again. Rows in every language, their checksums in
ATTRIBUTION.toml, parity edit.image-crop shipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Image crop: Bulgarian hint without a character the monospace UI font lacks

JetBrains Mono has no 'ѝ', so primary_ui_fonts_cover_latin_and_cyrillic_catalogs
failed; the hint is reworded ("with its handles" -> "with the handles").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Refresh catalog hashes after merging main

---------

Co-authored-by: Osvaldo Duran <338149330+regendorf-ux@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:37:56 -04:00
9ed12cb6a8 Forms: simplified field notation written as real JavaScript, not AFMakeNumber(0) (#1995)
calculate_js wrote a field's simplified field notation as
"/** BVCALC expr EVCALC **/ event.value = AFMakeNumber(0);". PdfCraft reads the
comment, but a viewer that runs the script instead (Acrobat among them) computed
0. notation_js now writes Acrobat's own translation after the comment: each
field name becomes AFMakeNumber(this.getField("name").value), with \ escapes
in names honoured, numbers and operators kept.

Co-authored-by: Osvaldo Duran <338149330+regendorf-ux@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 02:37:30 -04:00
Valentin KleinandClaude Sonnet 5.5 77640a030f Draw: a released pen stroke stays on screen until the page raster has it (#1858) (#1987)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 02:37:18 -04:00
bb2b03f263 View: Single Page wheel turns to the next page at the edge of a zoomed-in page (#1924) (#1986)
* View: in Single Page view the wheel turns the page at the edge of a zoomed-in page (#1924)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* ROADMAP: log edge paging (#1924)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* View: no jump past the last/first page, a turned page starts at its edge (#1924)

step_page is a no-op at either end (it re-aligned the view via goto), the page-view
ScrollArea is not animated (its zero-length offset target overwrote the turn's jump), and
the rest of a gesture that turned the page no longer pans the new page.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:37:07 -04:00
Valentin KleinandClaude Sonnet 5.5 16829991fe Viewer: a press on a page makes it the current page (#1956) (#1985)
Page commands with no selection (Rotate, Delete) act on the current page. A page gone to
earlier stayed current while it showed whole, so the next press on the page beside it
rotated the wrong one.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 02:36:45 -04:00
75c9a9c27b Comments: font size for text boxes in Comment Properties (#1908) (#1984)
* Comments: font size for text boxes in Comment Properties (#1908)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Refresh catalog hashes after merging main

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:36:35 -04:00
d3571c4019 M2: keep the raw crop minimum in physical points (#1982)
Apply the minimum after page-unit scaling without inflating small raw crops. Retain the existing unit policy and nearly-zero fallback, with regression coverage for origins, rotations and reciprocal rounding.

Co-authored-by: sanyamsingh001 <314051937+sanyamsingh001@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:36:12 -04:00
5fffd4cacd Export to Word: place pictures on positioned pages as DrawingML anchors and bound the pictures (#1936) (#1981)
A VML rectangle with an image did not show the picture in Word. Pictures on
positioned pages are now wp:anchor drawings behind the text, sharing the
picture XML with inline pictures. At most 1000 pictures / 512 MiB are embedded;
empty images are skipped.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:35:50 -04:00
Valentin KleinandClaude Sonnet 5.5 76037a77ac View: show page size when hovering the bottom-left of the page view (#1855) (#1979)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 02:35:26 -04:00
bc5f6ca8ee M5: undo the last stroke in the signature Draw pad (#1963) (#1978)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:35:15 -04:00
a4b7054919 Issue #1910: Organize opens on the current page (#1975)
Every way into the page grid (toolbar, tool panel, page.organize, --organize,
Create multiple) goes through DocView::open_organize, which drops a selection
left from earlier that does not include the page being read and scrolls that
page's cell into view, computed from the row so virtualized rows need not be
laid out. Grid clicks navigate the document as Pages-panel clicks do, so the
viewer no longer resets the current page from its scroll position when the
grid closes.

Regression: crates/ui-egui/tests/editing.rs::organize_opens_on_the_current_page
(middle page, last page, reopening after a selection elsewhere).

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:34:52 -04:00
Valentin KleinandClaude Sonnet 5.5 b870ad32e9 CLI: run --script --report prints one JSON document for the run (#1891) (#1974)
Per step: number, tool, status (ok/error/skipped) and result or error.
Without the flag the output is unchanged; exit codes are unchanged.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 02:34:30 -04:00
EromoseleandEromosele 9c42c0c5fe fix(automation): no top-level oneOf in form_set_props input schema (#1961) (#1967)
Anthropic's tool API rejects a request when any tool's input_schema has
oneOf, anyOf or allOf at the top level, so attaching `pdfcraft-cli mcp`
to a Claude-based client made every prompt fail. The field/fields rule is
already enforced in the form_set_props handler and described in the
schema, so the oneOf is dropped.

tool_table_is_well_formed now checks that no tool has a top-level
oneOf/anyOf/allOf.

Co-authored-by: Eromosele <188490945+andreero0@users.noreply.github.com>
2026-10-15 02:34:20 -04:00
8e803b6290 Print: print as image (Advanced), with grayscale pictures (#1965)
* Print: print as image (Advanced), with grayscale pictures

print::Settings.raster (dpi 72-600, grayscale): Session::print_pdf renders each
distinct page used once the way the viewer shows it (comments left out when the
print leaves them out), composes it over white, grey by Rec. 601 luma when
grayscale is on, makes an image-only document of the pictures and imposes it on
the paper like the pages themselves. The Print dialog's new Advanced section
(folded unless on) offers it at 150, 300 or 600 dpi; doc_print takes as_image
and dpi. Rows in every language; parity print.print-as-image shipped,
print.grayscale shipped (the pictures are grey, not only the printer asked).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ATTRIBUTION.toml: the language files' checksums after the new rows

cargo xtask assets checks each catalog's sha256; the rows this branch adds
changed the files without updating it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Refresh catalog hashes after merging main

---------

Co-authored-by: Osvaldo Duran <338149330+regendorf-ux@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:34:09 -04:00
Shawn SkaggsandClaude Opus 5.5 57256f9a4c M6: auto-size text fields shrink and wrap typed text on screen, as on the page (#1964)
Auto-size fields (`0 Tf`, often inherited from the AcroForm /DA) fit their
value to the box in the page appearance, but the in-place editor and the
preview shown before the page re-renders ignored it: they used a fixed guess
from the box height, never shrank to the width and never wrapped multiline
text, so long values ran past the box and were clipped.

The fit now lives in one place, `appearance::auto_text_size_by` (re-exported
as `field_auto_text_size_by`). The page appearance measures with Helvetica;
the screen measures with the interface font it draws in, which is wider and
breaks lines differently, so the text stays inside the box on screen too. The
editor wraps at the same scaled width the size was fitted to. Explicit sizes
are unchanged and still clip, as in Acrobat.

Tests: forms auto_text_size_fits_single_and_multiline_text (incl. NaN/inf/
negative boxes and a NaN measurer); ui-egui
auto_size_fields_shrink_and_wrap_typed_text_to_fit_their_box (fails with the
old on-screen sizing restored).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 02:33:45 -04:00
Marco Arnulfo 03f232be5a M4: write each page label dictionary as an indirect object (#1763) (#1959) 2026-10-15 02:33:35 -04:00
e4657eb577 M2: bound ASCII85 decoding by the stream's output limit (#1796) (#1954)
The vendored hayro-syntax passed each filter an output limit, but
ASCII85 ignored it: every `z` expands one byte to four, so a Flate
stage at its limit could be grown another 4x past every budget.

ASCII85 now writes only what fits under the limit, stops with a
warning once it is reached (like RunLength, LZW and Flate), and caps
its starting capacity at the limit.

Test: decompression_bombs_stop_at_the_stream_limit gets a
[/FlateDecode /ASCII85Decode] bomb; it decoded 301,989,892 bytes
before and stops at MAX_DECODED_STREAM now.

Co-authored-by: skylar taylor-barrick <hi@skylar.cc>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:33:24 -04:00