Skip to content

fix(textkit): skip hyphen insertion for CJK characters in line breaking - #3315

Open
matangot wants to merge 3 commits into
diegomura:masterfrom
matangot:fix/cjk-line-break-hyphenation
Open

fix(textkit): skip hyphen insertion for CJK characters in line breaking#3315
matangot wants to merge 3 commits into
diegomura:masterfrom
matangot:fix/cjk-line-break-hyphenation

Conversation

@matangot

@matangot matangot commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

Summary

CJK (Chinese/Japanese) text gets incorrectly hyphenated with - dashes at line breaks. This happens because breakLines unconditionally inserts a hyphen glyph when breaking at a penalty node, which is correct for Latin scripts but wrong for CJK text where characters wrap naturally without hyphens.

Before

image

After

image

The Problem

CJK text has no spaces between characters, so the entire text block becomes a single "word" in the layout engine. When a user's hyphenationCallback splits this into individual characters (the correct approach for CJK), breakLines inserts a - hyphen at every line break point — because it assumes all penalty-node breaks are Latin-style hyphenation.

The Fix

In breakLines, check whether the character at the break point is in the CJK Unicode range. If so, skip the hyphen glyph insertion.

const charAtBreak = attributedString.string.charAt(end - 1);
if (!CJK_RANGE.test(charAtBreak)) {
  line = insertGlyph(line.string.length, HYPHEN, line);
}

Where CJK_RANGE = /[\u3000-\u9fff\uf900-\ufaff\uff00-\uffef]/

This covers:

  • Japanese: Hiragana, Katakana, Kanji
  • Chinese: CJK Unified Ideographs
  • CJK punctuation and fullwidth forms

Korean (Hangul) is excluded as it uses spaces between words and doesn't hit this code path in practice.

Notes

A complete CJK line breaking solution would also include kinsoku (禁則処理) rules in the layout engine — preventing punctuation like from starting a new line. That's currently left to userland via hyphenationCallback but could be a follow-up enhancement.

CJK (Chinese/Japanese) text gets incorrectly hyphenated with `-` dashes
at line breaks. breakLines unconditionally inserts a hyphen glyph when
breaking at a penalty node, which is correct for Latin scripts but wrong
for CJK text where characters wrap naturally without hyphens.

Check whether the character at the break point is in the CJK Unicode
range and skip hyphen insertion if so.
@changeset-bot

changeset-bot Bot commented Mar 26, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2322daa

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

This PR includes changesets to release 9 packages
Name Type
@react-pdf/textkit Patch
@react-pdf/layout Patch
@react-pdf/render 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

Adds a changeset and a paired test: a CJK string that wraps keeps no
hyphen at the break, while a latin string under the same width and helper
still gets one, so the guard is shown to be selective rather than blanket.
Without the fix the CJK case reports ['日本語-', '組版-', '処理'].
@matangot

Copy link
Copy Markdown
Contributor Author

Rebased-by-merge onto current master and added the two things this was missing: a changeset and test coverage.

The test is a pair, so the guard is shown to be selective rather than blanket — same helper, same width, only the script differs:

  • CJK string wrapping → no hyphen at the break
  • latin string wrapping → hyphen still inserted

Without the fix the CJK case reports ['日本語-', '組版-', '処理']. Full textkit suite: 789 tests across 92 files passing.

On overlap with the other line-breaking work in flight (#3288 CSS-compatible wordBreak/hyphens/hyphenateCharacter, #3268 hyphenless word breaking): I think this sits at a different layer and is complementary rather than superseded. Those expose author-facing control over whether hyphenation applies; this is a correctness fix for what happens when it does — CJK has no hyphenation rules to opt into, so inserting U+002D mid-word is wrong regardless of how hyphens ends up being configured. Happy to rework it on top of either if you'd rather land the general API first.

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