Skip to content

fix(text): avoid splitting surrogate pairs when truncating text#1160

Open
greymoth-jp wants to merge 1 commit into
ecomfe:masterfrom
greymoth-jp:fix-truncate-surrogate-pairs
Open

fix(text): avoid splitting surrogate pairs when truncating text#1160
greymoth-jp wants to merge 1 commit into
ecomfe:masterfrom
greymoth-jp:fix-truncate-surrogate-pairs

Conversation

@greymoth-jp

Copy link
Copy Markdown

What

truncateSingleLine in parseText.ts cuts a line with textLine.substr(0, subLength), where subLength is a UTF-16 code unit count from estimateLength (or from the proportional estimate on later iterations). estimateLength walks the string one code unit at a time (text.charCodeAt(i)), so subLength can land between the two halves of a surrogate pair. The slice then keeps an orphaned lead surrogate and corrupts the character.

This affects real text. Characters outside the BMP are encoded as surrogate pairs in UTF-16, including CJK Extension B ideographs such as U+20BB7 (𠮷, which appears in Japanese names) and emoji.

Repro

With a font where one fullwidth glyph is 2 units wide:

truncateText('𠮷𠮷𠮷𠮷', 6, font, '')
// before: "𠮷\uD842"  (trailing lead surrogate, renders as a broken glyph)
// after:  "𠮷"

measureCharWidth counts each surrogate half as a full wide char, so estimateLength returns 3, an odd index inside the second pair. The whole-string measure measureWidth correctly treats the pair as a single width-2 glyph, so once the orphaned result fits the content width the loop stops and the broken character is kept.

Fix

Before slicing, if subLength would cut right after a lead surrogate, step it back by one to the pair boundary. ASCII and BMP text are unaffected, and the loop still appends the ellipsis as before.

I added a unit test that stubs measureText (so no canvas is needed) and checks that the surrogate pair is preserved, that text which fits is returned unchanged, and that ASCII truncation is unchanged. The full test/ut suite passes.

@Jancheng-z

Jancheng-z commented Jun 29, 2026 via email

Copy link
Copy Markdown

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.

2 participants