Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions Extensions.Test/StringExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,44 @@ public void NominalWordWrapClampsToAtLeastOneCharacterPerLine()
CollectionAssert.AreEqual(new List<string> { "a", "b" }, result);
}

[TestMethod]
[DataRow(0.1f)]
[DataRow(0.7f)]
[DataRow(6.6f)]
[DataRow(7.2f)]
[DataRow(13.3f)]
public void NominalWordWrapFitsExactlyNGlyphsInAWidthOfNGlyphs(float nominalGlyphWidth)
{
string input = new('a', 201);

for (int n = 1; n <= 200; n++)
{
string firstLine = input.NominalWordWrap(n * nominalGlyphWidth, nominalGlyphWidth).First();
Assert.AreEqual(n, firstLine.Length, $"A width of {n} glyphs of {nominalGlyphWidth} should hold {n} characters.");
}
}

[TestMethod]
public void NominalWordWrapKeepsAWordThatExactlyFillsTheWidthOnOneLine()
{
List<string> result = [.. "hello world".NominalWordWrap(11 * 0.7f, 0.7f)];
Assert.AreSequenceEqual(new List<string> { "hello world" }, result);
}

[TestMethod]
[DataRow(0.7f)]
[DataRow(6.6f)]
public void NominalWordWrapDoesNotRoundUpAWidthHalfAGlyphShort(float nominalGlyphWidth)
{
string input = new('a', 201);

for (int n = 1; n < 200; n++)
{
string firstLine = input.NominalWordWrap((n + 0.5f) * nominalGlyphWidth, nominalGlyphWidth).First();
Assert.AreEqual(n, firstLine.Length, $"A width of {n + 0.5f} glyphs of {nominalGlyphWidth} should hold {n} characters.");
}
}

[TestMethod]
public void NominalWordWrapHardBreakDoesNotSplitSurrogatePairs()
{
Expand Down
7 changes: 6 additions & 1 deletion Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@
#endif
}

private static Regex LineEndingRegexUnix { get; } = new(@"(?<!\r)\n", RegexOptions.Compiled);

Check warning on line 125 in Extensions/StringExtensions.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Pass a timeout to limit the execution time.

Check warning on line 125 in Extensions/StringExtensions.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Pass a timeout to limit the execution time.
private static Regex LineEndingRegexWindows { get; } = new(@"\r\n", RegexOptions.Compiled);

Check warning on line 126 in Extensions/StringExtensions.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Pass a timeout to limit the execution time.
private static Regex LineEndingRegexMac { get; } = new(@"\r(?!\n)", RegexOptions.Compiled);

Check warning on line 127 in Extensions/StringExtensions.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Pass a timeout to limit the execution time.

// One pass over every kind of line ending. Normalizing in several passes lets an earlier pass
// build a sequence the next one reads differently: under Mac, "\r\n\n" became "\r" + "\n", a new CRLF.
Expand Down Expand Up @@ -253,9 +253,14 @@
return [];
}

// A width of exactly n glyphs often divides to n - ε, because values like 0.7f are not exact in binary, so
// flooring the raw quotient wraps one character early. A small relative tolerance absorbs that error without
// rounding up a width that falls genuinely short of the next glyph.
double raw = (double)wrapWidth / nominalGlyphWidth;

// Clamp before casting: float-to-int conversion does not saturate before .NET 9, so an infinite or huge ratio
// would otherwise become int.MinValue and wrap one character per line.
double ratio = Math.Floor((double)wrapWidth / nominalGlyphWidth);
double ratio = Math.Floor(raw + (raw * 1e-6));
int maxCharsPerLine = ratio >= int.MaxValue ? int.MaxValue : Math.Max(1, (int)ratio);
return NominalWordWrapIterator(text, maxCharsPerLine);
}
Expand All @@ -265,7 +270,7 @@
/// </summary>
private const char SoftHyphen = '­';

private static IEnumerable<string> NominalWordWrapIterator(string text, int maxCharsPerLine)

Check warning on line 273 in Extensions/StringExtensions.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 28 to the 15 allowed.

Check warning on line 273 in Extensions/StringExtensions.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 28 to the 15 allowed.
{
string normalized = text.NormalizeLineEndings(LineEndingStyle.Unix);
string[] segments = normalized.Split('\n');
Expand Down Expand Up @@ -298,7 +303,7 @@

yield return line.ToString();
line.Clear();
atLineStart = true;

Check warning on line 306 in Extensions/StringExtensions.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this useless assignment to local variable 'atLineStart'.

Check warning on line 306 in Extensions/StringExtensions.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this useless assignment to local variable 'atLineStart'.
}
else if (!atLineStart)
{
Expand Down Expand Up @@ -393,7 +398,7 @@
return words;
}

private static List<(string Text, bool HyphenBefore)> ChunkWord(string word)

Check warning on line 401 in Extensions/StringExtensions.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 17 to the 15 allowed.

Check warning on line 401 in Extensions/StringExtensions.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 17 to the 15 allowed.
{
// Split a whitespace-delimited word into break-units at hyphenation opportunities:
// after a visible hyphen (kept on the left chunk) or at a soft hyphen (removed unless a break lands there).
Expand Down
Loading