Skip to content

feat(preflight): judge white overprint and transparency interaction on the compositor (#119) - #160

Open
mberrys wants to merge 6 commits into
devfrom
cc/issue-119-white-overprint-renderer
Open

mberrys wants to merge 6 commits into
devfrom
cc/issue-119-white-overprint-renderer

Conversation

@mberrys

@mberrys mberrys commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Closes #119 (L01-14). white-overprint and transparency-risk now also consult the overprint-accurate compositor that Output Preview uses (PDFTransparencyRenderer, preflight-analysis policy), not only page-view overprint state.

  • The compositor counts the overprint it actually applied (PDFOverprintObservation: overprinted pixels, paper-white overprinted pixels, overprinted pixels under a non-Normal blend mode, constant alpha below one or a knockout group). A new PDFOverprintProbe renders a page and returns that observation.
  • Only pages that declare overprint (/OP or /op in an ExtGState, including Form XObjects) are rasterized, once per run, at probe_dpi.
  • white-overprint adds a finding for paper-white pixels overprinted through paint the page-view scan does not inspect (images, shadings, transparency results). transparency-risk adds transparency-overprint-interaction for overprint composited with transparency. A page the compositor cannot judge (budget, render failure, non-exact overprint fidelity) reports an informational check-incomplete.

Proof

Fixtures white-overprint-image.pdf (white CMYK image, op true, OPM 1, over cyan) and transparency-overprint-knockout.pdf (black, op, OPM 1, alpha 0.5 in a knockout CMYK group) were committed first (generator loop-preflight/tools/generate_white_overprint_compositor_fixture.py).

Fixture Before After
white-overprint-image pass: true, 0 warnings 1 white-overprint warning, source compositor, 7056 white overprinted pixels
transparency-overprint-knockout pass: true, 0 warnings 1 transparency-overprint-interaction warning

Baseline PdfTool was built from the #118 head, which does not touch overprint code. Local (Windows, MSVC Release): UnitTestsPreflightEngine, UnitTestsPreflightCorpus, UnitTestsPreflightVerdict, UnitTestsPreflightChecks, UnitTestsEvidenceGraph, UnitTestsContentProcessorLimits and UnitTestsOverprintRender (renderer goldens) all exit 0. Regenerating the corpus snapshots produced only the two new files, so the existing goldens are unchanged. generate-architecture-catalogs.py --check, check_source_integrity.py, check_preflight_truth_source.py and check-change.py --dry-run pass. New tests cover both fixtures, a plain overprint page staying clean for transparency-risk, a page with no overprint not being rasterized, and the probe reporting a too-small budget as unrendered.

An early version also counted antialiased edge coverage as transparency and changed five existing overprint goldens; I removed that criterion rather than re-baselining them.

Not run locally

Full check-change build/ctest/clang-tidy (no clang-tidy-18), Linux lanes, and the remaining mapped test targets (including UnitTestsPageSurface); left to CI.

Remaining risk

  • The compositor probe sees paint of at least one pixel at probe_dpi and not annotation appearances; only pages that declare overprint are rasterized.
  • Page-view findings are kept as before; where the compositor disagrees (for example white overprint that OPM 1 makes vanish) the page-view finding still reports, it is not suppressed.
  • Pages that declare overprint now cost one extra raster per run.
  • The RIP's own result is not simulated; the check reports the interaction, not the outcome.

Handoff

Changed: pdftransparencyrenderer.* (observation counters), pdfinkcoverageprobe.* (PDFOverprintProbe), preflightengine.* (declaration scan, probe cache, two evaluators), tests, fixtures and generator, two snapshots, overlay (both rows landed, closed_by their check ids), regenerated catalogs, README, change record and evidence. Anti-slop review: no new profile parameters, the compositor is observed rather than modified, and uncertainty is reported as check-incomplete instead of guessed.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

mberrys and others added 5 commits September 30, 2026 17:59
…rency regression fixtures (#119)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…n the compositor (#119)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…#119)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…nteraction, add tests and snapshots (#119)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…robe (#119)

pdftransparencyrenderer.cpp already violated the format gate on dev; the gate
checks whole touched files, so it is formatted here without other changes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Comment thread LoopLibCore/sources/preflightengine.cpp Outdated
}

PDFOverprintProbe probe(session);
return &(cache[int(pageIndex)] = probe.probe(page, check.probeDpi, check.maxRasterPixels));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The probe cache ignores per-check probe_dpi and max_raster_pixels. The cache is keyed by page index only, so the second of white-overprint and transparency-risk reuses the first one's raster. It then reports its own probe_dpi in the evidence, which is not the resolution actually used. Key the cache by (page, dpi, budget).

/// True when the page, or a Form XObject it paints, declares overprint (/OP or /op true) in an
/// ExtGState. Only such pages are rasterized on the compositor, which keeps the cost off
/// the common case.
bool pageDeclaresOverprint(const PDFDocument* document, const PDFPage* page)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The overprint gate misses some resources. Only page and Form XObject ExtGStates are scanned. Overprint declared in pattern /Resources, soft-mask group (/SMask /G) resources or annotation appearance streams, or nested deeper than 8 levels, returns false. Those pages then get neither a compositor verdict nor a check-incomplete finding, so they pass clean.

{
}

PDFOverprintProbeResult PDFOverprintProbe::probe(const PDFPage* page, int dpi, qint64 maxRasterPixels)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This duplicates PDFInkCoverageProbe::probe. About 50 lines are copied: box resolution, size and budget check, renderer settings, ink mapper and renderer construction. A shared "render page on the compositor" helper would keep the two probes from drifting apart. evaluateWhiteOverprintOnCompositor and evaluateTransparencyOverprintOnCompositor are also near-identical loops.

}

PDFTransparencyRendererSettings rendererSettings;
rendererSettings.flags.setFlag(PDFTransparencyRendererSettings::SaveOriginalProcessImage, true);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Unused bitmap copy. SaveOriginalProcessImage makes the renderer keep a full-page float bitmap copy, but this probe only reads getOverprintObservation(). Drop the flag.

The probe cache was keyed by page only, so the second check reused the
first check's raster. The overprint gate now also scans patterns,
soft-mask groups and Type 3 fonts, and treats overly deep nesting as
declaring overprint. Both compositor probes share raster setup, and the
overprint probe no longer keeps an unused process bitmap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant