Skip to content

fix(font): keep glyph code points across documents - #3601

Open
iliev-blnk wants to merge 1 commit into
diegomura:masterfrom
iliev-blnk:fix/font-glyph-code-points
Open

iliev-blnk wants to merge 1 commit into
diegomura:masterfrom
iliev-blnk:fix/font-glyph-code-points

Conversation

@iliev-blnk

Copy link
Copy Markdown

Fixes #3589

Cause

fontkit caches glyph objects by id, with the code points of the first lookup (getGlyph(id, characters = [])). When a document ends, pdfkit embeds the font subset, and TTFSubset._addGlyph looks up the components of composite glyphs by id only. So after a document that used Ö (Roboto: O + diaeresis), the O glyph on the shared, cached font instance has codePoints: [], and every later font.layout('…O…') returns it that way.

Since #3357 and #3358, textkit resolves string and glyph indices from glyph.codePoints, so later documents lose glyphs. In the repro below, bold WO renders as nothing. encodeGlyphs also writes those empty code points to the ToUnicode map (the <> entries mentioned in the issue). Rendering the same document first, or in a fresh process, hides the problem, because the subset is only encoded at the end of a document.

Fix

FontSource wraps getGlyph on the fontkit fonts it loads: if a cached glyph has no code points and the current lookup provides them, they're filled in. Glyphs that already have code points are left alone.

Repro

Font.register({ family: 'Roboto', fonts: [
  { src: 'Roboto-Regular.ttf', fontWeight: 400 },
  { src: 'Roboto-Bold.ttf', fontWeight: 700 },
]});
const A = <Document><Page><Text style={{ fontFamily: 'Roboto', fontWeight: 700 }}>über ÄÖÜ</Text></Page></Document>;
const B = <Document><Page><Text style={{ fontFamily: 'Roboto', fontWeight: 700 }}>WO</Text></Page></Document>;

await renderToBuffer(A);
await renderToBuffer(B); // master: the bold run is a single glyph and "WO" isn't drawn

With this change, B rendered after other documents is pixel-identical to B rendered alone, and its ToUnicode map has no empty entries.

Tests

  • packages/font/tests/glyph-code-points.test.ts subsets a composite glyph and then lays out WO. It fails on master and passes with the fix.
  • vitest run: 248 files, 3667 tests passing.

fontkit caches glyphs by id with the code points of their first lookup.
Embedding a font subset looks up the components of composite glyphs by
id only, so after a document that used e.g. Ö, the O glyph has empty
code points for every later layout of the shared font instance. textkit
resolves string and glyph indices from those code points, which drops
glyphs from later documents and leaves empty ToUnicode entries.

Fill in the code points on the next lookup that provides them.

Fixes diegomura#3589
@vercel

vercel Bot commented Oct 22, 2026

Copy link
Copy Markdown

@iliev-blnk is attempting to deploy a commit to the diegomura Team on Vercel.

A member of the Team first needs to authorize it.

@changeset-bot

changeset-bot Bot commented Oct 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ad16843

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
@react-pdf/font Patch
@react-pdf/renderer Patch
@react-pdf/types Patch
@react-pdf/math Patch
@react-pdf/mermaid Patch
@react-pdf/ui Patch
@react-pdf/examples Patch
@react-pdf/layout Patch
@react-pdf/render Patch
@react-pdf/stylesheet Patch
@react-pdf/tailwind Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

This branch has not been deployed

No deployments
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.

4.9: glyphs missing from rendered PDF when multiple documents are rendered in one process (regression from 4.3)

1 participant