Session::refresh_with_indexes fills title, author and the other fields from XMP as build_document does; the test applies an edit and an undo and checks they stay.
crates/engine/src/lib.rs: union of the added:: re-exports (paste_place/paste_rect from #4230, CHAR_SPACING_LIMIT).
crates/ui-egui/src/content_ui.rs: editor keeps #4178's anchored width and horizontal_align and #4266's spacing layouter; spaced_job takes the alignment so the custom layouter keeps it.
crates/ui-egui/tests/it/content.rs: kept both tests.
crates/js/src/lib.rs: module doc app/util list has execMenuItem and scand; install() doc comment from main.
crates/js/src/tests.rs, crates/engine/src/tests.rs: both sides' appended tests kept.
parity/acrobat-features.toml: js notes: execMenuItem wording plus scand wording.
ROADMAP.md, docs/roadmap.md: kept both sides' status and progress-log lines; did not restore the older M14 status line that the branch had already trimmed.
crates/forms/src/tests.rs: append conflict, kept both tests.
parity/acrobat-features.toml: form.fill-text-field evidence is the union; notes keep HEAD's text plus the comb-cells sentence.
Conflicts:
- parity/acrobat-features.toml: comment.note: kept main's icon notes text, added the NoRotate sentence and the PR's three evidence entries.
- vendor/README.md: kept main's rows; added the PR's hayro-interpret patch as (53) with its test (53).
parity/acrobat-features.toml: form.fill-text-field evidence is the union of both sides; notes keep both sides' sentences (DoNotScroll clause and the Password sentence).
The on-screen drop-down button for combo boxes drawn display-only, the
narrowed text area beside it, and the Print as Image copy without it
(with_printed_field_appearances, printed_display_bytes) are removed.
Open PR #3567 draws the arrow for every combo box in the page view.
Check boxes, radio buttons, push buttons and comb lines are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
A form that leaves its appearances to the viewer had its text and choice
fields drawn for display (#3475), but not its buttons. Now, also for
display only, without changing the file:
- Check boxes and radio buttons with no normal appearance for their
state show their /MK background and border (Table 192), and when on,
the /MK /CA caption as a standard-14 ZapfDingbats character
(12.7.5.2.3), 4 or l by default, in the /DA colour, auto-sized for
0 Tf.
- Push buttons with none show their /MK background, their /BS border
(beveled per Table 168) and their centred caption.
- Comb fields with a border colour draw the MaxLen - 1 lines between
their positions (Table 228), up to 500 cells. This also applies to the
appearances PdfCraft saves for comb fields.
- A combo box drawn for display shows a drop-down button on screen, with
its text kept clear of it. Saved appearances and Print as Image leave
it out (with_printed_field_appearances, printed_display_bytes).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
Comments that arrive without /AP are drawn from their dictionaries by
appearance::build, which lost three entries:
- /Border [hr vr w [dash]] (Table 166): the dash array made the whole
array unreadable, so the width fell back to 1 and the border was solid.
The width is now element 2 whatever follows, and without /BS the
/Border dash array is drawn. /BS, when present, wins for both (Table
166: "the Border entry is ignored"), so a /BS without /W means 1.
- Dash arrays are drawn only when finite, non-negative, not all zero
(§8.4.3.6) and at most 64 long; anything else draws solid.
- Ink applies its /BS dash pattern (Table 168, Table 182), like the
other stroked shapes.
- A caret with /Sy /P (Table 183) shows a ¶ (Helvetica, WinAnsi 0xB6) to
its right in its colour, inside /Rect: in the room /RD leaves, or by
narrowing the caret. /Sy /None draws as before.
The Helvetica font resource, written out three times, is one helper.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
A rubber stamp from another program with no normal appearance was not
drawn at all. The display copy now gives it one:
- the fourteen standard names of Table 184 draw that stamp with
PdfCraft's stamp drawing (eight from the palette, six with labels of
their own), fitted to /Rect, in /C or the stamp's colour;
- no /Name draws Draft, the table's default, unless /IT says the stamp
is an image or a snapshot;
- any other name (names match byte for byte, §7.3.5) draws a thin grey
frame with both diagonals, so the stamp stays visible and selectable.
The drawing is display-only, like the other comments without an
appearance: Save writes the file as it was, and build() still declines
these stamps, so restyling and flattening never write it into the file.
A stamp with its own /AP /N is never touched. stamp() now takes the
label and the sign-here flag instead of a StampKind.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
Review: copying an added JPEG 2000 image failed even within its own
document, because the exporter can't write JPEG 2000 yet. Add Image stores
such a picture as the file it was given, so that file is the copy: it
pastes into the same document as the same object and into another as one
embedded image. The notes now also say that a CMYK picture stored
losslessly turns to RGB in another document.
Co-Authored-By: Opus 5.5 medium ultracode <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEgu9NcTcPqKovsLyfPxK
Edit::PasteContent pastes an added text box or image copied in the same
document or another, as it was (a text box's style; an image's picture,
turns, flips and crop), as one undo step. A pasted image draws its picture
from the copied object while that is still the picture in the target, else
from an added image there that already holds it (an earlier paste from
another document), else the file is embedded. Document::added_image_file
gives a copied image's picture as a file and honours the copy permission.
pdfcraft_edit::added::paste_rect / paste_place place a paste where the item
was, 18 pt down and to the right past each copy of it on the page (an image
with the same box and transforms counts whichever object holds its
picture), or at a point, kept on the page.
The content_copy tool drives it headlessly, across pages and documents.
The desktop keys and menus come in follow-up PRs.
Co-Authored-By: Opus 5.5 medium ultracode <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEgu9NcTcPqKovsLyfPxK
A content-only edit re-reads Document::added() and marks() only when the
edit itself is one that changes them. Edit::Batch has Scope::Content when
all its parts do, but content_refresh_lists never looked inside it, so a
batch of AddText/DeleteContent or AddWatermark edits left both lists stale
until the next full refresh or undo. It now answers for its parts.
Co-Authored-By: Opus 5.5 medium ultracode <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEgu9NcTcPqKovsLyfPxK
* M9: visible signatures read upright on rotated pages
A visible digital signature placed on a page with /Rotate was drawn in
user space, so its graphic and details turned with the page: on scans
stored landscape with /Rotate 270 the signature came out sideways.
Stamps (#1398) and Fill & Sign marks (#2392) were already corrected;
signatures were not.
A new signature widget on a turned page gets /MK /R equal to the page
rotation (inherited rotation included, via pdfcraft_model::pages), and
its appearance is laid out in the box as displayed (sides swapped for a
quarter turn) with a /Matrix from pdfcraft_model::view_matrix_for that
maps it exactly onto /Rect. Signing an existing field follows that
field's own /MK /R, so a field without one is drawn as before. Pages
that are not turned produce the same bytes as before.
Tests: sign crate (layout equals the upright box's, /MK /R, /Matrix,
inherited /Rotate, existing fields with and without /MK /R, malformed
/MK /R) and an engine render test on 0/90/180/270 pages with the
quadrant image. Both fail with the fix reverted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Keep one evidence/notes pair in the sign.digital parity entry
---------
Co-authored-by: vjeko2404 <vjekoslav@outlook.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M5: stamp_custom places a picture fitted inside a rect
stamp_custom only centred a PDF page or image at its natural size, at
most 200 pt, so a vector diagram could not be placed at a text column's
width. It now takes rect [x0, y0, x1, y1] (displayed-page points, like
at) as an alternative to at: the picture is scaled to fit inside the
box, keeping its shape, and centred in it. A PDF page stays a form
XObject, so it remains vector.
- Engine: Edit::AddCustomStamp fits the picture inside a non-point rect
instead of stretching it (fit_picture). A box already of the
picture's shape is kept exactly, so signatures and other callers that
size the box themselves are unchanged. The displayed sides are
swapped on quarter-turned pages, as for the natural size.
- Automation: rect is validated (four finite numbers, each side
at least 1 pt and at most 14400 pt, the largest PDF page; not clipped
to the page, like at); passing both at and rect,
or neither, is refused. path still goes through the read root.
- Tool schema and description, parity note and evidence.
Fixes#4077
* M14: update ctutils to 0.4.5 for RUSTSEC-2026-0385
The dependency-advisory gate rejects ctutils 0.4.2 (non-constant-time
mask generation and u128 comparisons, fixed in 0.4.4). It comes in
through crypto-bigint for the signing crates. Only its version and
checksum change in Cargo.lock.
(cherry picked from commit 8a687ee2c6)
Save as PDF/A removes the encryption (PDF/A forbids it), but it was gated
only by the modify permission, so a document opened with the user password
whose owner withheld printing or copying could be saved unencrypted with
every restriction gone. It now needs the same rights as Protect and Remove
security, also inside a batch.
Fixes#3921
Rewriting a paragraph in one style split its text at white space and
joined the words with one space, so a run of spaces, an indent or a
trailing space typed in the editor was saved as one space or none. The
text is now kept as typed (other white space becomes a space, and CR or
CRLF become line breaks), and the wrapper keeps each run of spaces,
dropping only the spaces where a row wraps.
A justified (stretched) row still drops its indent and the spaces after
its last word, as before, so word spacing reaches only the spaces
between its words; keeping an indent on a justified row is a possible
follow-up. Centred and right-aligned rows, and the editor's draft box,
are measured to the last word, and a paragraph at 0 Tz is not justified.
Fixes#4199Fixes#3797
Review on #3943. A black-box test of Acrobat (synthetic form, Save As and
Ctrl+S, every decoded object searched) showed how it saves a Password
field: the typed value is nowhere in the file; a field with /DV is saved
holding /DV, one without is left empty; the asterisks are drawn from the
saved value (5 for a 5-letter default, not 13 for the typed password);
/DV and /AA are kept.
clear_password_values now does the same: it restores the (inherited)
/DV as the value, or leaves the field empty, and redraws the widgets, so
the appearance no longer shows how long the typed password was. A field
already at its default no longer counts as holding a password, so such
a form needs no cleaned copy. The parity note for form.fill-text says so
and lists the two limits Acrobat shares (a script copying the password
into an ordinary field, and an earlier revision kept by an incremental
save).
Mutants, each failing a test: no /DV restore, no redraw, the default
ignored in password_fields, emptiness ignored (an empty string in an
array). Dropping the empty-default filter was equivalent, so the filter
is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CodeRabbit on #3943: sign, sign_with_timestamp, timestamp_document and dss::embed
each write a new incremental file from editor.cos, outside the writable() boundary
saves use, so a typed Password value was signed into the file. Each now writes from
the writable() copy. The new test signs a form with a typed value and checks the
signed file holds the masked appearance but not the value; the three TSA/DSS paths
need a timestamp server or validation data, and use the same one-line change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CodeRabbit on #3943: Extract Pages copies the fields of the pages it takes, and
write_new (Combine, Extract, Split, Create) wrote that copy without the writable()
boundary saves use, so the new file kept a typed Password value. write_unchecked, which
write_new and Split's size measuring both go through, now writes the sanitized copy. The
other write_full/write_incremental calls in the engine build in-memory bytes (display,
previews, the working copy, print rasters), not files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A text field with the Password flag kept its typed value in /V, so Save, a full
rewrite, Reduce File Size and the recovery snapshots all wrote it in plain text.
ISO 32000-2 §12.7.5.3, Table 231 (note to the Password flag): PDF processors
should never store the value in the PDF file. Each of those paths now writes a
copy without the values; the open document keeps them in memory, and the masked
appearance is kept.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>