diff --git a/src/Avalonia.Base/Media/TextFormatting/FormattingObjectPool.cs b/src/Avalonia.Base/Media/TextFormatting/FormattingObjectPool.cs index cb8168e693..c7cd58eb6d 100644 --- a/src/Avalonia.Base/Media/TextFormatting/FormattingObjectPool.cs +++ b/src/Avalonia.Base/Media/TextFormatting/FormattingObjectPool.cs @@ -93,16 +93,19 @@ namespace Avalonia.Media.TextFormatting [Conditional("DEBUG")] public void VerifyAllReturned() { - if (_pendingReturnCount > 0) + var pendingReturnCount = _pendingReturnCount; + _pendingReturnCount = 0; + + if (pendingReturnCount > 0) { throw new InvalidOperationException( - $"{_pendingReturnCount} RentedList<{typeof(T).Name} haven't been returned to the pool!"); + $"{pendingReturnCount} RentedList<{typeof(T).Name}> haven't been returned to the pool!"); } - if (_pendingReturnCount < 0) + if (pendingReturnCount < 0) { throw new InvalidOperationException( - $"{-_pendingReturnCount} RentedList<{typeof(T).Name} extra lists have been returned to the pool!"); + $"{-pendingReturnCount} RentedList<{typeof(T).Name}> extra lists have been returned to the pool!"); } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs b/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs index 47973e37b5..4c93a1d851 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs @@ -113,14 +113,18 @@ namespace Avalonia.Media.TextFormatting var (preSplitRuns, postSplitRuns) = TextFormatterImpl.SplitTextRuns(textRuns, collapsedLength, objectPool); - var collapsedRuns = new TextRun[preSplitRuns.Count + 1]; - preSplitRuns.CopyTo(collapsedRuns); - collapsedRuns[collapsedRuns.Length - 1] = shapedSymbol; - - objectPool.TextRunLists.Return(ref preSplitRuns); - objectPool.TextRunLists.Return(ref postSplitRuns); - - return collapsedRuns; + try + { + var collapsedRuns = new TextRun[preSplitRuns.Count + 1]; + preSplitRuns.CopyTo(collapsedRuns); + collapsedRuns[collapsedRuns.Length - 1] = shapedSymbol; + return collapsedRuns; + } + finally + { + objectPool.TextRunLists.Return(ref preSplitRuns); + objectPool.TextRunLists.Return(ref postSplitRuns); + } } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index 7de842ab39..7505b9ccdd 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -32,58 +32,64 @@ namespace Avalonia.Media.TextFormatting var fetchedRuns = FetchTextRuns(textSource, firstTextSourceIndex, objectPool, out var textEndOfLine, out var textSourceLength); - RentedList? shapedTextRuns; + RentedList? shapedTextRuns = null; - if (previousLineBreak?.RemainingRuns is { } remainingRuns) + try { - resolvedFlowDirection = previousLineBreak.FlowDirection; - textRuns = remainingRuns; - nextLineBreak = previousLineBreak; - shapedTextRuns = null; - } - else - { - shapedTextRuns = ShapeTextRuns(fetchedRuns, paragraphProperties, objectPool, fontManager, out resolvedFlowDirection); - textRuns = shapedTextRuns; - - if (nextLineBreak == null && textEndOfLine != null) + if (previousLineBreak?.RemainingRuns is { } remainingRuns) { - nextLineBreak = new TextLineBreak(textEndOfLine, resolvedFlowDirection); + resolvedFlowDirection = previousLineBreak.FlowDirection; + textRuns = remainingRuns; + nextLineBreak = previousLineBreak; + shapedTextRuns = null; } - } + else + { + shapedTextRuns = ShapeTextRuns(fetchedRuns, paragraphProperties, objectPool, fontManager, + out resolvedFlowDirection); + textRuns = shapedTextRuns; - TextLineImpl textLine; + if (nextLineBreak == null && textEndOfLine != null) + { + nextLineBreak = new TextLineBreak(textEndOfLine, resolvedFlowDirection); + } + } - switch (textWrapping) - { - case TextWrapping.NoWrap: + TextLineImpl textLine; + + switch (textWrapping) { - // perf note: if textRuns comes from remainingRuns above, it's very likely coming from this class - // which already uses an array: ToArray() won't ever be called in this case - var textRunArray = textRuns as TextRun[] ?? textRuns.ToArray(); + case TextWrapping.NoWrap: + { + // perf note: if textRuns comes from remainingRuns above, it's very likely coming from this class + // which already uses an array: ToArray() won't ever be called in this case + var textRunArray = textRuns as TextRun[] ?? textRuns.ToArray(); - textLine = new TextLineImpl(textRunArray, firstTextSourceIndex, textSourceLength, - paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak); + textLine = new TextLineImpl(textRunArray, firstTextSourceIndex, textSourceLength, + paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak); - textLine.FinalizeLine(); + textLine.FinalizeLine(); - break; - } - case TextWrapping.WrapWithOverflow: - case TextWrapping.Wrap: - { - textLine = PerformTextWrapping(textRuns, firstTextSourceIndex, paragraphWidth, - paragraphProperties, resolvedFlowDirection, nextLineBreak, objectPool, fontManager); - break; + break; + } + case TextWrapping.WrapWithOverflow: + case TextWrapping.Wrap: + { + textLine = PerformTextWrapping(textRuns, firstTextSourceIndex, paragraphWidth, + paragraphProperties, resolvedFlowDirection, nextLineBreak, objectPool, fontManager); + break; + } + default: + throw new ArgumentOutOfRangeException(nameof(textWrapping)); } - default: - throw new ArgumentOutOfRangeException(nameof(textWrapping)); - } - - objectPool.TextRunLists.Return(ref shapedTextRuns); - objectPool.TextRunLists.Return(ref fetchedRuns); - return textLine; + return textLine; + } + finally + { + objectPool.TextRunLists.Return(ref shapedTextRuns); + objectPool.TextRunLists.Return(ref fetchedRuns); + } } /// @@ -224,23 +230,26 @@ namespace Avalonia.Media.TextFormatting (resolvedEmbeddingLevel & 1) == 0 ? FlowDirection.LeftToRight : FlowDirection.RightToLeft; var processedRuns = objectPool.TextRunLists.Rent(); + var groupedRuns = objectPool.UnshapedTextRunLists.Rent(); - CoalesceLevels(textRuns, bidiAlgorithm.ResolvedLevels.Span, fontManager, processedRuns); + try + { + CoalesceLevels(textRuns, bidiAlgorithm.ResolvedLevels.Span, fontManager, processedRuns); - bidiData.Reset(); - bidiAlgorithm.Reset(); + bidiData.Reset(); + bidiAlgorithm.Reset(); - var groupedRuns = objectPool.UnshapedTextRunLists.Rent(); - var textShaper = TextShaper.Current; - for (var index = 0; index < processedRuns.Count; index++) - { - var currentRun = processedRuns[index]; + var textShaper = TextShaper.Current; - switch (currentRun) + for (var index = 0; index < processedRuns.Count; index++) { - case UnshapedTextRun shapeableRun: + var currentRun = processedRuns[index]; + + switch (currentRun) { + case UnshapedTextRun shapeableRun: + { groupedRuns.Clear(); groupedRuns.Add(shapeableRun); @@ -277,17 +286,20 @@ namespace Avalonia.Media.TextFormatting break; } - default: + default: { shapedRuns.Add(currentRun); break; } + } } } - - objectPool.TextRunLists.Return(ref processedRuns); - objectPool.UnshapedTextRunLists.Return(ref groupedRuns); + finally + { + objectPool.TextRunLists.Return(ref processedRuns); + objectPool.UnshapedTextRunLists.Return(ref groupedRuns); + } return shapedRuns; } @@ -805,25 +817,29 @@ namespace Avalonia.Media.TextFormatting var (preSplitRuns, postSplitRuns) = SplitTextRuns(textRuns, measuredLength, objectPool); - var textLineBreak = postSplitRuns?.Count > 0 ? - new TextLineBreak(null, resolvedFlowDirection, postSplitRuns.ToArray()) : - null; - - if (textLineBreak is null && currentLineBreak?.TextEndOfLine != null) + try { - textLineBreak = new TextLineBreak(currentLineBreak.TextEndOfLine, resolvedFlowDirection); - } + var textLineBreak = postSplitRuns?.Count > 0 ? + new TextLineBreak(null, resolvedFlowDirection, postSplitRuns.ToArray()) : + null; - var textLine = new TextLineImpl(preSplitRuns.ToArray(), firstTextSourceIndex, measuredLength, - paragraphWidth, paragraphProperties, resolvedFlowDirection, - textLineBreak); - - textLine.FinalizeLine(); + if (textLineBreak is null && currentLineBreak?.TextEndOfLine != null) + { + textLineBreak = new TextLineBreak(currentLineBreak.TextEndOfLine, resolvedFlowDirection); + } - objectPool.TextRunLists.Return(ref preSplitRuns); - objectPool.TextRunLists.Return(ref postSplitRuns); + var textLine = new TextLineImpl(preSplitRuns.ToArray(), firstTextSourceIndex, measuredLength, + paragraphWidth, paragraphProperties, resolvedFlowDirection, + textLineBreak); - return textLine; + textLine.FinalizeLine(); + return textLine; + } + finally + { + objectPool.TextRunLists.Return(ref preSplitRuns); + objectPool.TextRunLists.Return(ref postSplitRuns); + } } private struct TextRunEnumerator diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs index bb58e0d692..4923cdbe32 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs @@ -441,128 +441,133 @@ namespace Avalonia.Media.TextFormatting var textLines = objectPool.TextLines.Rent(); - double left = double.PositiveInfinity, width = 0.0, height = 0.0; - - _textSourceLength = 0; + try + { + double left = double.PositiveInfinity, width = 0.0, height = 0.0; - TextLine? previousLine = null; + _textSourceLength = 0; - var textFormatter = TextFormatter.Current; + TextLine? previousLine = null; - while (true) - { - var textLine = textFormatter.FormatLine(_textSource, _textSourceLength, MaxWidth, _paragraphProperties, - previousLine?.TextLineBreak); + var textFormatter = TextFormatter.Current; - if (textLine.Length == 0) + while (true) { - if (previousLine != null && previousLine.NewLineLength > 0) + var textLine = textFormatter.FormatLine(_textSource, _textSourceLength, MaxWidth, + _paragraphProperties, previousLine?.TextLineBreak); + + if (textLine.Length == 0) { - var emptyTextLine = TextFormatterImpl.CreateEmptyTextLine(_textSourceLength, MaxWidth, - _paragraphProperties, fontManager); + if (previousLine != null && previousLine.NewLineLength > 0) + { + var emptyTextLine = TextFormatterImpl.CreateEmptyTextLine(_textSourceLength, MaxWidth, + _paragraphProperties, fontManager); - textLines.Add(emptyTextLine); + textLines.Add(emptyTextLine); - UpdateBounds(emptyTextLine, ref left, ref width, ref height); - } + UpdateBounds(emptyTextLine, ref left, ref width, ref height); + } - break; - } + break; + } - _textSourceLength += textLine.Length; + _textSourceLength += textLine.Length; - //Fulfill max height constraint - if (textLines.Count > 0 && !double.IsPositiveInfinity(MaxHeight) && height + textLine.Height > MaxHeight) - { - if (previousLine?.TextLineBreak != null && _textTrimming != TextTrimming.None) + //Fulfill max height constraint + if (textLines.Count > 0 && !double.IsPositiveInfinity(MaxHeight) + && height + textLine.Height > MaxHeight) { - var collapsedLine = - previousLine.Collapse(GetCollapsingProperties(MaxWidth)); + if (previousLine?.TextLineBreak != null && _textTrimming != TextTrimming.None) + { + var collapsedLine = + previousLine.Collapse(GetCollapsingProperties(MaxWidth)); - textLines[textLines.Count - 1] = collapsedLine; - } + textLines[textLines.Count - 1] = collapsedLine; + } - break; - } + break; + } - var hasOverflowed = textLine.HasOverflowed; + var hasOverflowed = textLine.HasOverflowed; - if (hasOverflowed && _textTrimming != TextTrimming.None) - { - textLine = textLine.Collapse(GetCollapsingProperties(MaxWidth)); - } + if (hasOverflowed && _textTrimming != TextTrimming.None) + { + textLine = textLine.Collapse(GetCollapsingProperties(MaxWidth)); + } - textLines.Add(textLine); + textLines.Add(textLine); - UpdateBounds(textLine, ref left, ref width, ref height); + UpdateBounds(textLine, ref left, ref width, ref height); - previousLine = textLine; + previousLine = textLine; - //Fulfill max lines constraint - if (MaxLines > 0 && textLines.Count >= MaxLines) - { - if(textLine.TextLineBreak?.RemainingRuns is not null) + //Fulfill max lines constraint + if (MaxLines > 0 && textLines.Count >= MaxLines) { - textLines[textLines.Count - 1] = textLine.Collapse(GetCollapsingProperties(width)); + if (textLine.TextLineBreak?.RemainingRuns is not null) + { + textLines[textLines.Count - 1] = textLine.Collapse(GetCollapsingProperties(width)); + } + + break; } - break; + if (textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph) + { + break; + } } - if (textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph) + //Make sure the TextLayout always contains at least on empty line + if (textLines.Count == 0) { - break; - } - } + var textLine = + TextFormatterImpl.CreateEmptyTextLine(0, MaxWidth, _paragraphProperties, fontManager); - //Make sure the TextLayout always contains at least on empty line - if (textLines.Count == 0) - { - var textLine = TextFormatterImpl.CreateEmptyTextLine(0, MaxWidth, _paragraphProperties, fontManager); - - textLines.Add(textLine); - - UpdateBounds(textLine, ref left, ref width, ref height); - } + textLines.Add(textLine); - Bounds = new Rect(left, 0, width, height); + UpdateBounds(textLine, ref left, ref width, ref height); + } - if (_paragraphProperties.TextAlignment == TextAlignment.Justify) - { - var whitespaceWidth = 0d; + Bounds = new Rect(left, 0, width, height); - for (var i = 0; i < textLines.Count; i++) + if (_paragraphProperties.TextAlignment == TextAlignment.Justify) { - var line = textLines[i]; - var lineWhitespaceWidth = line.Width - line.WidthIncludingTrailingWhitespace; + var whitespaceWidth = 0d; - if (lineWhitespaceWidth > whitespaceWidth) + for (var i = 0; i < textLines.Count; i++) { - whitespaceWidth = lineWhitespaceWidth; - } - } + var line = textLines[i]; + var lineWhitespaceWidth = line.Width - line.WidthIncludingTrailingWhitespace; - var justificationWidth = width - whitespaceWidth; + if (lineWhitespaceWidth > whitespaceWidth) + { + whitespaceWidth = lineWhitespaceWidth; + } + } - if (justificationWidth > 0) - { - var justificationProperties = new InterWordJustification(justificationWidth); + var justificationWidth = width - whitespaceWidth; - for (var i = 0; i < textLines.Count - 1; i++) + if (justificationWidth > 0) { - var line = textLines[i]; + var justificationProperties = new InterWordJustification(justificationWidth); - line.Justify(justificationProperties); + for (var i = 0; i < textLines.Count - 1; i++) + { + var line = textLines[i]; + + line.Justify(justificationProperties); + } } } - } - var result = textLines.ToArray(); - - objectPool.TextLines.Return(ref textLines); - objectPool.VerifyAllReturned(); - - return result; + return textLines.ToArray(); + } + finally + { + objectPool.TextLines.Return(ref textLines); + objectPool.VerifyAllReturned(); + } } /// diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs b/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs index 0d777ad043..2e85b1e187 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs @@ -86,7 +86,6 @@ namespace Avalonia.Media.TextFormatting RentedList? rentedPreSplitRuns = null; RentedList? rentedPostSplitRuns = null; - TextRun[]? results; try { @@ -113,9 +112,7 @@ namespace Avalonia.Media.TextFormatting if (measuredLength <= _prefixLength || effectivePostSplitRuns is null) { - results = collapsedRuns.ToArray(); - objectPool.TextRunLists.Return(ref collapsedRuns); - return results; + return collapsedRuns.ToArray(); } var availableSuffixWidth = availableWidth; @@ -157,16 +154,15 @@ namespace Avalonia.Media.TextFormatting } } } + + return collapsedRuns.ToArray(); } finally { objectPool.TextRunLists.Return(ref rentedPreSplitRuns); objectPool.TextRunLists.Return(ref rentedPostSplitRuns); + objectPool.TextRunLists.Return(ref collapsedRuns); } - - results = collapsedRuns.ToArray(); - objectPool.TextRunLists.Return(ref collapsedRuns); - return results; } return new TextRun[] { shapedSymbol };