SimpleTextFormatterTests.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. using System;
  2. using Avalonia.Media;
  3. using Avalonia.Media.TextFormatting;
  4. using Avalonia.UnitTests;
  5. using Avalonia.Utility;
  6. using Xunit;
  7. namespace Avalonia.Skia.UnitTests
  8. {
  9. public class SimpleTextFormatterTests
  10. {
  11. [Fact]
  12. public void Should_Format_TextRuns_With_Default_Style()
  13. {
  14. using (Start())
  15. {
  16. const string text = "0123456789";
  17. var defaultTextRunStyle = new TextStyle(Typeface.Default, 12, Brushes.Black);
  18. var textSource = new SimpleTextSource(text, defaultTextRunStyle);
  19. var formatter = new SimpleTextFormatter();
  20. var textLine = formatter.FormatLine(textSource, 0, double.PositiveInfinity, new TextParagraphProperties());
  21. Assert.Single(textLine.TextRuns);
  22. var textRun = textLine.TextRuns[0];
  23. Assert.Equal(defaultTextRunStyle.TextFormat, textRun.Style.TextFormat);
  24. Assert.Equal(defaultTextRunStyle.Foreground, textRun.Style.Foreground);
  25. Assert.Equal(text.Length, textRun.Text.Length);
  26. }
  27. }
  28. [Fact]
  29. public void Should_Format_TextRuns_With_Multiple_Buffers()
  30. {
  31. using (Start())
  32. {
  33. var defaultTextRunStyle = new TextStyle(Typeface.Default, 12, Brushes.Black);
  34. var textSource = new MultiBufferTextSource(defaultTextRunStyle);
  35. var formatter = new SimpleTextFormatter();
  36. var textLine = formatter.FormatLine(textSource, 0, double.PositiveInfinity,
  37. new TextParagraphProperties(defaultTextRunStyle));
  38. Assert.Equal(5, textLine.TextRuns.Count);
  39. Assert.Equal(50, textLine.Text.Length);
  40. }
  41. }
  42. private class MultiBufferTextSource : ITextSource
  43. {
  44. private readonly string[] _runTexts;
  45. private readonly TextStyle _defaultStyle;
  46. public MultiBufferTextSource(TextStyle defaultStyle)
  47. {
  48. _defaultStyle = defaultStyle;
  49. _runTexts = new[] { "A123456789", "B123456789", "C123456789", "D123456789", "E123456789" };
  50. }
  51. public TextPointer TextPointer => new TextPointer(0, 50);
  52. public TextRun GetTextRun(int textSourceIndex)
  53. {
  54. if (textSourceIndex == 50)
  55. {
  56. return new TextEndOfParagraph();
  57. }
  58. var index = textSourceIndex / 10;
  59. var runText = _runTexts[index];
  60. return new TextCharacters(
  61. new ReadOnlySlice<char>(runText.AsMemory(), textSourceIndex, runText.Length), _defaultStyle);
  62. }
  63. }
  64. [Fact]
  65. public void Should_Format_TextRuns_With_TextRunStyles()
  66. {
  67. using (Start())
  68. {
  69. const string text = "0123456789";
  70. var defaultStyle = new TextStyle(Typeface.Default, 12, Brushes.Black);
  71. var textStyleRuns = new[]
  72. {
  73. new TextStyleRun(new TextPointer(0, 3), defaultStyle ),
  74. new TextStyleRun(new TextPointer(3, 3), new TextStyle(Typeface.Default, 13, Brushes.Black) ),
  75. new TextStyleRun(new TextPointer(6, 3), new TextStyle(Typeface.Default, 14, Brushes.Black) ),
  76. new TextStyleRun(new TextPointer(9, 1), defaultStyle )
  77. };
  78. var textSource = new FormattableTextSource(text, defaultStyle, textStyleRuns);
  79. var formatter = new SimpleTextFormatter();
  80. var textLine = formatter.FormatLine(textSource, 0, double.PositiveInfinity, new TextParagraphProperties());
  81. Assert.Equal(text.Length, textLine.Text.Length);
  82. for (var i = 0; i < textStyleRuns.Length; i++)
  83. {
  84. var textStyleRun = textStyleRuns[i];
  85. var textRun = textLine.TextRuns[i];
  86. Assert.Equal(textStyleRun.TextPointer.Length, textRun.Text.Length);
  87. }
  88. }
  89. }
  90. private class FormattableTextSource : ITextSource
  91. {
  92. private readonly ReadOnlySlice<char> _text;
  93. private readonly TextStyle _defaultStyle;
  94. private ReadOnlySlice<TextStyleRun> _textStyleRuns;
  95. public FormattableTextSource(string text, TextStyle defaultStyle, ReadOnlySlice<TextStyleRun> textStyleRuns)
  96. {
  97. _text = text.AsMemory();
  98. _defaultStyle = defaultStyle;
  99. _textStyleRuns = textStyleRuns;
  100. }
  101. public TextRun GetTextRun(int textSourceIndex)
  102. {
  103. if (_textStyleRuns.IsEmpty)
  104. {
  105. return new TextEndOfParagraph();
  106. }
  107. var styleRun = _textStyleRuns[0];
  108. _textStyleRuns = _textStyleRuns.Skip(1);
  109. return new TextCharacters(_text.AsSlice(styleRun.TextPointer.Start, styleRun.TextPointer.Length),
  110. _defaultStyle);
  111. }
  112. }
  113. [Theory]
  114. [InlineData("0123", 1)]
  115. [InlineData("\r\n", 1)]
  116. [InlineData("👍b", 2)]
  117. [InlineData("a👍b", 3)]
  118. [InlineData("a👍子b", 4)]
  119. public void Should_Produce_Unique_Runs(string text, int numberOfRuns)
  120. {
  121. using (Start())
  122. {
  123. var textSource = new SimpleTextSource(text, new TextStyle(Typeface.Default));
  124. var formatter = new SimpleTextFormatter();
  125. var textLine =
  126. formatter.FormatLine(textSource, 0, double.PositiveInfinity, new TextParagraphProperties());
  127. Assert.Equal(numberOfRuns, textLine.TextRuns.Count);
  128. }
  129. }
  130. private class SimpleTextSource : ITextSource
  131. {
  132. private readonly ReadOnlySlice<char> _text;
  133. private readonly TextStyle _defaultTextStyle;
  134. public SimpleTextSource(string text, TextStyle defaultText)
  135. {
  136. _text = text.AsMemory();
  137. _defaultTextStyle = defaultText;
  138. }
  139. public TextRun GetTextRun(int textSourceIndex)
  140. {
  141. var runText = _text.Skip(textSourceIndex);
  142. if (runText.IsEmpty)
  143. {
  144. return new TextEndOfParagraph();
  145. }
  146. return new TextCharacters(runText, _defaultTextStyle);
  147. }
  148. }
  149. [Fact]
  150. public void Should_Split_Run_On_Script()
  151. {
  152. using (Start())
  153. {
  154. const string text = "1234الدولي";
  155. var textSource = new SimpleTextSource(text, new TextStyle(Typeface.Default));
  156. var formatter = new SimpleTextFormatter();
  157. var textLine =
  158. formatter.FormatLine(textSource, 0, double.PositiveInfinity, new TextParagraphProperties());
  159. Assert.Equal(4, textLine.TextRuns[0].Text.Length);
  160. }
  161. }
  162. [Fact]
  163. public void Should_Get_Distance_From_CharacterHit()
  164. {
  165. using (Start())
  166. {
  167. var textSource = new MultiBufferTextSource(new TextStyle(Typeface.Default));
  168. var formatter = new SimpleTextFormatter();
  169. var textLine =
  170. formatter.FormatLine(textSource, 0, double.PositiveInfinity, new TextParagraphProperties());
  171. var currentDistance = 0.0;
  172. foreach (var run in textLine.TextRuns)
  173. {
  174. var textRun = (ShapedTextRun)run;
  175. var glyphRun = textRun.GlyphRun;
  176. for (var i = 0; i < glyphRun.GlyphClusters.Length; i++)
  177. {
  178. var cluster = glyphRun.GlyphClusters[i];
  179. var advance = glyphRun.GlyphAdvances[i];
  180. var distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(cluster));
  181. Assert.Equal(currentDistance, distance);
  182. currentDistance += advance;
  183. }
  184. }
  185. Assert.Equal(currentDistance, textLine.GetDistanceFromCharacterHit(new CharacterHit(textSource.TextPointer.Length)));
  186. }
  187. }
  188. [Fact]
  189. public void Should_Get_CharacterHit_From_Distance()
  190. {
  191. using (Start())
  192. {
  193. var textSource = new MultiBufferTextSource(new TextStyle(Typeface.Default));
  194. var formatter = new SimpleTextFormatter();
  195. var textLine =
  196. formatter.FormatLine(textSource, 0, double.PositiveInfinity, new TextParagraphProperties());
  197. var currentDistance = 0.0;
  198. CharacterHit characterHit;
  199. foreach (var run in textLine.TextRuns)
  200. {
  201. var textRun = (ShapedTextRun)run;
  202. var glyphRun = textRun.GlyphRun;
  203. for (var i = 0; i < glyphRun.GlyphClusters.Length; i++)
  204. {
  205. var cluster = glyphRun.GlyphClusters[i];
  206. var advance = glyphRun.GlyphAdvances[i];
  207. characterHit = textLine.GetCharacterHitFromDistance(currentDistance);
  208. Assert.Equal(cluster, characterHit.FirstCharacterIndex + characterHit.TrailingLength);
  209. currentDistance += advance;
  210. }
  211. }
  212. characterHit = textLine.GetCharacterHitFromDistance(textLine.LineMetrics.Size.Width);
  213. Assert.Equal(textSource.TextPointer.End, characterHit.FirstCharacterIndex);
  214. }
  215. }
  216. public static IDisposable Start()
  217. {
  218. var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface
  219. .With(renderInterface: new PlatformRenderInterface(null),
  220. textShaperImpl: new TextShaperImpl()));
  221. AvaloniaLocator.CurrentMutable
  222. .Bind<FontManager>().ToConstant(new FontManager(new CustomFontManagerImpl()));
  223. return disposable;
  224. }
  225. }
  226. }