FormattedTextImplTests.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using Avalonia.Media;
  4. using Avalonia.Platform;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Globalization;
  8. using System.Linq;
  9. using System.Text;
  10. using Xunit;
  11. #if AVALONIA_CAIRO
  12. namespace Avalonia.Cairo.RenderTests.Media
  13. #elif AVALONIA_SKIA
  14. namespace Avalonia.Skia.RenderTests
  15. #else
  16. using Avalonia.Direct2D1.RenderTests;
  17. namespace Avalonia.Direct2D1.RenderTests.Media
  18. #endif
  19. {
  20. public class FormattedTextImplTests : TestBase
  21. {
  22. private const string FontName = "Courier New";
  23. private const double FontSize = 12;
  24. private const double MediumFontSize = 18;
  25. private const double BigFontSize = 32;
  26. private const double FontSizeHeight = 13.594;//real value 13.59375
  27. private const string stringword = "word";
  28. private const string stringmiddle = "The quick brown fox jumps over the lazy dog";
  29. private const string stringmiddle2lines = "The quick brown fox\njumps over the lazy dog";
  30. private const string stringmiddle3lines = "01234567\n\n0123456789";
  31. private const string stringmiddlenewlines = "012345678\r 1234567\r\n 12345678\n0123456789";
  32. private const string stringlong =
  33. "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis " +
  34. "aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero" +
  35. " at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus " +
  36. "pretium ornare est.";
  37. public FormattedTextImplTests()
  38. : base(@"Media\FormattedText")
  39. {
  40. }
  41. private IFormattedTextImpl Create(string text,
  42. string fontFamily,
  43. double fontSize,
  44. FontStyle fontStyle,
  45. TextAlignment textAlignment,
  46. FontWeight fontWeight,
  47. TextWrapping wrapping)
  48. {
  49. var r = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
  50. return r.CreateFormattedText(text,
  51. fontFamily,
  52. fontSize,
  53. fontStyle,
  54. textAlignment,
  55. fontWeight,
  56. wrapping,
  57. Size.Infinity);
  58. }
  59. private IFormattedTextImpl Create(string text, double fontSize)
  60. {
  61. return Create(text, FontName, fontSize,
  62. FontStyle.Normal, TextAlignment.Left,
  63. FontWeight.Normal, TextWrapping.NoWrap);
  64. }
  65. private IFormattedTextImpl Create(string text, double fontSize, TextAlignment alignment)
  66. {
  67. return Create(text, FontName, fontSize,
  68. FontStyle.Normal, alignment,
  69. FontWeight.Normal, TextWrapping.NoWrap);
  70. }
  71. private IFormattedTextImpl Create(string text, double fontSize, TextWrapping wrap)
  72. {
  73. return Create(text, FontName, fontSize,
  74. FontStyle.Normal, TextAlignment.Left,
  75. FontWeight.Normal, wrap);
  76. }
  77. #if AVALONIA_CAIRO
  78. [Theory(Skip = "TODO: Font scaling currently broken on cairo")]
  79. #else
  80. [Theory]
  81. #endif
  82. [InlineData("", FontSize, 0, FontSizeHeight)]
  83. [InlineData("x", FontSize, 7.20, FontSizeHeight)]
  84. [InlineData(stringword, FontSize, 28.80, FontSizeHeight)]
  85. [InlineData(stringmiddle, FontSize, 309.65, FontSizeHeight)]
  86. [InlineData(stringmiddle, MediumFontSize, 464.48, 20.391)]
  87. [InlineData(stringmiddle, BigFontSize, 825.73, 36.25)]
  88. [InlineData(stringmiddle2lines, FontSize, 165.63, 2 * FontSizeHeight)]
  89. [InlineData(stringmiddle2lines, MediumFontSize, 248.44, 2 * 20.391)]
  90. [InlineData(stringmiddle2lines, BigFontSize, 441.67, 2 * 36.25)]
  91. [InlineData(stringlong, FontSize, 2160.35, FontSizeHeight)]
  92. [InlineData(stringmiddlenewlines, FontSize, 72.01, 4 * FontSizeHeight)]
  93. public void Should_Measure_String_Correctly(string input, double fontSize, double expWidth, double expHeight)
  94. {
  95. #if !AVALONIA_SKIA
  96. double heightCorr = 0;
  97. #else
  98. //In skia there is a small descent added to last line,
  99. //otherwise some letters are clipped at bottom
  100. //4.55273438 for font 12 size
  101. double heightCorr = 0.3793945*fontSize;
  102. #endif
  103. var fmt = Create(input, fontSize);
  104. var size = fmt.Size;
  105. Assert.Equal(expWidth, size.Width, 2);
  106. Assert.Equal(expHeight + heightCorr, size.Height, 2);
  107. var linesHeight = fmt.GetLines().Sum(l => l.Height);
  108. Assert.Equal(expHeight, linesHeight, 2);
  109. }
  110. #if AVALONIA_CAIRO
  111. [Theory(Skip = "TODO: Font scaling currently broken on cairo")]
  112. #else
  113. [Theory]
  114. #endif
  115. [InlineData("", 1, -1, TextWrapping.NoWrap)]
  116. [InlineData("x", 1, -1, TextWrapping.NoWrap)]
  117. [InlineData(stringword, 1, -1, TextWrapping.NoWrap)]
  118. [InlineData(stringmiddle, 1, -1, TextWrapping.NoWrap)]
  119. [InlineData(stringmiddle, 3, 150, TextWrapping.Wrap)]
  120. [InlineData(stringmiddle2lines, 2, -1, TextWrapping.NoWrap)]
  121. [InlineData(stringmiddle2lines, 3, 150, TextWrapping.Wrap)]
  122. [InlineData(stringlong, 1, -1, TextWrapping.NoWrap)]
  123. [InlineData(stringlong, 18, 150, TextWrapping.Wrap)]
  124. [InlineData(stringmiddlenewlines, 4, -1, TextWrapping.NoWrap)]
  125. [InlineData(stringmiddlenewlines, 4, 150, TextWrapping.Wrap)]
  126. public void Should_Break_Lines_String_Correctly(string input,
  127. int linesCount,
  128. double widthConstraint,
  129. TextWrapping wrap)
  130. {
  131. var fmt = Create(input, FontSize, wrap);
  132. var constrained = fmt;
  133. if (widthConstraint != -1)
  134. {
  135. constrained = fmt.WithConstraint(new Size(widthConstraint, 10000));
  136. }
  137. var lines = constrained.GetLines().ToArray();
  138. Assert.Equal(linesCount, lines.Count());
  139. }
  140. #if AVALONIA_CAIRO
  141. [Theory(Skip = "TODO: Font scaling currently broken on cairo")]
  142. #else
  143. [Theory]
  144. #endif
  145. [InlineData("x", 0, 0, true, false, 0)]
  146. [InlineData(stringword, -1, -1, false, false, 0)]
  147. [InlineData(stringword, 25, 13, true, false, 3)]
  148. [InlineData(stringword, 28.70, 13.5, true, true, 3)]
  149. [InlineData(stringword, 30, 13, false, true, 3)]
  150. [InlineData(stringword + "\r\n", 30, 13, false, false, 4)]
  151. [InlineData(stringword + "\r\nnext", 30, 13, false, false, 4)]
  152. [InlineData(stringword, 300, 13, false, true, 3)]
  153. [InlineData(stringword + "\r\n", 300, 13, false, false, 4)]
  154. [InlineData(stringword + "\r\nnext", 300, 13, false, false, 4)]
  155. [InlineData(stringword, 300, 300, false, true, 3)]
  156. //TODO: Direct2D implementation return textposition 6
  157. //but the text is 6 length, can't find the logic for me it should be 5
  158. //[InlineData(stringword + "\r\n", 300, 300, false, false, 6)]
  159. [InlineData(stringword + "\r\nnext", 300, 300, false, true, 9)]
  160. [InlineData(stringword + "\r\nnext", 300, 25, false, true, 9)]
  161. [InlineData(stringword, 28, 15, false, true, 3)]
  162. [InlineData(stringword, 30, 15, false, true, 3)]
  163. [InlineData(stringmiddle3lines, 30, 15, false, false, 9)]
  164. [InlineData(stringmiddle3lines, 500, 13, false, false, 8)]
  165. [InlineData(stringmiddle3lines, 30, 25, false, false, 9)]
  166. [InlineData(stringmiddle3lines, -1, 30, false, false, 10)]
  167. public void Should_HitTestPoint_Correctly(string input,
  168. double x, double y,
  169. bool isInside, bool isTrailing, int pos)
  170. {
  171. var fmt = Create(input, FontSize);
  172. var htRes = fmt.HitTestPoint(new Point(x, y));
  173. Assert.Equal(pos, htRes.TextPosition);
  174. Assert.Equal(isInside, htRes.IsInside);
  175. Assert.Equal(isTrailing, htRes.IsTrailing);
  176. }
  177. #if AVALONIA_CAIRO
  178. [Theory(Skip = "TODO: Font scaling currently broken on cairo")]
  179. #else
  180. [Theory]
  181. #endif
  182. [InlineData("", 0, 0, 0, 0, FontSizeHeight)]
  183. [InlineData("x", 0, 0, 0, 7.20, FontSizeHeight)]
  184. [InlineData("x", -1, 7.20, 0, 0, FontSizeHeight)]
  185. [InlineData(stringword, 3, 21.60, 0, 7.20, FontSizeHeight)]
  186. [InlineData(stringword, 4, 21.60 + 7.20, 0, 0, FontSizeHeight)]
  187. [InlineData(stringmiddlenewlines, 10, 0, FontSizeHeight, 7.20, FontSizeHeight)]
  188. [InlineData(stringmiddlenewlines, 15, 36.01, FontSizeHeight, 7.20, FontSizeHeight)]
  189. [InlineData(stringmiddlenewlines, 20, 0, 2 * FontSizeHeight, 7.20, FontSizeHeight)]
  190. [InlineData(stringmiddlenewlines, -1, 72.01, 3 * FontSizeHeight, 0, FontSizeHeight)]
  191. public void Should_HitTestPosition_Correctly(string input,
  192. int index, double x, double y, double width, double height)
  193. {
  194. var fmt = Create(input, FontSize);
  195. var r = fmt.HitTestTextPosition(index);
  196. Assert.Equal(x, r.X, 2);
  197. Assert.Equal(y, r.Y, 2);
  198. Assert.Equal(width, r.Width, 2);
  199. Assert.Equal(height, r.Height, 2);
  200. }
  201. #if AVALONIA_CAIRO
  202. [Theory(Skip = "TODO: Font scaling currently broken on cairo")]
  203. #else
  204. [Theory]
  205. #endif
  206. [InlineData("x", 0, 200, 200 - 7.20, 0, 7.20, FontSizeHeight)]
  207. [InlineData(stringword, 0, 200, 171.20, 0, 7.20, FontSizeHeight)]
  208. [InlineData(stringword, 3, 200, 200 - 7.20, 0, 7.20, FontSizeHeight)]
  209. public void Should_HitTestPosition_RigthAlign_Correctly(
  210. string input, int index, double widthConstraint,
  211. double x, double y, double width, double height)
  212. {
  213. //parse expected
  214. var fmt = Create(input, FontSize, TextAlignment.Right);
  215. var constrained = fmt;
  216. if (widthConstraint != -1)
  217. {
  218. constrained = fmt.WithConstraint(new Size(widthConstraint, 100));
  219. }
  220. var r = constrained.HitTestTextPosition(index);
  221. Assert.Equal(x, r.X, 2);
  222. Assert.Equal(y, r.Y, 2);
  223. Assert.Equal(width, r.Width, 2);
  224. Assert.Equal(height, r.Height, 2);
  225. }
  226. #if AVALONIA_CAIRO
  227. [Theory(Skip = "TODO: Font scaling currently broken on cairo")]
  228. #else
  229. [Theory]
  230. #endif
  231. [InlineData("x", 0, 200, 100 - 7.20 / 2, 0, 7.20, FontSizeHeight)]
  232. [InlineData(stringword, 0, 200, 85.6, 0, 7.20, FontSizeHeight)]
  233. [InlineData(stringword, 3, 200, 100 + 7.20, 0, 7.20, FontSizeHeight)]
  234. public void Should_HitTestPosition_CenterAlign_Correctly(
  235. string input, int index, double widthConstraint,
  236. double x, double y, double width, double height)
  237. {
  238. //parse expected
  239. var fmt = Create(input, FontSize, TextAlignment.Center);
  240. var constrained = fmt;
  241. if (widthConstraint != -1)
  242. {
  243. constrained = fmt.WithConstraint(new Size(widthConstraint, 100));
  244. }
  245. var r = constrained.HitTestTextPosition(index);
  246. Assert.Equal(x, r.X, 2);
  247. Assert.Equal(y, r.Y, 2);
  248. Assert.Equal(width, r.Width, 2);
  249. Assert.Equal(height, r.Height, 2);
  250. }
  251. #if AVALONIA_CAIRO
  252. [Theory(Skip = "TODO: Font scaling currently broken on cairo")]
  253. #else
  254. [Theory]
  255. #endif
  256. [InlineData("x", 0, 1, "0,0,7.20,13.59")]
  257. [InlineData(stringword, 0, 4, "0,0,28.80,13.59")]
  258. [InlineData(stringmiddlenewlines, 10, 10, "0,13.59,57.61,13.59")]
  259. [InlineData(stringmiddlenewlines, 10, 20, "0,13.59,57.61,13.59;0,27.19,64.81,13.59")]
  260. [InlineData(stringmiddlenewlines, 10, 15, "0,13.59,57.61,13.59;0,27.19,36.01,13.59")]
  261. [InlineData(stringmiddlenewlines, 15, 15, "36.01,13.59,21.60,13.59;0,27.19,64.81,13.59")]
  262. public void Should_HitTestRange_Correctly(string input,
  263. int index, int length,
  264. string expectedRects)
  265. {
  266. //parse expected result
  267. var rects = expectedRects.Split(';').Select(s =>
  268. {
  269. double[] v = s.Split(',')
  270. .Select(sd => double.Parse(sd, CultureInfo.InvariantCulture)).ToArray();
  271. return new Rect(v[0], v[1], v[2], v[3]);
  272. }).ToArray();
  273. var fmt = Create(input, FontSize);
  274. var htRes = fmt.HitTestTextRange(index, length).ToArray();
  275. Assert.Equal(rects.Length, htRes.Length);
  276. for (int i = 0; i < rects.Length; i++)
  277. {
  278. var exr = rects[i];
  279. var r = htRes[i];
  280. Assert.Equal(exr.X, r.X, 2);
  281. Assert.Equal(exr.Y, r.Y, 2);
  282. Assert.Equal(exr.Width, r.Width, 2);
  283. Assert.Equal(exr.Height, r.Height, 2);
  284. }
  285. }
  286. }
  287. }