mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-30 04:58:34 -06:00
* M8: Retire inherited redaction resources * Redaction: pin inherited resources only on redacted pages, shared by reference localize_resources rewrote every page in the document on every apply and copied a direct inherited /Resources dictionary into each one, so a large document grew by N x the dict. Now only redacted pages that inherit resources are pinned; a direct inherited dictionary is made one indirect object and shared by reference. The parent's /Resources is retired when no other page still inherits it (the leak fix), and kept (through the shared reference) when an unredacted page still draws with it. A /Parent chain that can't be followed or disagrees with the page tree retires nothing. Tests: an unredacted page is left untouched and the rewrite is bounded; redacting every inheriting page retires the parent's resources. ROADMAP entry moved into the revision-history table. 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>
66 lines
3.6 KiB
Markdown
66 lines
3.6 KiB
Markdown
# pdfcraft-redact
|
|
|
|
Layer L4 (may use `edit`): redaction, architecture §11.2.
|
|
|
|
```rust
|
|
// Marks are Redact annotations (pdfcraft-annot: Shape::Redact { quads, overlay }).
|
|
let marks: Vec<Mark> = marks(&doc); // page, areas (user space), fill, overlay text
|
|
let report = apply(&mut doc, None)?; // everything, or Some(&[pages])
|
|
clear_marks(&mut doc, None)?; // remove marks without applying
|
|
```
|
|
|
|
`apply` removes, under every marked area:
|
|
|
|
- **text**: glyphs are cut out of `Tj`/`TJ`/`'`/`"` and replaced by a `TJ` displacement of the
|
|
same advance, so the rest of the line keeps its exact position. Widths come from `/Widths`,
|
|
`/W`/`/DW` (composite fonts, embedded CMaps with codespace and CID ranges), Type 3 font
|
|
matrices, and approximations for the standard 14. A glyph goes when it overlaps an area by
|
|
more than a fifth of its size (or a point) both ways; zero-size text goes when its origin is
|
|
inside;
|
|
- **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;
|
|
- **vectors**: covered paths are removed; partly covered paths and shadings are clipped so
|
|
nothing paints inside the areas;
|
|
- **form XObjects**: rewritten recursively into new objects (pages sharing the original keep it);
|
|
- **annotations** whose rectangle overlaps (with their pop-ups), and **form fields** with a
|
|
widget under a mark;
|
|
- the marks themselves, replaced by boxes in their fill colour (and overlay text) drawn into the
|
|
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.
|
|
|
|
Structure cleanup resolves named MCIDs and indirect content references. Form content
|
|
references are updated to the cleaned copy; unchanged sibling descriptions remain.
|
|
|
|
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.
|
|
|
|
## Remove hidden information / Sanitize
|
|
|
|
```rust
|
|
let found = sanitize::scan(&doc); // [(Hidden, count)] per category
|
|
sanitize::remove_hidden(&mut doc, &[Hidden::Comments])?; // chosen categories
|
|
sanitize::sanitize(&mut doc)?; // all of them
|
|
```
|
|
|
|
Categories: metadata (`/Info`, XMP), file attachments, comments, form fields (flattened so the
|
|
values stay visible), hidden text (render modes 3 and 7, or wholly off the page), hidden layers
|
|
(content of off optional-content groups, then the groups), bookmarks, links/actions/JavaScript,
|
|
and `/PieceInfo` private data. Both make the next save a full rewrite.
|
|
|
|
`patterns` finds phone numbers, e-mail addresses, credit card numbers (Luhn-checked), US Social
|
|
Security numbers and dates for Search & Redact.
|
|
|
|
`codes` holds the redaction code sets (U.S. FOIA and U.S. Privacy Act exemptions):
|
|
`CodeSet::from_id("foia")?.overlay(&["(b)(6)"])` gives the overlay text for the picked codes.
|
|
Custom code sets aren't supported yet.
|
|
|
|
Not yet: pattern locales, struct-tree/alt-text cleanup, metadata and hidden-information
|
|
sanitising (M8.4), re-encoding of DCT images (they are removed instead).
|