Преглед изворни кода

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 <[email protected]>
Benedikt Stebner пре 2 година
родитељ
комит
bbe4ad8755

+ 24 - 18
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;

+ 65 - 12
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;
             }
         }