docs(redact): describe the overlay, text state, description and inline image rules

This commit is contained in:
aiongg
2026-10-22 15:17:44 -04:00
committed by Dylan M. Taylor
parent b72078871a
commit 01aa6bcc17
2 changed files with 23 additions and 10 deletions
+7 -4
View File
@@ -13,7 +13,10 @@ The parser is tolerant (unreadable bytes are skipped and counted, never fatal) a
panics. `redact` and (later) `edit` build their interpreters on it; the graphics-state tracker
and the `Patch` API of architecture §6 grow here.
Unfiltered inline images with built-in or Indexed colour spaces, and image masks, use their
sample layout (including per-row byte padding) to locate `EI`. This preserves pixel bytes
that look like the terminator. Filtered images and resource-defined colour spaces retain
the tolerant delimiter heuristic; damaged or truncated layouts fall back to it too.
An inline image ends where the renderer's tokenizer (`hayro-syntax`) ends it, so the parser
and the renderer read the same operators after the image, and redaction removes what the page
actually draws. Only where that tokenizer reads no image (its parameters don't read, or no `EI`
passes its checks) does the parser locate `EI` by its own rules: unfiltered images with built-in
or Indexed colour spaces, and image masks, by their sample layout (including per-row byte
padding), other images by the tolerant delimiter heuristic. Such an image is counted in
`Parsed::ambiguous_images`, because the operators after it may not be the ones the page draws.
+16 -6
View File
@@ -20,7 +20,8 @@ clear_marks(&mut doc, None)?; // remove marks without app
- **images**: fully covered → removed; partly covered → a copy with the covered pixels cleared
(8/16-bit and 1/2/4-bit images, image masks; Flate/LZW/RL/A85/AHx). Codecs PdfCraft can't
re-encode (DCT, JPX, JBIG2, CCITT) are removed whole (fail-closed); inline images under an
area are removed. Images with separate `/SMask` or `/Mask` data are removed whole;
area are removed. Images with separate `/SMask` or `/Mask` data, and images whose data decodes
to more than twice their declared raster (plus a byte per row and 4 KiB), are removed whole;
cleared copies drop `/Alternates` so alternate pixels cannot survive;
- **vectors**: covered paths are removed; partly covered paths and shadings are clipped so
nothing paints inside the areas;
@@ -31,8 +32,13 @@ clear_marks(&mut doc, None)?; // remove marks without app
page as a `/PCMark /Redaction` stream.
A verification pass re-reads each redacted page and fails the operation if any glyph or inline
image is still under an area. Content that can't be decoded makes the operation fail rather than
leave it unredacted. A Redact annotation with neither a usable `/QuadPoints` array nor a usable
image is still under an area. It checks every content stream the page had, whatever its
dictionary calls it; only the overlay streams this `apply` added (`Report::overlays`) are skipped.
Text is placed with the font and size set by `Tf` or by an ExtGState's `/Font` through `gs`, and a
form XObject starts from the text state of the content that draws it. Content that can't be
decoded makes the operation fail rather than leave it unredacted, and so does an inline image the
renderer's tokenizer reads no image for (`RedactError::Unreadable`): where the page's operators
resume after it would be a guess. A Redact annotation with neither a usable `/QuadPoints` array nor a usable
`/Rect` makes it fail with `RedactError::UnreadableMark` before anything changes, instead of
skipping that mark. The count covers the marks on the pages being applied.
@@ -50,15 +56,19 @@ as Sanitize drops them, so a full rewrite of a tagged PDF can't keep a removed c
A tree where that can't be done (an element written in place, or one too large to walk) makes
`apply` fail with `RedactError::Tags` rather than leave the annotation reachable.
Replacement descriptions are removed only from marked-content spans changed by cleanup.
Unchanged spans retain their descriptions; unused property entries are retired during redaction
Replacement descriptions (`/Alt`, `/ActualText`, `/E`) are removed from marked-content spans
changed by cleanup, from every structure element above them (found through both `/P` and `/K`),
and from elements that lose an annotation reference, which also lose `/T`. An element written in
place above one whose descriptions go makes `apply` fail with `RedactError::Tags`. Unchanged
spans retain their descriptions; unused property entries are retired during redaction
and hidden-content cleanup, including aliases in shared/inherited resources. Descriptions still
used by surviving pages or forms are kept.
A redacted page that inherits `/Resources` from the page tree gets them pinned (by reference;
a direct inherited dictionary is made one shared object first) before it is rewritten. The parent's
`/Resources` is then retired unless another page still inherits it, so it cannot keep a removed
XObject reachable. Pages that aren't redacted are left untouched.
XObject reachable. Surviving form XObjects lose the resource entries for removed XObjects their own
content doesn't draw, for the same reason. Pages that aren't redacted are left untouched.
## Remove hidden information / Sanitize