Browse Source

Reuse the shaped text runs as much as possible

pull/4437/head
Benedikt Schroeder 6 years ago
parent
commit
4f7a701ab7
  1. 79
      src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
  2. 5
      src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
  3. 4
      src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
  4. 10
      src/Skia/Avalonia.Skia/TextShaperImpl.cs

79
src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs

@ -31,7 +31,8 @@ namespace Avalonia.Media.TextFormatting
case TextWrapping.WrapWithOverflow: case TextWrapping.WrapWithOverflow:
case TextWrapping.Wrap: case TextWrapping.Wrap:
{ {
textLine = PerformTextWrapping(textRuns, textRange, paragraphWidth, paragraphProperties); textLine = PerformTextWrapping(textRuns, textRange, paragraphWidth, paragraphProperties,
nextLineBreak);
break; break;
} }
default: default:
@ -118,7 +119,7 @@ namespace Avalonia.Media.TextFormatting
/// <param name="textRuns">The text run's.</param> /// <param name="textRuns">The text run's.</param>
/// <param name="length">The length to split at.</param> /// <param name="length">The length to split at.</param>
/// <returns>The split text runs.</returns> /// <returns>The split text runs.</returns>
internal static SplitTextRunsResult SplitTextRuns(IReadOnlyList<ShapedTextCharacters> textRuns, int length) internal static SplitTextRunsResult SplitTextRuns(List<ShapedTextCharacters> textRuns, int length)
{ {
var currentLength = 0; var currentLength = 0;
@ -134,13 +135,13 @@ namespace Avalonia.Media.TextFormatting
var firstCount = currentRun.GlyphRun.Characters.Length >= 1 ? i + 1 : i; var firstCount = currentRun.GlyphRun.Characters.Length >= 1 ? i + 1 : i;
var first = new ShapedTextCharacters[firstCount]; var first = new List<ShapedTextCharacters>(firstCount);
if (firstCount > 1) if (firstCount > 1)
{ {
for (var j = 0; j < i; j++) for (var j = 0; j < i; j++)
{ {
first[j] = textRuns[j]; first.Add(textRuns[j]);
} }
} }
@ -148,7 +149,7 @@ namespace Avalonia.Media.TextFormatting
if (currentLength + currentRun.GlyphRun.Characters.Length == length) if (currentLength + currentRun.GlyphRun.Characters.Length == length)
{ {
var second = new ShapedTextCharacters[secondCount]; var second = new List<ShapedTextCharacters>(secondCount);
var offset = currentRun.GlyphRun.Characters.Length > 1 ? 1 : 0; var offset = currentRun.GlyphRun.Characters.Length > 1 ? 1 : 0;
@ -156,11 +157,11 @@ namespace Avalonia.Media.TextFormatting
{ {
for (var j = 0; j < secondCount; j++) for (var j = 0; j < secondCount; j++)
{ {
second[j] = textRuns[i + j + offset]; second.Add(textRuns[i + j + offset]);
} }
} }
first[i] = currentRun; first.Add(currentRun);
return new SplitTextRunsResult(first, second); return new SplitTextRunsResult(first, second);
} }
@ -168,22 +169,22 @@ namespace Avalonia.Media.TextFormatting
{ {
secondCount++; secondCount++;
var second = new ShapedTextCharacters[secondCount]; var second = new List<ShapedTextCharacters>(secondCount);
var split = currentRun.Split(length - currentLength);
first.Add(split.First);
second.Add(split.Second);
if (secondCount > 0) if (secondCount > 0)
{ {
for (var j = 1; j < secondCount; j++) for (var j = 1; j < secondCount; j++)
{ {
second[j] = textRuns[i + j]; second.Add(textRuns[i + j]);
} }
} }
var split = currentRun.Split(length - currentLength);
first[i] = split.First;
second[0] = split.Second;
return new SplitTextRunsResult(first, second); return new SplitTextRunsResult(first, second);
} }
} }
@ -201,7 +202,7 @@ namespace Avalonia.Media.TextFormatting
/// <returns> /// <returns>
/// The formatted text runs. /// The formatted text runs.
/// </returns> /// </returns>
private static IReadOnlyList<ShapedTextCharacters> FetchTextRuns(ITextSource textSource, private static List<ShapedTextCharacters> FetchTextRuns(ITextSource textSource,
int firstTextSourceIndex, TextLineBreak previousLineBreak, out TextLineBreak nextLineBreak) int firstTextSourceIndex, TextLineBreak previousLineBreak, out TextLineBreak nextLineBreak)
{ {
nextLineBreak = default; nextLineBreak = default;
@ -212,8 +213,10 @@ namespace Avalonia.Media.TextFormatting
if (previousLineBreak != null) if (previousLineBreak != null)
{ {
foreach (var shapedCharacters in previousLineBreak.RemainingCharacters) for (var index = 0; index < previousLineBreak.RemainingCharacters.Count; index++)
{ {
var shapedCharacters = previousLineBreak.RemainingCharacters[index];
if (shapedCharacters == null) if (shapedCharacters == null)
{ {
continue; continue;
@ -225,6 +228,14 @@ namespace Avalonia.Media.TextFormatting
{ {
var splitResult = SplitTextRuns(textRuns, currentLength + runLineBreak.PositionWrap); var splitResult = SplitTextRuns(textRuns, currentLength + runLineBreak.PositionWrap);
if (++index < previousLineBreak.RemainingCharacters.Count)
{
for (; index < previousLineBreak.RemainingCharacters.Count; index++)
{
splitResult.Second.Add(previousLineBreak.RemainingCharacters[index]);
}
}
nextLineBreak = new TextLineBreak(splitResult.Second); nextLineBreak = new TextLineBreak(splitResult.Second);
return splitResult.First; return splitResult.First;
@ -323,9 +334,10 @@ namespace Avalonia.Media.TextFormatting
/// <param name="textRange">The text range that is covered by the text runs.</param> /// <param name="textRange">The text range that is covered by the text runs.</param>
/// <param name="paragraphWidth">The paragraph width.</param> /// <param name="paragraphWidth">The paragraph width.</param>
/// <param name="paragraphProperties">The text paragraph properties.</param> /// <param name="paragraphProperties">The text paragraph properties.</param>
/// <param name="currentLineBreak">The current line break if the line was explicitly broken.</param>
/// <returns>The wrapped text line.</returns> /// <returns>The wrapped text line.</returns>
private static TextLine PerformTextWrapping(IReadOnlyList<ShapedTextCharacters> textRuns, TextRange textRange, private static TextLine PerformTextWrapping(List<ShapedTextCharacters> textRuns, TextRange textRange,
double paragraphWidth, TextParagraphProperties paragraphProperties) double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak currentLineBreak)
{ {
var availableWidth = paragraphWidth; var availableWidth = paragraphWidth;
var currentWidth = 0.0; var currentWidth = 0.0;
@ -388,8 +400,22 @@ namespace Avalonia.Media.TextFormatting
var textLineMetrics = TextLineMetrics.Create(splitResult.First, var textLineMetrics = TextLineMetrics.Create(splitResult.First,
new TextRange(textRange.Start, currentLength), paragraphWidth, paragraphProperties); new TextRange(textRange.Start, currentLength), paragraphWidth, paragraphProperties);
var lineBreak = splitResult.Second != null && splitResult.Second.Count > 0 ? var remainingCharacters = splitResult.Second;
new TextLineBreak(splitResult.Second) :
if (currentLineBreak?.RemainingCharacters != null)
{
if (remainingCharacters != null)
{
remainingCharacters.AddRange(currentLineBreak.RemainingCharacters);
}
else
{
remainingCharacters = new List<ShapedTextCharacters>(currentLineBreak.RemainingCharacters);
}
}
var lineBreak = remainingCharacters != null && remainingCharacters.Count > 0 ?
new TextLineBreak(remainingCharacters) :
null; null;
return new TextLineImpl(splitResult.First, textLineMetrics, lineBreak); return new TextLineImpl(splitResult.First, textLineMetrics, lineBreak);
@ -403,7 +429,10 @@ namespace Avalonia.Media.TextFormatting
} }
return new TextLineImpl(textRuns, return new TextLineImpl(textRuns,
TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties)); TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties),
currentLineBreak?.RemainingCharacters != null ?
new TextLineBreak(currentLineBreak.RemainingCharacters) :
null);
} }
/// <summary> /// <summary>
@ -434,7 +463,7 @@ namespace Avalonia.Media.TextFormatting
internal readonly struct SplitTextRunsResult internal readonly struct SplitTextRunsResult
{ {
public SplitTextRunsResult(IReadOnlyList<ShapedTextCharacters> first, IReadOnlyList<ShapedTextCharacters> second) public SplitTextRunsResult(List<ShapedTextCharacters> first, List<ShapedTextCharacters> second)
{ {
First = first; First = first;
@ -447,7 +476,7 @@ namespace Avalonia.Media.TextFormatting
/// <value> /// <value>
/// The first text runs. /// The first text runs.
/// </value> /// </value>
public IReadOnlyList<ShapedTextCharacters> First { get; } public List<ShapedTextCharacters> First { get; }
/// <summary> /// <summary>
/// Gets the second text runs. /// Gets the second text runs.
@ -455,7 +484,7 @@ namespace Avalonia.Media.TextFormatting
/// <value> /// <value>
/// The second text runs. /// The second text runs.
/// </value> /// </value>
public IReadOnlyList<ShapedTextCharacters> Second { get; } public List<ShapedTextCharacters> Second { get; }
} }
private struct TextRunEnumerator private struct TextRunEnumerator

5
src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs

@ -183,7 +183,10 @@ namespace Avalonia.Media.TextFormatting
var glyphRun = TextShaper.Current.ShapeText(new ReadOnlySlice<char>(s_empty, startingIndex, 1), var glyphRun = TextShaper.Current.ShapeText(new ReadOnlySlice<char>(s_empty, startingIndex, 1),
properties.Typeface, properties.FontRenderingEmSize, properties.CultureInfo); properties.Typeface, properties.FontRenderingEmSize, properties.CultureInfo);
var textRuns = new[] { new ShapedTextCharacters(glyphRun, _paragraphProperties.DefaultTextRunProperties) }; var textRuns = new List<ShapedTextCharacters>
{
new ShapedTextCharacters(glyphRun, _paragraphProperties.DefaultTextRunProperties)
};
return new TextLineImpl(textRuns, return new TextLineImpl(textRuns,
TextLineMetrics.Create(textRuns, new TextRange(startingIndex, 1), MaxWidth, _paragraphProperties)); TextLineMetrics.Create(textRuns, new TextRange(startingIndex, 1), MaxWidth, _paragraphProperties));

4
src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs

@ -6,9 +6,9 @@ namespace Avalonia.Media.TextFormatting
{ {
internal class TextLineImpl : TextLine internal class TextLineImpl : TextLine
{ {
private readonly IReadOnlyList<ShapedTextCharacters> _textRuns; private readonly List<ShapedTextCharacters> _textRuns;
public TextLineImpl(IReadOnlyList<ShapedTextCharacters> textRuns, TextLineMetrics lineMetrics, public TextLineImpl(List<ShapedTextCharacters> textRuns, TextLineMetrics lineMetrics,
TextLineBreak lineBreak = null, bool hasCollapsed = false) TextLineBreak lineBreak = null, bool hasCollapsed = false)
{ {
_textRuns = textRuns; _textRuns = textRuns;

10
src/Skia/Avalonia.Skia/TextShaperImpl.cs

@ -123,10 +123,7 @@ namespace Avalonia.Skia
return; return;
} }
if (offsetBuffer == null) offsetBuffer ??= new Vector[glyphPositions.Length];
{
offsetBuffer = new Vector[glyphPositions.Length];
}
var offsetX = position.XOffset * textScale; var offsetX = position.XOffset * textScale;
@ -138,10 +135,7 @@ namespace Avalonia.Skia
private static void SetAdvance(ReadOnlySpan<GlyphPosition> glyphPositions, int index, double textScale, private static void SetAdvance(ReadOnlySpan<GlyphPosition> glyphPositions, int index, double textScale,
ref double[] advanceBuffer) ref double[] advanceBuffer)
{ {
if (advanceBuffer == null) advanceBuffer ??= new double[glyphPositions.Length];
{
advanceBuffer = new double[glyphPositions.Length];
}
// Depends on direction of layout // Depends on direction of layout
// advanceBuffer[index] = buffer.GlyphPositions[index].YAdvance * textScale; // advanceBuffer[index] = buffer.GlyphPositions[index].YAdvance * textScale;

Loading…
Cancel
Save