Skip to content

fix(layout): strip default-ignorable characters instead of rendering fallback glyphs - #3457

Draft
hendrikmolder wants to merge 1 commit into
diegomura:masterfrom
hendrikmolder:layout/strip-default-ignorable-chars
Draft

fix(layout): strip default-ignorable characters instead of rendering fallback glyphs#3457
hendrikmolder wants to merge 1 commit into
diegomura:masterfrom
hendrikmolder:layout/strip-default-ignorable-chars

Conversation

@hendrikmolder

Copy link
Copy Markdown
Contributor

Fixes rendering of Unicode Default_Ignorable_Code_Points (bidi controls, zero-width chars, BOM, variation selectors, …) which currently show up as visible fallback .notdef glyphs in generated PDFs.

Problem

Characters like U+202A (LRE), U+202C (PDF), U+200F (RLM), zero-width spaces/joiners, and the BOM are default-ignorable — browsers render nothing for them. In react-pdf they appear as garbage, typically a stray glyph overprinted on the first letter of the following text (plus a swallowed adjacent space).

This is common with text (e.g. names) for RTL locales, which often arrive wrapped in bidi embedding controls. Example: "\u202aExample Name\u202c\u200f" renders with a stray glyph stacked on the first letter.

Root cause

packages/layout/src/text/ignoreChars.ts only stripped a 3-entry list (U+2028, U+2029, U+2060). All other default-ignorables reached font substitution, missed the font cmap, and were drawn as fallback glyphs. fontkit already handles these correctly via isDefaultIgnorable / hideDefaultIgnorables, but that path is not reached because react-pdf performs its own per-codepoint font substitution first.

Changes

  • Strip characters the fragment font cannot render when they are Unicode Default_Ignorable_Code_Points, mirroring fontkit's isDefaultIgnorable ranges.
  • Preserve existing U+2028 / U+2029 separator stripping (kept via an explicit extra set) to avoid a regression.
  • Characters the font does provide a glyph for are left untouched.
  • Adds unit tests in packages/layout/tests/text/ignoreChars.test.ts and a changeset.

Testing

yarn vitest run packages/layout — all 417 layout tests pass (incl. 6 new). Lint + typecheck clean.

…fallback glyphs

Bidi controls and other Unicode Default_Ignorable_Code_Points (e.g. U+202A
LRE, U+202C PDF, U+200F RLM, zero-width chars, BOM) were not in the small
IGNORABLE_CODEPOINTS list, so they reached glyph mapping, missed the font's
cmap and were drawn as fallback .notdef glyphs (visible garbage, often
overprinted on an adjacent letter).

Strip characters the font cannot render using the Unicode default-ignorable
set (mirroring fontkit's isDefaultIgnorable), while preserving the previously
handled U+2028/U+2029 separators. Characters the font does provide a glyph for
are left untouched.
@changeset-bot

changeset-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e2ef038

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

This PR includes changesets to release 7 packages
Name Type
@react-pdf/layout Patch
@react-pdf/renderer Patch
@react-pdf/math Patch
@react-pdf/mermaid Patch
next-14 Patch
next-15 Patch
@react-pdf/vite-example 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

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