Browse Source

Merge branch 'master' into fixes/revertChanges

pull/10063/head
Max Katz 4 years ago
committed by GitHub
parent
commit
5c438eb6cf
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      src/Avalonia.Base/Media/TextFormatting/FormattingObjectPool.cs
  2. 20
      src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs
  3. 154
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  4. 167
      src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
  5. 12
      src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs

11
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!");
}
}
}

20
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);
}
}
}
}

154
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<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

167
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();
}
}
/// <summary>

12
src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs

@ -86,7 +86,6 @@ namespace Avalonia.Media.TextFormatting
RentedList<TextRun>? rentedPreSplitRuns = null;
RentedList<TextRun>? 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 };

Loading…
Cancel
Save