diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index fc4bc6aa1c..b637c94d88 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -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 /// public sealed class GlyphRun : IDisposable { - private static readonly IComparer s_ascendingComparer = Comparer.Default; - private static readonly IComparer s_descendingComparer = new ReverseComparer(); - private IGlyphRunImpl? _glyphRunImpl; - private IGlyphTypeface _glyphTypeface; private double _fontRenderingEmSize; private int _biDiLevel; private Point? _baselineOrigin; private GlyphRunMetrics? _glyphRunMetrics; - private ReadOnlyMemory _characters; - private IReadOnlyList _glyphIndices; - private IReadOnlyList? _glyphAdvances; - private IReadOnlyList? _glyphOffsets; - private IReadOnlyList? _glyphClusters; + private IReadOnlyList _glyphInfos; + private bool _hasOneCharPerCluster; // if true, character index and cluster are similar /// - /// Initializes a new instance of the class by specifying properties of the class. + /// Initializes a new instance of the class by specifying properties of the class. /// /// The glyph typeface. /// The rendering em size. - /// The glyph indices. - /// The glyph advances. - /// The glyph offsets. /// The characters. - /// The glyph clusters. + /// The glyph indices. /// The bidi level. public GlyphRun( IGlyphTypeface glyphTypeface, double fontRenderingEmSize, ReadOnlyMemory characters, IReadOnlyList glyphIndices, - IReadOnlyList? glyphAdvances = null, - IReadOnlyList? glyphOffsets = null, - IReadOnlyList? glyphClusters = null, + int biDiLevel = 0) + : this(glyphTypeface, fontRenderingEmSize, characters, + CreateGlyphInfos(glyphIndices, fontRenderingEmSize, glyphTypeface), biDiLevel) + { + _hasOneCharPerCluster = true; + } + + /// + /// Initializes a new instance of the class by specifying properties of the class. + /// + /// The glyph typeface. + /// The rendering em size. + /// The characters. + /// The list of glyphs used. + /// The bidi level. + public GlyphRun( + IGlyphTypeface glyphTypeface, + double fontRenderingEmSize, + ReadOnlyMemory characters, + IReadOnlyList 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 CreateGlyphInfos(IReadOnlyList 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 ListToSpan(IReadOnlyList 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 concreteList) + { + return CollectionsMarshal.AsSpan(concreteList); + } +#endif + + array = new ushort[count]; + for (var i = 0; i < count; ++i) + { + array[i] = list[i]; + } + + return array.AsSpan(); } /// /// Gets the for the . /// - public IGlyphTypeface GlyphTypeface => _glyphTypeface; + public IGlyphTypeface GlyphTypeface { get; } /// /// Gets or sets the em size used for rendering the . @@ -88,56 +138,17 @@ namespace Avalonia.Media /// /// public GlyphRunMetrics Metrics - { - get - { - _glyphRunMetrics ??= CreateGlyphRunMetrics(); - - return _glyphRunMetrics.Value; - } - } + => _glyphRunMetrics ??= CreateGlyphRunMetrics(); /// /// Gets or sets the baseline origin of the. /// public Point BaselineOrigin { - get - { - _baselineOrigin ??= CalculateBaselineOrigin(); - - return _baselineOrigin.Value; - } + get => _baselineOrigin ??= CalculateBaselineOrigin(); set => Set(ref _baselineOrigin, value); } - /// - /// Gets or sets an array of values that represent the glyph indices in the rendering physical font. - /// - public IReadOnlyList GlyphIndices - { - get => _glyphIndices; - set => Set(ref _glyphIndices, value); - } - - /// - /// Gets or sets an array of values that represent the advances corresponding to the glyph indices. - /// - public IReadOnlyList? GlyphAdvances - { - get => _glyphAdvances; - set => Set(ref _glyphAdvances, value); - } - - /// - /// Gets or sets an array of values representing the offsets of the glyphs in the . - /// - public IReadOnlyList? GlyphOffsets - { - get => _glyphOffsets; - set => Set(ref _glyphOffsets, value); - } - /// /// Gets or sets the list of UTF16 code points that represent the Unicode content of the . /// @@ -148,12 +159,16 @@ namespace Avalonia.Media } /// - /// Gets or sets a list of values representing a mapping from character index to glyph index. + /// Gets or sets the list of glyphs to use to render this run. /// - public IReadOnlyList? GlyphClusters + public IReadOnlyList GlyphInfos { - get => _glyphClusters; - set => Set(ref _glyphClusters, value); + get => _glyphInfos; + set + { + Set(ref _glyphInfos, value); + _hasOneCharPerCluster = false; + } } /// @@ -179,17 +194,7 @@ namespace Avalonia.Media /// The platform implementation of the . /// public IGlyphRunImpl GlyphRunImpl - { - get - { - if (_glyphRunImpl == null) - { - Initialize(); - } - - return _glyphRunImpl!; - } - } + => _glyphRunImpl ??= CreateGlyphRunImpl(); /// /// 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 /// 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); } - /// - /// Gets a glyph's width. - /// - /// The glyph index. - /// The current cluster. - /// The glyph's width. - 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; - } - /// /// Calculates the default baseline origin of the . /// @@ -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; } - /// - /// Initializes the . - /// - 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(); - _glyphRunImpl = platformRenderInterface.CreateGlyphRun(GlyphTypeface, FontRenderingEmSize, GlyphIndices, GlyphAdvances, GlyphOffsets); + return platformRenderInterface.CreateGlyphRun(GlyphTypeface, FontRenderingEmSize, GlyphInfos); } public void Dispose() { _glyphRunImpl?.Dispose(); - } - - private class ReverseComparer : IComparer - { - public int Compare(T? x, T? y) - { - return Comparer.Default.Compare(y, x); - } + _glyphRunImpl = null; } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/GlyphInfo.cs b/src/Avalonia.Base/Media/TextFormatting/GlyphInfo.cs new file mode 100644 index 0000000000..36a07721a6 --- /dev/null +++ b/src/Avalonia.Base/Media/TextFormatting/GlyphInfo.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; + +namespace Avalonia.Media.TextFormatting +{ + /// + /// Represents a single glyph. + /// + public readonly record struct GlyphInfo(ushort GlyphIndex, int GlyphCluster, double GlyphAdvance, Vector GlyphOffset = default) + { + internal static Comparer ClusterAscendingComparer { get; } = + Comparer.Create((x, y) => x.GlyphCluster.CompareTo(y.GlyphCluster)); + + internal static Comparer ClusterDescendingComparer { get; } = + Comparer.Create((x, y) => y.GlyphCluster.CompareTo(x.GlyphCluster)); + + /// + /// Get the glyph index. + /// + public ushort GlyphIndex { get; } = GlyphIndex; + + /// + /// Get the glyph cluster. + /// + public int GlyphCluster { get; } = GlyphCluster; + + /// + /// Get the glyph advance. + /// + public double GlyphAdvance { get; } = GlyphAdvance; + + /// + /// Get the glyph offset. + /// + public Vector GlyphOffset { get; } = GlyphOffset; + } +} diff --git a/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs b/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs index b518d47a6d..6bfcfc06f8 100644 --- a/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs +++ b/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs @@ -111,7 +111,7 @@ namespace Avalonia.Media.TextFormatting shapedBuffer.GlyphInfos[glyphIndex] = new GlyphInfo(glyphInfo.GlyphIndex, glyphInfo.GlyphCluster, glyphInfo.GlyphAdvance + spacing); } - glyphRun.GlyphAdvances = shapedBuffer.GlyphAdvances; + glyphRun.GlyphInfos = shapedBuffer.GlyphInfos; } currentPosition += textRun.Length; diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs index 41bba2cd09..f29bdd4459 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs +++ b/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs @@ -1,14 +1,13 @@ using System; using System.Buffers; +using System.Collections; using System.Collections.Generic; using Avalonia.Utilities; namespace Avalonia.Media.TextFormatting { - public sealed class ShapedBuffer : IList, IDisposable + public sealed class ShapedBuffer : IReadOnlyList, IDisposable { - private static readonly IComparer s_clusterComparer = new CompareClusters(); - private GlyphInfo[]? _rentedBuffer; public ShapedBuffer(ReadOnlyMemory text, int bufferLength, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) @@ -42,14 +41,6 @@ namespace Avalonia.Media.TextFormatting public sbyte BidiLevel { get; } public bool IsLeftToRight => (BidiLevel & 1) == 0; - - public IReadOnlyList GlyphIndices => new GlyphIndexList(GlyphInfos); - - public IReadOnlyList GlyphClusters => new GlyphClusterList(GlyphInfos); - - public IReadOnlyList GlyphAdvances => new GlyphAdvanceList(GlyphInfos); - - public IReadOnlyList GlyphOffsets => new GlyphOffsetList(GlyphInfos); public ReadOnlyMemory Text { get; } @@ -73,13 +64,13 @@ namespace Avalonia.Media.TextFormatting } - var comparer = s_clusterComparer; + var comparer = GlyphInfo.ClusterAscendingComparer; - var clusters = GlyphInfos.Span; + var glyphInfos = GlyphInfos.Span; - var searchValue = new GlyphInfo(0, characterIndex); + var searchValue = new GlyphInfo(default, characterIndex, default); - var start = clusters.BinarySearch(searchValue, comparer); + var start = glyphInfos.BinarySearch(searchValue, comparer); if (start < 0) { @@ -87,9 +78,9 @@ namespace Avalonia.Media.TextFormatting { characterIndex--; - searchValue = new GlyphInfo(0, characterIndex); + searchValue = new GlyphInfo(default, characterIndex, default); - start = clusters.BinarySearch(searchValue, comparer); + start = glyphInfos.BinarySearch(searchValue, comparer); } if (start < 0) @@ -98,7 +89,7 @@ namespace Avalonia.Media.TextFormatting } } - while (start > 0 && clusters[start - 1].GlyphCluster == clusters[start].GlyphCluster) + while (start > 0 && glyphInfos[start - 1].GlyphCluster == glyphInfos[start].GlyphCluster) { start--; } @@ -118,8 +109,8 @@ namespace Avalonia.Media.TextFormatting return new SplitResult(this, null); } - var firstCluster = GlyphClusters[0]; - var lastCluster = GlyphClusters[GlyphClusters.Count - 1]; + var firstCluster = GlyphInfos[0].GlyphCluster; + var lastCluster = GlyphInfos[GlyphInfos.Length - 1].GlyphCluster; var start = firstCluster < lastCluster ? firstCluster : lastCluster; @@ -134,9 +125,7 @@ namespace Avalonia.Media.TextFormatting return new SplitResult(first, second); } - int ICollection.Count => throw new NotImplementedException(); - - bool ICollection.IsReadOnly => true; + int IReadOnlyCollection.Count => GlyphInfos.Length; public GlyphInfo this[int index] { @@ -144,130 +133,9 @@ namespace Avalonia.Media.TextFormatting set => GlyphInfos[index] = value; } - int IList.IndexOf(GlyphInfo item) - { - throw new NotImplementedException(); - } - - void IList.Insert(int index, GlyphInfo item) - { - throw new NotImplementedException(); - } - - void IList.RemoveAt(int index) - { - throw new NotImplementedException(); - } - - void ICollection.Add(GlyphInfo item) - { - throw new NotImplementedException(); - } - - void ICollection.Clear() - { - throw new NotImplementedException(); - } - - bool ICollection.Contains(GlyphInfo item) - { - throw new NotImplementedException(); - } - - void ICollection.CopyTo(GlyphInfo[] array, int arrayIndex) - { - throw new NotImplementedException(); - } - - bool ICollection.Remove(GlyphInfo item) - { - throw new NotImplementedException(); - } public IEnumerator GetEnumerator() => GlyphInfos.GetEnumerator(); - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); - - private class CompareClusters : IComparer - { - private static readonly Comparer s_intClusterComparer = Comparer.Default; - - public int Compare(GlyphInfo x, GlyphInfo y) - { - return s_intClusterComparer.Compare(x.GlyphCluster, y.GlyphCluster); - } - } - - private readonly struct GlyphAdvanceList : IReadOnlyList - { - private readonly ArraySlice _glyphInfos; - - public GlyphAdvanceList(ArraySlice glyphInfos) - { - _glyphInfos = glyphInfos; - } - - public double this[int index] => _glyphInfos[index].GlyphAdvance; - - public int Count => _glyphInfos.Length; - - public IEnumerator GetEnumerator() => new ImmutableReadOnlyListStructEnumerator(this); - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); - } - - private readonly struct GlyphIndexList : IReadOnlyList - { - private readonly ArraySlice _glyphInfos; - - public GlyphIndexList(ArraySlice glyphInfos) - { - _glyphInfos = glyphInfos; - } - - public ushort this[int index] => _glyphInfos[index].GlyphIndex; - - public int Count => _glyphInfos.Length; - - public IEnumerator GetEnumerator() => new ImmutableReadOnlyListStructEnumerator(this); - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); - } - - private readonly struct GlyphClusterList : IReadOnlyList - { - private readonly ArraySlice _glyphInfos; - - public GlyphClusterList(ArraySlice glyphInfos) - { - _glyphInfos = glyphInfos; - } - - public int this[int index] => _glyphInfos[index].GlyphCluster; - - public int Count => _glyphInfos.Length; - - public IEnumerator GetEnumerator() => new ImmutableReadOnlyListStructEnumerator(this); - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); - } - - private readonly struct GlyphOffsetList : IReadOnlyList - { - private readonly ArraySlice _glyphInfos; - - public GlyphOffsetList(ArraySlice glyphInfos) - { - _glyphInfos = glyphInfos; - } - - public Vector this[int index] => _glyphInfos[index].GlyphOffset; - - public int Count => _glyphInfos.Length; - - public IEnumerator GetEnumerator() => new ImmutableReadOnlyListStructEnumerator(this); - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public void Dispose() { @@ -279,35 +147,4 @@ namespace Avalonia.Media.TextFormatting } } } - - public readonly record struct GlyphInfo - { - public GlyphInfo(ushort glyphIndex, int glyphCluster, double glyphAdvance = 0, Vector glyphOffset = default) - { - GlyphIndex = glyphIndex; - GlyphAdvance = glyphAdvance; - GlyphCluster = glyphCluster; - GlyphOffset = glyphOffset; - } - - /// - /// Get the glyph index. - /// - public ushort GlyphIndex { get; } - - /// - /// Get the glyph cluster. - /// - public int GlyphCluster { get; } - - /// - /// Get the glyph advance. - /// - public double GlyphAdvance { get; } - - /// - /// Get the glyph offset. - /// - public Vector GlyphOffset { get; } - } } diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs index 583f2e49f1..d444a58297 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs +++ b/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs @@ -40,25 +40,14 @@ namespace Avalonia.Media.TextFormatting public override Size Size => GlyphRun.Size; - public GlyphRun GlyphRun - { - get - { - if(_glyphRun is null) - { - _glyphRun = CreateGlyphRun(); - } - - return _glyphRun; - } - } + public GlyphRun GlyphRun => _glyphRun ??= CreateGlyphRun(); /// public override void Draw(DrawingContext drawingContext, Point origin) { using (drawingContext.PushPreTransform(Matrix.CreateTranslation(origin))) { - if (GlyphRun.GlyphIndices.Count == 0) + if (GlyphRun.GlyphInfos.Count == 0) { return; } @@ -117,7 +106,7 @@ namespace Avalonia.Media.TextFormatting for (var i = 0; i < ShapedBuffer.Length; i++) { - var advance = ShapedBuffer.GlyphAdvances[i]; + var advance = ShapedBuffer.GlyphInfos[i].GlyphAdvance; if (currentWidth + advance > availableWidth) { @@ -141,7 +130,7 @@ namespace Avalonia.Media.TextFormatting for (var i = ShapedBuffer.Length - 1; i >= 0; i--) { - var advance = ShapedBuffer.GlyphAdvances[i]; + var advance = ShapedBuffer.GlyphInfos[i].GlyphAdvance; if (width + advance > availableWidth) { @@ -195,10 +184,7 @@ namespace Avalonia.Media.TextFormatting ShapedBuffer.GlyphTypeface, ShapedBuffer.FontRenderingEmSize, Text, - ShapedBuffer.GlyphIndices, - ShapedBuffer.GlyphAdvances, - ShapedBuffer.GlyphOffsets, - ShapedBuffer.GlyphClusters, + ShapedBuffer, BidiLevel); } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index f3cc0a714e..8ffe3e5da2 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -611,7 +611,7 @@ namespace Avalonia.Media.TextFormatting var properties = paragraphProperties.DefaultTextRunProperties; var glyphTypeface = properties.Typeface.GlyphTypeface; var glyph = glyphTypeface.GetGlyph(s_empty[0]); - var glyphInfos = new[] { new GlyphInfo(glyph, firstTextSourceIndex) }; + var glyphInfos = new[] { new GlyphInfo(glyph, firstTextSourceIndex, 0.0) }; var shapedBuffer = new ShapedBuffer(s_empty.AsMemory(), glyphInfos, glyphTypeface, properties.FontRenderingEmSize, (sbyte)flowDirection); diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs index 1828f24aff..9f4e96da25 100644 --- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using Avalonia.Media; using Avalonia.Media.Imaging; +using Avalonia.Media.TextFormatting; using Avalonia.Metadata; namespace Avalonia.Platform @@ -166,11 +167,9 @@ namespace Avalonia.Platform /// /// The glyph typeface. /// The font rendering em size. - /// The glyph indices. - /// The glyph advances. - /// The glyph offsets. - /// - IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, IReadOnlyList? glyphAdvances, IReadOnlyList? glyphOffsets); + /// The list of glyphs. + /// An . + IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphInfos); /// /// Creates a backend-specific object using a low-level API graphics context diff --git a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs index 18cb7a6308..32923a5257 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs @@ -32,7 +32,7 @@ internal class FpsCounter { var s = new string((char)c, 1); var glyph = typeface.GetGlyph((uint)(s[0])); - _runs[c - FirstChar] = new GlyphRun(typeface, 18, s.ToArray(), new ushort[] { glyph }); + _runs[c - FirstChar] = new GlyphRun(typeface, 18, s.AsMemory(), new ushort[] { glyph }); } } diff --git a/src/Avalonia.Base/Utilities/BinarySearchExtension.cs b/src/Avalonia.Base/Utilities/BinarySearchExtension.cs index b7060d2e21..defc9b1639 100644 --- a/src/Avalonia.Base/Utilities/BinarySearchExtension.cs +++ b/src/Avalonia.Base/Utilities/BinarySearchExtension.cs @@ -14,7 +14,6 @@ // under the License. // Copied from: https://github.com/toptensoftware/RichTextKit -using System; using System.Collections.Generic; namespace Avalonia.Utilities @@ -39,7 +38,7 @@ namespace Avalonia.Utilities /// The value to search for /// The comparer /// The index of the found item; otherwise the bitwise complement of the index of the next larger item - public static int BinarySearch(this IReadOnlyList list, T value, IComparer comparer) where T : IComparable + public static int BinarySearch(this IReadOnlyList list, T value, IComparer comparer) { return list.BinarySearch(0, list.Count, value, comparer); } diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index cb79cc85db..e368ddc373 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -9,6 +9,7 @@ using Avalonia.Rendering; using Avalonia.Rendering.SceneGraph; using Avalonia.Utilities; using Avalonia.Media.Imaging; +using Avalonia.Media.TextFormatting; namespace Avalonia.Headless { @@ -118,7 +119,7 @@ namespace Avalonia.Headless return new HeadlessGeometryStub(new Rect(glyphRun.Size)); } - public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets) + public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphInfos) { return new HeadlessGlyphRunStub(); } diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index b202b60cdf..3fb7491898 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -1,16 +1,11 @@ using System; -using System.Collections; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Threading; - -using Avalonia.Controls.Platform.Surfaces; using Avalonia.Media; using Avalonia.OpenGL; -using Avalonia.OpenGL.Imaging; using Avalonia.Platform; using Avalonia.Media.Imaging; +using Avalonia.Media.TextFormatting; using SkiaSharp; namespace Avalonia.Skia @@ -88,9 +83,9 @@ namespace Avalonia.Skia var (currentX, currentY) = glyphRun.BaselineOrigin; - for (var i = 0; i < glyphRun.GlyphIndices.Count; i++) + for (var i = 0; i < glyphRun.GlyphInfos.Count; i++) { - var glyph = glyphRun.GlyphIndices[i]; + var glyph = glyphRun.GlyphInfos[i].GlyphIndex; var glyphPath = skFont.GetGlyphPath(glyph); if (!glyphPath.IsEmpty) @@ -98,14 +93,7 @@ namespace Avalonia.Skia path.AddPath(glyphPath, (float)currentX, (float)currentY); } - if (glyphRun.GlyphAdvances != null) - { - currentX += glyphRun.GlyphAdvances[i]; - } - else - { - currentX += glyphPath.Bounds.Right; - } + currentX += glyphRun.GlyphInfos[i].GlyphAdvance; } return new StreamGeometryImpl(path); @@ -213,17 +201,16 @@ namespace Avalonia.Skia return new WriteableBitmapImpl(size, dpi, format, alphaFormat); } - public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, - IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets) + public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphInfos) { if (glyphTypeface == null) { throw new ArgumentNullException(nameof(glyphTypeface)); } - if (glyphIndices == null) + if (glyphInfos == null) { - throw new ArgumentNullException(nameof(glyphIndices)); + throw new ArgumentNullException(nameof(glyphInfos)); } var glyphTypefaceImpl = glyphTypeface as GlyphTypefaceImpl; @@ -242,59 +229,25 @@ namespace Avalonia.Skia var builder = new SKTextBlobBuilder(); - var count = glyphIndices.Count; + var count = glyphInfos.Count; - if (glyphOffsets != null && glyphAdvances != null) - { - var runBuffer = builder.AllocatePositionedRun(font, count); + var runBuffer = builder.AllocatePositionedRun(font, count); - var glyphSpan = runBuffer.GetGlyphSpan(); - var positionSpan = runBuffer.GetPositionSpan(); + var glyphSpan = runBuffer.GetGlyphSpan(); + var positionSpan = runBuffer.GetPositionSpan(); - var currentX = 0.0; + var currentX = 0.0; - for (int i = 0; i < glyphOffsets.Count; i++) - { - var offset = glyphOffsets[i]; - - glyphSpan[i] = glyphIndices[i]; - - positionSpan[i] = new SKPoint((float)(currentX + offset.X), (float)offset.Y); - - currentX += glyphAdvances[i]; - } - } - else + for (int i = 0; i < count; i++) { - if (glyphAdvances != null) - { - var runBuffer = builder.AllocateHorizontalRun(font, count, 0); - - var glyphSpan = runBuffer.GetGlyphSpan(); - var positionSpan = runBuffer.GetPositionSpan(); - - var currentX = 0.0; + var glyphInfo = glyphInfos[i]; + var offset = glyphInfo.GlyphOffset; - for (int i = 0; i < glyphAdvances.Count; i++) - { - glyphSpan[i] = glyphIndices[i]; + glyphSpan[i] = glyphInfo.GlyphIndex; - positionSpan[i] = (float)currentX; + positionSpan[i] = new SKPoint((float)(currentX + offset.X), (float)offset.Y); - currentX += glyphAdvances[i]; - } - } - else - { - var runBuffer = builder.AllocateRun(font, count, 0, 0); - - var glyphSpan = runBuffer.GetGlyphSpan(); - - for (int i = 0; i < glyphIndices.Count; i++) - { - glyphSpan[i] = glyphIndices[i]; - } - } + currentX += glyphInfo.GlyphAdvance; } return new GlyphRunImpl(builder.Build()); diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 5887ba2172..d9cd0590fc 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -7,6 +7,7 @@ using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Media; using Avalonia.Media.Imaging; +using Avalonia.Media.TextFormatting; using Avalonia.Platform; using SharpDX.DirectWrite; using GlyphRun = Avalonia.Media.GlyphRun; @@ -157,12 +158,11 @@ namespace Avalonia.Direct2D1 public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children) => new GeometryGroupImpl(fillRule, children); public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) => new CombinedGeometryImpl(combineMode, g1, g2); - public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, - IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets) + public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphInfos) { var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface; - var glyphCount = glyphIndices.Count; + var glyphCount = glyphInfos.Count; var run = new SharpDX.DirectWrite.GlyphRun { @@ -174,44 +174,23 @@ namespace Avalonia.Direct2D1 for (var i = 0; i < glyphCount; i++) { - indices[i] = (short)glyphIndices[i]; + indices[i] = (short)glyphInfos[i].GlyphIndex; } run.Indices = indices; run.Advances = new float[glyphCount]; - var scale = (float)(fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight); - - if (glyphAdvances == null) - { - for (var i = 0; i < glyphCount; i++) - { - var advance = glyphTypeface.GetGlyphAdvance(glyphIndices[i]) * scale; - - run.Advances[i] = advance; - } - } - else - { - for (var i = 0; i < glyphCount; i++) - { - var advance = (float)glyphAdvances[i]; - - run.Advances[i] = advance; - } - } - - if (glyphOffsets == null) + for (var i = 0; i < glyphCount; i++) { - return new GlyphRunImpl(run); + run.Advances[i] = (float)glyphInfos[i].GlyphAdvance; } run.Offsets = new GlyphOffset[glyphCount]; for (var i = 0; i < glyphCount; i++) { - var (x, y) = glyphOffsets[i]; + var (x, y) = glyphInfos[i].GlyphOffset; run.Offsets[i] = new GlyphOffset { @@ -254,11 +233,12 @@ namespace Avalonia.Direct2D1 using (var sink = pathGeometry.Open()) { - var glyphs = new short[glyphRun.GlyphIndices.Count]; + var glyphInfos = glyphRun.GlyphInfos; + var glyphs = new short[glyphInfos.Count]; - for (int i = 0; i < glyphRun.GlyphIndices.Count; i++) + for (int i = 0; i < glyphInfos.Count; i++) { - glyphs[i] = (short)glyphRun.GlyphIndices[i]; + glyphs[i] = (short)glyphInfos[i].GlyphIndex; } glyphTypeface.FontFace.GetGlyphRunOutline((float)glyphRun.FontRenderingEmSize, glyphs, null, null, false, !glyphRun.IsLeftToRight, sink); diff --git a/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs b/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs index 363fb3f5b3..3573ba6b07 100644 --- a/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs +++ b/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs @@ -1,5 +1,7 @@ -using System.Linq; +using System; +using System.Linq; using Avalonia.Media; +using Avalonia.Media.TextFormatting; using Avalonia.Platform; using Avalonia.UnitTests; using Avalonia.Utilities; @@ -179,12 +181,14 @@ namespace Avalonia.Base.UnitTests.Media private static GlyphRun CreateGlyphRun(double[] glyphAdvances, int[] glyphClusters, int bidiLevel = 0) { var count = glyphAdvances.Length; - var glyphIndices = new ushort[count]; - var characters = Enumerable.Repeat('a', count).ToArray(); + var glyphInfos = new GlyphInfo[count]; + for (var i = 0; i < count; ++i) + { + glyphInfos[i] = new GlyphInfo(0, glyphClusters[i], glyphAdvances[i]); + } - return new GlyphRun(new MockGlyphTypeface(), 10, characters, glyphIndices, glyphAdvances, - glyphClusters: glyphClusters, biDiLevel: bidiLevel); + return new GlyphRun(new MockGlyphTypeface(), 10, new string('a', count).AsMemory(), glyphInfos, bidiLevel); } } } diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs index 42e33729ac..93d97e6cfb 100644 --- a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs @@ -5,6 +5,7 @@ using Avalonia.Media; using Avalonia.Platform; using Avalonia.UnitTests; using Avalonia.Media.Imaging; +using Avalonia.Media.TextFormatting; namespace Avalonia.Base.UnitTests.VisualTree { @@ -74,7 +75,7 @@ namespace Avalonia.Base.UnitTests.VisualTree throw new NotImplementedException(); } - public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets) + public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphInfos) { throw new NotImplementedException(); } diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs index a272d89b8a..a802cd0958 100644 --- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs +++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs @@ -5,6 +5,7 @@ using Avalonia.Media; using Avalonia.Platform; using Avalonia.UnitTests; using Avalonia.Media.Imaging; +using Avalonia.Media.TextFormatting; using Microsoft.Diagnostics.Runtime; namespace Avalonia.Benchmarks @@ -120,7 +121,7 @@ namespace Avalonia.Benchmarks return new MockStreamGeometryImpl(); } - public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets) + public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphInfos) { return new MockGlyphRun(); } diff --git a/tests/Avalonia.RenderTests/Media/GlyphRunTests.cs b/tests/Avalonia.RenderTests/Media/GlyphRunTests.cs index 31e485448e..772d6e1023 100644 --- a/tests/Avalonia.RenderTests/Media/GlyphRunTests.cs +++ b/tests/Avalonia.RenderTests/Media/GlyphRunTests.cs @@ -5,6 +5,7 @@ using Avalonia.Controls.Documents; using Avalonia.Controls.Shapes; using Avalonia.Media; using Avalonia.Media.Imaging; +using Avalonia.Media.TextFormatting; using Xunit; #if AVALONIA_SKIA @@ -170,11 +171,16 @@ namespace Avalonia.Direct2D1.RenderTests.Media var advance = glyphTypeface.GetGlyphAdvance(glyphIndices[0]) * scale; - var advances = new[] { advance, advance, advance}; + var glyphInfos = new[] + { + new GlyphInfo(glyphIndices[0], 0, advance), + new GlyphInfo(glyphIndices[1], 1, advance), + new GlyphInfo(glyphIndices[2], 2, advance) + }; var characters = new[] { 'A', 'B', 'C' }; - GlyphRun = new GlyphRun(glyphTypeface, 100, characters, glyphIndices, advances); + GlyphRun = new GlyphRun(glyphTypeface, 100, characters, glyphInfos); } public GlyphRun GlyphRun { get; } diff --git a/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs b/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs index 04bc401479..f2d6670be5 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs @@ -134,11 +134,11 @@ namespace Avalonia.Skia.UnitTests.Media foreach (var rect in rects) { - var currentCluster = glyphRun.GlyphClusters[index]; + var currentCluster = glyphRun.GlyphInfos[index].GlyphCluster; - while (currentCluster == lastCluster && index + 1 < glyphRun.GlyphClusters.Count) + while (currentCluster == lastCluster && index + 1 < glyphRun.GlyphInfos.Count) { - currentCluster = glyphRun.GlyphClusters[++index]; + currentCluster = glyphRun.GlyphInfos[++index].GlyphCluster; } //Non trailing edge @@ -161,15 +161,15 @@ namespace Avalonia.Skia.UnitTests.Media var currentX = glyphRun.IsLeftToRight ? 0d : glyphRun.Metrics.WidthIncludingTrailingWhitespace; - var rects = new List(glyphRun.GlyphAdvances!.Count); + var rects = new List(glyphRun.GlyphInfos!.Count); var lastCluster = -1; - for (var index = 0; index < glyphRun.GlyphAdvances.Count; index++) + for (var index = 0; index < glyphRun.GlyphInfos.Count; index++) { - var currentCluster = glyphRun.GlyphClusters![index]; + var currentCluster = glyphRun.GlyphInfos[index].GlyphCluster; - var advance = glyphRun.GlyphAdvances[index]; + var advance = glyphRun.GlyphInfos[index].GlyphAdvance; if (lastCluster != currentCluster) { @@ -216,10 +216,7 @@ namespace Avalonia.Skia.UnitTests.Media shapedBuffer.GlyphTypeface, shapedBuffer.FontRenderingEmSize, shapedBuffer.Text, - shapedBuffer.GlyphIndices, - shapedBuffer.GlyphAdvances, - shapedBuffer.GlyphOffsets, - shapedBuffer.GlyphClusters, + shapedBuffer.GlyphInfos, shapedBuffer.BidiLevel); if(shapedBuffer.BidiLevel == 1) @@ -233,7 +230,7 @@ namespace Avalonia.Skia.UnitTests.Media private static IDisposable Start() { var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface - .With(renderInterface: new PlatformRenderInterface(null), + .With(renderInterface: new PlatformRenderInterface(), textShaperImpl: new TextShaperImpl(), fontManagerImpl: new CustomFontManagerImpl())); diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs index b90752861c..6b9fb579b1 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs @@ -585,7 +585,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var expectedRuns = expectedTextLine.TextRuns.Cast().ToList(); - var expectedGlyphs = expectedRuns.SelectMany(x => x.GlyphRun.GlyphIndices).ToList(); + var expectedGlyphs = expectedRuns + .SelectMany(run => run.GlyphRun.GlyphInfos, (_, glyph) => glyph.GlyphIndex) + .ToList(); for (var i = 0; i < text.Length; i++) { @@ -604,7 +606,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var shapedRuns = textLine.TextRuns.Cast().ToList(); - var actualGlyphs = shapedRuns.SelectMany(x => x.GlyphRun.GlyphIndices).ToList(); + var actualGlyphs = shapedRuns + .SelectMany(x => x.GlyphRun.GlyphInfos, (_, glyph) => glyph.GlyphIndex) + .ToList(); Assert.Equal(expectedGlyphs, actualGlyphs); } @@ -706,7 +710,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting public static IDisposable Start() { var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface - .With(renderInterface: new PlatformRenderInterface(null), + .With(renderInterface: new PlatformRenderInterface(), textShaperImpl: new TextShaperImpl())); AvaloniaLocator.CurrentMutable diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index 1fd26748cd..a24a0fcf70 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -142,8 +142,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting black, textWrapping: TextWrapping.Wrap); - var expectedGlyphs = expected.TextLines.Select(x => string.Join('|', x.TextRuns.Cast() - .SelectMany(x => x.ShapedBuffer.GlyphIndices))).ToList(); + var expectedGlyphs = GetGlyphs(expected); var outer = new GraphemeEnumerator(text); var inner = new GraphemeEnumerator(text); @@ -175,8 +174,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting textWrapping: TextWrapping.Wrap, textStyleOverrides: spans); - var actualGlyphs = actual.TextLines.Select(x => string.Join('|', x.TextRuns.Cast() - .SelectMany(x => x.ShapedBuffer.GlyphIndices))).ToList(); + var actualGlyphs = GetGlyphs(actual); Assert.Equal(expectedGlyphs.Count, actualGlyphs.Count); @@ -196,6 +194,13 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting i += outer.Current.Text.Length; } } + + static List GetGlyphs(TextLayout textLayout) + => textLayout.TextLines + .Select(line => string.Join('|', line.TextRuns + .Cast() + .SelectMany(run => run.ShapedBuffer.GlyphInfos, (_, glyph) => glyph.GlyphIndex))) + .ToList(); } [Fact] @@ -484,13 +489,13 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting { var shapedRun = (ShapedTextRun)textRun; - var glyphClusters = shapedRun.ShapedBuffer.GlyphClusters; + var glyphClusters = shapedRun.ShapedBuffer.GlyphInfos.Select(glyph => glyph.GlyphCluster).ToArray(); - var expected = clusters.Skip(index).Take(glyphClusters.Count).ToArray(); + var expected = clusters.Skip(index).Take(glyphClusters.Length).ToArray(); Assert.Equal(expected, glyphClusters); - index += glyphClusters.Count; + index += glyphClusters.Length; } } } @@ -515,13 +520,13 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Assert.Equal(1, layout.TextLines[0].TextRuns.Count); - Assert.Equal(expectedLength, ((ShapedTextRun)layout.TextLines[0].TextRuns[0]).GlyphRun.GlyphClusters.Count); + Assert.Equal(expectedLength, ((ShapedTextRun)layout.TextLines[0].TextRuns[0]).GlyphRun.GlyphInfos.Count); - Assert.Equal(5, ((ShapedTextRun)layout.TextLines[0].TextRuns[0]).ShapedBuffer.GlyphClusters[5]); + Assert.Equal(5, ((ShapedTextRun)layout.TextLines[0].TextRuns[0]).ShapedBuffer.GlyphInfos[5].GlyphCluster); if (expectedLength == 7) { - Assert.Equal(5, ((ShapedTextRun)layout.TextLines[0].TextRuns[0]).ShapedBuffer.GlyphClusters[6]); + Assert.Equal(5, ((ShapedTextRun)layout.TextLines[0].TextRuns[0]).ShapedBuffer.GlyphInfos[6].GlyphCluster); } } } @@ -562,9 +567,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var replacementGlyph = Typeface.Default.GlyphTypeface.GetGlyph(Codepoint.ReplacementCodepoint); - foreach (var glyph in textRun.GlyphRun.GlyphIndices) + foreach (var glyphInfo in textRun.GlyphRun.GlyphInfos) { - Assert.Equal(replacementGlyph, glyph); + Assert.Equal(replacementGlyph, glyphInfo.GlyphIndex); } } } @@ -776,8 +781,10 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Assert.Equal(textLine.WidthIncludingTrailingWhitespace, rect.Width); } - var rects = layout.TextLines.SelectMany(x => x.TextRuns.Cast()) - .SelectMany(x => x.ShapedBuffer.GlyphAdvances).ToArray(); + var rects = layout.TextLines + .SelectMany(x => x.TextRuns.Cast()) + .SelectMany(x => x.ShapedBuffer.GlyphInfos, (_, glyph) => glyph.GlyphAdvance) + .ToArray(); for (var i = 0; i < SingleLineText.Length; i++) { @@ -865,10 +872,10 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var currentX = 0.0; - for (var i = 0; i < firstRun.GlyphRun.GlyphClusters.Count; i++) + for (var i = 0; i < firstRun.GlyphRun.GlyphInfos.Count; i++) { - var cluster = firstRun.GlyphRun.GlyphClusters[i]; - var advance = firstRun.GlyphRun.GlyphAdvances[i]; + var cluster = firstRun.GlyphRun.GlyphInfos[i].GlyphCluster; + var advance = firstRun.GlyphRun.GlyphInfos[i].GlyphAdvance; hit = layout.HitTestPoint(new Point(currentX, 0)); @@ -895,10 +902,10 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting currentX = firstRun.Size.Width + 0.5; - for (var i = 0; i < secondRun.GlyphRun.GlyphClusters.Count; i++) + for (var i = 0; i < secondRun.GlyphRun.GlyphInfos.Count; i++) { - var cluster = secondRun.GlyphRun.GlyphClusters[i]; - var advance = secondRun.GlyphRun.GlyphAdvances[i]; + var cluster = secondRun.GlyphRun.GlyphInfos[i].GlyphCluster; + var advance = secondRun.GlyphRun.GlyphInfos[i].GlyphAdvance; hit = layout.HitTestPoint(new Point(currentX, 0)); @@ -932,7 +939,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var firstRun = (ShapedTextRun)textLine.TextRuns[0]; - var firstCluster = firstRun.ShapedBuffer.GlyphClusters[0]; + var firstCluster = firstRun.ShapedBuffer.GlyphInfos[0].GlyphCluster; var characterHit = textLine.GetCharacterHitFromDistance(0); @@ -946,7 +953,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(characterHit.FirstCharacterIndex)); - var firstAdvance = firstRun.ShapedBuffer.GlyphAdvances[0]; + var firstAdvance = firstRun.ShapedBuffer.GlyphInfos[0].GlyphAdvance; Assert.Equal(firstAdvance, distance, 5); @@ -991,9 +998,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var shapedRuns = textLine.TextRuns.Cast().ToList(); - var clusters = shapedRuns.SelectMany(x => x.ShapedBuffer.GlyphClusters).ToList(); + var clusters = shapedRuns.SelectMany(x => x.ShapedBuffer.GlyphInfos, (_, glyph) => glyph.GlyphCluster).ToList(); - var glyphAdvances = shapedRuns.SelectMany(x => x.ShapedBuffer.GlyphAdvances).ToList(); + var glyphAdvances = shapedRuns.SelectMany(x => x.ShapedBuffer.GlyphInfos, (_, glyph) => glyph.GlyphAdvance).ToList(); var currentX = 0.0; diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index 6993c70e8b..544b84912e 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -95,9 +95,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting { var shapedRun = (ShapedTextRun)textRun; - clusters.AddRange(shapedRun.IsReversed ? - shapedRun.ShapedBuffer.GlyphClusters.Reverse() : - shapedRun.ShapedBuffer.GlyphClusters); + var runClusters = shapedRun.ShapedBuffer.GlyphInfos.Select(glyph => glyph.GlyphCluster); + + clusters.AddRange(shapedRun.IsReversed ? runClusters.Reverse() : runClusters); } var nextCharacterHit = new CharacterHit(0, clusters[1] - clusters[0]); @@ -142,9 +142,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting { var shapedRun = (ShapedTextRun)textRun; - clusters.AddRange(shapedRun.IsReversed ? - shapedRun.ShapedBuffer.GlyphClusters.Reverse() : - shapedRun.ShapedBuffer.GlyphClusters); + var runClusters = shapedRun.ShapedBuffer.GlyphInfos.Select(glyph => glyph.GlyphCluster); + + clusters.AddRange(shapedRun.IsReversed ? runClusters.Reverse() : runClusters); } clusters.Reverse(); @@ -247,7 +247,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting formatter.FormatLine(textSource, 0, double.PositiveInfinity, new GenericTextParagraphProperties(defaultProperties)); - var clusters = textLine.TextRuns.Cast().SelectMany(x => x.ShapedBuffer.GlyphClusters) + var clusters = textLine.TextRuns + .Cast() + .SelectMany(x => x.ShapedBuffer.GlyphInfos, (_, glyph) => glyph.GlyphCluster) .ToArray(); var previousCharacterHit = new CharacterHit(text.Length); @@ -313,11 +315,11 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var glyphRun = textRun.GlyphRun; - for (var i = 0; i < glyphRun.GlyphClusters!.Count; i++) + for (var i = 0; i < glyphRun.GlyphInfos.Count; i++) { - var cluster = glyphRun.GlyphClusters[i]; + var cluster = glyphRun.GlyphInfos[i].GlyphCluster; - var advance = glyphRun.GlyphAdvances[i]; + var advance = glyphRun.GlyphInfos[i].GlyphAdvance; var distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(cluster)); @@ -750,7 +752,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting { var shapedBuffer = textRun.ShapedBuffer; - var currentClusters = shapedBuffer.GlyphClusters.ToList(); + var currentClusters = shapedBuffer.GlyphInfos.Select(glyph => glyph.GlyphCluster).ToList(); foreach (var currentCluster in currentClusters) { @@ -783,11 +785,11 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting { var shapedBuffer = textRun.ShapedBuffer; - for (var index = 0; index < shapedBuffer.GlyphAdvances.Count; index++) + for (var index = 0; index < shapedBuffer.GlyphInfos.Length; index++) { - var currentCluster = shapedBuffer.GlyphClusters[index]; + var currentCluster = shapedBuffer.GlyphInfos[index].GlyphCluster; - var advance = shapedBuffer.GlyphAdvances[index]; + var advance = shapedBuffer.GlyphInfos[index].GlyphAdvance; if (lastCluster != currentCluster) { diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextShaperTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextShaperTests.cs index 834dce4a90..3f02867aa9 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextShaperTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextShaperTests.cs @@ -19,10 +19,10 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var shapedBuffer = TextShaper.Current.ShapeText(text, options); Assert.Equal(shapedBuffer.Length, text.Length); - Assert.Equal(shapedBuffer.GlyphClusters.Count, text.Length); - Assert.Equal(0, shapedBuffer.GlyphClusters[0]); - Assert.Equal(1, shapedBuffer.GlyphClusters[1]); - Assert.Equal(1, shapedBuffer.GlyphClusters[2]); + Assert.Equal(shapedBuffer.GlyphInfos.Length, text.Length); + Assert.Equal(0, shapedBuffer.GlyphInfos[0].GlyphCluster); + Assert.Equal(1, shapedBuffer.GlyphInfos[1].GlyphCluster); + Assert.Equal(1, shapedBuffer.GlyphInfos[2].GlyphCluster); } } @@ -36,7 +36,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var shapedBuffer = TextShaper.Current.ShapeText(text, options); Assert.Equal(shapedBuffer.Length, text.Length); - Assert.Equal(100, shapedBuffer.GlyphAdvances[0]); + Assert.Equal(100, shapedBuffer.GlyphInfos[0].GlyphAdvance); } } diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index f9e1e45098..d56e360e22 100644 --- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -4,6 +4,7 @@ using System.IO; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Media.Imaging; +using Avalonia.Media.TextFormatting; using Avalonia.Rendering; using Moq; @@ -146,7 +147,7 @@ namespace Avalonia.UnitTests throw new NotImplementedException(); } - public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets) + public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphInfos) { return Mock.Of(); } diff --git a/tests/TestFiles/Skia/Media/GlyphRun/Should_Render_GlyphRun_Geometry.expected.png b/tests/TestFiles/Skia/Media/GlyphRun/Should_Render_GlyphRun_Geometry.expected.png index 407b67b8a0..547e9b2763 100644 Binary files a/tests/TestFiles/Skia/Media/GlyphRun/Should_Render_GlyphRun_Geometry.expected.png and b/tests/TestFiles/Skia/Media/GlyphRun/Should_Render_GlyphRun_Geometry.expected.png differ