Skip to content

Tolerate PDFium glyph-mask rasterization in conformance comparison - #565

Merged
Velli20 merged 1 commit into
mainfrom
conformance-glyph-rasterization
Oct 13, 2026
Merged

Velli20 merged 1 commit into
mainfrom
conformance-glyph-rasterization

Conversation

@Velli20

@Velli20 Velli20 commented Oct 13, 2026

Copy link
Copy Markdown
Owner

Problem

Almost every conformance page with text showed differences at glyph edges against PDFium (for example #479 and #480). PDFium (Skia) draws text from cached glyph masks:

  • each glyph's origin is snapped to a whole pixel vertically (the offset is constant along a line) and to a quarter pixel horizontally;
  • the masks are about 8–12% darker than the exact outline on small text.

Safe-PDF fills each glyph's exact outline at its exact position. The comparison only tolerated anti-aliasing and whole-pixel displacement, so these differences counted as mismatches and pushed text-heavy pages over the 0.2% tolerance.

Change

  • compare.rs: new Comparison::tolerate_glyph_rasterization. Inside glyph boxes it accepts a differing pixel when both images show the same hue and each image's darkness lies within the other image's 3x3 darkness range, widened by GLYPH_COVERAGE_SLACK (40%). Accepted pixels are drawn yellow and the mismatch ratio is recalculated.
  • worker.rs: when a page exceeds the tolerance, take Safe-PDF's glyph bounds (grown by 1 px) from the trace the worker already runs, apply the tolerance, then decide pass or fail. Passing pages do no extra work.
  • README.md: new "Text rasterization" section and an updated diff legend.

The tolerance applies only inside glyph bounds. Applying it to the whole page also cleared real color-shift and missing-ink failures.

Results (cargo conformance repro, all cases)

Issue Failing pages before → after
#479 64 → 44
#480 53 → 23
#528, #481, #493 (real bugs, controls) unchanged (1, 36, 4)
#485 2 → 1
#490 6 → 5

The pages that flipped in #485 and #490 had mostly text differences. Their other differences still count but now total less than 0.2% of the page. The #479 pages that still fail are real bugs, for example wrong glyphs on the FRC_8.2.4 pages.

Testing

  • cargo fmt --check and cargo clippy --all --workspace are clean.
  • cargo test -p pdf-conformance --profile regression passes (21 tests).
  • The full cargo test --workspace didn't run locally because its Skia build had to be downloaded, so CI covers it.

🤖 Generated with Claude Code

PDFium draws text from cached glyph masks: it snaps each glyph's origin
to a whole pixel vertically and a quarter pixel horizontally, and its
masks cover more than the exact outline on small text. Safe-PDF fills
exact outlines at exact positions, so glyph edges differed on nearly
every text page and text-heavy pages crossed the mismatch tolerance.

When a page exceeds the tolerance, accept same-hue differences inside
Safe-PDF's glyph bounds whose darkness fits the other image's 3x3
neighbourhood range widened by 40%, then decide pass or fail again.
Document the behavior in the conformance README.

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

Copy link
Copy Markdown

Conformance reads

Documents that Safe-PDF reads differently from the base commit, compared with PDFium. A read
regresses when Safe-PDF fails to open a document it opened before, crashes or times out, or
counts a different number of pages than PDFium.

pdf.js corpus

1008 documents compared with 2dab801: 0 read worse, 1 read better.

Improvements

Document Base This pull request
issue19517 timeout: time limit exceeded (stage safe-read) read, 1 page

840 documents of the PDFium corpus read as on 2dab801.

Details are in the job summaries of the workflow run. Rerun a case locally with
cargo conformance run --corpus <corpus> --read-only --case <id>.

@Velli20
Velli20 merged commit b469ecf into main Oct 13, 2026
10 checks passed
@Velli20
Velli20 deleted the conformance-glyph-rasterization branch October 13, 2026 18:08
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