Browse Source

Merge branch 'main' into richtext

Marcin Ziąbek 4 years ago
parent
commit
391c5a6092
56 changed files with 1282 additions and 319 deletions
  1. 3 3
      QuestPDF.Examples/ElementExamples.cs
  2. 2 2
      QuestPDF.Examples/QuestPDF.Examples.csproj
  3. 2 3
      QuestPDF.ReportSample/QuestPDF.ReportSample.csproj
  4. 1 5
      QuestPDF.UnitTests/AlignmentTests.cs
  5. 0 6
      QuestPDF.UnitTests/AspectRatioTests.cs
  6. 2 2
      QuestPDF.UnitTests/BackgroundTests.cs
  7. 2 2
      QuestPDF.UnitTests/BorderTests.cs
  8. 56 0
      QuestPDF.UnitTests/BoxTests.cs
  9. 0 6
      QuestPDF.UnitTests/ConstrainedTests.cs
  10. 0 10
      QuestPDF.UnitTests/DebugTests.cs
  11. 3 0
      QuestPDF.UnitTests/EnsureSpaceTests.cs
  12. 3 6
      QuestPDF.UnitTests/ExtendTests.cs
  13. 15 0
      QuestPDF.UnitTests/ExternalLinkTests.cs
  14. 27 27
      QuestPDF.UnitTests/GridTests.cs
  15. 0 30
      QuestPDF.UnitTests/Helpers.cs
  16. 0 6
      QuestPDF.UnitTests/ImageTests.cs
  17. 15 0
      QuestPDF.UnitTests/InternalLinkTests.cs
  18. 15 0
      QuestPDF.UnitTests/InternalLocationTests.cs
  19. 131 0
      QuestPDF.UnitTests/LayersTests.cs
  20. 0 6
      QuestPDF.UnitTests/PaddingTests.cs
  21. 0 10
      QuestPDF.UnitTests/PageNumberTests.cs
  22. 3 4
      QuestPDF.UnitTests/QuestPDF.UnitTests.csproj
  23. 31 0
      QuestPDF.UnitTests/RotateTests.cs
  24. 154 3
      QuestPDF.UnitTests/RowTests.cs
  25. 152 0
      QuestPDF.UnitTests/ScaleTests.cs
  26. 3 0
      QuestPDF.UnitTests/ShowEntireTests.cs
  27. 2 29
      QuestPDF.UnitTests/ShowOnceTest.cs
  28. 162 0
      QuestPDF.UnitTests/SimpleRotateTests.cs
  29. 79 3
      QuestPDF.UnitTests/StackTests.cs
  30. 9 25
      QuestPDF.UnitTests/TestEngine/MockCanvas.cs
  31. 25 0
      QuestPDF.UnitTests/TestEngine/OperationRecordingCanvas.cs
  32. 2 2
      QuestPDF.UnitTests/TestEngine/Operations/CanvasDrawImageOperation.cs
  33. 2 2
      QuestPDF.UnitTests/TestEngine/Operations/CanvasDrawRectangleOperation.cs
  34. 2 2
      QuestPDF.UnitTests/TestEngine/Operations/CanvasDrawTextOperation.cs
  35. 12 0
      QuestPDF.UnitTests/TestEngine/Operations/CanvasRotateOperation.cs
  36. 14 0
      QuestPDF.UnitTests/TestEngine/Operations/CanvasScaleOperation.cs
  37. 2 2
      QuestPDF.UnitTests/TestEngine/Operations/CanvasTranslateOperation.cs
  38. 2 2
      QuestPDF.UnitTests/TestEngine/Operations/ChildDrawOperation.cs
  39. 2 2
      QuestPDF.UnitTests/TestEngine/Operations/ChildMeasureOperation.cs
  40. 2 2
      QuestPDF.UnitTests/TestEngine/Operations/ElementMeasureOperation.cs
  41. 68 0
      QuestPDF.UnitTests/TestEngine/SimpleContainerTests.cs
  42. 0 31
      QuestPDF.UnitTests/TestEngine/SingleChildTests.cs
  43. 76 33
      QuestPDF.UnitTests/TestEngine/TestPlan.cs
  44. 22 0
      QuestPDF.UnitTests/TestsBase.cs
  45. 0 10
      QuestPDF.UnitTests/TextTests.cs
  46. 32 0
      QuestPDF.UnitTests/TranslateTests.cs
  47. 100 0
      QuestPDF.UnitTests/UnconstrainedTests.cs
  48. 10 0
      QuestPDF/Drawing/FreeCanvas.cs
  49. 10 0
      QuestPDF/Drawing/SkiaCanvasBase.cs
  50. 1 1
      QuestPDF/Elements/Grid.cs
  51. 2 9
      QuestPDF/Elements/Rotate.cs
  52. 8 13
      QuestPDF/Elements/Scale.cs
  53. 9 13
      QuestPDF/Elements/SimpleRotate.cs
  54. 1 1
      QuestPDF/Elements/Stack.cs
  55. 3 6
      QuestPDF/Elements/Translate.cs
  56. 3 0
      QuestPDF/Infrastructure/ICanvas.cs

+ 3 - 3
QuestPDF.Examples/ElementExamples.cs

@@ -581,7 +581,7 @@ namespace QuestPDF.Examples
         {
             RenderingTest
                 .Create()
-                .PageSize(350, 350)
+                .PageSize(650, 450)
                 .FileName()
                 .Render(container =>
                 {
@@ -595,8 +595,8 @@ namespace QuestPDF.Examples
                             foreach (var turns in Enumerable.Range(0, 4))
                             {
                                 grid.Item()
-                                    .Width(150)
-                                    .Height(150)
+                                    .Width(300)
+                                    .Height(200)
                                     .Background(Colors.Grey.Lighten2)
                                     .Padding(10)
                                     .Element(element =>

+ 2 - 2
QuestPDF.Examples/QuestPDF.Examples.csproj

@@ -6,8 +6,8 @@
     </PropertyGroup>
 
     <ItemGroup>
-        <PackageReference Include="nunit" Version="3.12.0" />
-        <PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
+        <PackageReference Include="nunit" Version="3.13.2" />
+        <PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
         <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
         <PackageReference Include="SkiaSharp" Version="2.80.3" />
     </ItemGroup>

+ 2 - 3
QuestPDF.ReportSample/QuestPDF.ReportSample.csproj

@@ -8,9 +8,8 @@
     </PropertyGroup>
 
     <ItemGroup>
-        <PackageReference Include="DeepCloner" Version="0.10.2" />
-        <PackageReference Include="nunit" Version="3.12.0" />
-        <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
+        <PackageReference Include="nunit" Version="3.13.2" />
+        <PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
         <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
         <PackageReference Include="SkiaSharp" Version="2.80.3" />
     </ItemGroup>

+ 1 - 5
QuestPDF.UnitTests/AlignmentTests.cs

@@ -10,11 +10,7 @@ namespace QuestPDF.UnitTests
     public class AlignmentTests
     {
         [Test]
-        public void Measure_ShouldHandleNullChild() => new Alignment().MeasureWithoutChild();
-        
-        [Test]
-        public void Draw_ShouldHandleNullChild() => new Alignment().DrawWithoutChild();
-
+        public void Measure() => SimpleContainerTests.Measure<Alignment>();
         [Test]
         public void Draw_HorizontalCenter_VerticalCenter()
         {

+ 0 - 6
QuestPDF.UnitTests/AspectRatioTests.cs

@@ -9,12 +9,6 @@ namespace QuestPDF.UnitTests
     [TestFixture]
     public class AspectRatioTests
     {
-        [Test]
-        public void Measure_ShouldHandleNullChild() => new AspectRatio().MeasureWithoutChild();
-        
-        [Test]
-        public void Draw_ShouldHandleNullChild() => new AspectRatio().DrawWithoutChild();
-        
         [Test]
         public void Measure_FitWidth_EnoughSpace_FullRender()
         {

+ 2 - 2
QuestPDF.UnitTests/BackgroundTests.cs

@@ -10,8 +10,8 @@ namespace QuestPDF.UnitTests
     public class BackgroundTests
     {
         [Test]
-        public void Measure_ShouldHandleNullChild() => new Background().MeasureWithoutChild();
-
+        public void Measure() => SimpleContainerTests.Measure<Background>();
+        
         [Test]
         public void Draw_ShouldHandleNullChild()
         {

+ 2 - 2
QuestPDF.UnitTests/BorderTests.cs

@@ -11,8 +11,8 @@ namespace QuestPDF.UnitTests
     public class BorderTests
     {
         [Test]
-        public void Measure_ShouldHandleNullChild() => new Border().MeasureWithoutChild();
-
+        public void Measure() => SimpleContainerTests.Measure<Border>();
+        
         [Test]
         public void ComponentShouldNotAffectLayout()
         {

+ 56 - 0
QuestPDF.UnitTests/BoxTests.cs

@@ -0,0 +1,56 @@
+using NUnit.Framework;
+using QuestPDF.Drawing.SpacePlan;
+using QuestPDF.Elements;
+using QuestPDF.Infrastructure;
+using QuestPDF.UnitTests.TestEngine;
+
+namespace QuestPDF.UnitTests
+{
+    [TestFixture]
+    public class BoxTests
+    {
+        [Test]
+        public void Measure() => SimpleContainerTests.Measure<Box>();
+        
+        [Test]
+        public void Draw_Wrap()
+        {
+            TestPlan
+                .For(x => new Box
+                {
+                    Child = x.CreateChild()
+                })
+                .DrawElement(new Size(400, 300))
+                .ExpectChildMeasure(expectedInput: new Size(400, 300), returns: new Wrap())
+                .CheckDrawResult();
+        }
+        
+        [Test]
+        public void Measure_PartialRender()
+        {
+            TestPlan
+                .For(x => new Box
+                {
+                    Child = x.CreateChild()
+                })
+                .MeasureElement(new Size(400, 300))
+                .ExpectChildMeasure(expectedInput: new Size(400, 300), returns: new PartialRender(200, 100))
+                .ExpectChildDraw(new Size(200, 100))
+                .CheckDrawResult();
+        }
+        
+        [Test]
+        public void Measure_FullRender()
+        {
+            TestPlan
+                .For(x => new Box
+                {
+                    Child = x.CreateChild()
+                })
+                .MeasureElement(new Size(500, 400))
+                .ExpectChildMeasure(expectedInput: new Size(500, 400), returns: new FullRender(300, 200))
+                .ExpectChildDraw(new Size(300, 200))
+                .CheckDrawResult();
+        }
+    }
+}

+ 0 - 6
QuestPDF.UnitTests/ConstrainedTests.cs

@@ -9,12 +9,6 @@ namespace QuestPDF.UnitTests
     [TestFixture]
     public class ConstrainedTests
     {
-        [Test]
-        public void Measure_ShouldHandleNullChild() => new Constrained().MeasureWithoutChild();
-        
-        [Test]
-        public void Draw_ShouldHandleNullChild() => new Constrained().DrawWithoutChild();
-
         [Test]
         public void Measure_MinHeight_ExpectWrap()
         {

+ 0 - 10
QuestPDF.UnitTests/DebugTests.cs

@@ -1,10 +0,0 @@
-using NUnit.Framework;
-
-namespace QuestPDF.UnitTests
-{
-    [TestFixture]
-    public class DebugTests
-    {
-        
-    }
-}

+ 3 - 0
QuestPDF.UnitTests/EnsureSpaceTests.cs

@@ -78,5 +78,8 @@ namespace QuestPDF.UnitTests
                 .ExpectChildMeasure(new Size(400, 300), new FullRender(300, 250))
                 .CheckMeasureResult(new FullRender(300, 250));
         }
+        
+        [Test]
+        public void Draw() => SimpleContainerTests.Draw<EnsureSpace>();
     }
 }

+ 3 - 6
QuestPDF.UnitTests/ExtendTests.cs

@@ -9,12 +9,6 @@ namespace QuestPDF.UnitTests
     [TestFixture]
     public class ExtendTests
     {
-        [Test]
-        public void Measure_ShouldHandleNullChild() => new Extend().MeasureWithoutChild();
-        
-        [Test]
-        public void Draw_ShouldHandleNullChild() => new Extend().DrawWithoutChild();
-        
         [Test]
         public void Measure_ReturnsWrap_WhenChildReturnsWrap()
         {
@@ -87,5 +81,8 @@ namespace QuestPDF.UnitTests
                 .ExpectChildMeasure(new Size(400, 200), new FullRender(100, 100))
                 .CheckMeasureResult(new FullRender(100, 200));
         }
+
+        [Test]
+        public void Draw() => SimpleContainerTests.Draw<Extend>();
     }
 }

+ 15 - 0
QuestPDF.UnitTests/ExternalLinkTests.cs

@@ -0,0 +1,15 @@
+using NUnit.Framework;
+using QuestPDF.Elements;
+using QuestPDF.UnitTests.TestEngine;
+
+namespace QuestPDF.UnitTests
+{
+    [TestFixture]
+    public class ExternalLinkTests
+    {
+        [Test]
+        public void Measure() => SimpleContainerTests.Measure<ExternalLink>();
+        
+        // TODO: consider tests for the Draw method
+    }
+}

+ 27 - 27
QuestPDF.UnitTests/GridTests.cs

@@ -39,30 +39,30 @@ namespace QuestPDF.UnitTests
             // assert
             var expected = new Container();
             
-            expected.Container().Stack(stack =>
+            expected.Stack(stack =>
             {
                 stack.Item().Row(row =>
                 {
-                    row.RelativeColumn(6).Container().Element(childA);
-                    row.RelativeColumn(4).Container().Element(childB);
+                    row.RelativeColumn(6).Element(childA);
+                    row.RelativeColumn(4).Element(childB);
                     row.RelativeColumn(2);
                 });
                 
                 stack.Item().Row(row =>
                 {
-                    row.RelativeColumn(4).Container().Element(childC);
-                    row.RelativeColumn(2).Container().Element(childD);
+                    row.RelativeColumn(4).Element(childC);
+                    row.RelativeColumn(2).Element(childD);
                     row.RelativeColumn(6);
                 });
                 
                 stack.Item().Row(row =>
                 {
-                    row.RelativeColumn(8).Container().Element(childE);
+                    row.RelativeColumn(8).Element(childE);
                     row.RelativeColumn(4);
                 });
             });
             
-            structure.Should().BeEquivalentTo(expected, o => o.WithTracing().WithAutoConversion().WithStrictOrdering().IncludingAllRuntimeProperties());
+            TestPlan.CompareOperations(structure, expected);
         }
         
         [Test]
@@ -93,33 +93,33 @@ namespace QuestPDF.UnitTests
             // assert
             var expected = new Container();
             
-            expected.Container().Stack(stack =>
+            expected.Stack(stack =>
             {
                 stack.Item().Row(row =>
                 {
                     row.RelativeColumn(1);
-                    row.RelativeColumn(6).Container().Element(childA);
-                    row.RelativeColumn(4).Container().Element(childB);
+                    row.RelativeColumn(6).Element(childA);
+                    row.RelativeColumn(4).Element(childB);
                     row.RelativeColumn(1);
                 });
                 
                 stack.Item().Row(row =>
                 {
                     row.RelativeColumn(3);
-                    row.RelativeColumn(4).Container().Element(childC);
-                    row.RelativeColumn(2).Container().Element(childD);
+                    row.RelativeColumn(4).Element(childC);
+                    row.RelativeColumn(2).Element(childD);
                     row.RelativeColumn(3);
                 });
                 
                 stack.Item().Row(row =>
                 {
                     row.RelativeColumn(2);
-                    row.RelativeColumn(8).Container().Element(childE);
+                    row.RelativeColumn(8).Element(childE);
                     row.RelativeColumn(2);
                 });
             });
 
-            structure.Should().BeEquivalentTo(expected, o => o.WithTracing().WithAutoConversion().WithStrictOrdering().IncludingAllRuntimeProperties());
+            TestPlan.CompareOperations(structure, expected);
         }
         
         [Test]
@@ -150,30 +150,30 @@ namespace QuestPDF.UnitTests
             // assert
             var expected = new Container();
             
-            expected.Container().Stack(stack =>
+            expected.Stack(stack =>
             {
                 stack.Item().Row(row =>
                 {
                     row.RelativeColumn(2);
-                    row.RelativeColumn(6).Container().Element(childA);
-                    row.RelativeColumn(4).Container().Element(childB);
+                    row.RelativeColumn(6).Element(childA);
+                    row.RelativeColumn(4).Element(childB);
                 });
                 
                 stack.Item().Row(row =>
                 {
                     row.RelativeColumn(6);
-                    row.RelativeColumn(4).Container().Element(childC);
-                    row.RelativeColumn(2).Container().Element(childD);
+                    row.RelativeColumn(4).Element(childC);
+                    row.RelativeColumn(2).Element(childD);
                 });
                 
                 stack.Item().Row(row =>
                 {
                     row.RelativeColumn(4);
-                    row.RelativeColumn(8).Container().Element(childE);
+                    row.RelativeColumn(8).Element(childE);
                 });
             });
             
-            structure.Should().BeEquivalentTo(expected, o => o.WithTracing().WithAutoConversion().WithStrictOrdering().IncludingAllRuntimeProperties());
+            TestPlan.CompareOperations(structure, expected);
         }
         
         #endregion
@@ -210,7 +210,7 @@ namespace QuestPDF.UnitTests
             // assert
             var expected = new Container();
             
-            expected.Container().Stack(stack =>
+            expected.Stack(stack =>
             {
                 stack.Spacing(20);
                 
@@ -219,8 +219,8 @@ namespace QuestPDF.UnitTests
                     row.Spacing(30);
                     
                     row.RelativeColumn(3);
-                    row.RelativeColumn(5).Container().Element(childA);
-                    row.RelativeColumn(5).Container().Element(childB);
+                    row.RelativeColumn(5).Element(childA);
+                    row.RelativeColumn(5).Element(childB);
                     row.RelativeColumn(3);
                 });
                 
@@ -229,7 +229,7 @@ namespace QuestPDF.UnitTests
                     row.Spacing(30);
                     
                     row.RelativeColumn(3);
-                    row.RelativeColumn(10).Container().Element(childC);
+                    row.RelativeColumn(10).Element(childC);
                     row.RelativeColumn(3);
                 });
                 
@@ -238,12 +238,12 @@ namespace QuestPDF.UnitTests
                     row.Spacing(30);
                     
                     row.RelativeColumn(2);
-                    row.RelativeColumn(12).Container().Element(childD);
+                    row.RelativeColumn(12).Element(childD);
                     row.RelativeColumn(2);
                 });
             });
             
-            structure.Should().BeEquivalentTo(expected, o => o.WithTracing().WithAutoConversion().WithStrictOrdering().IncludingAllRuntimeProperties().AllowingInfiniteRecursion());
+            TestPlan.CompareOperations(structure, expected);
         }
         
         #endregion

+ 0 - 30
QuestPDF.UnitTests/Helpers.cs

@@ -1,30 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using FluentAssertions;
-using QuestPDF.Infrastructure;
-using QuestPDF.UnitTests.TestEngine;
-
-namespace QuestPDF.UnitTests
-{
-    public static class Helpers
-    {
-        public static Random Random = new Random();
-        
-        public static Size RandomSize => new Size(Random.Next(200, 400), Random.Next(100, 200));
-
-        public static void ShouldEqual(this IEnumerable<OperationBase> commands, IEnumerable<OperationBase> expected)
-        {
-            commands.Should().HaveSameCount(expected);
-            
-            commands
-                .Zip(expected)
-                .ToList()
-                .ForEach(x =>
-                {
-                    x.First.Should().BeOfType(x.Second.GetType());
-                    x.First.Should().BeEquivalentTo(x.Second, y => y.RespectingRuntimeTypes());
-                });
-        }
-    }
-}

+ 0 - 6
QuestPDF.UnitTests/ImageTests.cs

@@ -11,12 +11,6 @@ namespace QuestPDF.UnitTests
     [TestFixture]
     public class ImageTests
     {
-        [Test]
-        public void Measure_ShouldHandleNullChild() => new AspectRatio().MeasureWithoutChild();
-        
-        [Test]
-        public void Draw_ShouldHandleNullChild() => new AspectRatio().DrawWithoutChild();
-        
         [Test]
         public void Measure_TakesAvailableSpaceRegardlessOfSize()
         {

+ 15 - 0
QuestPDF.UnitTests/InternalLinkTests.cs

@@ -0,0 +1,15 @@
+using NUnit.Framework;
+using QuestPDF.Elements;
+using QuestPDF.UnitTests.TestEngine;
+
+namespace QuestPDF.UnitTests
+{
+    [TestFixture]
+    public class InternalLinkTests
+    {
+        [Test]
+        public void Measure() => SimpleContainerTests.Measure<InternalLink>();
+        
+        // TODO: consider tests for the Draw method
+    }
+}

+ 15 - 0
QuestPDF.UnitTests/InternalLocationTests.cs

@@ -0,0 +1,15 @@
+using NUnit.Framework;
+using QuestPDF.Elements;
+using QuestPDF.UnitTests.TestEngine;
+
+namespace QuestPDF.UnitTests
+{
+    [TestFixture]
+    public class InternalLocationTests
+    {
+        [Test]
+        public void Measure() => SimpleContainerTests.Measure<InternalLink>();
+        
+        // TODO: consider tests for the Draw method
+    }
+}

+ 131 - 0
QuestPDF.UnitTests/LayersTests.cs

@@ -0,0 +1,131 @@
+using System.Collections.Generic;
+using NUnit.Framework;
+using QuestPDF.Drawing.SpacePlan;
+using QuestPDF.Elements;
+using QuestPDF.Infrastructure;
+using QuestPDF.UnitTests.TestEngine;
+
+namespace QuestPDF.UnitTests
+{
+    [TestFixture]
+    public class LayersTests
+    {
+        private const string BackgroundLayer = "background";
+        private const string MainLayer = "main";
+        private const string ForegroundLayer = "foreground";
+        
+        private static Layers GetLayers(TestPlan x)
+        {
+            return new Layers
+            {
+                Children = new List<Layer>
+                {
+                    new Layer
+                    {
+                        Child = x.CreateChild(BackgroundLayer)
+                    },
+                    new Layer
+                    {
+                        Child = x.CreateChild(MainLayer),
+                        IsPrimary = true
+                    },
+                    new Layer
+                    {
+                        Child = x.CreateChild(ForegroundLayer)
+                    }
+                }
+            };
+        }
+        
+        #region measure
+        
+        [Test]
+        public void Measure_Wrap()
+        {
+            TestPlan
+                .For(GetLayers)
+                .MeasureElement(new Size(800, 600))
+                .ExpectChildMeasure(MainLayer, new Size(800, 600), new Wrap())
+                .CheckMeasureResult(new Wrap());
+        }
+
+        [Test]
+        public void Measure_PartialRender()
+        {
+            TestPlan
+                .For(GetLayers)
+                .MeasureElement(new Size(800, 600))
+                .ExpectChildMeasure(MainLayer, new Size(800, 600), new PartialRender(700, 500))
+                .CheckMeasureResult(new PartialRender(700, 500));
+        }
+        
+        [Test]
+        public void Measure_FullRender()
+        {
+            TestPlan
+                .For(GetLayers)
+                .MeasureElement(new Size(800, 600))
+                .ExpectChildMeasure(MainLayer, new Size(800, 600), new FullRender(500, 400))
+                .CheckMeasureResult(new FullRender(500, 400));
+        }
+        
+        #endregion
+        
+        #region draw
+        
+        [Test]
+        public void Draw_Simple()
+        {
+            TestPlan
+                .For(GetLayers)
+                .MeasureElement(new Size(800, 600))
+                
+                .ExpectChildMeasure(BackgroundLayer, new Size(800, 600), new FullRender(100, 200))
+                .ExpectChildMeasure(MainLayer, new Size(800, 600), new PartialRender(200, 300))
+                .ExpectChildMeasure(ForegroundLayer, new Size(800, 600), new FullRender(300, 400))
+                
+                
+                .ExpectChildDraw(BackgroundLayer, new Size(800, 600))
+                .ExpectChildDraw(MainLayer, new Size(800, 600))
+                .ExpectChildDraw(ForegroundLayer, new Size(800, 600))
+                
+                .CheckDrawResult();
+        }
+        
+        [Test]
+        public void Draw_WhenSecondaryLayerReturnsWrap_SkipThatLayer_1()
+        {
+            TestPlan
+                .For(GetLayers)
+                .MeasureElement(new Size(800, 600))
+                
+                .ExpectChildMeasure(BackgroundLayer, new Size(800, 600), new PartialRender(100, 200))
+                .ExpectChildMeasure(MainLayer, new Size(800, 600), new PartialRender(200, 300))
+                .ExpectChildMeasure(ForegroundLayer, new Size(800, 600), new Wrap())
+                
+                .ExpectChildDraw(BackgroundLayer, new Size(800, 600))
+                .ExpectChildDraw(MainLayer, new Size(800, 600))
+                
+                .CheckDrawResult();
+        }
+        
+        [Test]
+        public void Draw_WhenSecondaryLayerReturnsWrap_SkipThatLayer_2()
+        {
+            TestPlan
+                .For(GetLayers)
+                .MeasureElement(new Size(800, 600))
+                
+                .ExpectChildMeasure(BackgroundLayer, new Size(800, 600), new Wrap())
+                .ExpectChildMeasure(MainLayer, new Size(800, 600), new PartialRender(200, 300))
+                .ExpectChildMeasure(ForegroundLayer, new Size(800, 600), new PartialRender(300, 400))
+                
+                .ExpectChildDraw(MainLayer, new Size(800, 600))
+                .ExpectChildDraw(ForegroundLayer, new Size(800, 600))
+                
+                .CheckDrawResult();
+        }
+        
+        #endregion
+    }
+}

+ 0 - 6
QuestPDF.UnitTests/PaddingTests.cs

@@ -9,12 +9,6 @@ namespace QuestPDF.UnitTests
     [TestFixture]
     public class PaddingTests
     {
-        [Test]
-        public void Measure_ShouldHandleNullChild() => new Padding().MeasureWithoutChild();
-        
-        [Test]
-        public void Draw_ShouldHandleNullChild() => new Padding().DrawWithoutChild();
-        
         private Padding GetPadding(TestPlan plan)
         {
             return new Padding()

+ 0 - 10
QuestPDF.UnitTests/PageNumberTests.cs

@@ -1,10 +0,0 @@
-using NUnit.Framework;
-
-namespace QuestPDF.UnitTests
-{
-    [TestFixture]
-    public class PageNumberTests
-    {
-
-    }
-}

+ 3 - 4
QuestPDF.UnitTests/QuestPDF.UnitTests.csproj

@@ -6,10 +6,9 @@
     </PropertyGroup>
 
     <ItemGroup>
-        <PackageReference Include="FluentAssertions" Version="5.10.3" />
-        <PackageReference Include="Moq" Version="4.13.1" />
-        <PackageReference Include="nunit" Version="3.13.1" />
-        <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
+        <PackageReference Include="FluentAssertions" Version="6.1.0" />
+        <PackageReference Include="nunit" Version="3.13.2" />
+        <PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
         <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
         <PackageReference Include="SkiaSharp" Version="2.80.3" />
     </ItemGroup>

+ 31 - 0
QuestPDF.UnitTests/RotateTests.cs

@@ -0,0 +1,31 @@
+using NUnit.Framework;
+using QuestPDF.Drawing.SpacePlan;
+using QuestPDF.Elements;
+using QuestPDF.Infrastructure;
+using QuestPDF.UnitTests.TestEngine;
+
+namespace QuestPDF.UnitTests
+{
+    [TestFixture]
+    public class RotateTests
+    {
+        [Test]
+        public void Measure() => SimpleContainerTests.Measure<Rotate>();
+
+        [Test]
+        public void Draw()
+        {
+            TestPlan
+                .For(x => new Rotate
+                {
+                    Child = x.CreateChild(),
+                    Angle = 123
+                })
+                .DrawElement(new Size(400, 300))
+                .ExpectCanvasRotate(123)
+                .ExpectChildDraw(new Size(400, 300))
+                .ExpectCanvasRotate(-123)
+                .CheckDrawResult();
+        } 
+    }
+}

+ 154 - 3
QuestPDF.UnitTests/RowTests.cs

@@ -1,6 +1,7 @@
 using NUnit.Framework;
 using QuestPDF.Drawing.SpacePlan;
 using QuestPDF.Elements;
+using QuestPDF.Fluent;
 using QuestPDF.Infrastructure;
 using QuestPDF.UnitTests.TestEngine;
 
@@ -109,8 +110,158 @@ namespace QuestPDF.UnitTests
 
         #endregion
         
-        // TODO: add tests for the spacing property
-        // TODO: add tests for the tree builder method
-        // TODO: add tests for relative column
+        #region Structure
+        
+        [Test]
+        public void Structure_RelativeColumnsHandling()
+        { 
+            // arrange
+            var childA = TestPlan.CreateUniqueElement();
+            var childB = TestPlan.CreateUniqueElement();
+            var childC = TestPlan.CreateUniqueElement();
+            var childD = TestPlan.CreateUniqueElement();
+            var childE = TestPlan.CreateUniqueElement();
+
+            const int spacing = 25;
+            var availableSpace = new Size(1100, 400);
+            
+            // act
+            var value = new Container();
+
+            value.Row(row =>
+            {
+                row.Spacing(spacing);
+                
+                row.ConstantColumn(150).Element(childA);
+                row.ConstantColumn(250).Element(childB);
+                row.RelativeColumn(1).Element(childC);
+                row.RelativeColumn(2).Element(childD);
+                row.RelativeColumn(3).Element(childE);
+            });
+            
+            // assert
+            var expected = new Container();
+
+            expected.Row(row =>
+            {
+                row.Spacing(spacing);
+                
+                row.ConstantColumn(150).Element(childA);
+                row.ConstantColumn(250).Element(childB);
+                row.ConstantColumn(100).Element(childC);
+                row.ConstantColumn(200).Element(childD);
+                row.ConstantColumn(300).Element(childE);
+            });
+            
+            TestPlan.CompareOperations(value, expected, availableSpace);
+        }
+        
+        [Test]
+        public void Structure_Tree()
+        { 
+            // arrange
+            var childA = TestPlan.CreateUniqueElement();
+            var childB = TestPlan.CreateUniqueElement();
+            var childC = TestPlan.CreateUniqueElement();
+            var childD = TestPlan.CreateUniqueElement();
+            var childE = TestPlan.CreateUniqueElement();
+
+            const int spacing = 25;
+            var availableSpace = new Size(1200, 400);
+            
+            // act
+            var value = new Container();
+
+            value.Row(row =>
+            {
+                row.Spacing(spacing);
+                
+                row.ConstantColumn(150).Element(childA);
+                row.ConstantColumn(200).Element(childB);
+                row.ConstantColumn(250).Element(childC);
+                row.RelativeColumn(2).Element(childD);
+                row.RelativeColumn(3).Element(childE);
+            });
+            
+            // assert
+            var expected = new SimpleRow
+            {
+                Left = new SimpleRow
+                {
+                    Left = new SimpleRow
+                    {
+                        Left = new Constrained
+                        {
+                            MinWidth = 150,
+                            MaxWidth = 150,
+                            Child = childA
+                        },
+                        Right = new Constrained
+                        {
+                            MinWidth = 25,
+                            MaxWidth = 25
+                        }
+                    },
+                    Right = new SimpleRow
+                    {
+                        Left = new Constrained
+                        {
+                            MinWidth = 200,
+                            MaxWidth = 200,
+                            Child = childB
+                        },
+                        Right = new Constrained
+                        {
+                            MinWidth = 25,
+                            MaxWidth = 25
+                        }
+                    }
+                },
+                Right = new SimpleRow
+                {
+                    Left = new SimpleRow
+                    {
+                        Left = new Constrained
+                        {
+                            MinWidth = 250,
+                            MaxWidth = 250,
+                            Child = childC
+                        },
+                        Right = new Constrained
+                        {
+                            MinWidth = 25,
+                            MaxWidth = 25
+                        }
+                    },
+                    Right = new SimpleRow
+                    {
+                        Left = new Constrained
+                        {
+                            MinWidth = 200,
+                            MaxWidth = 200,
+                            Child = childD
+                        },
+                        Right = new SimpleRow
+                        {
+                            Left = new Constrained
+                            {
+                                MinWidth = 25,
+                                MaxWidth = 25
+                            },
+                            Right = new Constrained
+                            {
+                                MinWidth = 300,
+                                MaxWidth = 300,
+                                Child = childE
+                            }
+                        }
+                    }
+                }
+            };
+            
+            TestPlan.CompareOperations(value, expected, availableSpace);
+        }
+        
+        #endregion
     }
 }

+ 152 - 0
QuestPDF.UnitTests/ScaleTests.cs

@@ -0,0 +1,152 @@
+using NUnit.Framework;
+using QuestPDF.Drawing.SpacePlan;
+using QuestPDF.Elements;
+using QuestPDF.Infrastructure;
+using QuestPDF.UnitTests.TestEngine;
+
+namespace QuestPDF.UnitTests
+{
+    [TestFixture]
+    public class ScaleTests
+    {
+        #region measure
+        
+        [Test]
+        public void Measure_Wrap()
+        {
+            TestPlan
+                .For(x => new Scale
+                {
+                    Child = x.CreateChild(),
+                    ScaleX = 3,
+                    ScaleY = 2
+                })
+                .MeasureElement(new Size(900, 800))
+                .ExpectChildMeasure(new Size(300, 400), new Wrap())
+                .CheckMeasureResult(new Wrap());
+        }
+        
+        [Test]
+        public void Measure_PartialRender()
+        {
+            TestPlan
+                .For(x => new Scale
+                {
+                    Child = x.CreateChild(),
+                    ScaleX = 3,
+                    ScaleY = 2
+                })
+                .MeasureElement(new Size(900, 800))
+                .ExpectChildMeasure(new Size(300, 400), new PartialRender(200, 350))
+                .CheckMeasureResult(new PartialRender(600, 700));
+        }
+        
+        [Test]
+        public void Measure_FullRender()
+        {
+            TestPlan
+                .For(x => new Scale
+                {
+                    Child = x.CreateChild(),
+                    ScaleX = 3,
+                    ScaleY = 2
+                })
+                .MeasureElement(new Size(900, 800))
+                .ExpectChildMeasure(new Size(300, 400), new FullRender(250, 300))
+                .CheckMeasureResult(new FullRender(750, 600));
+        }
+        
+        [Test]
+        public void Measure_NegativeScaleX()
+        {
+            TestPlan
+                .For(x => new Scale
+                {
+                    Child = x.CreateChild(),
+                    ScaleX = -2,
+                    ScaleY = 1
+                })
+                .MeasureElement(new Size(800, 600))
+                .ExpectChildMeasure(new Size(400, 600), new FullRender(300, 500))
+                .CheckMeasureResult(new FullRender(600, 500));
+        }
+        
+        [Test]
+        public void Measure_NegativeScaleY()
+        {
+            TestPlan
+                .For(x => new Scale
+                {
+                    Child = x.CreateChild(),
+                    ScaleX = 1,
+                    ScaleY = -3
+                })
+                .MeasureElement(new Size(800, 600))
+                .ExpectChildMeasure(new Size(800, 200), new FullRender(800, 100))
+                .CheckMeasureResult(new FullRender(800, 300));
+        }
+        
+        #endregion
+        
+        #region draw
+        
+        [Test]
+        public void Draw_Simple()
+        {
+            TestPlan
+                .For(x => new Scale
+                {
+                    Child = x.CreateChild(),
+                    ScaleX = 3,
+                    ScaleY = 2
+                })
+                .DrawElement(new Size(900, 800))
+                .ExpectCanvasTranslate(0, 0)
+                .ExpectCanvasScale(3, 2)
+                .ExpectChildDraw(new Size(300, 400))
+                .ExpectCanvasScale(1/3f, 1/2f)
+                .ExpectCanvasTranslate(0, 0)
+                .CheckDrawResult();
+        }
+        
+        [Test]
+        public void Draw_NegativeScaleX()
+        {
+            TestPlan
+                .For(x => new Scale
+                {
+                    Child = x.CreateChild(),
+                    ScaleX = -3,
+                    ScaleY = 2
+                })
+                .DrawElement(new Size(900, 800))
+                .ExpectCanvasTranslate(900, 0)
+                .ExpectCanvasScale(-3, 2)
+                .ExpectChildDraw(new Size(300, 400))
+                .ExpectCanvasScale(-1/3f, 1/2f)
+                .ExpectCanvasTranslate(-900, 0)
+                .CheckDrawResult();
+        }
+        
+        [Test]
+        public void Draw_NegativeScaleY()
+        {
+            TestPlan
+                .For(x => new Scale
+                {
+                    Child = x.CreateChild(),
+                    ScaleX = 3,
+                    ScaleY = -2
+                })
+                .DrawElement(new Size(900, 800))
+                .ExpectCanvasTranslate(0, 800)
+                .ExpectCanvasScale(3, -2)
+                .ExpectChildDraw(new Size(300, 400))
+                .ExpectCanvasScale(1/3f, -1/2f)
+                .ExpectCanvasTranslate(0, -800)
+                .CheckDrawResult();
+        }
+        
+        #endregion
+    }
+}

+ 3 - 0
QuestPDF.UnitTests/ShowEntireTests.cs

@@ -47,5 +47,8 @@ namespace QuestPDF.UnitTests
                 .ExpectChildMeasure(new Size(400, 300), new FullRender(300, 200))
                 .CheckMeasureResult(new FullRender(300, 200));
         }
+        
+        [Test]
+        public void Draw() => SimpleContainerTests.Draw<ShowEntire>();
     }
 }

+ 2 - 29
QuestPDF.UnitTests/ShowOnceTest.cs

@@ -1,5 +1,4 @@
-using Moq;
-using NUnit.Framework;
+using NUnit.Framework;
 using QuestPDF.Drawing.SpacePlan;
 using QuestPDF.Elements;
 using QuestPDF.Infrastructure;
@@ -11,33 +10,7 @@ namespace QuestPDF.UnitTests
     public class ShowOnceTest
     {
         [Test]
-        public void Measure_ShouldHandleNullChild() => new ShowOnce().MeasureWithoutChild();
-        
-        [Test]
-        public void Draw_ShouldHandleNullChild() => new ShowOnce().DrawWithoutChild();
-
-        [Test]
-        public void ShouldRenderOnce_WhenRenderingCalledMultipleTimes()
-        {
-            var child = new Mock<Element>();
-            
-            child
-                .Setup(x => x.Measure(It.IsAny<Size>()))
-                .Returns(() => new FullRender(Size.Zero));
-
-            var element = new ShowOnce()
-            {
-                Child = child.Object
-            };
-
-            element.Draw(Size.Zero);
-            element.Draw(Size.Zero);
-            
-            child.Verify(x => x.Draw(It.IsAny<Size>()), Times.Once);
-        }
-        
-        [Test]
-        public void Draw_HorizontalRight_VerticalTop()
+        public void Draw()
         {
             TestPlan
                 .For(x => new ShowOnce()

+ 162 - 0
QuestPDF.UnitTests/SimpleRotateTests.cs

@@ -0,0 +1,162 @@
+using NUnit.Framework;
+using QuestPDF.Drawing.SpacePlan;
+using QuestPDF.Elements;
+using QuestPDF.Infrastructure;
+using QuestPDF.UnitTests.TestEngine;
+
+namespace QuestPDF.UnitTests
+{
+    [TestFixture]
+    public class SimpleRotateTests
+    {
+        #region measure
+        
+        [Test]
+        public void Measure_Wrap()
+        {
+            TestPlan
+                .For(x => new SimpleRotate
+                {
+                    Child = x.CreateChild(),
+                    TurnCount = 0
+                })
+                .MeasureElement(new Size(400, 300))
+                .ExpectChildMeasure(new Size(400, 300), new Wrap())
+                .CheckMeasureResult(new Wrap());
+        }
+        
+        [Test]
+        public void Measure_PartialRender()
+        {
+            TestPlan
+                .For(x => new SimpleRotate
+                {
+                    Child = x.CreateChild(),
+                    TurnCount = 0
+                })
+                .MeasureElement(new Size(400, 300))
+                .ExpectChildMeasure(new Size(400, 300), new PartialRender(300, 200))
+                .CheckMeasureResult(new PartialRender(300, 200));
+        }
+        
+        [Test]
+        public void Measure_RotateRight()
+        {
+            TestPlan
+                .For(x => new SimpleRotate
+                {
+                    Child = x.CreateChild(),
+                    TurnCount = 1
+                })
+                .MeasureElement(new Size(400, 300))
+                .ExpectChildMeasure(new Size(300, 400), new FullRender(200, 300))
+                .CheckMeasureResult(new FullRender(300, 200));
+        }
+        
+        [Test]
+        public void Measure_RotateFlip()
+        {
+            TestPlan
+                .For(x => new SimpleRotate
+                {
+                    Child = x.CreateChild(),
+                    TurnCount = 2
+                })
+                .MeasureElement(new Size(400, 300))
+                .ExpectChildMeasure(new Size(400, 300), new FullRender(200, 100))
+                .CheckMeasureResult(new FullRender(200, 100));
+        }
+        
+        [Test]
+        public void Measure_RotateLeft()
+        {
+            TestPlan
+                .For(x => new SimpleRotate
+                {
+                    Child = x.CreateChild(),
+                    TurnCount = 3 // or -1
+                })
+                .MeasureElement(new Size(500, 400))
+                .ExpectChildMeasure(new Size(400, 500), new FullRender(300, 350))
+                .CheckMeasureResult(new FullRender(350, 300));
+        }
+        
+        #endregion
+        
+        #region draw
+        
+        [Test]
+        public void Draw_Simple()
+        {
+            TestPlan
+                .For(x => new SimpleRotate
+                {
+                    Child = x.CreateChild(),
+                    TurnCount = 0
+                })
+                .DrawElement(new Size(640, 480))
+                .ExpectCanvasTranslate(0, 0)
+                .ExpectCanvasRotate(0)
+                .ExpectChildDraw(new Size(640, 480))
+                .ExpectCanvasRotate(0)
+                .ExpectCanvasTranslate(0, 0)
+                .CheckDrawResult();
+        }
+        
+        [Test]
+        public void Draw_RotateRight()
+        {
+            TestPlan 
+                .For(x => new SimpleRotate
+                {
+                    Child = x.CreateChild(),
+                    TurnCount = 1
+                })
+                .DrawElement(new Size(640, 480))
+                .ExpectCanvasTranslate(640, 0)
+                .ExpectCanvasRotate(90)
+                .ExpectChildDraw(new Size(480, 640))
+                .ExpectCanvasRotate(-90)
+                .ExpectCanvasTranslate(-640, 0)
+                .CheckDrawResult();
+        }
+        
+        [Test]
+        public void Draw_RotateFlip()
+        {
+            TestPlan 
+                .For(x => new SimpleRotate
+                {
+                    Child = x.CreateChild(),
+                    TurnCount = 2
+                })
+                .DrawElement(new Size(640, 480))
+                .ExpectCanvasTranslate(640, 480)
+                .ExpectCanvasRotate(180)
+                .ExpectChildDraw(new Size(640, 480))
+                .ExpectCanvasRotate(-180)
+                .ExpectCanvasTranslate(-640, -480)
+                .CheckDrawResult();
+        }
+        
+        [Test]
+        public void Draw_RotateLeft()
+        {
+            TestPlan 
+                .For(x => new SimpleRotate
+                {
+                    Child = x.CreateChild(),
+                    TurnCount = 3 // or -1
+                })
+                .DrawElement(new Size(640, 480))
+                .ExpectCanvasTranslate(0, 480)
+                .ExpectCanvasRotate(270)
+                .ExpectChildDraw(new Size(480, 640))
+                .ExpectCanvasRotate(-270)
+                .ExpectCanvasTranslate(0, -480)
+                .CheckDrawResult();
+        }
+        
+        #endregion
+    }
+}

+ 79 - 3
QuestPDF.UnitTests/StackTests.cs

@@ -1,6 +1,8 @@
-using NUnit.Framework;
+using FluentAssertions;
+using NUnit.Framework;
 using QuestPDF.Drawing.SpacePlan;
 using QuestPDF.Elements;
+using QuestPDF.Fluent;
 using QuestPDF.Infrastructure;
 using QuestPDF.UnitTests.TestEngine;
 
@@ -229,7 +231,81 @@ namespace QuestPDF.UnitTests
         
         #endregion
         
-        // TODO: add tests for the spacing property
-        // TODO: add tests for the tree builder method
+        #region Structure
+        
+        [Test]
+        public void Structure_Simple()
+        { 
+            // arrange
+            var childA = TestPlan.CreateUniqueElement();
+            var childB = TestPlan.CreateUniqueElement();
+            var childC = TestPlan.CreateUniqueElement();
+            var childD = TestPlan.CreateUniqueElement();
+            var childE = TestPlan.CreateUniqueElement();
+
+            const int spacing = 20;
+            
+            // act
+            var structure = new Container();
+            
+            structure.Stack(stack =>
+            {
+                stack.Spacing(spacing);
+                
+                stack.Item().Element(childA);
+                stack.Item().Element(childB);
+                stack.Item().Element(childC);
+                stack.Item().Element(childD);
+                stack.Item().Element(childE);
+            });
+            
+            // assert
+            var expected = new Padding
+            {
+                Bottom = -spacing,
+
+                Child = new SimpleStack
+                {
+                    First = new SimpleStack
+                    {
+                        First = new Padding
+                        {
+                            Bottom = spacing,
+                            Child = childA
+                        },
+                        Second = new Padding
+                        {
+                            Bottom = spacing,
+                            Child = childB
+                        }
+                    },
+                    Second = new SimpleStack
+                    {
+                        First = new Padding
+                        {
+                            Bottom = spacing,
+                            Child = childC
+                        },
+                        Second = new SimpleStack
+                        {
+                            First = new Padding
+                            {
+                                Bottom = spacing,
+                                Child = childD
+                            },
+                            Second = new Padding
+                            {
+                                Bottom = spacing,
+                                Child = childE
+                            }
+                        }
+                    }
+                }
+            };
+
+            TestPlan.CompareOperations(structure, expected);
+        }
+        
+        #endregion
     }
 }

+ 9 - 25
QuestPDF.UnitTests/TestEngine/CanvasMock.cs → QuestPDF.UnitTests/TestEngine/MockCanvas.cs

@@ -4,41 +4,25 @@ using SkiaSharp;
 
 namespace QuestPDF.UnitTests.TestEngine
 {
-    internal class CanvasMock : ICanvas
+    internal class MockCanvas : ICanvas
     {
         public Action<Position> TranslateFunc { get; set; }
+        public Action<float> RotateFunc { get; set; }
+        public Action<float, float> ScaleFunc { get; set; }
         public Action<SKImage, Position, Size> DrawImageFunc { get; set; }
         public Action<string, Position, TextStyle> DrawTextFunc { get; set; }
         public Action<Position, Size, string> DrawRectFunc { get; set; }
 
         public void Translate(Position vector) => TranslateFunc(vector);
+        public void Rotate(float angle) => RotateFunc(angle);
+        public void Scale(float scaleX, float scaleY) => ScaleFunc(scaleX, scaleY);
+
         public void DrawRectangle(Position vector, Size size, string color) => DrawRectFunc(vector, size, color);
         public void DrawText(string text, Position position, TextStyle style) => DrawTextFunc(text, position, style);
         public void DrawImage(SKImage image, Position position, Size size) => DrawImageFunc(image, position, size);
         
-        public void DrawExternalLink(string url, Size size)
-        {
-            throw new NotImplementedException();
-        }
-
-        public void DrawLocationLink(string locationName, Size size)
-        {
-            throw new NotImplementedException();
-        }
-
-        public void DrawLocation(string locationName)
-        {
-            throw new NotImplementedException();
-        }
-
-        public void DrawLink(string url, Size size)
-        {
-            throw new NotImplementedException();
-        }
-
-        public Size MeasureText(string text, TextStyle style)
-        {
-            return new Size(text.Length * style.Size, style.Size);
-        }
+        public void DrawExternalLink(string url, Size size) => throw new NotImplementedException();
+        public void DrawLocationLink(string locationName, Size size) => throw new NotImplementedException();
+        public void DrawLocation(string locationName) => throw new NotImplementedException();
     }
 }

+ 25 - 0
QuestPDF.UnitTests/TestEngine/OperationRecordingCanvas.cs

@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using QuestPDF.Infrastructure;
+using QuestPDF.UnitTests.TestEngine.Operations;
+using SkiaSharp;
+
+namespace QuestPDF.UnitTests.TestEngine
+{
+    internal class OperationRecordingCanvas : ICanvas
+    {
+        public ICollection<OperationBase> Operations { get; } = new List<OperationBase>();
+
+        public void Translate(Position vector) => Operations.Add(new CanvasTranslateOperation(vector));
+        public void Rotate(float angle) => Operations.Add(new CanvasRotateOperation(angle));
+        public void Scale(float scaleX, float scaleY) => Operations.Add(new CanvasScaleOperation(scaleX, scaleY));
+
+        public void DrawRectangle(Position vector, Size size, string color) => Operations.Add(new CanvasDrawRectangleOperation(vector, size, color));
+        public void DrawText(string text, Position position, TextStyle style) => Operations.Add(new CanvasDrawTextOperation(text, position, style));
+        public void DrawImage(SKImage image, Position position, Size size) => Operations.Add(new CanvasDrawImageOperation(position, size));
+        
+        public void DrawExternalLink(string url, Size size) => throw new NotImplementedException();
+        public void DrawLocationLink(string locationName, Size size) => throw new NotImplementedException();
+        public void DrawLocation(string locationName) => throw new NotImplementedException();
+    }
+}

+ 2 - 2
QuestPDF.UnitTests/TestEngine/Operations/CanvasDrawImageOperation.cs

@@ -2,12 +2,12 @@
 
 namespace QuestPDF.UnitTests.TestEngine.Operations
 {
-    internal class CanvasDrawImageOperationBase : OperationBase
+    internal class CanvasDrawImageOperation : OperationBase
     {
         public Position Position { get; }
         public Size Size { get; }
 
-        public CanvasDrawImageOperationBase(Position position, Size size)
+        public CanvasDrawImageOperation(Position position, Size size)
         {
             Position = position;
             Size = size;

+ 2 - 2
QuestPDF.UnitTests/TestEngine/Operations/CanvasDrawRectangleOperation.cs

@@ -2,13 +2,13 @@
 
 namespace QuestPDF.UnitTests.TestEngine.Operations
 {
-    internal class CanvasDrawRectangleOperationBase : OperationBase
+    internal class CanvasDrawRectangleOperation : OperationBase
     {
         public Position Position { get; } 
         public Size Size { get; }
         public string Color { get; }
 
-        public CanvasDrawRectangleOperationBase(Position position, Size size, string color)
+        public CanvasDrawRectangleOperation(Position position, Size size, string color)
         {
             Position = position;
             Size = size;

+ 2 - 2
QuestPDF.UnitTests/TestEngine/Operations/CanvasDrawTextOperation.cs

@@ -2,13 +2,13 @@
 
 namespace QuestPDF.UnitTests.TestEngine.Operations
 {
-    internal class CanvasDrawTextOperationBase : OperationBase
+    internal class CanvasDrawTextOperation : OperationBase
     {
         public string Text { get; }
         public Position Position { get; }
         public TextStyle Style { get; }
 
-        public CanvasDrawTextOperationBase(string text, Position position, TextStyle style)
+        public CanvasDrawTextOperation(string text, Position position, TextStyle style)
         {
             Text = text;
             Position = position;

+ 12 - 0
QuestPDF.UnitTests/TestEngine/Operations/CanvasRotateOperation.cs

@@ -0,0 +1,12 @@
+namespace QuestPDF.UnitTests.TestEngine.Operations
+{
+    public class CanvasRotateOperation : OperationBase
+    {
+        public float Angle { get; }
+
+        public CanvasRotateOperation(float angle)
+        {
+            Angle = angle;
+        }
+    }
+}

+ 14 - 0
QuestPDF.UnitTests/TestEngine/Operations/CanvasScaleOperation.cs

@@ -0,0 +1,14 @@
+namespace QuestPDF.UnitTests.TestEngine.Operations
+{
+    public class CanvasScaleOperation : OperationBase
+    {
+        public float ScaleX { get; }
+        public float ScaleY { get; }
+
+        public CanvasScaleOperation(float scaleX, float scaleY)
+        {
+            ScaleX = scaleX;
+            ScaleY = scaleY;
+        }
+    }
+}

+ 2 - 2
QuestPDF.UnitTests/TestEngine/Operations/CanvasTranslateOperation.cs

@@ -2,11 +2,11 @@
 
 namespace QuestPDF.UnitTests.TestEngine.Operations
 {
-    internal class CanvasTranslateOperationBase : OperationBase
+    internal class CanvasTranslateOperation : OperationBase
     {
         public Position Position { get; }
 
-        public CanvasTranslateOperationBase(Position position)
+        public CanvasTranslateOperation(Position position)
         {
             Position = position;
         }

+ 2 - 2
QuestPDF.UnitTests/TestEngine/Operations/ChildDrawOperation.cs

@@ -2,12 +2,12 @@
 
 namespace QuestPDF.UnitTests.TestEngine.Operations
 {
-    public class ChildDrawOperationBase : OperationBase
+    public class ChildDrawOperation : OperationBase
     {
         public string ChildId { get; }
         public Size Input { get; }
 
-        public ChildDrawOperationBase(string childId, Size input)
+        public ChildDrawOperation(string childId, Size input)
         {
             ChildId = childId;
             Input = input;

+ 2 - 2
QuestPDF.UnitTests/TestEngine/Operations/ChildMeasureOperation.cs

@@ -3,13 +3,13 @@ using QuestPDF.Infrastructure;
 
 namespace QuestPDF.UnitTests.TestEngine.Operations
 {
-    internal class ChildMeasureOperationBase : OperationBase
+    internal class ChildMeasureOperation : OperationBase
     {
         public string ChildId { get; }
         public Size Input { get; }
         public ISpacePlan Output { get; }
 
-        public ChildMeasureOperationBase(string childId, Size input, ISpacePlan output)
+        public ChildMeasureOperation(string childId, Size input, ISpacePlan output)
         {
             ChildId = childId;
             Input = input;

+ 2 - 2
QuestPDF.UnitTests/TestEngine/Operations/ElementMeasureOperation.cs

@@ -2,9 +2,9 @@
 
 namespace QuestPDF.UnitTests.TestEngine.Operations
 {
-    public class ElementMeasureOperationBase : OperationBase
+    public class ElementMeasureOperation : OperationBase
     {
-        public ElementMeasureOperationBase(Size input)
+        public ElementMeasureOperation(Size input)
         {
             
         }

+ 68 - 0
QuestPDF.UnitTests/TestEngine/SimpleContainerTests.cs

@@ -0,0 +1,68 @@
+using QuestPDF.Drawing.SpacePlan;
+using QuestPDF.Elements;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.UnitTests.TestEngine
+{
+    internal static class SimpleContainerTests
+    {
+        #region measure
+        
+        public static void Measure<TElement>() where TElement : Element, IContainer, new()
+        {
+            Measure_Wrap<TElement>();
+            Measure_PartialRender<TElement>();
+            Measure_FullRender<TElement>();
+        }
+        
+        private static void Measure_Wrap<TElement>() where TElement : Element, IContainer, new()
+        {
+            TestPlan
+                .For(x => new TElement
+                {
+                    Child = x.CreateChild()
+                })
+                .MeasureElement(new Size(400, 300))
+                .ExpectChildMeasure(new Size(400, 300), new Wrap())
+                .CheckMeasureResult(new Wrap());
+        }
+        
+        private static void Measure_PartialRender<TElement>() where TElement : Element, IContainer, new()
+        {
+            TestPlan
+                .For(x => new TElement
+                {
+                    Child = x.CreateChild()
+                })
+                .MeasureElement(new Size(400, 300))
+                .ExpectChildMeasure(new Size(400, 300), new PartialRender(200, 100))
+                .CheckMeasureResult(new PartialRender(200, 100));
+        }
+        
+        private static void Measure_FullRender<TElement>() where TElement : Element, IContainer, new()
+        {
+            TestPlan
+                .For(x => new TElement
+                {
+                    Child = x.CreateChild()
+                })
+                .MeasureElement(new Size(400, 300))
+                .ExpectChildMeasure(new Size(400, 300), new FullRender(250, 150))
+                .CheckMeasureResult(new FullRender(250, 150));
+        }
+        
+        #endregion
+        
+        public static void Draw<TElement>() where TElement : Element, IContainer, new()
+        {
+            TestPlan
+                .For(x => new TElement
+                {
+                    Child = x.CreateChild()
+                })
+                .DrawElement(new Size(1200, 900))
+                .ExpectChildDraw(new Size(1200, 900))
+                .CheckDrawResult();
+        }
+    }
+}

+ 0 - 31
QuestPDF.UnitTests/TestEngine/SingleChildTests.cs

@@ -1,31 +0,0 @@
-using FluentAssertions;
-using NUnit.Framework;
-using QuestPDF.Drawing.SpacePlan;
-using QuestPDF.Infrastructure;
-
-namespace QuestPDF.UnitTests.TestEngine
-{
-    public static class SingleChildTests
-    {
-        internal static void MeasureWithoutChild<T>(this T element) where T : ContainerElement
-        {
-            element.Child = null;
-            element.Measure(Size.Zero).Should().BeEquivalentTo(new FullRender(Size.Zero));
-        }
-        
-        internal static void DrawWithoutChild<T>(this T element) where T : ContainerElement
-        {
-            // component does not throw an exception when called with null child
-            Assert.DoesNotThrow(() =>
-            {
-                element.Child = null;
-                
-                // component does not perform any canvas operation when called with null child
-                TestPlan
-                    .For(x => element)
-                    .DrawElement(new Size(200, 100))
-                    .CheckDrawResult();
-            });
-        }
-    }
-}

+ 76 - 33
QuestPDF.UnitTests/TestEngine/TestPlan.cs

@@ -1,7 +1,9 @@
 using System;
 using System.Collections.Generic;
 using System.Text.Json;
+using FluentAssertions;
 using NUnit.Framework;
+using QuestPDF.Drawing;
 using QuestPDF.Drawing.SpacePlan;
 using QuestPDF.Elements;
 using QuestPDF.Helpers;
@@ -13,6 +15,8 @@ namespace QuestPDF.UnitTests.TestEngine
     internal class TestPlan
     {
         private const string DefaultChildName = "child";
+
+        private static Random Random { get; } = new Random();
         
         private Element Element { get; set; }
         private ICanvas Canvas { get; }
@@ -45,20 +49,31 @@ namespace QuestPDF.UnitTests.TestEngine
         
         private ICanvas CreateCanvas()
         {
-            return new CanvasMock
+            return new MockCanvas
             {
                 TranslateFunc = position =>
                 {
-                    var expected = GetExpected<CanvasTranslateOperationBase>();
+                    var expected = GetExpected<CanvasTranslateOperation>();
 
                     Assert.AreEqual(expected.Position.X, position.X, "Translate X");
                     Assert.AreEqual(expected.Position.Y, position.Y, "Translate Y");
-                    
-                    //position.Should().BeEquivalentTo(expected.Position);
+                },
+                RotateFunc = angle =>
+                {
+                    var expected = GetExpected<CanvasRotateOperation>();
+
+                    Assert.AreEqual(expected.Angle, angle, "Rotate angle");
+                },
+                ScaleFunc = (scaleX, scaleY) =>
+                {
+                    var expected = GetExpected<CanvasScaleOperation>();
+
+                    Assert.AreEqual(expected.ScaleX, scaleX, "Scale X");
+                    Assert.AreEqual(expected.ScaleY, scaleY, "Scale Y");
                 },
                 DrawRectFunc = (position, size, color) =>
                 {
-                    var expected = GetExpected<CanvasDrawRectangleOperationBase>();
+                    var expected = GetExpected<CanvasDrawRectangleOperation>();
                     
                     Assert.AreEqual(expected.Position.X, position.X, "Draw rectangle: X");
                     Assert.AreEqual(expected.Position.Y, position.Y, "Draw rectangle: Y");
@@ -67,14 +82,10 @@ namespace QuestPDF.UnitTests.TestEngine
                     Assert.AreEqual(expected.Size.Height, size.Height, "Draw rectangle: height");
                     
                     Assert.AreEqual(expected.Color, color, "Draw rectangle: color");
-                    
-                    /*position.Should().BeEquivalentTo(expected.Position);
-                    size.Should().BeEquivalentTo(expected.Size);
-                    color.Should().Be(expected.Color);*/
                 },
                 DrawTextFunc = (text, position, style) => 
                 {
-                    var expected = GetExpected<CanvasDrawTextOperationBase>();
+                    var expected = GetExpected<CanvasDrawTextOperation>();
                     
                     Assert.AreEqual(expected.Text, text);
                     
@@ -84,23 +95,16 @@ namespace QuestPDF.UnitTests.TestEngine
                     Assert.AreEqual(expected.Style.Color, style.Color, "Draw text: color");
                     Assert.AreEqual(expected.Style.FontType, style.FontType, "Draw text: font");
                     Assert.AreEqual(expected.Style.Size, style.Size, "Draw text: size");
-
-                    /*text.Should().Be(expected.Text);
-                    position.Should().BeEquivalentTo(expected.Position);
-                    style.Should().BeEquivalentTo(expected.Style);*/
                 },
                 DrawImageFunc = (image, position, size) =>
                 {
-                    var expected = GetExpected<CanvasDrawImageOperationBase>();
+                    var expected = GetExpected<CanvasDrawImageOperation>();
                     
                     Assert.AreEqual(expected.Position.X, position.X, "Draw image: X");
                     Assert.AreEqual(expected.Position.Y, position.Y, "Draw image: Y");
                     
                     Assert.AreEqual(expected.Size.Width, size.Width, "Draw image: width");
                     Assert.AreEqual(expected.Size.Height, size.Height, "Draw image: height");
-                    
-                    /*position.Should().BeEquivalentTo(expected.Position);
-                    size.Should().BeEquivalentTo(expected.Size);*/
                 }
             };
         }
@@ -114,29 +118,23 @@ namespace QuestPDF.UnitTests.TestEngine
                 Id = id,
                 MeasureFunc = availableSpace =>
                 {
-                    var expected = GetExpected<ChildMeasureOperationBase>();
+                    var expected = GetExpected<ChildMeasureOperation>();
 
                     Assert.AreEqual(expected.ChildId, id);
                     
                     Assert.AreEqual(expected.Input.Width, availableSpace.Width, $"Measure: width of child '{expected.ChildId}'");
                     Assert.AreEqual(expected.Input.Height, availableSpace.Height, $"Measure: height of child '{expected.ChildId}'");
 
-                    // id.Should().Be(expected.ChildId);
-                    // availableSpace.Should().Be(expected.Input);
-
                     return expected.Output;
                 },
                 DrawFunc = availableSpace =>
                 {
-                    var expected = GetExpected<ChildDrawOperationBase>();
+                    var expected = GetExpected<ChildDrawOperation>();
 
                     Assert.AreEqual(expected.ChildId, id);
                     
                     Assert.AreEqual(expected.Input.Width, availableSpace.Width, $"Draw: width of child '{expected.ChildId}'");
                     Assert.AreEqual(expected.Input.Height, availableSpace.Height, $"Draw: width of child '{expected.ChildId}'");
-                    
-                    /*id.Should().Be(expected.ChildId);
-                    availableSpace.Should().Be(expected.Input);*/
                 }
             };
         }
@@ -166,7 +164,7 @@ namespace QuestPDF.UnitTests.TestEngine
         
         public TestPlan ExpectChildMeasure(string child, Size expectedInput, ISpacePlan returns)
         {
-            return AddOperation(new ChildMeasureOperationBase(child, expectedInput, returns));
+            return AddOperation(new ChildMeasureOperation(child, expectedInput, returns));
         }
         
         public TestPlan ExpectChildDraw(Size expectedInput)
@@ -176,32 +174,42 @@ namespace QuestPDF.UnitTests.TestEngine
         
         public TestPlan ExpectChildDraw(string child, Size expectedInput)
         {
-            return AddOperation(new ChildDrawOperationBase(child, expectedInput));
+            return AddOperation(new ChildDrawOperation(child, expectedInput));
         }
 
         public TestPlan ExpectCanvasTranslate(Position position)
         {
-            return AddOperation(new CanvasTranslateOperationBase(position));
+            return AddOperation(new CanvasTranslateOperation(position));
         }
         
         public TestPlan ExpectCanvasTranslate(float left, float top)
         {
-            return AddOperation(new CanvasTranslateOperationBase(new Position(left, top)));
+            return AddOperation(new CanvasTranslateOperation(new Position(left, top)));
         }
 
+        public TestPlan ExpectCanvasScale(float scaleX, float scaleY)
+        {
+            return AddOperation(new CanvasScaleOperation(scaleX, scaleY));
+        }
+        
+        public TestPlan ExpectCanvasRotate(float angle)
+        {
+            return AddOperation(new CanvasRotateOperation(angle));
+        }
+        
         public TestPlan ExpectCanvasDrawRectangle(Position position, Size size, string color)
         {
-            return AddOperation(new CanvasDrawRectangleOperationBase(position, size, color));
+            return AddOperation(new CanvasDrawRectangleOperation(position, size, color));
         }
         
         public TestPlan ExpectCanvasDrawText(string text, Position position, TextStyle style)
         {
-            return AddOperation(new CanvasDrawTextOperationBase(text, position, style));
+            return AddOperation(new CanvasDrawTextOperation(text, position, style));
         }
         
         public TestPlan ExpectCanvasDrawImage(Position position, Size size)
         {
-            return AddOperation(new CanvasDrawImageOperationBase(position, size));
+            return AddOperation(new CanvasDrawImageOperation(position, size));
         }
         
         public TestPlan CheckMeasureResult(ISpacePlan expected)
@@ -251,5 +259,40 @@ namespace QuestPDF.UnitTests.TestEngine
                 Source = Placeholders.Image
             };
         }
+
+        public static void CompareOperations(Element value, Element expected, Size? availableSpace = null)
+        {
+            CompareMeasureOperations(value, expected, availableSpace);
+            CompareDrawOperations(value, expected, availableSpace);
+        }
+        
+        private static void CompareMeasureOperations(Element value, Element expected, Size? availableSpace = null)
+        {
+            availableSpace ??= new Size(400, 300);
+            
+            var canvas = new FreeCanvas();
+            value.HandleVisitor(x => x.Initialize(null, canvas));
+            var valueMeasure = value.Measure(availableSpace);
+            
+            expected.HandleVisitor(x => x.Initialize(null, canvas));
+            var expectedMeasure = expected.Measure(availableSpace);
+            
+            valueMeasure.Should().BeEquivalentTo(expectedMeasure);
+        }
+        
+        private static void CompareDrawOperations(Element value, Element expected, Size? availableSpace = null)
+        {
+            availableSpace ??= new Size(400, 300);
+            
+            var valueCanvas = new OperationRecordingCanvas();
+            value.HandleVisitor(x => x.Initialize(null, valueCanvas));
+            value.Draw(availableSpace);
+            
+            var expectedCanvas = new OperationRecordingCanvas();
+            expected.HandleVisitor(x => x.Initialize(null, expectedCanvas));
+            expected.Draw(availableSpace);
+            
+            valueCanvas.Operations.Should().BeEquivalentTo(expectedCanvas.Operations);
+        }
     }
 }

+ 22 - 0
QuestPDF.UnitTests/TestsBase.cs

@@ -0,0 +1,22 @@
+using FluentAssertions;
+using NUnit.Framework;
+
+namespace QuestPDF.UnitTests
+{
+    [SetUpFixture]
+    public class TestsBase
+    {
+        [OneTimeSetUp]
+        public void RunBeforeAnyTests()
+        {
+            AssertionOptions.AssertEquivalencyUsing(options => options
+                .IncludingNestedObjects()
+                .IncludingInternalProperties()
+                .IncludingInternalFields()
+                .AllowingInfiniteRecursion()
+                .RespectingRuntimeTypes()
+                .WithTracing()
+                .WithStrictOrdering());
+        }
+    }
+}

+ 0 - 10
QuestPDF.UnitTests/TextTests.cs

@@ -1,10 +0,0 @@
-using NUnit.Framework;
-
-namespace QuestPDF.UnitTests
-{
-    [TestFixture]
-    public class TextTests
-    {
-        
-    }
-}

+ 32 - 0
QuestPDF.UnitTests/TranslateTests.cs

@@ -0,0 +1,32 @@
+using NUnit.Framework;
+using QuestPDF.Drawing.SpacePlan;
+using QuestPDF.Elements;
+using QuestPDF.Infrastructure;
+using QuestPDF.UnitTests.TestEngine;
+
+namespace QuestPDF.UnitTests
+{
+    [TestFixture]
+    public class TranslateTests
+    {
+        [Test]
+        public void Measure() => SimpleContainerTests.Measure<Translate>();
+        
+        [Test]
+        public void Draw()
+        {
+            TestPlan
+                .For(x => new Translate
+                {
+                    Child = x.CreateChild(),
+                    TranslateX = 50,
+                    TranslateY = 75
+                })
+                .DrawElement(new Size(400, 300))
+                .ExpectCanvasTranslate(50, 75)
+                .ExpectChildDraw(new Size(400, 300))
+                .ExpectCanvasTranslate(-50, -75)
+                .CheckDrawResult();
+        }
+    }
+}

+ 100 - 0
QuestPDF.UnitTests/UnconstrainedTests.cs

@@ -0,0 +1,100 @@
+using NUnit.Framework;
+using QuestPDF.Drawing.SpacePlan;
+using QuestPDF.Elements;
+using QuestPDF.Infrastructure;
+using QuestPDF.UnitTests.TestEngine;
+
+namespace QuestPDF.UnitTests
+{
+    [TestFixture]
+    public class UnconstrainedTests
+    {
+        #region measure
+        
+        [Test]
+        public void Measure_Wrap()
+        {
+            TestPlan
+                .For(x => new Unconstrained
+                {
+                    Child = x.CreateChild()
+                })
+                .MeasureElement(new Size(900, 800))
+                .ExpectChildMeasure(Size.Max, new Wrap())
+                .CheckMeasureResult(new Wrap());
+        }
+        
+        [Test]
+        public void Measure_PartialRender()
+        {
+            TestPlan
+                .For(x => new Unconstrained
+                {
+                    Child = x.CreateChild()
+                })
+                .MeasureElement(new Size(900, 800))
+                .ExpectChildMeasure(Size.Max, new PartialRender(1200, 1600))
+                .CheckMeasureResult(new PartialRender(Size.Zero));
+        }
+        
+        [Test]
+        public void Measure_FullRender()
+        {
+            TestPlan
+                .For(x => new Unconstrained
+                {
+                    Child = x.CreateChild()
+                })
+                .MeasureElement(new Size(900, 800))
+                .ExpectChildMeasure(Size.Max, new FullRender(1200, 1600))
+                .CheckMeasureResult(new FullRender(Size.Zero));
+        }
+        
+        #endregion
+        
+        #region draw
+        
+        [Test]
+        public void Draw_SkipWhenChildWraps()
+        {
+            TestPlan
+                .For(x => new Unconstrained
+                {
+                    Child = x.CreateChild()
+                })
+                .DrawElement(new Size(900, 800))
+                .ExpectChildMeasure(Size.Max, new Wrap())
+                .CheckDrawResult();
+        }
+        
+        [Test]
+        public void Draw_WhenChildPartiallyRenders()
+        {
+            TestPlan
+                .For(x => new Unconstrained
+                {
+                    Child = x.CreateChild()
+                })
+                .DrawElement(new Size(900, 800))
+                .ExpectChildMeasure(Size.Max, new PartialRender(1200, 1600))
+                .ExpectChildDraw(new Size(1200, 1600))
+                .CheckDrawResult();
+        }
+        
+        [Test]
+        public void Draw_WhenChildFullyRenders()
+        {
+            TestPlan
+                .For(x => new Unconstrained
+                {
+                    Child = x.CreateChild()
+                })
+                .DrawElement(new Size(900, 800))
+                .ExpectChildMeasure(Size.Max, new FullRender(1600, 1000))
+                .ExpectChildDraw(new Size(1600, 1000))
+                .CheckDrawResult();
+        }
+        
+        #endregion
+    }
+}

+ 10 - 0
QuestPDF/Drawing/FreeCanvas.cs

@@ -66,6 +66,16 @@ namespace QuestPDF.Drawing
             
         }
 
+        public void Rotate(float angle)
+        {
+            
+        }
+
+        public void Scale(float scaleX, float scaleY)
+        {
+            
+        }
+
         #endregion
     }
 }

+ 10 - 0
QuestPDF/Drawing/SkiaCanvasBase.cs

@@ -51,5 +51,15 @@ namespace QuestPDF.Drawing
         {
             Canvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), locationName);
         }
+
+        public void Rotate(float angle)
+        {
+            Canvas.RotateDegrees(angle);
+        }
+
+        public void Scale(float scaleX, float scaleY)
+        {
+            Canvas.Scale(scaleX, scaleY);
+        }
     }
 }

+ 1 - 1
QuestPDF/Elements/Grid.cs

@@ -16,7 +16,7 @@ namespace QuestPDF.Elements
     {
         public const int DefaultColumnsCount = 12;
         
-        public List<GridElement> Children { get; set; } = new List<GridElement>();
+        public List<GridElement> Children { get; } = new List<GridElement>();
         public Queue<GridElement> ChildrenQueue { get; set; } = new Queue<GridElement>();
         public int ColumnsCount { get; set; } = DefaultColumnsCount;
 

+ 2 - 9
QuestPDF/Elements/Rotate.cs

@@ -8,16 +8,9 @@ namespace QuestPDF.Elements
 
         internal override void Draw(Size availableSpace)
         {
-            var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
-            
-            if (skiaCanvas == null)
-                return;
-            
-            var currentMatrix = skiaCanvas.TotalMatrix;
-            
-            skiaCanvas.RotateDegrees(Angle);
+            Canvas.Rotate(Angle);
             Child?.Draw(availableSpace);
-            skiaCanvas.SetMatrix(currentMatrix);
+            Canvas.Rotate(-Angle);
         }
     }
 }

+ 8 - 13
QuestPDF/Elements/Scale.cs

@@ -35,26 +35,21 @@ namespace QuestPDF.Elements
         
         internal override void Draw(Size availableSpace)
         {
-            var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
-            
-            if (skiaCanvas == null)
-                return;
-            
             var targetSpace = new Size(
                 Math.Abs(availableSpace.Width / ScaleX), 
                 Math.Abs(availableSpace.Height / ScaleY));
 
-            var currentMatrix = skiaCanvas.TotalMatrix;
-            
-            if (ScaleX < 0)
-                skiaCanvas.Translate(availableSpace.Width, 0);
+            var translate = new Position(
+                ScaleX < 0 ? availableSpace.Width : 0,
+                ScaleY < 0 ? availableSpace.Height : 0);
             
-            if (ScaleY < 0)
-                skiaCanvas.Translate(0, availableSpace.Height);
+            Canvas.Translate(translate);
+            Canvas.Scale(ScaleX, ScaleY);
             
-            skiaCanvas.Scale(ScaleX, ScaleY);
             Child?.Draw(targetSpace);
-            skiaCanvas.SetMatrix(currentMatrix);
+             
+            Canvas.Scale(1/ScaleX, 1/ScaleY);
+            Canvas.Translate(translate.Reverse());
         }
     }
 }

+ 9 - 13
QuestPDF/Elements/SimpleRotate.cs

@@ -33,26 +33,22 @@ namespace QuestPDF.Elements
         
         internal override void Draw(Size availableSpace)
         {
-            var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
-            
-            if (skiaCanvas == null)
-                return;
-
-            var currentMatrix = skiaCanvas.TotalMatrix;
+            var translate = new Position(
+                (NormalizedTurnCount == 1 || NormalizedTurnCount == 2) ? availableSpace.Width : 0,
+                (NormalizedTurnCount == 2 || NormalizedTurnCount == 3) ? availableSpace.Height : 0);
 
-            if (NormalizedTurnCount == 1 || NormalizedTurnCount == 2)
-                skiaCanvas.Translate(availableSpace.Width, 0);
+            var rotate = NormalizedTurnCount * 90;
             
-            if (NormalizedTurnCount == 2  || NormalizedTurnCount == 3)
-                skiaCanvas.Translate(0, availableSpace.Height);
-
-            skiaCanvas.RotateDegrees(NormalizedTurnCount * 90);
+            Canvas.Translate(translate);
+            Canvas.Rotate(rotate);
             
             if (NormalizedTurnCount == 1 || NormalizedTurnCount == 3)
                 availableSpace = new Size(availableSpace.Height, availableSpace.Width);
             
             Child?.Draw(availableSpace);
-            skiaCanvas.SetMatrix(currentMatrix);
+            
+            Canvas.Rotate(-rotate);
+            Canvas.Translate(translate.Reverse());
         }
     }
 }

+ 1 - 1
QuestPDF/Elements/Stack.cs

@@ -91,7 +91,7 @@ namespace QuestPDF.Elements
     
     internal class Stack : IComponent
     {
-        public ICollection<Element> Children { get; internal set; } = new List<Element>();
+        public ICollection<Element> Children { get; } = new List<Element>();
         public float Spacing { get; set; } = 0;
         
         public void Compose(IContainer container)

+ 3 - 6
QuestPDF/Elements/Translate.cs

@@ -10,14 +10,11 @@ namespace QuestPDF.Elements
 
         internal override void Draw(Size availableSpace)
         {
-            var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
+            var translate = new Position(TranslateX, TranslateY);
             
-            if (skiaCanvas == null)
-                return;
-            
-            skiaCanvas.Translate(TranslateX, TranslateY);
+            Canvas.Translate(translate);
             base.Draw(availableSpace);
-            skiaCanvas.Translate(-TranslateX, -TranslateY);
+            Canvas.Translate(translate.Reverse());
         }
     }
 }

+ 3 - 0
QuestPDF/Infrastructure/ICanvas.cs

@@ -13,5 +13,8 @@ namespace QuestPDF.Infrastructure
         void DrawExternalLink(string url, Size size);
         void DrawLocationLink(string locationName, Size size);
         void DrawLocation(string locationName);
+        
+        void Rotate(float angle);
+        void Scale(float scaleX, float scaleY);
     }
 }