Skip to content

Commit 95e5d75

Browse files
Merge pull request #454 from SixLabors/js/additional-linebreak-fixes
Fix #448 and #450
2 parents d74f3fa + 4686b5c commit 95e5d75

File tree

7 files changed

+157
-5
lines changed

7 files changed

+157
-5
lines changed

src/SixLabors.Fonts/TextLayout.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1179,7 +1179,28 @@ VerticalOrientationType.Rotate or
11791179
List<LineBreak> lineBreaks = new();
11801180
while (lineBreakEnumerator.MoveNext())
11811181
{
1182-
lineBreaks.Add(lineBreakEnumerator.Current);
1182+
// URLs are now so common in regular plain text that they need to be taken into account when
1183+
// assigning general-purpose line breaking properties.
1184+
//
1185+
// To handle this we disallow breaks after solidus (U+002F) entirely.
1186+
// Testing seems to indicate Chrome and other browsers do this as well.
1187+
//
1188+
// We do this outside of the line breaker so that the expected results from the Unicode
1189+
// tests are not affected.
1190+
// https://www.unicode.org/reports/tr14/#SY
1191+
LineBreak current = lineBreakEnumerator.Current;
1192+
int i = current.PositionMeasure;
1193+
if (i < textLine.Count)
1194+
{
1195+
CodePoint c = textLine[i].CodePoint;
1196+
CodePoint p = textLine[Math.Max(0, i - 1)].CodePoint;
1197+
if (c.Value == 0x002F || p.Value == 0x002F)
1198+
{
1199+
continue;
1200+
}
1201+
}
1202+
1203+
lineBreaks.Add(current);
11831204
}
11841205

11851206
int processed = 0;

src/SixLabors.Fonts/Unicode/LineBreakEnumerator.cs

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -347,11 +347,52 @@ private bool GetPairTableBreak(LineBreakClass lastClass)
347347
}
348348

349349
// LB25
350-
if (this.lb25ex && (this.nextClass == LineBreakClass.PR || this.nextClass == LineBreakClass.NU))
350+
if (this.lb25ex)
351351
{
352-
shouldBreak = true;
353-
this.lb25ex = false;
354-
break;
352+
switch (lastClass)
353+
{
354+
case LineBreakClass.PO:
355+
case LineBreakClass.PR:
356+
if (this.currentClass == LineBreakClass.NU)
357+
{
358+
this.lb25ex = false;
359+
return false;
360+
}
361+
362+
LineBreakClass ahead = this.PeekNextCharClass();
363+
if (ahead == LineBreakClass.NU && this.nextClass is LineBreakClass.OP or LineBreakClass.HY)
364+
{
365+
this.lb25ex = false;
366+
return false;
367+
}
368+
369+
break;
370+
case LineBreakClass.HY:
371+
case LineBreakClass.OP:
372+
if (this.currentClass == LineBreakClass.NU)
373+
{
374+
this.lb25ex = false;
375+
return false;
376+
}
377+
378+
break;
379+
380+
case LineBreakClass.NU:
381+
if (this.currentClass is LineBreakClass.PO or LineBreakClass.PR or LineBreakClass.NU)
382+
{
383+
this.lb25ex = false;
384+
return false;
385+
}
386+
387+
break;
388+
}
389+
390+
if (this.nextClass is LineBreakClass.PR or LineBreakClass.NU)
391+
{
392+
shouldBreak = true;
393+
this.lb25ex = false;
394+
break;
395+
}
355396
}
356397

357398
// LB24
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
namespace SixLabors.Fonts.Tests.Issues;
5+
public class Issues_448
6+
{
7+
[Fact]
8+
public void Issue_448()
9+
{
10+
if (SystemFonts.TryGet("Arial", out FontFamily family))
11+
{
12+
Font font = family.CreateFont(20);
13+
14+
TextLayoutTestUtilities.TestLayout(
15+
"aaaaa bbbbb/ccccc ddddd",
16+
new TextOptions(font)
17+
{
18+
WrappingLength = 150
19+
});
20+
}
21+
}
22+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Numerics;
5+
6+
namespace SixLabors.Fonts.Tests.Issues;
7+
public class Issues_450
8+
{
9+
[Fact]
10+
public void Issue_450()
11+
{
12+
if (SystemFonts.TryGet("Arial", out FontFamily family))
13+
{
14+
Font font = family.CreateFont(92);
15+
16+
TextLayoutTestUtilities.TestLayout(
17+
"Super, Smash Bros (1999)",
18+
new TextOptions(font)
19+
{
20+
Origin = new Vector2(50, 20),
21+
WrappingLength = 960,
22+
});
23+
}
24+
}
25+
}

tests/SixLabors.Fonts.Tests/Unicode/LineBreakEnumeratorTests.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,43 @@ public void BasicLatinTest()
4646
Assert.False(lineBreaker.MoveNext());
4747
}
4848

49+
[Fact]
50+
public void NumericTests()
51+
{
52+
const string text1 = "Super Smash Bros (1999)";
53+
const string text2 = "Super, Smash Bros (1999)";
54+
List<LineBreak> breaks1 = new();
55+
56+
foreach (LineBreak lineBreak in new LineBreakEnumerator(text1.AsSpan()))
57+
{
58+
breaks1.Add(lineBreak);
59+
}
60+
61+
Assert.Equal(5, breaks1[0].PositionMeasure);
62+
Assert.Equal(6, breaks1[0].PositionWrap);
63+
Assert.Equal(11, breaks1[1].PositionMeasure);
64+
Assert.Equal(12, breaks1[1].PositionWrap);
65+
Assert.Equal(16, breaks1[2].PositionMeasure);
66+
Assert.Equal(17, breaks1[2].PositionWrap);
67+
Assert.Equal(23, breaks1[3].PositionMeasure);
68+
Assert.Equal(23, breaks1[3].PositionWrap);
69+
70+
List<LineBreak> breaks2 = new();
71+
foreach (LineBreak lineBreak in new LineBreakEnumerator(text2.AsSpan()))
72+
{
73+
breaks2.Add(lineBreak);
74+
}
75+
76+
Assert.Equal(6, breaks2[0].PositionMeasure);
77+
Assert.Equal(7, breaks2[0].PositionWrap);
78+
Assert.Equal(12, breaks2[1].PositionMeasure);
79+
Assert.Equal(13, breaks2[1].PositionWrap);
80+
Assert.Equal(17, breaks2[2].PositionMeasure);
81+
Assert.Equal(18, breaks2[2].PositionWrap);
82+
Assert.Equal(24, breaks2[3].PositionMeasure);
83+
Assert.Equal(24, breaks2[3].PositionWrap);
84+
}
85+
4986
[Fact]
5087
public void ForwardTextWithOuterWhitespace()
5188
{

0 commit comments

Comments
 (0)