Browse Source

Fix TextWrapping for all modes when nothing fits into a line (#13241)

* TextFormatter freeze repro

* Fix emergency text wrapping for all wrapping modes

---------

Co-authored-by: Nikita Tsukanov <keks9n@gmail.com>
pull/13255/head
Benedikt Stebner 3 years ago
committed by GitHub
parent
commit
bbe4ad8755
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 42
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  2. 77
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs

42
src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs

@ -713,31 +713,37 @@ namespace Avalonia.Media.TextFormatting
var measuredLength = MeasureLength(textRuns, paragraphWidth);
if (measuredLength == 0 && paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow)
if(measuredLength == 0)
{
for (int i = 0; i < textRuns.Count; i++)
if(paragraphProperties.TextWrapping == TextWrapping.NoWrap)
{
measuredLength += textRuns[i].Length;
}
TextLineBreak? textLineBreak;
if (currentLineBreak?.TextEndOfLine is { } textEndOfLine)
{
textLineBreak = new TextLineBreak(textEndOfLine, resolvedFlowDirection);
for (int i = 0; i < textRuns.Count; i++)
{
measuredLength += textRuns[i].Length;
}
}
else
{
textLineBreak = null;
}
var textLine = new TextLineImpl(textRuns.ToArray(), firstTextSourceIndex, measuredLength,
paragraphWidth, paragraphProperties, resolvedFlowDirection,
textLineBreak);
var firstRun = textRuns[0];
textLine.FinalizeLine();
if(firstRun is ShapedTextRun)
{
var graphemeEnumerator = new GraphemeEnumerator(firstRun.Text.Span);
return textLine;
if(graphemeEnumerator.MoveNext(out var grapheme))
{
measuredLength = grapheme.Length;
}
else
{
measuredLength = 1;
}
}
else
{
measuredLength = firstRun.Length;
}
}
}
var currentLength = 0;

77
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs

@ -766,12 +766,14 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
[Fact]
public void Line_Formatting_For_Oversized_Embedded_Runs_Does_Not_Produce_Empty_Lines()
[Theory]
[InlineData(TextWrapping.NoWrap),InlineData(TextWrapping.Wrap),InlineData(TextWrapping.WrapWithOverflow)]
public void Line_Formatting_For_Oversized_Embedded_Runs_Does_Not_Produce_Empty_Lines(TextWrapping wrapping)
{
var defaultRunProperties = new GenericTextRunProperties(Typeface.Default, foregroundBrush: Brushes.Black);
var paragraphProperties = new GenericTextParagraphProperties(defaultRunProperties,
textWrap: TextWrapping.WrapWithOverflow);
textWrap: wrapping);
using (Start())
{
@ -780,6 +782,48 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.Equal(200d, textLine.WidthIncludingTrailingWhitespace);
}
}
[Theory]
[InlineData(TextWrapping.NoWrap),InlineData(TextWrapping.Wrap),InlineData(TextWrapping.WrapWithOverflow)]
public void Line_Formatting_For_Oversized_Embedded_Runs_Inside_Normal_Text_Does_Not_Produce_Empty_Lines(
TextWrapping wrapping)
{
var defaultRunProperties = new GenericTextRunProperties(Typeface.Default, foregroundBrush: Brushes.Black);
var paragraphProperties = new GenericTextParagraphProperties(defaultRunProperties,
textWrap: wrapping);
using (Start())
{
var typeface = new Typeface(FontFamily.Parse("resm:Avalonia.Skia.UnitTests.Fonts?assembly=Avalonia.Skia.UnitTests#DejaVu Sans"));
var text1 = new TextCharacters("Hello",
new GenericTextRunProperties(typeface, foregroundBrush: Brushes.Black));
var text2 = new TextCharacters("world",
new GenericTextRunProperties(typeface, foregroundBrush: Brushes.Black));
var source = new ListTextSource(
text1,
new RectangleRun(new Rect(0, 0, 200, 10), Brushes.Aqua),
new InvisibleRun(1),
new TextEndOfLine(),
text2,
new TextEndOfParagraph(1));
var lines = new List<TextLine>();
var dcp = 0;
for (var c = 0;; c++)
{
Assert.True(c < 1000, "Infinite loop");
var textLine = TextFormatter.Current.FormatLine(source, dcp, 30, paragraphProperties);
Assert.NotNull(textLine);
lines.Add(textLine);
dcp += textLine.Length;
if (textLine.TextLineBreak is {} eol && eol.TextEndOfLine is TextEndOfParagraph)
break;
}
}
}
class IncrementalTabProperties : TextParagraphProperties
{
@ -910,7 +954,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
private class ListTextSource : ITextSource
{
private Dictionary<int, TextRun> _runs = new();
private List<TextRun> _runs = new();
public ListTextSource(params TextRun[] runs) : this((IEnumerable<TextRun>)runs)
{
@ -919,18 +963,27 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
public ListTextSource(IEnumerable<TextRun> runs)
{
var off = 0;
foreach (var r in runs)
{
_runs[off] = r;
off += r.Length;
}
_runs = runs.ToList();
}
public TextRun GetTextRun(int textSourceIndex)
{
_runs.TryGetValue(textSourceIndex, out var rv);
return rv;
var off = 0;
for (var c = 0; c < _runs.Count; c++)
{
var run = _runs[c];
if (textSourceIndex >= off && textSourceIndex - off < run.Length)
{
if (run.Length == 1)
return run;
var chars = ((TextCharacters)run);
return new TextCharacters(chars.Text.Slice(textSourceIndex - off), chars.Properties);
}
off += run.Length;
}
return null;
}
}

Loading…
Cancel
Save