Skip to content

Commit 6de8c5c

Browse files
Merge pull request #439 from SixLabors/js/fix-break-word
Ensure BreakWord wrapping includes at least one glyph
2 parents bcea187 + 54b1a5d commit 6de8c5c

5 files changed

+63
-13
lines changed

src/SixLabors.Fonts/TextLayout.cs

+39-9
Original file line numberDiff line numberDiff line change
@@ -1223,7 +1223,11 @@ VerticalOrientationType.Rotate or
12231223
// If the line is too long, insert a forced line break.
12241224
if (textLine.ScaledLineAdvance > wrappingLength)
12251225
{
1226-
remaining.InsertAt(0, textLine.SplitAt(wrappingLength));
1226+
TextLine overflow = textLine.SplitAt(wrappingLength);
1227+
if (overflow != textLine)
1228+
{
1229+
remaining.InsertAt(0, overflow);
1230+
}
12271231
}
12281232
}
12291233

@@ -1249,6 +1253,21 @@ VerticalOrientationType.Rotate or
12491253
// Add the final line.
12501254
if (textLine.Count > 0)
12511255
{
1256+
if (shouldWrap && (breakWord || breakAll))
1257+
{
1258+
while (textLine.ScaledLineAdvance > wrappingLength)
1259+
{
1260+
TextLine overflow = textLine.SplitAt(wrappingLength);
1261+
if (overflow == textLine)
1262+
{
1263+
break;
1264+
}
1265+
1266+
textLines.Add(textLine.Finalize(options));
1267+
textLine = overflow;
1268+
}
1269+
}
1270+
12521271
textLines.Add(textLine.Finalize(options));
12531272
}
12541273

@@ -1342,29 +1361,40 @@ public TextLine SplitAt(int index)
13421361
return this;
13431362
}
13441363

1345-
TextLine result = new();
1346-
result.data.AddRange(this.data.GetRange(index, this.data.Count - index));
1364+
int count = this.data.Count - index;
1365+
TextLine result = new(count);
1366+
result.data.AddRange(this.data.GetRange(index, count));
13471367
RecalculateLineMetrics(result);
13481368

1349-
this.data.RemoveRange(index, this.data.Count - index);
1369+
this.data.RemoveRange(index, count);
13501370
RecalculateLineMetrics(this);
13511371
return result;
13521372
}
13531373

13541374
public TextLine SplitAt(float length)
13551375
{
1356-
TextLine result = new();
1357-
float advance = 0;
1358-
for (int i = 0; i < this.data.Count; i++)
1376+
float advance = this.data[0].ScaledAdvance;
1377+
1378+
// Ensure at least one glyph is in the line.
1379+
// trailing whitespace should be ignored as it is trimmed
1380+
// on finalization.
1381+
for (int i = 1; i < this.data.Count; i++)
13591382
{
13601383
GlyphLayoutData glyph = this.data[i];
13611384
advance += glyph.ScaledAdvance;
1385+
if (CodePoint.IsWhiteSpace(glyph.CodePoint))
1386+
{
1387+
continue;
1388+
}
1389+
13621390
if (advance >= length)
13631391
{
1364-
result.data.AddRange(this.data.GetRange(i, this.data.Count - i));
1392+
int count = this.data.Count - i;
1393+
TextLine result = new(count);
1394+
result.data.AddRange(this.data.GetRange(i, count));
13651395
RecalculateLineMetrics(result);
13661396

1367-
this.data.RemoveRange(i, this.data.Count - i);
1397+
this.data.RemoveRange(i, count);
13681398
RecalculateLineMetrics(this);
13691399
return result;
13701400
}

tests/SixLabors.Fonts.Tests/TextLayoutTests.cs

+17
Original file line numberDiff line numberDiff line change
@@ -1206,6 +1206,23 @@ public void IsMeasureCharacterBoundsSameAsRenderBounds()
12061206
}
12071207
}
12081208

1209+
[Fact]
1210+
public void BreakWordEnsuresSingleCharacterPerLine()
1211+
{
1212+
Font font = CreateRenderingFont(20);
1213+
TextOptions options = new(font)
1214+
{
1215+
WordBreaking = WordBreaking.BreakWord,
1216+
WrappingLength = 1
1217+
};
1218+
1219+
const string text = "Hello World!";
1220+
int lineCount = TextMeasurer.CountLines(text, options);
1221+
Assert.Equal(text.Length - 1, lineCount);
1222+
1223+
TextLayoutTestUtilities.TestLayout(text, options);
1224+
}
1225+
12091226
private class CaptureGlyphBoundBuilder : IGlyphRenderer
12101227
{
12111228
public static List<FontRectangle> GenerateGlyphsBoxes(string text, TextOptions options)

0 commit comments

Comments
 (0)