Skip to content

fix(audit): a cover over a TJ displacement hides no text (#1885) - #1930

Closed
mtlcl410 wants to merge 1 commit into
storytold:mainfrom
mtlcl410:fix/audit-after-redact-apply
Closed

mtlcl410 wants to merge 1 commit into
storytold:mainfrom
mtlcl410:fix/audit-after-redact-apply

Conversation

@mtlcl410

Copy link
Copy Markdown
Contributor

What this changes

Fixes #1885. The redaction audit no longer flags the program's own applied redaction. All three acceptance criteria in the issue hold.

Cause. redact_apply keeps the text left on a line in place through a TJ displacement of the removed glyphs' width: [(Call ) -6672] TJ. pdfcraft_edit::text_lines measured a text show from its origin to the end of its advance, displacement included, so the "Call " line's box ran on under the black box the redaction drew. The box covered more than 40% of that box, and the audit reported covered_text: "Call". The same happened with a leading displacement ([-6672 (is mine)] TJ, "is mine" flagged) and would with one in the middle.

Fix.

  1. crates/edit/src/text.rs: a show's box spans the glyphs it paints. A displacement before, between or after them (a tab stop, the width of text a redaction removed) paints nothing, so it is in no box. Glyph runs separated by a large displacement (the existing "reads as a space" threshold) become separate boxes, and a TextLine keeps the boxes of every show it joins (TextLine::spans). The line's rect, advance, start_x/end_x and the line-joining rules are unchanged, so editing, reflow and move/resize behave as before (the edit crate's 132 tests and the engine's text tests pass).
  2. crates/audit/src/lib.rs: covers measures the cover against those spans, not the line's whole box: the covered fraction is the overlapped span area over the total span area. Unapplied-mark findings use the same measure.

The audit's documented behaviour, "a properly applied redaction removes the text, so it never flags", is now true for the program's own output as well as for other producers' streams with the same shape (tab stops drawn as TJ gaps under a filled box).

How you verified it

  • cargo fmt --check
  • cargo clippy -p pdfcraft-edit -p pdfcraft-audit -p pdfcraft-automation -j 2 --all-targets -- -D warnings
  • cargo test -p pdfcraft-audit -j 2 (14), -p pdfcraft-edit (132), -p pdfcraft-redact (40), -p pdfcraft-automation --test automation (154), -p pdfcraft-engine --lib text (13): all pass
  • cargo test -p pdfcraft-ui-egui --test editing: 115 pass; the two that fail (files_dropped_outside_the_page_grid_still_open_as_documents, password_prompt_opens_and_security_tab_reports_the_details) fail or flake on main without this change — they are the background-open race from Desktop: prepare large PDFs off the UI thread #1493 that test(ui): extend native opening and continuation regression coverage #1764 fixes with wait_for.

The issue's own steps, with the PyMuPDF fixture and a built pdfcraft-cli:

before after
pdfcraft-cli audit m2_out.pdf {"count":1,"findings":[{"covered_text":"Call",…,"rect":[72.0,759.8,145.392,770.8]}]}, exit 1 {"count":0,"findings":[]}, exit 0
PyMuPDF text of m2_out.pdf Call Call

Tests.

  • a_cover_over_a_tj_gap_hides_no_text (audit): the one-line fixture as a content stream, in the three shapes [(Call ) -6672] TJ, [-6672 (is mine)] TJ and [(Call ) -6672 ( is mine)] TJ, each with the box over the displacement: no finding. The same text with the box over "Call" itself: one finding, covered_text: "Call" (criterion 2, together with the existing rectangle_drawn_as_a_path_covers_text_like_re).
  • an_applied_redaction_audits_clean (automation, end to end): doc_create from=text → redact_mark pattern=phone → redact_apply → doc_audit_redactions on Call 01 23 45 67 89, 01 23 45 67 89 is mine and Call 01 23 45 67 89 is mine: the number is gone from the page text and the count is 0.

Each half of the fix is needed; the tests show it:

State Result
neither change audit test fails on the trailing case; end-to-end test reports count: 1
text.rs change without splitting at inner gaps audit test fails on the inner case
text.rs change, audit measuring the whole line box audit test fails on the inner case ("Call is mine")

Never-crash

  • No new unwrap/expect/indexing outside tests; the span fold uses fold/max/min over finite operands as before
  • No unsafe

Provenance

  • No assets, no external code

Security

  • This is the verification tool for redactions. The change narrows what counts as covered to the glyphs actually painted; a cover over painted glyphs is still found (tested), and nothing else in the audit (paint order, contrast, form XObjects, fail-closed errors) changes. No paths, MCP, control channel or network involved.

Parity and reach

  • No parity change: a correctness fix in the existing audit; doc_audit_redactions and pdfcraft-cli audit share it

🤖 Generated with Claude Code

)

An applied redaction keeps the remaining text in place through a TJ
displacement of the removed glyphs' width. text_lines measured a show from
its origin to the end of its advance, displacement included, so the box the
redaction drew over that gap covered 40% of the "line" and the audit flagged
the program's own output. A show's box now spans the glyphs it paints, split
at large displacements, and a line keeps those spans; the audit measures a
cover against them, so a gap under a box hides nothing while a box over
glyphs is still found.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
@mtlcl410
mtlcl410 force-pushed the fix/audit-after-redact-apply branch from 0082452 to fc27cbc Compare October 14, 2026 04:46
@not-a-feature

Copy link
Copy Markdown
Contributor

This is still needed alongside #1949: its audit changes continue to measure line.rect, so they do not exclude the empty space introduced by a redaction's TJ displacement. Both change covers, though, so integration should combine your painted-span geometry with #1949's opaque-cover/appearance handling and narrow-cover criteria. Keep the leading, trailing and interior-gap tests, plus genuine covers over glyphs; neither PR should replace the other's checks.

@mtlcl410

Copy link
Copy Markdown
Contributor Author

Agreed. Once #1949 is merged I will rebase this on top of it and merge the two sets of checks, keeping the leading, trailing and interior-gap tests here and the opaque-cover and appearance tests there.

@mtlcl410

Copy link
Copy Markdown
Contributor Author

Closing: #1898 (merged today) fixed #1885 another way, by measuring covers against word boxes (ink). Both of this PR's tests, a_cover_over_a_tj_gap_hides_no_text and an_applied_redaction_audits_clean, pass on current main unchanged, so there's nothing left for this PR to add. #1885 can be closed against #1898.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

doc_audit_redactions / pdfcraft-cli audit flags the output of redact_apply (false positive)

2 participants