Observed in Acrobat Pro on the public ISCIII form: the once-only page keeps
its place when printing (7 pages with nothing typed) but is printed blank.
PdfCraft draws its content, and has no relevant attribute or print-off
layer yet; the note says so.
The headless counterpart of the Print dialog's Printer default: duplex
"printer" sends no two-sided setting, so the printer's own default applies.
The default stays "off", so existing scripts behave as before. Cut and stack
with "printer" sends single-sided sheets, as the dialog does, and still
refuses an explicit edge.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Print dialog always sent its Two-sided choice, Off unless changed, so a
two-sided default set in the printer's Properties never applied. Acrobat has no
default of its own here and follows the printer's. Two-sided now starts at
Printer default, which sends no duplex setting (craft-print's Job::duplex =
None). Off and the two edges still override it. Cut and stack still sends Off,
since it needs single-sided sheets.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A page area with <occur min="1" max="1"> that the body never reached was
never laid out, so the public ISCIII form lost the page that holds its
finishing instructions and the TERMINAR MEMORIA button once the repeated
copies over the body pages were removed.
PageArea records occur min (0 without an <occur>). When the body has been
laid out, an area used fewer times than its minimum gets the pages it still
needs, in template order, after the body's pages. The form is 7 pages with
the once-only page last, which is what Acrobat Pro shows.
`pdfcraft-cli info`, `text`, `render` and `audit` rejected the owner
password of RC4-40, RC4-128 and AES-128 files with "the password is
incorrect", while `run doc_open` accepted it (#2670, item 8). The renderer
authenticates only the user password of revisions 2–4. `Session::open`
works around that by handing it the user password that owner
authentication recovers, but the CLI passed the typed password straight to
`inspect` and `PageRenderer`.
`pdfcraft_engine::render_password_for` now gives any render path outside a
session the password the renderer takes, sharing the recovery with
`Session::open`. The CLI uses it for the renderer and keeps the typed
password for the engine session and the cos document, so the owner
password still lifts copy restrictions in `text`.
The render test decodes the PNG and checks its dimensions and that it
has non-white pixels, for every algorithm with either password.
Refs #2670 (item 8 only).
Checks: cargo fmt --check; cargo clippy -p pdfcraft-cli -p pdfcraft-engine
--all-targets -- -D warnings; cargo test -p pdfcraft-cli; cargo test -p
pdfcraft-engine (and --lib); cargo xtask layers, assets, parity.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The IRCC IMM forms are certified (DocMDP P=2) and then saved once more
with Reader Extensions: a later revision adds the UR3 usage-rights
signature and rewrites the XFA `datasets` packet. The diff classified any
rewritten stream that wasn't page content or the catalog's XMP as "other
changes", so these untouched forms validated as invalid.
A stream the signed revision's /AcroForm /XFA array names `datasets` or
`form` (the form's data and state) is now a form fill when rewritten,
permitted at P=2 and P=3 and refused at P=1 as before. The roles come from
the signed revision, so a later revision that names the template
"datasets" doesn't make rewriting it a form fill.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
Save is chosen when the prompt opens, so Enter saves as before. The arrow keys
and Tab / Shift+Tab move the choice between Don't save, Cancel and Save, shown
with a focus ring; Enter or Space press the chosen button, Escape cancels.
Before, Enter always saved, even with another button reached by Tab.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Renaming a field changes only its own /T, so MASTER.a renamed to b
becomes MASTER.b. set_props compared the bare new partial name with the
other fields' fully qualified names: renaming MASTER.a to b succeeded
while MASTER.b existed, leaving two fields with one fully qualified
name, which are one field with a shared value (ISO 32000-2 §12.7.4.2);
and renaming MASTER.a to c was refused because of an unrelated
top-level field c.
The check now compares the new full name, leaving the field itself out
so a kid keeping its own partial name still succeeds, and the error
names that full name. Top-level renames are checked as before.
Optimize decoded DCT images with the generic JPEG decoder, which converts
three components from YCbCr to RGB whenever the JPEG itself doesn't say
otherwise, and then dropped /DecodeParms. An RGB JPEG stored untransformed
(/ColorTransform 0 and no Adobe marker) therefore came out in other colours
when recompressed, resampled or merged from strips.
The samples are now read with the colour transform ISO 32000-2 §7.4.8
gives them: an Adobe APP14 marker's flag, else /ColorTransform (default 1).
Untransformed samples are taken as stored, and the result is written as a
plain YCbCr JPEG (or Flate), which needs no /DecodeParms. When the marker
and /ColorTransform disagree, a value is out of range, the parameters are
not paired with the filter as §7.4 pairs them, or the decoder reads the
components otherwise (named R, G and B), the image is left as it is.
The tests draw each image with PdfCraft's renderer before and after
Optimize: RGB JPEGs with and without an Adobe marker under each
/ColorTransform, CMYK JPEGs (still left alone) and JPEG strips merged into
one picture.
Part of #1201.
Preserve already-merged upstream work and add the focus-loss/Escape regression. Exact candidate tree qualified with workspace tests, 31 WASM checks and a separate successful dependency audit.
The prompt's field requested focus on every frame. In egui 0.36 each
request also interrupts the input method, and egui-winit answers with
set_ime_allowed(false) then (true), which recreates the X11 input
context (or re-enables Wayland text-input) every frame. On Linux typed
keys go through the input method, so nothing typed reached the field.
Request focus only when the field doesn't have it, as the command
search does (#1638). The permissions password prompt shares the box.
Tests type through synthetic IME pre-edits and commits after opening
an encrypted file from the command line, File > Open, the start page
and a drop, and into the permissions prompt; both fail without the fix.
The Header and Footer, Watermark and Background dialogs drew their
preview on a blank sheet, so you could not see where the marks would
land on the page's text and pictures. The preview now paints the page's
own thumbnail, rendered off the UI thread like the pages panel's, with a
blank sheet until it arrives. It shows the current page when the page
range selects it, otherwise the first page the range selects, at its
displayed size (after /Rotate), which is the frame the engine lays the
marks out in. A background colour tints the page picture instead of
covering it, as a background behind the content looks.
Fixes#2837.
ROADMAP.md: next's file; the PR's progress line and revision row added once; status-line edit dropped.
docs/roadmap.md: next's file; the PR's revision row and M6 milestone sentence added once; status-line edit dropped.
crates/xfdf/README.md: all three paragraphs kept (#1917, #2686, this PR).
crates/xfdf/src/tests.rs: #2686's test and this PR's two tests all kept.
parity/acrobat-features.toml: form.data-xml-csv-txt keeps every evidence entry and note.
ROADMAP.md: next's file plus this PR's 2026-10-13 progress line and revision row; the #1919 refresh line and the status line repeating an M13 item dropped
docs/roadmap.md: next's file plus this PR's revision row; its refresh status line and the refresh note in the M4 row dropped
ROADMAP.md: next's file; the PR's progress line and revision row added once; status-line edit dropped.
docs/roadmap.md: next's file; the PR's revision row and M6 milestone sentence added once; status-line edit dropped.
crates/xfdf/README.md: both paragraphs kept (#1917 export whitespace, this PR's plain XML import text).
parity/acrobat-features.toml: form.data-xml-csv-txt keeps both evidence entries and both notes.
ROADMAP.md: next's revision history plus this PR's row; the #2267 refresh progress line dropped
docs/roadmap.md: next's revision history plus this PR's row; refresh wording dropped from the status line and the M1 row
ROADMAP.md: next's file; the PR's progress line and revision row added once; refresh line and status-line edits dropped.
docs/roadmap.md: next's file; the PR's revision row and M6 milestone sentence added once; refresh note and status-line edit dropped.
crates/cos/src/document.rs: union with #2525: header_relative revisions, the renumbering log line, and log passed to read_section on the first try and the header-relative retry
In Edit text & images, a paragraph's width handle is drawn centred on its
box's edge, and the edge grabs from up to 6 px outside the box. Object
selection sees a press first, though, and handed it to the paragraph tool only
on the box itself. A press on the outer half of the handle started an area
selection instead: its rectangle followed the pointer like a resized frame,
and on release the paragraph kept its old width and the text never moved.
Object selection now also leaves a press on a closed paragraph's edge grip to
the paragraph tool, through edit_text_ui::grabs_edge. That uses the same edge
test, box and upright-page rule as the tool itself, so the two can't drift
apart. The rewrap is the existing EditTextBlock with a width, so undo, redo
and save are unchanged.
Fixes#2276
Prepare a form ▸ Detect form fields places each field on a line or box the
page already draws, but every new widget got a grey /MK /BC and an opaque
white /MK /BG, so the fill hid the printed form underneath. Detected fields
now clear both in the same undoable step; fields drawn by hand keep the
defaults.
Refs #294, #2831.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Table detection took a sentence whose link was set in another colour
and font, split into blocks that run on into each other, for two table
columns, and took a paragraph's lines, whose box overlaps the line
before, for a cell beside it. The table it built put the paragraph's
first line in one cell, a fragment of the link in another, and dropped
the paragraph's later lines. A block that starts where another on the
same line ends now adds no column of its own, and a row whose cells
overlap is not a table row; a superscript beside a number stays in its
table.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
Add Background draws behind the page content, so on a page that first
fills itself with opaque white (browsers' Save as PDF, print drivers) the
background was hidden completely and the tool looked broken.
- pdfcraft-edit: new `backdrop` module. A strictly detected backdrop (one
axis-aligned `re`, opaque white in a device colour space, painted before
anything else and covering the crop box) is replaced by turning that one
fill into `n` in a per-page copy of its stream; the original stream is
left untouched for other pages. Remove background restores it, unless
the copy was edited since. Page-sized pictures (scans) are reported only.
- Background gains `replace_backdrop` (default on); the default colour is
a visible light yellow instead of near-white.
- Dialog: offers to replace the backdrop, warns about scanned pages,
checks pages a few per frame, preview draws text over the colour.
- Automation: `doc_background` takes `replace_backdrop`; new read-only
`doc_backdrops` lists pages with a backdrop.
Acrobat's behaviour on such pages is unobserved; recorded as our choice
in parity/.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A file edited after it was made PDF/A or PDF/X can keep claiming a
standard it no longer meets. organize::remove_standard_id removes the
claim: the pdfaid: XMP properties for PDF/A, or GTS_PDFXVersion and
GTS_PDFXConformance in the document information and as pdfxid: XMP
properties for PDF/X. Output intents and other claims (PDF/UA) stay.
The XMP is edited with the packet editor Document Properties already
uses (sync_info and the new remove_properties share its read and write),
so the rest of the packet stays byte for byte and unsafe packets are left
alone. It is one undoable edit, in Standards ▸ PDF/A as a button worded
like its undo label, and the standards_remove_id tool (pdfa or pdfx).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: ATTRIBUTION.toml, crates/ui-egui/src/i18n/es.tsv, crates/ui-egui/src/i18n/vi.tsv. Catalogs keep upstream's rows, then this branch's rows upstream lacks; ATTRIBUTION.toml is upstream's with every sha256 recomputed; progress lines keep both sides, upstream's first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ui): validated signature display, snapshot tab reuse
Signature widgets with layered appearances baked the unverified state
into the page, so a valid signature kept reading 'Signature Not Verified'
while the banner and panel said valid. Validated verdicts are now drawn
into the icon and caption layers in the display copy only: the signed
bytes are untouched, saving keeps the original appearance, and files
without those layers or without a valid verdict render exactly as before.
Other valid signatures still get the verdict mark over their widget,
sharing one style with the Signatures panel, and hide it again once the
appearance itself shows the verdict.
View signed version and View revision opened a new tab on every click,
and every tab showed the document title, so repeats piled up identical
tabs. Repeats now reuse the snapshot tab, snapshot tabs read like their
source plus the suffix in the tab strip, window title and Combine picker,
and carry a notice bar naming the revision with a Close that asks about
saving first.
Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>
* Address review comments on validated appearances and trust refresh
Count every field when checking shared appearance streams, so a
caption shared with an unvalidated field is never rewritten for the
valid one alone.
Pick the icon slot by drawing instead of length, and only when a
single layer qualifies. Bare rectangles never count as the icon, and
ambiguous or missing slots fall back to the overlay badge.
Revalidate and invalidate together through one helper, so trusting a
certificate always drops rendered pages. Trust alone still marks
nothing dirty.
Tests: mixed-verdict sharing, longer decorative borders, competing
and doubled layers, capture-before preservation, trust invalidation
without zooming, and signed Session round trips with rendering,
saving and passwords.
Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>
* Prove the icon slot geometrically and cover real layered signatures
A lone line drawing is still not the icon: frames and borders span the
box while the status mark lives in a corner of it. The icon now has to
sit strictly inside the appearance frame and cover at most half of it;
anything else falls back to the overlay badge. Widgets without a
measurable frame veto the rewrite the same way.
Tests: parsed extents and inhabitant checks as units, both reported
border repros plus a frameless widget, and real layered signatures
built with cms::sign_detached through Session open, trust, rendering,
save and reopen, plain and password-protected.
Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>
* Identify the icon by shape instead of geometry
A lone line drawing is still not the icon, but size and position were
the wrong signals: the real status mark is large, and geometry blocked
it. Frames, borders and panels are rectangles by nature while the mark
is not, so a drawing made only of axis-aligned rectangles is decoration
however it is stroked. Singularity, shared-stream and template checks
are unchanged; anything ambiguous still falls back to the overlay badge.
Tests: rectangularity as a unit, nested line-drawn frames, and a
triangular fixture mark in place of the rectangular one.
Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>
* Budget the appearance walk and keep the marks with the bytes
The walk that finds a signature's icon and caption capped depth,
layers and fields, but not the work: a dictionary whose entries all
point at the next one is visited once per entry per level, so a small
hostile file never finishes opening. The walk now counts its visits
and gives up, and a field whose tree cannot be read to the end stops
the whole pass, because a layer we never read may be the one two
fields share and a rewritten shared layer puts one field's verdict on
another field's page. The sharing count is exact again with that: two
widgets of one field pointing at the same appearance claim it once,
or the field vetoes its own rewrite.
A mark used to outlive the bytes it described. A failed display write
left the page showing the file's own caption while the verdict still
claimed its state was drawn; a metadata edit dropped the mark while
the page kept drawing the state, so the badge landed on top of it. The
write fallback now drops the marks and logs the loss, and metadata
carries them over.
Tests: a self-referential appearance tree, which never finished before
the budget, two widgets sharing one appearance, a metadata edit that
keeps its mark, a save that goes through the writer instead of
short-circuiting on the opened file, and the snapshot bar's Close
button pressed for real.
Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>
* Cite the new regressions in parity, restore main's installer note
The appearance entry now cites the bounded walk and the save through
the writer, and says what a tree that cannot be read to the end does.
The installer entry carried main's older text, which still claims
signed installers are planned; main's newer note is taken verbatim.
Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>
* M9: preserve rounded decoration beside raster status glyphs
* M9: place appearance progress beside signature history
* M9: retain appearances with ambiguous curved layers
* M9: keep signature verdicts in viewer overlays
Defer generic appearance rewriting: caption text and a unique drawing do not establish an icon role. Preserve original artwork for saves, image and text exports, and both print modes while retaining snapshot reuse and trust refresh. Cover chamfered borders and mixed signer/caption streams through signed Session round trips.
---------
Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>
Co-authored-by: skylar taylor-barrick <hi@skylar.cc>
- Detect a one-row centred title through a new `is_centred` helper in
`text_regions.rs`, shared by both rewrite paths, instead of the multi-row
ragged-centres rule that can never fire on a single row.
- A lone row counts as centred when its centre sits within one font size of
the page centre and its left edge is not the page's dominant left edge, so a
full-width left-aligned line at the margin is not mistaken for a title.
- In the single-style path, a centred block with no explicit width or
alignment no longer has its width stretched to the page margin; the line is
centred in the box it already sits in, so a grown row keeps its centre and
every wrapped row shares it.
- Gate the new rule behind main's `upright` flag: `is_centred` measures
page-space rects, which are not meaningful across a turned baseline; frame
coordinates (#1443) are the tool for that, not this change.
- Add `a_one_row_title_keeps_its_centre_when_it_grows_and_when_it_wraps`,
built from the issue's fixture; red before the fix (centre 398.025 vs
expected 306), green after.
Some producers head a classic xref table "1 n" and then write object 0's
free entry first, so every row is one number off and object 1 reads as
free. The renderer coped, but cos did not: printing a scanned invoice
built from cos dropped object 1 (page 1's image) and printed the page
blank. Such a subsection is now read from 0 when its second row locates
an object that is numbered 1, in both xref readers, and the repair is
logged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz