ShapedTextRun.cs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. using System;
  2. using Avalonia.Media.TextFormatting.Unicode;
  3. namespace Avalonia.Media.TextFormatting
  4. {
  5. /// <summary>
  6. /// A text run that holds shaped characters.
  7. /// </summary>
  8. public sealed class ShapedTextRun : DrawableTextRun, IDisposable
  9. {
  10. private GlyphRun? _glyphRun;
  11. public ShapedTextRun(ShapedBuffer shapedBuffer, TextRunProperties properties)
  12. {
  13. ShapedBuffer = shapedBuffer;
  14. Properties = properties;
  15. TextMetrics = new TextMetrics(properties.CachedGlyphTypeface, properties.FontRenderingEmSize);
  16. }
  17. public bool IsReversed { get; private set; }
  18. public sbyte BidiLevel => ShapedBuffer.BidiLevel;
  19. public ShapedBuffer ShapedBuffer { get; }
  20. /// <inheritdoc/>
  21. public override ReadOnlyMemory<char> Text
  22. => ShapedBuffer.Text;
  23. /// <inheritdoc/>
  24. public override TextRunProperties Properties { get; }
  25. /// <inheritdoc/>
  26. public override int Length
  27. => ShapedBuffer.Text.Length;
  28. public TextMetrics TextMetrics { get; }
  29. public override double Baseline => -TextMetrics.Ascent;
  30. public override Size Size => GlyphRun.Bounds.Size;
  31. public GlyphRun GlyphRun => _glyphRun ??= CreateGlyphRun();
  32. /// <inheritdoc/>
  33. public override void Draw(DrawingContext drawingContext, Point origin)
  34. {
  35. using (drawingContext.PushTransform(Matrix.CreateTranslation(origin)))
  36. {
  37. if (GlyphRun.GlyphInfos.Count == 0)
  38. {
  39. return;
  40. }
  41. if (Properties.Typeface == default)
  42. {
  43. return;
  44. }
  45. if (Properties.ForegroundBrush == null)
  46. {
  47. return;
  48. }
  49. if (Properties.BackgroundBrush != null)
  50. {
  51. drawingContext.DrawRectangle(Properties.BackgroundBrush, null, GlyphRun.Bounds.Translate(new Vector(0, -Baseline)));
  52. }
  53. drawingContext.DrawGlyphRun(Properties.ForegroundBrush, GlyphRun);
  54. if (Properties.TextDecorations == null)
  55. {
  56. return;
  57. }
  58. foreach (var textDecoration in Properties.TextDecorations)
  59. {
  60. textDecoration.Draw(drawingContext, GlyphRun, TextMetrics, Properties.ForegroundBrush);
  61. }
  62. }
  63. }
  64. internal void Reverse()
  65. {
  66. _glyphRun = null;
  67. ShapedBuffer.Reverse();
  68. IsReversed = !IsReversed;
  69. }
  70. /// <summary>
  71. /// Measures the number of characters that fit into available width.
  72. /// </summary>
  73. /// <param name="availableWidth">The available width.</param>
  74. /// <param name="length">The count of fitting characters.</param>
  75. /// <returns>
  76. /// <c>true</c> if characters fit into the available width; otherwise, <c>false</c>.
  77. /// </returns>
  78. public bool TryMeasureCharacters(double availableWidth, out int length)
  79. {
  80. length = 0;
  81. var currentWidth = 0.0;
  82. var charactersSpan = GlyphRun.Characters.Span;
  83. for (var i = 0; i < ShapedBuffer.Length; i++)
  84. {
  85. var advance = ShapedBuffer[i].GlyphAdvance;
  86. var currentCluster = ShapedBuffer[i].GlyphCluster;
  87. if (currentWidth + advance > availableWidth)
  88. {
  89. break;
  90. }
  91. if(i + 1 < ShapedBuffer.Length)
  92. {
  93. var nextCluster = ShapedBuffer[i + 1].GlyphCluster;
  94. var count = nextCluster - currentCluster;
  95. length += count;
  96. }
  97. else
  98. {
  99. Codepoint.ReadAt(charactersSpan, length, out var count);
  100. length += count;
  101. }
  102. currentWidth += advance;
  103. }
  104. return length > 0;
  105. }
  106. internal bool TryMeasureCharactersBackwards(double availableWidth, out int length, out double width)
  107. {
  108. length = 0;
  109. width = 0;
  110. var charactersSpan = GlyphRun.Characters.Span;
  111. for (var i = ShapedBuffer.Length - 1; i >= 0; i--)
  112. {
  113. var advance = ShapedBuffer[i].GlyphAdvance;
  114. if (width + advance > availableWidth)
  115. {
  116. break;
  117. }
  118. Codepoint.ReadAt(charactersSpan, length, out var count);
  119. length += count;
  120. width += advance;
  121. }
  122. return length > 0;
  123. }
  124. internal SplitResult<ShapedTextRun> Split(int length)
  125. {
  126. var isReversed = IsReversed;
  127. if (isReversed)
  128. {
  129. Reverse();
  130. length = Length - length;
  131. }
  132. #if DEBUG
  133. if (length == 0)
  134. {
  135. throw new ArgumentOutOfRangeException(nameof(length), "length must be greater than zero.");
  136. }
  137. #endif
  138. var splitBuffer = ShapedBuffer.Split(length);
  139. var first = new ShapedTextRun(splitBuffer.First, Properties);
  140. #if DEBUG
  141. if (first.Length != length)
  142. {
  143. throw new InvalidOperationException("Split length mismatch.");
  144. }
  145. #endif
  146. var second = new ShapedTextRun(splitBuffer.Second!, Properties);
  147. if (isReversed)
  148. {
  149. return new SplitResult<ShapedTextRun>(second, first);
  150. }
  151. return new SplitResult<ShapedTextRun>(first, second);
  152. }
  153. internal GlyphRun CreateGlyphRun()
  154. {
  155. return new GlyphRun(
  156. ShapedBuffer.GlyphTypeface,
  157. ShapedBuffer.FontRenderingEmSize,
  158. Text,
  159. ShapedBuffer,
  160. biDiLevel: BidiLevel,
  161. baselineOrigin: new Point());
  162. }
  163. public void Dispose()
  164. {
  165. _glyphRun?.Dispose();
  166. ShapedBuffer.Dispose();
  167. }
  168. }
  169. }