LineBreakEnumuratorTests.cs 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Net.Http;
  7. using Avalonia.Media.TextFormatting.Unicode;
  8. using Xunit;
  9. using Xunit.Abstractions;
  10. namespace Avalonia.Base.UnitTests.Media.TextFormatting
  11. {
  12. public class LineBreakEnumeratorTests
  13. {
  14. private readonly ITestOutputHelper _outputHelper;
  15. public LineBreakEnumeratorTests(ITestOutputHelper outputHelper)
  16. {
  17. _outputHelper = outputHelper;
  18. }
  19. [Fact]
  20. public void BasicLatinTest()
  21. {
  22. var lineBreaker = new LineBreakEnumerator("Hello World\r\nThis is a test.".AsMemory());
  23. Assert.True(lineBreaker.MoveNext());
  24. Assert.Equal(6, lineBreaker.Current.PositionWrap);
  25. Assert.False(lineBreaker.Current.Required);
  26. Assert.True(lineBreaker.MoveNext());
  27. Assert.Equal(13, lineBreaker.Current.PositionWrap);
  28. Assert.True(lineBreaker.Current.Required);
  29. Assert.True(lineBreaker.MoveNext());
  30. Assert.Equal(18, lineBreaker.Current.PositionWrap);
  31. Assert.False(lineBreaker.Current.Required);
  32. Assert.True(lineBreaker.MoveNext());
  33. Assert.Equal(21, lineBreaker.Current.PositionWrap);
  34. Assert.False(lineBreaker.Current.Required);
  35. Assert.True(lineBreaker.MoveNext());
  36. Assert.Equal(23, lineBreaker.Current.PositionWrap);
  37. Assert.False(lineBreaker.Current.Required);
  38. Assert.True(lineBreaker.MoveNext());
  39. Assert.Equal(28, lineBreaker.Current.PositionWrap);
  40. Assert.False(lineBreaker.Current.Required);
  41. Assert.False(lineBreaker.MoveNext());
  42. }
  43. [Fact]
  44. public void ForwardTextWithOuterWhitespace()
  45. {
  46. var lineBreaker = new LineBreakEnumerator(" Apples Pears Bananas ".AsMemory());
  47. var positionsF = GetBreaks(lineBreaker);
  48. Assert.Equal(1, positionsF[0].PositionWrap);
  49. Assert.Equal(0, positionsF[0].PositionMeasure);
  50. Assert.Equal(8, positionsF[1].PositionWrap);
  51. Assert.Equal(7, positionsF[1].PositionMeasure);
  52. Assert.Equal(14, positionsF[2].PositionWrap);
  53. Assert.Equal(13, positionsF[2].PositionMeasure);
  54. Assert.Equal(24, positionsF[3].PositionWrap);
  55. Assert.Equal(21, positionsF[3].PositionMeasure);
  56. }
  57. private static List<LineBreak> GetBreaks(LineBreakEnumerator lineBreaker)
  58. {
  59. var breaks = new List<LineBreak>();
  60. while (lineBreaker.MoveNext())
  61. {
  62. breaks.Add(lineBreaker.Current);
  63. }
  64. return breaks;
  65. }
  66. [Fact]
  67. public void ForwardTest()
  68. {
  69. var lineBreaker = new LineBreakEnumerator("Apples Pears Bananas".AsMemory());
  70. var positionsF = GetBreaks(lineBreaker);
  71. Assert.Equal(7, positionsF[0].PositionWrap);
  72. Assert.Equal(6, positionsF[0].PositionMeasure);
  73. Assert.Equal(13, positionsF[1].PositionWrap);
  74. Assert.Equal(12, positionsF[1].PositionMeasure);
  75. Assert.Equal(20, positionsF[2].PositionWrap);
  76. Assert.Equal(20, positionsF[2].PositionMeasure);
  77. }
  78. [Theory(Skip = "Only run when the Unicode spec changes.")]
  79. [ClassData(typeof(LineBreakTestDataGenerator))]
  80. public void ShouldFindBreaks(int lineNumber, int[] codePoints, int[] breakPoints)
  81. {
  82. var text = string.Join(null, codePoints.Select(char.ConvertFromUtf32));
  83. var lineBreaker = new LineBreakEnumerator(text.AsMemory());
  84. var foundBreaks = new List<int>();
  85. while (lineBreaker.MoveNext())
  86. {
  87. foundBreaks.Add(lineBreaker.Current.PositionWrap);
  88. }
  89. // Check the same
  90. var pass = true;
  91. if (foundBreaks.Count != breakPoints.Length)
  92. {
  93. pass = false;
  94. }
  95. else
  96. {
  97. for (var i = 0; i < foundBreaks.Count; i++)
  98. {
  99. if (foundBreaks[i] != breakPoints[i])
  100. {
  101. pass = false;
  102. }
  103. }
  104. }
  105. if (!pass)
  106. {
  107. _outputHelper.WriteLine($"Failed test on line {lineNumber}");
  108. _outputHelper.WriteLine("");
  109. _outputHelper.WriteLine($" Code Points: {string.Join(" ", codePoints)}");
  110. _outputHelper.WriteLine($"Expected Breaks: {string.Join(" ", breakPoints)}");
  111. _outputHelper.WriteLine($" Actual Breaks: {string.Join(" ", foundBreaks)}");
  112. _outputHelper.WriteLine($" Text: {text}");
  113. _outputHelper.WriteLine($" Char Props: {string.Join(" ", codePoints.Select(x => new Codepoint((uint)x).LineBreakClass))}");
  114. _outputHelper.WriteLine("");
  115. }
  116. Assert.True(pass);
  117. }
  118. private class LineBreakTestDataGenerator : IEnumerable<object[]>
  119. {
  120. private readonly List<object[]> _testData;
  121. public LineBreakTestDataGenerator()
  122. {
  123. _testData = GenerateTestData();
  124. }
  125. public IEnumerator<object[]> GetEnumerator()
  126. {
  127. return _testData.GetEnumerator();
  128. }
  129. IEnumerator IEnumerable.GetEnumerator()
  130. {
  131. return GetEnumerator();
  132. }
  133. private static List<object[]> GenerateTestData()
  134. {
  135. // Process each line
  136. var tests = new List<object[]>();
  137. // Read the test file
  138. var url = Path.Combine(UnicodeDataGenerator.Ucd, "auxiliary/LineBreakTest.txt");
  139. using (var client = new HttpClient())
  140. using (var result = client.GetAsync(url).GetAwaiter().GetResult())
  141. {
  142. if (!result.IsSuccessStatusCode)
  143. {
  144. return tests;
  145. }
  146. using (var stream = result.Content.ReadAsStreamAsync().GetAwaiter().GetResult())
  147. using (var reader = new StreamReader(stream))
  148. {
  149. var lineNumber = 1;
  150. while (!reader.EndOfStream)
  151. {
  152. var line = reader.ReadLine();
  153. if (line is null)
  154. {
  155. break;
  156. }
  157. // Get the line, remove comments
  158. line = line.Split('#')[0].Trim();
  159. // Ignore blank/comment only lines
  160. if (string.IsNullOrWhiteSpace(line))
  161. {
  162. lineNumber++;
  163. continue;
  164. }
  165. var codePoints = new List<int>();
  166. var breakPoints = new List<int>();
  167. // Parse the test
  168. var p = 0;
  169. while (p < line.Length)
  170. {
  171. // Ignore white space
  172. if (char.IsWhiteSpace(line[p]))
  173. {
  174. p++;
  175. continue;
  176. }
  177. if (line[p] == '×')
  178. {
  179. p++;
  180. continue;
  181. }
  182. if (line[p] == '÷')
  183. {
  184. breakPoints.Add(codePoints.Select(x=> x > ushort.MaxValue ? 2 : 1).Sum());
  185. p++;
  186. continue;
  187. }
  188. var codePointPos = p;
  189. while (p < line.Length && IsHexDigit(line[p]))
  190. {
  191. p++;
  192. }
  193. var codePointStr = line.Substring(codePointPos, p - codePointPos);
  194. var codePoint = Convert.ToInt32(codePointStr, 16);
  195. codePoints.Add(codePoint);
  196. }
  197. tests.Add(new object[] { lineNumber, codePoints.ToArray(), breakPoints.ToArray() });
  198. lineNumber++;
  199. }
  200. }
  201. }
  202. return tests;
  203. }
  204. private static bool IsHexDigit(char ch)
  205. {
  206. return char.IsDigit(ch) || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f');
  207. }
  208. }
  209. }
  210. }