Commit Graph
3518 Commits
Author SHA1 Message Date
Eric Kaden cddc327264 Merge branch 'main' of github.com:storytold/pdfcraft into fix/xfa-script-error-keeps-effects
# Conflicts:
#	parity/acrobat-features.toml
2026-10-19 13:23:34 -04:00
PhilipandClaude Sonnet 5.5 102ceecf66 M2: review of #1567: find PingFang in the macOS font assets
CodeRabbit: on recent macOS the PingFang collection is not in
/System/Library/Fonts but in the downloaded font assets
(/System/Library/AssetsV2/com_apple_MobileAsset_Font*/<id>.asset/AssetData), so
the fixed path missed it, and the PingFang test returned early without checking
anything on such a machine. The macOS candidates now include the AssetData
folders of those assets (listed once, at most 512 per kind), and a test
searches the assets independently and requires the candidate list to contain
what it finds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 18:22:15 +01:00
Eric Kaden d022254ce7 Merge branch 'main' of github.com:storytold/pdfcraft into fix/xfa-layout-drops-usage-rights
# Conflicts:
#	parity/acrobat-features.toml
2026-10-19 13:18:04 -04:00
Michael 2f70006d60 Merge branch 'main' of https://github.com/storytold/pdfcraft into fix/superscript-footnote-paragraph-split
# Conflicts:
#	crates/edit/src/tests.rs
2026-10-20 01:10:02 +08:00
Claude 7f9d86c915 M2: doc comments for the Identity-H ToUnicode paths
Type0Font::new and char_code_to_unicode get doc comments, and
unicode_map's names the identity ToUnicode name. The comments and
vendor/README.md cite Acrobat's observed behaviour only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LymhVaGZCWwB9HNEbLzeMM
2026-10-19 17:07:36 +00:00
Claude 62f306ba4c Merge branch 'main' of https://github.com/storytold/pdfcraft into fix/identity-h-tounicode
# Conflicts:
#	contributors/people.toml
2026-10-19 17:05:37 +00:00
459379ef9b Edit text: refuse a rewrite of text that is a clipping path (#3447)
* Edit text: refuse a rewrite of text that is a clipping path

Editing text in a Photoshop-exported PDF destroyed the artwork on the page.
Such exports draw text in `7 Tr`: the glyphs are not ink but a mask, and a fill
or image is painted through them. Rewriting the run removed the glyphs and so
the mask, the fill behind flooded its own rectangle, and the file still opened
without a warning (#3429).

Measured on a page whose only text is a `7 Tr` mask over a red rectangle: the
fill covered 3.4% of the page before the edit and 100% after it.

`clear_block` already refuses these runs -- "text used as a clipping path
cannot be previewed for editing" -- but `replace_line` and `rewrite_block` did
not, so the destructive path was the one the editor offered. Both refuse modes
4 to 7 now, and the document is left untouched. Modes 0 to 2 paint ink and are
rewritten as before.

This does not keep the design: preserving it means emitting the replacement
inside the same `BT … ET` with the mode intact, which needs the writer to place
glyphs in a clipping object, as both reporters noted. Refusing turns silent
data loss into an error while that is written.

Found and analysed by @RavenCrowe666 and @mtlcl410, who converged on this fix.

* Check every run the rewrite removes, not the line's first one

The guard read `target.origin.state.render_mode`, the mode the line's first run
set. Two paths got a clipping run past it:

A line's runs need not share a rendering mode. Nothing in the join in
`text_lines` requires one -- it asks for the same stream piece, scope, text
object or neighbouring word, font, size, weight, slant and fill colour -- so a
line that starts as ink can continue as a mask, and the whole line's operators
are what the rewrite removes:

  BT /F1 48 Tf 40 60 Td 0 Tr (TITLE) Tj 7 Tr (MASK) Tj ET

reads as one line "TITLEMASK" whose origin is `0 Tr`. Replacing it passed the
guard and took the mask away with the ink.

`coincident_ops` drops another drawing of the line on the same baseline, so that
the replacement is all that shows -- a fake-bold second copy, an invisible OCR
layer. It matches on text, overlap and baseline, not on rendering mode, so that
copy can be the mask while the line being replaced is ink. Both rewrite paths
could drop it.

`TextLine` now carries whether any of its runs clips, and the guard reads every
line whose operators are in the removal set, the way `clear_block` already
checks every run it drops. In `replace_line_with_limit` the removal set is
computed before the replacement font is generated, so a refusal still leaves the
document untouched.

Both cases have a test; on the previous commit each returns
`Ok(LineEdit { substituted: None })`.

* Edit text: keep existing fixtures on text_page; build the clipping fixture on it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:03:54 -04:00
Earl Co e5033243f4 Add fills and border widths to text boxes and callouts (#3366)
* Support fills and border widths for text comments

* Allow borderless text comment defaults
2026-10-19 13:03:27 -04:00
FabianandClaude e45d5dca5c Tests: the parser-cancel test guards against a hang, not a 2 s limit (#1970) (#3462)
waiting_worker_can_cancel_before_initializer_returns gave its threads
2 s to start, and a busy FreeBSD VM in CI took longer. Its waits only
guard against a hang: the initializer is held until the end of the
test, so a waiter that can't cancel never returns, however long the
guard is. They now allow 600 s, like the hang guard in
page_contents_and_nested_forms_share_one_budget (#2812), which already
covers the second test named in the issue. The test polls with a 1 ms
sleep instead of yield_now, and the held initializer no longer panics
a second time when the test fails.

Checked by hand: with a 3 s delay at both threads' start the old test
fails at its deadline and the new one passes; with the waiter's
condvar wait made untimed (a real stall) the new test fails at its
guard.

Fixes #1970

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 13:03:13 -04:00
5602fbba12 M10: Office export follows forms 50 deep and up to 50 000 a page and says where it stops (#3120) (#3426)
* M10: Office export reads forms as far as the renderer and says where it stops (#3120)

Export to Word, PowerPoint, HTML and RTF stopped at 12 nested forms and
4 096 forms a page and left the rest out silently. It now follows forms
50 deep, as the renderer does, and up to 50 000 a page (the renderer's
budget for nested paints), still bounded on hostile files.
doc_export_office lists pages past those limits in truncated_pages, with
a warning. New: reading_blocks_checked and export_office_checked;
reading_blocks and export_office keep their signatures.

Fixes #3120

Co-Authored-By: Claude <noreply@anthropic.com>

* Tests: pin the form depth limit to the renderer's 50 (#3120)

The depth test built its chain from MAX_FORM_DEPTH, so it moved with the
constant. Write the renderer's limit out: depth 50 is read, depth 51 is
left out and reported. Fails with the constant at 49 and at 51.

Co-Authored-By: Claude <noreply@anthropic.com>

* Export: say which forms count toward the page limit (#3120)

The page budget also counts forms the page draws itself, which the
renderer does not charge, so a page can be reported as cut although the
viewer shows it. The warning and the tool description now say so.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-19 13:02:44 -04:00
84bd488db3 M3: keep save shortcuts behind other dialogs (#3387)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:01:58 -04:00
Earl Co 8b88a2d14b M2: fix shading bounds in Type 3 glyphs (#3178) (#3412) 2026-10-19 13:01:14 -04:00
Giuseppe Silano b113896461 Never-crash gate on the oracle and testkit crates; oracle find() no longer panics on an empty needle (#3435) 2026-10-19 13:01:03 -04:00
Eric KadenandBrandon Thomas 0831d911b4 M6: a date typed without separators fills a yyyymmdd field (#3388)
* M6: a date typed without separators fills a yyyymmdd field

parse_date read each run of digits as one number, so 20261018 in a
yyyymmdd field was a single number with nothing left for the month and
the day, and the keystroke check refused it. Every date field on forms
that use the YYYYMMDD date format (US Army forms such as DA Form 31 among
them) refused every valid date.

order() now records each field's fixed width (4 for yyyy; 2 for yy, mm,
dd, HH, MM and ss) and whether the next field follows with nothing in
between. parse_date keeps runs of digits as text and splits a run between
such joined fields by those widths. Formats with separators, month names,
literal digits (dd.mm.26) and two-digit years behave as before.

A run of digits too long for a u32 used to be dropped silently, which let
"2026 99999999999" pass for yyyy; it is now read like any other number and
refused when it is left over or does not fit.

* chore(contributors): credit Eric Kaden by real name

* M6: separated runs of digits stay whole in a joined date format

With separators each run of digits is one field's number, as before this
branch: "2026-10-18" in a yymmdd field reads the year 2026, where the
first commit split the run into 20 and 26 and refused the date. A run is
now split between joined fields only when fewer runs were typed than the
fields still to fill, so "2610" and "261018" still split.

* M6: what is left of a split run of digits fills its field exactly

"202610018" in a yyyymmdd field split off 2026 and 10 and read the rest,
018, as day 18, so a nine-digit date passed. The rest of a split run now
has to be its field's width; a number typed on its own ("2026 10 018")
keeps its leeway.

* M6: a number typed on its own is split only when its rest fills the next field

The optional time fields of a format like yyyymmddHHMM counted as fields
still to fill, so "2026 10 018" split the day 018 into 01 and 8 and was
refused; on main the time defaults to 00:00. A run typed on its own is
now split only when what is left can fill the next field; the rest of a
run that was already split is split as before.

* M6: describe the undelimited-date tests without naming one form

The tests and their comments now say what they check (fields written side by side, such as yyyymmdd) instead of naming the form that showed the problem, and use a generic field label.

* M6: count date and time fields apart when deciding to split a run

Optional time fields counted towards the fields still to fill, so
"2026-10-18" in a yymmddHHMM field split the year into 20 and 26 and was
refused, where main reads it with the time left out. A run typed on its
own is now matched against the fields of its own kind: date runs against
date fields, time runs against time fields. What is left of a split run
goes on being split along the fields written side by side, so
"20261018143005" still fills yyyymmddHHMMss. A mix such as
"2026 1018 1430" (year apart, month and day joined, then a time) is still
refused, as on main.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-19 13:00:52 -04:00
Eric Kaden a30dde2225 M6: the date picker follows a whole date typed while it is open (#3393)
The calendar took its month once, when it opened, so a date typed while
it was open showed only after leaving the field and coming back. It now
moves to a date as soon as the text becomes one, and stays on the month
it shows while the text is half typed or unchanged, so its arrows still
turn the months.
2026-10-19 12:58:15 -04:00
Eric Kaden caaf058815 M6: a secured form's notice bar still says it is a form (#3404) 2026-10-19 12:58:03 -04:00
32abf71049 Text: tracked glyphs retain their word boundaries (#3477)
* Text: tracked glyphs retain their word boundaries

* M7: cap inferred tracking at half an em so one-glyph words and cells keep their spaces

Adds the regression test from the #3477 review (fails without the cap).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:57:52 -04:00
Nyanpasuwu 14fcc82a24 i18n: add Central Kurdish (Sorani) as an interface language
2,670 entries in ckb.tsv, written in logical order, with the same
(context, source) key set as ar.tsv. Registered with rtl: true and
plural_one_other (CLDR ckb: one, other); Sorani keeps the noun singular
after a numeral, so the 13 @plural rows usually read the same in both
forms.

RTL reuses the Arabic path: the catalog is put into display order by
bidi::display_rtl when it loads, so Kurdish has the same four limits
docs/localization.md lists for Arabic. No new layout or bidi code.

DATE_LANGUAGES gains Kurdish month and weekday names, because
date_languages_are_the_interface_languages keeps both registries in
step.

ku-* locales are not mapped to ckb on purpose: ku is the macrolanguage
and means Kurmanji in Latin script. Only ckb* tags resolve.
2026-10-19 19:32:21 +03:00
Philip 33fe9f440b Merge remote-tracking branch 'origin/main' into feat/edit-scanned-page-offers-ocr 2026-10-19 17:29:26 +01:00
Philip d4035709dc Merge remote-tracking branch 'origin/main' into feat/system-cjk-font-fallback 2026-10-19 17:29:23 +01:00
PerchAnton 6bcfb872e2 M7: resolve main integration for PR #3000 2026-10-19 19:22:46 +03:00
Lcoding15andClaude Opus 5.5 978c77c583 M2: the Identity-H ToUnicode map leaves out DEL and C1 controls
crates/fonts mapped codes U+007F and U+0080–U+009F to those controls while
the renderer's text layer (hayro patch 35) gives them no text. The generated
map now covers U+0020–U+007E and U+00A0–U+00FF in the first high byte, as
the text layer does; both tests check DEL and a C1 control.
(CodeRabbit review on #3222.)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 16:20:28 +00:00
Lcoding15andClaude Opus 5.5 96db0e3bb5 M2: Type 0 fonts with /ToUnicode /Identity-H read codes as Unicode
WSCAD (and other CAD exporters) write `/ToUnicode /Identity-H` — a name,
not a CMap stream — on composite fonts whose two-byte codes are the UTF-16
code units. Acrobat, MuPDF and pdf.js read each code as its Unicode value.
hayro resolved the name to the Identity CMap, which maps codes to CIDs only,
so such pages had no text at all: nothing could be selected, searched,
copied or edited (147 of 147 pages of a WSCAD schematic).

- vendored hayro-interpret patch 35: `Type0Font::identity_to_unicode`
- crates/fonts: the same reading for Edit text, find and redaction
  (controls and lone surrogates map to nothing; simple fonts unaffected)

Tests fail without the fix: render `identity_to_unicode_names_give_text`,
fonts `identity_to_unicode_names_read_codes_as_unicode`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 16:20:28 +00:00
PerchAnton 73a98ffd79 M12: resolve main integration for PR #2648 2026-10-19 19:12:49 +03:00
PerchAnton 4bba9e08c6 M12: resolve main integration for PR #2624 2026-10-19 19:07:47 +03:00
PerchAnton 0d502f028b M12: resolve main integration for PR #2619 2026-10-19 19:05:12 +03:00
skylar taylor-barrick e397efa854 Merge pull request #3285 from mtlcl410/fix/struct-refs-parent-tree-2532
M8: redaction's structure-tree walks start from the parent and ID trees too (#2447, #3302)
2026-10-19 10:05:04 -06:00
PerchAnton b2477ff326 M12: resolve main integration for PR #2615 2026-10-19 19:01:17 +03:00
PerchAnton d1ea34e0db M7: resolve main integration for PR #3187 2026-10-19 18:59:01 +03:00
oracola91 737f2f725f test(sign): a small RSA key's signature verifies with the short-key note
verify_noting adds the note only when the signature verifies. The test
uses a synthetic 1024-bit key and its PKCS #1 v1.5 signature: the valid
signature carries the note, and one that doesn't verify has none.
2026-10-19 18:57:26 +03:00
PerchAnton c975abbc02 M4: resolve main integration for PR #3311 2026-10-19 18:56:09 +03:00
PhilipandClaude Sonnet 5.5 703db23de3 M10: review of #1609: judge a page that is no scan again once the document changes
CodeRabbit: Edit marked a page as tried before checking that it was a scan, so
a page that was not one when Edit first looked (no picture over it yet) was
never looked at again, even after a picture of text was added to it. A page
found not to be a scan is now remembered with the document's edit generation
and judged again only after the document changes (not every frame: listing a
page's images parses it); a page whose recognition has started is still never
started again, so Undo sticks.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 16:46:35 +01:00
PerchAnton a33cf0fe36 M1: resolve main integration for PR #3042 2026-10-19 18:43:18 +03:00
PhilipandClaude Sonnet 5.5 5b3b87430b M2: review of #1567: read each installed font file once and share it
CodeRabbit: the fallback cache was keyed by (script, weight) and every key read
and kept its own copy of the file, so PingFang.ttc (tens of megabytes, a
candidate of several scripts and both weights) could be held up to four times.
The bytes now come from a process-wide cache keyed by path, remembering files
that cannot be used too; the per-script cache keeps a handle and a face index.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 16:42:08 +01:00
oracola91 cb226fb08b fix(sign): the key description counts the modulus bits like the warning
describe() named an RSA key by its modulus length in bytes times
eight, so a 2047-bit key read "RSA 2048-bit" next to the new warning
that it is shorter than 2048 bits, and a modulus with a leading zero
byte read 8 bits too long. It now uses rsa_bits, as weakness() does.
2026-10-19 18:27:34 +03:00
Philip 41ee1460d6 Merge remote-tracking branch 'origin/main' into feat/edit-scanned-page-offers-ocr
# Conflicts:
#	ROADMAP.md
2026-10-19 16:08:07 +01:00
Philip 07a6c59b38 Merge remote-tracking branch 'origin/main' into feat/system-cjk-font-fallback
# Conflicts:
#	ROADMAP.md
2026-10-19 16:05:02 +01:00
mtlcl410andClaude Opus 5.5 da351cda8f fix(forms): cache field appearances drawn for display, redraw only what changed (#3470)
Review follow-up (CodeRabbit on #3475). A form edit refreshes the display
copy on every keystroke, and with /NeedAppearances that redrew every field
of the form: 68 ms per edit on a 1000-field form against 7 ms on main.

FieldAppearanceCache keeps each widget's drawing with the objects it was
drawn from: the widget and its field ancestors, the form dictionary and
the fonts of its /DR, each with the objects its entries refer to. An
entry is used only while each of them is still the same object
(Arc::ptr_eq); the document replaces an object it edits, so a changed
input is a miss, never a stale hit. The engine keeps one per editor. A
field edit on that form now takes 13 ms.

The repair note on a failed display write now names field appearances
too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv2WwdgjmBz33GWkwgNn3Q
2026-10-19 22:26:31 +08:00
mtlcl410andClaude Opus 5.5 b661ba4584 fix(forms): draw fields the file leaves to the viewer, for display only (#3470)
A form with /NeedAppearances true (ISO 32000-2 Table 224: the writer
"has not provided appearance streams for all visible widget
annotations") showed its text fields empty: PdfCraft only read the flag
when writing, and the display copy that draws missing appearances skips
widgets. Filled forms from tools that leave drawing to the viewer looked
blank, while Acrobat, Firefox and MuPDF show the values.

pdfcraft_forms::with_field_appearances returns a copy with text and
choice widgets drawn from their values (every one when the flag is set,
otherwise those with no /AP /N), with the generator a typed value uses.
The engine's display copy (page view, page images, Print as Image, CLI
render) applies it after the comment appearances. The document itself
is not changed, so saving writes the file as it was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv2WwdgjmBz33GWkwgNn3Q
2026-10-19 21:53:42 +08:00
MichaelandClaude Sonnet 5 8477810738 fix(edit): keep a superscript footnote marker in its paragraph's row (#3252)
A footnote marker drawn with Ts (text rise) and a smaller Tf size sits on
the same visual row as the body text around it, but the row-joining
baseline tolerance was scaled by the smaller of the two runs' sizes
(20% of it) — far tighter than a typical superscript's rise, which runs
up to about half the body size. The marker split off into its own line,
and then its own block, fragmenting one paragraph into three separate
editable zones in Edit PDF > Edit text and images.

Scale the tolerance by the larger of the two sizes instead, which still
leaves ordinary next-line leading (about 1.2x the font size) well
outside it.

Added a regression test in crates/edit/src/tests.rs that fails on main
(reverted the fix locally and confirmed it goes red: 3 blocks instead
of 1) and passes with the fix.

cargo fmt --check, clippy -D warnings, cargo test -p pdfcraft-edit (186
passed), cargo xtask layers and cargo xtask assets all clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-19 21:31:49 +08:00
Luigi Pinna 04697ad492 fix(content): an inline image ends at its own EI when later text holds a non-ASCII byte
The end-of-data look-ahead rejected a valid EI candidate when any byte
above 0x7F lay between it and the next EI. Those bytes are the content
stream that follows the image, and content streams carry such bytes
inside strings, so no candidate qualified and the image data ran to the
end of the stream. The operator check that follows now decides alone,
bounded as before by MAX_EI_CANDIDATES.

Fixes #2456
2026-10-19 15:00:31 +02:00
mtlcl410andClaude Opus 5.5 23a032ef3b fix(engine): a reload keeps reading colours and hidden comments (#3443)
`Session::reload` (the file changed on disk) opens the new bytes with a
default render config, so a document shown white on black, or with its
comments hidden, went back to the file's own colours and comments until
the preference was toggled. The UI re-applies the preference only on
open. Reload now carries both display settings over and rebuilds the
renderer with them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv2WwdgjmBz33GWkwgNn3Q
2026-10-19 19:17:21 +08:00
jonathanwxh-cell f9d37c089f fix(render): use unfiltered inline image byte length before EI heuristic (#3107) 2026-10-19 17:33:48 +08:00
mtlcl410andClaude Opus 5.5 bae6981959 fix(render): the device decides whether a knockout group's image clears its rectangle (#2971)
Review on #2974: the interpreter decided with a full-resolution decode
(`decoded_raster(None)`) before the renderer decoded at its draw size, a
second decode of every downscaled image under another cache key; and an
image a preview skips for its size still cleared what was under it.
`Device::knockout_image` now lets the renderer decide, with the same
eligibility and decode size as `draw_image` (shared `image_draw_size`), so
the draw reuses that decode and a skipped image clears nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv2WwdgjmBz33GWkwgNn3Q
2026-10-19 16:34:34 +08:00
mtlcl410andClaude Opus 5.5 3fff309dd3 M3: a link followed in full screen cancels the click's pending page turn (#1459)
Review on #2070: a click on the page schedules a turn after the
double-click time, and a link clicked before then jumped and then the page
still moved on. Following a link now drops the pending turn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv2WwdgjmBz33GWkwgNn3Q
2026-10-19 16:21:20 +08:00
mtlcl410andClaude Opus 5.5 a6b2ad7fb5 fix(render): resolved JP2 palette colours keep 8 bits whatever the index precision (#3179)
Review on #3184: with no /BitsPerComponent the decoded image took the
codestream's precision, which for a palette image is that of the indices.
The resolved colours are decoded to 8 bits, so a 1-bit index rescaled them to
0 or 255 per channel ((64, 128, 192) became (0, 255, 255)). When the palette
is applied the samples are now reported as 8-bit; an image without a palette,
or one whose raw indices are kept for an /Indexed space, keeps its precision.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv2WwdgjmBz33GWkwgNn3Q
2026-10-19 16:10:17 +08:00
mtlcl410andClaude Opus 5.5 3df8a6386d fix(render): a knockout clear follows a shading's /BBox and skips images that don't decode (#2971)
Review on #2974. In the erase pass `set_paint` returned no clip, so a
shading fill cleared its whole path while the redraw is clipped to the
shading's /BBox (ISO 32000-2 Table 77): red outside the box stayed erased.
It now returns the shading's clip there too. And an unmasked image cleared
its placement before decoding, so an image that doesn't decode (over
MAX_IMAGE_PIXELS, damaged data) erased the group's earlier artwork and drew
nothing; the clear now happens only when the image decodes, asked only in a
knockout group (Device::in_knockout_group). The stencil case in the test now
also checks that the stencil draws.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv2WwdgjmBz33GWkwgNn3Q
2026-10-19 16:03:57 +08:00
LabDennis 62cde99aef M5: reveal sidebar comments at the current zoom (#3418) 2026-10-19 10:02:56 +02:00
mtlcl410andClaude Opus 5.5 14e17ece3b M3: in full screen a double-click selects a word, and full screen selects the Select tool (#1459)
Review on #2070 (CodeRabbit):
- A click turned the page on its release, so a double-click's second click,
  meant to select a word as the PR promises, landed on the next page. The
  turn now waits out egui's double-click time, and is dropped when a
  selection appears or full screen is left; a double-click on blank page
  area still turns once.
- Entering full screen from another tool left it on, and clicks only turn
  pages with Select while the quick tools are hidden. Full screen now selects
  Select and gives the earlier tool back on leaving, as Space's temporary
  Hand tool does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv2WwdgjmBz33GWkwgNn3Q
2026-10-19 15:56:30 +08:00
mtlcl410andClaude Opus 5.5 771129594c test(render): the non-separable blend modes keep their colour over an empty page too (#2970)
Hue, Saturation, Color and Luminosity blended with the white paper the
same way; the fix covers them, and the test now checks them over an
empty part of the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
2026-10-19 15:39:51 +08:00