Skip to content

Commit a82862f

Browse files
Merge pull request #442 from SixLabors/js/additional-linebreak-fixes
Fix newline handling and whitespace trimming
2 parents 24871a8 + d6c5c87 commit a82862f

14 files changed

+158
-71
lines changed

src/SixLabors.Fonts/GlyphMetrics.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,8 @@ void SetDecoration(TextDecorations decorations, float thickness, float position)
401401
/// <returns>The <see cref="bool"/>.</returns>
402402
[MethodImpl(MethodImplOptions.AggressiveInlining)]
403403
protected internal static bool ShouldSkipGlyphRendering(CodePoint codePoint)
404-
=> UnicodeUtility.IsDefaultIgnorableCodePoint((uint)codePoint.Value) && !UnicodeUtility.ShouldRenderWhiteSpaceOnly(codePoint);
404+
=> CodePoint.IsNewLine(codePoint) ||
405+
(UnicodeUtility.IsDefaultIgnorableCodePoint((uint)codePoint.Value) && !UnicodeUtility.ShouldRenderWhiteSpaceOnly(codePoint));
405406

406407
/// <summary>
407408
/// Returns the size to render/measure the glyph based on the given size and resolution in px units.

src/SixLabors.Fonts/TextLayout.cs

Lines changed: 69 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -391,12 +391,23 @@ private static IEnumerable<GlyphLayout> LayoutLineHorizontal(
391391
TextLine.GlyphLayoutData data = textLine[i];
392392
if (data.IsNewLine)
393393
{
394+
glyphs.Add(new GlyphLayout(
395+
new Glyph(data.Metrics[0], data.PointSize),
396+
boxLocation,
397+
penLocation,
398+
Vector2.Zero,
399+
data.ScaledAdvance,
400+
yLineAdvance,
401+
GlyphLayoutMode.Horizontal,
402+
true,
403+
data.GraphemeIndex,
404+
data.StringIndex));
405+
394406
penLocation.X = originX;
395407
penLocation.Y += yLineAdvance;
396-
397408
boxLocation.X = originX;
398409
boxLocation.Y += advanceY;
399-
continue;
410+
return glyphs;
400411
}
401412

402413
int j = 0;
@@ -524,12 +535,23 @@ private static IEnumerable<GlyphLayout> LayoutLineVertical(
524535
TextLine.GlyphLayoutData data = textLine[i];
525536
if (data.IsNewLine)
526537
{
538+
glyphs.Add(new GlyphLayout(
539+
new Glyph(data.Metrics[0], data.PointSize),
540+
boxLocation,
541+
penLocation,
542+
Vector2.Zero,
543+
xLineAdvance,
544+
data.ScaledAdvance,
545+
GlyphLayoutMode.Vertical,
546+
true,
547+
data.GraphemeIndex,
548+
data.StringIndex));
549+
527550
boxLocation.X += advanceX;
528551
boxLocation.Y = originY;
529-
530552
penLocation.X += xLineAdvance;
531553
penLocation.Y = originY;
532-
continue;
554+
return glyphs;
533555
}
534556

535557
int j = 0;
@@ -671,12 +693,23 @@ private static IEnumerable<GlyphLayout> LayoutLineVerticalMixed(
671693
TextLine.GlyphLayoutData data = textLine[i];
672694
if (data.IsNewLine)
673695
{
696+
glyphs.Add(new GlyphLayout(
697+
new Glyph(data.Metrics[0], data.PointSize),
698+
boxLocation,
699+
penLocation,
700+
Vector2.Zero,
701+
xLineAdvance,
702+
data.ScaledAdvance,
703+
GlyphLayoutMode.Vertical,
704+
true,
705+
data.GraphemeIndex,
706+
data.StringIndex));
707+
674708
boxLocation.X += advanceX;
675709
boxLocation.Y = originY;
676-
677710
penLocation.X += xLineAdvance;
678711
penLocation.Y = originY;
679-
continue;
712+
return glyphs;
680713
}
681714

682715
if (data.IsTransformed)
@@ -1170,10 +1203,30 @@ VerticalOrientationType.Rotate or
11701203
{
11711204
// Mandatory line break at index.
11721205
TextLine remaining = textLine.SplitAt(i);
1173-
textLines.Add(textLine.Finalize(options));
1174-
textLine = remaining;
1175-
i = -1;
1176-
lineAdvance = 0;
1206+
1207+
if (shouldWrap && textLine.ScaledLineAdvance - glyph.ScaledAdvance > wrappingLength)
1208+
{
1209+
// We've overshot the wrapping length so we need to split the line
1210+
// at the previous break and add both lines.
1211+
TextLine overflow = textLine.SplitAt(lastLineBreak, keepAll);
1212+
if (overflow != textLine)
1213+
{
1214+
textLines.Add(textLine.Finalize(options));
1215+
textLine = overflow;
1216+
}
1217+
1218+
textLines.Add(textLine.Finalize(options));
1219+
textLine = remaining;
1220+
i = -1;
1221+
lineAdvance = 0;
1222+
}
1223+
else
1224+
{
1225+
textLines.Add(textLine.Finalize(options));
1226+
textLine = remaining;
1227+
i = -1;
1228+
lineAdvance = 0;
1229+
}
11771230
}
11781231
else if (shouldWrap)
11791232
{
@@ -1201,7 +1254,7 @@ VerticalOrientationType.Rotate or
12011254
{
12021255
// If the current break is a space, and the line minus the space
12031256
// is less than the wrapping length, we can break using the current break.
1204-
float previousAdvance = lineAdvance - (float)glyph.ScaledAdvance;
1257+
float previousAdvance = lineAdvance - glyph.ScaledAdvance;
12051258
TextLine.GlyphLayoutData lastGlyph = textLine[i - 1];
12061259
if (CodePoint.IsWhiteSpace(lastGlyph.CodePoint))
12071260
{
@@ -1463,8 +1516,9 @@ public TextLine SplitAt(LineBreak lineBreak, bool keepAll)
14631516

14641517
private void TrimTrailingWhitespace()
14651518
{
1466-
int index = this.data.Count;
1467-
while (index > 0)
1519+
int count = this.data.Count;
1520+
int index = count;
1521+
while (index > 1)
14681522
{
14691523
// Trim trailing breaking whitespace.
14701524
CodePoint point = this.data[index - 1].CodePoint;
@@ -1476,9 +1530,9 @@ private void TrimTrailingWhitespace()
14761530
index--;
14771531
}
14781532

1479-
if (index < this.data.Count && index != 0)
1533+
if (index < count)
14801534
{
1481-
this.data.RemoveRange(index, this.data.Count - index);
1535+
this.data.RemoveRange(index, count - index);
14821536
}
14831537
}
14841538

Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading

tests/SixLabors.Fonts.Tests/Issues/Issues_27.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public void ThrowsMeasuringWhitespace()
1212
Font font = new FontCollection().Add(TestFonts.WendyOneFile).CreateFont(12);
1313
FontRectangle size = TextMeasurer.MeasureBounds(" ", new TextOptions(new Font(font, 30)));
1414

15-
Assert.Equal(60, size.Width, 1F);
15+
Assert.Equal(6, size.Width, 1F);
1616
Assert.Equal(0, size.Height, 1F);
1717
}
1818
}

tests/SixLabors.Fonts.Tests/Issues/Issues_33.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ namespace SixLabors.Fonts.Tests.Issues;
99
public class Issues_33
1010
{
1111
[Theory]
12-
[InlineData("\naaaabbbbccccddddeeee\n\t\t\t3 tabs\n\t\t\t\t\t5 tabs", 580, 70)] // newlines aren't directly measured but it is used for offsetting
13-
[InlineData("\n\tHelloworld", 310, 10)]
12+
[InlineData("\naaaabbbbccccddddeeee\n\t\t\t3 tabs\n\t\t\t\t\t5 tabs", 580, 120)]
13+
[InlineData("\n\tHelloworld", 310, 60)]
1414
[InlineData("\tHelloworld", 310, 10)]
1515
[InlineData(" Helloworld", 340, 10)]
1616
[InlineData("Hell owor ld\t", 340, 10)]

tests/SixLabors.Fonts.Tests/Issues/Issues_35.cs

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,10 @@ public class Issues_35
1212
public void RenderingTabAtStartOrLineTooShort()
1313
{
1414
Font font = CreateFont("\t x");
15-
FontRectangle xWidth = TextMeasurer.MeasureBounds("x", new TextOptions(font) { Dpi = font.FontMetrics.ScaleFactor });
16-
FontRectangle tabWidth = TextMeasurer.MeasureBounds("\t", new TextOptions(font) { Dpi = font.FontMetrics.ScaleFactor });
17-
FontRectangle tabWithXWidth = TextMeasurer.MeasureBounds("\tx", new TextOptions(font) { Dpi = font.FontMetrics.ScaleFactor });
18-
19-
Assert.Equal(tabWidth.Width + xWidth.Width, tabWithXWidth.Width, 2F);
20-
}
21-
22-
[Fact]
23-
public void Rendering2TabsAtStartOfLineTooShort()
24-
{
25-
Font font = CreateFont("\t x");
26-
FontRectangle xWidth = TextMeasurer.MeasureBounds("x", new TextOptions(font) { Dpi = font.FontMetrics.ScaleFactor });
27-
FontRectangle tabWidth = TextMeasurer.MeasureBounds("\t\t", new TextOptions(font) { Dpi = font.FontMetrics.ScaleFactor });
28-
FontRectangle tabWithXWidth = TextMeasurer.MeasureBounds("\t\tx", new TextOptions(font) { Dpi = font.FontMetrics.ScaleFactor });
15+
TextOptions options = new(font) { Dpi = font.FontMetrics.ScaleFactor };
16+
FontRectangle xWidth = TextMeasurer.MeasureBounds("x", options);
17+
FontRectangle tabWidth = TextMeasurer.MeasureBounds("\t", options);
18+
FontRectangle tabWithXWidth = TextMeasurer.MeasureBounds("\tx", options);
2919

3020
Assert.Equal(tabWidth.Width + xWidth.Width, tabWithXWidth.Width, 2F);
3121
}
@@ -34,27 +24,17 @@ public void Rendering2TabsAtStartOfLineTooShort()
3424
public void TwoTabsAreDoubleWidthOfOneTab()
3525
{
3626
Font font = CreateFont("\t x");
37-
FontRectangle xWidth = TextMeasurer.MeasureBounds("x", new TextOptions(font) { Dpi = font.FontMetrics.ScaleFactor });
38-
FontRectangle tabWidth = TextMeasurer.MeasureBounds("\t", new TextOptions(font) { Dpi = font.FontMetrics.ScaleFactor });
39-
FontRectangle twoTabWidth = TextMeasurer.MeasureBounds("\t\t", new TextOptions(font) { Dpi = font.FontMetrics.ScaleFactor });
40-
41-
Assert.Equal(twoTabWidth.Width, tabWidth.Width * 2, 2F);
42-
}
43-
44-
[Fact]
45-
public void TwoTabsAreDoubleWidthOfOneTabMinusXWidth()
46-
{
47-
Font font = CreateFont("\t x");
48-
FontRectangle xWidth = TextMeasurer.MeasureBounds("x", new TextOptions(font) { Dpi = font.FontMetrics.ScaleFactor });
49-
FontRectangle tabWidth = TextMeasurer.MeasureBounds("\tx", new TextOptions(font) { Dpi = font.FontMetrics.ScaleFactor });
50-
FontRectangle twoTabWidth = TextMeasurer.MeasureBounds("\t\tx", new TextOptions(font) { Dpi = font.FontMetrics.ScaleFactor });
27+
TextOptions options = new(font) { Dpi = font.FontMetrics.ScaleFactor };
28+
FontRectangle xWidth = TextMeasurer.MeasureBounds("x", options);
29+
FontRectangle tabWithXWidth = TextMeasurer.MeasureBounds("\tx", options);
30+
FontRectangle tabTabWithXWidth = TextMeasurer.MeasureBounds("\t\tx", options);
5131

52-
Assert.Equal(twoTabWidth.Width - xWidth.Width, (tabWidth.Width - xWidth.Width) * 2, 2F);
32+
Assert.Equal(tabTabWithXWidth.Width - xWidth.Width, 2 * (tabWithXWidth.Width - xWidth.Width), 2F);
5333
}
5434

5535
public static Font CreateFont(string text)
5636
{
57-
var fc = (IFontMetricsCollection)new FontCollection();
37+
IFontMetricsCollection fc = new FontCollection();
5838
Font d = fc.AddMetrics(new FakeFontInstance(text), CultureInfo.InvariantCulture).CreateFont(12);
5939
return new Font(d, 1F);
6040
}

0 commit comments

Comments
 (0)