|
|
@@ -2,7 +2,6 @@
|
|
|
using System;
|
|
|
using System.Buffers;
|
|
|
using System.Collections.Generic;
|
|
|
-using System.Linq;
|
|
|
using System.Runtime.InteropServices;
|
|
|
using Avalonia.Media.TextFormatting.Unicode;
|
|
|
using Avalonia.Utilities;
|
|
|
@@ -22,68 +21,55 @@ namespace Avalonia.Media.TextFormatting
|
|
|
public override TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth,
|
|
|
TextParagraphProperties paragraphProperties, TextLineBreak? previousLineBreak = null)
|
|
|
{
|
|
|
- var textWrapping = paragraphProperties.TextWrapping;
|
|
|
- FlowDirection resolvedFlowDirection;
|
|
|
TextLineBreak? nextLineBreak = null;
|
|
|
- IReadOnlyList<TextRun>? textRuns;
|
|
|
var objectPool = FormattingObjectPool.Instance;
|
|
|
var fontManager = FontManager.Current;
|
|
|
|
|
|
- var fetchedRuns = FetchTextRuns(textSource, firstTextSourceIndex, objectPool,
|
|
|
- out var textEndOfLine, out var textSourceLength);
|
|
|
+ // we've wrapped the previous line and need to continue wrapping: ignore the textSource and do that instead
|
|
|
+ if (previousLineBreak is WrappingTextLineBreak wrappingTextLineBreak
|
|
|
+ && wrappingTextLineBreak.AcquireRemainingRuns() is { } remainingRuns
|
|
|
+ && paragraphProperties.TextWrapping != TextWrapping.NoWrap)
|
|
|
+ {
|
|
|
+ return PerformTextWrapping(remainingRuns, true, firstTextSourceIndex, paragraphWidth,
|
|
|
+ paragraphProperties, previousLineBreak.FlowDirection, previousLineBreak, objectPool);
|
|
|
+ }
|
|
|
|
|
|
+ RentedList<TextRun>? fetchedRuns = null;
|
|
|
RentedList<TextRun>? shapedTextRuns = null;
|
|
|
-
|
|
|
try
|
|
|
{
|
|
|
- if (previousLineBreak?.RemainingRuns is { } remainingRuns)
|
|
|
- {
|
|
|
- resolvedFlowDirection = previousLineBreak.FlowDirection;
|
|
|
- textRuns = remainingRuns;
|
|
|
- nextLineBreak = previousLineBreak;
|
|
|
- shapedTextRuns = null;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- shapedTextRuns = ShapeTextRuns(fetchedRuns, paragraphProperties, objectPool, fontManager,
|
|
|
- out resolvedFlowDirection);
|
|
|
- textRuns = shapedTextRuns;
|
|
|
+ fetchedRuns = FetchTextRuns(textSource, firstTextSourceIndex, objectPool, out var textEndOfLine,
|
|
|
+ out var textSourceLength);
|
|
|
|
|
|
- if (nextLineBreak == null && textEndOfLine != null)
|
|
|
- {
|
|
|
- nextLineBreak = new TextLineBreak(textEndOfLine, resolvedFlowDirection);
|
|
|
- }
|
|
|
- }
|
|
|
+ shapedTextRuns = ShapeTextRuns(fetchedRuns, paragraphProperties, objectPool, fontManager,
|
|
|
+ out var resolvedFlowDirection);
|
|
|
|
|
|
- TextLineImpl textLine;
|
|
|
+ if (nextLineBreak == null && textEndOfLine != null)
|
|
|
+ {
|
|
|
+ nextLineBreak = new TextLineBreak(textEndOfLine, resolvedFlowDirection);
|
|
|
+ }
|
|
|
|
|
|
- switch (textWrapping)
|
|
|
+ switch (paragraphProperties.TextWrapping)
|
|
|
{
|
|
|
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,
|
|
|
+ var textLine = new TextLineImpl(shapedTextRuns.ToArray(), firstTextSourceIndex,
|
|
|
+ textSourceLength,
|
|
|
paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak);
|
|
|
|
|
|
textLine.FinalizeLine();
|
|
|
|
|
|
- break;
|
|
|
+ return textLine;
|
|
|
}
|
|
|
case TextWrapping.WrapWithOverflow:
|
|
|
case TextWrapping.Wrap:
|
|
|
{
|
|
|
- textLine = PerformTextWrapping(textRuns, firstTextSourceIndex, paragraphWidth,
|
|
|
- paragraphProperties, resolvedFlowDirection, nextLineBreak, objectPool, fontManager);
|
|
|
- break;
|
|
|
+ return PerformTextWrapping(shapedTextRuns, false, firstTextSourceIndex, paragraphWidth,
|
|
|
+ paragraphProperties, resolvedFlowDirection, nextLineBreak, objectPool);
|
|
|
}
|
|
|
default:
|
|
|
- throw new ArgumentOutOfRangeException(nameof(textWrapping));
|
|
|
+ throw new ArgumentOutOfRangeException(nameof(paragraphProperties.TextWrapping));
|
|
|
}
|
|
|
-
|
|
|
- return textLine;
|
|
|
}
|
|
|
finally
|
|
|
{
|
|
|
@@ -108,15 +94,16 @@ namespace Avalonia.Media.TextFormatting
|
|
|
for (var i = 0; i < textRuns.Count; i++)
|
|
|
{
|
|
|
var currentRun = textRuns[i];
|
|
|
+ var currentRunLength = currentRun.Length;
|
|
|
|
|
|
- if (currentLength + currentRun.Length < length)
|
|
|
+ if (currentLength + currentRunLength < length)
|
|
|
{
|
|
|
- currentLength += currentRun.Length;
|
|
|
+ currentLength += currentRunLength;
|
|
|
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
- var firstCount = currentRun.Length >= 1 ? i + 1 : i;
|
|
|
+ var firstCount = currentRunLength >= 1 ? i + 1 : i;
|
|
|
|
|
|
if (firstCount > 1)
|
|
|
{
|
|
|
@@ -128,13 +115,13 @@ namespace Avalonia.Media.TextFormatting
|
|
|
|
|
|
var secondCount = textRuns.Count - firstCount;
|
|
|
|
|
|
- if (currentLength + currentRun.Length == length)
|
|
|
+ if (currentLength + currentRunLength == length)
|
|
|
{
|
|
|
var second = secondCount > 0 ? objectPool.TextRunLists.Rent() : null;
|
|
|
|
|
|
if (second != null)
|
|
|
{
|
|
|
- var offset = currentRun.Length >= 1 ? 1 : 0;
|
|
|
+ var offset = currentRunLength >= 1 ? 1 : 0;
|
|
|
|
|
|
for (var j = 0; j < secondCount; j++)
|
|
|
{
|
|
|
@@ -653,7 +640,7 @@ namespace Avalonia.Media.TextFormatting
|
|
|
/// </summary>
|
|
|
/// <returns>The empty text line.</returns>
|
|
|
public static TextLineImpl CreateEmptyTextLine(int firstTextSourceIndex, double paragraphWidth,
|
|
|
- TextParagraphProperties paragraphProperties, FontManager fontManager)
|
|
|
+ TextParagraphProperties paragraphProperties)
|
|
|
{
|
|
|
var flowDirection = paragraphProperties.FlowDirection;
|
|
|
var properties = paragraphProperties.DefaultTextRunProperties;
|
|
|
@@ -675,21 +662,21 @@ namespace Avalonia.Media.TextFormatting
|
|
|
/// Performs text wrapping returns a list of text lines.
|
|
|
/// </summary>
|
|
|
/// <param name="textRuns"></param>
|
|
|
+ /// <param name="canReuseTextRunList">Whether <see cref="textRuns"/> can be reused to store the split runs.</param>
|
|
|
/// <param name="firstTextSourceIndex">The first text source index.</param>
|
|
|
/// <param name="paragraphWidth">The paragraph width.</param>
|
|
|
/// <param name="paragraphProperties">The text paragraph properties.</param>
|
|
|
/// <param name="resolvedFlowDirection"></param>
|
|
|
/// <param name="currentLineBreak">The current line break if the line was explicitly broken.</param>
|
|
|
/// <param name="objectPool">A pool used to get reusable formatting objects.</param>
|
|
|
- /// <param name="fontManager">The font manager to use.</param>
|
|
|
/// <returns>The wrapped text line.</returns>
|
|
|
- private static TextLineImpl PerformTextWrapping(IReadOnlyList<TextRun> textRuns, int firstTextSourceIndex,
|
|
|
- double paragraphWidth, TextParagraphProperties paragraphProperties, FlowDirection resolvedFlowDirection,
|
|
|
- TextLineBreak? currentLineBreak, FormattingObjectPool objectPool, FontManager fontManager)
|
|
|
+ private static TextLineImpl PerformTextWrapping(List<TextRun> textRuns, bool canReuseTextRunList,
|
|
|
+ int firstTextSourceIndex, double paragraphWidth, TextParagraphProperties paragraphProperties,
|
|
|
+ FlowDirection resolvedFlowDirection, TextLineBreak? currentLineBreak, FormattingObjectPool objectPool)
|
|
|
{
|
|
|
if (textRuns.Count == 0)
|
|
|
{
|
|
|
- return CreateEmptyTextLine(firstTextSourceIndex, paragraphWidth, paragraphProperties, fontManager);
|
|
|
+ return CreateEmptyTextLine(firstTextSourceIndex, paragraphWidth, paragraphProperties);
|
|
|
}
|
|
|
|
|
|
if (!TryMeasureLength(textRuns, paragraphWidth, out var measuredLength))
|
|
|
@@ -819,13 +806,37 @@ namespace Avalonia.Media.TextFormatting
|
|
|
|
|
|
try
|
|
|
{
|
|
|
- var textLineBreak = postSplitRuns?.Count > 0 ?
|
|
|
- new TextLineBreak(null, resolvedFlowDirection, postSplitRuns.ToArray()) :
|
|
|
- null;
|
|
|
+ TextLineBreak? textLineBreak;
|
|
|
+ if (postSplitRuns?.Count > 0)
|
|
|
+ {
|
|
|
+ List<TextRun> remainingRuns;
|
|
|
+
|
|
|
+ // reuse the list as much as possible:
|
|
|
+ // if canReuseTextRunList == true it's coming from previous remaining runs
|
|
|
+ if (canReuseTextRunList)
|
|
|
+ {
|
|
|
+ remainingRuns = textRuns;
|
|
|
+ remainingRuns.Clear();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ remainingRuns = new List<TextRun>();
|
|
|
+ }
|
|
|
|
|
|
- if (textLineBreak is null && currentLineBreak?.TextEndOfLine != null)
|
|
|
+ for (var i = 0; i < postSplitRuns.Count; ++i)
|
|
|
+ {
|
|
|
+ remainingRuns.Add(postSplitRuns[i]);
|
|
|
+ }
|
|
|
+
|
|
|
+ textLineBreak = new WrappingTextLineBreak(null, resolvedFlowDirection, remainingRuns);
|
|
|
+ }
|
|
|
+ else if (currentLineBreak?.TextEndOfLine is { } textEndOfLine)
|
|
|
{
|
|
|
- textLineBreak = new TextLineBreak(currentLineBreak.TextEndOfLine, resolvedFlowDirection);
|
|
|
+ textLineBreak = new TextLineBreak(textEndOfLine, resolvedFlowDirection);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ textLineBreak = null;
|
|
|
}
|
|
|
|
|
|
var textLine = new TextLineImpl(preSplitRuns.ToArray(), firstTextSourceIndex, measuredLength,
|
|
|
@@ -833,6 +844,7 @@ namespace Avalonia.Media.TextFormatting
|
|
|
textLineBreak);
|
|
|
|
|
|
textLine.FinalizeLine();
|
|
|
+
|
|
|
return textLine;
|
|
|
}
|
|
|
finally
|