Skip to content

fix(text): keep unique /PlacedPDF bodies instead of suppressing them - #896

Merged
yfedoseev merged 2 commits into
yfedoseev:mainfrom
ajbufort:fix/placed-pdf-dedup
Jul 20, 2026
Merged

fix(text): keep unique /PlacedPDF bodies instead of suppressing them#896
yfedoseev merged 2 commits into
yfedoseev:mainfrom
ajbufort:fix/placed-pdf-dedup

Conversation

@ajbufort

Copy link
Copy Markdown
Contributor

Problem

InDesign wraps placed artwork in /PlacedPDF marked content, and the text extractor suppresses that text to drop decorative overlays and duplicated draft galleys (the PMC8100493 case). The keep/suppress decision in placed_pdf_text_dominates used only a character-count ratio:

placed_chars >= 800 && other_chars.saturating_mul(3) < placed_chars

i.e. keep placed text only when it is substantial and the non-placed text is a small (< 1/3) minority of it.

That heuristic misfires on spreads where the publisher places the entire page body inside /PlacedPDF (a floor-plan / marketing PDF: room labels, dimensions, disclaimer) but a comparable amount of unique text also sits outside the region (headers, callouts). The 3:1 dominance test fails, so the whole page body is suppressed - extract_text / extract_spans return nothing, while pdftotext and PyMuPDF extract the full page.

Concretely, on a 6-page InDesign floor-plan spread, two whole pages returned 0 spans where poppler returns 768 and 825 words. extract_chars already recovered ~3400 chars on those pages, so the loss was purely in this suppression gate.

Fix

The char-count ratio is a crude proxy for the real signal, which is duplication: a draft-galley overlay repeats the surrounding text, whereas a placed page body is mostly unique. This keeps the two cheap gates and replaces the ratio's second job with an actual duplication check:

  1. placed_chars < 800 -> suppress (decorative figure; unchanged).
  2. placed text dominates the page (other*3 < placed) -> keep (whole-body-placed, MATEC; unchanged).
  3. otherwise -> keep unless a majority (>= 50%) of the placed word tokens also appear in the non-placed text (a duplicate overlay).

Gate 3's tokenisation runs only after gates 1 and 2, so the common single-column path stays allocation-free.

Measured

On a 468-doc consensus content-F1 sample: 3 docs recovered (a placed floor-plan 0.51 -> 0.99, another 0.89 -> 1.00, a third +0.01), no regressions, mean +0.0013. The change only ever affects pages carrying the /PlacedPDF tag.

Tests

  • test_placed_pdf_kept_when_unique_body_amid_comparable_outside - the recovered case.
  • test_placed_pdf_suppressed_when_large_duplicate_overlay - a full-size placed duplicate still suppressed.
  • Existing placed_pdf tests (MATEC keep, minority-overlay suppress, no-op without tag) unchanged and passing.

Full gate on this branch off main: cargo test --lib (5744 passed), cargo test --doc (139 passed), cargo clippy --lib clean, cargo fmt --check clean.

InDesign wraps placed artwork in /PlacedPDF marked content, and pdf_oxide
suppresses that text to drop decorative overlays and duplicated draft
galleys (PMC8100493). The keep/suppress decision used only a char-count
ratio: keep when placed text is >=800 chars AND non-placed text is < 1/3
of it. On spreads where the whole page body is placed (a floor-plan /
marketing PDF: room labels, dimensions, disclaimer) but a comparable
amount of unique text also sits outside the region, the 3:1 test fails
and the ENTIRE page body is suppressed - extract_text/extract_spans
return nothing while pdftotext/pymupdf extract the full page.

The ratio is a crude proxy for the real signal, which is duplication: a
draft-galley overlay REPEATS the surrounding text, whereas a placed page
body is mostly unique. Replace the second half of the test with an actual
duplication check (gate 3): once placed text clears the size gate but
does not dominate, keep it unless a majority of its word tokens also
appear in the non-placed text. Whole-body-placed (MATEC) still keeps via
the dominance gate; minority/duplicate overlays still suppress.

Measured on a 468-doc consensus content-F1 sample: 3 docs recovered
(one placed floor-plan 0.51 -> 0.99, another 0.89 -> 1.00), no
regressions; mean +0.0013. Two unit tests added for the gate-3 branches.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Anthony J. (Tony) Bufort <ajbufort@ajbconsulting.us>

@yfedoseev yfedoseev left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — reviewed and regression-passed; in the merge queue. (Formal approval to supersede the earlier review state and record sign-off.)

@ajbufort
ajbufort marked this pull request as ready for review July 20, 2026 08:05
@yfedoseev
yfedoseev merged commit 92a77f0 into yfedoseev:main Jul 20, 2026
232 checks passed
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.

2 participants