Browse Source

Added tests for stack and row, project cleanup

Marcin Ziąbek 4 years ago
parent
commit
f792f6c749

+ 10 - 10
QuestPDF.Examples/ElementExamples.cs

@@ -19,20 +19,20 @@ namespace QuestPDF.Examples
         
         //[ShowResult]
         [ImageSize(300, 300)]
-        public void Section(IContainer container)
+        public void Decoration(IContainer container)
         {
             container
                 .Background("#FFF")
                 .Padding(25)
-                .Decoration(section =>
+                .Decoration(decoration =>
                 {
-                    section
+                    decoration
                         .Header()
                         .Background("#888")
                         .Padding(10)
                         .Text("Notes", TextStyle.Default.Size(16).Color("#FFF"));
                     
-                    section
+                    decoration
                         .Content()
                         .Background("#DDD")
                         .Padding(10)
@@ -181,12 +181,12 @@ namespace QuestPDF.Examples
                     grid.Spacing(5);
                     grid.Columns(12);
 
-                    grid.Element(8).Background("#DDD").Height(50).Padding(5).Text("This is a short text", textStyle);
-                    grid.Element(4).Background("#BBB").Padding(5).Text("Showing how to...", textStyle);
-                    grid.Element(2).Background("#999").Height(50);
-                    grid.Element(4).Background("#AAA").Border(2).BorderColor("#666").Padding(5).Text("...generate", textStyle);
-                    grid.Element(6).Background("#CCC").Padding(5).Text("simple grids", textStyle.Size(18).Bold());
-                    grid.Element(8).Background("#DDD").Height(50);
+                    grid.Item(8).Background("#DDD").Height(50).Padding(5).Text("This is a short text", textStyle);
+                    grid.Item(4).Background("#BBB").Padding(5).Text("Showing how to...", textStyle);
+                    grid.Item(2).Background("#999").Height(50);
+                    grid.Item(4).Background("#AAA").Border(2).BorderColor("#666").Padding(5).Text("...generate", textStyle);
+                    grid.Item(6).Background("#CCC").Padding(5).Text("simple grids", textStyle.Size(18).Bold());
+                    grid.Item(8).Background("#DDD").Height(50);
                 });
         }
         

+ 8 - 8
QuestPDF.ReportSample/Layouts/PhotoTemplate.cs

@@ -20,8 +20,8 @@ namespace QuestPDF.ReportSample.Layouts
                 .Stack(stack =>
                 {
                     stack.Spacing(5);
-                    stack.Item(PhotoWithMaps);
-                    stack.Item(PhotoDetails);
+                    stack.Item().Element(PhotoWithMaps);
+                    stack.Item().Element(PhotoDetails);
                 });
         }
         
@@ -48,13 +48,13 @@ namespace QuestPDF.ReportSample.Layouts
             {
                 grid.Columns(6);
                 
-                grid.Element().LabelCell().Text("Date", Typography.Normal);
-                grid.Element(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty, Typography.Normal);
-                grid.Element().LabelCell().Text("Location", Typography.Normal);
-                grid.Element(2).ValueCell().Text(Model.Location.Format(), Typography.Normal);
+                grid.Item().LabelCell().Text("Date", Typography.Normal);
+                grid.Item(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty, Typography.Normal);
+                grid.Item().LabelCell().Text("Location", Typography.Normal);
+                grid.Item(2).ValueCell().Text(Model.Location.Format(), Typography.Normal);
                 
-                grid.Element().LabelCell().Text("Comments", Typography.Normal);
-                grid.Element(5).ValueCell().Text(Model.Comments, Typography.Normal);
+                grid.Item().LabelCell().Text("Comments", Typography.Normal);
+                grid.Item(5).ValueCell().Text(Model.Comments, Typography.Normal);
             });
         }
     }

+ 4 - 4
QuestPDF.ReportSample/Layouts/SectionTemplate.cs

@@ -17,14 +17,14 @@ namespace QuestPDF.ReportSample.Layouts
         {
             container
                 .EnsureSpace()
-                .Decoration(section =>
+                .Decoration(decoration =>
                 {
-                    section
+                    decoration
                         .Header()
                         .PaddingBottom(5)
                         .Text(Model.Title, Typography.Headline);
 
-                    section.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Stack(stack =>
+                    decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Stack(stack =>
                     {
                         foreach (var part in Model.Parts)
                         {
@@ -77,7 +77,7 @@ namespace QuestPDF.ReportSample.Layouts
                 grid.Spacing(5);
                 grid.Columns(3);
                 
-                model.Photos.ForEach(x => grid.Element().AspectRatio(4 / 3f).Image(Placeholders.Image));
+                model.Photos.ForEach(x => grid.Item().AspectRatio(4 / 3f).Image(Placeholders.Image));
             });
         }
     }

+ 3 - 3
QuestPDF.ReportSample/Layouts/StandardReport.cs

@@ -30,8 +30,8 @@ namespace QuestPDF.ReportSample.Layouts
                 .PaddingHorizontal(50)
                 .Page(page =>
                 {
-                    page.Header(ComposeHeader);
-                    page.Content(ComposeContent);
+                    page.Header().Element(ComposeHeader);
+                    page.Content().Element(ComposeContent);
                     page.Footer().AlignCenter().PageNumber("Page {number}");
                 });
         }
@@ -57,7 +57,7 @@ namespace QuestPDF.ReportSample.Layouts
                         
                     foreach (var field in Model.HeaderFields)
                     {
-                        grid.Element().Stack(row =>
+                        grid.Item().Stack(row =>
                         {   
                             row.Item().AlignLeft().Text(field.Label, Typography.Normal.SemiBold());
                             row.Item().Text(field.Value, Typography.Normal);

+ 5 - 5
QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs

@@ -16,21 +16,21 @@ namespace QuestPDF.ReportSample.Layouts
         public void Compose(IContainer container)
         {
             container
-                .Decoration(section =>
+                .Decoration(decoration =>
                 {
-                    section
+                    decoration
                         .Header()
                         .PaddingBottom(5)
                         .Text("Table of contents", Typography.Headline);
 
-                    section.Content().Stack(stack =>
+                    decoration.Content().Stack(stack =>
                     {
                         stack.Spacing(5);
                         
                         for (var i = 0; i < Sections.Count; i++)
-                            stack.Item(c => DrawLink(c, i+1, Sections[i].Title));
+                            stack.Item().Element(c => DrawLink(c, i+1, Sections[i].Title));
 
-                        stack.Item(c => DrawLink(c, Sections.Count+1, "Photos"));
+                        stack.Item().Element(c => DrawLink(c, Sections.Count+1, "Photos"));
                     });
                 });
         }

+ 46 - 3
QuestPDF.UnitTests/EnsureSpaceTests.cs

@@ -7,10 +7,10 @@ using QuestPDF.UnitTests.TestEngine;
 namespace QuestPDF.UnitTests
 {
     [TestFixture]
-    public class WrapWhenLittleSpaceTests
+    public class EnsureSpaceTests
     {
         [Test]
-        public void Measure_ReturnsWrap_WhenNotEnoughSpace()
+        public void Measure_ReturnsWrap_WhenChildReturnsWrap()
         {
             TestPlan
                 .For(x => new EnsureSpace
@@ -19,11 +19,54 @@ namespace QuestPDF.UnitTests
                     MinHeight = 200
                 })
                 .MeasureElement(new Size(400, 100))
+                .ExpectChildMeasure(new Size(400, 100), new Wrap())
                 .CheckMeasureResult(new Wrap());
         }
         
         [Test]
-        public void Measure_Continues_WhenEnoughSpace()
+        public void Measure_ReturnsWrap_WhenChildReturnsPartialRender_AndNotEnoughSpace()
+        {
+            TestPlan
+                .For(x => new EnsureSpace
+                {
+                    Child = x.CreateChild(),
+                    MinHeight = 200
+                })
+                .MeasureElement(new Size(400, 100))
+                .ExpectChildMeasure(new Size(400, 100), new PartialRender(300, 50))
+                .CheckMeasureResult(new Wrap());
+        }
+        
+        [Test]
+        public void Measure_ReturnsPartialRender_WhenChildReturnsPartialRender_AndEnoughSpace()
+        {
+            TestPlan
+                .For(x => new EnsureSpace
+                {
+                    Child = x.CreateChild(),
+                    MinHeight = 200
+                })
+                .MeasureElement(new Size(400, 300))
+                .ExpectChildMeasure(new Size(400, 300), new PartialRender(300, 250))
+                .CheckMeasureResult(new PartialRender(300, 250));
+        }
+        
+        [Test]
+        public void Measure_ReturnsFullRender_WhenChildReturnsFullRender_AndNotEnoughSpace()
+        {
+            TestPlan
+                .For(x => new EnsureSpace
+                {
+                    Child = x.CreateChild(),
+                    MinHeight = 200
+                })
+                .MeasureElement(new Size(400, 100))
+                .ExpectChildMeasure(new Size(400, 100), new FullRender(300, 50))
+                .CheckMeasureResult(new FullRender(300, 50));
+        }
+        
+        [Test]
+        public void Measure_ReturnsFullRender_WhenChildReturnsFullRender_AndEnoughSpace()
         {
             TestPlan
                 .For(x => new EnsureSpace

+ 17 - 17
QuestPDF.UnitTests/GridTests.cs

@@ -6,7 +6,7 @@ using QuestPDF.UnitTests.TestEngine;
 
 namespace QuestPDF.UnitTests
 {
-    [TestFixture]
+    /*[TestFixture]
     public class GridTests
     {
         #region Alignment
@@ -29,11 +29,11 @@ namespace QuestPDF.UnitTests
                 {
                     grid.AlignLeft();
                     
-                    grid.Element(6).Element(childA);
-                    grid.Element(4).Element(childB);
-                    grid.Element(4).Element(childC);
-                    grid.Element(2).Element(childD);
-                    grid.Element(8).Element(childE);
+                    grid.Item(6).Element(childA);
+                    grid.Item(4).Element(childB);
+                    grid.Item(4).Element(childC);
+                    grid.Item(2).Element(childD);
+                    grid.Item(8).Element(childE);
                 });
             
             // assert
@@ -83,11 +83,11 @@ namespace QuestPDF.UnitTests
                 {
                     grid.AlignCenter();
                     
-                    grid.Element(6).Element(childA);
-                    grid.Element(4).Element(childB);
-                    grid.Element(4).Element(childC);
-                    grid.Element(2).Element(childD);
-                    grid.Element(8).Element(childE);
+                    grid.Item(6).Element(childA);
+                    grid.Item(4).Element(childB);
+                    grid.Item(4).Element(childC);
+                    grid.Item(2).Element(childD);
+                    grid.Item(8).Element(childE);
                 });
             
             // assert
@@ -140,11 +140,11 @@ namespace QuestPDF.UnitTests
                 {
                     grid.AlignRight();
                     
-                    grid.Element(6).Element(childA);
-                    grid.Element(4).Element(childB);
-                    grid.Element(4).Element(childC);
-                    grid.Element(2).Element(childD);
-                    grid.Element(8).Element(childE);
+                    grid.Item(6).Element(childA);
+                    grid.Item(4).Element(childB);
+                    grid.Item(4).Element(childC);
+                    grid.Item(2).Element(childD);
+                    grid.Item(8).Element(childE);
                 });
             
             // assert
@@ -177,5 +177,5 @@ namespace QuestPDF.UnitTests
         }
         
         #endregion
-    }
+    }*/
 }

+ 92 - 88
QuestPDF.UnitTests/RowTests.cs

@@ -1,8 +1,7 @@
-using System.Collections.Generic;
-using FluentAssertions;
-using NUnit.Framework;
+using NUnit.Framework;
+using QuestPDF.Drawing.SpacePlan;
 using QuestPDF.Elements;
-using QuestPDF.Fluent;
+using QuestPDF.Infrastructure;
 using QuestPDF.UnitTests.TestEngine;
 
 namespace QuestPDF.UnitTests
@@ -10,103 +9,108 @@ namespace QuestPDF.UnitTests
     [TestFixture]
     public class RowTests
     {
-        #region Spacing
+        #region Measure
         
         [Test]
-        public void Fluent_WithoutSpacing()
+        public void Measure_ReturnsWrap_WhenLeftChildReturnsWrap()
         {
-            // arrange
-            var structure = new Container();
-
-            var childA = TestPlan.CreateUniqueElement();
-            var childB = TestPlan.CreateUniqueElement();
-            var childC = TestPlan.CreateUniqueElement();
-
-            // act
-            structure
-                .Row(stack =>
+            TestPlan
+                .For(x => new SimpleRow
                 {
-                    stack.Spacing(0);
-
-                    stack.ConstantColumn(100).Element(childA);
-                    stack.RelativeColumn(2).Element(childB);
-                    stack.RelativeColumn(3).Element(childC);
-                });
-            
-            // assert
-            var expected = new Row()
-            {
-                Children = new List<RowElement>
+                    Left = x.CreateChild("left"),
+                    Right = x.CreateChild("right")
+                })
+                .MeasureElement(new Size(400, 300))
+                .ExpectChildMeasure("left", new Size(400, 300), new Wrap())
+                .CheckMeasureResult(new Wrap());
+        }
+        
+        [Test]
+        public void Measure_ReturnsWrap_WhenRightChildReturnsWrap()
+        {
+            TestPlan
+                .For(x => new SimpleRow
                 {
-                    new ConstantRowElement(100)
-                    {
-                        Child = childA
-                    },
-                    new RelativeRowElement(2)
-                    {
-                        Child = childB
-                    },
-                    new RelativeRowElement(3)
-                    {
-                        Child = childC
-                    },
-                }
-            };
-            
-            structure.Child.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering().IncludingAllRuntimeProperties());
+                    Left = x.CreateChild("left"),
+                    Right = x.CreateChild("right")
+                })
+                .MeasureElement(new Size(400, 300))
+                .ExpectChildMeasure("left", new Size(400, 300), new FullRender(250, 150))
+                .ExpectChildMeasure("right", new Size(150, 300), new Wrap())
+                .CheckMeasureResult(new Wrap());
         }
         
         [Test]
-        public void Fluent_WithSpacing()
+        public void Measure_ReturnsPartialRender_WhenLeftChildReturnsPartialRender()
         {
-            // arrange
-            var structure = new Container();
-
-            var childA = TestPlan.CreateUniqueElement();
-            var childB = TestPlan.CreateUniqueElement();
-            var childC = TestPlan.CreateUniqueElement();
-
-            // act
-            structure
-                .Row(stack =>
+            TestPlan
+                .For(x => new SimpleRow
                 {
-                    stack.Spacing(25);
-
-                    stack.ConstantColumn(100).Element(childA);
-                    stack.RelativeColumn(2).Element(childB);
-                    stack.RelativeColumn(3).Element(childC);
-                });
-            
-            // assert
-            var expected = new Padding
-            {
-                Right = -25,
-                Child = new Row()
+                    Left = x.CreateChild("left"),
+                    Right = x.CreateChild("right")
+                })
+                .MeasureElement(new Size(400, 300))
+                .ExpectChildMeasure("left", new Size(400, 300), new PartialRender(250, 150))
+                .ExpectChildMeasure("right", new Size(150, 300), new FullRender(100, 100))
+                .CheckMeasureResult(new PartialRender(350, 150));
+        }
+        
+        [Test]
+        public void Measure_ReturnsPartialRender_WhenRightChildReturnsPartialRender()
+        {
+            TestPlan
+                .For(x => new SimpleRow
                 {
-                    Children = new List<RowElement>
-                    {
-                        new ConstantRowElement(100)
-                        {
-                            Child = childA
-                        },
-                        new ConstantRowElement(25),
-                        new RelativeRowElement(2)
-                        {
-                            Child = childB
-                        },
-                        new ConstantRowElement(25),
-                        new RelativeRowElement(3)
-                        {
-                            Child = childC
-                        },
-                        new ConstantRowElement(25)
-                    }
-                }
-            };
-            
-            structure.Child.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering().IncludingAllRuntimeProperties());
+                    Left = x.CreateChild("left"),
+                    Right = x.CreateChild("right")
+                })
+                .MeasureElement(new Size(400, 300))
+                .ExpectChildMeasure("left", new Size(400, 300), new FullRender(250, 150))
+                .ExpectChildMeasure("right", new Size(150, 300), new PartialRender(100, 100))
+                .CheckMeasureResult(new PartialRender(350, 150));
         }
         
+        [Test]
+        public void Measure_ReturnsFullRender_WhenBothChildrenReturnFullRender()
+        {
+            TestPlan
+                .For(x => new SimpleRow
+                {
+                    Left = x.CreateChild("left"),
+                    Right = x.CreateChild("right")
+                })
+                .MeasureElement(new Size(400, 300))
+                .ExpectChildMeasure("left", new Size(400, 300), new FullRender(200, 150))
+                .ExpectChildMeasure("right", new Size(200, 300), new FullRender(100, 100))
+                .CheckMeasureResult(new FullRender(300, 150));
+        }
+        
+        #endregion
+
+        #region Draw
+
+        [Test]
+        public void Draw()
+        {
+            TestPlan
+                .For(x => new SimpleRow
+                {
+                    Left = x.CreateChild("left"),
+                    Right = x.CreateChild("right")
+                })
+                .DrawElement(new Size(400, 300))
+                .ExpectChildMeasure("left", new Size(400, 300), new FullRender(250, 150))
+                .ExpectChildDraw("left", new Size(250, 300))
+                .ExpectCanvasTranslate(250, 0)
+                .ExpectChildDraw("right", new Size(150, 300))
+                .ExpectCanvasTranslate(-250, 0)
+                .CheckDrawResult();
+        }
+
         #endregion
+        
+        // TODO: add tests for the spacing property
+        // TODO: add tests for the tree builder method
+        // TODO: add tests for relative column
     }
 }

+ 172 - 227
QuestPDF.UnitTests/StackTests.cs

@@ -1,9 +1,6 @@
-using System.Collections.Generic;
-using FluentAssertions;
-using NUnit.Framework;
+using NUnit.Framework;
 using QuestPDF.Drawing.SpacePlan;
 using QuestPDF.Elements;
-using QuestPDF.Fluent;
 using QuestPDF.Infrastructure;
 using QuestPDF.UnitTests.TestEngine;
 
@@ -13,278 +10,226 @@ namespace QuestPDF.UnitTests
     public class StackTests
     {
         #region Measure
+
+        [Test]
+        public void Measure_ReturnsWrap_WhenFirstChildWraps()
+        {
+            TestPlan
+                .For(x => new SimpleStack
+                {
+                    First = x.CreateChild("first"),
+                    Second = x.CreateChild("second")
+                })
+                .MeasureElement(new Size(400, 300))
+                .ExpectChildMeasure("first", new Size(400, 300), new Wrap())
+                .CheckMeasureResult(new Wrap());
+        }
         
         [Test]
-        public void Measure_NoChildren_Empty()
+        public void Measure_ReturnsPartialRender_WhenFirstChildReturnsPartialRender()
         {
             TestPlan
-                .For(x => new Stack())
-                .MeasureElement(new Size(500, 1000))
-                .CheckMeasureResult(new FullRender(Size.Zero));
+                .For(x => new SimpleStack
+                {
+                    First = x.CreateChild("first"),
+                    Second = x.CreateChild("second")
+                })
+                .MeasureElement(new Size(400, 300))
+                .ExpectChildMeasure("first", new Size(400, 300), new PartialRender(300, 200))
+                .CheckMeasureResult(new PartialRender(300, 200));
         }
         
         [Test]
-        public void Measure_ReturnsWrap_WhenPageable_AndAnyChildReturnsWrap()
+        public void Measure_ReturnsPartialRender_WhenSecondChildWraps()
         {
             TestPlan
-                .For(x => new Stack
+                .For(x => new SimpleStack
                 {
-                    Children = new []
-                    {
-                        x.CreateChild("a"),
-                        x.CreateChild("b"),
-                        x.CreateChild("c"),
-                        x.CreateChild("d")
-                    }
+                    First = x.CreateChild("first"),
+                    Second = x.CreateChild("second")
                 })
-                .MeasureElement(new Size(500, 1000))
-                .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(500, 200))
-                .ExpectChildMeasure("b", expectedInput: new Size(500, 800), returns: new FullRender(500, 300))
-                .ExpectChildMeasure("c", expectedInput: new Size(500, 500), returns: new Wrap())
-                .CheckMeasureResult(new PartialRender(500, 500));
+                .MeasureElement(new Size(400, 300))
+                .ExpectChildMeasure("first", new Size(400, 300), new FullRender(200, 100))
+                .ExpectChildMeasure("second", new Size(400, 200), new Wrap())
+                .CheckMeasureResult(new PartialRender(200, 100));
         }
         
         [Test]
-        public void Measure_ReturnsWrap_WhenPageable_AndFirstChildReturnsWrap()
+        public void Measure_ReturnsPartialRender_WhenSecondChildReturnsPartialRender()
         {
             TestPlan
-                .For(x => new Stack
+                .For(x => new SimpleStack
                 {
-                    Children = new []
-                    {
-                        x.CreateChild("a"),
-                        x.CreateChild("b"),
-                        x.CreateChild("c")
-                    }
+                    First = x.CreateChild("first"),
+                    Second = x.CreateChild("second")
                 })
-                .MeasureElement(new Size(500, 1000))
-                .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new Wrap())
-                .CheckMeasureResult(new Wrap());
+                .MeasureElement(new Size(400, 300))
+                .ExpectChildMeasure("first", new Size(400, 300), new FullRender(200, 100))
+                .ExpectChildMeasure("second", new Size(400, 200), new PartialRender(300, 150))
+                .CheckMeasureResult(new PartialRender(300, 250));
+        }
+        
+        [Test]
+        public void Measure_ReturnsFullRender_WhenSecondChildReturnsFullRender()
+        {
+            TestPlan
+                .For(x => new SimpleStack
+                {
+                    First = x.CreateChild("first"),
+                    Second = x.CreateChild("second")
+                })
+                .MeasureElement(new Size(400, 300))
+                .ExpectChildMeasure("first", new Size(400, 300), new FullRender(200, 100))
+                .ExpectChildMeasure("second", new Size(400, 200), new FullRender(100, 50))
+                .CheckMeasureResult(new FullRender(200, 150));
+        }
+        
+        [Test]
+        public void Measure_UsesEmpty_WhenFirstChildIsRendered()
+        {
+            TestPlan
+                .For(x => new SimpleStack
+                {
+                    First = x.CreateChild("first"),
+                    Second = x.CreateChild("second"),
+                    
+                    IsFirstRendered = true
+                })
+                .MeasureElement(new Size(400, 300))
+                .ExpectChildMeasure("second", new Size(400, 300), new FullRender(200, 300))
+                .CheckMeasureResult(new FullRender(200, 300));
         }
         
         #endregion
-
-        #region Combined
-
+        
+        #region Draw
+        
         [Test]
-        public void WhenPageable_AndChildReturnsWrap()
+        public void Draw_WhenFirstChildWraps()
         {
             TestPlan
-                .For(x => new Stack
+                .For(x => new SimpleStack
                 {
-                    Children = new[]
-                    {
-                        x.CreateChild("a"),
-                        x.CreateChild("b"),
-                        x.CreateChild("c")
-                    }
+                    First = x.CreateChild("first"),
+                    Second = x.CreateChild("second")
                 })
-
-                // page 1: measure
-                .MeasureElement(new Size(500, 1000))
-                .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(400, 200))
-                .ExpectChildMeasure("b", expectedInput: new Size(500, 800), returns: new Wrap())
-                .CheckMeasureResult(new PartialRender(500, 200))
-
-                // page 1: draw
-                .DrawElement(new Size(500, 1000))
-
-                .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(400, 200))
-                .ExpectCanvasTranslate(0, 0)
-                .ExpectChildDraw("a", new Size(500, 200))
-                .ExpectCanvasTranslate(0, 0)
-
-                .ExpectChildMeasure("b", expectedInput: new Size(500, 800), returns: new Wrap())
-
-                .CheckDrawResult()
-
-                // // page 2: measure
-                .MeasureElement(new Size(500, 900))
-                .ExpectChildMeasure("b", expectedInput: new Size(500, 900), returns: new FullRender(300, 200))
-                .ExpectChildMeasure("c", expectedInput: new Size(500, 700), returns: new FullRender(300, 300))
-                .CheckMeasureResult(new FullRender(500, 500))
-                
-                // page 2: draw
-                .DrawElement(new Size(500, 900))
-                
-                .ExpectChildMeasure("b", expectedInput: new Size(500, 900), returns: new FullRender(300, 200))
-                .ExpectCanvasTranslate(0, 0)
-                .ExpectChildDraw("b", new Size(500, 200))
-                .ExpectCanvasTranslate(0, 0)
-                
-                .ExpectChildMeasure("c", expectedInput: new Size(500, 700), returns: new FullRender(300, 300))
-                .ExpectCanvasTranslate(0, 200)
-                .ExpectChildDraw("c", new Size(500, 300))
-                .ExpectCanvasTranslate(0, -200)
-                
+                .DrawElement(new Size(400, 300))
+                .ExpectChildMeasure("first", new Size(400, 300), new Wrap())
                 .CheckDrawResult();
         }
-
+        
         [Test]
-        public void WhenPageable_AndChildReturnsPartialRender()
+        public void Draw_WhenFirstChildPartiallyRenders()
         {
             TestPlan
-                .For(x => new Stack
+                .For(x => new SimpleStack
                 {
-                    Children = new[]
-                    {
-                        x.CreateChild("a"),
-                        x.CreateChild("b"),
-                        x.CreateChild("c")
-                    }
+                    First = x.CreateChild("first"),
+                    Second = x.CreateChild("second")
                 })
-
-                // page 1: measure
-                .MeasureElement(new Size(500, 1000))
-                .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(400, 200))
-                .ExpectChildMeasure("b", expectedInput: new Size(500, 800), returns: new PartialRender(300, 300))
-                .CheckMeasureResult(new PartialRender(500, 500))
-
-                // page 1: draw
-                .DrawElement(new Size(500, 1000))
-
-                .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(400, 200))
-                .ExpectCanvasTranslate(0, 0)
-                .ExpectChildDraw("a", new Size(500, 200))
-                .ExpectCanvasTranslate(0, 0)
-
-                .ExpectChildMeasure("b", expectedInput: new Size(500, 800), returns: new PartialRender(300, 300))
-                .ExpectCanvasTranslate(0, 200)
-                .ExpectChildDraw("b", new Size(500, 300))
-                .ExpectCanvasTranslate(0, -200)
-                
-                .CheckDrawResult()
-
-                // page 2: measure
-                .MeasureElement(new Size(500, 900))
-                .ExpectChildMeasure("b", expectedInput: new Size(500, 900), returns: new FullRender(300, 200))
-                .ExpectChildMeasure("c", expectedInput: new Size(500, 700), returns: new FullRender(300, 300))
-                .CheckMeasureResult(new FullRender(500, 500))
-                
-                // page 2: draw
-                .DrawElement(new Size(500, 900))
-                
-                .ExpectChildMeasure("b", expectedInput: new Size(500, 900), returns: new FullRender(300, 200))
-                .ExpectCanvasTranslate(0, 0)
-                .ExpectChildDraw("b", new Size(500, 200))
-                .ExpectCanvasTranslate(0, 0)
-                
-                .ExpectChildMeasure("c", expectedInput: new Size(500, 700), returns: new FullRender(300, 300))
-                .ExpectCanvasTranslate(0, 200)
-                .ExpectChildDraw("c", new Size(500, 300))
-                .ExpectCanvasTranslate(0, -200)
-                
+                .DrawElement(new Size(400, 300))
+                .ExpectChildMeasure("first", new Size(400, 300), new PartialRender(200, 100))
+                .ExpectChildDraw("first", new Size(400, 100))
                 .CheckDrawResult();
         }
         
-        #endregion
-        
-        #region Spacing
+        [Test]
+        public void Draw_WhenFirstChildFullyRenders_AndSecondChildWraps()
+        {
+            TestPlan
+                .For(x => new SimpleStack
+                {
+                    First = x.CreateChild("first"),
+                    Second = x.CreateChild("second")
+                })
+                .DrawElement(new Size(400, 300))
+                .ExpectChildMeasure("first", new Size(400, 300), new FullRender(200, 100))
+                .ExpectChildDraw("first", new Size(400, 100))
+                .ExpectChildMeasure("second", new Size(400, 200), new Wrap())
+                .CheckDrawResult();
+        }
         
         [Test]
-        public void Fluent_WithoutSpacing()
+        public void Draw_WhenFirstChildFullyRenders_AndSecondChildPartiallyRenders()
         {
-            // arrange
-            var structure = new Container();
-
-            var childA = TestPlan.CreateUniqueElement();
-            var childB = TestPlan.CreateUniqueElement();
-            var childC = TestPlan.CreateUniqueElement();
-
-            // act
-            structure
-                .Stack(stack =>
+            TestPlan
+                .For(x => new SimpleStack
                 {
-                    stack.Spacing(0);
-
-                    stack.Item().Element(childA);
-                    stack.Item().Element(childB);
-                    stack.Item().Element(childC);
-                });
-            
-            // assert
-            var expected = new Stack
-            {
-                Children = new List<Element>
+                    First = x.CreateChild("first"),
+                    Second = x.CreateChild("second")
+                })
+                .DrawElement(new Size(400, 300))
+                .ExpectChildMeasure("first", new Size(400, 300), new FullRender(200, 100))
+                .ExpectChildDraw("first", new Size(400, 100))
+                .ExpectChildMeasure("second", new Size(400, 200), new PartialRender(250, 150))
+                .ExpectCanvasTranslate(0, 100)
+                .ExpectChildDraw("second", new Size(400, 150))
+                .ExpectCanvasTranslate(0, -100)
+                .CheckDrawResult();
+        }
+        
+        [Test]
+        public void Draw_WhenFirstChildFullyRenders_AndSecondChildFullyRenders()
+        {
+            TestPlan
+                .For(x => new SimpleStack
                 {
-                    new Container
-                    {
-                        Child = childA
-                    },
-                    new Container
-                    {
-                        Child = childB
-                    },
-                    new Container
-                    {
-                        Child = childC
-                    }
-                }
-            };
-            
-            structure.Child.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering().IncludingAllRuntimeProperties());
+                    First = x.CreateChild("first"),
+                    Second = x.CreateChild("second")
+                })
+                .DrawElement(new Size(400, 300))
+                .ExpectChildMeasure("first", new Size(400, 300), new FullRender(200, 100))
+                .ExpectChildDraw("first", new Size(400, 100))
+                .ExpectChildMeasure("second", new Size(400, 200), new FullRender(250, 150))
+                .ExpectCanvasTranslate(0, 100)
+                .ExpectChildDraw("second", new Size(400, 150))
+                .ExpectCanvasTranslate(0, -100)
+                .CheckDrawResult();
         }
         
         [Test]
-        public void Fluent_WithSpacing()
+        public void Draw_UsesEmpty_WhenFirstChildIsRendered()
         {
-            // arrange
-            var structure = new Container();
-
-            var childA = TestPlan.CreateUniqueElement();
-            var childB = TestPlan.CreateUniqueElement();
-            var childC = TestPlan.CreateUniqueElement();
-
-            // act
-            structure
-                .Stack(stack =>
+            TestPlan
+                .For(x => new SimpleStack
                 {
-                    stack.Spacing(100);
-
-                    stack.Item().Element(childA);
-                    stack.Item().Element(childB);
-                    stack.Item().Element(childC);
-                });
-            
-            // assert
-            var expected = new Padding
-            {
-                Bottom = -100,
-                Child = new Stack
+                    First = x.CreateChild("first"),
+                    Second = x.CreateChild("second"),
+                    
+                    IsFirstRendered = true
+                })
+                .DrawElement(new Size(400, 300))
+                .ExpectChildMeasure("second", new Size(400, 300), new PartialRender(200, 300))
+                .ExpectCanvasTranslate(0, 0)
+                .ExpectChildDraw("second", new Size(400, 300))
+                .ExpectCanvasTranslate(0, 0)
+                .CheckState<SimpleStack>(x => x.IsFirstRendered)
+                .CheckDrawResult();
+        }
+        
+        [Test]
+        public void Draw_TogglesFirstRenderedFlag_WhenSecondFullyRenders()
+        {
+            TestPlan
+                .For(x => new SimpleStack
                 {
-                    Children = new List<Element>
-                    {
-                        new Padding
-                        {
-                            Bottom = 100,
-                            Child = new Container
-                            {
-                                Child = childA
-                            }
-                        },
-                        new Padding
-                        {
-                            Bottom = 100,
-                            Child = new Container
-                            {
-                                Child = childB
-                            }
-                        },
-                        new Padding
-                        {
-                            Bottom = 100,
-                            Child = new Container
-                            {
-                                Child = childC
-                            }
-                        },
-                    }
-                }
-            };
-            
-            structure.Child.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering().IncludingAllRuntimeProperties());
+                    First = x.CreateChild("first"),
+                    Second = x.CreateChild("second"),
+                    
+                    IsFirstRendered = true
+                })
+                .DrawElement(new Size(400, 300))
+                .ExpectChildMeasure("second", new Size(400, 300), new FullRender(200, 300))
+                .ExpectCanvasTranslate(0, 0)
+                .ExpectChildDraw("second", new Size(400, 300))
+                .ExpectCanvasTranslate(0, 0)
+                .CheckDrawResult()
+                .CheckState<SimpleStack>(x => !x.IsFirstRendered);
         }
         
         #endregion
+        
+        // TODO: add tests for the spacing property
+        // TODO: add tests for the tree builder method
     }
 }

+ 36 - 23
QuestPDF.UnitTests/TestEngine/TestPlan.cs

@@ -49,9 +49,9 @@ namespace QuestPDF.UnitTests.TestEngine
                 TranslateFunc = position =>
                 {
                     var expected = GetExpected<CanvasTranslateOperationBase>();
-                    
-                    Assert.AreEqual(expected.Position.X, position.X);
-                    Assert.AreEqual(expected.Position.Y, position.Y);
+
+                    Assert.AreEqual(expected.Position.X, position.X, "Translate X");
+                    Assert.AreEqual(expected.Position.Y, position.Y, "Translate Y");
                     
                     //position.Should().BeEquivalentTo(expected.Position);
                 },
@@ -59,13 +59,13 @@ namespace QuestPDF.UnitTests.TestEngine
                 {
                     var expected = GetExpected<CanvasDrawRectangleOperationBase>();
                     
-                    Assert.AreEqual(expected.Position.X, position.X);
-                    Assert.AreEqual(expected.Position.Y, position.Y);
+                    Assert.AreEqual(expected.Position.X, position.X, "Draw rectangle: X");
+                    Assert.AreEqual(expected.Position.Y, position.Y, "Draw rectangle: Y");
                     
-                    Assert.AreEqual(expected.Size.Width, size.Width);
-                    Assert.AreEqual(expected.Size.Height, size.Height);
+                    Assert.AreEqual(expected.Size.Width, size.Width, "Draw rectangle: width");
+                    Assert.AreEqual(expected.Size.Height, size.Height, "Draw rectangle: height");
                     
-                    Assert.AreEqual(expected.Color, color);
+                    Assert.AreEqual(expected.Color, color, "Draw rectangle: color");
                     
                     /*position.Should().BeEquivalentTo(expected.Position);
                     size.Should().BeEquivalentTo(expected.Size);
@@ -77,12 +77,12 @@ namespace QuestPDF.UnitTests.TestEngine
                     
                     Assert.AreEqual(expected.Text, text);
                     
-                    Assert.AreEqual(expected.Position.X, position.X);
-                    Assert.AreEqual(expected.Position.Y, position.Y);
+                    Assert.AreEqual(expected.Position.X, position.X, "Draw text: X");
+                    Assert.AreEqual(expected.Position.Y, position.Y, "Draw text: Y");
                     
-                    Assert.AreEqual(expected.Style.Color, style.Color);
-                    Assert.AreEqual(expected.Style.FontType, style.FontType);
-                    Assert.AreEqual(expected.Style.Size, style.Size);
+                    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);
@@ -92,11 +92,11 @@ namespace QuestPDF.UnitTests.TestEngine
                 {
                     var expected = GetExpected<CanvasDrawImageOperationBase>();
                     
-                    Assert.AreEqual(expected.Position.X, position.X);
-                    Assert.AreEqual(expected.Position.Y, position.Y);
+                    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);
-                    Assert.AreEqual(expected.Size.Height, size.Height);
+                    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);*/
@@ -117,8 +117,8 @@ namespace QuestPDF.UnitTests.TestEngine
 
                     Assert.AreEqual(expected.ChildId, id);
                     
-                    Assert.AreEqual(expected.Input.Width, availableSpace.Width);
-                    Assert.AreEqual(expected.Input.Height, availableSpace.Height);
+                    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);
@@ -131,8 +131,8 @@ namespace QuestPDF.UnitTests.TestEngine
 
                     Assert.AreEqual(expected.ChildId, id);
                     
-                    Assert.AreEqual(expected.Input.Width, availableSpace.Width);
-                    Assert.AreEqual(expected.Input.Height, availableSpace.Height);
+                    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);*/
@@ -214,8 +214,8 @@ namespace QuestPDF.UnitTests.TestEngine
 
             if (expectedSize != null)
             {
-                Assert.AreEqual(expectedSize.Width, actualSize.Width);
-                Assert.AreEqual(expectedSize.Height, actualSize.Height);
+                Assert.AreEqual(expectedSize.Width, actualSize.Width, "Measure: width");
+                Assert.AreEqual(expectedSize.Height, actualSize.Height, "Measure: height");
             }
             
             return this;
@@ -227,6 +227,19 @@ namespace QuestPDF.UnitTests.TestEngine
             return this;
         }
 
+        public TestPlan CheckState(Func<Element, bool> condition)
+        {
+            Assert.IsTrue(condition(Element), "Checking condition");
+            return this;
+        }
+
+        public TestPlan CheckState<T>(Func<T, bool> condition) where T : Element
+        {
+            Assert.IsTrue(Element is T);
+            Assert.IsTrue(condition(Element as T), "Checking condition");
+            return this;
+        }
+        
         public static Element CreateUniqueElement()
         {
             return new Text

+ 58 - 0
QuestPDF/Elements/Decoration.cs

@@ -1,8 +1,66 @@
+using System;
+using QuestPDF.Drawing.SpacePlan;
 using QuestPDF.Fluent;
 using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Elements
 {
+    internal enum DecorationType
+    {
+        Prepend,
+        Append
+    }
+    
+    internal class SimpleDecoration : Element
+    {
+        public Element DecorationElement { get; set; } = Empty.Instance;
+        public Element ContentElement { get; set; } = Empty.Instance;
+        public DecorationType Type { get; set; } 
+
+        internal override ISpacePlan Measure(Size availableSpace)
+        {
+            var decorationMeasure = DecorationElement?.Measure(availableSpace);
+            
+            if (decorationMeasure is Wrap || decorationMeasure is PartialRender)
+                return new Wrap();
+
+            var decorationSize = decorationMeasure as Size ?? Size.Zero;
+            var contentMeasure = ContentElement?.Measure(new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height)) ?? new FullRender(Size.Zero);
+            
+            if (contentMeasure is Wrap)
+                return new Wrap();
+
+            var contentSize = contentMeasure as Size ?? Size.Zero;
+            var resultSize = new Size(availableSpace.Width, decorationSize.Height + contentSize.Height);
+            
+            if (contentSize is PartialRender)
+                return new PartialRender(resultSize);
+            
+            if (contentSize is FullRender)
+                return new FullRender(resultSize);
+            
+            throw new NotSupportedException();
+        }
+
+        internal override void Draw(ICanvas canvas, Size availableSpace)
+        {
+            var decorationSize = DecorationElement?.Measure(availableSpace) as Size ?? Size.Zero;
+            var contentSize = new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height);
+
+            var translateHeight = Type == DecorationType.Prepend ? decorationSize.Height : contentSize.Height;
+            Action drawDecoration = () => DecorationElement?.Draw(canvas, new Size(availableSpace.Width, decorationSize.Height));
+            Action drawContent = () => ContentElement?.Draw(canvas, new Size (availableSpace.Width, contentSize.Height));
+
+            var first = Type == DecorationType.Prepend ? drawDecoration : drawContent;
+            var second = Type == DecorationType.Prepend ? drawContent : drawDecoration;
+
+            first();
+            canvas.Translate(new Position(0, translateHeight));
+            second();
+            canvas.Translate(new Position(0, -translateHeight));
+        }
+    }
+    
     internal class Decoration : IComponent
     {
         public Element Header { get; set; } = Empty.Instance;

+ 4 - 4
QuestPDF/Elements/Page.cs

@@ -11,11 +11,11 @@ namespace QuestPDF.Elements
 
         public void Compose(IContainer container)
         {
-            container.Decoration(section =>
+            container.Decoration(decoration =>
             {
-                section.Header().Element(Header);
-                section.Content().Extend().Element(Content);
-                section.Footer().Element(Footer);
+                decoration.Header().Element(Header);
+                decoration.Content().Extend().Element(Content);
+                decoration.Footer().Element(Footer);
             });
         }
     }

+ 102 - 49
QuestPDF/Elements/Row.cs

@@ -1,6 +1,8 @@
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using QuestPDF.Drawing.SpacePlan;
+using QuestPDF.Fluent;
 using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Elements
@@ -31,78 +33,129 @@ namespace QuestPDF.Elements
         }
     }
     
-    internal class Row : Element
+    internal class SimpleRow : Element
     {
-        public List<RowElement> Children { get; set; } = new List<RowElement>();
-        
-        float? ConstantWidthSum { get; set; }
-        float? RelativeWidthSum { get; set; }
+        internal Element Left { get; set; }
+        internal Element Right { get; set; }
 
         internal override ISpacePlan Measure(Size availableSpace)
         {
-            var sizes = Children
-                .Select(x =>
-                {
-                    var space = GetTargetSize(x, availableSpace);
-                    return x.Child.Measure(space);
-                })
-                .ToList();
+            var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height)) as Size;
             
-            if (sizes.Any(x => x is Wrap))
+            if (leftMeasurement == null)
                 return new Wrap();
-
-            var height = sizes
-                .Where(x => x is Size)
-                .Cast<Size>()
-                .DefaultIfEmpty(Size.Zero)
-                .Max(x => x.Height);
             
-            if (sizes.All(x => x is FullRender))
-                return new FullRender(availableSpace.Width, height);
+            var rightMeasurement = Right.Measure(new Size(availableSpace.Width - leftMeasurement.Width, availableSpace.Height)) as Size;
+
+            if (rightMeasurement == null)
+                return new Wrap();
             
-            if (sizes.Any(x => x is PartialRender))
-                return new PartialRender(availableSpace.Width, height);
+            var totalWidth = leftMeasurement.Width + rightMeasurement.Width;
+            var totalHeight = Math.Max(leftMeasurement.Height, rightMeasurement.Height);
 
-            return new FullRender(Size.Zero);
+            var targetSize = new Size(totalWidth, totalHeight);
+
+            if (leftMeasurement is PartialRender || rightMeasurement is PartialRender)
+                return new PartialRender(targetSize);
+            
+            return new FullRender(targetSize);
         }
 
         internal override void Draw(ICanvas canvas, Size availableSpace)
         {
-            var targetSpace = Measure(availableSpace) as Size;
+            var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
+            var leftWidth = (leftMeasurement as Size)?.Width ?? 0;
+            
+            Left.Draw(canvas, new Size(leftWidth, availableSpace.Height));
+            
+            canvas.Translate(new Position(leftWidth, 0));
+            Right.Draw(canvas, new Size(availableSpace.Width - leftWidth, availableSpace.Height));
+            canvas.Translate(new Position(-leftWidth, 0));
+        }
+    }
+    
+    internal class Row : Element
+    {
+        public ICollection<RowElement> Children { get; internal set; } = new List<RowElement>();
+        public float Spacing { get; set; } = 0;
+        
+        public Element Compose(float availableWidth)
+        {
+            var elements = ReduceRows(AddSpacing(Children));
+            return BuildTree(elements.ToArray());
 
-            if (targetSpace == null)
-                return;
+            ICollection<Element> ReduceRows(ICollection<RowElement> elements)
+            {
+                var constantWidth = elements
+                    .Where(x => x is ConstantRowElement)
+                    .Cast<ConstantRowElement>()
+                    .Sum(x => x.Width);
             
-            var offset = 0f;
+                var relativeWidth = elements
+                    .Where(x => x is RelativeRowElement)
+                    .Cast<RelativeRowElement>()
+                    .Sum(x => x.Width);
 
-            foreach (var column in Children)
-            { 
-                var space = GetTargetSize(column, targetSpace);
+                var widthPerRelativeUnit = (availableWidth - constantWidth) / relativeWidth;
+                
+                return elements
+                    .Select(x =>
+                    {
+                        if (x is RelativeRowElement r)
+                            return new ConstantRowElement(r.Width * widthPerRelativeUnit)
+                            {
+                                Child = x.Child
+                            };
+                        
+                        return x;
+                    })
+                    .Select(x => new Constrained
+                    {
+                        MinWidth = x.Width,
+                        MaxWidth = x.Width,
+                        Child = x.Child
+                    })
+                    .Cast<Element>()
+                    .ToList();
+            }
+            
+            ICollection<RowElement> AddSpacing(ICollection<RowElement> elements)
+            {
+                if (Spacing < Size.Epsilon)
+                    return elements;
                 
-                canvas.Translate(new Position(offset, 0));
-                column.Child.Draw(canvas, space);
-                canvas.Translate(new Position(-offset, 0));
+                return elements
+                    .SelectMany(x => new[] { new ConstantRowElement(Spacing), x })
+                    .Skip(1)
+                    .ToList();
+            }
+
+            Element BuildTree(Span<Element> elements)
+            {
+                if (elements.IsEmpty)
+                    return Empty.Instance;
 
-                offset += space.Width;
+                if (elements.Length == 1)
+                    return elements[0];
+
+                var half = elements.Length / 2;
+                
+                return new SimpleRow
+                {
+                    Left = BuildTree(elements.Slice(0, half)),
+                    Right = BuildTree(elements.Slice(half))
+                };
             }
         }
 
-        private Size GetTargetSize(RowElement rowElement, Size availableSpace)
+        internal override ISpacePlan Measure(Size availableSpace)
         {
-            if (rowElement is ConstantRowElement)
-                return new Size(rowElement.Width, availableSpace.Height);
+            return Compose(availableSpace.Width).Measure(availableSpace);
+        }
 
-            ConstantWidthSum ??= Children
-                .Where(x => x is ConstantRowElement)
-                .Cast<ConstantRowElement>()
-                .Sum(x => x.Width);
-            
-            RelativeWidthSum ??= Children
-                .Where(x => x is RelativeRowElement)
-                .Cast<RelativeRowElement>()
-                .Sum(x => x.Width);
-            
-            return new Size((availableSpace.Width - ConstantWidthSum.Value) * rowElement.Width / RelativeWidthSum.Value, availableSpace.Height);
+        internal override void Draw(ICanvas canvas, Size availableSpace)
+        {
+            Compose(availableSpace.Width).Draw(canvas, availableSpace);
         }
     }
 }

+ 0 - 62
QuestPDF/Elements/SimpleDecoration.cs

@@ -1,62 +0,0 @@
-using System;
-using QuestPDF.Drawing.SpacePlan;
-using QuestPDF.Infrastructure;
-
-namespace QuestPDF.Elements
-{
-    internal enum DecorationType
-    {
-        Prepend,
-        Append
-    }
-    
-    internal class SimpleDecoration : Element
-    {
-        public Element DecorationElement { get; set; } = Empty.Instance;
-        public Element ContentElement { get; set; } = Empty.Instance;
-        public DecorationType Type { get; set; } 
-
-        internal override ISpacePlan Measure(Size availableSpace)
-        {
-            var decorationMeasure = DecorationElement?.Measure(availableSpace);
-            
-            if (decorationMeasure is Wrap || decorationMeasure is PartialRender)
-                return new Wrap();
-
-            var decorationSize = decorationMeasure as Size ?? Size.Zero;
-            var contentMeasure = ContentElement?.Measure(new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height)) ?? new FullRender(Size.Zero);
-            
-            if (contentMeasure is Wrap)
-                return new Wrap();
-
-            var contentSize = contentMeasure as Size ?? Size.Zero;
-            var resultSize = new Size(availableSpace.Width, decorationSize.Height + contentSize.Height);
-            
-            if (contentSize is PartialRender)
-                return new PartialRender(resultSize);
-            
-            if (contentSize is FullRender)
-                return new FullRender(resultSize);
-            
-            throw new NotSupportedException();
-        }
-
-        internal override void Draw(ICanvas canvas, Size availableSpace)
-        {
-            var decorationSize = DecorationElement?.Measure(availableSpace) as Size ?? Size.Zero;
-            var contentSize = new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height);
-
-            var translateHeight = Type == DecorationType.Prepend ? decorationSize.Height : contentSize.Height;
-            Action drawDecoration = () => DecorationElement?.Draw(canvas, new Size(availableSpace.Width, decorationSize.Height));
-            Action drawContent = () => ContentElement?.Draw(canvas, new Size (availableSpace.Width, contentSize.Height));
-
-            var first = Type == DecorationType.Prepend ? drawDecoration : drawContent;
-            var second = Type == DecorationType.Prepend ? drawContent : drawDecoration;
-
-            first();
-            canvas.Translate(new Position(0, translateHeight));
-            second();
-            canvas.Translate(new Position(0, -translateHeight));
-        }
-    }
-}

+ 0 - 47
QuestPDF/Elements/SimpleRow.cs

@@ -1,47 +0,0 @@
-using System;
-using QuestPDF.Drawing.SpacePlan;
-using QuestPDF.Infrastructure;
-
-namespace QuestPDF.Elements
-{
-    internal class SimpleRow : Element
-    {
-        internal Element Left { get; set; }
-        internal Element Right { get; set; }
-
-        internal override ISpacePlan Measure(Size availableSpace)
-        {
-            var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height)) as Size;
-            
-            if (leftMeasurement == null)
-                return new Wrap();
-            
-            var rightMeasurement = Right.Measure(new Size(availableSpace.Width - leftMeasurement.Width, availableSpace.Height)) as Size;
-
-            if (rightMeasurement == null)
-                return new Wrap();
-            
-            var totalWidth = leftMeasurement.Width + rightMeasurement.Width;
-            var totalHeight = Math.Max(leftMeasurement.Height, rightMeasurement.Height);
-
-            var targetSize = new Size(totalWidth, totalHeight);
-
-            if (leftMeasurement is PartialRender || rightMeasurement is PartialRender)
-                return new PartialRender(targetSize);
-            
-            return new FullRender(targetSize);
-        }
-
-        internal override void Draw(ICanvas canvas, Size availableSpace)
-        {
-            var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
-            var leftWidth = (leftMeasurement as Size)?.Width ?? 0;
-            
-            Left.Draw(canvas, new Size(leftWidth, availableSpace.Height));
-            
-            canvas.Translate(new Position(leftWidth, 0));
-            Right.Draw(canvas, new Size(availableSpace.Width - leftWidth, availableSpace.Height));
-            canvas.Translate(new Position(-leftWidth, 0));
-        }
-    }
-}

+ 0 - 71
QuestPDF/Elements/SimpleStack.cs

@@ -1,71 +0,0 @@
-using System;
-using QuestPDF.Drawing.SpacePlan;
-using QuestPDF.Infrastructure;
-
-namespace QuestPDF.Elements
-{
-    internal class SimpleStack : Element
-    {
-        internal Element Current { get; set; } = Empty.Instance;
-        internal Element Next { get; set; } = Empty.Instance;
-
-        private bool IsFirstRendered { get; set; } = false;
-        
-        internal override ISpacePlan Measure(Size availableSpace)
-        {
-            var firstElement = IsFirstRendered ? Empty.Instance : Current;
-            var firstSize = firstElement.Measure(availableSpace) as Size;
-
-            if (firstSize == null)
-                return new Wrap();
-            
-            if (firstSize is PartialRender partialRender)
-                return partialRender;
-                
-            var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstSize.Height);
-                
-            var secondSize = Next.Measure(spaceForSecond) as Size;
-
-            if (secondSize == null)
-                return new PartialRender(firstSize);
-
-            var totalWidth = Math.Max(firstSize.Width, secondSize.Width);
-            var totalHeight = firstSize.Height + secondSize.Height;
-
-            var targetSize = new Size(totalWidth, totalHeight);
-
-            if (secondSize is PartialRender)
-                return new PartialRender(targetSize);
-                
-            return new FullRender(targetSize);
-        }
-
-        internal override void Draw(ICanvas canvas, Size availableSpace)
-        {
-            var firstElement = IsFirstRendered ? Empty.Instance : Current;
-
-            var firstMeasurement = firstElement.Measure(availableSpace);
-
-            if (firstMeasurement is FullRender)
-                IsFirstRendered = true;
-
-            var firstSize = firstMeasurement as Size;
-
-            if (firstSize != null)
-                firstElement.Draw(canvas, firstSize);
-
-            var firstHeight = firstSize?.Height ?? 0;
-
-            var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstHeight);
-
-            var secondMeasurement = Next?.Measure(spaceForSecond);
-
-            if (secondMeasurement is Wrap)
-                return;
-
-            canvas.Translate(new Position(0, firstHeight));
-            Next.Draw(canvas, secondMeasurement as Size);
-            canvas.Translate(new Position(0, -firstHeight));
-        }
-    }
-}

+ 0 - 29
QuestPDF/Elements/SimpleStackCache.cs

@@ -1,29 +0,0 @@
-using System;
-using QuestPDF.Drawing.SpacePlan;
-using QuestPDF.Infrastructure;
-
-namespace QuestPDF.Elements
-{
-    internal class SimpleStackWithCache : SimpleStack
-    {
-        private ISpacePlan? Cache { get; set; }
-        private Size? CacheForSize { get; set; }
-        
-        internal override ISpacePlan Measure(Size availableSpace)
-        {
-            if (Cache != null && CacheForSize != null && CacheForSize.Equals(availableSpace))
-                return Cache;
-
-            CacheForSize = availableSpace;
-            Cache = base.Measure(availableSpace);
-            return Cache;
-        }
-        
-        internal override void Draw(ICanvas canvas, Size availableSpace)
-        {
-            base.Draw(canvas, availableSpace);
-            Cache = null;
-            CacheForSize = null;
-        }
-    }
-}

+ 98 - 53
QuestPDF/Elements/Stack.cs

@@ -1,81 +1,126 @@
+using System;
 using System.Collections.Generic;
+using System.ComponentModel;
 using System.Linq;
 using QuestPDF.Drawing.SpacePlan;
+using QuestPDF.Fluent;
 using QuestPDF.Infrastructure;
+using IComponent = QuestPDF.Infrastructure.IComponent;
+using IContainer = QuestPDF.Infrastructure.IContainer;
 
 namespace QuestPDF.Elements
 {
-    internal class Stack : Element
+    internal class SimpleStack : Element
     {
-        public ICollection<Element?> Children { get; internal set; } = new List<Element?>();
-        private Queue<Element?> ChildrenQueue { get; set; } = new Queue<Element?>();
-        
-        private void Initialize()
-        {
-            if (ChildrenQueue.Count == 0)
-                ChildrenQueue = new Queue<Element>(Children.Where(x => x != null));
-        }
+        internal Element First { get; set; } = Empty.Instance;
+        internal Element Second { get; set; } = Empty.Instance;
+
+        internal bool IsFirstRendered { get; set; } = false;
         
         internal override ISpacePlan Measure(Size availableSpace)
         {
-            Initialize();
+            var firstElement = IsFirstRendered ? Empty.Instance : First;
+            var firstSize = firstElement.Measure(availableSpace) as Size;
+
+            if (firstSize == null)
+                return new Wrap();
             
-            if(!ChildrenQueue.Any())
-                return new FullRender(Size.Zero);
+            if (firstSize is PartialRender partialRender)
+                return partialRender;
+                
+            var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstSize.Height);
+            var secondSize = Second.Measure(spaceForSecond) as Size;
 
-            var heightOnCurrentPage = 0f;
+            if (secondSize == null)
+                return new PartialRender(firstSize);
 
-            foreach (var renderer in ChildrenQueue)
-            {
-                var space = renderer.Measure(new Size(availableSpace.Width, availableSpace.Height - heightOnCurrentPage));
+            var totalWidth = Math.Max(firstSize.Width, secondSize.Width);
+            var totalHeight = firstSize.Height + secondSize.Height;
+            var targetSize = new Size(totalWidth, totalHeight);
 
-                if (space is Wrap)
-                {
-                    if (heightOnCurrentPage < Size.Epsilon)
-                        return new Wrap();
-                    
-                    return new PartialRender(availableSpace.Width, heightOnCurrentPage);
-                }
-
-                var size = space as Size;
-                heightOnCurrentPage += size.Height;
-
-                if (space is PartialRender)
-                    return new PartialRender(availableSpace.Width, heightOnCurrentPage);
-            }
-            
-            return new FullRender(availableSpace.Width, heightOnCurrentPage);
+            if (secondSize is PartialRender)
+                return new PartialRender(targetSize);
+                
+            return new FullRender(targetSize);
         }
 
         internal override void Draw(ICanvas canvas, Size availableSpace)
         {
-            Initialize();
+            var firstElement = IsFirstRendered ? Empty.Instance : First;
+
+            var firstMeasurement = firstElement.Measure(availableSpace);
+
+            if (firstMeasurement is FullRender)
+                IsFirstRendered = true;
+
+            var firstSize = firstMeasurement as Size;
+
+            if (firstSize != null)
+                firstElement.Draw(canvas, new Size(availableSpace.Width, firstSize.Height));
+
+            if (firstMeasurement is Wrap || firstMeasurement is PartialRender)
+                return;
+
+            var firstHeight = firstSize?.Height ?? 0;
+            var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstHeight);
+            var secondMeasurement = Second?.Measure(spaceForSecond) as Size;
+
+            if (secondMeasurement == null)
+                return;
+
+            canvas.Translate(new Position(0, firstHeight));
+            Second.Draw(canvas, new Size(availableSpace.Width, secondMeasurement.Height));
+            canvas.Translate(new Position(0, -firstHeight));
             
-            var topOffset = 0f;
+            if (secondMeasurement is FullRender)
+                IsFirstRendered = false;
+        }
+    }
+    
+    internal class Stack : IComponent
+    {
+        public ICollection<Element> Children { get; internal set; } = new List<Element>();
+        public float Spacing { get; set; } = 0;
+        
+        public void Compose(IContainer container)
+        {
+            var elements = AddSpacing(Spacing, Children);
 
-            while (ChildrenQueue.Any())
-            {
-                var child = ChildrenQueue.Peek();
-                
-                var restSpace = new Size(availableSpace.Width, availableSpace.Height - topOffset);
-                var space = child.Measure(restSpace);
+            container
+                .PaddingBottom(-Spacing)    
+                .Element(BuildTree(elements.ToArray()));
+        }
+        
+        static ICollection<Element> AddSpacing(float spacing, ICollection<Element> elements)
+        {
+            if (spacing < Size.Epsilon)
+                return elements;
                 
-                if (space is Wrap)
-                    break;
+            return elements
+                .Select(x => new Padding
+                {
+                    Bottom = spacing,
+                    Child = x
+                })
+                .Cast<Element>()
+                .ToList();
+        }
 
-                var size = space as Size;
-                
-                canvas.Translate(new Position(0, topOffset));
-                child.Draw(canvas, new Size(availableSpace.Width, size.Height));
-                canvas.Translate(new Position(0, -topOffset));
-                
-                topOffset += size.Height;
+        static Element BuildTree(Span<Element> elements)
+        {
+            if (elements.IsEmpty)
+                return Empty.Instance;
 
-                if (space is PartialRender)
-                    break;
+            if (elements.Length == 1)
+                return elements[0];
+
+            var half = elements.Length / 2;
                 
-                ChildrenQueue.Dequeue();
-            }
+            return new SimpleStack
+            {
+                First = BuildTree(elements.Slice(0, half)),
+                Second = BuildTree(elements.Slice(half))
+            };
         }
     }
 }

+ 0 - 94
QuestPDF/Elements/TreeRow.cs

@@ -1,94 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using QuestPDF.Drawing.SpacePlan;
-using QuestPDF.Fluent;
-using QuestPDF.Infrastructure;
-
-namespace QuestPDF.Elements
-{
-    internal class TreeRow : Element
-    {
-        public ICollection<RowElement> Children { get; internal set; } = new List<RowElement>();
-        public float Spacing { get; set; } = 0;
-        
-        public Element Compose(float availableWidth)
-        {
-            var elements = ReduceRows(AddSpacing(Children));
-            return BuildTree(elements.ToArray());
-
-            ICollection<Element> ReduceRows(ICollection<RowElement> elements)
-            {
-                var constantWidth = elements
-                    .Where(x => x is ConstantRowElement)
-                    .Cast<ConstantRowElement>()
-                    .Sum(x => x.Width);
-            
-                var relativeWidth = elements
-                    .Where(x => x is RelativeRowElement)
-                    .Cast<RelativeRowElement>()
-                    .Sum(x => x.Width);
-
-                var widthPerRelativeUnit = (availableWidth - constantWidth) / relativeWidth;
-                
-                return elements
-                    .Select(x =>
-                    {
-                        if (x is RelativeRowElement r)
-                            return new ConstantRowElement(r.Width * widthPerRelativeUnit)
-                            {
-                                Child = x.Child
-                            };
-                        
-                        return x;
-                    })
-                    .Select(x => new Constrained
-                    {
-                        MinWidth = x.Width,
-                        MaxWidth = x.Width,
-                        Child = x.Child
-                    })
-                    .Cast<Element>()
-                    .ToList();
-            }
-            
-            ICollection<RowElement> AddSpacing(ICollection<RowElement> elements)
-            {
-                if (Spacing < Size.Epsilon)
-                    return elements;
-                
-                return elements
-                    .SelectMany(x => new[] { new ConstantRowElement(Spacing), x })
-                    .Skip(1)
-                    .ToList();
-            }
-
-            Element BuildTree(Span<Element> elements)
-            {
-                if (elements.IsEmpty)
-                    return Empty.Instance;
-
-                if (elements.Length == 1)
-                    return elements[0];
-
-                var half = elements.Length / 2;
-                
-                return new SimpleRow
-                {
-                    Left = BuildTree(elements.Slice(0, half)),
-                    Right = BuildTree(elements.Slice(half))
-                };
-            }
-        }
-
-        internal override ISpacePlan Measure(Size availableSpace)
-        {
-            return Compose(availableSpace.Width).Measure(availableSpace);
-        }
-
-        internal override void Draw(ICanvas canvas, Size availableSpace)
-        {
-            Compose(availableSpace.Width).Draw(canvas, availableSpace);
-        }
-    }
-}

+ 0 - 58
QuestPDF/Elements/TreeStack.cs

@@ -1,58 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using QuestPDF.Fluent;
-using QuestPDF.Infrastructure;
-using IComponent = QuestPDF.Infrastructure.IComponent;
-using IContainer = QuestPDF.Infrastructure.IContainer;
-
-namespace QuestPDF.Elements
-{
-    internal class TreeStack : IComponent
-    {
-        public ICollection<Element> Children { get; internal set; } = new List<Element>();
-        public float Spacing { get; set; } = 0;
-        
-        public void Compose(IContainer container)
-        {
-            var elements = AddSpacing(Children);
-
-            container
-                .PaddingBottom(-Spacing)    
-                .Element(BuildTree(elements.ToArray()));
-
-            ICollection<Element> AddSpacing(ICollection<Element> elements)
-            {
-                if (Spacing < Size.Epsilon)
-                    return elements;
-                
-                return elements
-                    .Select(x => new Padding
-                    {
-                        Bottom = Spacing,
-                        Child = x
-                    })
-                    .Cast<Element>()
-                    .ToList();
-            }
-
-            Element BuildTree(Span<Element> elements)
-            {
-                if (elements.IsEmpty)
-                    return Empty.Instance;
-
-                if (elements.Length == 1)
-                    return elements[0];
-
-                var half = elements.Length / 2;
-                
-                return new SimpleStack
-                {
-                    Current = BuildTree(elements.Slice(0, half)),
-                    Next = BuildTree(elements.Slice(half))
-                };
-            }
-        }
-    }
-}

+ 1 - 1
QuestPDF/Fluent/SectionExtensions.cs → QuestPDF/Fluent/DecorationExtensions.cs

@@ -45,7 +45,7 @@ namespace QuestPDF.Fluent
         }
     }
     
-    public static class SectionExtensions
+    public static class DecorationExtensions
     {
         public static void Decoration(this IContainer element, Action<DecorationDescriptor> handler)
         {

+ 1 - 1
QuestPDF/Fluent/GridExtensions.cs

@@ -38,7 +38,7 @@ namespace QuestPDF.Fluent
         public void AlignCenter() => Alignment(HorizontalAlignment.Center);
         public void AlignRight() => Alignment(HorizontalAlignment.Right);
         
-        public IContainer Element(int columns = 1)
+        public IContainer Item(int columns = 1)
         {
             var container = new Container();
             

+ 0 - 15
QuestPDF/Fluent/PageExtensions.cs

@@ -14,11 +14,6 @@ namespace QuestPDF.Fluent
             Page.Header = container;
             return container;
         }
-
-        public void Header(Action<IContainer> handler)
-        {
-            handler?.Invoke(Header());
-        }
         
         public IContainer Content()
         {
@@ -27,22 +22,12 @@ namespace QuestPDF.Fluent
             return container;
         }
 
-        public void Content(Action<IContainer> handler)
-        {
-            handler?.Invoke(Content());
-        }
-        
         public IContainer Footer()
         {
             var container = new Container();
             Page.Footer = container;
             return container;
         }
-
-        public void Footer(Action<IContainer> handler)
-        {
-            handler?.Invoke(Footer());
-        }
     }
     
     public static class PageExtensions

+ 6 - 44
QuestPDF/Fluent/RowExtensions.cs

@@ -8,19 +8,18 @@ namespace QuestPDF.Fluent
 {
     public class RowDescriptor
     {
-        private List<RowElement> Elements { get; } = new List<RowElement>();
-        private float RowSpacing { get; set; } = 0;
-        
+        internal Row Row { get; } = new Row();
+
         public void Spacing(float value)
         {
-            RowSpacing = value;
+            Row.Spacing = value;
         }
         
         public IContainer ConstantColumn(float width)
         {
             var element = new ConstantRowElement(width);
             
-            Elements.Add(element);
+            Row.Children.Add(element);
             return element;
         }
         
@@ -28,45 +27,9 @@ namespace QuestPDF.Fluent
         {
             var element = new RelativeRowElement(width);
             
-            Elements.Add(element);
+            Row.Children.Add(element);
             return element;
         }
-        
-        internal Element CreateRow()
-        {
-            return new TreeRow
-            {
-                Children = Elements,
-                Spacing = RowSpacing
-            };
-        }
-        
-        internal Element CreateRow2()
-        {
-            if (Elements.Count == 0)
-                return Empty.Instance;
-            
-            if (RowSpacing <= Size.Epsilon)
-                return new Row
-                {
-                    Children = Elements
-                };
-
-            var children = Elements
-                .SelectMany(x => new[] {x, new ConstantRowElement(RowSpacing) })
-                .ToList();
-
-            var row = new Row
-            {
-                Children = children
-            };
-            
-            return new Padding
-            {
-                Right = -RowSpacing,
-                Child = row
-            };
-        }
     }
     
     public static class RowExtensions
@@ -75,8 +38,7 @@ namespace QuestPDF.Fluent
         {
             var descriptor = new RowDescriptor();
             handler(descriptor);
-            element.Element(descriptor.CreateRow());
-            //element.Element(descriptor.CreateRow2());
+            element.Element(descriptor.Row);
         }
     }
 }

+ 5 - 52
QuestPDF/Fluent/StackExtensions.cs

@@ -8,66 +8,19 @@ namespace QuestPDF.Fluent
 {
     public class StackDescriptor
     {
-        private List<Element> Items { get; } = new List<Element>();
-        private float StackSpacing { get; set; } = 0;
-        
+        internal Stack Stack { get; } = new Stack();
+
         public void Spacing(float value)
         {
-            StackSpacing = value;
+            Stack.Spacing = value;
         }
         
         public IContainer Item()
         {
             var container = new Container();
-            Items.Add(container);
+            Stack.Children.Add(container);
             return container;
         }
-        
-        public void Item(Action<IContainer> handler)
-        {
-            handler?.Invoke(Item());
-        }
-        
-        internal IComponent CreateStack()
-        {
-            return new TreeStack
-            {
-                Children = Items,
-                Spacing = StackSpacing
-            };
-        }
-        
-        internal Element CreateStack2()
-        {
-            if (Items.Count == 0)
-                return Empty.Instance;
-            
-            if (StackSpacing <= Size.Epsilon)
-                return new Stack
-                {
-                    Children = Items
-                };
-            
-            var children = Items
-                .Select(x => new Padding
-                {
-                    Bottom = StackSpacing,
-                    Child = x
-                })
-                .Cast<Element>()
-                .ToList();
-
-            var stack = new Stack
-            {
-                Children = children
-            };
-            
-            return new Padding
-            {
-                Bottom = -StackSpacing,
-                Child = stack
-            };
-        }
     }
     
     public static class StackExtensions
@@ -76,7 +29,7 @@ namespace QuestPDF.Fluent
         {
             var descriptor = new StackDescriptor();
             handler(descriptor);
-            element.Component(descriptor.CreateStack());
+            element.Component(descriptor.Stack);
         }
     }
 }