浏览代码

Layout testing: added support for z-depth

Marcin Ziąbek 2 年之前
父节点
当前提交
f6ddc038e4

+ 46 - 0
Source/QuestPDF.LayoutTests/TestEngine/BoundingBox.cs

@@ -0,0 +1,46 @@
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.LayoutTests.TestEngine;
+
+internal class BoundingBox
+{
+    public double MinX { get; init; }
+    public double MinY { get; init; }
+    public double MaxX { get; init; }
+    public double MaxY { get; init; }
+    
+    public double Width => MaxX - MinX;
+    public double Height => MaxY - MinY;
+
+    public override string ToString() => $"BBox(Min: {MinX:N0}x{MinY:N0}, Max: {MaxX:N0}x{MaxY:N0}, W: {Width:N0}, H: {Height:N0})";
+
+    public static BoundingBox From(Position position, Size size)
+    {
+        return new BoundingBox
+        {
+            MinX = position.X,
+            MinY = position.Y,
+            MaxX = position.X + size.Width,
+            MaxY = position.Y + size.Height
+        };
+    }
+}
+
+internal static class BoundingBoxExtensions
+{
+    public static BoundingBox? Intersection(BoundingBox first, BoundingBox second)
+    {
+        var common = new BoundingBox
+        {
+            MinX = Math.Max(first.MinX, second.MinX),
+            MinY = Math.Max(first.MinY, second.MinY),
+            MaxX = Math.Min(first.MaxX, second.MaxX),
+            MaxY = Math.Min(first.MaxY, second.MaxY),
+        };
+
+        if (common.Width < 0 || common.Height < 0)
+            return null;
+
+        return common;
+    }
+}

+ 101 - 2
Source/QuestPDF.LayoutTests/TestEngine/LayoutTest.cs

@@ -1,3 +1,4 @@
+using System.Collections;
 using QuestPDF.Drawing;
 using QuestPDF.Drawing.Proxy;
 using QuestPDF.Elements;
@@ -229,7 +230,87 @@ internal class LayoutTest
     {
         VisualizeExpectedResult(PageSize, ActualCommands, ExpectedCommands);
     }
-    
+
+    public void Validate()
+    {
+        if (ActualCommands.Count != ExpectedCommands.Count)
+            throw new Exception($"Generated {ActualCommands.Count} but expected {ExpectedCommands.Count} pages.");
+
+        var numberOfPages = ActualCommands.Count;
+        
+        foreach (var i in Enumerable.Range(0, numberOfPages))
+        {
+            try
+            {
+                var actual = ActualCommands.ElementAt(i);
+                var expected = ExpectedCommands.ElementAt(i);
+
+                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}");
+                
+                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}");
+                
+                if (actual.Children.Count != expected.Children.Count)
+                    throw new Exception($"Visible {actual.Children.Count} but expected {expected.Children.Count}");
+
+                foreach (var child in expected.Children)
+                {
+                    var matchingActualElements = actual
+                        .Children
+                        .Where(x => x.ChildId == child.ChildId)
+                        .Where(x => Position.Equal(x.Position, child.Position))
+                        .Where(x => Size.Equal(x.Size, child.Size))
+                        .Count();
+
+                    if (matchingActualElements == 0)
+                        throw new Exception($"Cannot find actual drawing command for child {child.ChildId} on position {child.Position} and size {child.Size}");
+                    
+                    if (matchingActualElements > 1)
+                        throw new Exception($"Found multiple drawing commands for child {child.ChildId} on position {child.Position} and size {child.Size}");
+                }
+                
+                // todo: add z-depth testing
+                var actualOverlaps = GetOverlappingItems(actual.Children);
+                var expectedOverlaps = GetOverlappingItems(expected.Children);
+                
+                foreach (var overlap in expectedOverlaps)
+                {
+                    var matchingActualElements = actualOverlaps.Count(x => x.Item1 == overlap.Item1 && x.Item2 == overlap.Item2);
+
+                    if (matchingActualElements != 1)
+                        throw new Exception($"Element {overlap.Item1} should be visible underneath element {overlap.Item2}");
+                }
+                
+                IEnumerable<(string, string)> GetOverlappingItems(ICollection<ChildDrawingCommand> items)
+                {
+                    for (var i = 0; i < items.Count; i++)
+                    {
+                        for (var j = i; j < items.Count; j++)
+                        {
+                            var beforeChild = items.ElementAt(i);
+                            var afterChild = items.ElementAt(j);
+
+                            var beforeBoundingBox = BoundingBox.From(beforeChild.Position, beforeChild.Size);
+                            var afterBoundingBox = BoundingBox.From(afterChild.Position, afterChild.Size);
+
+                            var intersection = BoundingBoxExtensions.Intersection(beforeBoundingBox, afterBoundingBox);
+                            
+                            if (intersection == null)
+                                continue;
+
+                            yield return (beforeChild.ChildId, afterChild.ChildId);
+                        }
+                    }
+                }
+            }
+            catch (Exception e)
+            {
+                throw new Exception($"Error on page {i + 1}: {e.Message}");
+            }
+        }
+    }
+        
     private static void VisualizeExpectedResult(Size pageSize, ICollection<PageDrawingCommand> left, ICollection<PageDrawingCommand> right)
     {
         var path = "test.pdf";
@@ -291,11 +372,29 @@ internal class LayoutTest
         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();
@@ -347,7 +446,7 @@ internal class LayoutTest
             
                 using var childAreaPaint = new SKPaint
                 {
-                    Color = SKColor.Parse(color).WithAlpha(64)
+                    Color = SKColor.Parse(color).WithAlpha(128)
                 };
             
                 canvas.Translate(child.Position.X, child.Position.Y);

+ 0 - 21
Source/QuestPDF.LayoutTests/TestEngine/MockChildren.cs

@@ -1,21 +0,0 @@
-using QuestPDF.Helpers;
-
-namespace QuestPDF.LayoutTests.TestEngine;
-
-internal static class MockChildren
-{
-    private static MockChild Create(string id, string color, float width, float height)
-    {
-        return new MockChild
-        {
-            Id = id,
-            Color = color,
-            TotalWidth = width,
-            TotalHeight = height
-        };
-    }
-
-    public static MockChild Red(float width, float height) => Create("red", Colors.Red.Medium, width, height);
-    public static MockChild Green(float width, float height) => Create("green", Colors.Green.Medium, width, height);
-    public static MockChild Blue(float width, float height) => Create("blue", Colors.Blue.Medium, width, height);
-}

+ 33 - 2
Source/QuestPDF.LayoutTests/UnitTest1.cs

@@ -14,6 +14,8 @@ public class Tests
     [Test]
     public void Test1()
     {
+        return;
+        
         LayoutTest
             .HavingSpaceOfSize(200, 400)
             .WithContent(content =>
@@ -53,8 +55,8 @@ public class Tests
                         page.Child("b").Position(250, 150).Size(50, 150);
                         page.Child("c").Position(300, 200).Size(100, 50);
                     });
-            })
-            .CompareVisually();
+            });
+            //.CompareVisually();
     }
     
     [Test]
@@ -91,6 +93,35 @@ public class Tests
                         page.Child("b").Position(0, 0).Size(125, 75);
                     });
             })
+            .Validate();
+    }
+    
+    [Test]
+    public void Test3()
+    {
+        LayoutTest
+            .HavingSpaceOfSize(200, 200)
+            .WithContent(content =>
+            {
+                content.Layers(layers =>
+                {
+                    layers.Layer().Mock("a", 100, 150);
+                    layers.PrimaryLayer().Mock("b", 150, 100);
+                });
+            })
+            .ExpectedDrawResult(document =>
+            {
+                document
+                    .Page()
+                    .TakenAreaSize(150, 100)
+                    .Content(page =>
+                    {
+                        page.Child("b").Position(0, 0).Size(150, 100);
+                        page.Child("a").Position(0, 0).Size(100, 150);
+                        
+                    });
+            })
             .CompareVisually();
+           //.Validate();
     }
 }

+ 14 - 1
Source/QuestPDF/Infrastructure/Position.cs

@@ -1,4 +1,6 @@
-namespace QuestPDF.Infrastructure
+using System;
+
+namespace QuestPDF.Infrastructure
 {
     internal readonly struct Position
     {
@@ -18,6 +20,17 @@
             return new Position(-X, -Y);
         }
         
+        public static bool Equal(Position first, Position second)
+        {
+            if (Math.Abs(first.X - second.X) > Size.Epsilon)
+                return false;
+            
+            if (Math.Abs(first.Y - second.Y) > Size.Epsilon)
+                return false;
+
+            return true;
+        }
+        
         public override string ToString() => $"(Left: {X:N3}, Top: {Y:N3})";
     }
 }

+ 14 - 1
Source/QuestPDF/Infrastructure/Size.cs

@@ -1,4 +1,6 @@
-namespace QuestPDF.Infrastructure
+using System;
+
+namespace QuestPDF.Infrastructure
 {
     public readonly struct Size
     {
@@ -17,6 +19,17 @@
             Height = height;
         }
         
+        internal static bool Equal(Size first, Size second)
+        {
+            if (Math.Abs(first.Width - second.Width) > Size.Epsilon)
+                return false;
+            
+            if (Math.Abs(first.Height - second.Height) > Size.Epsilon)
+                return false;
+
+            return true;
+        }
+        
         public override string ToString() => $"(Width: {Width:N3}, Height: {Height:N3})";
     }
 }