diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs index cedb2f63cf..efce67e90b 100644 --- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs +++ b/src/Skia/Avalonia.Skia/TextShaperImpl.cs @@ -14,6 +14,9 @@ namespace Avalonia.Skia { internal class TextShaperImpl : ITextShaperImpl { + [ThreadStatic] + private static Buffer? s_buffer; + private static readonly ConcurrentDictionary s_cachedLanguage = new(); public ShapedBuffer ShapeText(ReadOnlyMemory text, TextShaperOptions options) @@ -24,69 +27,70 @@ namespace Avalonia.Skia var bidiLevel = options.BidiLevel; var culture = options.Culture; - using (var buffer = new Buffer()) - { - // HarfBuzz needs the surrounding characters to correctly shape the text - var containingText = GetContainingMemory(text, out var start, out var length).Span; - buffer.AddUtf16(containingText, start, length); + var buffer = s_buffer ??= new Buffer(); - MergeBreakPair(buffer); + buffer.Reset(); - buffer.GuessSegmentProperties(); + // HarfBuzz needs the surrounding characters to correctly shape the text + var containingText = GetContainingMemory(text, out var start, out var length).Span; + buffer.AddUtf16(containingText, start, length); - buffer.Direction = (bidiLevel & 1) == 0 ? Direction.LeftToRight : Direction.RightToLeft; + MergeBreakPair(buffer); - var usedCulture = culture ?? CultureInfo.CurrentCulture; + buffer.GuessSegmentProperties(); - buffer.Language = s_cachedLanguage.GetOrAdd(usedCulture.LCID, _ => new Language(usedCulture)); + buffer.Direction = (bidiLevel & 1) == 0 ? Direction.LeftToRight : Direction.RightToLeft; - var font = ((GlyphTypefaceImpl)typeface).Font; + var usedCulture = culture ?? CultureInfo.CurrentCulture; - font.Shape(buffer, GetFeatures(options)); + buffer.Language = s_cachedLanguage.GetOrAdd(usedCulture.LCID, _ => new Language(usedCulture)); - if (buffer.Direction == Direction.RightToLeft) - { - buffer.Reverse(); - } + var font = ((GlyphTypefaceImpl)typeface).Font; - font.GetScale(out var scaleX, out _); + font.Shape(buffer, GetFeatures(options)); - var textScale = fontRenderingEmSize / scaleX; + if (buffer.Direction == Direction.RightToLeft) + { + buffer.Reverse(); + } - var bufferLength = buffer.Length; + font.GetScale(out var scaleX, out _); - var shapedBuffer = new ShapedBuffer(text, bufferLength, typeface, fontRenderingEmSize, bidiLevel); + var textScale = fontRenderingEmSize / scaleX; - var glyphInfos = buffer.GetGlyphInfoSpan(); + var bufferLength = buffer.Length; - var glyphPositions = buffer.GetGlyphPositionSpan(); + var shapedBuffer = new ShapedBuffer(text, bufferLength, typeface, fontRenderingEmSize, bidiLevel); - for (var i = 0; i < bufferLength; i++) - { - var sourceInfo = glyphInfos[i]; + var glyphInfos = buffer.GetGlyphInfoSpan(); - var glyphIndex = (ushort)sourceInfo.Codepoint; + var glyphPositions = buffer.GetGlyphPositionSpan(); - var glyphCluster = (int)sourceInfo.Cluster; + for (var i = 0; i < bufferLength; i++) + { + var sourceInfo = glyphInfos[i]; - var glyphAdvance = GetGlyphAdvance(glyphPositions, i, textScale) + options.LetterSpacing; + var glyphIndex = (ushort)sourceInfo.Codepoint; - var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale); + var glyphCluster = (int)sourceInfo.Cluster; - if (glyphCluster < containingText.Length && containingText[glyphCluster] == '\t') - { - glyphIndex = typeface.GetGlyph(' '); + var glyphAdvance = GetGlyphAdvance(glyphPositions, i, textScale) + options.LetterSpacing; - glyphAdvance = options.IncrementalTabWidth > 0 ? - options.IncrementalTabWidth : - 4 * typeface.GetGlyphAdvance(glyphIndex) * textScale; - } + var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale); - shapedBuffer[i] = new Media.TextFormatting.GlyphInfo(glyphIndex, glyphCluster, glyphAdvance, glyphOffset); + if (glyphCluster < containingText.Length && containingText[glyphCluster] == '\t') + { + glyphIndex = typeface.GetGlyph(' '); + + glyphAdvance = options.IncrementalTabWidth > 0 ? + options.IncrementalTabWidth : + 4 * typeface.GetGlyphAdvance(glyphIndex) * textScale; } - return shapedBuffer; + shapedBuffer[i] = new Media.TextFormatting.GlyphInfo(glyphIndex, glyphCluster, glyphAdvance, glyphOffset); } + + return shapedBuffer; } private static void MergeBreakPair(Buffer buffer)