Browse Source

Reuse the shaped text runs as much as possible

Benedikt Schroeder 5 years ago
parent
commit
4f7a701ab7

+ 54 - 25
src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs

@@ -31,7 +31,8 @@ namespace Avalonia.Media.TextFormatting
                 case TextWrapping.WrapWithOverflow:
                 case TextWrapping.Wrap:
                     {
-                        textLine = PerformTextWrapping(textRuns, textRange, paragraphWidth, paragraphProperties);
+                        textLine = PerformTextWrapping(textRuns, textRange, paragraphWidth, paragraphProperties,
+                            nextLineBreak);
                         break;
                     }
                 default:
@@ -118,7 +119,7 @@ namespace Avalonia.Media.TextFormatting
         /// <param name="textRuns">The text run's.</param>
         /// <param name="length">The length to split at.</param>
         /// <returns>The split text runs.</returns>
-        internal static SplitTextRunsResult SplitTextRuns(IReadOnlyList<ShapedTextCharacters> textRuns, int length)
+        internal static SplitTextRunsResult SplitTextRuns(List<ShapedTextCharacters> textRuns, int length)
         {
             var currentLength = 0;
 
@@ -134,13 +135,13 @@ namespace Avalonia.Media.TextFormatting
 
                 var firstCount = currentRun.GlyphRun.Characters.Length >= 1 ? i + 1 : i;
 
-                var first = new ShapedTextCharacters[firstCount];
+                var first = new List<ShapedTextCharacters>(firstCount);
 
                 if (firstCount > 1)
                 {
                     for (var j = 0; j < i; j++)
                     {
-                        first[j] = textRuns[j];
+                        first.Add(textRuns[j]);
                     }
                 }
 
@@ -148,7 +149,7 @@ namespace Avalonia.Media.TextFormatting
 
                 if (currentLength + currentRun.GlyphRun.Characters.Length == length)
                 {
-                    var second = new ShapedTextCharacters[secondCount];
+                    var second = new List<ShapedTextCharacters>(secondCount);
 
                     var offset = currentRun.GlyphRun.Characters.Length > 1 ? 1 : 0;
 
@@ -156,11 +157,11 @@ namespace Avalonia.Media.TextFormatting
                     {
                         for (var j = 0; j < secondCount; j++)
                         {
-                            second[j] = textRuns[i + j + offset];
+                            second.Add(textRuns[i + j + offset]);
                         }
                     }
 
-                    first[i] = currentRun;
+                    first.Add(currentRun);
 
                     return new SplitTextRunsResult(first, second);
                 }
@@ -168,22 +169,22 @@ namespace Avalonia.Media.TextFormatting
                 {
                     secondCount++;
 
-                    var second = new ShapedTextCharacters[secondCount];
+                    var second = new List<ShapedTextCharacters>(secondCount);
+
+                    var split = currentRun.Split(length - currentLength);
+
+                    first.Add(split.First);
+
+                    second.Add(split.Second);
 
                     if (secondCount > 0)
                     {
                         for (var j = 1; j < secondCount; j++)
                         {
-                            second[j] = textRuns[i + j];
+                            second.Add(textRuns[i + j]);
                         }
                     }
 
-                    var split = currentRun.Split(length - currentLength);
-
-                    first[i] = split.First;
-
-                    second[0] = split.Second;
-
                     return new SplitTextRunsResult(first, second);
                 }
             }
@@ -201,7 +202,7 @@ namespace Avalonia.Media.TextFormatting
         /// <returns>
         /// The formatted text runs.
         /// </returns>
-        private static IReadOnlyList<ShapedTextCharacters> FetchTextRuns(ITextSource textSource,
+        private static List<ShapedTextCharacters> FetchTextRuns(ITextSource textSource,
             int firstTextSourceIndex, TextLineBreak previousLineBreak, out TextLineBreak nextLineBreak)
         {
             nextLineBreak = default;
@@ -212,8 +213,10 @@ namespace Avalonia.Media.TextFormatting
 
             if (previousLineBreak != null)
             {
-                foreach (var shapedCharacters in previousLineBreak.RemainingCharacters)
+                for (var index = 0; index < previousLineBreak.RemainingCharacters.Count; index++)
                 {
+                    var shapedCharacters = previousLineBreak.RemainingCharacters[index];
+
                     if (shapedCharacters == null)
                     {
                         continue;
@@ -225,6 +228,14 @@ namespace Avalonia.Media.TextFormatting
                     {
                         var splitResult = SplitTextRuns(textRuns, currentLength + runLineBreak.PositionWrap);
 
+                        if (++index < previousLineBreak.RemainingCharacters.Count)
+                        {
+                            for (; index < previousLineBreak.RemainingCharacters.Count; index++)
+                            {
+                                splitResult.Second.Add(previousLineBreak.RemainingCharacters[index]);
+                            }
+                        }
+
                         nextLineBreak = new TextLineBreak(splitResult.Second);
 
                         return splitResult.First;
@@ -323,9 +334,10 @@ namespace Avalonia.Media.TextFormatting
         /// <param name="textRange">The text range that is covered by the text runs.</param>
         /// <param name="paragraphWidth">The paragraph width.</param>
         /// <param name="paragraphProperties">The text paragraph properties.</param>
+        /// <param name="currentLineBreak">The current line break if the line was explicitly broken.</param>
         /// <returns>The wrapped text line.</returns>
-        private static TextLine PerformTextWrapping(IReadOnlyList<ShapedTextCharacters> textRuns, TextRange textRange,
-            double paragraphWidth, TextParagraphProperties paragraphProperties)
+        private static TextLine PerformTextWrapping(List<ShapedTextCharacters> textRuns, TextRange textRange,
+            double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak currentLineBreak)
         {
             var availableWidth = paragraphWidth;
             var currentWidth = 0.0;
@@ -388,8 +400,22 @@ namespace Avalonia.Media.TextFormatting
                     var textLineMetrics = TextLineMetrics.Create(splitResult.First,
                         new TextRange(textRange.Start, currentLength), paragraphWidth, paragraphProperties);
 
-                    var lineBreak = splitResult.Second != null && splitResult.Second.Count > 0 ?
-                        new TextLineBreak(splitResult.Second) :
+                    var remainingCharacters = splitResult.Second;
+
+                    if (currentLineBreak?.RemainingCharacters != null)
+                    {
+                        if (remainingCharacters != null)
+                        {
+                            remainingCharacters.AddRange(currentLineBreak.RemainingCharacters);
+                        }
+                        else
+                        {
+                            remainingCharacters = new List<ShapedTextCharacters>(currentLineBreak.RemainingCharacters);
+                        }
+                    }
+
+                    var lineBreak = remainingCharacters != null && remainingCharacters.Count > 0 ?
+                        new TextLineBreak(remainingCharacters) :
                         null;
 
                     return new TextLineImpl(splitResult.First, textLineMetrics, lineBreak);
@@ -403,7 +429,10 @@ namespace Avalonia.Media.TextFormatting
             }
 
             return new TextLineImpl(textRuns,
-                TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties));
+                TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties),
+                currentLineBreak?.RemainingCharacters != null ?
+                    new TextLineBreak(currentLineBreak.RemainingCharacters) :
+                    null);
         }
 
         /// <summary>
@@ -434,7 +463,7 @@ namespace Avalonia.Media.TextFormatting
 
         internal readonly struct SplitTextRunsResult
         {
-            public SplitTextRunsResult(IReadOnlyList<ShapedTextCharacters> first, IReadOnlyList<ShapedTextCharacters> second)
+            public SplitTextRunsResult(List<ShapedTextCharacters> first, List<ShapedTextCharacters> second)
             {
                 First = first;
 
@@ -447,7 +476,7 @@ namespace Avalonia.Media.TextFormatting
             /// <value>
             /// The first text runs.
             /// </value>
-            public IReadOnlyList<ShapedTextCharacters> First { get; }
+            public List<ShapedTextCharacters> First { get; }
 
             /// <summary>
             /// Gets the second text runs.
@@ -455,7 +484,7 @@ namespace Avalonia.Media.TextFormatting
             /// <value>
             /// The second text runs.
             /// </value>
-            public IReadOnlyList<ShapedTextCharacters> Second { get; }
+            public List<ShapedTextCharacters> Second { get; }
         }
 
         private struct TextRunEnumerator

+ 4 - 1
src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs

@@ -183,7 +183,10 @@ namespace Avalonia.Media.TextFormatting
             var glyphRun = TextShaper.Current.ShapeText(new ReadOnlySlice<char>(s_empty, startingIndex, 1),
                 properties.Typeface, properties.FontRenderingEmSize, properties.CultureInfo);
 
-            var textRuns = new[] { new ShapedTextCharacters(glyphRun, _paragraphProperties.DefaultTextRunProperties) };
+            var textRuns = new List<ShapedTextCharacters>
+            {
+                new ShapedTextCharacters(glyphRun, _paragraphProperties.DefaultTextRunProperties)
+            };
 
             return new TextLineImpl(textRuns,
                 TextLineMetrics.Create(textRuns, new TextRange(startingIndex, 1), MaxWidth, _paragraphProperties));

+ 2 - 2
src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs

@@ -6,9 +6,9 @@ namespace Avalonia.Media.TextFormatting
 {
     internal class TextLineImpl : TextLine
     {
-        private readonly IReadOnlyList<ShapedTextCharacters> _textRuns;
+        private readonly List<ShapedTextCharacters> _textRuns;
 
-        public TextLineImpl(IReadOnlyList<ShapedTextCharacters> textRuns, TextLineMetrics lineMetrics,
+        public TextLineImpl(List<ShapedTextCharacters> textRuns, TextLineMetrics lineMetrics,
             TextLineBreak lineBreak = null, bool hasCollapsed = false)
         {
             _textRuns = textRuns;

+ 2 - 8
src/Skia/Avalonia.Skia/TextShaperImpl.cs

@@ -123,10 +123,7 @@ namespace Avalonia.Skia
                 return;
             }
 
-            if (offsetBuffer == null)
-            {
-                offsetBuffer = new Vector[glyphPositions.Length];
-            }
+            offsetBuffer ??= new Vector[glyphPositions.Length];
 
             var offsetX = position.XOffset * textScale;
 
@@ -138,10 +135,7 @@ namespace Avalonia.Skia
         private static void SetAdvance(ReadOnlySpan<GlyphPosition> glyphPositions, int index, double textScale,
             ref double[] advanceBuffer)
         {
-            if (advanceBuffer == null)
-            {
-                advanceBuffer = new double[glyphPositions.Length];
-            }
+            advanceBuffer ??= new double[glyphPositions.Length];
 
             // Depends on direction of layout
             // advanceBuffer[index] = buffer.GlyphPositions[index].YAdvance * textScale;