|
|
|
@ -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; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|