TextEllipsisHelper.cs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. using System;
  2. using Avalonia.Media.TextFormatting.Unicode;
  3. namespace Avalonia.Media.TextFormatting
  4. {
  5. internal static class TextEllipsisHelper
  6. {
  7. public static TextRun[]? Collapse(TextLine textLine, TextCollapsingProperties properties, bool isWordEllipsis)
  8. {
  9. var textRuns = textLine.TextRuns;
  10. if (textRuns.Count == 0)
  11. {
  12. return null;
  13. }
  14. var runIndex = 0;
  15. var currentWidth = 0.0;
  16. var collapsedLength = 0;
  17. var shapedSymbol = TextFormatterImpl.CreateSymbol(properties.Symbol, FlowDirection.LeftToRight);
  18. if (properties.Width < shapedSymbol.GlyphRun.Bounds.Width)
  19. {
  20. //Not enough space to fit in the symbol
  21. return Array.Empty<TextRun>();
  22. }
  23. var availableWidth = properties.Width - shapedSymbol.Size.Width;
  24. if(properties.FlowDirection== FlowDirection.LeftToRight)
  25. {
  26. while (runIndex < textRuns.Count)
  27. {
  28. var currentRun = textRuns[runIndex];
  29. switch (currentRun)
  30. {
  31. case ShapedTextRun shapedRun:
  32. {
  33. currentWidth += shapedRun.Size.Width;
  34. if (currentWidth > availableWidth)
  35. {
  36. if (shapedRun.TryMeasureCharacters(availableWidth, out var measuredLength))
  37. {
  38. if (isWordEllipsis && measuredLength < textLine.Length)
  39. {
  40. var currentBreakPosition = 0;
  41. var lineBreaker = new LineBreakEnumerator(currentRun.Text.Span);
  42. while (currentBreakPosition < measuredLength && lineBreaker.MoveNext(out var lineBreak))
  43. {
  44. var nextBreakPosition = lineBreak.PositionMeasure;
  45. if (nextBreakPosition == 0)
  46. {
  47. break;
  48. }
  49. if (nextBreakPosition >= measuredLength)
  50. {
  51. break;
  52. }
  53. currentBreakPosition = nextBreakPosition;
  54. }
  55. measuredLength = currentBreakPosition;
  56. }
  57. }
  58. collapsedLength += measuredLength;
  59. return CreateCollapsedRuns(textLine, collapsedLength, FlowDirection.LeftToRight, shapedSymbol);
  60. }
  61. availableWidth -= shapedRun.Size.Width;
  62. break;
  63. }
  64. case DrawableTextRun drawableRun:
  65. {
  66. //The whole run needs to fit into available space
  67. if (currentWidth + drawableRun.Size.Width > availableWidth)
  68. {
  69. return CreateCollapsedRuns(textLine, collapsedLength, FlowDirection.LeftToRight, shapedSymbol);
  70. }
  71. availableWidth -= drawableRun.Size.Width;
  72. break;
  73. }
  74. }
  75. collapsedLength += currentRun.Length;
  76. runIndex++;
  77. }
  78. }
  79. else
  80. {
  81. runIndex = textRuns.Count - 1;
  82. while (runIndex >= 0)
  83. {
  84. var currentRun = textRuns[runIndex];
  85. switch (currentRun)
  86. {
  87. case ShapedTextRun shapedRun:
  88. {
  89. currentWidth += shapedRun.Size.Width;
  90. if (currentWidth > availableWidth)
  91. {
  92. if (shapedRun.TryMeasureCharacters(availableWidth, out var measuredLength))
  93. {
  94. if (isWordEllipsis && measuredLength < textLine.Length)
  95. {
  96. var currentBreakPosition = 0;
  97. var lineBreaker = new LineBreakEnumerator(currentRun.Text.Span);
  98. while (currentBreakPosition < measuredLength && lineBreaker.MoveNext(out var lineBreak))
  99. {
  100. var nextBreakPosition = lineBreak.PositionMeasure;
  101. if (nextBreakPosition == 0)
  102. {
  103. break;
  104. }
  105. if (nextBreakPosition >= measuredLength)
  106. {
  107. break;
  108. }
  109. currentBreakPosition = nextBreakPosition;
  110. }
  111. measuredLength = currentBreakPosition;
  112. }
  113. }
  114. collapsedLength += measuredLength;
  115. return CreateCollapsedRuns(textLine, collapsedLength, FlowDirection.RightToLeft, shapedSymbol);
  116. }
  117. availableWidth -= shapedRun.Size.Width;
  118. break;
  119. }
  120. case DrawableTextRun drawableRun:
  121. {
  122. //The whole run needs to fit into available space
  123. if (currentWidth + drawableRun.Size.Width > availableWidth)
  124. {
  125. return CreateCollapsedRuns(textLine, collapsedLength, FlowDirection.RightToLeft, shapedSymbol);
  126. }
  127. availableWidth -= drawableRun.Size.Width;
  128. break;
  129. }
  130. }
  131. collapsedLength += currentRun.Length;
  132. runIndex--;
  133. }
  134. }
  135. return null;
  136. }
  137. private static TextRun[] CreateCollapsedRuns(TextLine textLine, int collapsedLength,
  138. FlowDirection flowDirection, TextRun shapedSymbol)
  139. {
  140. var textRuns = textLine.TextRuns;
  141. if (collapsedLength <= 0)
  142. {
  143. return new[] { shapedSymbol };
  144. }
  145. if(flowDirection == FlowDirection.RightToLeft)
  146. {
  147. collapsedLength = textLine.Length - collapsedLength;
  148. }
  149. var objectPool = FormattingObjectPool.Instance;
  150. var (preSplitRuns, postSplitRuns) = TextFormatterImpl.SplitTextRuns(textRuns, collapsedLength, objectPool);
  151. try
  152. {
  153. if (flowDirection == FlowDirection.RightToLeft)
  154. {
  155. var collapsedRuns = new TextRun[postSplitRuns!.Count + 1];
  156. postSplitRuns.CopyTo(collapsedRuns, 1);
  157. collapsedRuns[0] = shapedSymbol;
  158. return collapsedRuns;
  159. }
  160. else
  161. {
  162. var collapsedRuns = new TextRun[preSplitRuns!.Count + 1];
  163. preSplitRuns.CopyTo(collapsedRuns);
  164. collapsedRuns[collapsedRuns.Length - 1] = shapedSymbol;
  165. return collapsedRuns;
  166. }
  167. }
  168. finally
  169. {
  170. objectPool.TextRunLists.Return(ref preSplitRuns);
  171. objectPool.TextRunLists.Return(ref postSplitRuns);
  172. }
  173. }
  174. }
  175. }