From 658fd804af0835600d6c5e0fcdfa2826590f4ace Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Thu, 19 Jan 2023 01:36:45 +0100 Subject: [PATCH] Removed some temporary List from text layout --- .../Media/TextFormatting/SplitResult.cs | 11 ++++ .../Media/TextFormatting/TextCharacters.cs | 9 +-- .../TextCollapsingProperties.cs | 6 +- .../TextFormatting/TextEllipsisHelper.cs | 62 ++++++++++--------- .../Media/TextFormatting/TextFormatterImpl.cs | 53 +++++++++------- .../Media/TextFormatting/TextLayout.cs | 51 ++++++++------- .../TextLeadingPrefixCharacterEllipsis.cs | 50 ++++++++------- .../Media/TextFormatting/TextLineImpl.cs | 56 ++++++++--------- .../TextTrailingCharacterEllipsis.cs | 7 +-- .../TextTrailingWordEllipsis.cs | 8 +-- 10 files changed, 169 insertions(+), 144 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/SplitResult.cs b/src/Avalonia.Base/Media/TextFormatting/SplitResult.cs index 53021c4656..c1ac57ce46 100644 --- a/src/Avalonia.Base/Media/TextFormatting/SplitResult.cs +++ b/src/Avalonia.Base/Media/TextFormatting/SplitResult.cs @@ -26,5 +26,16 @@ /// The second part. /// public T? Second { get; } + + /// + /// Deconstructs the split results into its components. + /// + /// On return, contains the first part. + /// On return, contains the second part. + public void Deconstruct(out T first, out T? second) + { + first = First; + second = Second; + } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs index 2525f0dbf9..6454f9bfa3 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs @@ -46,24 +46,21 @@ namespace Avalonia.Media.TextFormatting /// Gets a list of . /// /// The shapeable text characters. - internal IReadOnlyList GetShapeableCharacters(ReadOnlyMemory text, sbyte biDiLevel, - ref TextRunProperties? previousProperties) + internal void GetShapeableCharacters(ReadOnlyMemory text, sbyte biDiLevel, + ref TextRunProperties? previousProperties, List results) { - var shapeableCharacters = new List(2); var properties = Properties; while (!text.IsEmpty) { var shapeableRun = CreateShapeableRun(text, properties, biDiLevel, ref previousProperties); - shapeableCharacters.Add(shapeableRun); + results.Add(shapeableRun); text = text.Slice(shapeableRun.Length); previousProperties = shapeableRun.Properties; } - - return shapeableCharacters; } /// diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs b/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs index 01804e1ce3..72882df0b5 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace Avalonia.Media.TextFormatting +namespace Avalonia.Media.TextFormatting { /// /// Properties of text collapsing. @@ -21,6 +19,6 @@ namespace Avalonia.Media.TextFormatting /// Collapses given text line. /// /// Text line to collapse. - public abstract List? Collapse(TextLine textLine); + public abstract TextRun[]? Collapse(TextLine textLine); } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs b/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs index 528cd45581..97f8b2483b 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs @@ -1,15 +1,18 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using Avalonia.Media.TextFormatting.Unicode; +using Avalonia.Utilities; namespace Avalonia.Media.TextFormatting { internal static class TextEllipsisHelper { - public static List? Collapse(TextLine textLine, TextCollapsingProperties properties, bool isWordEllipsis) + public static TextRun[]? Collapse(TextLine textLine, TextCollapsingProperties properties, bool isWordEllipsis) { var textRuns = textLine.TextRuns; - if (textRuns == null || textRuns.Count == 0) + if (textRuns.Count == 0) { return null; } @@ -22,7 +25,7 @@ namespace Avalonia.Media.TextFormatting if (properties.Width < shapedSymbol.GlyphRun.Size.Width) { //Not enough space to fit in the symbol - return new List(0); + return Array.Empty(); } var availableWidth = properties.Width - shapedSymbol.Size.Width; @@ -70,18 +73,7 @@ namespace Avalonia.Media.TextFormatting collapsedLength += measuredLength; - var collapsedRuns = new List(textRuns.Count); - - if (collapsedLength > 0) - { - var splitResult = TextFormatterImpl.SplitTextRuns(textRuns, collapsedLength); - - collapsedRuns.AddRange(splitResult.First); - } - - collapsedRuns.Add(shapedSymbol); - - return collapsedRuns; + return CreateCollapsedRuns(textRuns, collapsedLength, shapedSymbol); } availableWidth -= shapedRun.Size.Width; @@ -94,18 +86,7 @@ namespace Avalonia.Media.TextFormatting //The whole run needs to fit into available space if (currentWidth + drawableRun.Size.Width > availableWidth) { - var collapsedRuns = new List(textRuns.Count); - - if (collapsedLength > 0) - { - var splitResult = TextFormatterImpl.SplitTextRuns(textRuns, collapsedLength); - - collapsedRuns.AddRange(splitResult.First); - } - - collapsedRuns.Add(shapedSymbol); - - return collapsedRuns; + return CreateCollapsedRuns(textRuns, collapsedLength, shapedSymbol); } availableWidth -= drawableRun.Size.Width; @@ -121,5 +102,30 @@ namespace Avalonia.Media.TextFormatting return null; } + + private static TextRun[] CreateCollapsedRuns(IReadOnlyList textRuns, int collapsedLength, + TextRun shapedSymbol) + { + if (collapsedLength <= 0) + { + return new[] { shapedSymbol }; + } + + // perf note: the runs are very likely to come from TextLineImpl + // which already uses an array: ToArray() won't ever be called in this case + var textRunArray = textRuns as TextRun[] ?? textRuns.ToArray(); + + var (preSplitRuns, _) = TextFormatterImpl.SplitTextRuns(textRunArray, collapsedLength); + + var collapsedRuns = new TextRun[preSplitRuns.Count + 1]; + + for (var i = 0; i < preSplitRuns.Count; ++i) + { + collapsedRuns[i] = preSplitRuns[i]; + } + + collapsedRuns[collapsedRuns.Length - 1] = shapedSymbol; + return collapsedRuns; + } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index 8afecb09e2..5c073452f4 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -1,6 +1,7 @@ using System; using System.Buffers; using System.Collections.Generic; +using System.Linq; using System.Runtime.InteropServices; using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Utilities; @@ -23,10 +24,10 @@ namespace Avalonia.Media.TextFormatting var fetchedRuns = FetchTextRuns(textSource, firstTextSourceIndex, out var textEndOfLine, out var textSourceLength); - if (previousLineBreak?.RemainingRuns != null) + if (previousLineBreak?.RemainingRuns is { } remainingRuns) { resolvedFlowDirection = previousLineBreak.FlowDirection; - textRuns = previousLineBreak.RemainingRuns; + textRuns = remainingRuns; nextLineBreak = previousLineBreak; } else @@ -45,7 +46,7 @@ namespace Avalonia.Media.TextFormatting { case TextWrapping.NoWrap: { - textLine = new TextLineImpl(textRuns, firstTextSourceIndex, textSourceLength, + textLine = new TextLineImpl(textRuns.ToArray(), firstTextSourceIndex, textSourceLength, paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak); textLine.FinalizeLine(); @@ -160,6 +161,14 @@ namespace Avalonia.Media.TextFormatting { var flowDirection = paragraphProperties.FlowDirection; var shapedRuns = new List(); + + if (textRuns.Count == 0) + { + resolvedFlowDirection = flowDirection; + return shapedRuns; + } + + using var biDiData = new BidiData((sbyte)flowDirection); foreach (var textRun in textRuns) @@ -224,7 +233,7 @@ namespace Avalonia.Media.TextFormatting shapeableRun.BidiLevel, currentRun.Properties.CultureInfo, paragraphProperties.DefaultIncrementalTab, paragraphProperties.LetterSpacing); - shapedRuns.AddRange(ShapeTogether(groupedRuns, text, shaperOptions)); + ShapeTogether(groupedRuns, text, shaperOptions, shapedRuns); break; } @@ -309,11 +318,9 @@ namespace Avalonia.Media.TextFormatting && x.Typeface == y.Typeface && x.BaselineAlignment == y.BaselineAlignment; - private static IReadOnlyList ShapeTogether( - IReadOnlyList textRuns, ReadOnlyMemory text, TextShaperOptions options) + private static void ShapeTogether(IReadOnlyList textRuns, ReadOnlyMemory text, + TextShaperOptions options, List results) { - var shapedRuns = new List(textRuns.Count); - var shapedBuffer = TextShaper.Current.ShapeText(text, options); for (var i = 0; i < textRuns.Count; i++) @@ -322,12 +329,10 @@ namespace Avalonia.Media.TextFormatting var splitResult = shapedBuffer.Split(currentRun.Length); - shapedRuns.Add(new ShapedTextRun(splitResult.First, currentRun.Properties)); + results.Add(new ShapedTextRun(splitResult.First, currentRun.Properties)); shapedBuffer = splitResult.Second!; } - - return shapedRuns; } /// @@ -335,7 +340,7 @@ namespace Avalonia.Media.TextFormatting /// /// The text characters to form from. /// The bidi levels. - /// + /// A list that will be filled with the processed runs. /// private static void CoalesceLevels(IReadOnlyList textCharacters, ArraySlice levels, List processedRuns) @@ -385,7 +390,8 @@ namespace Avalonia.Media.TextFormatting if (j == runTextSpan.Length) { - processedRuns.AddRange(currentRun.GetShapeableCharacters(runText.Slice(0, j), runLevel, ref previousProperties)); + currentRun.GetShapeableCharacters(runText.Slice(0, j), runLevel, ref previousProperties, + processedRuns); runLevel = levels[levelIndex]; @@ -398,7 +404,8 @@ namespace Avalonia.Media.TextFormatting } // End of this run - processedRuns.AddRange(currentRun.GetShapeableCharacters(runText.Slice(0, j), runLevel, ref previousProperties)); + currentRun.GetShapeableCharacters(runText.Slice(0, j), runLevel, ref previousProperties, + processedRuns); runText = runText.Slice(j); runTextSpan = runText.Span; @@ -415,7 +422,7 @@ namespace Avalonia.Media.TextFormatting return; } - processedRuns.AddRange(currentRun.GetShapeableCharacters(runText, runLevel, ref previousProperties)); + currentRun.GetShapeableCharacters(runText, runLevel, ref previousProperties, processedRuns); } /// @@ -423,8 +430,8 @@ namespace Avalonia.Media.TextFormatting /// /// The text source. /// The first text source index. - /// - /// + /// On return, the end of line, if any. + /// On return, the processed text source length. /// /// The formatted text runs. /// @@ -602,7 +609,7 @@ namespace Avalonia.Media.TextFormatting var shapedBuffer = new ShapedBuffer(s_empty.AsMemory(), glyphInfos, glyphTypeface, properties.FontRenderingEmSize, (sbyte)flowDirection); - var textRuns = new List { new ShapedTextRun(shapedBuffer, properties) }; + var textRuns = new TextRun[] { new ShapedTextRun(shapedBuffer, properties) }; return new TextLineImpl(textRuns, firstTextSourceIndex, 0, paragraphWidth, paragraphProperties, flowDirection).FinalizeLine(); } @@ -749,12 +756,10 @@ namespace Avalonia.Media.TextFormatting break; } - var splitResult = SplitTextRuns(textRuns, measuredLength); - - var remainingCharacters = splitResult.Second; + var (preSplitRuns, postSplitRuns) = SplitTextRuns(textRuns, measuredLength); - var lineBreak = remainingCharacters?.Count > 0 ? - new TextLineBreak(null, resolvedFlowDirection, remainingCharacters) : + var lineBreak = postSplitRuns?.Count > 0 ? + new TextLineBreak(null, resolvedFlowDirection, postSplitRuns) : null; if (lineBreak is null && currentLineBreak?.TextEndOfLine != null) @@ -762,7 +767,7 @@ namespace Avalonia.Media.TextFormatting lineBreak = new TextLineBreak(currentLineBreak.TextEndOfLine, resolvedFlowDirection); } - var textLine = new TextLineImpl(splitResult.First, firstTextSourceIndex, measuredLength, + var textLine = new TextLineImpl(preSplitRuns.ToArray(), firstTextSourceIndex, measuredLength, paragraphWidth, paragraphProperties, resolvedFlowDirection, lineBreak); diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs index 468623b356..55b6f14267 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Avalonia.Utilities; namespace Avalonia.Media.TextFormatting @@ -13,6 +12,7 @@ namespace Avalonia.Media.TextFormatting private readonly ITextSource _textSource; private readonly TextParagraphProperties _paragraphProperties; private readonly TextTrimming _textTrimming; + private readonly TextLine[] _textLines; private int _textSourceLength; @@ -69,7 +69,7 @@ namespace Avalonia.Media.TextFormatting MaxLines = maxLines; - TextLines = CreateTextLines(); + _textLines = CreateTextLines(); } /// @@ -109,7 +109,7 @@ namespace Avalonia.Media.TextFormatting MaxLines = maxLines; - TextLines = CreateTextLines(); + _textLines = CreateTextLines(); } /// @@ -147,7 +147,8 @@ namespace Avalonia.Media.TextFormatting /// /// The text lines. /// - public IReadOnlyList TextLines { get; private set; } + public IReadOnlyList TextLines + => _textLines; /// /// Gets the bounds of the layout. @@ -164,14 +165,14 @@ namespace Avalonia.Media.TextFormatting /// The origin. public void Draw(DrawingContext context, Point origin) { - if (!TextLines.Any()) + if (_textLines.Length == 0) { return; } var (currentX, currentY) = origin; - foreach (var textLine in TextLines) + foreach (var textLine in _textLines) { textLine.Draw(context, new Point(currentX + textLine.Start, currentY)); @@ -186,7 +187,7 @@ namespace Avalonia.Media.TextFormatting /// public Rect HitTestTextPosition(int textPosition) { - if (TextLines.Count == 0) + if (_textLines.Length == 0) { return new Rect(); } @@ -198,7 +199,7 @@ namespace Avalonia.Media.TextFormatting var currentY = 0.0; - foreach (var textLine in TextLines) + foreach (var textLine in _textLines) { var end = textLine.FirstTextSourceIndex + textLine.Length; @@ -230,11 +231,11 @@ namespace Avalonia.Media.TextFormatting return Array.Empty(); } - var result = new List(TextLines.Count); + var result = new List(_textLines.Length); var currentY = 0d; - foreach (var textLine in TextLines) + foreach (var textLine in _textLines) { //Current line isn't covered. if (textLine.FirstTextSourceIndex + textLine.Length < start) @@ -284,13 +285,12 @@ namespace Avalonia.Media.TextFormatting { var currentY = 0d; - var lineIndex = 0; TextLine? currentLine = null; CharacterHit characterHit; - for (; lineIndex < TextLines.Count; lineIndex++) + for (var lineIndex = 0; lineIndex < _textLines.Length; lineIndex++) { - currentLine = TextLines[lineIndex]; + currentLine = _textLines[lineIndex]; if (currentY + currentLine.Height > point.Y) { @@ -322,12 +322,12 @@ namespace Avalonia.Media.TextFormatting if (charIndex > _textSourceLength) { - return TextLines.Count - 1; + return _textLines.Length - 1; } - for (var index = 0; index < TextLines.Count; index++) + for (var index = 0; index < _textLines.Length; index++) { - var textLine = TextLines[index]; + var textLine = _textLines[index]; if (textLine.FirstTextSourceIndex + textLine.Length < charIndex) { @@ -341,7 +341,7 @@ namespace Avalonia.Media.TextFormatting } } - return TextLines.Count - 1; + return _textLines.Length - 1; } private TextHitTestResult GetHitTestResult(TextLine textLine, CharacterHit characterHit, Point point) @@ -424,7 +424,7 @@ namespace Avalonia.Media.TextFormatting height += textLine.Height; } - private IReadOnlyList CreateTextLines() + private TextLine[] CreateTextLines() { if (MathUtilities.IsZero(MaxWidth) || MathUtilities.IsZero(MaxHeight)) { @@ -432,7 +432,7 @@ namespace Avalonia.Media.TextFormatting Bounds = new Rect(0, 0, 0, textLine.Height); - return new List { textLine }; + return new TextLine[] { textLine }; } var textLines = new List(); @@ -443,12 +443,14 @@ namespace Avalonia.Media.TextFormatting TextLine? previousLine = null; + var textFormatter = TextFormatter.Current; + while (true) { - var textLine = TextFormatter.Current.FormatLine(_textSource, _textSourceLength, MaxWidth, + var textLine = textFormatter.FormatLine(_textSource, _textSourceLength, MaxWidth, _paragraphProperties, previousLine?.TextLineBreak); - if(textLine == null || textLine.Length == 0) + if (textLine.Length == 0) { if (previousLine != null && previousLine.NewLineLength > 0) { @@ -524,8 +526,9 @@ namespace Avalonia.Media.TextFormatting { var whitespaceWidth = 0d; - foreach (var line in textLines) + for (var i = 0; i < textLines.Count; i++) { + var line = textLines[i]; var lineWhitespaceWidth = line.Width - line.WidthIncludingTrailingWhitespace; if (lineWhitespaceWidth > whitespaceWidth) @@ -549,7 +552,7 @@ namespace Avalonia.Media.TextFormatting } } - return textLines; + return textLines.ToArray(); } /// @@ -569,7 +572,7 @@ namespace Avalonia.Media.TextFormatting public void Dispose() { - foreach (var line in TextLines) + foreach (var line in _textLines) { line.Dispose(); } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs b/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs index e30a0fe9f4..672a15b398 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs @@ -1,5 +1,7 @@ -using System; +// ReSharper disable ForCanBeConvertedToForeach +using System; using System.Collections.Generic; +using System.Linq; namespace Avalonia.Media.TextFormatting { @@ -39,11 +41,12 @@ namespace Avalonia.Media.TextFormatting /// public override TextRun Symbol { get; } - public override List? Collapse(TextLine textLine) + /// + public override TextRun[]? Collapse(TextLine textLine) { var textRuns = textLine.TextRuns; - if (textRuns == null || textRuns.Count == 0) + if (textRuns.Count == 0) { return null; } @@ -54,7 +57,7 @@ namespace Avalonia.Media.TextFormatting if (Width < shapedSymbol.GlyphRun.Size.Width) { - return new List(0); + return Array.Empty(); } // Overview of ellipsis structure @@ -75,41 +78,48 @@ namespace Avalonia.Media.TextFormatting { shapedRun.TryMeasureCharacters(availableWidth, out var measuredLength); - var collapsedRuns = new List(textRuns.Count); - if (measuredLength > 0) { - IReadOnlyList? preSplitRuns = null; + var collapsedRuns = new List(textRuns.Count + 1); + + // perf note: the runs are very likely to come from TextLineImpl, + // which already uses an array: ToArray() won't ever be called in this case + var textRunArray = textRuns as TextRun[] ?? textRuns.ToArray(); + + IReadOnlyList? preSplitRuns; IReadOnlyList? postSplitRuns; if (_prefixLength > 0) { - var splitResult = TextFormatterImpl.SplitTextRuns(textRuns, - Math.Min(_prefixLength, measuredLength)); + (preSplitRuns, postSplitRuns) = TextFormatterImpl.SplitTextRuns( + textRunArray, Math.Min(_prefixLength, measuredLength)); - collapsedRuns.AddRange(splitResult.First); - - preSplitRuns = splitResult.First; - postSplitRuns = splitResult.Second; + for (var i = 0; i < preSplitRuns.Count; i++) + { + var preSplitRun = preSplitRuns[i]; + collapsedRuns.Add(preSplitRun); + } } else { - postSplitRuns = textRuns; + preSplitRuns = null; + postSplitRuns = textRunArray; } collapsedRuns.Add(shapedSymbol); if (measuredLength <= _prefixLength || postSplitRuns is null) { - return collapsedRuns; + return collapsedRuns.ToArray(); } var availableSuffixWidth = availableWidth; if (preSplitRuns is not null) { - foreach (var run in preSplitRuns) + for (var i = 0; i < preSplitRuns.Count; i++) { + var run = preSplitRuns[i]; if (run is DrawableTextRun drawableTextRun) { availableSuffixWidth -= drawableTextRun.Size.Width; @@ -143,13 +153,11 @@ namespace Avalonia.Media.TextFormatting } } } - } - else - { - collapsedRuns.Add(shapedSymbol); + + return collapsedRuns.ToArray(); } - return collapsedRuns; + return new TextRun[] { shapedSymbol }; } availableWidth -= shapedRun.Size.Width; diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index ab9686a34a..ae6df3a232 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -1,19 +1,18 @@ using System; using System.Collections.Generic; -using System.Linq; using Avalonia.Utilities; namespace Avalonia.Media.TextFormatting { - internal class TextLineImpl : TextLine + internal sealed class TextLineImpl : TextLine { - private IReadOnlyList _textRuns; + private TextRun[] _textRuns; private readonly double _paragraphWidth; private readonly TextParagraphProperties _paragraphProperties; private TextLineMetrics _textLineMetrics; private readonly FlowDirection _resolvedFlowDirection; - public TextLineImpl(IReadOnlyList textRuns, int firstTextSourceIndex, int length, double paragraphWidth, + public TextLineImpl(TextRun[] textRuns, int firstTextSourceIndex, int length, double paragraphWidth, TextParagraphProperties paragraphProperties, FlowDirection resolvedFlowDirection = FlowDirection.LeftToRight, TextLineBreak? lineBreak = null, bool hasCollapsed = false) { @@ -147,7 +146,7 @@ namespace Avalonia.Media.TextFormatting var collapsedLine = new TextLineImpl(collapsedRuns, FirstTextSourceIndex, Length, _paragraphWidth, _paragraphProperties, _resolvedFlowDirection, TextLineBreak, true); - if (collapsedRuns.Count > 0) + if (collapsedRuns.Length > 0) { collapsedLine.FinalizeLine(); } @@ -166,7 +165,7 @@ namespace Avalonia.Media.TextFormatting /// public override CharacterHit GetCharacterHitFromDistance(double distance) { - if (_textRuns.Count == 0) + if (_textRuns.Length == 0) { return new CharacterHit(); } @@ -182,7 +181,7 @@ namespace Avalonia.Media.TextFormatting if (distance >= WidthIncludingTrailingWhitespace) { - var lastRun = _textRuns[_textRuns.Count - 1]; + var lastRun = _textRuns[_textRuns.Length - 1]; var size = 0.0; @@ -199,7 +198,7 @@ namespace Avalonia.Media.TextFormatting var currentPosition = FirstTextSourceIndex; var currentDistance = 0.0; - for (var i = 0; i < _textRuns.Count; i++) + for (var i = 0; i < _textRuns.Length; i++) { var currentRun = _textRuns[i]; @@ -208,7 +207,7 @@ namespace Avalonia.Media.TextFormatting var rightToLeftIndex = i; currentPosition += currentRun.Length; - while (rightToLeftIndex + 1 <= _textRuns.Count - 1) + while (rightToLeftIndex + 1 <= _textRuns.Length - 1) { var nextShaped = _textRuns[++rightToLeftIndex] as ShapedTextRun; @@ -224,7 +223,7 @@ namespace Avalonia.Media.TextFormatting for (var j = i; i <= rightToLeftIndex; j++) { - if (j > _textRuns.Count - 1) + if (j > _textRuns.Length - 1) { break; } @@ -254,7 +253,7 @@ namespace Avalonia.Media.TextFormatting if (currentRun is DrawableTextRun drawableTextRun) { - if (i < _textRuns.Count - 1 && currentDistance + drawableTextRun.Size.Width < distance) + if (i < _textRuns.Length - 1 && currentDistance + drawableTextRun.Size.Width < distance) { currentDistance += drawableTextRun.Size.Width; @@ -328,7 +327,7 @@ namespace Avalonia.Media.TextFormatting if (flowDirection == FlowDirection.LeftToRight) { - for (var index = 0; index < _textRuns.Count; index++) + for (var index = 0; index < _textRuns.Length; index++) { var currentRun = _textRuns[index]; @@ -338,7 +337,7 @@ namespace Avalonia.Media.TextFormatting var rightToLeftWidth = shapedRun.Size.Width; - while (i + 1 <= _textRuns.Count - 1) + while (i + 1 <= _textRuns.Length - 1) { var nextRun = _textRuns[i + 1]; @@ -402,7 +401,7 @@ namespace Avalonia.Media.TextFormatting { currentDistance += WidthIncludingTrailingWhitespace; - for (var index = _textRuns.Count - 1; index >= 0; index--) + for (var index = _textRuns.Length - 1; index >= 0; index--) { var currentRun = _textRuns[index]; @@ -502,7 +501,7 @@ namespace Avalonia.Media.TextFormatting /// public override CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit) { - if (_textRuns.Count == 0) + if (_textRuns.Length == 0) { return new CharacterHit(); } @@ -637,7 +636,7 @@ namespace Avalonia.Media.TextFormatting var rightToLeftIndex = index; var rightToLeftWidth = currentShapedRun.Size.Width; - while (rightToLeftIndex + 1 <= _textRuns.Count - 1 && _textRuns[rightToLeftIndex + 1] is ShapedTextRun nextShapedRun) + while (rightToLeftIndex + 1 <= _textRuns.Length - 1 && _textRuns[rightToLeftIndex + 1] is ShapedTextRun nextShapedRun) { if (nextShapedRun == null || nextShapedRun.ShapedBuffer.IsLeftToRight) { @@ -981,7 +980,7 @@ namespace Avalonia.Media.TextFormatting public override void Dispose() { - for (int i = 0; i < _textRuns.Count; i++) + for (int i = 0; i < _textRuns.Length; i++) { if (_textRuns[i] is ShapedTextRun shapedTextRun) { @@ -1013,7 +1012,7 @@ namespace Avalonia.Media.TextFormatting private void BidiReorder() { - if (_textRuns.Count == 0) + if (_textRuns.Length == 0) { return; } @@ -1025,7 +1024,7 @@ namespace Avalonia.Media.TextFormatting var current = orderedRun; - for (var i = 1; i < _textRuns.Count; i++) + for (var i = 1; i < _textRuns.Length; i++) { run = _textRuns[i]; @@ -1044,7 +1043,7 @@ namespace Avalonia.Media.TextFormatting sbyte max = 0; var min = sbyte.MaxValue; - for (var i = 0; i < _textRuns.Count; i++) + for (var i = 0; i < _textRuns.Length; i++) { var currentRun = _textRuns[i]; @@ -1095,13 +1094,14 @@ namespace Avalonia.Media.TextFormatting minLevelToReverse--; } - var textRuns = new List(_textRuns.Count); + var textRuns = new TextRun[_textRuns.Length]; + var index = 0; current = orderedRun; while (current != null) { - textRuns.Add(current.Run); + textRuns[index++] = current.Run; current = current.Next; } @@ -1197,7 +1197,7 @@ namespace Avalonia.Media.TextFormatting var runIndex = GetRunIndexAtCharacterIndex(codepointIndex, LogicalDirection.Forward, out var currentPosition); - while (runIndex < _textRuns.Count) + while (runIndex < _textRuns.Length) { var currentRun = _textRuns[runIndex]; @@ -1346,7 +1346,7 @@ namespace Avalonia.Media.TextFormatting textPosition = FirstTextSourceIndex; TextRun? previousRun = null; - while (runIndex < _textRuns.Count) + while (runIndex < _textRuns.Length) { var currentRun = _textRuns[runIndex]; @@ -1395,7 +1395,7 @@ namespace Avalonia.Media.TextFormatting } } - if (runIndex + 1 >= _textRuns.Count) + if (runIndex + 1 >= _textRuns.Length) { return runIndex; } @@ -1411,7 +1411,7 @@ namespace Avalonia.Media.TextFormatting return runIndex; } - if (runIndex + 1 >= _textRuns.Count) + if (runIndex + 1 >= _textRuns.Length) { return runIndex; } @@ -1448,14 +1448,14 @@ namespace Avalonia.Media.TextFormatting var lineHeight = _paragraphProperties.LineHeight; - var lastRunIndex = _textRuns.Count - 1; + var lastRunIndex = _textRuns.Length - 1; if (lastRunIndex > 0 && _textRuns[lastRunIndex] is TextEndOfLine) { lastRunIndex--; } - for (var index = 0; index < _textRuns.Count; index++) + for (var index = 0; index < _textRuns.Length; index++) { switch (_textRuns[index]) { diff --git a/src/Avalonia.Base/Media/TextFormatting/TextTrailingCharacterEllipsis.cs b/src/Avalonia.Base/Media/TextFormatting/TextTrailingCharacterEllipsis.cs index deecbbe476..ccae99cc75 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextTrailingCharacterEllipsis.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextTrailingCharacterEllipsis.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace Avalonia.Media.TextFormatting +namespace Avalonia.Media.TextFormatting { /// /// A collapsing properties to collapse whole line toward the end @@ -26,7 +24,8 @@ namespace Avalonia.Media.TextFormatting /// public override TextRun Symbol { get; } - public override List? Collapse(TextLine textLine) + /// + public override TextRun[]? Collapse(TextLine textLine) { return TextEllipsisHelper.Collapse(textLine, this, false); } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextTrailingWordEllipsis.cs b/src/Avalonia.Base/Media/TextFormatting/TextTrailingWordEllipsis.cs index c291e1dfb9..c622c76a60 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextTrailingWordEllipsis.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextTrailingWordEllipsis.cs @@ -1,7 +1,4 @@ -using System.Collections.Generic; -using Avalonia.Utilities; - -namespace Avalonia.Media.TextFormatting +namespace Avalonia.Media.TextFormatting { /// /// a collapsing properties to collapse whole line toward the end @@ -31,7 +28,8 @@ namespace Avalonia.Media.TextFormatting /// public override TextRun Symbol { get; } - public override List? Collapse(TextLine textLine) + /// + public override TextRun[]? Collapse(TextLine textLine) { return TextEllipsisHelper.Collapse(textLine, this, true); }