123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- using System;
- using System.Buffers;
- using System.Collections.Concurrent;
- using System.Globalization;
- using System.Runtime.InteropServices;
- using Avalonia.Media.TextFormatting;
- using Avalonia.Media.TextFormatting.Unicode;
- using Avalonia.Platform;
- using HarfBuzzSharp;
- using Buffer = HarfBuzzSharp.Buffer;
- using GlyphInfo = HarfBuzzSharp.GlyphInfo;
- namespace Avalonia.UnitTests
- {
- internal class HarfBuzzTextShaperImpl : ITextShaperImpl
- {
- private static readonly ConcurrentDictionary<int, Language> s_cachedLanguage = new();
- public ShapedBuffer ShapeText(ReadOnlyMemory<char> text, TextShaperOptions options)
- {
- var textSpan = text.Span;
- var typeface = options.Typeface;
- var fontRenderingEmSize = options.FontRenderingEmSize;
- 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);
- MergeBreakPair(buffer);
- buffer.GuessSegmentProperties();
- buffer.Direction = (bidiLevel & 1) == 0 ? Direction.LeftToRight : Direction.RightToLeft;
- var usedCulture = culture ?? CultureInfo.CurrentCulture;
- buffer.Language = s_cachedLanguage.GetOrAdd(usedCulture.LCID, _ => new Language(usedCulture));
- var font = ((HarfBuzzGlyphTypefaceImpl)typeface).Font;
- font.Shape(buffer);
- if (buffer.Direction == Direction.RightToLeft)
- {
- buffer.Reverse();
- }
- font.GetScale(out var scaleX, out _);
- var textScale = fontRenderingEmSize / scaleX;
- var bufferLength = buffer.Length;
- var shapedBuffer = new ShapedBuffer(text, bufferLength, typeface, fontRenderingEmSize, bidiLevel);
- var glyphInfos = buffer.GetGlyphInfoSpan();
- var glyphPositions = buffer.GetGlyphPositionSpan();
- for (var i = 0; i < bufferLength; i++)
- {
- var sourceInfo = glyphInfos[i];
- var glyphIndex = (ushort)sourceInfo.Codepoint;
- var glyphCluster = (int)(sourceInfo.Cluster);
- var glyphAdvance = GetGlyphAdvance(glyphPositions, i, textScale) + options.LetterSpacing;
- var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale);
- if (glyphCluster < containingText.Length && containingText[glyphCluster] == '\t')
- {
- glyphIndex = typeface.GetGlyph(' ');
- glyphAdvance = options.IncrementalTabWidth > 0 ?
- options.IncrementalTabWidth :
- 4 * typeface.GetGlyphAdvance(glyphIndex) * textScale;
- }
- shapedBuffer[i] = new Media.TextFormatting.GlyphInfo(glyphIndex, glyphCluster, glyphAdvance, glyphOffset);
- }
- return shapedBuffer;
- }
- }
- private static void MergeBreakPair(Buffer buffer)
- {
- var length = buffer.Length;
- var glyphInfos = buffer.GetGlyphInfoSpan();
- var second = glyphInfos[length - 1];
- if (!new Codepoint(second.Codepoint).IsBreakChar)
- {
- return;
- }
- if (length > 1 && glyphInfos[length - 2].Codepoint == '\r' && second.Codepoint == '\n')
- {
- var first = glyphInfos[length - 2];
- first.Codepoint = '\u200C';
- second.Codepoint = '\u200C';
- second.Cluster = first.Cluster;
- unsafe
- {
- fixed (GlyphInfo* p = &glyphInfos[length - 2])
- {
- *p = first;
- }
- fixed (GlyphInfo* p = &glyphInfos[length - 1])
- {
- *p = second;
- }
- }
- }
- else
- {
- second.Codepoint = '\u200C';
- unsafe
- {
- fixed (GlyphInfo* p = &glyphInfos[length - 1])
- {
- *p = second;
- }
- }
- }
- }
- private static Vector GetGlyphOffset(ReadOnlySpan<GlyphPosition> glyphPositions, int index, double textScale)
- {
- var position = glyphPositions[index];
- var offsetX = position.XOffset * textScale;
- var offsetY = position.YOffset * textScale;
- return new Vector(offsetX, offsetY);
- }
- private static double GetGlyphAdvance(ReadOnlySpan<GlyphPosition> glyphPositions, int index, double textScale)
- {
- // Depends on direction of layout
- // glyphPositions[index].YAdvance * textScale;
- return glyphPositions[index].XAdvance * textScale;
- }
- private static ReadOnlyMemory<char> GetContainingMemory(ReadOnlyMemory<char> memory, out int start, out int length)
- {
- if (MemoryMarshal.TryGetString(memory, out var containingString, out start, out length))
- {
- return containingString.AsMemory();
- }
- if (MemoryMarshal.TryGetArray(memory, out var segment))
- {
- start = segment.Offset;
- length = segment.Count;
- return segment.Array.AsMemory();
- }
- if (MemoryMarshal.TryGetMemoryManager(memory, out MemoryManager<char> memoryManager, out start, out length))
- {
- return memoryManager.Memory;
- }
- // should never happen
- throw new InvalidOperationException("Memory not backed by string, array or manager");
- }
- }
- }
|