Commit Graph

1189 Commits

Author SHA1 Message Date
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 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
Aleksander Helgaker 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
mtlcl410 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
Michael Sharon 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-cell 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
jonathanwxh-cell 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
sanyamsingh001 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 Helgaker 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
mtlcl410 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
mtlcl410 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
mtlcl410 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
mtlcl410 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
Lucas Sampaio Dias 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
CindyGest 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 Saidahmed 8c4e633a6e make xfa som index lookup linear (#2047)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:40:48 -04:00
cyfung1031 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
Leo Camus 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
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
mtlcl410 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
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
regendorf-ux 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
regendorf-ux 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
regendorf-ux 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 Klein 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
Valentin Klein 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 Klein 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
Valentin Klein 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
sanyamsingh001 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
Valentin Klein 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 Klein 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
Valentin Klein 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
Aleksander Helgaker 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
Eromosele 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
regendorf-ux 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 Skaggs 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
Ridwan 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
sanyamsingh001 8bd200f643 M3: retain native open replies behind dialogs and pickers (#1953)
Check admission before each prepared result so a modal or queued picker keeps its document recipient. Wake retained replies once after picker consumption or worker abandonment.

Preserve merged #1914 continuation/startup behavior and the three worker regressions from upstream 3f044b62 (jcl-bit and its recorded coauthors). Keep the inherited printing/dependency changes intact; add only the remaining ownership barrier, wakes and focused regressions.

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:33:01 -04:00
Zeyu Li f52c92882d M9: signature labels follow the interface language, and one the font can't draw is refused, never written as ? (#1951)
* M9: signature labels follow the interface language, and one the font can't draw is refused, never written as ?

The labels of a visible signature ("Digitally signed by", "Reason: ", ...) were written in WinAnsi unchecked, and every caller passed the English defaults. Now:

- sign: a drawn label is checked as the details are, and one Helvetica can't draw is refused (SignError::Unsupported) naming the label and the character. AppearanceLabels::fields and ::first_undrawable say which labels a caller can use.
- ui-egui: signing stamps the labels in the interface language when Helvetica can draw all of them, otherwise all in English (never a mix) with a note under the preview. The preview uses the same labels, the stamp's line layout and the signing time.
- automation: sign_document takes optional labels and appearance; a label the font can't draw, an unknown key or one without a visible signature is InvalidArgs.
- Catalogs: the five labels and the note in every language; refresh hashes.

Follows up the note on #1112.

* Refresh catalog hashes after merging main

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:32:38 -04:00
ysrdevs e8d1980ded XFA: flatten a form into a plain PDF, or convert it to a static form (#1948)
Flattening the fields of a laid-out XFA form used to leave the XFA in
the file: Adobe's viewers then laid the form out again from the
template over the baked pages, so the "flattened" PDF was still a live
form there and a plain one everywhere else.

pdfcraft_xfa::strip takes the XFA out of a document: the packets
(template, data and all), /NeedsRendering on the catalog and the
AcroForm, the layout mark, the overrides, and the private keys on
every field down the tree. The engine calls it after Flatten form
fields on a document with XFA packets, so the result is a plain PDF
every viewer shows the same, and for the new Edit::XfaToStatic
("Convert to a static form"; tool xfa_to_static), which keeps the
laid-out pages and the fields with their values as an ordinary
AcroForm that no viewer lays out again and whose scripts no longer
run. Both undoable: the engine's post-edit refresh re-reads the
layout mark to drop or restore its XFA state and the cached template.

What the packets held beyond the fields is reported (packets by
name, data no field showed, buttons left inert, the layout's own
warnings, Reader usage rights) as document warnings, and a dynamic
form that was never laid out is refused rather than left with
nothing. Applying redactions shares the same strip. Closes the last
planned XFA parity entry (form.xfa-flatten shipped).
2026-10-15 02:32:04 -04:00
Alex 9f0ed4ba86 feat(organize): Insert pages from file brings the file's bookmarks (#1947)
Inserting an annex into a guide dropped the annex's bookmarks: the pages
came in without them, while Combine Files keeps every file's bookmarks
under one named after the file.

Insert pages from file now does the same: the inserted file's bookmarks,
destinations mapped to the copied pages (positions kept, named ones
resolved), go under one new bookmark titled with the file's name at its
first inserted page, collapsed. That bookmark is placed among the
document's top-level bookmarks before the first one pointing after the
inserted pages. A file without bookmarks adds none.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: OpenAI Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:31:53 -04:00
mtlcl410 e07759d657 fix(sign): a signed revision of a password-protected file opens with the document's key (#1808) (#1945)
To classify what later revisions changed, the signed bytes were reopened
with Document::open, without a password, so for a file opened with a
nonempty user password the earlier revision was unreadable and the earlier
signature became Invalid as soon as it was counter-signed. Document::
open_with_same_credentials opens such bytes with the current document's own
security handler when their /Encrypt and /ID are the same (no password is
needed again or kept), and the signature validator uses it.


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:31:30 -04:00
mtlcl410 ff6cabf047 fix(control): a deadline and a cap for connections that have not authenticated (#1806) (#1939)
The loopback listener spawned a thread per accepted connection and read its
first line with no deadline and no limit on how many could wait, so any
local process could hold threads for ever before knowing the token. Each
connection now has an absolute deadline to authenticate (10 s from
acceptance, re-armed per read so a byte at a time cannot extend it), at most
8 may wait at once (the rest are refused on the accepting thread with an
error and closed), writes time out, and a waiting place is given back on
authentication and on every exit.


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

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-15 02:30:52 -04:00
mtlcl410 6b7abc61d1 fix(edit): flattening keeps a widget it cannot draw and an undrawn reply (#1794) (#1934)
A widget with no appearance to draw (none stored, or none for its selected
state) was removed from the page and the field tree with nothing in its
place, so the field's value was lost; a reply kept because it could not be
drawn was then deleted anyway as a reply of the flattened comment. Both now
stay as they are. A widget whose appearance draws nothing is still removed,
as is a reply note's empty appearance with its comment.


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

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-15 02:30:42 -04:00
mlkonrad 3f25d55a96 M11: measure every use of an image drawn through deeply nested forms (#1811) (#1933)
The effective-resolution walk skipped any form XObject already visited
once when it was nested more than four deep, with one seen-set for the
whole document. A later, larger use of the same form was never measured,
so Reduce File Size and Optimize PDF resampled the image for its smallest
use: a 1200 px image drawn 1 in and 8 in wide through six nested forms
came out 150 px wide, about 19 ppi on the large copy.

- Cycles are detected on the current path of forms, not document-wide.
- A form with its own resources drawn again at the same scale (only
  moved) is skipped, as that can't change any image's resolution; forms
  without resources draw their caller's images and are always walked.
- Repeat walks are capped at 100,000. If the cap or the 12-level depth
  limit leaves a use unmeasured, every image gets INFINITY: recompressed
  as the settings say, never resampled.

Fixes #1811

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:30:30 -04:00
regendorf-ux 9caddc6e9b Compare files: side by side, scrolled and zoomed together (#1927)
* Compare files: side by side, scrolled and zoomed together

After Compare files, when the old version is open in a tab and the window is
at least 1000 px wide, the document area splits: the old file on the left, the
new on the right, each a page view with its own shading (deleted and replaced
on the old, inserted and replaced on the new, visual differences on both). The
half under the pointer leads; the other takes its zoom, fit, layout and
rotation, and its scroll offset on the next frame. A change clicked in the
panel jumps both halves; Side by side / New file only toggle the mode; Clear
ends it. The old file keeps rendering while shown. Rows in every language,
parity misc.compare-side-by-side shipped.

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:30:08 -04:00
Sir Clangington III. c948d09c6a M2: bound page-tree traversal memory for repeated children (#1926)
Co-authored-by: LabDennis <341377186+LabDennis@users.noreply.github.com>
2026-10-15 02:29:46 -04:00