|
|
|
@ -14,6 +14,9 @@ namespace Avalonia.Skia |
|
|
|
{ |
|
|
|
internal class TextShaperImpl : ITextShaperImpl |
|
|
|
{ |
|
|
|
[ThreadStatic] |
|
|
|
private static Buffer? s_buffer; |
|
|
|
|
|
|
|
private static readonly ConcurrentDictionary<int, Language> s_cachedLanguage = new(); |
|
|
|
|
|
|
|
public ShapedBuffer ShapeText(ReadOnlyMemory<char> 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) |
|
|
|
|