diegomura / diegomura/react-pdf
@react-pdf/textkit drops source character mapping when fontkit returns a glyph with empty codePoints
- Dominant language
- TypeScript
- Stars
- 16.8k
- Forks
- 1.3k
- Avg merge
- 5h 6m
- Merged PRs (30d)
- 52
Description
## Summary
`@react-pdf/textkit@6.3.0` appears to drop glyph/source-character mapping when a font layout engine returns a glyph with `codePoints: []`.
This can happen with `fontkit@2.0.4`: if a glyph is first loaded by glyph id via `font.getGlyph(gid)`, a later `font.layout(...)` call can return that same glyph object with empty `codePoints`.
When textkit receives that glyph run, it drops the empty-codepoint glyph from `glyphs`, `stringIndices`, and `glyphIndices`.
## Reproduction
Repro repo:
https://github.com/brandon-julio-t/react-pdf-textkit-codepoints-repro
Steps:
```sh
bun install
bun run repro
```
The repro uses:
- `@react-pdf/textkit@6.3.0`
- `fontkit@2.0.4`
- a real font file from `@fontsource/roboto`
It first opens the font with fontkit, primes one glyph by glyph id, then lays out `"AB"`.
## Actual Output
```txt
fontkit glyph.codePoints after getGlyph(gid): [ [], [ 66 ] ]
Text: AB
Expected codePoints: [ 65, 66 ]
Actual textkit glyph.codePoints: [ [ 66 ] ]
Actual textkit stringIndices: [ 0 ]
Actual textkit glyphIndices: [ 0 ]
AssertionError [ERR_ASSERTION]: textkit should preserve source character mapping when fontkit returns a glyph with empty codePoints
```
The first character, `A` / code point `65`, is lost from the final textkit glyph mapping.
## Expected Behavior
Textkit should preserve source-character mapping when `font.layout(...)` returns a glyph with missing/empty `codePoints`, at least when the glyph sequence can still be aligned unambiguously against the original run string.
Expected output for the repro:
```txt
Actual textkit glyph.codePoints: [ [ 65 ], [ 66 ] ]
Actual textkit stringIndices: [ 0, 1 ]
Actual textkit glyphIndices: [ 0, 1 ]
```
## Notes
This repro does not involve PDF rendering. It isolates the behavior to textkit's handling of a fontkit glyph run.
End-to-end PDF reproduction is difficult because the symptom depends on prior fontkit glyph-cache state in a long-running process. The repro makes that state explicit by calling `font.getGlyph(gid)` before `font.layout(...)`, so the textkit behavior is deterministic.
## Production Context
We observed this in a long-running Node/Bun service that generates PDFs with `@react-pdf/renderer@4.5.1`, `@react-pdf/textkit@6.3.0`, and `fontkit@2.0.4`.
Our PDFs use `NotoSansSC-Regular.ttf` for Chinese text. The visible symptom was intermittent missing leading Chinese characters in rendered PDFs, for example the first character of an item name such as `洗水...` or `六...` disappearing from the PDF text mapping/rendered output.
The source data and React PDF input strings were intact. Regenerating after a process restart restored the missing characters, which made the issue appear random at the PDF level. The isolated repro above makes the underlying fontkit glyph-cache state deterministic.
A local patch that normalizes missing glyph `codePoints` from the original run string fixes the repro and avoids mutating fontkit's cached glyph objects.
Contributor guide
Assessment
This issue has not been assessed yet.