Commit Graph
4058 Commits
Author SHA1 Message Date
Lombardi2003andClaude Opus 5.5 15a0b696c5 fix(comments): the eraser's size sets how wide it rubs (#3747)
The eraser always rubbed 6 pixels either side and drew a 12-pixel
preview, whatever its thickness setting. Both now follow the setting,
at three pixels per point, so the default 2 pt keeps today's 12-pixel
eraser and the slider's 0.5-12 pt gives 3 to 72 pixels across.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 23:17:21 +02:00
Oliver HagmannandClaude Opus 5.5 4cd646811a M5: drag a line's ends (and a polygon's corners) to change its length and angle
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>
2026-10-20 21:29:14 +02:00
Aman Mehtar 1d76fcbc3b Edit text: tests for re-encoded subsets and five-letter prefixes (#3661)
From @mtlcl410's independent run of this PR:

- a second subset of the same font can be re-encoded with `/Differences`, so
  "-l" has different codes from the first subset's; the line still joins,
  reads in coding order, and editing it replaces every run, including the
  re-encoded subset's;
- `ABCDE+Helvetica-Bold` has a five-letter prefix, not the six-letter subset
  tag of ISO 32000-2 §9.6.4, so it stays a different font from
  `Helvetica-Bold` and the runs stay apart.

The first case (and its edit) fails before the subset fix; the second guards
the tag-length rule against stripping any `+` prefix.
2026-10-21 00:46:01 +05:30
Mex 17dbc88556 Revise evidence and notes in acrobat-features.toml
Keep the initial-view feature status and missing-feature notes unchanged. Add the named synthetic regression and supported indirect-boolean behavior.
2026-10-20 21:08:57 +02:00
Mex 37454d9ce1 M4: document indirect initial-view boolean handling
Update the M4 milestone note and revision history; preserve existing gaps and estimates.
2026-10-20 21:08:17 +02:00
Mex 4bcae6dd4f M4: record indirect initial-view preference coverage
Add the required progress-log entry without changing readiness estimates.
2026-10-20 21:07:48 +02:00
Mex 3d32ffd27c M4: resolve indirect initial-view boolean preferences
Read all six boolean ViewerPreferences through the document resolver. Synthetic regressions cover direct/indirect dictionaries, true/false, malformed values and save/reopen.
2026-10-20 21:07:13 +02:00
DubblbindongandClaude Opus 5.5 93945aeade M3: pin the reported OS in the panel snapshot harness
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>
2026-10-20 20:46:47 +02:00
DubblbindongandClaude Opus 5.5 14471d76f4 M3: panel snapshots run from one exhaustive setup
Review (CodeRabbit): every_panel_has_a_snapshot only checked that each
panel's file existed, so a new panel with a file but no test would pass.
Now one function opens each RightPanel with no wildcard in its match, and
every_panel_matches_its_snapshot compares all eleven, collecting every
mismatch before failing. Snapshots unchanged.

Seen failing: renaming "Validate all" reports "1 of 11 panels differ" with
both trees for the Signatures panel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 20:46:47 +02:00
DubblbindongandClaude Opus 5.5 b0e751500b M3: snapshot tests of every side panel
Each of the eleven right-hand panels (Comments, Bookmarks, Pages, Fields,
Layers, Attachments, Signatures, Accessibility, Tags, Search, Compare) opens
on one synthetic document, and its AccessKit tree is compared with a text
file in crates/ui-egui/tests/snapshots/. Text rather than pixels: the same
on every platform and GPU, not an asset under AGENTS.md §1, and readable in
a diff. Positions are left out, so a moved widget is not a change but an
added, removed or renamed one is. UPDATE_SNAPSHOTS=1 rewrites the files.

An exhaustive match over RightPanel stops the file compiling when a panel
is added without a snapshot.

Seen failing: renaming the Signatures panel's "Validate all" button fails
signatures_panel alone, showing both trees. Stable under TZ=UTC,
America/Los_Angeles and Asia/Tokyo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 20:46:47 +02:00
Hamza e3fc82938f A rectangle's corners are equal, not nearly equal
is_nearly_equal allows a 256th of a path unit, and stroke_path_impl
applies the matrix after path_as_rect, so a cm of 10,000 turns that
tolerance into tens of device units: a path that only nearly closes was
painted as the rectangle, with a side it does not have, and an edge off
true by that much counted as axis-aligned.

The fast path now compares coordinates exactly. Nothing that is a
rectangle loses it: a rectangle's coordinates are equal in the content
stream, not approximately equal. A path that misses takes the general
route and is painted as written, which is the safe direction.

I could not make a rendered case that distinguishes the two: this
renderer paints a 10,000x cm as a solid page whatever the line width, so
there are no pixels to compare at the scale where the tolerance shows.
The change rests on the argument above, and the three existing tests
still pin the behaviour that is measurable.

Reported by CodeRabbit on #3327.
2026-10-20 23:27:49 +05:00
NetterSilverandClaude Haiku 5.5 be223888f7 Join /Contents pieces with a space in measure snap
Fixes #3570

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 20:20:31 +02:00
eliets2 b97b383232 redact: count inline outline items against the total budget
The total budget of the outline count (100,000 items) was checked against the
number of indirect items the walk had reached. Items written inline, as a
direct dictionary, were counted in the result but never entered that set, so
an outline of 60,000 indirect siblings, each with one inline child, counted
120,000 items.

The walk now keeps a separate running total of every item it counts, inline
items included, and stops when that total reaches the budget. The set of
indirect items is still used to count shared items once. Ordinary outlines
count as before.

The doc comment of the walk now states the depth limit in one sentence: an
item first reached through a deeper path is not visited again from a shallower
one, so items nested past depth 64 may stay uncounted. The depth behaviour is
unchanged.

Test added in crates/redact/src/tests.rs:
inline_outline_children_count_towards_the_total_budget. It reads 120000 on the
previous head and 100000 now. Reverting the budget check to the indirect-only
count fails it; reverting the increment fails it and the existing total budget
test. The other outline tests pass unchanged.

Checked with cargo fmt --all -- --check, clippy with -D warnings on
pdfcraft-redact, and cargo test for pdfcraft-redact (85 passed, 0 failed).
2026-10-20 21:04:06 +03:00
eliets2 71e0346186 docs(redact): say that a mark with no readable area fails apply
The apply section of the redact README described the fail-closed rules but not the new refusal of a Redact annotation with neither a usable QuadPoints array nor a usable Rect. Documented here with the error name and the page scope of the count. No behaviour change in this commit.
2026-10-20 21:03:24 +03:00
eliets2 92c979c5b2 redact: refuse marks with no readable area instead of skipping them
A Redact annotation with neither a usable QuadPoints array nor a usable
Rect was dropped silently by marks(). Applying the readable marks beside
it then reported success, and the text under the dropped mark stayed in
the file.

apply() now counts such marks on the pages being applied and returns
RedactError::UnreadableMark(n) before anything changes. The message goes
out through the existing error text: the engine edit error, the
automation reply and the UI failure line all print it. Marks that were
readable through the existing fallbacks (a Rect alone, a usable
QuadPoints array) work as before.

Tests: three in crates/redact/src/tests.rs (refusal with the count and
no change, Rect-only still redacts, an unreadable mark on another page
does not block the selected page) and one end to end in
crates/automation/tests/automation.rs (the reply carries the reason and
the page keeps its text). On upstream main with only the variant added,
the two refusal tests and the automation test fail; the Rect test passes.
Reverting each pinned production line turns the tests red again.
Gates: cargo fmt --check, clippy -D warnings for pdfcraft-redact and
pdfcraft-automation, and the redact test suite pass.
2026-10-20 21:03:24 +03:00
Combiler01 d4a739c3b5 Code Rabbit Fixes 2026-10-20 23:32:53 +05:30
Aman MehtarandClaude Code c7505930b8 M12: accept Identity CJK Unicode mappings in accessibility checks (#3673)
Treat predefined CJK CMaps and Adobe CID collections as alternative mapping routes. Resolve indirect metadata and reject unknown names and foreign registries without changing ToUnicode handling. Add synthetic checker and read-only automation regressions with red/green evidence.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 23:29:13 +05:30
O Hingst 9ca2927807 docs: credit MatrixMagician in the About window by their GitHub profile name 2026-10-20 18:55:34 +01:00
O Hingst f99966a195 fix(render): hit-test selection against the glyph quad, not its bounding box (#3693)
PageText::nearest measured the distance to each glyph's axis-aligned bounding
box. In sheared or turned text that box is wider than the glyph and overlaps its
neighbours, so a pointer on a letter was also inside the box of the letter before
it, at distance 0, and the earlier glyph won. A drag ending on the last letter of
a word or line then ended one or two letters short.

A glyph whose drawn em box (quad) holds the point now wins, the nearest centre if
several do. With no quad hit the bounding-box distance still decides, so pointers
past a line end or between lines behave as before; a tie there goes to the nearer
centre rather than the first glyph.

Tests: pdfcraft-render lib 312 -> 314. The sheared and 45-degree test fails on
the old hit-test (glyph "i" resolves to the glyph before it) and passes now.
pdfcraft-ui-egui text_select: 14 pass.
2026-10-20 18:52:38 +01:00
NetterSilverandClaude Haiku 5.5 12c66f7113 Keep zero annotation flags through XFDF export/import
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>
2026-10-20 19:44:42 +02:00
NetterSilverandClaude Haiku 5.5 3246587e85 Return empty WordNum result when cents rounding carries to 1e12
Found while testing crates/js/src/formcalc.rs:2024-2050 (word_num)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 17:38:34 +00:00
NetterSilverandClaude Haiku 5.5 ab815b50a6 Stop Std14::recognise mapping sans-serif font names to Times
Found while testing crates/fonts/src/std14.rs:84

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 19:36:57 +02:00
NetterSilverandClaude Haiku 5.5 4125bddbae Parse a bare trailing "a" as AM in AcroForm date/time parsing
Found while testing crates/forms/src/af.rs:638-642 and 750-755 (parse_date)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 19:30:45 +02:00
ffishmanandClaude Opus 5.5 63b4af8ba5 fix(sign): embed the issuers of macOS Keychain identities
A Keychain identity came back with an empty `chain`, so a signature made with
it carried only the signer's certificate. A validator that doesn't already
hold the intermediates (Acrobat, or PdfCraft on another machine) then can't
build the path to the root. The Windows certificate store identities already
carry the chain Windows builds.

`keychain::find` now asks the Security framework for the chain (SecTrust with
the basic X.509 policy, no network fetch) and keeps every certificate but the
signer's. It is best effort: a certificate macOS can't chain still signs.

`chain-legacy.p12` is `chain.p12` re-exported in the legacy format that
`security import` accepts, for a new ignored Keychain test.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 14:27:52 -03:00
SBaksa 1f182ff0bf Select All of the document: copy off the UI thread, end it on any page press
- Copy checks the document's copying permission first, then reads the
  pages whose text wasn't read for display on a worker thread
  (DocumentCopy; in place on the web), keeping only each page's text as
  it goes; the clipboard gets the text on a later frame. A new copy or
  the document closing cancels it.
- Any press on a page ends Select All of the document, whatever the tool
  and whether its text is read; so do switching to one page at a time, a
  page selection, and an edit of the document.
2026-10-21 04:24:30 +11:00
robertobulltech 1451325a42 M2: preserve word spaces around TOC leaders (#3086) 2026-10-20 19:15:53 +02:00
PerchAnton 941fbe7cda M3: choose overlapping comment hover by annotation index (#3620) 2026-10-20 19:58:22 +03:00
Aman Mehtar 4a618e8dcb Edit text: do not group or join by an empty font name (#3661)
Review follow-up. `Metrics::from_dict` leaves `base_font` empty when a font
has no `/BaseFont`, and a bare subset tag such as `/ABCDEF+` left an empty
name after stripping. Two consequences:

- `same_style` split one paragraph into separate reading blocks for the
  exporters when both names were empty, though they grouped before subset
  joining;
- `/ABCDEF+` and `/GHIJKL+` compared equal after stripping, so unrelated
  malformed fonts could join into one line.

`same_base_font` now strips both tags first and requires a non-empty, equal
name, so a missing name or a bare tag matches nothing. `same_style` keeps
grouping lines whose fonts both lack a name, since the resource or fallback
face `same_font` already matched is their whole identity.

Tests: a page in a font without `/BaseFont` still reads as one block (it
split into two before this change), and a bare tag matches no other name.
2026-10-20 22:28:08 +05:30
ffishmanandClaude Opus 5.5 b941402558 M9: require eContentType and at most one certificates and CRLs field
The bundle's encapContentInfo must start with its eContentType, and the
optional [0] certificates and [1] CRLs appear at most once each, in that
order (RFC 5652 §5.1; CodeRabbit on #3561).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 16:54:43 +00:00
Hamza 0f5e74101c Follow a chain of references to the tagged flag, as the inspector does
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.
2026-10-20 21:52:36 +05:00
ffishmanandClaude Opus 5.5 f96e440a47 M9: check the bundle's SignedData shape and refuse trailing bytes
A .p7b must be SignedData with version, digestAlgorithms,
encapContentInfo, optional certificates and CRLs, then signerInfos
(RFC 5652 §5.1), and nothing may follow it (CodeRabbit on #3561).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 16:46:26 +00:00
Hamza 9a997cf1b1 A rectangle is a traversal, not four corners visited
FredApps's note on #3327: five points that are all on the bounding box's
corners still need not trace the rectangle. Three sides and a step back
along the third --

    10 10 m 30 10 l 30 30 l 10 30 l 30 30 l S

-- visits every corner, so it was taken as the box and stroked with a
left side the path does not have.

path_as_rect now asks what a rectangle actually is: four distinct
points, each step from one to the next along a single axis, and the last
point back at the first (written as the fourth LineTo or as ClosePath).
That is a rectangle and nothing else is, so the corner test and its
off_corner flag are gone -- four points of an axis-aligned traversal are
necessarily the corners of their own bounding box. A degenerate path,
whose points are not distinct, now takes the general route as well,
where it painted a zero-width or zero-height rectangle before.

The vendored-patch table has the row this change was missing: without
it a re-vendor of hayro-interpret would drop the patch silently, which
is the other half of FredApps's review.
2026-10-20 21:28:05 +05:00
hcchung1andClaude Sonnet 5.5 0ca7ed07fa fix(automation): a page whose paragraph listing was dropped at the cap is refused, not read by position (#2516)
The listings kept for paragraph numbers are capped at 256 pages, and the 257th cleared them all.
A page that lost its listing fell back to reading a number as a position in the page's current
paragraphs, so after an edit regrouped it a number could rewrite another paragraph.

Documents that lose a listing to the cap are remembered, and a page of one with no listing is
refused with a message to list it again. doc_close drops a document's listings and that mark.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 00:22:07 +08:00
Combiler 4d12633cb1 Merge branch 'storytold:main' into main 2026-10-20 16:21:11 +00:00
PerchAnton 929455ad5b M3: fix multiline annotation hover bounds and paint order (#3541) 2026-10-20 19:20:56 +03:00
Aman Mehtar 5eae8d6e90 Edit text: keep a line together across subsets of the same font (#3661)
Word and other writers split one font into several subsets, each a font
resource of its own with its own six-letter tag: "Two-line" can be drawn as
/F1 (Two) /F2 (-l) /F1 (ine) with F1 and F2 both Helvetica-Bold. `text_lines`
ended a line wherever the resource name changed, so Edit text showed three
lines and Word/HTML export wrote three paragraphs -- out of order when the
subsets' boxes differ by a fraction of a point.

A run now continues a line when its `/BaseFont` names the same font as the
line's, ignoring the subset tag (ISO 32000-2 §9.6.4), as well as when it is
the same resource or another subset of our Japanese/Hebrew fallback face.
Block grouping in `text_regions` uses the same comparison, so the exporters
see one block.

Tests: the issue's two-resource case and Word's two-subset case read as one
line and one block; two different fonts still split. Before the fix the first
gave ["Hel", "lo"] and the second ["ab", "ca", "b"].
2026-10-20 21:41:00 +05:30
eliets2 c2c4db7eca redact: count shared outline items once so the walk ends
An outline whose items name shared children (/First and /Next pointing at
nodes that other items also name) was counted by following every path. With
60 levels of two items each, a walk that follows every path takes 2^60
steps. The outline count never finished, and Remove Hidden Information and
Sanitize both run that count first.

The count now keeps the set of items it has reached. An item reached a
second time is not counted again, and the level stops there: the walk that
reached the item first goes on to count the rest of that level. A total
budget of 100,000 distinct items bounds the work for an outline with very
many items. The per-level cap is unchanged.

Ordinary outlines count as before. The existing hidden-information test
(two bookmarks) passes unchanged, and a new nested outline test (five
items) pins the count for nesting and siblings.

Tests added in crates/redact/src/tests.rs:
- an_outline_whose_items_share_children_is_counted_without_spinning
- removing_bookmarks_from_an_outline_whose_items_share_children_finishes
- an_outline_counts_no_more_items_than_its_total_budget
- a_nested_outline_is_counted_item_by_item

The first, second and third were seen to fail on the code before this
change: the first two timed out after 10 seconds, and the third read 120000
where 100000 is expected. The fourth passes before and after the change and
guards the ordinary count. The spin tests run the walk on a thread with a
10 second bound, so a regression fails the test instead of hanging the run.
Reverting only the shared-item check, or only the budget check, fails the
tests they pin.

Checked with cargo fmt --all -- --check, clippy with -D warnings on
pdfcraft-redact, and cargo test for pdfcraft-redact.
2026-10-20 19:09:44 +03:00
ffishmanandClaude Opus 5.5 ddff6b64fe M9: refuse an unclosed PEM block; review follow-ups
A PEM file whose last block never ends is now an error instead of a
shorter certificate list (CodeRabbit on #3561). The test's doc comment
for the bundle builder is back above its function.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 15:42:04 +00:00
Brandon ThomasandClaude Opus 5.5 22bedbf2af Skip CodeRabbit reviews on Dependabot PRs (#3692)
Adds .coderabbit.yaml so CodeRabbit no longer reviews or comments on PRs opened by dependabot[bot].

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 11:38:59 -04:00
Brandon ThomasandClaude Opus 5.5 b77d57000c Release 0.10.1: rebuild with the shared workflow's exact Linux tarball pick
0.10.0's Arch Linux and Flatpak aarch64 jobs picked up the
pdfcraft-cli tarball (storytold/craft-common-actions#35).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.10.1
2026-10-20 11:38:32 -04:00
Dylan M. Taylor 59a5a83774 docs: add SonarCloud and CodeRabbit badges to README (#3271)
* docs: add SonarCloud and CodeRabbit badges and standardize to flat-square

* docs: put SonarCloud and CodeRabbit badges on their own line
2026-10-20 11:29:59 -04:00
Hazel Granados 604529e929 M3: let Escape close an open menu before it clears the selection (#3535)
The page's Escape handler runs in logic(), before the UI is drawn, and
ignored open popups, so one Escape closed the main menu and also cleared
the text selection. It now leaves Escape to an open popup, as
combine_ui.rs already does for its keys; the next Escape clears the
selection as before.
2026-10-20 10:28:33 -05:00
Hamza bfc7487384 Tagged PDF: resolve /MarkInfo /Marked when it is an indirect reference
Any 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.
2026-10-20 20:28:27 +05:00
Combiler beb1a70d71 Merge branch 'main' into main 2026-10-20 15:26:13 +00:00
Jayesh-Prajapati9 ebc7b6d801 M2: text extraction keeps doubled letters in text condensed to 20 Tz and below (#2457)
A glyph narrower than a twentieth of its height was matched as a fake-bold
redraw by a fifth of its height, so in OCR text layers squeezed to 20 Tz or
less the second l of "Hallo", and at 10 and 5 Tz the tt, ss, mm and ff and
letters repeated two or three places on, were dropped. Every glyph with a
width is now matched within half of it; only zero-width marks keep the
height-based distance.
2026-10-20 20:34:15 +05:30
Jayesh-Prajapati9 e9fc4cadcf M8: applying redactions drops structure-tree references to the comments and fields it removes (#2874)
Redaction removed annotations and form fields under a mark from the pages
but left the structure tree's /OBJR references to them, so a full rewrite of
a tagged PDF kept the comment's /Contents and the field's /V. apply now
drops references to every annotation no page lists afterwards (widgets of a
removed field on other pages included), as Sanitize does since #2532, and
fails closed with RedactError::Tags when an element can't be edited.
2026-10-20 20:05:01 +05:30
Hamza ab7b584811 Redaction: follow nested forms as deep as the readers do
A form past the pass's depth limit is removed rather than walked. That
is the fail-safe choice for a redaction -- marked content inside a form
we cannot read would otherwise survive -- but it also removes whatever
else that form drew, marked or not. At a limit of 12, a page with a
13-deep chain lost the text of its innermost form with nothing in it
marked.

Measured on a page that draws HEAD, a chain of forms whose innermost
draws DEEP, then TAIL, with only HEAD marked:

    chain   before            after (12)      after (50)
    1       HEAD DEEP TAIL    DEEP TAIL       DEEP TAIL
    12      HEAD DEEP TAIL    DEEP TAIL       DEEP TAIL
    13      HEAD DEEP TAIL    TAIL            DEEP TAIL
    14      HEAD DEEP TAIL    TAIL            DEEP TAIL
    50      HEAD DEEP TAIL    TAIL            DEEP TAIL
    51      HEAD TAIL         TAIL            TAIL

The limit is now 50, which is what the renderer follows
(MAX_NESTED_INTERPRETATION_DEPTH) and what Edit text's reader follows
(text::MAX_FORM_DEPTH). So anything a reader can see, this pass can
walk. The last row is why 50 and not more: at 51 the renderer does not
draw DEEP either, so removing that form costs nothing visible and the
fail-safe still holds.

Fixes #3645's data loss. The other half of that report -- that nothing
tells the user when a form is removed -- is untouched: Report.forms_removed
is counted but the engine's edit pipeline does not carry it out, and the
one channel that reaches the result (doc.edit_notices) is typed
TextNotice, for text that could not be drawn. Widening that type or
adding a channel is a design choice for the maintainers rather than
something to slip into this fix.
2026-10-20 19:25:01 +05:00
NetterSilverandClaude Haiku 5.5 b85af272c3 Accept a delimiter after EI when ending an inline image
Fixes #3578

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 14:11:33 +00:00
Eric Kaden 572e01a71d M12: the wrapping test checks the text stays within its room 2026-10-20 09:39:32 -04:00
Ozt-y 9451e0652c fix(content): recognize ~>EI terminator for ASCII85 inline images and cover filter arrays 2026-10-20 18:57:43 +05:30