Skip to content

Commit

Permalink
Merge pull request #439 from SixLabors/js/fix-break-word
Browse files Browse the repository at this point in the history
Ensure BreakWord wrapping includes at least one glyph
  • Loading branch information
JimBobSquarePants authored Jan 9, 2025
2 parents bcea187 + 54b1a5d commit 6de8c5c
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 13 deletions.
48 changes: 39 additions & 9 deletions src/SixLabors.Fonts/TextLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1223,7 +1223,11 @@ VerticalOrientationType.Rotate or
// If the line is too long, insert a forced line break.
if (textLine.ScaledLineAdvance > wrappingLength)
{
remaining.InsertAt(0, textLine.SplitAt(wrappingLength));
TextLine overflow = textLine.SplitAt(wrappingLength);
if (overflow != textLine)
{
remaining.InsertAt(0, overflow);
}
}
}

Expand All @@ -1249,6 +1253,21 @@ VerticalOrientationType.Rotate or
// Add the final line.
if (textLine.Count > 0)
{
if (shouldWrap && (breakWord || breakAll))
{
while (textLine.ScaledLineAdvance > wrappingLength)
{
TextLine overflow = textLine.SplitAt(wrappingLength);
if (overflow == textLine)
{
break;
}

textLines.Add(textLine.Finalize(options));
textLine = overflow;
}
}

textLines.Add(textLine.Finalize(options));
}

Expand Down Expand Up @@ -1342,29 +1361,40 @@ public TextLine SplitAt(int index)
return this;
}

TextLine result = new();
result.data.AddRange(this.data.GetRange(index, this.data.Count - index));
int count = this.data.Count - index;
TextLine result = new(count);
result.data.AddRange(this.data.GetRange(index, count));
RecalculateLineMetrics(result);

this.data.RemoveRange(index, this.data.Count - index);
this.data.RemoveRange(index, count);
RecalculateLineMetrics(this);
return result;
}

public TextLine SplitAt(float length)
{
TextLine result = new();
float advance = 0;
for (int i = 0; i < this.data.Count; i++)
float advance = this.data[0].ScaledAdvance;

// Ensure at least one glyph is in the line.
// trailing whitespace should be ignored as it is trimmed
// on finalization.
for (int i = 1; i < this.data.Count; i++)
{
GlyphLayoutData glyph = this.data[i];
advance += glyph.ScaledAdvance;
if (CodePoint.IsWhiteSpace(glyph.CodePoint))
{
continue;
}

if (advance >= length)
{
result.data.AddRange(this.data.GetRange(i, this.data.Count - i));
int count = this.data.Count - i;
TextLine result = new(count);
result.data.AddRange(this.data.GetRange(i, count));
RecalculateLineMetrics(result);

this.data.RemoveRange(i, this.data.Count - i);
this.data.RemoveRange(i, count);
RecalculateLineMetrics(this);
return result;
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions tests/SixLabors.Fonts.Tests/TextLayoutTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1206,6 +1206,23 @@ public void IsMeasureCharacterBoundsSameAsRenderBounds()
}
}

[Fact]
public void BreakWordEnsuresSingleCharacterPerLine()
{
Font font = CreateRenderingFont(20);
TextOptions options = new(font)
{
WordBreaking = WordBreaking.BreakWord,
WrappingLength = 1
};

const string text = "Hello World!";
int lineCount = TextMeasurer.CountLines(text, options);
Assert.Equal(text.Length - 1, lineCount);

TextLayoutTestUtilities.TestLayout(text, options);
}

private class CaptureGlyphBoundBuilder : IGlyphRenderer
{
public static List<FontRectangle> GenerateGlyphsBoxes(string text, TextOptions options)
Expand Down

0 comments on commit 6de8c5c

Please sign in to comment.