LayoutTest.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. using QuestPDF.Drawing;
  2. using QuestPDF.Drawing.Proxy;
  3. using QuestPDF.Elements;
  4. using QuestPDF.Fluent;
  5. using QuestPDF.Helpers;
  6. using QuestPDF.Infrastructure;
  7. using SkiaSharp;
  8. namespace QuestPDF.LayoutTests.TestEngine;
  9. internal class LayoutBuilderDescriptor
  10. {
  11. public void Compose(Action<IContainer> container)
  12. {
  13. }
  14. }
  15. internal class DocumentLayoutBuilder
  16. {
  17. public List<PageDrawingCommand> Commands { get; } = new();
  18. public PageLayoutDescriptor Page()
  19. {
  20. var page = new PageDrawingCommand();
  21. Commands.Add(page);
  22. return new PageLayoutDescriptor(page);
  23. }
  24. }
  25. internal class PageLayoutDescriptor
  26. {
  27. private PageDrawingCommand Command { get; }
  28. public PageLayoutDescriptor(PageDrawingCommand command)
  29. {
  30. Command = command;
  31. }
  32. public PageLayoutDescriptor TakenAreaSize(float width, float height)
  33. {
  34. Command.RequiredArea = new Size(width, height);
  35. return this;
  36. }
  37. public PageLayoutDescriptor Content(Action<PageLayoutBuilder> content)
  38. {
  39. var pageContent = new PageLayoutBuilder();
  40. content(pageContent);
  41. Command.Children = pageContent.Commands;
  42. return this;
  43. }
  44. }
  45. internal class PageLayoutBuilder
  46. {
  47. public List<ChildDrawingCommand> Commands { get;} = new();
  48. public ChildLayoutDescriptor Child(string childId)
  49. {
  50. var child = new ChildDrawingCommand { ChildId = childId };
  51. Commands.Add(child);
  52. return new ChildLayoutDescriptor(child);
  53. }
  54. }
  55. internal class ChildLayoutDescriptor
  56. {
  57. private ChildDrawingCommand Command { get; }
  58. public ChildLayoutDescriptor(ChildDrawingCommand command)
  59. {
  60. Command = command;
  61. }
  62. public ChildLayoutDescriptor Position(float x, float y)
  63. {
  64. Command.Position = new Position(x, y);
  65. return this;
  66. }
  67. public ChildLayoutDescriptor Size(float width, float height)
  68. {
  69. Command.Size = new Size(width, height);
  70. return this;
  71. }
  72. }
  73. internal class ExpectedLayoutChildPosition
  74. {
  75. public string ElementId { get; set; }
  76. public int PageNumber { get; set; }
  77. public int DepthIndex { get; set; }
  78. public Position Position { get; set; }
  79. public Size Size { get; set; }
  80. }
  81. public static class ElementExtensions
  82. {
  83. public static void Mock(this IContainer element, string id, float width, float height)
  84. {
  85. var mock = new MockChild
  86. {
  87. Id = id,
  88. TotalWidth = width,
  89. TotalHeight = height
  90. };
  91. element.Element(mock);
  92. }
  93. }
  94. internal class LayoutTest
  95. {
  96. private const string DocumentColor = Colors.Grey.Lighten1;
  97. private const string PageColor = Colors.Grey.Lighten3;
  98. private const string TargetColor = Colors.White;
  99. public Size PageSize { get; set; }
  100. public ICollection<PageDrawingCommand> ActualCommands { get; set; }
  101. public ICollection<PageDrawingCommand> ExpectedCommands { get; set; }
  102. public static LayoutTest HavingSpaceOfSize(float width, float height)
  103. {
  104. return new LayoutTest
  105. {
  106. PageSize = new Size(width, height)
  107. };
  108. }
  109. public LayoutTest WithContent(Action<IContainer> handler)
  110. {
  111. // compose content
  112. var container = new Container();
  113. container.Element(handler);
  114. ActualCommands = GenerateResult(PageSize, container);
  115. return this;
  116. }
  117. private static ICollection<PageDrawingCommand> GenerateResult(Size pageSize, Container container)
  118. {
  119. // inject dependencies
  120. var pageContext = new PageContext();
  121. pageContext.ResetPageNumber();
  122. var canvas = new PreviewerCanvas();
  123. container.InjectDependencies(pageContext, canvas);
  124. // distribute global state
  125. container.ApplyInheritedAndGlobalTexStyle(TextStyle.Default);
  126. container.ApplyContentDirection(ContentDirection.LeftToRight);
  127. container.ApplyDefaultImageConfiguration(DocumentSettings.Default.ImageRasterDpi, DocumentSettings.Default.ImageCompressionQuality, true);
  128. // render
  129. container.VisitChildren(x => (x as IStateResettable)?.ResetState());
  130. canvas.BeginDocument();
  131. var pageSizes = new List<Size>();
  132. while(true)
  133. {
  134. var spacePlan = container.Measure(pageSize);
  135. pageSizes.Add(spacePlan);
  136. if (spacePlan.Type == SpacePlanType.Wrap)
  137. {
  138. throw new Exception();
  139. }
  140. try
  141. {
  142. canvas.BeginPage(pageSize);
  143. container.Draw(pageSize);
  144. pageContext.IncrementPageNumber();
  145. }
  146. catch (Exception exception)
  147. {
  148. canvas.EndDocument();
  149. throw new Exception();
  150. }
  151. canvas.EndPage();
  152. if (spacePlan.Type == SpacePlanType.FullRender)
  153. break;
  154. }
  155. // extract results
  156. var mocks = container.ExtractElementsOfType<MockChild>().Select(x => x.Value); // mock cannot contain another mock, flat structure
  157. return mocks
  158. .SelectMany(x => x.DrawingCommands)
  159. .GroupBy(x => x.PageNumber)
  160. .Select(x => new PageDrawingCommand
  161. {
  162. RequiredArea = pageSizes[x.Key - 1],
  163. Children = x
  164. .Select(y => new ChildDrawingCommand
  165. {
  166. ChildId = y.ChildId,
  167. Size = y.Size,
  168. Position = y.Position
  169. })
  170. .ToList()
  171. })
  172. .ToList();
  173. }
  174. public void ExpectWrap()
  175. {
  176. }
  177. public LayoutTest ExpectedDrawResult(Action<DocumentLayoutBuilder> handler)
  178. {
  179. var builder = new DocumentLayoutBuilder();
  180. handler(builder);
  181. ExpectedCommands = builder.Commands;
  182. return this;
  183. }
  184. public void CompareVisually()
  185. {
  186. VisualizeExpectedResult(PageSize, ActualCommands, ExpectedCommands);
  187. }
  188. private static void VisualizeExpectedResult(Size pageSize, ICollection<PageDrawingCommand> left, ICollection<PageDrawingCommand> right)
  189. {
  190. var path = "test.pdf";
  191. if (File.Exists(path))
  192. File.Delete(path);
  193. // default colors
  194. var defaultColors = new string[]
  195. {
  196. Colors.Red.Medium,
  197. Colors.Green.Medium,
  198. Colors.Blue.Medium,
  199. Colors.Pink.Medium,
  200. Colors.Orange.Medium,
  201. Colors.Lime.Medium,
  202. Colors.Cyan.Medium,
  203. Colors.Indigo.Medium
  204. };
  205. // determine children colors
  206. var children = Enumerable
  207. .Concat(left, right)
  208. .SelectMany(x => x.Children)
  209. .Select(x => x.ChildId)
  210. .Distinct()
  211. .ToList();
  212. var colors = Enumerable
  213. .Range(0, children.Count)
  214. .ToDictionary(i => children[i], i => defaultColors[i]);
  215. // create new pdf document output
  216. var matrixHeight = Math.Max(left.Count, right.Count);
  217. const int pagePadding = 25;
  218. var imageInfo = new SKImageInfo((int)pageSize.Width * 2 + pagePadding * 4, (int)(pageSize.Height * matrixHeight + pagePadding * (matrixHeight + 2)));
  219. using var pdf = SKDocument.CreatePdf(path);
  220. using var canvas = pdf.BeginPage(imageInfo.Width, imageInfo.Height);
  221. // page background
  222. canvas.Clear(SKColor.Parse(DocumentColor));
  223. // chain titles
  224. // available area
  225. using var titlePaint = TextStyle.LibraryDefault.FontSize(16).Bold().ToPaint().Clone();
  226. titlePaint.TextAlign = SKTextAlign.Center;
  227. canvas.Save();
  228. canvas.Translate(pagePadding + pageSize.Width / 2f, pagePadding + titlePaint.TextSize / 2);
  229. canvas.DrawText("RESULT", 0, 0, titlePaint);
  230. canvas.Translate(pagePadding * 2 + pageSize.Width, 0);
  231. canvas.DrawText("EXPECTED", 0, 0, titlePaint);
  232. canvas.Restore();
  233. // side visualization
  234. canvas.Translate(pagePadding, pagePadding * 2);
  235. DrawSide(left);
  236. canvas.Translate(pageSize.Width + pagePadding * 2, 0);
  237. DrawSide(right);
  238. pdf.EndPage();
  239. pdf.Close();
  240. void DrawSide(ICollection<PageDrawingCommand> commands)
  241. {
  242. canvas.Save();
  243. foreach (var pageDrawingCommand in commands)
  244. {
  245. DrawPage(pageDrawingCommand);
  246. canvas.Translate(0, pageSize.Height + pagePadding);
  247. }
  248. canvas.Restore();
  249. }
  250. void DrawPage(PageDrawingCommand command)
  251. {
  252. // available area
  253. using var availableAreaPaint = new SKPaint
  254. {
  255. Color = SKColor.Parse(PageColor)
  256. };
  257. canvas.DrawRect(0, 0, pageSize.Width, pageSize.Height, availableAreaPaint);
  258. // taken area
  259. using var takenAreaPaint = new SKPaint
  260. {
  261. Color = SKColor.Parse(TargetColor)
  262. };
  263. canvas.DrawRect(0, 0, command.RequiredArea.Width, command.RequiredArea.Height, takenAreaPaint);
  264. // draw children
  265. foreach (var child in command.Children)
  266. {
  267. canvas.Save();
  268. var color = colors[child.ChildId];
  269. using var childBorderPaint = new SKPaint
  270. {
  271. Color = SKColor.Parse(color),
  272. IsStroke = true,
  273. StrokeWidth = 2
  274. };
  275. using var childAreaPaint = new SKPaint
  276. {
  277. Color = SKColor.Parse(color).WithAlpha(64)
  278. };
  279. canvas.Translate(child.Position.X, child.Position.Y);
  280. canvas.DrawRect(0, 0, child.Size.Width, child.Size.Height, childAreaPaint);
  281. canvas.DrawRect(0, 0, child.Size.Width, child.Size.Height, childBorderPaint);
  282. canvas.Restore();
  283. }
  284. }
  285. // save
  286. GenerateExtensions.OpenFileUsingDefaultProgram(path);
  287. }
  288. }