Files
pdfcraft/crates/audit
56ef1f1c4e fix(audit): an applied redaction at the end of a line is not a faux redaction (#1898)
* fix(audit): an applied redaction at the end of a line is not a faux redaction

After Apply Redactions removes text at the end of a line, the line keeps a
TJ adjustment where the text was, and the box of the line still spans it.
The audit compared the redaction box with that box: at the end of a line
it covered more than 40% of it and the words left beside it were reported
as covered text, on a correctly redacted file. In the middle of a line the
gap was usually too small to cross the threshold.

Text lines now also give the boxes of the words they draw (`ink`: glyphs,
without spaces and TJ adjustments), and the audit measures how much of
those words a cover hides. A real cover over text that is still there is
found as before.

doc_audit_redactions also reports its rectangles in top-left-origin points
like comment_list and doc_info; they were in PDF user space.

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

* fix(audit): leave the rectangles of doc_audit_redactions in user space here

Reporting doc_audit_redactions rectangles in top-left-origin points is a
separate subject from the false alarm this branch fixes, and on its own it
made the tool and `pdfcraft-cli audit` disagree on the same file: the CLI
still prints PDF user space. Take it out of this change; the tool and the
CLI can move together in their own change.

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

* fix(edit): word boxes span whole words when glyphs run backwards

With a negative font size or a negative horizontal scale (Tz), each glyph
advances backwards along the text matrix, and the box of a word kept only
its first glyph: the end of the word was compared with a start that had
already moved past it. A word box is now the union of its glyphs whichever
way they go, and a TJ adjustment ends a word past 0.2 em of the scaled size,
whatever its sign.

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

* fix(audit): a cover over a short value after a long label is still found

Measuring a cover on the words of a line alone missed faux redactions main
found: a long label followed by a short value hidden under the box is less
than 40% of the line's words. A cover now also counts, as before, when it
hides 40% of the line's box and at least half of one word still drawn; a
cover over the gap an applied redaction left, with no word under it, still
does not. The audit README states both rules.

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

* fix(audit): retain partial-value cover detections

Co-Authored-By: OpenAI Codex <noreply@openai.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: OpenAI Codex <noreply@openai.com>
2026-10-15 02:27:55 -04:00
..

pdfcraft-audit

Layer L4 (may use edit): verification tooling, the faux-redaction audit.

let findings: Vec<Finding> = audit_redactions(&doc)?;  // page, kind, rect, covered_text

It reports two kinds of finding:

  • faux redaction: an opaque filled rectangle painted after text it overlaps, dark enough against the text's colour (WCAG contrast below 3) to hide it, while the text is still extractable underneath;
  • unapplied mark: a Redact annotation that was marked but never applied.

A properly applied redaction removes the text, so it never flags. Page backgrounds, table shading and highlights don't either: they're painted before the text, or light against it.

A rectangle covers a text line when it hides at least 40% of the area of the words the line draws, or at least 40% of the line's box together with a positive-area intersection with one of its words. The gap an applied redaction leaves (a TJ adjustment where the text was) is in the line's box but holds no word, so a redaction box over it alone does not count.

Limits, by design and documented rather than hidden:

  • only filled rectangles count as covers, drawn with re or as a single m/l subpath whose corners form an axis-aligned rectangle (not other paths, shadings or patterns), and a translucent one (/ca below 1) is not flagged;
  • covers inside form XObjects are found, but only text drawn directly in the page content is checked under them;
  • annotation rectangles are compared in unrotated user space, so /Rotate pages may misalign;
  • a cover that hides less than 40% of a line's box and less than 40% of its words (a short value in the middle of a long line) is not flagged.

It fails closed: an undecodable content stream, malformed /Annots, form XObjects nested deeper than 32 or in a cycle, or a walk past the operator bound is an error, never "no findings".

Surfaces: the redact_audit automation tool and pdfcraft-cli.