|
|
|
@ -32,58 +32,64 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
var fetchedRuns = FetchTextRuns(textSource, firstTextSourceIndex, objectPool, |
|
|
|
out var textEndOfLine, out var textSourceLength); |
|
|
|
|
|
|
|
RentedList<TextRun>? shapedTextRuns; |
|
|
|
RentedList<TextRun>? 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); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -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 |
|
|
|
|