Preserve the idle repaint and second-edit recovery regression while refreshing already-merged upstream work. Validated exact tree with full local CI and native Linux idle recovery.
A resource dictionary's DefaultGray, DefaultRGB and DefaultCMYK colour
spaces replace the device space content selects (ISO 32000-2 §8.6.5.6).
hayro ignored them: g/G, rg/RG and k/K set the device space directly,
cs/CS resolved the device names before looking at the resources, and
images took their /ColorSpace as written. Pages that calibrate device
colour this way, common in PDF/A, came out in the wrong colours.
The interpreter now looks the defaults up once per content window
(DefaultColorSpaces), in the resources and those they inherit, so a form
XObject without its own draws in its page's. They replace DeviceGray,
DeviceRGB and DeviceCMYK selected by the colour operators, by cs/CS (by
name or through a /ColorSpace resource that is a device space), and as
the /ColorSpace of image XObjects and inline images. A default that is a
Lab, Indexed, Pattern or device space, has another number of components
or is not a colour space is ignored, and device colour is drawn as
before. Device spaces nested in other spaces, shading dictionaries,
masks and transparency group spaces are unchanged. The decoded-image
cache keys a decode by the default it was drawn in, since one image can
be drawn with and without one.
Part of #1201.
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
After a highlight, its note editor in the Comments panel kept the
keyboard, so V and H were typed into the note (#2669, item 2).
- The editor asked for the keyboard on every frame when no other widget
had it, so it took it back after every click elsewhere. It now takes
it once, when it opens (CommentView::open_editor, EditorFocus::Take).
- Adding another comment left the editor open, and drawing is a drag,
which egui doesn't treat as a click elsewhere. An empty editor on
another comment now closes when a comment is added; one with text
stays open with it but gives the keyboard back (EditorFocus::Release).
Part of #2669.
#2241 made leaving the Edit tab put Edit text & images down and apply a
paragraph still open in the editor, but only in select_mode, which only
the mode bar reaches. The Read mode command (View > Read mode, Ctrl+H,
the command search) went through switch_mode's other branch, which only
set the mode: Read mode then kept Edit text & images on, every paragraph
boxed, and an open paragraph editor floating over the page with its edit
not applied.
enter_mode now sets the workspace together with its tool, and both
routes go through it, so they can't drift apart again. The command still
leaves the panels as they were.
Test: editing.rs::the_read_mode_command_applies_an_open_paragraph_and_puts_edit_text_down
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.
Object-stream members were indexed with unchecked casts: `/First` and each
offset went through `as usize` and an unchecked addition, which truncate or
wrap on 32-bit targets such as the wasm32 build, and the member number went
through `as u32` on every target, so a member numbered 2^32 + 3 was read as
object 3. A negative offset was clamped to zero and read the stream's first
member instead.
Each member is now converted and added with checks, and kept as an unusable
slot (preserving positions, which cross-reference entries refer to) when its
number is not an object number or its start falls outside the decoded data.
Looking such a member up is a MissingObject error, as for any other absent
object.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
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#2752
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.
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
With the All tools panel closed, page text scrolled up under the mode bar
gave the Read, Edit, Convert and E-Sign tabs the page's cursor: the
Select tool's I-beam, or Edit text's four-way arrow (the I-beam once a
paragraph was open). The page code tested egui's raw hover position
against the page's whole rectangle, which runs on under the chrome when
the page is scrolled, and it sets the cursor after the mode bar does.
The text layer and Edit text now set their cursor only while the pages'
widget contains the pointer, as Fill & Sign already does; a selection
being dragged keeps the I-beam past the edge. The open paragraph's
editor, a foreground Area placed on its paragraph, also floated over the
tabs and took their clicks: it is now clipped and hit-tested to the
document viewport and stays on its paragraph instead of being pushed
back into the window.
Fixes#2755
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