|
|
@@ -1,5 +1,7 @@
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
+using System.Runtime.InteropServices;
|
|
|
+using Avalonia.Media.TextFormatting;
|
|
|
using Avalonia.Media.TextFormatting.Unicode;
|
|
|
using Avalonia.Platform;
|
|
|
using Avalonia.Utilities;
|
|
|
@@ -11,64 +13,112 @@ namespace Avalonia.Media
|
|
|
/// </summary>
|
|
|
public sealed class GlyphRun : IDisposable
|
|
|
{
|
|
|
- private static readonly IComparer<int> s_ascendingComparer = Comparer<int>.Default;
|
|
|
- private static readonly IComparer<int> s_descendingComparer = new ReverseComparer<int>();
|
|
|
-
|
|
|
private IGlyphRunImpl? _glyphRunImpl;
|
|
|
- private IGlyphTypeface _glyphTypeface;
|
|
|
private double _fontRenderingEmSize;
|
|
|
private int _biDiLevel;
|
|
|
private Point? _baselineOrigin;
|
|
|
private GlyphRunMetrics? _glyphRunMetrics;
|
|
|
-
|
|
|
private ReadOnlyMemory<char> _characters;
|
|
|
- private IReadOnlyList<ushort> _glyphIndices;
|
|
|
- private IReadOnlyList<double>? _glyphAdvances;
|
|
|
- private IReadOnlyList<Vector>? _glyphOffsets;
|
|
|
- private IReadOnlyList<int>? _glyphClusters;
|
|
|
+ private IReadOnlyList<GlyphInfo> _glyphInfos;
|
|
|
+ private bool _hasOneCharPerCluster; // if true, character index and cluster are similar
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Initializes a new instance of the <see cref="GlyphRun"/> class by specifying properties of the class.
|
|
|
+ /// Initializes a new instance of the <see cref="GlyphRun"/> class by specifying properties of the class.
|
|
|
/// </summary>
|
|
|
/// <param name="glyphTypeface">The glyph typeface.</param>
|
|
|
/// <param name="fontRenderingEmSize">The rendering em size.</param>
|
|
|
- /// <param name="glyphIndices">The glyph indices.</param>
|
|
|
- /// <param name="glyphAdvances">The glyph advances.</param>
|
|
|
- /// <param name="glyphOffsets">The glyph offsets.</param>
|
|
|
/// <param name="characters">The characters.</param>
|
|
|
- /// <param name="glyphClusters">The glyph clusters.</param>
|
|
|
+ /// <param name="glyphIndices">The glyph indices.</param>
|
|
|
/// <param name="biDiLevel">The bidi level.</param>
|
|
|
public GlyphRun(
|
|
|
IGlyphTypeface glyphTypeface,
|
|
|
double fontRenderingEmSize,
|
|
|
ReadOnlyMemory<char> characters,
|
|
|
IReadOnlyList<ushort> glyphIndices,
|
|
|
- IReadOnlyList<double>? glyphAdvances = null,
|
|
|
- IReadOnlyList<Vector>? glyphOffsets = null,
|
|
|
- IReadOnlyList<int>? glyphClusters = null,
|
|
|
+ int biDiLevel = 0)
|
|
|
+ : this(glyphTypeface, fontRenderingEmSize, characters,
|
|
|
+ CreateGlyphInfos(glyphIndices, fontRenderingEmSize, glyphTypeface), biDiLevel)
|
|
|
+ {
|
|
|
+ _hasOneCharPerCluster = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Initializes a new instance of the <see cref="GlyphRun"/> class by specifying properties of the class.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="glyphTypeface">The glyph typeface.</param>
|
|
|
+ /// <param name="fontRenderingEmSize">The rendering em size.</param>
|
|
|
+ /// <param name="characters">The characters.</param>
|
|
|
+ /// <param name="glyphInfos">The list of glyphs used.</param>
|
|
|
+ /// <param name="biDiLevel">The bidi level.</param>
|
|
|
+ public GlyphRun(
|
|
|
+ IGlyphTypeface glyphTypeface,
|
|
|
+ double fontRenderingEmSize,
|
|
|
+ ReadOnlyMemory<char> characters,
|
|
|
+ IReadOnlyList<GlyphInfo> glyphInfos,
|
|
|
int biDiLevel = 0)
|
|
|
{
|
|
|
- _glyphTypeface = glyphTypeface;
|
|
|
+ GlyphTypeface = glyphTypeface;
|
|
|
|
|
|
_fontRenderingEmSize = fontRenderingEmSize;
|
|
|
|
|
|
_characters = characters;
|
|
|
|
|
|
- _glyphIndices = glyphIndices;
|
|
|
+ _glyphInfos = glyphInfos;
|
|
|
|
|
|
- _glyphAdvances = glyphAdvances;
|
|
|
+ _biDiLevel = biDiLevel;
|
|
|
+ }
|
|
|
|
|
|
- _glyphOffsets = glyphOffsets;
|
|
|
+ private static IReadOnlyList<GlyphInfo> CreateGlyphInfos(IReadOnlyList<ushort> glyphIndices,
|
|
|
+ double fontRenderingEmSize, IGlyphTypeface glyphTypeface)
|
|
|
+ {
|
|
|
+ var glyphIndexSpan = ListToSpan(glyphIndices);
|
|
|
+ var glyphAdvances = glyphTypeface.GetGlyphAdvances(glyphIndexSpan);
|
|
|
|
|
|
- _glyphClusters = glyphClusters;
|
|
|
+ var glyphInfos = new GlyphInfo[glyphIndexSpan.Length];
|
|
|
+ var scale = fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight;
|
|
|
|
|
|
- _biDiLevel = biDiLevel;
|
|
|
+ for (var i = 0; i < glyphIndexSpan.Length; ++i)
|
|
|
+ {
|
|
|
+ glyphInfos[i] = new GlyphInfo(glyphIndexSpan[i], i, glyphAdvances[i] * scale);
|
|
|
+ }
|
|
|
+
|
|
|
+ return glyphInfos;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static ReadOnlySpan<ushort> ListToSpan(IReadOnlyList<ushort> list)
|
|
|
+ {
|
|
|
+ var count = list.Count;
|
|
|
+
|
|
|
+ if (count == 0)
|
|
|
+ {
|
|
|
+ return default;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (list is ushort[] array)
|
|
|
+ {
|
|
|
+ return array.AsSpan();
|
|
|
+ }
|
|
|
+
|
|
|
+#if NET6_0_OR_GREATER
|
|
|
+ if (list is List<ushort> concreteList)
|
|
|
+ {
|
|
|
+ return CollectionsMarshal.AsSpan(concreteList);
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
+ array = new ushort[count];
|
|
|
+ for (var i = 0; i < count; ++i)
|
|
|
+ {
|
|
|
+ array[i] = list[i];
|
|
|
+ }
|
|
|
+
|
|
|
+ return array.AsSpan();
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets the <see cref="IGlyphTypeface"/> for the <see cref="GlyphRun"/>.
|
|
|
/// </summary>
|
|
|
- public IGlyphTypeface GlyphTypeface => _glyphTypeface;
|
|
|
+ public IGlyphTypeface GlyphTypeface { get; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets or sets the em size used for rendering the <see cref="GlyphRun"/>.
|
|
|
@@ -88,56 +138,17 @@ namespace Avalonia.Media
|
|
|
///
|
|
|
/// </summary>
|
|
|
public GlyphRunMetrics Metrics
|
|
|
- {
|
|
|
- get
|
|
|
- {
|
|
|
- _glyphRunMetrics ??= CreateGlyphRunMetrics();
|
|
|
-
|
|
|
- return _glyphRunMetrics.Value;
|
|
|
- }
|
|
|
- }
|
|
|
+ => _glyphRunMetrics ??= CreateGlyphRunMetrics();
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets or sets the baseline origin of the<see cref="GlyphRun"/>.
|
|
|
/// </summary>
|
|
|
public Point BaselineOrigin
|
|
|
{
|
|
|
- get
|
|
|
- {
|
|
|
- _baselineOrigin ??= CalculateBaselineOrigin();
|
|
|
-
|
|
|
- return _baselineOrigin.Value;
|
|
|
- }
|
|
|
+ get => _baselineOrigin ??= CalculateBaselineOrigin();
|
|
|
set => Set(ref _baselineOrigin, value);
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Gets or sets an array of <see cref="ushort"/> values that represent the glyph indices in the rendering physical font.
|
|
|
- /// </summary>
|
|
|
- public IReadOnlyList<ushort> GlyphIndices
|
|
|
- {
|
|
|
- get => _glyphIndices;
|
|
|
- set => Set(ref _glyphIndices, value);
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets or sets an array of <see cref="double"/> values that represent the advances corresponding to the glyph indices.
|
|
|
- /// </summary>
|
|
|
- public IReadOnlyList<double>? GlyphAdvances
|
|
|
- {
|
|
|
- get => _glyphAdvances;
|
|
|
- set => Set(ref _glyphAdvances, value);
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets or sets an array of <see cref="Vector"/> values representing the offsets of the glyphs in the <see cref="GlyphRun"/>.
|
|
|
- /// </summary>
|
|
|
- public IReadOnlyList<Vector>? GlyphOffsets
|
|
|
- {
|
|
|
- get => _glyphOffsets;
|
|
|
- set => Set(ref _glyphOffsets, value);
|
|
|
- }
|
|
|
-
|
|
|
/// <summary>
|
|
|
/// Gets or sets the list of UTF16 code points that represent the Unicode content of the <see cref="GlyphRun"/>.
|
|
|
/// </summary>
|
|
|
@@ -148,12 +159,16 @@ namespace Avalonia.Media
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Gets or sets a list of <see cref="int"/> values representing a mapping from character index to glyph index.
|
|
|
+ /// Gets or sets the list of glyphs to use to render this run.
|
|
|
/// </summary>
|
|
|
- public IReadOnlyList<int>? GlyphClusters
|
|
|
+ public IReadOnlyList<GlyphInfo> GlyphInfos
|
|
|
{
|
|
|
- get => _glyphClusters;
|
|
|
- set => Set(ref _glyphClusters, value);
|
|
|
+ get => _glyphInfos;
|
|
|
+ set
|
|
|
+ {
|
|
|
+ Set(ref _glyphInfos, value);
|
|
|
+ _hasOneCharPerCluster = false;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -179,17 +194,7 @@ namespace Avalonia.Media
|
|
|
/// The platform implementation of the <see cref="GlyphRun"/>.
|
|
|
/// </summary>
|
|
|
public IGlyphRunImpl GlyphRunImpl
|
|
|
- {
|
|
|
- get
|
|
|
- {
|
|
|
- if (_glyphRunImpl == null)
|
|
|
- {
|
|
|
- Initialize();
|
|
|
- }
|
|
|
-
|
|
|
- return _glyphRunImpl!;
|
|
|
- }
|
|
|
- }
|
|
|
+ => _glyphRunImpl ??= CreateGlyphRunImpl();
|
|
|
|
|
|
/// <summary>
|
|
|
/// Obtains geometry for the glyph run.
|
|
|
@@ -221,38 +226,32 @@ namespace Avalonia.Media
|
|
|
|
|
|
if (IsLeftToRight)
|
|
|
{
|
|
|
- if (GlyphClusters != null)
|
|
|
+ if (characterIndex < Metrics.FirstCluster)
|
|
|
{
|
|
|
- if (characterIndex < Metrics.FirstCluster)
|
|
|
- {
|
|
|
- return 0;
|
|
|
- }
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
|
|
|
- if (characterIndex > Metrics.LastCluster)
|
|
|
- {
|
|
|
- return Metrics.WidthIncludingTrailingWhitespace;
|
|
|
- }
|
|
|
+ if (characterIndex > Metrics.LastCluster)
|
|
|
+ {
|
|
|
+ return Metrics.WidthIncludingTrailingWhitespace;
|
|
|
}
|
|
|
|
|
|
var glyphIndex = FindGlyphIndex(characterIndex);
|
|
|
|
|
|
- if (GlyphClusters != null)
|
|
|
- {
|
|
|
- var currentCluster = GlyphClusters[glyphIndex];
|
|
|
+ var currentCluster = _glyphInfos[glyphIndex].GlyphCluster;
|
|
|
|
|
|
- //Move to the end of the glyph cluster
|
|
|
- if (characterHit.TrailingLength > 0)
|
|
|
+ //Move to the end of the glyph cluster
|
|
|
+ if (characterHit.TrailingLength > 0)
|
|
|
+ {
|
|
|
+ while (glyphIndex + 1 < _glyphInfos.Count && _glyphInfos[glyphIndex + 1].GlyphCluster == currentCluster)
|
|
|
{
|
|
|
- while (glyphIndex + 1 < GlyphClusters.Count && GlyphClusters[glyphIndex + 1] == currentCluster)
|
|
|
- {
|
|
|
- glyphIndex++;
|
|
|
- }
|
|
|
+ glyphIndex++;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for (var i = 0; i < glyphIndex; i++)
|
|
|
{
|
|
|
- distance += GetGlyphAdvance(i, out _);
|
|
|
+ distance += _glyphInfos[i].GlyphAdvance;
|
|
|
}
|
|
|
|
|
|
return distance;
|
|
|
@@ -262,22 +261,19 @@ namespace Avalonia.Media
|
|
|
//RightToLeft
|
|
|
var glyphIndex = FindGlyphIndex(characterIndex);
|
|
|
|
|
|
- if (GlyphClusters != null && GlyphClusters.Count > 0)
|
|
|
+ if (characterIndex > Metrics.LastCluster)
|
|
|
{
|
|
|
- if (characterIndex > Metrics.LastCluster)
|
|
|
- {
|
|
|
- return 0;
|
|
|
- }
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
|
|
|
- if (characterIndex <= Metrics.FirstCluster)
|
|
|
- {
|
|
|
- return Size.Width;
|
|
|
- }
|
|
|
+ if (characterIndex <= Metrics.FirstCluster)
|
|
|
+ {
|
|
|
+ return Size.Width;
|
|
|
}
|
|
|
|
|
|
- for (var i = glyphIndex + 1; i < GlyphIndices.Count; i++)
|
|
|
+ for (var i = glyphIndex + 1; i < _glyphInfos.Count; i++)
|
|
|
{
|
|
|
- distance += GetGlyphAdvance(i, out _);
|
|
|
+ distance += _glyphInfos[i].GlyphAdvance;
|
|
|
}
|
|
|
|
|
|
return Size.Width - distance;
|
|
|
@@ -322,11 +318,12 @@ namespace Avalonia.Media
|
|
|
|
|
|
if (IsLeftToRight)
|
|
|
{
|
|
|
- for (var index = 0; index < GlyphIndices.Count; index++)
|
|
|
+ for (var index = 0; index < _glyphInfos.Count; index++)
|
|
|
{
|
|
|
- var advance = GetGlyphAdvance(index, out var cluster);
|
|
|
+ var glyphInfo = _glyphInfos[index];
|
|
|
+ var advance = glyphInfo.GlyphAdvance;
|
|
|
|
|
|
- characterIndex = cluster;
|
|
|
+ characterIndex = glyphInfo.GlyphCluster;
|
|
|
|
|
|
if (distance > currentX && distance <= currentX + advance)
|
|
|
{
|
|
|
@@ -340,11 +337,12 @@ namespace Avalonia.Media
|
|
|
{
|
|
|
currentX = Size.Width;
|
|
|
|
|
|
- for (var index = GlyphIndices.Count - 1; index >= 0; index--)
|
|
|
+ for (var index = _glyphInfos.Count - 1; index >= 0; index--)
|
|
|
{
|
|
|
- var advance = GetGlyphAdvance(index, out var cluster);
|
|
|
+ var glyphInfo = _glyphInfos[index];
|
|
|
+ var advance = glyphInfo.GlyphAdvance;
|
|
|
|
|
|
- characterIndex = cluster;
|
|
|
+ characterIndex = glyphInfo.GlyphCluster;
|
|
|
|
|
|
var offsetX = currentX - advance;
|
|
|
|
|
|
@@ -424,7 +422,7 @@ namespace Avalonia.Media
|
|
|
/// </returns>
|
|
|
public int FindGlyphIndex(int characterIndex)
|
|
|
{
|
|
|
- if (GlyphClusters == null || GlyphClusters.Count == 0)
|
|
|
+ if (_hasOneCharPerCluster)
|
|
|
{
|
|
|
return characterIndex;
|
|
|
}
|
|
|
@@ -433,7 +431,7 @@ namespace Avalonia.Media
|
|
|
{
|
|
|
if (IsLeftToRight)
|
|
|
{
|
|
|
- return GlyphIndices.Count - 1;
|
|
|
+ return _glyphInfos.Count - 1;
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
|
@@ -446,15 +444,13 @@ namespace Avalonia.Media
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
- return GlyphIndices.Count - 1;
|
|
|
+ return _glyphInfos.Count - 1;
|
|
|
}
|
|
|
|
|
|
- var comparer = IsLeftToRight ? s_ascendingComparer : s_descendingComparer;
|
|
|
-
|
|
|
- var clusters = GlyphClusters;
|
|
|
+ var comparer = IsLeftToRight ? GlyphInfo.ClusterAscendingComparer : GlyphInfo.ClusterDescendingComparer;
|
|
|
|
|
|
// Find the start of the cluster at the character index.
|
|
|
- var start = clusters.BinarySearch(characterIndex, comparer);
|
|
|
+ var start = _glyphInfos.BinarySearch(new GlyphInfo(default, characterIndex, default), comparer);
|
|
|
|
|
|
// No cluster found.
|
|
|
if (start < 0)
|
|
|
@@ -463,40 +459,38 @@ namespace Avalonia.Media
|
|
|
{
|
|
|
characterIndex--;
|
|
|
|
|
|
- start = clusters.BinarySearch(characterIndex, comparer);
|
|
|
+ start = _glyphInfos.BinarySearch(new GlyphInfo(default, characterIndex, default), comparer);
|
|
|
}
|
|
|
|
|
|
if (start < 0)
|
|
|
{
|
|
|
- goto result;
|
|
|
+ return 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (IsLeftToRight)
|
|
|
{
|
|
|
- while (start > 0 && clusters[start - 1] == clusters[start])
|
|
|
+ while (start > 0 && _glyphInfos[start - 1].GlyphCluster == _glyphInfos[start].GlyphCluster)
|
|
|
{
|
|
|
start--;
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- while (start + 1 < clusters.Count && clusters[start + 1] == clusters[start])
|
|
|
+ while (start + 1 < _glyphInfos.Count && _glyphInfos[start + 1].GlyphCluster == _glyphInfos[start].GlyphCluster)
|
|
|
{
|
|
|
start++;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- result:
|
|
|
-
|
|
|
if (start < 0)
|
|
|
{
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
- if (start > GlyphIndices.Count - 1)
|
|
|
+ if (start > _glyphInfos.Count - 1)
|
|
|
{
|
|
|
- return GlyphIndices.Count - 1;
|
|
|
+ return _glyphInfos.Count - 1;
|
|
|
}
|
|
|
|
|
|
return start;
|
|
|
@@ -516,14 +510,14 @@ namespace Avalonia.Media
|
|
|
|
|
|
var glyphIndex = FindGlyphIndex(index);
|
|
|
|
|
|
- if (GlyphClusters == null)
|
|
|
+ if (_hasOneCharPerCluster)
|
|
|
{
|
|
|
- width = GetGlyphAdvance(index, out _);
|
|
|
+ width = _glyphInfos[index].GlyphAdvance;
|
|
|
|
|
|
return new CharacterHit(glyphIndex, 1);
|
|
|
}
|
|
|
|
|
|
- var cluster = GlyphClusters[glyphIndex];
|
|
|
+ var cluster = _glyphInfos[glyphIndex].GlyphCluster;
|
|
|
|
|
|
var nextCluster = cluster;
|
|
|
|
|
|
@@ -531,13 +525,13 @@ namespace Avalonia.Media
|
|
|
|
|
|
while (nextCluster == cluster)
|
|
|
{
|
|
|
- width += GetGlyphAdvance(currentIndex, out _);
|
|
|
+ width += _glyphInfos[currentIndex].GlyphAdvance;
|
|
|
|
|
|
if (IsLeftToRight)
|
|
|
{
|
|
|
currentIndex++;
|
|
|
|
|
|
- if (currentIndex == GlyphClusters.Count)
|
|
|
+ if (currentIndex == _glyphInfos.Count)
|
|
|
{
|
|
|
break;
|
|
|
}
|
|
|
@@ -552,7 +546,7 @@ namespace Avalonia.Media
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- nextCluster = GlyphClusters[currentIndex];
|
|
|
+ nextCluster = _glyphInfos[currentIndex].GlyphCluster;
|
|
|
}
|
|
|
|
|
|
var clusterLength = Math.Max(0, nextCluster - cluster);
|
|
|
@@ -565,9 +559,9 @@ namespace Avalonia.Media
|
|
|
|
|
|
if (IsLeftToRight)
|
|
|
{
|
|
|
- for (int i = 1; i < GlyphClusters.Count; i++)
|
|
|
+ for (int i = 1; i < _glyphInfos.Count; i++)
|
|
|
{
|
|
|
- nextCluster = GlyphClusters[i];
|
|
|
+ nextCluster = _glyphInfos[i].GlyphCluster;
|
|
|
|
|
|
if (currentCluster > cluster)
|
|
|
{
|
|
|
@@ -583,9 +577,9 @@ namespace Avalonia.Media
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- for (int i = GlyphClusters.Count - 1; i >= 0; i--)
|
|
|
+ for (int i = _glyphInfos.Count - 1; i >= 0; i--)
|
|
|
{
|
|
|
- nextCluster = GlyphClusters[i];
|
|
|
+ nextCluster = _glyphInfos[i].GlyphCluster;
|
|
|
|
|
|
if (currentCluster > cluster)
|
|
|
{
|
|
|
@@ -613,26 +607,6 @@ namespace Avalonia.Media
|
|
|
return new CharacterHit(cluster, clusterLength);
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Gets a glyph's width.
|
|
|
- /// </summary>
|
|
|
- /// <param name="index">The glyph index.</param>
|
|
|
- /// <param name="cluster">The current cluster.</param>
|
|
|
- /// <returns>The glyph's width.</returns>
|
|
|
- private double GetGlyphAdvance(int index, out int cluster)
|
|
|
- {
|
|
|
- cluster = GlyphClusters != null ? GlyphClusters[index] : index;
|
|
|
-
|
|
|
- if (GlyphAdvances != null)
|
|
|
- {
|
|
|
- return GlyphAdvances[index];
|
|
|
- }
|
|
|
-
|
|
|
- var glyph = GlyphIndices[index];
|
|
|
-
|
|
|
- return GlyphTypeface.GetGlyphAdvance(glyph) * Scale;
|
|
|
- }
|
|
|
-
|
|
|
/// <summary>
|
|
|
/// Calculates the default baseline origin of the <see cref="GlyphRun"/>.
|
|
|
/// </summary>
|
|
|
@@ -644,20 +618,17 @@ namespace Avalonia.Media
|
|
|
|
|
|
private GlyphRunMetrics CreateGlyphRunMetrics()
|
|
|
{
|
|
|
- int firstCluster = 0, lastCluster = 0;
|
|
|
+ int firstCluster, lastCluster;
|
|
|
|
|
|
- if (_glyphClusters != null && _glyphClusters.Count > 0)
|
|
|
+ if (Characters.IsEmpty)
|
|
|
{
|
|
|
- firstCluster = _glyphClusters[0];
|
|
|
- lastCluster = _glyphClusters[_glyphClusters.Count - 1];
|
|
|
+ firstCluster = 0;
|
|
|
+ lastCluster = 0;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- if (!Characters.IsEmpty)
|
|
|
- {
|
|
|
- firstCluster = 0;
|
|
|
- lastCluster = Characters.Length - 1;
|
|
|
- }
|
|
|
+ firstCluster = _glyphInfos[0].GlyphCluster;
|
|
|
+ lastCluster = _glyphInfos[_glyphInfos.Count - 1].GlyphCluster;
|
|
|
}
|
|
|
|
|
|
if (!IsLeftToRight)
|
|
|
@@ -671,9 +642,9 @@ namespace Avalonia.Media
|
|
|
|
|
|
var trailingWhitespaceLength = GetTrailingWhitespaceLength(isReversed, out var newLineLength, out var glyphCount);
|
|
|
|
|
|
- for (var index = 0; index < GlyphIndices.Count; index++)
|
|
|
+ for (var index = 0; index < _glyphInfos.Count; index++)
|
|
|
{
|
|
|
- var advance = GetGlyphAdvance(index, out _);
|
|
|
+ var advance = _glyphInfos[index].GlyphAdvance;
|
|
|
|
|
|
widthIncludingTrailingWhitespace += advance;
|
|
|
}
|
|
|
@@ -684,14 +655,14 @@ namespace Avalonia.Media
|
|
|
{
|
|
|
for (var index = 0; index < glyphCount; index++)
|
|
|
{
|
|
|
- width -= GetGlyphAdvance(index, out _);
|
|
|
+ width -= _glyphInfos[index].GlyphAdvance;
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- for (var index = GlyphIndices.Count - glyphCount; index < GlyphIndices.Count; index++)
|
|
|
+ for (var index = _glyphInfos.Count - glyphCount; index < _glyphInfos.Count; index++)
|
|
|
{
|
|
|
- width -= GetGlyphAdvance(index, out _);
|
|
|
+ width -= _glyphInfos[index].GlyphAdvance;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -710,7 +681,7 @@ namespace Avalonia.Media
|
|
|
{
|
|
|
if (isReversed)
|
|
|
{
|
|
|
- return GetTralingWhitespaceLengthRightToLeft(out newLineLength, out glyphCount);
|
|
|
+ return GetTrailingWhitespaceLengthRightToLeft(out newLineLength, out glyphCount);
|
|
|
}
|
|
|
|
|
|
glyphCount = 0;
|
|
|
@@ -720,84 +691,59 @@ namespace Avalonia.Media
|
|
|
|
|
|
if (!charactersSpan.IsEmpty)
|
|
|
{
|
|
|
- if (GlyphClusters == null)
|
|
|
- {
|
|
|
- for (var i = charactersSpan.Length - 1; i >= 0;)
|
|
|
- {
|
|
|
- var codepoint = Codepoint.ReadAt(charactersSpan, i, out var count);
|
|
|
-
|
|
|
- if (!codepoint.IsWhiteSpace)
|
|
|
- {
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- if (codepoint.IsBreakChar)
|
|
|
- {
|
|
|
- newLineLength++;
|
|
|
- }
|
|
|
-
|
|
|
- trailingWhitespaceLength++;
|
|
|
+ var characterIndex = charactersSpan.Length - 1;
|
|
|
|
|
|
- i -= count;
|
|
|
- glyphCount++;
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
+ for (var i = _glyphInfos.Count - 1; i >= 0; i--)
|
|
|
{
|
|
|
- var characterIndex = charactersSpan.Length - 1;
|
|
|
+ var currentCluster = _glyphInfos[i].GlyphCluster;
|
|
|
+ var codepoint = Codepoint.ReadAt(charactersSpan, characterIndex, out var characterLength);
|
|
|
|
|
|
- for (var i = GlyphClusters.Count - 1; i >= 0; i--)
|
|
|
- {
|
|
|
- var currentCluster = GlyphClusters[i];
|
|
|
- var codepoint = Codepoint.ReadAt(charactersSpan, characterIndex, out var characterLength);
|
|
|
+ characterIndex -= characterLength;
|
|
|
|
|
|
- characterIndex -= characterLength;
|
|
|
+ if (!codepoint.IsWhiteSpace)
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
|
|
|
- if (!codepoint.IsWhiteSpace)
|
|
|
- {
|
|
|
- break;
|
|
|
- }
|
|
|
+ var clusterLength = 1;
|
|
|
|
|
|
- var clusterLength = 1;
|
|
|
+ while (i - 1 >= 0)
|
|
|
+ {
|
|
|
+ var nextCluster = _glyphInfos[i - 1].GlyphCluster;
|
|
|
|
|
|
- while (i - 1 >= 0)
|
|
|
+ if (currentCluster == nextCluster)
|
|
|
{
|
|
|
- var nextCluster = GlyphClusters[i - 1];
|
|
|
+ clusterLength++;
|
|
|
+ i--;
|
|
|
|
|
|
- if (currentCluster == nextCluster)
|
|
|
+ if(characterIndex >= 0)
|
|
|
{
|
|
|
- clusterLength++;
|
|
|
- i--;
|
|
|
-
|
|
|
- if(characterIndex >= 0)
|
|
|
- {
|
|
|
- codepoint = Codepoint.ReadAt(charactersSpan, characterIndex, out characterLength);
|
|
|
+ codepoint = Codepoint.ReadAt(charactersSpan, characterIndex, out characterLength);
|
|
|
|
|
|
- characterIndex -= characterLength;
|
|
|
- }
|
|
|
-
|
|
|
- continue;
|
|
|
+ characterIndex -= characterLength;
|
|
|
}
|
|
|
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- if (codepoint.IsBreakChar)
|
|
|
- {
|
|
|
- newLineLength += clusterLength;
|
|
|
+ continue;
|
|
|
}
|
|
|
|
|
|
- trailingWhitespaceLength += clusterLength;
|
|
|
+ break;
|
|
|
+ }
|
|
|
|
|
|
- glyphCount++;
|
|
|
+ if (codepoint.IsBreakChar)
|
|
|
+ {
|
|
|
+ newLineLength += clusterLength;
|
|
|
}
|
|
|
+
|
|
|
+ trailingWhitespaceLength += clusterLength;
|
|
|
+
|
|
|
+ glyphCount++;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return trailingWhitespaceLength;
|
|
|
}
|
|
|
|
|
|
- private int GetTralingWhitespaceLengthRightToLeft(out int newLineLength, out int glyphCount)
|
|
|
+ private int GetTrailingWhitespaceLengthRightToLeft(out int newLineLength, out int glyphCount)
|
|
|
{
|
|
|
glyphCount = 0;
|
|
|
newLineLength = 0;
|
|
|
@@ -806,71 +752,46 @@ namespace Avalonia.Media
|
|
|
|
|
|
if (!charactersSpan.IsEmpty)
|
|
|
{
|
|
|
- if (GlyphClusters == null)
|
|
|
- {
|
|
|
- for (var i = 0; i < charactersSpan.Length;)
|
|
|
- {
|
|
|
- var codepoint = Codepoint.ReadAt(charactersSpan, i, out var count);
|
|
|
+ var characterIndex = 0;
|
|
|
|
|
|
- if (!codepoint.IsWhiteSpace)
|
|
|
- {
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- if (codepoint.IsBreakChar)
|
|
|
- {
|
|
|
- newLineLength++;
|
|
|
- }
|
|
|
-
|
|
|
- trailingWhitespaceLength++;
|
|
|
-
|
|
|
- i += count;
|
|
|
- glyphCount++;
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
+ for (var i = 0; i < _glyphInfos.Count; i++)
|
|
|
{
|
|
|
- var characterIndex = 0;
|
|
|
+ var currentCluster = _glyphInfos[i].GlyphCluster;
|
|
|
+ var codepoint = Codepoint.ReadAt(charactersSpan, characterIndex, out var characterLength);
|
|
|
|
|
|
- for (var i = 0; i < GlyphClusters.Count; i++)
|
|
|
- {
|
|
|
- var currentCluster = GlyphClusters[i];
|
|
|
- var codepoint = Codepoint.ReadAt(charactersSpan, characterIndex, out var characterLength);
|
|
|
+ characterIndex += characterLength;
|
|
|
|
|
|
- characterIndex += characterLength;
|
|
|
+ if (!codepoint.IsWhiteSpace)
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
|
|
|
- if (!codepoint.IsWhiteSpace)
|
|
|
- {
|
|
|
- break;
|
|
|
- }
|
|
|
+ var clusterLength = 1;
|
|
|
|
|
|
- var clusterLength = 1;
|
|
|
+ var j = i;
|
|
|
|
|
|
- var j = i;
|
|
|
+ while (j - 1 >= 0)
|
|
|
+ {
|
|
|
+ var nextCluster = _glyphInfos[--j].GlyphCluster;
|
|
|
|
|
|
- while (j - 1 >= 0)
|
|
|
+ if (currentCluster == nextCluster)
|
|
|
{
|
|
|
- var nextCluster = GlyphClusters[--j];
|
|
|
-
|
|
|
- if (currentCluster == nextCluster)
|
|
|
- {
|
|
|
- clusterLength++;
|
|
|
+ clusterLength++;
|
|
|
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- if (codepoint.IsBreakChar)
|
|
|
- {
|
|
|
- newLineLength += clusterLength;
|
|
|
+ continue;
|
|
|
}
|
|
|
|
|
|
- trailingWhitespaceLength += clusterLength;
|
|
|
+ break;
|
|
|
+ }
|
|
|
|
|
|
- glyphCount += clusterLength;
|
|
|
+ if (codepoint.IsBreakChar)
|
|
|
+ {
|
|
|
+ newLineLength += clusterLength;
|
|
|
}
|
|
|
+
|
|
|
+ trailingWhitespaceLength += clusterLength;
|
|
|
+
|
|
|
+ glyphCount += clusterLength;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -890,44 +811,17 @@ namespace Avalonia.Media
|
|
|
field = value;
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Initializes the <see cref="GlyphRun"/>.
|
|
|
- /// </summary>
|
|
|
- private void Initialize()
|
|
|
+ private IGlyphRunImpl CreateGlyphRunImpl()
|
|
|
{
|
|
|
- if (GlyphIndices == null)
|
|
|
- {
|
|
|
- throw new InvalidOperationException();
|
|
|
- }
|
|
|
-
|
|
|
- var glyphCount = GlyphIndices.Count;
|
|
|
-
|
|
|
- if (GlyphAdvances != null && GlyphAdvances.Count > 0 && GlyphAdvances.Count != glyphCount)
|
|
|
- {
|
|
|
- throw new InvalidOperationException();
|
|
|
- }
|
|
|
-
|
|
|
- if (GlyphOffsets != null && GlyphOffsets.Count > 0 && GlyphOffsets.Count != glyphCount)
|
|
|
- {
|
|
|
- throw new InvalidOperationException();
|
|
|
- }
|
|
|
-
|
|
|
var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
|
|
|
|
|
|
- _glyphRunImpl = platformRenderInterface.CreateGlyphRun(GlyphTypeface, FontRenderingEmSize, GlyphIndices, GlyphAdvances, GlyphOffsets);
|
|
|
+ return platformRenderInterface.CreateGlyphRun(GlyphTypeface, FontRenderingEmSize, GlyphInfos);
|
|
|
}
|
|
|
|
|
|
public void Dispose()
|
|
|
{
|
|
|
_glyphRunImpl?.Dispose();
|
|
|
- }
|
|
|
-
|
|
|
- private class ReverseComparer<T> : IComparer<T>
|
|
|
- {
|
|
|
- public int Compare(T? x, T? y)
|
|
|
- {
|
|
|
- return Comparer<T>.Default.Compare(y, x);
|
|
|
- }
|
|
|
+ _glyphRunImpl = null;
|
|
|
}
|
|
|
}
|
|
|
}
|