HarfBuzzTextShaperImpl.cs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. using System;
  2. using System.Globalization;
  3. using Avalonia.Media.TextFormatting;
  4. using Avalonia.Media.TextFormatting.Unicode;
  5. using Avalonia.Platform;
  6. using Avalonia.Utilities;
  7. using HarfBuzzSharp;
  8. using Buffer = HarfBuzzSharp.Buffer;
  9. namespace Avalonia.UnitTests
  10. {
  11. public class HarfBuzzTextShaperImpl : ITextShaperImpl
  12. {
  13. public ShapedBuffer ShapeText(ReadOnlySlice<char> text, TextShaperOptions options)
  14. {
  15. var typeface = options.Typeface;
  16. var fontRenderingEmSize = options.FontRenderingEmSize;
  17. var bidiLevel = options.BidiLevel;
  18. var culture = options.Culture;
  19. using (var buffer = new Buffer())
  20. {
  21. buffer.AddUtf16(text.Buffer.Span, text.Start, text.Length);
  22. MergeBreakPair(buffer);
  23. buffer.GuessSegmentProperties();
  24. buffer.Direction = (bidiLevel & 1) == 0 ? Direction.LeftToRight : Direction.RightToLeft;
  25. buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
  26. var font = ((HarfBuzzGlyphTypefaceImpl)typeface.PlatformImpl).Font;
  27. font.Shape(buffer);
  28. if (buffer.Direction == Direction.RightToLeft)
  29. {
  30. buffer.Reverse();
  31. }
  32. font.GetScale(out var scaleX, out _);
  33. var textScale = fontRenderingEmSize / scaleX;
  34. var bufferLength = buffer.Length;
  35. var shapedBuffer = new ShapedBuffer(text, bufferLength, typeface, fontRenderingEmSize, bidiLevel);
  36. var glyphInfos = buffer.GetGlyphInfoSpan();
  37. var glyphPositions = buffer.GetGlyphPositionSpan();
  38. for (var i = 0; i < bufferLength; i++)
  39. {
  40. var sourceInfo = glyphInfos[i];
  41. var glyphIndex = (ushort)sourceInfo.Codepoint;
  42. var glyphCluster = (int)sourceInfo.Cluster;
  43. var glyphAdvance = GetGlyphAdvance(glyphPositions, i, textScale);
  44. var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale);
  45. var targetInfo = new Media.TextFormatting.GlyphInfo(glyphIndex, glyphCluster, glyphAdvance, glyphOffset);
  46. shapedBuffer[i] = targetInfo;
  47. }
  48. return shapedBuffer;
  49. }
  50. }
  51. private static void MergeBreakPair(Buffer buffer)
  52. {
  53. var length = buffer.Length;
  54. var glyphInfos = buffer.GetGlyphInfoSpan();
  55. var second = glyphInfos[length - 1];
  56. if (!new Codepoint(second.Codepoint).IsBreakChar)
  57. {
  58. return;
  59. }
  60. if (length > 1 && glyphInfos[length - 2].Codepoint == '\r' && second.Codepoint == '\n')
  61. {
  62. var first = glyphInfos[length - 2];
  63. first.Codepoint = '\u200C';
  64. second.Codepoint = '\u200C';
  65. second.Cluster = first.Cluster;
  66. unsafe
  67. {
  68. fixed (HarfBuzzSharp.GlyphInfo* p = &glyphInfos[length - 2])
  69. {
  70. *p = first;
  71. }
  72. fixed (HarfBuzzSharp.GlyphInfo* p = &glyphInfos[length - 1])
  73. {
  74. *p = second;
  75. }
  76. }
  77. }
  78. else
  79. {
  80. second.Codepoint = '\u200C';
  81. unsafe
  82. {
  83. fixed (HarfBuzzSharp.GlyphInfo* p = &glyphInfos[length - 1])
  84. {
  85. *p = second;
  86. }
  87. }
  88. }
  89. }
  90. private static Vector GetGlyphOffset(ReadOnlySpan<GlyphPosition> glyphPositions, int index, double textScale)
  91. {
  92. var position = glyphPositions[index];
  93. var offsetX = position.XOffset * textScale;
  94. var offsetY = position.YOffset * textScale;
  95. return new Vector(offsetX, offsetY);
  96. }
  97. private static double GetGlyphAdvance(ReadOnlySpan<GlyphPosition> glyphPositions, int index, double textScale)
  98. {
  99. // Depends on direction of layout
  100. // advanceBuffer[index] = buffer.GlyphPositions[index].YAdvance * textScale;
  101. return glyphPositions[index].XAdvance * textScale;
  102. }
  103. }
  104. }