fix(pdf): stop embedded fonts rendering as tofu and wrong glyphs - #660
Merged
Conversation
A `CFF ` Name INDEX entry holding a space or a PostScript delimiter makes the browser's OpenType Sanitizer discard the whole table, so the wrapped font never loads and the page renders tofu. PDF producers emit such names routinely (`*Microsoft Sans Serif-3111`). Patch the offending bytes in place. The replacement is the same length, so the absolute offsets the Top DICT carries stay valid and the CFF is still passed through byte for byte otherwise. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NpJEXjYDdL43zpDg3SvHTK
`reencode_to_pua` rewrites the font's cmap in place, and the `SfntFont` is shared by every page and every output of a document. `write_font_face` left the PUA cmap behind, so the next output's `glyph_for_code` found nothing in it and fell back to using the character code as a glyph id — whole runs drawn with the wrong glyphs. Route both callers through one helper that restores the cmap, as the usability probe already did. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NpJEXjYDdL43zpDg3SvHTK
Every table was copied through verbatim, including the OpenType Layout, device-metric and signature tables. One malformed table — routine in a PDF subset — makes the OpenType Sanitizer reject the whole font, and none of them earn their place: the caller positions every glyph itself, so layout never applies. Keep outlines, metrics, hinting and color strikes; drop the rest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NpJEXjYDdL43zpDg3SvHTK
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
andiwand
enabled auto-merge (squash)
August 7, 2026 22:59
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 Generated with Claude Code
odr-private/pdf/VZA_Ankauf_Kunde.pdftranslated to a page of tofu. Threeindependent defects, all in the font embedding path.
1. CFF font names the sanitizer rejects
A
CFFName INDEX entry holding a space or a PostScript delimiter makesthe browser's OpenType Sanitizer discard the whole table — the
@font-facenever loads and every glyph in it renders as tofu. PDF producers emit such
names routinely; this file carries
*Microsoft Sans Serif-3111, which cost4 of its 7 embedded fonts.
Fixed by patching the offending bytes in place. Same length, so the absolute
offsets the Top DICT carries stay valid and the CFF is otherwise still passed
through byte for byte.
2. A left-behind PUA cmap
reencode_to_puarewrites the font's cmap in place and theSfntFontisshared by every page and every output of a document.
write_font_faceneverrestored it, so the next output's
glyph_for_codemissed and fell back tousing the character code as a glyph id. In this file that turned every number,
date and amount into a run of accented letters —
20.984,99came out asÄÃÄ.ÃÄÅ,ÃÃ. Both callers now go through one helper that restores the cmap.3. Tables that only put the font at risk
SfntFont::writecopied every table through verbatim, including OpenTypeLayout, device metrics and signatures. One malformed table sinks the whole
font at the sanitizer, and none of them earn their place — the caller
positions every glyph itself, and the CSS already sets
font-kerning:none/font-variant-ligatures:none, so layout never applies. Now an allowlist:outlines, metrics, hinting and color strikes; everything else dropped.
Verification
A sweep over the whole generated corpus — every
@font-facein every outputHTML, run through
ots-sanitize:Unit tests cover the name patch (CFF still parses against its own absolute
offsets afterwards) and the table allowlist. Full
HtmlOutputTestsgreen.