Browse Source

Code refactoring

Marcin Ziąbek 2 years ago
parent
commit
d0bda45173

+ 0 - 16
Source/QuestPDF.LayoutTests/TestEngine/DrawCommand.cs

@@ -1,16 +0,0 @@
-using QuestPDF.Infrastructure;
-
-namespace QuestPDF.LayoutTests.TestEngine;
-
-internal class PageDrawingCommand
-{
-    public Size RequiredArea { get; set; }
-    public ICollection<ChildDrawingCommand> Children { get; set; }
-}
-
-internal class ChildDrawingCommand
-{
-    public string ChildId { get; set; }
-    public Position Position { get; set; }
-    public Size Size { get; set; }
-}

+ 40 - 236
Source/QuestPDF.LayoutTests/TestEngine/LayoutTest.cs

@@ -19,11 +19,11 @@ internal class LayoutBuilderDescriptor
 
 
 internal class DocumentLayoutBuilder
 internal class DocumentLayoutBuilder
 {
 {
-    public List<PageDrawingCommand> Commands { get; } = new(); 
+    public List<LayoutTestResult.PageLayoutSnapshot> Commands { get; } = new(); 
     
     
     public PageLayoutDescriptor Page()
     public PageLayoutDescriptor Page()
     {
     {
-        var page = new PageDrawingCommand();
+        var page = new LayoutTestResult.PageLayoutSnapshot();
         Commands.Add(page);
         Commands.Add(page);
         return new PageLayoutDescriptor(page);
         return new PageLayoutDescriptor(page);
     }
     }
@@ -31,9 +31,9 @@ internal class DocumentLayoutBuilder
 
 
 internal class PageLayoutDescriptor
 internal class PageLayoutDescriptor
 {
 {
-    private PageDrawingCommand Command { get; }
+    private LayoutTestResult.PageLayoutSnapshot Command { get; }
 
 
-    public PageLayoutDescriptor(PageDrawingCommand command)
+    public PageLayoutDescriptor(LayoutTestResult.PageLayoutSnapshot command)
     {
     {
         Command = command;
         Command = command;
     }
     }
@@ -48,18 +48,18 @@ internal class PageLayoutDescriptor
     {
     {
         var pageContent = new PageLayoutBuilder();
         var pageContent = new PageLayoutBuilder();
         content(pageContent);
         content(pageContent);
-        Command.Children = pageContent.Commands;
+        Command.MockPositions = pageContent.Commands;
         return this;
         return this;
     }
     }
 }
 }
 
 
 internal class PageLayoutBuilder
 internal class PageLayoutBuilder
 {
 {
-    public List<ChildDrawingCommand> Commands { get;} = new();
+    public List<LayoutTestResult.MockLayoutPosition> Commands { get;} = new();
     
     
-    public ChildLayoutDescriptor Child(string childId)
+    public ChildLayoutDescriptor Child(string mockId)
     {
     {
-        var child = new ChildDrawingCommand { ChildId = childId };
+        var child = new LayoutTestResult.MockLayoutPosition { MockId = mockId };
         Commands.Add(child);
         Commands.Add(child);
         return new ChildLayoutDescriptor(child);
         return new ChildLayoutDescriptor(child);
     }
     }
@@ -67,9 +67,9 @@ internal class PageLayoutBuilder
 
 
 internal class ChildLayoutDescriptor
 internal class ChildLayoutDescriptor
 {
 {
-    private ChildDrawingCommand Command { get; }
+    private LayoutTestResult.MockLayoutPosition Command { get; }
 
 
-    public ChildLayoutDescriptor(ChildDrawingCommand command)
+    public ChildLayoutDescriptor(LayoutTestResult.MockLayoutPosition command)
     {
     {
         Command = command;
         Command = command;
     }
     }
@@ -87,22 +87,13 @@ internal class ChildLayoutDescriptor
     }
     }
 }
 }
 
 
-internal class ExpectedLayoutChildPosition
-{
-    public string ElementId { get; set; }
-    public int PageNumber { get; set; }
-    public int DepthIndex { get; set; }
-    public Position Position { get; set; }
-    public Size Size { get; set; }
-}
-
 internal static class ElementExtensions
 internal static class ElementExtensions
 {
 {
     public static MockDescriptor Mock(this IContainer element, string id)
     public static MockDescriptor Mock(this IContainer element, string id)
     {
     {
         var mock = new ElementMock
         var mock = new ElementMock
         {
         {
-            Id = id
+            MockId = id
         };
         };
         
         
         element.Element(mock);
         element.Element(mock);
@@ -130,21 +121,13 @@ internal class MockDescriptor
 
 
 internal class LayoutTest
 internal class LayoutTest
 {
 {
-    private const string DocumentColor = Colors.Grey.Lighten1;
-    private const string PageColor = Colors.Grey.Lighten3;
-    private const string TargetColor = Colors.White;
-    private const string GridColor = Colors.Grey.Lighten1;
-    
-    public Size PageSize { get; set; }
-    public ICollection<PageDrawingCommand> ActualCommands { get; set; }
-    public ICollection<PageDrawingCommand> ExpectedCommands { get; set; }
-    
+    private LayoutTestResult TestResult { get; } = new LayoutTestResult();
+  
     public static LayoutTest HavingSpaceOfSize(float width, float height)
     public static LayoutTest HavingSpaceOfSize(float width, float height)
     {
     {
-        return new LayoutTest
-        {
-            PageSize = new Size(width, height)
-        };
+        var result = new LayoutTest();
+        result.TestResult.PageSize = new Size(width, height);
+        return result;
     }
     }
 
 
     public LayoutTest WithContent(Action<IContainer> handler)
     public LayoutTest WithContent(Action<IContainer> handler)
@@ -153,12 +136,12 @@ internal class LayoutTest
         var container = new Container();
         var container = new Container();
         container.Element(handler);
         container.Element(handler);
 
 
-        ActualCommands = GenerateResult(PageSize, container);
+        TestResult.GeneratedLayout = GenerateResult(TestResult.PageSize, container);
         
         
         return this;
         return this;
     }
     }
 
 
-    private static ICollection<PageDrawingCommand> GenerateResult(Size pageSize, Container container)
+    private static ICollection<LayoutTestResult.PageLayoutSnapshot> GenerateResult(Size pageSize, Container container)
     {
     {
         // inject dependencies
         // inject dependencies
         var pageContext = new PageContext();
         var pageContext = new PageContext();
@@ -215,13 +198,13 @@ internal class LayoutTest
         return mocks
         return mocks
             .SelectMany(x => x.DrawingCommands)
             .SelectMany(x => x.DrawingCommands)
             .GroupBy(x => x.PageNumber)
             .GroupBy(x => x.PageNumber)
-            .Select(x => new PageDrawingCommand
+            .Select(x => new LayoutTestResult.PageLayoutSnapshot
             {
             {
                 RequiredArea = pageSizes[x.Key - 1],
                 RequiredArea = pageSizes[x.Key - 1],
-                Children = x
-                    .Select(y => new ChildDrawingCommand
+                MockPositions = x
+                    .Select(y => new LayoutTestResult.MockLayoutPosition
                     {
                     {
-                        ChildId = y.ElementId,
+                        MockId = y.MockId,
                         Size = y.Size,
                         Size = y.Size,
                         Position = y.Position
                         Position = y.Position
                     })
                     })
@@ -240,28 +223,28 @@ internal class LayoutTest
         var builder = new DocumentLayoutBuilder();
         var builder = new DocumentLayoutBuilder();
         handler(builder);
         handler(builder);
 
 
-        ExpectedCommands = builder.Commands;
+        TestResult.ExpectedLayout = builder.Commands;
         return this;
         return this;
     }
     }
 
 
     public void CompareVisually()
     public void CompareVisually()
     {
     {
-        VisualizeExpectedResult(PageSize, ActualCommands, ExpectedCommands);
+        LayoutTestResultVisualization.Visualize(TestResult);
     }
     }
 
 
     public void Validate()
     public void Validate()
     {
     {
-        if (ActualCommands.Count != ExpectedCommands.Count)
-            throw new Exception($"Generated {ActualCommands.Count} but expected {ExpectedCommands.Count} pages.");
+        if (TestResult.GeneratedLayout.Count != TestResult.ExpectedLayout.Count)
+            throw new Exception($"Generated {TestResult.GeneratedLayout.Count} but expected {TestResult.ExpectedLayout.Count} pages.");
 
 
-        var numberOfPages = ActualCommands.Count;
+        var numberOfPages = TestResult.GeneratedLayout.Count;
         
         
         foreach (var i in Enumerable.Range(0, numberOfPages))
         foreach (var i in Enumerable.Range(0, numberOfPages))
         {
         {
             try
             try
             {
             {
-                var actual = ActualCommands.ElementAt(i);
-                var expected = ExpectedCommands.ElementAt(i);
+                var actual = TestResult.GeneratedLayout.ElementAt(i);
+                var expected = TestResult.ExpectedLayout.ElementAt(i);
 
 
                 if (Math.Abs(actual.RequiredArea.Width - expected.RequiredArea.Width) > Size.Epsilon)
                 if (Math.Abs(actual.RequiredArea.Width - expected.RequiredArea.Width) > Size.Epsilon)
                     throw new Exception($"Taken area width is equal to {actual.RequiredArea.Width} but expected {expected.RequiredArea.Width}");
                     throw new Exception($"Taken area width is equal to {actual.RequiredArea.Width} but expected {expected.RequiredArea.Width}");
@@ -269,28 +252,28 @@ internal class LayoutTest
                 if (Math.Abs(actual.RequiredArea.Height - expected.RequiredArea.Height) > Size.Epsilon)
                 if (Math.Abs(actual.RequiredArea.Height - expected.RequiredArea.Height) > Size.Epsilon)
                     throw new Exception($"Taken area height is equal to {actual.RequiredArea.Height} but expected {expected.RequiredArea.Height}");
                     throw new Exception($"Taken area height is equal to {actual.RequiredArea.Height} but expected {expected.RequiredArea.Height}");
                 
                 
-                if (actual.Children.Count != expected.Children.Count)
-                    throw new Exception($"Visible {actual.Children.Count} but expected {expected.Children.Count}");
+                if (actual.MockPositions.Count != expected.MockPositions.Count)
+                    throw new Exception($"Visible {actual.MockPositions.Count} but expected {expected.MockPositions.Count}");
 
 
-                foreach (var child in expected.Children)
+                foreach (var child in expected.MockPositions)
                 {
                 {
                     var matchingActualElements = actual
                     var matchingActualElements = actual
-                        .Children
-                        .Where(x => x.ChildId == child.ChildId)
+                        .MockPositions
+                        .Where(x => x.MockId == child.MockId)
                         .Where(x => Position.Equal(x.Position, child.Position))
                         .Where(x => Position.Equal(x.Position, child.Position))
                         .Where(x => Size.Equal(x.Size, child.Size))
                         .Where(x => Size.Equal(x.Size, child.Size))
                         .Count();
                         .Count();
 
 
                     if (matchingActualElements == 0)
                     if (matchingActualElements == 0)
-                        throw new Exception($"Cannot find actual drawing command for child {child.ChildId} on position {child.Position} and size {child.Size}");
+                        throw new Exception($"Cannot find actual drawing command for child {child.MockId} on position {child.Position} and size {child.Size}");
                     
                     
                     if (matchingActualElements > 1)
                     if (matchingActualElements > 1)
-                        throw new Exception($"Found multiple drawing commands for child {child.ChildId} on position {child.Position} and size {child.Size}");
+                        throw new Exception($"Found multiple drawing commands for child {child.MockId} on position {child.Position} and size {child.Size}");
                 }
                 }
                 
                 
                 // todo: add z-depth testing
                 // todo: add z-depth testing
-                var actualOverlaps = GetOverlappingItems(actual.Children);
-                var expectedOverlaps = GetOverlappingItems(expected.Children);
+                var actualOverlaps = GetOverlappingItems(actual.MockPositions);
+                var expectedOverlaps = GetOverlappingItems(expected.MockPositions);
                 
                 
                 foreach (var overlap in expectedOverlaps)
                 foreach (var overlap in expectedOverlaps)
                 {
                 {
@@ -300,7 +283,7 @@ internal class LayoutTest
                         throw new Exception($"Element {overlap.Item1} should be visible underneath element {overlap.Item2}");
                         throw new Exception($"Element {overlap.Item1} should be visible underneath element {overlap.Item2}");
                 }
                 }
                 
                 
-                IEnumerable<(string, string)> GetOverlappingItems(ICollection<ChildDrawingCommand> items)
+                IEnumerable<(string, string)> GetOverlappingItems(ICollection<LayoutTestResult.MockLayoutPosition> items)
                 {
                 {
                     for (var i = 0; i < items.Count; i++)
                     for (var i = 0; i < items.Count; i++)
                     {
                     {
@@ -317,7 +300,7 @@ internal class LayoutTest
                             if (intersection == null)
                             if (intersection == null)
                                 continue;
                                 continue;
 
 
-                            yield return (beforeChild.ChildId, afterChild.ChildId);
+                            yield return (beforeChild.MockId, afterChild.MockId);
                         }
                         }
                     }
                     }
                 }
                 }
@@ -328,183 +311,4 @@ internal class LayoutTest
             }
             }
         }
         }
     }
     }
-        
-    private static void VisualizeExpectedResult(Size pageSize, ICollection<PageDrawingCommand> left, ICollection<PageDrawingCommand> right)
-    {
-        var path = "test.pdf";
-        
-        if (File.Exists(path))
-            File.Delete(path);
-        
-        // default colors
-        var defaultColors = new string[]
-        {
-            Colors.Red.Medium,
-            Colors.Green.Medium,
-            Colors.Blue.Medium,
-            Colors.Pink.Medium,
-            Colors.Orange.Medium,
-            Colors.Lime.Medium,
-            Colors.Cyan.Medium,
-            Colors.Indigo.Medium
-        };
-        
-        // determine children colors
-        var children = Enumerable
-            .Concat(left, right)
-            .SelectMany(x => x.Children)
-            .Select(x => x.ChildId)
-            .Distinct()
-            .ToList();
-
-        var colors = Enumerable
-            .Range(0, children.Count)
-            .ToDictionary(i => children[i], i => defaultColors[i]);
-
-        // create new pdf document output
-        var matrixHeight = Math.Max(left.Count, right.Count);
-        
-        const int pagePadding = 25;
-        var imageInfo = new SKImageInfo((int)pageSize.Width * 2 + pagePadding * 4, (int)(pageSize.Height * matrixHeight + pagePadding * (matrixHeight + 2)));
-
-        const int outputScale = 2;
-        
-        using var pdf = SKDocument.CreatePdf(path);
-        using var canvas = pdf.BeginPage(imageInfo.Width * outputScale, imageInfo.Height * outputScale);
-        
-        canvas.Scale(outputScale, outputScale);
-        
-        // page background
-        canvas.Clear(SKColor.Parse(DocumentColor));
-        
-        // chain titles
-        
-        // available area
-        using var titlePaint = TextStyle.LibraryDefault.FontSize(16).Bold().ToPaint().Clone();
-        titlePaint.TextAlign = SKTextAlign.Center;
-
-        canvas.Save();
-        
-        canvas.Translate(pagePadding + pageSize.Width / 2f, pagePadding + titlePaint.TextSize / 2);
-        canvas.DrawText("RESULT", 0, 0, titlePaint);
-        
-        canvas.Translate(pagePadding * 2 + pageSize.Width, 0);
-        canvas.DrawText("EXPECTED", 0, 0, titlePaint);
-        
-        canvas.Restore();
-
-        // side visualization
-        canvas.Save();
-        
-        canvas.Translate(pagePadding, pagePadding * 2);
-        DrawSide(left);
-        
-        canvas.Translate(pageSize.Width + pagePadding * 2, 0);
-        DrawSide(right);
-        
-        canvas.Restore();
-        
-        
-        // draw page numbers
-        canvas.Save();
-        
-        canvas.Translate(pagePadding * 2 + pageSize.Width, pagePadding * 2 + titlePaint.TextSize);
-        
-        foreach (var i in Enumerable.Range(0, matrixHeight))
-        {
-            canvas.DrawText((i + 1).ToString(), 0, 0, titlePaint);
-            canvas.Translate(0, pagePadding + pageSize.Height);
-        }
-        
-        canvas.Restore();
-
-        pdf.EndPage();
-        pdf.Close();
-        
-        void DrawSide(ICollection<PageDrawingCommand> commands)
-        {
-            canvas.Save();
-            
-            foreach (var pageDrawingCommand in commands)
-            {
-                DrawPage(pageDrawingCommand);
-                canvas.Translate(0, pageSize.Height + pagePadding);
-            }
-            
-            canvas.Restore();
-        }
-
-        void DrawPageGrid(Size pageSize)
-        {
-            using var paint = new SKPaint
-            {
-                Color = SKColor.Parse(GridColor),
-                StrokeWidth = 1
-            };
-
-            const float GridSize = 10f;
-            
-            foreach (var i in Enumerable.Range(1, (int)Math.Floor(pageSize.Width / GridSize)))
-            {
-                canvas.DrawLine(new SKPoint(i * GridSize, 0), new SKPoint(i * GridSize, pageSize.Height), paint);
-            }
-            
-            foreach (var i in Enumerable.Range(1, (int)Math.Floor(pageSize.Height / GridSize)))
-            {
-                canvas.DrawLine(new SKPoint(0, i * GridSize), new SKPoint(pageSize.Width, i * GridSize), paint);
-            }
-        }
-
-        void DrawPage(PageDrawingCommand command)
-        {
-            // available area
-            using var availableAreaPaint = new SKPaint
-            {
-                Color = SKColor.Parse(PageColor)
-            };
-            
-            canvas.DrawRect(0, 0, pageSize.Width, pageSize.Height, availableAreaPaint);
-            
-            // taken area
-            using var takenAreaPaint = new SKPaint
-            {
-                Color = SKColor.Parse(TargetColor)
-            };
-            
-            canvas.DrawRect(0, 0, command.RequiredArea.Width, command.RequiredArea.Height, takenAreaPaint);
-        
-            DrawPageGrid(pageSize);
-            
-            // draw children
-            foreach (var child in command.Children)
-            {
-                canvas.Save();
-
-                const float strokeWidth = 3f;
-
-                var color = colors[child.ChildId];
-            
-                using var childBorderPaint = new SKPaint
-                {
-                    Color = SKColor.Parse(color),
-                    IsStroke = true,
-                    StrokeWidth = strokeWidth
-                };
-            
-                using var childAreaPaint = new SKPaint
-                {
-                    Color = SKColor.Parse(color).WithAlpha(128)
-                };
-            
-                canvas.Translate(child.Position.X, child.Position.Y);
-                canvas.DrawRect(0, 0, child.Size.Width, child.Size.Height, childAreaPaint);
-                canvas.DrawRect(strokeWidth / 2, strokeWidth / 2, child.Size.Width - strokeWidth, child.Size.Height - strokeWidth, childBorderPaint);
-            
-                canvas.Restore();
-            }
-        }
-        
-        // save
-        GenerateExtensions.OpenFileUsingDefaultProgram(path);
-    }
 }
 }

+ 193 - 0
Source/QuestPDF.LayoutTests/TestEngine/LayoutTestOutputVisualization.cs

@@ -0,0 +1,193 @@
+using QuestPDF.Drawing;
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+using SkiaSharp;
+
+namespace QuestPDF.LayoutTests.TestEngine;
+
+internal class LayoutTestResultVisualization
+{
+    private const string DocumentBackgroundColor = Colors.Grey.Lighten1;
+    private const string PageBackgroundColor = Colors.Grey.Lighten3;
+    private const string RequiredAreaBackgroundColor = Colors.White;
+    private const string GridLineColor = Colors.Grey.Lighten1;
+
+    private static readonly string[] DefaultElementColors =
+    {
+        Colors.Red.Medium,
+        Colors.Green.Medium,
+        Colors.Blue.Medium,
+        Colors.Pink.Medium,
+        Colors.Orange.Medium,
+        Colors.Lime.Medium,
+        Colors.Cyan.Medium,
+        Colors.Indigo.Medium
+    };
+    
+    public static void Visualize(LayoutTestResult result)
+    {
+        var path = "test.pdf";
+        
+        if (File.Exists(path))
+            File.Delete(path);
+        
+        // determine children colors
+        var mocks = Enumerable
+            .Concat(result.GeneratedLayout, result.ExpectedLayout)
+            .SelectMany(x => x.MockPositions)
+            .Select(x => x.MockId)
+            .Distinct()
+            .ToList();
+
+        var colors = Enumerable
+            .Range(0, mocks.Count)
+            .ToDictionary(i => mocks[i], i => DefaultElementColors[i]);
+
+        // create new pdf document output
+        var matrixHeight = Math.Max(result.GeneratedLayout.Count, result.ExpectedLayout.Count);
+        
+        const int pagePadding = 25;
+        var imageInfo = new SKImageInfo((int)result.PageSize.Width * 2 + pagePadding * 4, (int)(result.PageSize.Height * matrixHeight + pagePadding * (matrixHeight + 2)));
+
+        const int outputScale = 2;
+        
+        using var pdf = SKDocument.CreatePdf(path);
+        using var canvas = pdf.BeginPage(imageInfo.Width * outputScale, imageInfo.Height * outputScale);
+        
+        canvas.Scale(outputScale, outputScale);
+        
+        // page background
+        canvas.Clear(SKColor.Parse(DocumentBackgroundColor));
+        
+        // chain titles
+        
+        // available area
+        using var titlePaint = TextStyle.LibraryDefault.FontSize(16).Bold().ToPaint().Clone();
+        titlePaint.TextAlign = SKTextAlign.Center;
+
+        canvas.Save();
+        
+        canvas.Translate(pagePadding + result.PageSize.Width / 2f, pagePadding + titlePaint.TextSize / 2);
+        canvas.DrawText("RESULT", 0, 0, titlePaint);
+        
+        canvas.Translate(pagePadding * 2 + result.PageSize.Width, 0);
+        canvas.DrawText("EXPECTED", 0, 0, titlePaint);
+        
+        canvas.Restore();
+
+        // side visualization
+        canvas.Save();
+        
+        canvas.Translate(pagePadding, pagePadding * 2);
+        DrawSide(result.GeneratedLayout);
+        
+        canvas.Translate(result.PageSize.Width + pagePadding * 2, 0);
+        DrawSide(result.ExpectedLayout);
+        
+        canvas.Restore();
+        
+        
+        // draw page numbers
+        canvas.Save();
+        
+        canvas.Translate(pagePadding * 2 + result.PageSize.Width, pagePadding * 2 + titlePaint.TextSize);
+        
+        foreach (var i in Enumerable.Range(0, matrixHeight))
+        {
+            canvas.DrawText((i + 1).ToString(), 0, 0, titlePaint);
+            canvas.Translate(0, pagePadding + result.PageSize.Height);
+        }
+        
+        canvas.Restore();
+
+        pdf.EndPage();
+        pdf.Close();
+        
+        void DrawSide(ICollection<LayoutTestResult.PageLayoutSnapshot> commands)
+        {
+            canvas.Save();
+            
+            foreach (var pageDrawingCommand in commands)
+            {
+                DrawPage(pageDrawingCommand);
+                canvas.Translate(0, result.PageSize.Height + pagePadding);
+            }
+            
+            canvas.Restore();
+        }
+
+        void DrawPageGrid(Size pageSize)
+        {
+            using var paint = new SKPaint
+            {
+                Color = SKColor.Parse(GridLineColor),
+                StrokeWidth = 1
+            };
+
+            const float GridSize = 10f;
+            
+            foreach (var i in Enumerable.Range(1, (int)Math.Floor(pageSize.Width / GridSize)))
+            {
+                canvas.DrawLine(new SKPoint(i * GridSize, 0), new SKPoint(i * GridSize, pageSize.Height), paint);
+            }
+            
+            foreach (var i in Enumerable.Range(1, (int)Math.Floor(pageSize.Height / GridSize)))
+            {
+                canvas.DrawLine(new SKPoint(0, i * GridSize), new SKPoint(pageSize.Width, i * GridSize), paint);
+            }
+        }
+
+        void DrawPage(LayoutTestResult.PageLayoutSnapshot command)
+        {
+            // available area
+            using var availableAreaPaint = new SKPaint
+            {
+                Color = SKColor.Parse(PageBackgroundColor)
+            };
+            
+            canvas.DrawRect(0, 0, result.PageSize.Width, result.PageSize.Height, availableAreaPaint);
+            
+            // taken area
+            using var takenAreaPaint = new SKPaint
+            {
+                Color = SKColor.Parse(RequiredAreaBackgroundColor)
+            };
+            
+            canvas.DrawRect(0, 0, command.RequiredArea.Width, command.RequiredArea.Height, takenAreaPaint);
+        
+            DrawPageGrid(result.PageSize);
+            
+            // draw children
+            foreach (var child in command.MockPositions)
+            {
+                canvas.Save();
+
+                const float strokeWidth = 3f;
+
+                var color = colors[child.MockId];
+            
+                using var childBorderPaint = new SKPaint
+                {
+                    Color = SKColor.Parse(color),
+                    IsStroke = true,
+                    StrokeWidth = strokeWidth
+                };
+            
+                using var childAreaPaint = new SKPaint
+                {
+                    Color = SKColor.Parse(color).WithAlpha(128)
+                };
+            
+                canvas.Translate(child.Position.X, child.Position.Y);
+                canvas.DrawRect(0, 0, child.Size.Width, child.Size.Height, childAreaPaint);
+                canvas.DrawRect(strokeWidth / 2, strokeWidth / 2, child.Size.Width - strokeWidth, child.Size.Height - strokeWidth, childBorderPaint);
+            
+                canvas.Restore();
+            }
+        }
+        
+        // save
+        GenerateExtensions.OpenFileUsingDefaultProgram(path);
+    }
+}

+ 24 - 0
Source/QuestPDF.LayoutTests/TestEngine/LayoutTestResult.cs

@@ -0,0 +1,24 @@
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.LayoutTests.TestEngine;
+
+internal class LayoutTestResult
+{
+    public Size PageSize { get; set; }
+    
+    public ICollection<PageLayoutSnapshot> GeneratedLayout { get; set; }
+    public ICollection<PageLayoutSnapshot> ExpectedLayout { get; set; }
+
+    public class PageLayoutSnapshot
+    {
+        public Size RequiredArea { get; set; }
+        public ICollection<MockLayoutPosition> MockPositions { get; set; }
+    }
+
+    public class MockLayoutPosition
+    {
+        public string MockId { get; set; }
+        public Position Position { get; set; }
+        public Size Size { get; set; }
+    }
+}

+ 3 - 7
Source/QuestPDF.LayoutTests/TestEngine/MockChild.cs

@@ -1,12 +1,11 @@
 using QuestPDF.Drawing;
 using QuestPDF.Drawing;
-using QuestPDF.Helpers;
 using QuestPDF.Infrastructure;
 using QuestPDF.Infrastructure;
 
 
 namespace QuestPDF.LayoutTests.TestEngine;
 namespace QuestPDF.LayoutTests.TestEngine;
 
 
 internal class MockDrawingCommand
 internal class MockDrawingCommand
 {
 {
-    public string ElementId { get; set; }
+    public string MockId { get; set; }
     public int PageNumber { get; set; }
     public int PageNumber { get; set; }
     public Position Position { get; set; }
     public Position Position { get; set; }
     public Size Size { get; set; }
     public Size Size { get; set; }
@@ -14,8 +13,7 @@ internal class MockDrawingCommand
 
 
 internal class ElementMock : Element
 internal class ElementMock : Element
 {
 {
-    public string Id { get; set; }
-    public string Color { get; set; } = Placeholders.Color();
+    public string MockId { get; set; }
     
     
     public float TotalWidth { get; set; }
     public float TotalWidth { get; set; }
     public float TotalHeight { get; set; }
     public float TotalHeight { get; set; }
@@ -48,8 +46,6 @@ internal class ElementMock : Element
         var height = Math.Min(TotalHeight - HeightOffset, availableSpace.Height);
         var height = Math.Min(TotalHeight - HeightOffset, availableSpace.Height);
         var size = new Size(TotalWidth, height);
         var size = new Size(TotalWidth, height);
         
         
-        Canvas.DrawRectangle(Position.Zero, size, Color);
-
         HeightOffset += height;
         HeightOffset += height;
         
         
         if (Canvas is not SkiaCanvasBase canvasBase)
         if (Canvas is not SkiaCanvasBase canvasBase)
@@ -59,7 +55,7 @@ internal class ElementMock : Element
         
         
         DrawingCommands.Add(new MockDrawingCommand
         DrawingCommands.Add(new MockDrawingCommand
         {
         {
-            ElementId = Id,
+            MockId = MockId,
             PageNumber = PageContext.CurrentPage,
             PageNumber = PageContext.CurrentPage,
             Position = new Position(matrix.TransX / matrix.ScaleX, matrix.TransY / matrix.ScaleY),
             Position = new Position(matrix.TransX / matrix.ScaleX, matrix.TransY / matrix.ScaleY),
             Size = size
             Size = size