chore: sync upstream (804 Commits) #5
No reviewers
Labels
No labels
herkunft/alpha-feedback
status
blockiert
status
zu-testen
typ
bug
typ
docs
typ
enhancement
typ
feature
typ
idea
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
iris/pdfcraft!5
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "chore/sync-upstream-20261022"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Upstream-Sync von
upstream/main(804 Commits). Fast-Forward.9a0ec645Merge pull request #3983 from aiongg/next022a970fIntegration: #2173's restored-paper test lets the dialog relabel its button before clicking Save as PDF8662b2feMerge r2-fix-ui: print test waits for its render; ui tests serialize GPU renderingb99c774fIntegration: serialize GPU rendering in the ui tests (batch render tests × lavapipe)e85fca66Integration: text field table labels in the batch's catalogues (upstream #3479 × r2 catalogues)3f3b9130Integration: the dead-read print test waits for the preview rasters before checking idle (#2108 × print preview)8293832aIntegration: Norwegian Bokmål page-gap labels (upstream #1836 × #3101)472717fcMerge upstream main88dad98ddinto next4bd08a75Integration: Albanian signature labels all translated (#3101 × r2 catalogues)bbdbca62Merge r2-fix-ui: tests the combined batch broke38daa0ecMerge r2-fix-engine: tests the combined batch broke47ac03b8Merge r2-fix-i18n: tests the combined batch broke9a2d2cfdIntegration: panel snapshots for the assembled batch (#3525 × #2607, #2731, #3828)46417784Integration: the live line-resize test follows lines across rotated pages (#3520 × #3781)8341a721Integration: mode-tab hand tests use the tabs the mode bar has (#2240 × #3010, #3022)Rewords 70 existing or new French entries to the terms French prepress users know: Recadrer for Crop, Supprimer for Discard in the optimizer ("Ignorer les signets" read as if bookmarks were kept), Mode de sortie for Output intent, Annotations for Markup, Interrogeable for searchable OCR output. 22 terms are attested in Adobe's public French help pages; 48 come from the reviewer's knowledge of the French interface. The catalog header says so.A Polish signature panel headed "Ważność podpisu jest NIEZNANA" and listed its details in English: the catalog had none of the engine's signature wordings (sign::pdf::WORDINGS, 57) or the changes after signing they name (kind::ALL, 10), and lacked 34 UI literals besides. All 101 are translated, and two tests keep them so: - polish_covers_ui_literals, every tl!() literal (as Japanese has); - polish_says_what_a_signature_says, the signature wordings and change kinds with their {} holes. Both fail on the previous catalog. ATTRIBUTION.toml carries the new hash. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>Adds crates/crypt/tests/qpdf_literals.rs (305 lines): three tests that hand our security handler /O, /U, /OE, /UE and /Perms values from outside implementations, then require our reader to authenticate them and report the intended role. Tests only - no product code, no new dependency; PDFs stay outside the repository and only quoted hex is committed. Why outsiders wrote these numbers: create() and open() share one derivation, so a wrong-but-self-consistent implementation passes every internal test. Two review rounds proved it by mutation - several key-derivation breaks stayed green in both suites because writer and reader drifted together. Quoting bytes we did not compute breaks the circle: open() recomputes /U (or the revision 6 hash chain) and compares it to what was stored, so a divergent derivation cannot authenticate. Six cases come from qpdf core 12.2.0 via pikepdf (R2/R3, R4 in both metadata modes, R6 in both). Revisions 2-4 are deterministic given /ID and passwords, so the generator reproduces their quoted bytes byte for byte (verified). Revision 6 salts are fresh on every run by design: these literals match the fixtures as captured, and re-running the generator will produce different - equally valid - revision 6 numbers, so compare R/V/Length structurally there and re-quote if you regenerate. One case is HAND-DERIVED, labelled as such in the source: an independent re-implementation of ISO Algorithms 2/3 at n = 5, cross-validated by having that same routine reproduce qpdf's n=16 /O byte for byte. It exists because no conforming producer emits R3 at 40 bits - qpdf writes 128 there and this crate's create() maps Rc4_40 to revision 2 - yet the derivation loop only runs for r >= 3, so without a crafted row its truncation is unobservable and mutating it changes behaviour that nothing catches. Measured effect, same battery, upstream --lib run separately each time: r6-hash-selector (%3 collapsed to %2) CAUGHT here; upstream stays green r6-sha-arms-swapped (SHA-384 <-> SHA-512) CAUGHT here; upstream stays green r6-selector-window (sums 17 bytes not 16) CAUGHT here; upstream stays green r4-key-chain-truncation (loop hashes whole digest) CAUGHT here, via the crafted row; upstream stays green. Pristine 3/3 pass, mutated 2 passed / 1 FAILED - verified twice. Still invisible, named rather than glossed: r6-round-floor (minimum rounds 64 -> 63) - the terminator does not separate the two floors on these inputs; /OE,/UE length loosening and the /Perms filler source, both unreached here. Corrections carried from review: qpdf's advice against a static /ID does not weaken these revision 6 vectors, because key_r6 never receives the file /ID at all; it matters only to the revision 2-4 paths, where we quote /ID literally so the numbers stay exact. And no ATTRIBUTION entry is added, matching crates/crypt/src/tests.rs:237 which already quotes qpdf vectors unattributed; cargo xtask assets stays ok. Gates on a clean tree: cargo test -p pdfcraft-crypt 28 passed / 0 failed (17 lib + the 8 properties from #2358, now merged, plus 3 oracle tests - one per concern - role authentication across seven dictionaries, metadata-flag key separation, /Perms decode); clippy --all-targets -D warnings clean; cargo fmt --check clean; xtask layers OK (35 crates). Integrity note: the literals in this commit were checked field by field against the PDFs on disk - 27 of 27 fields quoted verbatim, no transcription drift. A regression test for that check belongs in the generator's output comparison rather than here, since it needs pikepdf.Adds crates/cos/tests/proptests.rs (464 lines) - three properties over generated catalogs (edit sequence vs a plain ordered model; full save in both output styles; incremental save over bases built BOTH ways) - and a self-credit entry in contributors/people.toml committed under the author's own identity. No product code changes; total +466 lines across two files: people.toml +2; proptests.rs +464. Two design decisions that came out of adversarial review, each measured rather than asserted: * catalog keys insert in a scrambled order ([5,0,7,2,6,1,4,3] filtered to the drawn keys), because with ascending insertion "preserves key order" and "sorted by key" are the same observable. * incremental expectations derive from the values written, not from reading the writer's own output back, so a consistently-wrong implementation cannot pass by agreeing with itself. Measured against one-line breaks in crates/cos/src/writer.rs, each reverted and byte-compared afterwards (verdicts from cargo exit codes; "pre-existing" = lib 48 + writer 10 + encryption 8 + shared_streams 3 + bounded_writer 4, i.e. every target that existed before this PR): sort every dictionary by key CAUGHT (props 1+2) pre-existing suite FULLY GREEN reverse dictionary emission CAUGHT (all three props) pre-existing suite FULLY GREEN move /Type to the end of every dict CAUGHT (prop 1) pre-existing suite FULLY GREEN - caught only after catalog_keys() was added to pin the full key sequence; without it this break was green everywhere classic-table incremental startxref +1 CAUGHT (prop 2 alone; props 1+3 green), failure message names "classic-table", confirming the incremental table path executes drop null-valued dictionary entries CAUGHT (all three) at the pre-fix commit b51381c this same break left property 2 green - the self-reference it removed Known limits, stated not hidden: incremental saves are asserted to add bytes and leave the original prefix untouched - minimality is NOT claimed; catalog key order pins one fixed scramble rather than drawn permutations, so 25.4% of cases draw too few keys for order to be meaningful; nested dictionary key order is deliberately not pinned (canon sorts inside values); /Perms and permissions semantics are out of scope here. Gates at this tip (upstream24f93e6, drift 0): cargo test -p pdfcraft-cos 85 passed / 0 failed / 2 ignored; cargo fmt -p pdfcraft-cos --check clean; clippy exit 0 for this crate with -D warnings -A clippy::chunks_exact_to_as_chunks (that lint fires only in upstream's own crates/oracle/src/pixels.rs:189 fromf988cf8, tracked by #2342/#2324; flags after `--` forward into every crate in the build graph, and crates/cos itself emits zero warnings under a forced recompile). cargo xtask layers OK: 37 crates. Scope: part of #1187 (items 1-3 partly; the reference-following comparison, undo state machine, password property and damaged-file splices remain open). The encryption-side properties are PR #2358 by the same author; earlier drafts of this text mis-cited #2014, which is certificate encryption and still open.The Measure panel's drawing scale had its page side fixed in PDF points, so "1 = 50 mm" was stored as 1 pt = 50 mm and every length came out 72 / 25.4 = 2.835 times too big (a 1:50 plan needed 50 x 25.4 / 72 = 17.64). The page side now carries a unit (pt, in, mm, cm). It defaults to the real-world unit, and to the display unit when that is not one of the four. Scale::ratio converts it to points before building the /X, /D and /A number formats (ISO 32000-2 12.9) and writes /R as entered ("1 mm = 50 mm"). measure_scale takes the same ratio through page_distance and page_unit. Scales read from files, Scale::calibrate and units_per_point are unchanged. Fixes #2752Found while testing crates/js/src/formcalc.rs:2348-2352 ("time2num" | "isotime2num") Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>d8b270588e9292075bd8b27058226ed13c88d8b27058309834ffefd8b27058095e1d36bdFound while testing crates/js/src/formcalc.rs:2240-2276 ("format" builtin, number branch) Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>A BricsCAD plan page (3370 × 1524 pt, 251,000 single-segment strokes and 310 one-triangle type 4 shadings painted with `sh` at 50% alpha) took 2 s per 1024 px tile at every zoom, and 7.9 s for a fit-to-width render; on a busy machine the 20 s watchdog gave the page up ("page 57 took longer than 20 s and was skipped"). A 16 px tile of the same page took 65 ms, so neither parsing nor the strokes were the cost: `sh` fills the whole canvas with the shading, and `set_paint` sampled the mesh texture over the whole path, one hash lookup and one texel per canvas pixel, 310 times per tile. hayro-interpret patch (35): `EncodedShadingPattern::extent`, the device-space pixels a mesh's triangles touched when it was encoded (`None` for shadings that paint everywhere, and for a mesh with a `/Background`). hayro patch (19): `set_paint` returns `PaintSetup::Nothing` for a mesh with no triangle on the canvas (an empty extent, tested before it is intersected: `Rect::ZERO` sits at the canvas origin), which the fill, stroke, rect and stencil callers skip, and otherwise intersects the texture with the extent and pads instead of repeating it. Measured with `page_timing` on the page: fit 7864 → 880 ms, a 1024 px tile 2030 → 85 ms at 100% and 2030 → 70 ms at 400% (the 16 px tile's 65 ms is the page's re-interpretation, paid per tile), with byte-identical output hashes. The three `mesh_extent_tests` pin what the texture must and must not cover. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>Czech had every `tl!` literal but lacked ~220 keys the other catalogs carry: the sentences the engine words (signature details, accessibility findings and report, PDF/A problems, pages to print, history labels), the date picker's weekday heads, the certificate viewer's fields and shared buttons. All are now translated (2,674 entries). "Tag" is "tag" throughout, as the catalog's own glossary says: the Tags panel said "značky", which also names redaction marks, and "{n} tags" read "1 značek". Two new tests pin the engine wordings (with their holes), weekdays and certificate fields; both fail on the previous catalog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>A field's data picture (<bind><picture>, such as date{YYYYMMDD}) is how its value is written in the XFA data, but PdfCraft always wrote dates there as ISO (2026-10-18) and read them back as ISO, so a form that keeps its dates as 20261018 got a different format from PdfCraft than from other viewers, and data those viewers wrote was shown as stored. The parser keeps <bind><picture> as Field::data_picture. A typed date is still read in the field's own pattern and turned into ISO; it is then written in the data picture when the field has one. Reading goes the other way, on layout and when values are taken from the data of a form laid out before. A value that doesn't match its data picture is read as ISO, so data written by earlier versions still reads. The date-letters rule for XFA picture clauses moves to one helper (date_letters) that the layout's date_pattern now uses too.The template field behind a SOM path is now found by its number, as the form numbers same-named siblings and repeated instances; the live form's value refresh uses the same lookup. A data picture without a year, month and day (date{YYYYMM}) keeps ISO dates in the data, since it can't hold the date the field shows.A number written with a doubled sign was read by the renderer's lexer as two numbers. `--280` set the sign, found no digit before the second `-`, and returned 0, leaving `-280` to be read next: a `cm` carrying it got seven operands, kept the last six, and the matrix `0 0 1 0 -280 10` hid everything drawn under it. The page rendered blank while text extraction still reported the text, because the glyphs were there and only the matrix was wrong. Measured on the report's own file, at 72 dpi, counting pixels darker than mid-grey: double_minus.pdf before: 0 dark px (blank) after: 581, leftmost at x=121 control.pdf (-280) 581, leftmost at x=121 -- unchanged so the two files now render identically, which is what the report asks for and what MuPDF does. One extra sign is absorbed and the value is negative if either sign was a minus, matching the COS lexer since #3299 (`--280` and `+-280` are both -280). `Number::skip` already consumed the whole token, so read and skip had disagreed about where the number ended. Fixes #3565. #3299 fixed the same input in the COS lexer only.The JHOVE PDF-HUL-157 files (tiff2pdf output) end their first trailer with `/Prev 297298 0 R`: the offset written as a reference. The chain reader took /Prev as an integer only, so the chain stopped after the first section without a word, and every object of the earlier sections (here pages 2-4 and their images) read as null. The document showed one page where Poppler and MuPDF show four, and a full save wrote the missing kids as null, so pages 2-4 were unreadable afterwards ("cycle in page tree", 1x1 renders). A reference-valued /Prev is now read as the offset it names, with a repair-log note; any other non-offset value is noted and ends the chain as before. Regression test: document::tests::a_prev_written_as_a_reference_still_reaches_the_earlier_sections. Verified on JA3-1998-PDF.pdf: four pages read, and after a Subject edit and a full save Poppler renders all four pages without a warning (before: "Kid object (page 2) is not an indirect reference (null)"). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011K1peyrh5MdZ6ttHkVq8GhAny value in a PDF may be written as an indirect reference, the tagged flag included: `/MarkInfo << /Marked 9 0 R >>` with object 9 holding `true` is valid, and veraPDF's corpus has pass files that write it that way. Both readers of the flag tested the reference itself, so such a document was reported as untagged: doc_info "tagged": false Accessibility Checker "The document isn't marked as tagged (MarkInfo Marked)" Both now resolve the value first. On a pair of otherwise identical tagged pages, one with `/Marked true` and one with `/Marked 8 0 R`: before direct: tagged true indirect: tagged false after direct: tagged true indirect: tagged true and the checker reports the finding for neither. A document with no flag at all still fails the rule, which the test asserts alongside, so this does not turn the rule into one that always passes. Fixes #3671.Document::resolve follows one reference, and an indirect object's value may itself be a reference (ISO 32000-2 7.3.10): `/Marked 8 0 R` with object 8 holding `9 0 R` holding `true` is a tagged document. The rule was left holding a reference and reported it untagged, while the render inspector -- which follows chains through lopdf's dereference -- read the same file as tagged. The point of this branch is that the two readers agree, so the chain is followed here too. Bounded at 32 references and cycle-safe: a damaged file can point a reference at itself, and the test covers that case as well as the two-hop chain. Without the fix the chain case reports left: Failed, right: Passed Reported by CodeRabbit on #3686.Found while testing crates/xfdf/src/lib.rs:313-317 (xfdf_annots) and :~1010 (annot_from_xml, `unwrap_or("print")`) Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>Shortcut labels follow the OS egui reports ("Ctrl+B" against "⌘B"), so the snapshots were only host-independent because kittest happens to default to Nix. Pin it explicitly. With the OS set to Mac the test fails on the Bookmarks panel's "New bookmark (⌘B)" button. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>Once drawn, a line or arrow could be moved but not changed: selected, it showed a frame and no handles, so a wrong length or angle meant drawing it again. A selected line, connected lines, polygon or cloud now has a handle on each end or corner instead of the frame; dragging one moves that point (Shift holds it to 45° from the point before), the new shape is drawn while dragging, and the release is one undo step. The drawing tools keep drawing from an end (a new line starts there); the Select and Fill & Sign tools pick the handles up. pdfcraft_annot::set_points sets /L or /Vertices, lets /Rect follow with room for the width and arrowheads (the rule a new comment is drawn with) and draws the appearance anew; measurements keep the points they measured, locked comments their shape. Edit::ReshapeAnnotation ("Reshape comment"), comment_edit `points`; the comment list (Summary, DocInfo, comment_list) carries a line's ends and a polygon's corners. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>date{DD MMM YYYY} and date{EEE …} have parts formatted_value can't show yet, so it gives the raw value instead of text like 20 mmm 2026.2026-1-2 isn't an ISO date and stands as it is. A test also pins that parentheses hold a picture's locale (date(en_US){YYYYMMDD} formats) and that a clause without braces has no pattern.A TextSession opens a paragraph once: it keeps the document as it was and the page's lines then. Each draft is laid out on a copy of that document, so no draft sees another's output, and the commit writes into the document it was opened on, refusing ("the paragraph changed while it was being edited") when that document has changed since. text_edit_layout and rewrite_block now open a session and lay out or commit once, so there is one path and their output is unchanged. No caches yet: those come next, each under the same equivalence test. First step of #2512. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>Create a new digital ID rejects a password shorter than sign::pkcs12::MIN_PASSWORD_CHARS (8) with "The password must have at least 8 characters.", which the dialog shows through tl!. Italian only had the older 6-character wording, so the message appeared in English. Czech, Lithuanian and Vietnamese already carry the current key. The other 67 keys some catalogs have and Italian lacks are not looked up by the code: shortcut tooltips the code now fills from {key} rows, older wordings, plain-string plurals and generic labels only ar/lt/vi carry. The catalog's SHA-256 in ATTRIBUTION.toml is updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>* M6: Prepare a form ▸ Text field table: rows and columns of text fields as one edit Drag an area with the Text field table tool and set it up in a movable window: rows, columns, row and column spacing, the first row number, column widths (draggable dividers, arrow keys once focused) and a name pattern per column, with the table previewed on the page. Add fields adds every cell as one undoable edit ("Add text field table"). - forms: `table` lays the table out in view space and names its cells. It refuses the whole table for a cell under MIN_FIELD_SIDE (now one constant) or a name with a period, used twice or already taken, and free_base picks the first free TableK for default names. - engine: `field_table::edit` maps the cells onto the page, /Rotate and /UserUnit included, and checks their size again there. - automation: `form_add_field_table`, with an end-to-end test. - ui-egui: the window belongs to one page of one document and closes when that stops being the one prepared; Enter commits a number instead of adding the table; dividers between many columns no longer panic in f64::clamp. Strings in all 22 catalogs, and the Spanish engine-wording tests cover the table's mistakes. - parity: form.multiple-copies is partial; copying an existing field into a grid is still missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M6: text field table: refuse corners past the page's number range, run it by its command Review follow-up on #3479. - engine: a corner finite as an f64 but past f32's range came back from the page's f32 mapping infinite. With one such corner a side was infinite rather than NaN, so the cell passed and add_field refused it only once the edit was applied ("the field is too small"). The edit now refuses any corner that isn't finite on the page with the table's own reason, before anything is added. - automation: form.add.text_table runs form_add_field_table through command_run, as comment.stamp runs stamp_custom. - ui-egui: doc comments for the window's three remaining helpers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M6: text field table: close on document edits, lay out once per change, keep Escape for fields Review follow-up on #3479 (benandez). - The window belongs to the document as it was when it opened: it closes when the edit generation moves (a page rotated, moved or deleted, an edit undone), so a table set up for one page never lands on another or off it. - The table is laid out and checked once per change, not several times a frame: the window and the page preview share one `Laid` (cells and the edit), and the preview draws only the cells in view, naming only those with room for a name. - Escape in a field of the window leaves the field and keeps the setup; a second Escape closes the window. - A table placed with a click is moved in from the page's edges, so all of it is on the page. - `free_base` takes one pass over the names; `{seq}` is documented as counting from the first row number; `form_add_field_table` takes its names from the batch instead of laying the table out again; the unreachable `Dialog::FieldTable` arm says why it is there. Each new test fails with its fix reverted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z * M6: text field table: drawn with its panel closed, the window opens Prepare a form instead of closing With the tool picked and the Prepare a form panel closed, the form is still prepared and a table can be drawn. Drawing sends the tool back to Select, though, so nothing was prepared on the next frame, and the table window, which lasts only while the form is prepared, closed itself at once: the table couldn't be set up. Opening the window now opens the Prepare a form panel it belongs to. Closing the panel while the window is open still closes the window. Found by CodeRabbit, which saw the preview stop drawing; the window closing first was the larger effect. prepare.rs a_table_drawn_with_the_panel_closed_opens_it_again fails without the fix ("the window stays open": (None, false)). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z * Drop the second generation binding the main merge left in document_area --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: aiongg <aiongtaigi@gmail.com>English fill (the "Untranslated" section of each catalogue), for keys the coverage tests only need present: - ca, fi, ro, pt-br: keys other catalogues gained (print, thumbnails, layers, signature checks, Reshape comment, Remove {standard} identification) - sq: UI literals and the visible-signature labels - hi, ko, id: calculation-order, rich-text and print/thumbnail literals - pl: signature-check wordings AI-assisted translation (noted in ATTRIBUTION.toml): - cs: the four new signature-check wordings and Remove {standard} identification, since Czech asserts every entry is translated Lao's file.word_count command id said its own id; it reads as the plain "Word count…" translation.88dad98ddinto next 472717fcd7