Commit Graph
4058 Commits
Author SHA1 Message Date
aiongg aa1c13f71c fix(engine): list a page truncated for both forms and pictures once 2026-10-22 20:56:02 +00:00
aiongg 6394779b81 fix(annot): check a whole link batch before creating any link object 2026-10-22 20:51:55 +00:00
aiongg 923e499597 Merge upstream/main into fix/coderabbit-c5-forms-misc
Conflict in crates/content/src/lib.rs: main ends inline images where the
renderer's tokenizer ends them and counts the images whose end is a guess
(Parsed::ambiguous_images); this branch reads operators through the
streaming Ops iterator that parse collects. The iterator's BI branch takes
main's inline_image result and keeps the ambiguous-image count, which parse
copies into Parsed and Ops::ambiguous_images reports.
2026-10-22 20:51:14 +00:00
aiongg f63f509647 fix(cos): compare each section's table with a repeated hybrid stream
A section naming an /XRefStm that a newer section had already read
skipped the stream entirely, so an entry in its own table that
disagreed with the stream was no longer recorded in xref_oddities.
Signature validation reads those records to refuse an appended section
another reader could follow. The reader keeps each hybrid stream's
entries (up to MAX_XREF_OBJECTS in all) and compares every later
naming section's table with them, without decoding the stream again.
2026-10-22 20:37:04 +00:00
aiongg 0e643d45a0 Merge #4114 (fix/coderabbit-b-shared) into fix/coderabbit-c4-core
#4114 moves the bodies of Stream::decoded_within and decoded_strict into
decoded_within_typed and decoded_strict_within_typed, which return
FilterError so DecodeBudget can tell a spent limit from corrupt data.
This branch checks /Filter with readable_filters there. Kept #4114's
typed functions and made readable_filters return
FilterError::Unsupported, so a /Filter that is not a name fails both
typed decodes without charging the budget; decoded_first_filter_strict
maps it to CosError as before.
2026-10-22 20:30:25 +00:00
aiongg 60132bbfbf Merge upstream/main into fix/coderabbit-c4-core 2026-10-22 20:29:34 +00:00
Lombardi2003andClaude Opus 5.5 cf730534f0 fix(ui): "Don't show again" only on the bar that shows the security notice (#4035)
From review of #4096: when a secured form's scripts were turned off, the
bar shows that message instead of the security one, but still offered
"Don't show again", which would turn off a notice the user had not seen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 22:28:22 +02:00
Lombardi2003andClaude Opus 5.5 31828efd6e feat(ui): the secured-document notice can be turned off for good (#4035)
The notice that a document is secured came back on every open; Dismiss
lasted only for that view. Its new "Don't show again" turns it off in
every document, kept in the settings as show_secured_notice and turned
back on in Preferences (or --secured-notice). Only the notice changes:
the restrictions, the encryption and Document Properties > Security stay
as they are. The two labels are translated in all 33 catalogues, whose
ATTRIBUTION hashes and provenance notes are refreshed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 22:28:22 +02:00
aiongg 1e2ac1d85a fix(xfa): copy embedded field values out only as far as the text budget
The text layout cut each embedded value to the bytes its block had left, but
the resolver cloned the whole value first. With a byte or two left and a
value starting with a multibyte character, every later run cloned the full
value and kept none of it. The resolver is given the bytes left and returns
only a prefix that fits.
2026-10-22 20:21:21 +00:00
aiongg 2ece498ffc fix(js): charge both copies of a written XFA value before keeping either
set_value charged the snapshot's copy, changed the field, then charged the
effect's copy in push. When only the first fit, the effect was dropped while
later reads in the script saw the new value. Both copies are charged at once
before the field changes.
2026-10-22 20:21:21 +00:00
aiongg 2019726eae Merge upstream/main into fix/coderabbit-c1-js-xfa 2026-10-22 20:16:55 +00:00
Oliver HagmannandClaude Opus 5.5 60ce45f9ba Flatten comments from the page's right-click menu and the Comments panel's menu
Reviewers flatten their comments and Fill & Sign signature once done, so
nothing on the signed or reviewed pages moves any more. The command was
only in All tools and the palette; it now also ends the page's
right-click menu (with or without a comment selected, while the document
has comments) and the Comments panel's More options menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 22:08:35 +02:00
aiongg 668d8f777e fix(xfa): every drawn subform instance spends the layout budget
A positioned root draws its children instead of flowing them, so nested
repeating subforms under it multiplied their instances without spending
the measurement budget that flowed instances spend. Drawing a subform
instance spends it too.
2026-10-22 20:08:33 +00:00
aiongg a887405ea1 fix(sign): don't report valid when a DSS cap hides revocation evidence
The evidence caps counted repeated references to one stream, so a /DSS
array could list one benign stream 1024 times ahead of a revoking CRL,
and the check never reached it. Repeats are skipped before the entry
cap counts anything, and when a cap still stops an array being read in
full without a revocation found, the signature's status is unknown
rather than valid, with the reason in its details.
2026-10-22 20:07:41 +00:00
aiongg 071130d498 fix(render): list links and attachments after the comment budget fills
Reaching the 64 MiB comment budget ended the whole annotation scan, so
links and file attachments listed after it were missing, and a later
JavaScript link left the document unmarked. Past the budget, comments
are no longer built, and the scan goes on for links and attachments.
2026-10-22 20:05:05 +00:00
aiongg 02f0f7396f fix(annot): count web-address text after its UTF-8 conversion
An address of invalid UTF-8 grows up to three times when converted, so
the listing's 16 MiB cap counted fewer bytes than it kept. The cap now
counts the converted text; the raw length is still checked first, so an
address already over the cap is never converted.
2026-10-22 20:04:04 +00:00
ffishmanandClaude Opus 5.5 c829a1ad44 fix(sign): a removed annotation is not hidden behind an added signature
Review follow-up: an indirect /Annots array that drops a signed
annotation while gaining a signature widget was judged by the widget
alone, so a form-fill certification let a signed comment be deleted.
A removed comment is now a comment change, and a removed widget is a
change no permission covers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 17:03:37 -03:00
aiongg d818e4a20a Merge upstream/main into fix/coderabbit-b-caps 2026-10-22 20:03:27 +00:00
aiongg f02b998122 Format the accessibility tests 2026-10-22 19:54:10 +00:00
aiongg 9d5bcc0745 Accessibility check asks for a manual check where page content ran past the decode budget 2026-10-22 19:51:47 +00:00
aiongg 089882f372 Marking a figure decorative refuses content past the page's decode budget 2026-10-22 19:50:42 +00:00
aiongg f980b34ef3 Text and image reading charge a page's content and its forms to one decode budget 2026-10-22 19:49:44 +00:00
aiongg e2a6771641 Redaction charges a page's content streams and its forms to one decode budget 2026-10-22 19:48:33 +00:00
aiongg e0e7d4462e Cover audit charges each form entry to the page's form allowance 2026-10-22 19:46:50 +00:00
aiongg 3dbdc13b1d Image resolution walk charges each form entry to the page's form allowance 2026-10-22 19:46:11 +00:00
aiongg a72845ad80 Merge upstream/main into fix/coderabbit-b-shared
Redaction reads page content and form XObjects strictly (upstream) and
within one page's decode budget (this branch): both now go through
DecodeBudget::decode_strict.
2026-10-22 19:44:33 +00:00
Brandon ThomasandClaude Opus 5.5 39844f06b5 Release 0.12.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.12.0
2026-10-22 15:39:48 -04:00
aiongg 68c14c9ddc docs(audit): the comparison bound counts words 2026-10-22 19:27:10 +00:00
aiongg 3e16c71a51 fix(audit): count only the covered text a finding reports against the output bound
A copy-restricted document reports no text, so it no longer fails the bound on text it withholds.
2026-10-22 19:27:10 +00:00
aiongg 747fb2dfd8 fix(audit): count each word box a line has against the comparison bound
covers walks a line's word boxes, so a page with few lines and many words did more work than the line count charged.
2026-10-22 19:26:45 +00:00
aiongg 1fef4d64a9 Merge upstream/main into fix/coderabbit-a1-audit 2026-10-22 19:25:41 +00:00
aiongg 8555d6f159 fix(parity): cite the renamed renderer-check test and drop fixed redaction gaps 2026-10-22 15:17:44 -04:00
aiongg 01aa6bcc17 docs(redact): describe the overlay, text state, description and inline image rules 2026-10-22 15:17:44 -04:00
aiongg b72078871a fix(content): end inline images where the renderer ends them
Filtered inline image data has no length, so its end is an EI chosen
among candidates, and the content parser chose by a different rule
than the renderer's tokenizer (hayro-syntax): the renderer also takes an
EI with no whitespace before it, and checks a candidate by reading what
follows as operators. With image data such as "7FEI 1 0 0 1 200 0 cm",
the renderer ran a cm that redaction read as image data, so text was
drawn 200 pt from where removal and its verify pass looked.

The parser now takes each inline image's end from the renderer's
tokenizer (already in the tree through pdfcraft-cos), so both read the
same operators after the image; a test runs both on the same fixtures.
Only where that tokenizer reads no image (no EI it accepts, or
parameters it can't read) does the parser fall back to its own rules,
counting the image in Parsed::ambiguous_images, and applying redactions
refuses such a page as unreadable.
2026-10-22 15:17:44 -04:00
aiongg 7db37911ed fix(redact): clear descriptions above removed content and annotations
Structure cleanup cleared alternate text, actual text and expansions
only on the element that directly owned changed marked content, and an
element that lost an annotation reference kept its descriptions unless
it lost every kid. An ancestor's /ActualText covering a redacted span,
or a link element's /Alt repeating the address of a removed link, stayed
in the saved file. Elements that lose an annotation now always lose
their descriptions, and every ancestor of an affected element (found
through both /P and the tree's /K, which may disagree) loses its own.
An element written in place above one with a description to clear
refuses the operation, as other in-place cases already do.
2026-10-22 15:17:44 -04:00
aiongg 2cdb39503e fix(redact): compare marked content without a copy per enclosing MCID
Finding which marked content a redaction changed copied every operator
into the buffer of each MCID enclosing it, so content nested n deep was
held n times: a modest stream of nested BDCs could exhaust memory.
Each operator inside marked content is now serialized once, and each
MCID keeps the ranges its sequences span. The bytes compared are capped;
MCIDs past the cap count as changed, so their descriptions are cleared
rather than kept.
2026-10-22 15:17:44 -04:00
aiongg cccd435a53 fix(redact): bound image decoding by the declared raster size
Clearing the pixels of a partly covered image decoded its stream with
the global 1 GiB allowance and only then cut it to the raster length its
Width, Height and bit depth declare. A small Flate stream could make
every such image allocate and inflate far more than it can use. Decoding
now stops at twice the raster plus a byte per row (filter-chain and
predictor overhead) and a little slack; an image past that is removed
whole, as an undecodable one already is.
2026-10-22 15:17:44 -04:00
aiongg be64a60c87 fix(redact): unlink removed XObjects from surviving forms' resources
After applying redactions, removed images and forms were unlinked from
page-tree resource dictionaries but not from the resources of forms the
page still draws. A form sharing the page's original resource dictionary
(a common producer layout) kept the removed image reachable, so a full
save wrote it. The walk over surviving forms now drops XObject entries
for removed objects that the form's own content does not draw, and fails
closed when that cannot be determined.
2026-10-22 15:17:44 -04:00
aiongg 22aa6d5f9a fix(redact): place glyphs with the inherited and ExtGState-set text state
The redaction interpreter started every form XObject with an empty text
state (no font, size 0, no spacing) although a form inherits its caller's
graphics state, and it ignored the /Font entry of an ExtGState set by gs.
Text shown that way was placed at the wrong positions, so removal and
the verify pass could both miss glyphs under a mark. Forms now start
from the caller's state, and gs sets the font and size as Tf does.

The engine's renderer cross-check test used the gs case as its example
of interpreter/renderer disagreement; it now runs the check directly on
an unredacted page, which also covers an input stream labelled as a
redaction overlay.
2026-10-22 15:17:44 -04:00
aiongg 6ca4431b50 fix(redact): check every input content stream, whatever it calls itself
Both checks for text left under applied redaction marks (redaction's own
verify pass and the engine's renderer cross-check) skipped any page
content stream whose dictionary said /PCMark /Redaction, so that the
overlay's label would not count. An input file could put that label on
its own content stream and keep it out of both checks. Applying
redactions now records the overlay streams it adds (stamp returns the
new stream), and only those are skipped.
2026-10-22 15:17:44 -04:00
aiongg f42fb0ad49 fix(forms): a deleted field leaves the calculation order
Deleting a field took it off its pages and out of the field tree but left
its entry in the form's /CO array. That reference kept the field
dictionary, its value and its widgets reachable, so a full save after
applying redactions over the field still wrote the redacted value.
2026-10-22 15:17:44 -04:00
Combiler 118f1ae6ca Merge branch 'storytold:main' into main 2026-10-22 18:56:33 +00:00
Osvaldo DuranandClaude Sonnet 5.5 542196d907 Merge upstream main 3e968c8a into pr/attachments
Mechanical merge (ATTRIBUTION.toml): main's copy of the pinned files, hashes recomputed, both sides' catalog rows kept.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 18:55:02 +00:00
Combiler01 dea68e6350 Fixes Suggest by Reviewers 2026-10-23 00:21:21 +05:30
ffishmanandClaude Opus 5.5 4ac5165a86 fix(sign): let an LTV update declare the ESIC extension in the catalog
PAdES LTV writers add the /DSS and, in the same revision, declare the
ESIC developer extension in the catalog's /Extensions (ISO 32000-2
§7.12). Any catalog change outside a short key list was "document
structure", so the signature such an update protects showed as invalid.

/Extensions may now gain declarations after signing; one that was
signed may still not change or go.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 15:35:41 -03:00
ffishmanandClaude Opus 5.5 5856c12eb3 fix(sign): judge a page's indirect /Annots array by the annotations it gains
A page whose /Annots is an object of its own keeps its dictionary when a
later revision adds a signature widget or a comment: only the array
changes. That array fell through to "other changes", so countersigning
such a document (as many signing services do) turned every earlier
signature invalid, including PdfCraft's own second signature.

A changed array that was a signed page's /Annots is now classified like
an inline /Annots: by the annotations it gained (signature, form fill or
comments), under the same DocMDP rules.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 15:35:41 -03:00
aiongg 3e968c8aa2 chore(a11y, fonts): satisfy clippy in the CID width ranges and a test helper 2026-10-22 14:15:50 -04:00
aiongg 6b62585339 fix(optimize): bound the search for fragment runs on a page
A run of images that is not one picture was searched again from each of
its groups, collecting the rest of the run every time, so many images drawn
over one another made a page quadratic. The search reads at most 16 groups
per content operator (at least 65 536) and leaves the rest of the page as
it is once that is spent.
2026-10-22 14:15:50 -04:00
aiongg dba51fc823 fix(optimize): refuse a JPEG whose own size differs from its dictionary's
The pixel reader capped the image size the dictionary gives, then decoded
the JPEG at the size its header gives and compared afterwards, so a small
image dictionary over a huge JPEG allocated the huge one first. The reader
takes the size from the JPEG header and refuses a mismatch before decoding.
2026-10-22 14:15:50 -04:00
aiongg fcedb15689 fix(export): find font size and colour slots through a map
The body-size tally and the RTF colour table searched every slot seen so
far for each block, and the RTF writer searched the colour table again for
every paragraph and cell, so a document with many distinct sizes or colours
made export quadratic. Both keep their first-seen order and look slots up
in a hash map.
2026-10-22 14:15:50 -04:00