Skip to content

Stop NominalWordWrap wrapping one character early at exact widths [patch] - #173

Merged
matt-edmondson merged 2 commits into
mainfrom
fix/171-wordwrap-exact-multiple
Oct 18, 2026
Merged

matt-edmondson merged 2 commits into
mainfrom
fix/171-wordwrap-exact-multiple

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #171

Summary

Before: NominalWordWrap computed its line length as Math.Floor((double)wrapWidth / nominalGlyphWidth). Glyph widths such as 0.7f are not exact in binary, so a width of exactly n glyphs often divides to n - ε and floors to n - 1. For example, "hello world".NominalWordWrap(11 * 0.7f, 0.7f) returned two lines. About 45% of exact multiples came out one short, whatever glyph width I tried.

After: I took the issue's suggested fix and add a relative tolerance before flooring: Math.Floor(raw + raw * 1e-6). The int.MaxValue clamp is unchanged, and the result for infinity is still infinity.

Tests

  • NominalWordWrapFitsExactlyNGlyphsInAWidthOfNGlyphs is data-driven over g ∈ {0.1, 0.7, 6.6, 7.2, 13.3}. For n = 1 to 200 it checks that a width of n * g gives a first line of exactly n characters.
  • NominalWordWrapKeepsAWordThatExactlyFillsTheWidthOnOneLine is the issue's "hello world" repro.
  • NominalWordWrapDoesNotRoundUpAWidthHalfAGlyphShort checks that a width of (n + 0.5) * g still holds n, so the tolerance never rounds up a width that is genuinely short.
  • With the StringExtensions.cs change reverted, all five glyph-width rows and the repro test fail. With it in place, the full Extensions.Test suite passes (206/206), including the existing NaN, infinity and clamp tests.

Generated by Claude Code

The line length is floor(wrapWidth / glyphWidth) in double. Glyph widths
such as 0.7f are not exact in binary, so a width of exactly n glyphs
often divides to n - ε and floors to n - 1: "hello world" at 11 × 0.7
wrapped onto two lines. About 45% of exact multiples were one short for
every glyph width tried.

A relative tolerance of 1e-6 is added before flooring. It absorbs the
representation error without rounding up a width that is half a glyph
short. The int.MaxValue clamp is unchanged.

Fixes #171

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

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit b326786 into main Oct 18, 2026
15 checks passed
@matt-edmondson
matt-edmondson deleted the fix/171-wordwrap-exact-multiple branch October 18, 2026 10:52
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 wraps one character early when wrapWidth is an exact multiple of the glyph width (regression from #146)

2 participants