Skip to content

fix: keep surrogate pairs whole when NominalWordWrap hard-breaks a word [patch] - #147

Open
matt-edmondson wants to merge 2 commits into
mainfrom
fix/word-wrap-surrogate-pairs
Open

matt-edmondson wants to merge 2 commits into
mainfrom
fix/word-wrap-surrogate-pairs

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

When NominalWordWrap hard-breaks a word, it no longer cuts between the two halves of a surrogate pair. Before this, "😀😀😀".NominalWordWrap(3f, 1f) gave "😀\uD83D" and "\uDE00😀". Both lines were invalid UTF-16, rendered as replacement glyphs, and made a strict encoder throw.

Mechanism

  • If a cut would land between a high and a low surrogate, it moves back one code unit, so that line is one unit shorter.
  • At a width of 1, moving back would leave nothing, so the whole pair (two code units) goes on the line instead. The <returns> doc now mentions this, next to the existing soft-hyphen overhang note.
  • Only the hard-break loop changes. Ordinary breaks happen between whitespace-separated words and could never split a pair.

This is the minimal fix the issue suggests. It keeps surrogate pairs together but not combining sequences or ZWJ emoji, which breaking on StringInfo text elements would also keep together.

Tests

  • New: both inputs from the issue ("😀😀😀" and "ab😀cd" at width 3), and "a😀b" at width 1 for the overhang case.
  • Both tests fail with the fix reverted. With it, the full suite passes (161 tests), and dotnet build is clean on every target framework.

Fixes #142

🤖 Generated with Claude Code

https://claude.ai/code/session_01VwgZGoivWyqL4pWDsenTPm


Generated by Claude Code

…rd [patch]

The hard-break loop cut every maxCharsPerLine UTF-16 code units, so a cut
could land between the halves of a non-BMP character and leave both lines
with a lone surrogate. The cut now steps back one code unit when it would
split a pair, and at a width of one lets the pair overhang instead.

Fixes #142

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VwgZGoivWyqL4pWDsenTPm
…STEST0068)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VwgZGoivWyqL4pWDsenTPm
@sonarqubecloud

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.

NominalWordWrap hard-breaks between the halves of a surrogate pair: "😀😀😀".NominalWordWrap(3f, 1f) yields "😀\uD83D" and "\uDE00😀"

2 participants