TextFormatterTests.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. using System;
  2. using System.Collections.Generic;
  3. using Avalonia.Media;
  4. using Avalonia.Media.TextFormatting;
  5. using Avalonia.Media.TextFormatting.Unicode;
  6. using Avalonia.UnitTests;
  7. using Avalonia.Utilities;
  8. using Xunit;
  9. namespace Avalonia.Skia.UnitTests.Media.TextFormatting
  10. {
  11. public class TextFormatterTests
  12. {
  13. [Fact]
  14. public void Should_Format_TextRuns_With_Default_Style()
  15. {
  16. using (Start())
  17. {
  18. const string text = "0123456789";
  19. var defaultProperties =
  20. new GenericTextRunProperties(Typeface.Default, 12, foregroundBrush: Brushes.Black);
  21. var textSource = new SingleBufferTextSource(text, defaultProperties);
  22. var formatter = new TextFormatterImpl();
  23. var textLine = formatter.FormatLine(textSource, 0, double.PositiveInfinity,
  24. new GenericTextParagraphProperties(defaultProperties));
  25. Assert.Single(textLine.TextRuns);
  26. var textRun = textLine.TextRuns[0];
  27. Assert.Equal(defaultProperties.Typeface, textRun.Properties.Typeface);
  28. Assert.Equal(defaultProperties.ForegroundBrush, textRun.Properties.ForegroundBrush);
  29. Assert.Equal(text.Length, textRun.Text.Length);
  30. }
  31. }
  32. [Fact]
  33. public void Should_Format_TextRuns_With_Multiple_Buffers()
  34. {
  35. using (Start())
  36. {
  37. var defaultProperties =
  38. new GenericTextRunProperties(Typeface.Default, 12, foregroundBrush: Brushes.Black);
  39. var textSource = new MultiBufferTextSource(defaultProperties);
  40. var formatter = new TextFormatterImpl();
  41. var textLine = formatter.FormatLine(textSource, 0, double.PositiveInfinity,
  42. new GenericTextParagraphProperties(defaultProperties));
  43. Assert.Equal(5, textLine.TextRuns.Count);
  44. Assert.Equal(50, textLine.TextRange.Length);
  45. }
  46. }
  47. [Fact]
  48. public void Should_Format_TextRuns_With_TextRunStyles()
  49. {
  50. using (Start())
  51. {
  52. const string text = "0123456789";
  53. var defaultProperties =
  54. new GenericTextRunProperties(Typeface.Default, 12, foregroundBrush: Brushes.Black);
  55. var GenericTextRunPropertiesRuns = new[]
  56. {
  57. new ValueSpan<TextRunProperties>(0, 3, defaultProperties),
  58. new ValueSpan<TextRunProperties>(3, 3,
  59. new GenericTextRunProperties(Typeface.Default, 13, foregroundBrush: Brushes.Black)),
  60. new ValueSpan<TextRunProperties>(6, 3,
  61. new GenericTextRunProperties(Typeface.Default, 14, foregroundBrush: Brushes.Black)),
  62. new ValueSpan<TextRunProperties>(9, 1, defaultProperties)
  63. };
  64. var textSource = new FormattableTextSource(text, defaultProperties, GenericTextRunPropertiesRuns);
  65. var formatter = new TextFormatterImpl();
  66. var textLine = formatter.FormatLine(textSource, 0, double.PositiveInfinity,
  67. new GenericTextParagraphProperties(defaultProperties));
  68. Assert.Equal(text.Length, textLine.TextRange.Length);
  69. for (var i = 0; i < GenericTextRunPropertiesRuns.Length; i++)
  70. {
  71. var GenericTextRunPropertiesRun = GenericTextRunPropertiesRuns[i];
  72. var textRun = textLine.TextRuns[i];
  73. Assert.Equal(GenericTextRunPropertiesRun.Length, textRun.Text.Length);
  74. }
  75. }
  76. }
  77. [Theory]
  78. [InlineData("0123", 1)]
  79. [InlineData("\r\n", 1)]
  80. [InlineData("👍b", 2)]
  81. [InlineData("a👍b", 3)]
  82. [InlineData("a👍子b", 4)]
  83. public void Should_Produce_Unique_Runs(string text, int numberOfRuns)
  84. {
  85. using (Start())
  86. {
  87. var defaultProperties = new GenericTextRunProperties(Typeface.Default);
  88. var textSource = new SingleBufferTextSource(text, defaultProperties);
  89. var formatter = new TextFormatterImpl();
  90. var textLine =
  91. formatter.FormatLine(textSource, 0, double.PositiveInfinity,
  92. new GenericTextParagraphProperties(defaultProperties));
  93. Assert.Equal(numberOfRuns, textLine.TextRuns.Count);
  94. }
  95. }
  96. [Fact]
  97. public void Should_Split_Run_On_Script()
  98. {
  99. using (Start())
  100. {
  101. const string text = "ABCDالدولي";
  102. var defaultProperties = new GenericTextRunProperties(Typeface.Default);
  103. var textSource = new SingleBufferTextSource(text, defaultProperties);
  104. var formatter = new TextFormatterImpl();
  105. var textLine =
  106. formatter.FormatLine(textSource, 0, double.PositiveInfinity,
  107. new GenericTextParagraphProperties(defaultProperties));
  108. Assert.Equal(4, textLine.TextRuns[0].Text.Length);
  109. }
  110. }
  111. [InlineData("𐐷𐐷𐐷𐐷𐐷", 10, 1)]
  112. [InlineData("01234 56789 01234 56789", 6, 4)]
  113. [Theory]
  114. public void Should_Wrap_With_Overflow(string text, int expectedCharactersPerLine, int expectedNumberOfLines)
  115. {
  116. using (Start())
  117. {
  118. var defaultProperties = new GenericTextRunProperties(Typeface.Default);
  119. var textSource = new SingleBufferTextSource(text, defaultProperties);
  120. var formatter = new TextFormatterImpl();
  121. var numberOfLines = 0;
  122. var currentPosition = 0;
  123. while (currentPosition < text.Length)
  124. {
  125. var textLine =
  126. formatter.FormatLine(textSource, currentPosition, 1,
  127. new GenericTextParagraphProperties(defaultProperties, textWrapping: TextWrapping.WrapWithOverflow));
  128. if (text.Length - currentPosition > expectedCharactersPerLine)
  129. {
  130. Assert.Equal(expectedCharactersPerLine, textLine.TextRange.Length);
  131. }
  132. currentPosition += textLine.TextRange.Length;
  133. numberOfLines++;
  134. }
  135. Assert.Equal(expectedNumberOfLines, numberOfLines);
  136. }
  137. }
  138. [InlineData("Whether to turn off HTTPS. This option only applies if Individual, " +
  139. "IndividualB2C, SingleOrg, or MultiOrg aren't used for &#8209;&#8209;auth."
  140. , "Noto Sans", 40)]
  141. [InlineData("01234 56789 01234 56789", "Noto Mono", 7)]
  142. [Theory]
  143. public void Should_Wrap(string text, string familyName, int numberOfCharactersPerLine)
  144. {
  145. using (Start())
  146. {
  147. var lineBreaker = new LineBreakEnumerator(text.AsMemory());
  148. var expected = new List<int>();
  149. while (lineBreaker.MoveNext())
  150. {
  151. expected.Add(lineBreaker.Current.PositionWrap - 1);
  152. }
  153. var typeface = new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#" +
  154. familyName);
  155. var defaultProperties = new GenericTextRunProperties(Typeface.Default);
  156. var textSource = new SingleBufferTextSource(text, defaultProperties);
  157. var formatter = new TextFormatterImpl();
  158. var glyph = typeface.GlyphTypeface.GetGlyph('a');
  159. var advance = typeface.GlyphTypeface.GetGlyphAdvance(glyph) *
  160. (12.0 / typeface.GlyphTypeface.DesignEmHeight);
  161. var paragraphWidth = advance * numberOfCharactersPerLine;
  162. var currentPosition = 0;
  163. while (currentPosition < text.Length)
  164. {
  165. var textLine =
  166. formatter.FormatLine(textSource, currentPosition, paragraphWidth,
  167. new GenericTextParagraphProperties(defaultProperties, textWrapping: TextWrapping.Wrap));
  168. Assert.True(expected.Contains(textLine.TextRange.End));
  169. var index = expected.IndexOf(textLine.TextRange.End);
  170. for (var i = 0; i <= index; i++)
  171. {
  172. expected.RemoveAt(0);
  173. }
  174. currentPosition += textLine.TextRange.Length;
  175. }
  176. }
  177. }
  178. [Fact]
  179. public void Should_Produce_Fixed_Height_Lines()
  180. {
  181. using (Start())
  182. {
  183. const string text = "012345";
  184. var defaultProperties = new GenericTextRunProperties(Typeface.Default);
  185. var textSource = new SingleBufferTextSource(text, defaultProperties);
  186. var formatter = new TextFormatterImpl();
  187. var textLine =
  188. formatter.FormatLine(textSource, 0, double.PositiveInfinity,
  189. new GenericTextParagraphProperties(defaultProperties, lineHeight: 50));
  190. Assert.Equal(50, textLine.LineMetrics.Size.Height);
  191. }
  192. }
  193. [Fact]
  194. public void Should_Not_Produce_TextLine_Wider_Than_ParagraphWidth()
  195. {
  196. using (Start())
  197. {
  198. const string text =
  199. "Multiline TextBlock with TextWrapping.\r\rLorem ipsum dolor sit amet, consectetur adipiscing elit. " +
  200. "Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. " +
  201. "Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. " +
  202. "Vivamus pretium ornare est.";
  203. var defaultProperties = new GenericTextRunProperties(Typeface.Default);
  204. var paragraphProperties = new GenericTextParagraphProperties(defaultProperties, textWrapping: TextWrapping.Wrap);
  205. var textSource = new SingleBufferTextSource(text, defaultProperties);
  206. var formatter = new TextFormatterImpl();
  207. var textSourceIndex = 0;
  208. while (textSourceIndex < text.Length)
  209. {
  210. var textLine =
  211. formatter.FormatLine(textSource, textSourceIndex, 200, paragraphProperties);
  212. Assert.True(textLine.LineMetrics.Size.Width <= 200);
  213. textSourceIndex += textLine.TextRange.Length;
  214. }
  215. }
  216. }
  217. public static IDisposable Start()
  218. {
  219. var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface
  220. .With(renderInterface: new PlatformRenderInterface(null),
  221. textShaperImpl: new TextShaperImpl()));
  222. AvaloniaLocator.CurrentMutable
  223. .Bind<FontManager>().ToConstant(new FontManager(new CustomFontManagerImpl()));
  224. return disposable;
  225. }
  226. }
  227. }