Browse Source

Merge branch 'main' into element-proxy

# Conflicts:
#	QuestPDF.ReportSample/Layouts/PhotoTemplate.cs
#	QuestPDF.ReportSample/Layouts/SectionTemplate.cs
#	QuestPDF.ReportSample/Tests.cs
#	QuestPDF/Drawing/DocumentGenerator.cs
#	QuestPDF/Elements/Constrained.cs
#	QuestPDF/Infrastructure/Position.cs
#	QuestPDF/Infrastructure/Size.cs
Marcin Ziąbek 4 years ago
parent
commit
9de87061e4
53 changed files with 1268 additions and 248 deletions
  1. 0 1
      QuestPDF.Examples/BarCode.cs
  2. 0 1
      QuestPDF.Examples/BarcodeExamples.cs
  3. 57 0
      QuestPDF.Examples/ComplexLayoutBenchmark.cs
  4. 48 0
      QuestPDF.Examples/DefaultTextStyleExample.cs
  5. 0 25
      QuestPDF.Examples/ElementExamples.cs
  6. 28 7
      QuestPDF.Examples/Engine/RenderingTest.cs
  7. 6 11
      QuestPDF.Examples/Engine/SimpleDocument.cs
  8. 53 0
      QuestPDF.Examples/EnsureSpaceExample.cs
  9. 0 1
      QuestPDF.Examples/FrameExample.cs
  10. 47 0
      QuestPDF.Examples/ImageExamples.cs
  11. 122 0
      QuestPDF.Examples/InlinedExamples.cs
  12. 0 1
      QuestPDF.Examples/LoremPicsumExample.cs
  13. 0 4
      QuestPDF.Examples/Padding.cs
  14. 3 0
      QuestPDF.Examples/QuestPDF.Examples.csproj
  15. 54 0
      QuestPDF.Examples/ShowOnceExample.cs
  16. 47 0
      QuestPDF.Examples/SkipOnceExample.cs
  17. 1 3
      QuestPDF.Examples/TextBenchmark.cs
  18. 1 7
      QuestPDF.Examples/TextExamples.cs
  19. BIN
      QuestPDF.Examples/logo.png
  20. 2 5
      QuestPDF.ReportSample/DataSource.cs
  21. 85 0
      QuestPDF.ReportSample/Layouts/DifferentHeadersTemplate.cs
  22. 20 0
      QuestPDF.ReportSample/Layouts/ImagePlaceholder.cs
  23. 0 31
      QuestPDF.ReportSample/Layouts/ImageTemplate.cs
  24. 9 9
      QuestPDF.ReportSample/Layouts/PhotoTemplate.cs
  25. 8 8
      QuestPDF.ReportSample/Layouts/SectionTemplate.cs
  26. 4 4
      QuestPDF.ReportSample/Layouts/StandardReport.cs
  27. 3 3
      QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs
  28. 2 0
      QuestPDF.ReportSample/Tests.cs
  29. 1 1
      QuestPDF.ReportSample/Typography.cs
  30. 88 0
      QuestPDF.UnitTests/ConstrainedTests.cs
  31. 1 1
      QuestPDF.UnitTests/DynamicImageTests.cs
  32. 32 1
      QuestPDF/Drawing/DocumentGenerator.cs
  33. 5 5
      QuestPDF/Drawing/FontManager.cs
  34. 12 12
      QuestPDF/Elements/Constrained.cs
  35. 4 5
      QuestPDF/Elements/Grid.cs
  36. 257 0
      QuestPDF/Elements/Inlined.cs
  37. 9 1
      QuestPDF/Elements/Page.cs
  38. 34 0
      QuestPDF/Elements/SkipOnce.cs
  39. 1 0
      QuestPDF/Elements/Text/Items/ITextBlockItem.cs
  40. 5 16
      QuestPDF/Elements/Text/Items/TextBlockExternalLink.cs
  41. 5 16
      QuestPDF/Elements/Text/Items/TextBlockInternalLink.cs
  42. 10 13
      QuestPDF/Elements/Text/Items/TextBlockPageNumber.cs
  43. 8 8
      QuestPDF/Elements/Text/Items/TextBlockSpan.cs
  44. 6 1
      QuestPDF/Fluent/ElementExtensions.cs
  45. 23 4
      QuestPDF/Fluent/ImageExtensions.cs
  46. 50 0
      QuestPDF/Fluent/InlinedExtensions.cs
  47. 10 0
      QuestPDF/Fluent/PageExtensions.cs
  48. 41 21
      QuestPDF/Fluent/TextExtensions.cs
  49. 1 1
      QuestPDF/Helpers/Placeholders.cs
  50. 3 3
      QuestPDF/Infrastructure/Size.cs
  51. 54 15
      QuestPDF/Infrastructure/TextStyle.cs
  52. 5 3
      QuestPDF/QuestPDF.csproj
  53. 3 0
      readme.md

+ 0 - 1
QuestPDF.Examples/BarCode.cs

@@ -18,7 +18,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(400, 100)
-                .FileName()
                 .ShowResults()
                 .Render(container =>
                 {

+ 0 - 1
QuestPDF.Examples/BarcodeExamples.cs

@@ -15,7 +15,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(300, 300)
-                .FileName()
                 .Render(container =>
                 {
                     container

+ 57 - 0
QuestPDF.Examples/ComplexLayoutBenchmark.cs

@@ -0,0 +1,57 @@
+using NUnit.Framework;
+using QuestPDF.Examples.Engine;
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Examples
+{
+    public class ComplexLayoutBenchmark
+    {
+        [Test]
+        public void ComplexLayout()
+        {
+            RenderingTest
+                .Create()
+                .PageSize(PageSizes.A4)
+                .ProducePdf()
+                .ShowResults()
+                .Render(x => x.Image(new byte[] { 1, 2, 3 }));
+                //.Render(x => GenerateStructure(x, 16));
+        }
+
+        private void GenerateStructure(IContainer container, int level)
+        {
+            if (level <= 0)
+            {
+                container.Background(Placeholders.BackgroundColor()).Height(10);
+                return;
+            }
+
+            level--;
+
+            if (level % 3 == 0)
+            {
+                container
+                    .Border(level / 10f)
+                    .BorderColor(Colors.Black)
+                    .Row(row =>
+                    {
+                        row.RelativeColumn().Element(x => GenerateStructure(x, level));
+                        row.RelativeColumn().Element(x => GenerateStructure(x, level));
+                    });
+            }
+            else
+            {
+                container
+                    .Border(level / 10f)
+                    .BorderColor(Colors.Black)
+                    .Stack(stack =>
+                    {
+                        stack.Item().Element(x => GenerateStructure(x, level));
+                        stack.Item().Element(x => GenerateStructure(x, level));
+                    });
+            }
+        }
+    }
+}

+ 48 - 0
QuestPDF.Examples/DefaultTextStyleExample.cs

@@ -0,0 +1,48 @@
+using NUnit.Framework;
+using QuestPDF.Examples.Engine;
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Examples
+{
+    public class DefaultTextStyleExample
+    {
+        [Test]
+        public void DefaultTextStyle()
+        {
+            RenderingTest
+                .Create()
+                .ProduceImages()
+                .ShowResults()
+                .RenderDocument(container =>
+                {
+                    container.Page(page =>
+                    {
+                        // all text in this set of pages has size 20
+                        page.DefaultTextStyle(TextStyle.Default.Size(20));
+                    
+                        page.Margin(20);
+                        page.Size(PageSizes.A4);
+                        page.Background(Colors.White);
+        
+                        page.Content().Stack(stack =>
+                        {
+                            stack.Item().Text(Placeholders.Sentence());
+                        
+                            stack.Item().Text(text =>
+                            {
+                                // text in this block is additionally semibold
+                                text.DefaultTextStyle(TextStyle.Default.SemiBold());
+        
+                                text.Line(Placeholders.Sentence());
+                            
+                                // this text has size 20 but also semibold and red
+                                text.Span(Placeholders.Sentence(), TextStyle.Default.Color(Colors.Red.Medium));
+                            });
+                        });
+                    });
+                });
+        }
+    }
+}

+ 0 - 25
QuestPDF.Examples/ElementExamples.cs

@@ -17,7 +17,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(200, 150)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -33,7 +32,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(300, 300)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -63,7 +61,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(740, 200)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -104,7 +101,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(740, 200)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -126,7 +122,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(500, 360)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -149,7 +144,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(210, 210)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -172,7 +166,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(300, 200)
-                .FileName()
                 .Render(container =>
                 {
                     var text = "";
@@ -195,7 +188,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(400, 230)
-                .FileName()
                 .Render(container =>
                 {
                     var textStyle = TextStyle.Default.Size(14);
@@ -230,7 +222,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(300, 200)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -260,7 +251,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(400, 250)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -336,7 +326,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(300, 300)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -381,7 +370,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(450, 150)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -414,7 +402,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(500, 175)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -441,7 +428,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(300, 300)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -481,7 +467,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(300, 150)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -502,7 +487,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(300, 175)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -554,7 +538,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(300, 200)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -582,7 +565,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(650, 450)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -621,7 +603,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(300, 300)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -648,7 +629,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(300, 300)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -680,7 +660,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(350, 350)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -722,7 +701,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(200, 200)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -748,7 +726,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(400, 350)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -785,7 +762,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(500, 225)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -839,7 +815,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(600, 310)
-                .FileName()
                 .Render(container =>
                 {
                     container

+ 28 - 7
QuestPDF.Examples/Engine/RenderingTest.cs

@@ -3,6 +3,7 @@ using System.Diagnostics;
 using System.Runtime.CompilerServices;
 using QuestPDF.Elements;
 using QuestPDF.Fluent;
+using QuestPDF.Helpers;
 using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Examples.Engine
@@ -17,6 +18,7 @@ namespace QuestPDF.Examples.Engine
     {
         private string FileNamePrefix = "test";
         private Size Size { get; set; }
+        private int? MaxPagesThreshold { get; set; }
         private bool ShowResult { get; set; }
         private RenderingTestResult ResultType { get; set; } = RenderingTestResult.Images;
         
@@ -25,12 +27,12 @@ namespace QuestPDF.Examples.Engine
             
         }
 
-        public static RenderingTest Create()
+        public static RenderingTest Create([CallerMemberName] string fileName = "test")
         {
-            return new RenderingTest();
+            return new RenderingTest().FileName(fileName);
         }
 
-        public RenderingTest FileName([CallerMemberName] string fileName = "test")
+        public RenderingTest FileName(string fileName)
         {
             FileNamePrefix = fileName;
             return this;
@@ -67,12 +69,31 @@ namespace QuestPDF.Examples.Engine
         
         public void Render(Action<IContainer> content)
         {
-            var container = new Container();
-            content(container);
+            RenderDocument(container =>
+            {
+                container.Page(page =>
+                {
+                    page.Size(new PageSize(Size.Width, Size.Height));
+                    page.Content().Container().Background(Colors.White).Element(content);
+                });
+            });
+        }
+        
+        public void MaxPages(int value)
+        {
+            MaxPagesThreshold = value;
+        }
 
-            var maxPages = ResultType == RenderingTestResult.Pdf ? 1000 : 10;
-            var document = new SimpleDocument(container, Size, maxPages);
+        public void RenderDocument(Action<IDocumentContainer> content)
+        {
+            MaxPagesThreshold ??= ResultType == RenderingTestResult.Pdf ? 1000 : 10;
+            var document = new SimpleDocument(content, MaxPagesThreshold.Value);
 
+            Render(document);
+        }
+        
+        private void Render(IDocument document)
+        {
             if (ResultType == RenderingTestResult.Images)
             {
                 Func<int, string> fileNameSchema = i => $"{FileNamePrefix}-${i}.png";

+ 6 - 11
QuestPDF.Examples/Engine/SimpleDocument.cs

@@ -1,4 +1,5 @@
-using QuestPDF.Drawing;
+using System;
+using QuestPDF.Drawing;
 using QuestPDF.Elements;
 using QuestPDF.Fluent;
 using QuestPDF.Helpers;
@@ -10,14 +11,12 @@ namespace QuestPDF.Examples.Engine
     {
         public const int ImageScalingFactor = 2;
         
-        private IContainer Container { get; }
-        private Size Size { get; }
+        private Action<IDocumentContainer> Content { get; }
         private int MaxPages { get; }
 
-        public SimpleDocument(IContainer container, Size size, int maxPages)
+        public SimpleDocument(Action<IDocumentContainer> content, int maxPages)
         {
-            Container = container;
-            Size = size;
+            Content = content;
             MaxPages = maxPages;
         }
         
@@ -32,11 +31,7 @@ namespace QuestPDF.Examples.Engine
         
         public void Compose(IDocumentContainer container)
         {
-            container.Page(page =>
-            {
-                page.Size(new PageSize(Size.Width, Size.Height));
-                page.Content().Container().Background(Colors.White).Element(Container as Container);
-            });
+            Content(container);
         }
     }
 }

+ 53 - 0
QuestPDF.Examples/EnsureSpaceExample.cs

@@ -0,0 +1,53 @@
+using NUnit.Framework;
+using QuestPDF.Examples.Engine;
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Examples
+{
+    public class EnsureSpaceExample
+    {
+        [Test]
+        public void EnsureSpaceWith()
+        {
+            RenderingTest
+                .Create()
+                .ProduceImages()
+                .ShowResults()
+                .RenderDocument(container =>
+                {
+                    container.Page(page =>
+                    {
+                        page.Margin(20);
+                        page.Size(PageSizes.A7.Landscape());
+                        page.Background(Colors.White);
+                        
+                        page.Header().Text("With ensure space", TextStyle.Default.SemiBold());
+                        
+                        page.Content().Stack(stack =>
+                        {
+                            stack
+                                .Item()
+                                .ExtendHorizontal()
+                                .Height(75)
+                                .Background(Colors.Grey.Lighten2);
+                            
+                            stack
+                                .Item()
+                                .EnsureSpace(100)
+                                .Text(Placeholders.LoremIpsum());
+                        });
+                        
+                        page.Footer().Text(text =>
+                        {
+                            text.Span("Page ");
+                            text.CurrentPageNumber();
+                            text.Span(" out of ");
+                            text.TotalPages();
+                        });
+                    });
+                });
+        }
+    }
+}

+ 0 - 1
QuestPDF.Examples/FrameExample.cs

@@ -28,7 +28,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(550, 400)
-                .FileName()
                 .ShowResults()
                 .Render(container =>
                 {

+ 47 - 0
QuestPDF.Examples/ImageExamples.cs

@@ -0,0 +1,47 @@
+using System.IO;
+using NUnit.Framework;
+using QuestPDF.Examples.Engine;
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+
+namespace QuestPDF.Examples
+{
+    public class ImageExamples
+    {
+        [Test]
+        public void LoadingImage()
+        {
+            RenderingTest
+                .Create()
+                .PageSize(PageSizes.A5)
+                .ProducePdf()
+                .ShowResults()
+                .Render(page =>
+                {
+                    page.Padding(25).Stack(stack =>
+                    {
+                        stack.Spacing(25);
+                        
+                        stack.Item().Image("logo.png");
+
+                        var binaryData = File.ReadAllBytes("logo.png");
+                        stack.Item().Image(binaryData);
+                        
+                        using var stream = new FileStream("logo.png", FileMode.Open);
+                        stack.Item().Image(stream);
+                    });
+                });
+        }
+        
+        [Test]
+        public void Exception()
+        {
+            RenderingTest
+                .Create()
+                .PageSize(PageSizes.A5)
+                .ProducePdf()
+                .ShowResults()
+                .Render(page => page.Image("non_existent.png"));
+        }
+    }
+}

+ 122 - 0
QuestPDF.Examples/InlinedExamples.cs

@@ -0,0 +1,122 @@
+using System;
+using System.Linq;
+using System.Reflection.Metadata.Ecma335;
+using NUnit.Framework;
+using QuestPDF.Examples.Engine;
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Examples
+{
+    public class InlinedExamples
+    {
+        [Test]
+        public void Inlined()
+        {
+            RenderingTest
+                .Create()
+                .PageSize(800, 650)
+                .ProduceImages()
+                .ShowResults()
+                .Render(container =>
+                {
+                    container
+                        .Padding(25)
+                        .Decoration(decoration =>
+                        {
+                            decoration.Header().Text(text =>
+                            {
+                                text.DefaultTextStyle(TextStyle.Default.Size(20));
+                                
+                                text.CurrentPageNumber();
+                                text.Span(" / ");
+                                text.TotalPages();
+                            });
+                            
+                            decoration
+                                .Content()
+                                .PaddingTop(25)
+                                //.Box()
+                                .Border(1)
+                                .Background(Colors.Grey.Lighten2)
+                                .Inlined(inlined =>
+                                {
+                                    inlined.Spacing(25);
+
+                                    inlined.AlignSpaceAround();
+                                    inlined.BaselineMiddle();
+
+                                    var random = new Random(123);
+
+                                    foreach (var _ in Enumerable.Range(0, 50))
+                                    {
+                                        var width = random.Next(2, 7);
+                                        var height = random.Next(2, 7);
+
+                                        var sizeText = $"{width}×{height}";
+                                        
+                                        inlined
+                                            .Item()
+                                            .Border(1)
+                                            .Width(width * 25)
+                                            .Height(height * 25)
+                                            .Background(Placeholders.BackgroundColor())
+                                            .Layers(layers =>
+                                            {
+                                                layers.Layer().Grid(grid =>
+                                                {
+                                                    grid.Columns(width);
+                                                    Enumerable.Range(0, width * height).ToList().ForEach(x => grid.Item().Border(1).BorderColor(Colors.White).Width(25).Height(25));
+                                                });
+                                                
+                                                layers
+                                                    .PrimaryLayer()
+                                                    .AlignCenter()
+                                                    .AlignMiddle()
+                                                    .Text(sizeText, TextStyle.Default.Size(15));
+                                            });
+                                    }
+                                });
+                        });
+                });
+        }
+        
+        [Test]
+        public void Inline_AlignLeft_BaselineBottom()
+        {
+            RenderingTest
+                .Create()
+                .PageSize(400, 250)
+                .ProduceImages()
+                .ShowResults()
+                .Render(container =>
+                {
+                    container
+                        .Padding(20)
+                        .Border(1)
+                        .Background(Colors.Grey.Lighten3)
+                        .Inlined(inlined =>
+                        {
+                            inlined.VerticalSpacing(50);
+                            inlined.HorizontalSpacing(20);
+                            inlined.AlignSpaceAround();
+                            inlined.BaselineTop();
+
+                            foreach (var _ in Enumerable.Range(0, 20))
+                                inlined.Item().Element(RandomBlock);
+                        });
+                });
+
+            void RandomBlock(IContainer container)
+            {
+                container
+                    .Width(Placeholders.Random.Next(1, 5) * 20)
+                    .Height(Placeholders.Random.Next(1, 5) * 20)
+                    .Border(1)
+                    .BorderColor(Colors.Grey.Darken2)
+                    .Background(Placeholders.BackgroundColor());
+            }
+        }
+    }
+}

+ 0 - 1
QuestPDF.Examples/LoremPicsumExample.cs

@@ -36,7 +36,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(350, 280)
-                .FileName()
                 .Render(container =>
                 {
                     container

+ 0 - 4
QuestPDF.Examples/Padding.cs

@@ -13,7 +13,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(300, 300)
-                .FileName()
                 .ShowResults()
                 .Render(container =>
                 {
@@ -37,7 +36,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(200, 150)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -62,7 +60,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(200, 150)
-                .FileName()
                 .Render(container =>
                 {
                     container
@@ -113,7 +110,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(200, 150)
-                .FileName()
                 .Render(container =>
                 {
                     container

+ 3 - 0
QuestPDF.Examples/QuestPDF.Examples.csproj

@@ -23,6 +23,9 @@
       <None Update="LibreBarcode39-Regular.ttf">
         <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       </None>
+      <None Update="logo.png">
+        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      </None>
     </ItemGroup>
 
 </Project>

+ 54 - 0
QuestPDF.Examples/ShowOnceExample.cs

@@ -0,0 +1,54 @@
+using NUnit.Framework;
+using QuestPDF.Examples.Engine;
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Examples
+{
+    public class ShowOnceExample
+    {
+        [Test]
+        public void ShowOnce()
+        {
+            RenderingTest
+                .Create()
+                .ProduceImages()
+                .ShowResults()
+                .RenderDocument(container =>
+                {
+                    container.Page(page =>
+                    {
+                        page.Margin(20);
+                        page.Size(PageSizes.A7.Landscape());
+                        page.Background(Colors.White);
+
+                        page.Header().Text("With show once", TextStyle.Default.SemiBold());
+                        
+                        page.Content().PaddingVertical(5).Row(row =>
+                        {
+                            row.RelativeColumn()
+                                .Background(Colors.Grey.Lighten2)
+                                .Border(1)
+                                .Padding(5)
+                                .ShowOnce()
+                                .Text(Placeholders.Label());
+                            
+                            row.RelativeColumn(2)
+                                .Border(1)
+                                .Padding(5)
+                                .Text(Placeholders.Paragraph());
+                        });
+                        
+                        page.Footer().Text(text =>
+                        {
+                            text.Span("Page ");
+                            text.CurrentPageNumber();
+                            text.Span(" out of ");
+                            text.TotalPages();
+                        });
+                    });
+                });
+        }
+    }
+}

+ 47 - 0
QuestPDF.Examples/SkipOnceExample.cs

@@ -0,0 +1,47 @@
+using NUnit.Framework;
+using QuestPDF.Examples.Engine;
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Examples
+{
+    public class SkipOnceExample
+    {
+        [Test]
+        public void SkipOnce()
+        {
+            RenderingTest
+                .Create()
+                .ProduceImages()
+                .ShowResults()
+                .RenderDocument(container =>
+                {
+                    container.Page(page =>
+                    {
+                        page.Margin(20);
+                        page.Size(PageSizes.A7.Landscape());
+                        page.Background(Colors.White);
+        
+                        page.Header().Stack(stack =>
+                        {
+                            stack.Item().ShowOnce().Text("This header is visible on the first page.");
+                            stack.Item().SkipOnce().Text("This header is visible on the second page and all following.");
+                        });
+                        
+                        page.Content()
+                            .PaddingVertical(10)
+                            .Text(Placeholders.Paragraphs(), TextStyle.Default.Color(Colors.Grey.Medium));
+                        
+                        page.Footer().Text(text =>
+                        {
+                            text.Span("Page ");
+                            text.CurrentPageNumber();
+                            text.Span(" out of ");
+                            text.TotalPages();
+                        });
+                    });
+                });
+        }
+    }
+}

+ 1 - 3
QuestPDF.Examples/TextBenchmark.cs

@@ -21,7 +21,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(PageSizes.A4)
-                .FileName()
                 .ProducePdf()
                 .ShowResults()
                 .Render(x => ComposeBook(x, chapters));
@@ -43,8 +42,7 @@ namespace QuestPDF.Examples
                 RenderingTest
                     .Create()
                     .PageSize(PageSizes.A4)
-                    .FileName()
-                    .ProducePdf()
+                        .ProducePdf()
                     .Render(x => ComposeBook(x, chapters));
             }
 

+ 1 - 7
QuestPDF.Examples/TextExamples.cs

@@ -17,7 +17,7 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(500, 300)
-                .FileName()
+                
                 .ProduceImages()
                 .ShowResults()
                 .Render(container =>
@@ -42,7 +42,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(500, 300)
-                .FileName()
                 .ProduceImages()
                 .ShowResults()
                 .Render(container =>
@@ -71,7 +70,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(500, 200)
-                .FileName()
                 .ProduceImages()
                 .ShowResults()
                 .Render(container =>
@@ -103,7 +101,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(PageSizes.A4)
-                .FileName()
                 .ProducePdf()
                 .ShowResults()
                 .Render(container =>
@@ -134,7 +131,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(PageSizes.A4)
-                .FileName()
                 .ProducePdf()
                 .ShowResults()
                 .Render(container =>
@@ -164,7 +160,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(PageSizes.A4)
-                .FileName()
                 .ProducePdf()
                 .ShowResults()
                 .Render(container =>
@@ -228,7 +223,6 @@ namespace QuestPDF.Examples
             RenderingTest
                 .Create()
                 .PageSize(PageSizes.A4)
-                .FileName()
                 .ProducePdf()
                 .ShowResults()
                 .Render(container =>

BIN
QuestPDF.Examples/logo.png


+ 2 - 5
QuestPDF.ReportSample/DataSource.cs

@@ -7,9 +7,6 @@ namespace QuestPDF.ReportSample
 {
     public static class DataSource
     {
-        public static int SectionCounter { get; set; }
-        public static int FieldCounter { get; set; }
-        
         public static ReportModel GetReport()
         {
             return new ReportModel
@@ -116,8 +113,8 @@ namespace QuestPDF.ReportSample
                     Date = DateTime.Now - TimeSpan.FromDays(Helpers.Random.NextDouble() * 100),
                     Location = Helpers.RandomLocation(),
 
-                    MapContextSource = x => Placeholders.Image(400, 300),
-                    MapDetailsSource = x => Placeholders.Image(400, 300)
+                    MapContextSource = Placeholders.Image,
+                    MapDetailsSource = Placeholders.Image
                 };
             }
         }

+ 85 - 0
QuestPDF.ReportSample/Layouts/DifferentHeadersTemplate.cs

@@ -0,0 +1,85 @@
+using QuestPDF.Drawing;
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.ReportSample.Layouts
+{
+    public class DifferentHeadersTemplate : IDocument
+    {
+        public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
+
+        public void Compose(IDocumentContainer container)
+        {
+            container
+                .Page(page =>
+                {
+                    page.Margin(40);
+
+                    page.Size(PageSizes.A4);
+
+                    page.Header().Element(ComposeHeader);
+                    page.Content().Element(ComposeContent);
+                    page.Footer().Element(ComposeFooter);
+                });
+        }
+
+        private void ComposeHeader(IContainer container)
+        {
+            container.Background(Colors.Grey.Lighten3).Border(1).Stack(stack =>
+            {
+                stack.Item().ShowOnce().Padding(5).AlignMiddle().Row(row =>
+                {
+                    row.RelativeColumn(2).AlignMiddle().Text("PRIMARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
+                    row.RelativeColumn(1).AlignRight().Box().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30);
+                });
+                stack.Item().SkipOnce().Padding(5).Row(row =>
+                {
+                    row.RelativeColumn(2).Text("SECONDARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
+                    row.RelativeColumn(1).AlignRight().Box().Background(Colors.Blue.Lighten4).Padding(15);
+                });
+            });
+        }
+
+        private void ComposeContent(IContainer container)
+        {
+            container.Stack(stack =>
+            {
+                stack.Item().PaddingVertical(80).Text("First");
+                stack.Item().PageBreak();
+                stack.Item().PaddingVertical(80).Text("Second");
+                stack.Item().PageBreak();
+                stack.Item().PaddingVertical(80).Text("Third");
+                stack.Item().PageBreak();
+            });
+        }
+
+        private void ComposeFooter(IContainer container)
+        {
+            container.Background(Colors.Grey.Lighten3).Stack(stack =>
+            {
+                stack.Item().ShowOnce().Background(Colors.Grey.Lighten3).Row(row =>
+                {
+                    row.RelativeColumn().Text(x =>
+                    {
+                        x.CurrentPageNumber();
+                        x.Span(" / ");
+                        x.TotalPages();
+                    });
+                    row.RelativeColumn().AlignRight().Text("Footer for header");
+                });
+
+                stack.Item().SkipOnce().Background(Colors.Grey.Lighten3).Row(row =>
+                {
+                    row.RelativeColumn().Text(x =>
+                    {
+                        x.CurrentPageNumber();
+                        x.Span(" / ");
+                        x.TotalPages();
+                    });
+                    row.RelativeColumn().AlignRight().Text("Footer for every page except header");
+                });
+            });
+        }
+    }
+}

+ 20 - 0
QuestPDF.ReportSample/Layouts/ImagePlaceholder.cs

@@ -0,0 +1,20 @@
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.ReportSample.Layouts
+{
+    public class ImagePlaceholder : IComponent
+    {
+        public static bool Solid { get; set; } = false;
+        
+        public void Compose(IContainer container)
+        {
+            if (Solid)
+                container.Background(Placeholders.Color());
+            
+            else
+                container.Image(Placeholders.Image);
+        }
+    }
+}

+ 0 - 31
QuestPDF.ReportSample/Layouts/ImageTemplate.cs

@@ -1,31 +0,0 @@
-using System;
-using QuestPDF.Fluent;
-using QuestPDF.Infrastructure;
-
-namespace QuestPDF.ReportSample.Layouts
-{
-    public class ImageTemplate : IComponent
-    {
-        private Func<Size, byte[]> Source { get; }
-        private float AspectRatio { get; }
-
-        public ImageTemplate(byte[] source, float aspectRatio = 1.333333f) : this(_ => source, aspectRatio)
-        {
-            
-        }
-        
-        public ImageTemplate(Func<Size, byte[]> source, float aspectRatio = 1.333333f)
-        {
-            Source = source;
-            AspectRatio = aspectRatio;
-        }
-
-        public void Compose(IContainer container)
-        {
-            container
-                .AspectRatio(AspectRatio)
-                .Background("#EEEEEE")
-                .Image(Source(Size.Zero));
-        }
-    }
-}

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

@@ -30,14 +30,14 @@ namespace QuestPDF.ReportSample.Layouts
             container
                 .Row(row =>
                 {
-                    row.RelativeColumn(2).AspectRatio(4 / 3f).Background(Colors.Grey.Lighten3);
+                    row.RelativeColumn(2).AspectRatio(4 / 3f).Component<ImagePlaceholder>();
 
                     row.RelativeColumn().PaddingLeft(5).Stack(stack =>
                     {
                         stack.Spacing(7f);
                         
-                        stack.Item().AspectRatio(4 / 3f).Background(Colors.Grey.Lighten3);
-                        stack.Item().AspectRatio(4 / 3f).Background(Colors.Grey.Lighten3);
+                        stack.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>();
+                        stack.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>();
                     });
                 });
         }
@@ -48,13 +48,13 @@ namespace QuestPDF.ReportSample.Layouts
             {
                 grid.Columns(6);
                 
-                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.Item().LabelCell().Text("Date");
+                grid.Item(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty);
+                grid.Item().LabelCell().Text("Location");
+                grid.Item(2).ValueCell().Text(Model.Location.Format());
                 
-                grid.Item().LabelCell().Text("Comments", Typography.Normal);
-                grid.Item(5).ValueCell().Text(Model.Comments, Typography.Normal);
+                grid.Item().LabelCell().Text("Comments");
+                grid.Item(5).ValueCell().Text(Model.Comments);
             });
         }
     }

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

@@ -30,11 +30,11 @@ namespace QuestPDF.ReportSample.Layouts
                         {
                             stack.Item().EnsureSpace(25).Row(row =>
                             {
-                                row.ConstantColumn(150).LabelCell().Text(part.Label, Typography.Normal);
+                                row.ConstantColumn(150).LabelCell().Text(part.Label);
                                 var frame = row.RelativeColumn().ValueCell();
                             
                                 if (part is ReportSectionText text)
-                                    frame.ShowEntire().Text(text.Text, Typography.Normal);
+                                    frame.ShowEntire().Text(text.Text);
                         
                                 if (part is ReportSectionMap map)
                                     frame.Element(x => MapElement(x, map));
@@ -51,7 +51,7 @@ namespace QuestPDF.ReportSample.Layouts
         {
             if (model.ImageSource == null || model.Location == null)
             {
-                container.Text("No location provided", Typography.Normal);
+                container.Text("No location provided");
                 return;
             }
 
@@ -59,25 +59,25 @@ namespace QuestPDF.ReportSample.Layouts
             {
                 stack.Spacing(5);
                 
-                stack.Item().MaxWidth(250).AspectRatio(4 / 3f).Background(Colors.Grey.Lighten3);
-                stack.Item().Text(model.Location.Format(), Typography.Normal);
+                stack.Item().MaxWidth(250).AspectRatio(4 / 3f).Component<ImagePlaceholder>();
+                stack.Item().Text(model.Location.Format());
             });
         }
         
         void PhotosElement(IContainer container, ReportSectionPhotos model)
         {
-            if (model.Photos.Count == 0) 
+            if (model.Photos.Count == 0)
             {
                 container.Text("No photos", Typography.Normal);
                 return;
             }
 
-            container.DebugArea("Photos").Grid(grid =>
+            container.Debug("Photos").Grid(grid =>
             {
                 grid.Spacing(5);
                 grid.Columns(3);
                 
-                model.Photos.ForEach(x => grid.Item().AspectRatio(4 / 3f).Background(Colors.Grey.Lighten3));
+                model.Photos.ForEach(x => grid.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>());
             });
         }
     }

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

@@ -27,6 +27,8 @@ namespace QuestPDF.ReportSample.Layouts
             container
                 .Page(page =>
                 {
+                    page.DefaultTextStyle(Typography.Normal);
+                    
                     page.MarginVertical(40);
                     page.MarginHorizontal(50);
                     
@@ -37,8 +39,6 @@ namespace QuestPDF.ReportSample.Layouts
                     
                     page.Footer().AlignCenter().Text(text =>
                     {
-                        text.DefaultTextStyle(Typography.Normal);
-                        
                         text.CurrentPageNumber();
                         text.Span(" / ");
                         text.TotalPages();
@@ -69,8 +69,8 @@ namespace QuestPDF.ReportSample.Layouts
                     {
                         grid.Item().Text(text =>
                         {
-                            text.Span($"{field.Label}: ", Typography.Normal.SemiBold());
-                            text.Span(field.Value, Typography.Normal);
+                            text.Span($"{field.Label}: ", TextStyle.Default.SemiBold());
+                            text.Span(field.Value);
                         });
                     }
                 });

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

@@ -41,9 +41,9 @@ namespace QuestPDF.ReportSample.Layouts
                 .InternalLink(locationName)
                 .Row(row =>
                 {
-                    row.ConstantColumn(25).Text($"{number}.", Typography.Normal);
-                    row.RelativeColumn().Text(locationName, Typography.Normal);
-                    row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName, Typography.Normal));
+                    row.ConstantColumn(25).Text($"{number}.");
+                    row.RelativeColumn().Text(locationName);
+                    row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName));
                 });
         }
     }

+ 2 - 0
QuestPDF.ReportSample/Tests.cs

@@ -33,6 +33,8 @@ namespace QuestPDF.ReportSample
         [Test] 
         public void Profile()
         {
+            ImagePlaceholder.Solid = true;
+            
             var container = new DocumentContainer();
             Report.Compose(container);
             var content = container.Compose();

+ 1 - 1
QuestPDF.ReportSample/Typography.cs

@@ -8,6 +8,6 @@ namespace QuestPDF.ReportSample
     {
         public static TextStyle Title => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Darken3).Size(26).Black();
         public static TextStyle Headline => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Medium).Size(16).SemiBold();
-        public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Black).Size(11).LineHeight(1.1f);
+        public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Verdana).Color(Colors.Black).Size(10).LineHeight(1.2f);
     }
 }

+ 88 - 0
QuestPDF.UnitTests/ConstrainedTests.cs

@@ -9,6 +9,8 @@ namespace QuestPDF.UnitTests
     [TestFixture]
     public class ConstrainedTests
     {
+        #region Height
+        
         [Test]
         public void Measure_MinHeight_ExpectWrap()
         {
@@ -88,5 +90,91 @@ namespace QuestPDF.UnitTests
                 .ExpectChildMeasure(new Size(400, 100), SpacePlan.Wrap())
                 .CheckMeasureResult(SpacePlan.Wrap());
         }
+        
+        #endregion
+        
+        #region Width
+        
+        [Test]
+        public void Measure_MinWidth_ExpectWrap()
+        {
+            TestPlan
+                .For(x => new Constrained
+                {
+                    MinWidth = 100
+                })
+                .MeasureElement(new Size(50, 400))
+                .CheckMeasureResult(new Wrap());
+        }
+        
+        [Test]
+        public void Measure_MinWidth_ExtendHeight()
+        {
+            TestPlan
+                .For(x => new Constrained
+                {
+                    MinWidth = 100,
+                    Child = x.CreateChild()
+                })
+                .MeasureElement(new Size(200, 400))
+                .ExpectChildMeasure(new Size(200, 400), new FullRender(50, 400))
+                .CheckMeasureResult(new FullRender(100, 400));
+        }
+        
+        [Test]
+        public void Measure_MinWidth_PassHeight()
+        {
+            TestPlan
+                .For(x => new Constrained
+                {
+                    MinWidth = 100,
+                    Child = x.CreateChild()
+                })
+                .MeasureElement(new Size(200, 400))
+                .ExpectChildMeasure(new Size(200, 400), new FullRender(150, 400))
+                .CheckMeasureResult(new FullRender(150, 400));
+        }
+        
+        [Test]
+        public void Measure_MaxWidth_Empty()
+        {
+            TestPlan
+                .For(x => new Constrained
+                {
+                    MaxWidth = 100
+                })
+                .MeasureElement(new Size(150, 400))
+                .CheckMeasureResult(new FullRender(0, 0));
+        }
+        
+        [Test]
+        public void Measure_MaxWidth_PartialRender()
+        {
+            TestPlan
+                .For(x => new Constrained
+                {
+                    MaxWidth = 100,
+                    Child = x.CreateChild()
+                })
+                .MeasureElement(new Size(200, 400))
+                .ExpectChildMeasure(new Size(100, 400), new PartialRender(75, 400))
+                .CheckMeasureResult(new PartialRender(75, 400));
+        }
+        
+        [Test]
+        public void Measure_MaxWidth_ExpectWrap()
+        {
+            TestPlan
+                .For(x => new Constrained
+                {
+                    MaxWidth = 100,
+                    Child = x.CreateChild()
+                })
+                .MeasureElement(new Size(200, 400))
+                .ExpectChildMeasure(new Size(100, 400), new Wrap())
+                .CheckMeasureResult(new Wrap());
+        }
+        
+        #endregion
     }
 }

+ 1 - 1
QuestPDF.UnitTests/DynamicImageTests.cs

@@ -66,7 +66,7 @@ namespace QuestPDF.UnitTests
                 .ExpectCanvasDrawImage(Position.Zero, new Size(400, 300))
                 .CheckDrawResult();
             
-            passedSize.Should().Be(new Size(400, 300));
+            passedSize.Should().BeEquivalentTo(new Size(400, 300));
         }
         
         byte[] GenerateImage(Size size)

+ 32 - 1
QuestPDF/Drawing/DocumentGenerator.cs

@@ -1,9 +1,12 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
+using System.Linq;
 using QuestPDF.Drawing.Exceptions;
 using QuestPDF.Drawing.Proxy;
 using QuestPDF.Elements;
+using QuestPDF.Elements.Text;
+using QuestPDF.Elements.Text.Items;
 using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Drawing
@@ -107,6 +110,12 @@ namespace QuestPDF.Drawing
                 {
                     ElementTrace = debuggingState?.BuildTrace() ?? "Debug trace is available only in the DEBUG mode."
                 };
+                throw new DocumentLayoutException(
+                    $"Composed layout generates infinite document. This may happen in two cases. " +
+                    $"1) Your document and its layout configuration is correct but the content takes more than {documentMetadata.DocumentLayoutExceptionThreshold} pages. " +
+                    $"In this case, please increase the value {nameof(DocumentMetadata)}.{nameof(DocumentMetadata.DocumentLayoutExceptionThreshold)} property configured in the {nameof(IDocument.GetMetadata)} method. " +
+                    $"2) The layout configuration of your document is invalid. Some of the elements require more space than is provided." +
+                    $"Please analyze your documents structure to detect this element and fix its size constraints.");
             }
         }
 
@@ -130,5 +139,27 @@ namespace QuestPDF.Drawing
 
             return debuggingState;
         }
+
+        internal static void ApplyDefaultTextStyle(this Element content, TextStyle documentDefaultTextStyle)
+        {
+            documentDefaultTextStyle.ApplyGlobalStyle(TextStyle.LibraryDefault);
+            
+            content.HandleVisitor(element =>
+            {
+                var text = element as TextBlock;
+                
+                if (text == null)
+                    return;
+
+                foreach (var child in text.Children)
+                {
+                    if (child is TextBlockSpan textSpan)
+                        textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
+
+                    if (child is TextBlockElement textElement)
+                        ApplyDefaultTextStyle(textElement.Element, documentDefaultTextStyle);
+                }
+            });
+        }
     }
-}
+}

+ 5 - 5
QuestPDF/Drawing/FontManager.cs

@@ -33,7 +33,7 @@ namespace QuestPDF.Drawing
         
         internal static SKPaint ToPaint(this TextStyle style)
         {
-            return Paints.GetOrAdd(style.ToString(), key => Convert(style));
+            return Paints.GetOrAdd(style.Key, key => Convert(style));
             
             static SKPaint Convert(TextStyle style)
             {
@@ -41,7 +41,7 @@ namespace QuestPDF.Drawing
                 {
                     Color = SKColor.Parse(style.Color),
                     Typeface = GetTypeface(style),
-                    TextSize = style.Size,
+                    TextSize = (style.Size ?? 12),
                     TextEncoding = SKTextEncoding.Utf32
                 };
             }
@@ -51,16 +51,16 @@ namespace QuestPDF.Drawing
                 if (Typefaces.TryGetValue(style.FontType, out var result))
                     return result;
                 
-                var slant = style.IsItalic ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
+                var slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
                 
-                return SKTypeface.FromFamilyName(style.FontType, (int)style.FontWeight, (int)SKFontStyleWidth.Normal, slant) 
+                return SKTypeface.FromFamilyName(style.FontType, (int)(style.FontWeight ?? FontWeight.Normal), (int)SKFontStyleWidth.Normal, slant) 
                        ?? throw new ArgumentException($"The typeface {style.FontType} could not be found.");
             }
         }
 
         internal static SKFontMetrics ToFontMetrics(this TextStyle style)
         {
-            return FontMetrics.GetOrAdd(style.ToString(), key => style.ToPaint().FontMetrics);
+            return FontMetrics.GetOrAdd(style.Key, key => style.ToPaint().FontMetrics);
         }
     }
 }

+ 12 - 12
QuestPDF/Elements/Constrained.cs

@@ -22,8 +22,8 @@ namespace QuestPDF.Elements
                 return SpacePlan.Wrap();
             
             var available = new Size(
-                LimitMin(availableSpace.Width, MaxWidth),
-                LimitMin(availableSpace.Height, MaxHeight));
+                Min(MaxWidth, availableSpace.Width),
+                Min(MaxHeight, availableSpace.Height));
 
             var measurement = base.Measure(available);
 
@@ -31,8 +31,8 @@ namespace QuestPDF.Elements
                 return SpacePlan.Wrap();
             
             var actualSize = new Size(
-                LimitMax(measurement.Width, MinWidth),
-                LimitMax(measurement.Height, MinHeight));
+                Max(MinWidth, measurement.Width),
+                Max(MinHeight, measurement.Height));
             
             if (measurement.Type == SpacePlanType.FullRender)
                 return SpacePlan.FullRender(actualSize);
@@ -46,20 +46,20 @@ namespace QuestPDF.Elements
         internal override void Draw(Size availableSpace)
         {
             var available = new Size(
-                LimitMin(availableSpace.Width, MaxWidth),
-                LimitMin(availableSpace.Height, MaxHeight));
+                Min(MaxWidth, availableSpace.Width),
+                Min(MaxHeight, availableSpace.Height));
             
-            base.Draw(available);
+            Child?.Draw(available);
         }
-
-        private float LimitMin(float value, float? limit)
+        
+        private static float Min(float? x, float y)
         {
-            return limit.HasValue ? Math.Min(value, limit.Value) : value;
+            return x.HasValue ? Math.Min(x.Value, y) : y; 
         }
         
-        private float LimitMax(float value, float? limit)
+        private static float Max(float? x, float y)
         {
-            return limit.HasValue ? Math.Max(value, limit.Value) : value;
+            return x.HasValue ? Math.Max(x.Value, y) : y;
         }
     }
 }

+ 4 - 5
QuestPDF/Elements/Grid.cs

@@ -2,7 +2,6 @@
 using System.Linq;
 using QuestPDF.Fluent;
 using QuestPDF.Infrastructure;
-using static QuestPDF.Infrastructure.HorizontalAlignment;
 
 namespace QuestPDF.Elements
 {
@@ -20,7 +19,7 @@ namespace QuestPDF.Elements
         public Queue<GridElement> ChildrenQueue { get; set; } = new Queue<GridElement>();
         public int ColumnsCount { get; set; } = DefaultColumnsCount;
 
-        public HorizontalAlignment Alignment { get; set; } = Left;
+        public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
         public float VerticalSpacing { get; set; } = 0;
         public float HorizontalSpacing { get; set; } = 0;
         
@@ -62,15 +61,15 @@ namespace QuestPDF.Elements
             var emptySpace = ColumnsCount - columnsWidth;
             var hasEmptySpace = emptySpace >= Size.Epsilon;
 
-            if (Alignment == Center)
+            if (Alignment == HorizontalAlignment.Center)
                 emptySpace /= 2;
             
-            if (hasEmptySpace && Alignment != Left)
+            if (hasEmptySpace && Alignment != HorizontalAlignment.Left)
                 row.RelativeColumn(emptySpace);
                 
             elements.ForEach(x => row.RelativeColumn(x.Columns).Element(x.Child));
 
-            if (hasEmptySpace && Alignment != Right)
+            if (hasEmptySpace && Alignment != HorizontalAlignment.Right)
                 row.RelativeColumn(emptySpace);
         }
     }

+ 257 - 0
QuestPDF/Elements/Inlined.cs

@@ -0,0 +1,257 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using QuestPDF.Drawing.SpacePlan;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Elements
+{
+    internal class InlinedElement : Container
+    {
+        public ISpacePlan? MeasureCache { get; set; }
+
+        internal override ISpacePlan Measure(Size availableSpace)
+        {
+            // TODO: once element caching proxy is introduces, this can be removed
+            
+            MeasureCache ??= Child.Measure(Size.Max);
+            return MeasureCache;
+        }
+    }
+
+    internal enum InlinedAlignment
+    {
+        Left,
+        Center,
+        Right,
+        Justify,
+        SpaceAround
+    }
+    
+    internal class Inlined : Element, IStateResettable
+    {
+        public List<InlinedElement> Elements { get; internal set; } = new List<InlinedElement>();
+        private Queue<InlinedElement> ChildrenQueue { get; set; }
+
+        internal float VerticalSpacing { get; set; }
+        internal float HorizontalSpacing { get; set; }
+        
+        internal InlinedAlignment ElementsAlignment { get; set; }
+        internal VerticalAlignment BaselineAlignment { get; set; }
+        
+        public void ResetState()
+        {
+            ChildrenQueue = new Queue<InlinedElement>(Elements);
+        }
+        
+        internal override void HandleVisitor(Action<Element?> visit)
+        {
+            Elements.ForEach(x => x.HandleVisitor(visit));
+            base.HandleVisitor(visit);
+        }
+
+        internal override ISpacePlan Measure(Size availableSpace)
+        {
+            if (!ChildrenQueue.Any())
+                return new FullRender(Size.Zero);
+            
+            var lines = Compose(availableSpace);
+
+            if (!lines.Any())
+                return new Wrap();
+
+            var lineSizes = lines
+                .Select(line =>
+                {
+                    var size = GetLineSize(line);
+                    var heightWithSpacing = size.Height + (line.Count - 1) * HorizontalSpacing;
+                    return new Size(size.Width, heightWithSpacing);
+                })
+                .ToList();
+            
+            var width = lineSizes.Max(x => x.Width);
+            var height = lineSizes.Sum(x => x.Height) + (lines.Count - 1) * VerticalSpacing;
+            var targetSize = new Size(width, height);
+
+            var isPartiallyRendered = lines.Sum(x => x.Count) != ChildrenQueue.Count;
+
+            if (isPartiallyRendered)
+                return new PartialRender(targetSize);
+            
+            return new FullRender(targetSize);
+        }
+
+        internal override void Draw(Size availableSpace)
+        {
+            var lines = Compose(availableSpace);
+            var topOffset = 0f;
+            
+            foreach (var line in lines)
+            {
+                var height = line.Select(x => x.Measure(Size.Max) as Size).Where(x => x != null).Max(x => x.Height);
+                DrawLine(line);
+
+                topOffset += height + VerticalSpacing;
+                Canvas.Translate(new Position(0, height + VerticalSpacing));
+            }
+            
+            Canvas.Translate(new Position(0, -topOffset));
+            lines.SelectMany(x => x).ToList().ForEach(x => ChildrenQueue.Dequeue());
+
+            void DrawLine(ICollection<InlinedElement> elements)
+            {
+                var lineSize = GetLineSize(elements);
+
+                var elementOffset = ElementOffset();
+                var leftOffset = AlignOffset();
+                Canvas.Translate(new Position(leftOffset, 0));
+                
+                foreach (var element in elements)
+                {
+                    var size = element.Measure(Size.Max) as Size;
+                    var baselineOffset = BaselineOffset(size, lineSize.Height);
+                    
+                    Canvas.Translate(new Position(0, baselineOffset));
+                    element.Draw(size);
+                    Canvas.Translate(new Position(0, -baselineOffset));
+
+                    leftOffset += size.Width + elementOffset;
+                    Canvas.Translate(new Position(size.Width + elementOffset, 0));
+                }
+                
+                Canvas.Translate(new Position(-leftOffset, 0));
+
+                float ElementOffset()
+                {
+                    var difference = availableSpace.Width - lineSize.Width;
+
+                    if (elements.Count == 1)
+                        return 0;
+
+                    if (ElementsAlignment == InlinedAlignment.Justify)
+                        return difference / (elements.Count - 1);
+                    
+                    if (ElementsAlignment == InlinedAlignment.SpaceAround)
+                        return difference / (elements.Count + 1);
+                    
+                    return HorizontalSpacing;
+                }
+
+                float AlignOffset()
+                {
+                    if (ElementsAlignment == InlinedAlignment.Left)
+                        return 0;
+                    
+                    if (ElementsAlignment == InlinedAlignment.Justify)
+                        return 0;
+                    
+                    if (ElementsAlignment == InlinedAlignment.SpaceAround)
+                        return elementOffset;
+
+                    var difference = availableSpace.Width - lineSize.Width - (elements.Count - 1) * HorizontalSpacing;
+                    
+                    if (ElementsAlignment == InlinedAlignment.Center)
+                        return difference / 2;
+
+                    if (ElementsAlignment == InlinedAlignment.Right)
+                        return difference;
+
+                    return 0;
+                }
+                
+                float BaselineOffset(Size elementSize, float lineHeight)
+                {
+                    if (BaselineAlignment == VerticalAlignment.Top)
+                        return 0;
+
+                    var difference = lineHeight - elementSize.Height;
+                    
+                    if (BaselineAlignment == VerticalAlignment.Middle)
+                        return difference / 2;
+
+                    return difference;
+                }
+            }
+        }
+
+        Size GetLineSize(ICollection<InlinedElement> elements)
+        {
+            var sizes = elements
+                .Select(x => x.Measure(Size.Max) as Size)
+                .Where(x => x != null)
+                .ToList();
+            
+            var width = sizes.Sum(x => x.Width);
+            var height = sizes.Max(x => x.Height);
+
+            return new Size(width, height);
+        }
+        
+        // list of lines, each line is a list of elements
+        private ICollection<ICollection<InlinedElement>> Compose(Size availableSize)
+        {
+            var queue = new Queue<InlinedElement>(ChildrenQueue);
+            var result = new List<ICollection<InlinedElement>>();
+
+            var topOffset = 0f;
+            
+            while (true)
+            {
+                var line = GetNextLine();
+                
+                if (!line.Any())
+                    break;
+
+                var height = line
+                    .Select(x => x.Measure(availableSize) as Size)
+                    .Where(x => x != null)
+                    .Max(x => x.Height);
+                
+                if (topOffset + height > availableSize.Height + Size.Epsilon)
+                    break;
+
+                topOffset += height + VerticalSpacing;
+                result.Add(line);
+            }
+
+            return result;
+
+            ICollection<InlinedElement> GetNextLine()
+            {
+                var result = new List<InlinedElement>();
+                var leftOffset = GetInitialAlignmentOffset();
+                
+                while (true)
+                {
+                    if (!queue.Any())
+                        break;
+                    
+                    var element = queue.Peek();
+                    var size = element.Measure(Size.Max) as Size;
+                    
+                    if (size == null)
+                        break;
+                    
+                    if (leftOffset + size.Width > availableSize.Width + Size.Epsilon)
+                        break;
+
+                    queue.Dequeue();
+                    leftOffset += size.Width + HorizontalSpacing;
+                    result.Add(element);    
+                }
+
+                return result;
+            }
+
+            float GetInitialAlignmentOffset()
+            {
+                // this method makes sure that the spacing between elements is no lesser than configured
+                
+                if (ElementsAlignment == InlinedAlignment.SpaceAround)
+                    return HorizontalSpacing * 2;
+
+                return 0;
+            }
+        }
+    }
+}

+ 9 - 1
QuestPDF/Elements/Page.cs

@@ -1,4 +1,5 @@
 using System;
+using QuestPDF.Drawing;
 using QuestPDF.Fluent;
 using QuestPDF.Helpers;
 using QuestPDF.Infrastructure;
@@ -7,6 +8,8 @@ namespace QuestPDF.Elements
 {
     internal class Page : IComponent
     {
+        public TextStyle DefaultTextStyle { get; set; } = new TextStyle();
+        
         public Size MinSize { get; set; } = PageSizes.A4;
         public Size MaxSize { get; set; } = PageSizes.A4;
 
@@ -15,6 +18,8 @@ namespace QuestPDF.Elements
         public float MarginTop { get; set; }
         public float MarginBottom { get; set; }
 
+        public string BackgroundColor { get; set; } = Colors.Transparent;
+        
         public Element Header { get; set; } = Empty.Instance;
         public Element Content { get; set; } = Empty.Instance;
         public Element Footer { get; set; } = Empty.Instance;
@@ -22,12 +27,13 @@ namespace QuestPDF.Elements
         public void Compose(IContainer container)
         {
             container
-
                 .MinWidth(MinSize.Width)
                 .MinHeight(MinSize.Height)
                 
                 .MaxWidth(MaxSize.Width)
                 .MaxHeight(MaxSize.Height)
+                
+                .Background(BackgroundColor)
      
                 .PaddingLeft(MarginLeft)
                 .PaddingRight(MarginRight)
@@ -54,6 +60,8 @@ namespace QuestPDF.Elements
                         .Element(Footer);
                 });
 
+            (container as Element).ApplyDefaultTextStyle(DefaultTextStyle);
+            
             bool IsClose(float x, float y)
             {
                 return Math.Abs(x - y) < Size.Epsilon;

+ 34 - 0
QuestPDF/Elements/SkipOnce.cs

@@ -0,0 +1,34 @@
+using QuestPDF.Drawing.SpacePlan;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Elements
+{
+    internal class SkipOnce : ContainerElement, IStateResettable
+    {
+        private bool FirstPageWasSkipped { get; set; }
+
+        public void ResetState()
+        {
+            FirstPageWasSkipped = false;
+        }
+
+        internal override ISpacePlan Measure(Size availableSpace)
+        {
+            if (Child == null || !FirstPageWasSkipped)
+                return new FullRender(Size.Zero);
+
+            return Child.Measure(availableSpace);
+        }
+
+        internal override void Draw(Size availableSpace)
+        {
+            if (Child == null)
+                return;
+
+            if (FirstPageWasSkipped)
+                Child.Draw(availableSpace);
+
+            FirstPageWasSkipped = true;
+        }
+    }
+}

+ 1 - 0
QuestPDF/Elements/Text/Items/ITextBlockItem.cs

@@ -1,4 +1,5 @@
 using QuestPDF.Elements.Text.Calculation;
+using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Elements.Text.Items
 {

+ 5 - 16
QuestPDF/Elements/Text/Items/TextBlockExternalLink.cs

@@ -3,33 +3,22 @@ using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Elements.Text.Items
 {
-    internal class TextBlockExternalLink : ITextBlockItem
+    internal class TextBlockExternalLink : TextBlockSpan
     {
-        public TextStyle Style { get; set; } = new TextStyle();
-        public string Text { get; set; }
         public string Url { get; set; }
         
-        public TextMeasurementResult? Measure(TextMeasurementRequest request)
+        public override TextMeasurementResult? Measure(TextMeasurementRequest request)
         {
-            return GetItem().MeasureWithoutCache(request);
+            return MeasureWithoutCache(request);
         }
 
-        public void Draw(TextDrawingRequest request)
+        public override void Draw(TextDrawingRequest request)
         {
             request.Canvas.Translate(new Position(0, request.TotalAscent));
             request.Canvas.DrawExternalLink(Url, new Size(request.TextSize.Width, request.TextSize.Height));
             request.Canvas.Translate(new Position(0, -request.TotalAscent));
             
-            GetItem().Draw(request);
-        }
-
-        private TextBlockSpan GetItem()
-        {
-            return new TextBlockSpan
-            {
-                Style = Style,
-                Text = Text
-            };
+            base.Draw(request);
         }
     }
 }

+ 5 - 16
QuestPDF/Elements/Text/Items/TextBlockInternalLink.cs

@@ -3,33 +3,22 @@ using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Elements.Text.Items
 {
-    internal class TextBlockInternalLink : ITextBlockItem
+    internal class TextBlockInternalLink : TextBlockSpan
     {
-        public TextStyle Style { get; set; } = new TextStyle();
-        public string Text { get; set; }
         public string LocationName { get; set; }
         
-        public TextMeasurementResult? Measure(TextMeasurementRequest request)
+        public override TextMeasurementResult? Measure(TextMeasurementRequest request)
         {
-            return GetItem().MeasureWithoutCache(request);
+            return MeasureWithoutCache(request);
         }
 
-        public void Draw(TextDrawingRequest request)
+        public override void Draw(TextDrawingRequest request)
         {
             request.Canvas.Translate(new Position(0, request.TotalAscent));
             request.Canvas.DrawLocationLink(LocationName, new Size(request.TextSize.Width, request.TextSize.Height));
             request.Canvas.Translate(new Position(0, -request.TotalAscent));
             
-            GetItem().Draw(request);
-        }
-
-        private TextBlockSpan GetItem()
-        {
-            return new TextBlockSpan
-            {
-                Style = Style,
-                Text = Text
-            };
+            base.Draw(request);
         }
     }
 }

+ 10 - 13
QuestPDF/Elements/Text/Items/TextBlockPageNumber.cs

@@ -3,34 +3,31 @@ using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Elements.Text.Items
 {
-    internal class TextBlockPageNumber : ITextBlockItem
+    internal class TextBlockPageNumber : TextBlockSpan
     {
-        public TextStyle Style { get; set; } = new TextStyle();
         public string SlotName { get; set; }
         
-        public TextMeasurementResult? Measure(TextMeasurementRequest request)
+        public override TextMeasurementResult? Measure(TextMeasurementRequest request)
         {
-            return GetItem(request.PageContext).MeasureWithoutCache(request);
+            SetPageNumber(request.PageContext);
+            return MeasureWithoutCache(request);
         }
 
-        public void Draw(TextDrawingRequest request)
+        public override void Draw(TextDrawingRequest request)
         {
-            GetItem(request.PageContext).Draw(request);
+            SetPageNumber(request.PageContext);
+            base.Draw(request);
         }
 
-        private TextBlockSpan GetItem(IPageContext context)
+        private void SetPageNumber(IPageContext context)
         {
             var pageNumberPlaceholder = 123;
             
             var pageNumber = context.GetRegisteredLocations().Contains(SlotName)
                 ? context.GetLocationPage(SlotName)
                 : pageNumberPlaceholder;
-            
-            return new TextBlockSpan
-            {
-                Style = Style,
-                Text = pageNumber.ToString()
-            };
+
+            Text = pageNumber.ToString();
         }
     }
 }

+ 8 - 8
QuestPDF/Elements/Text/Items/TextBlockSpan.cs

@@ -14,7 +14,7 @@ namespace QuestPDF.Elements.Text.Items
         private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache =
             new Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?>();
 
-        public TextMeasurementResult? Measure(TextMeasurementRequest request)
+        public virtual TextMeasurementResult? Measure(TextMeasurementRequest request)
         {
             var cacheKey = (request.StartIndex, request.AvailableWidth);
             
@@ -45,7 +45,7 @@ namespace QuestPDF.Elements.Text.Items
                 {
                     Width = 0,
                     
-                    LineHeight = Style.LineHeight,
+                    LineHeight = Style.LineHeight ?? 1,
                     Ascent = fontMetrics.Ascent,
                     Descent = fontMetrics.Descent
                 };
@@ -96,7 +96,7 @@ namespace QuestPDF.Elements.Text.Items
                 Ascent = fontMetrics.Ascent,
                 Descent = fontMetrics.Descent,
      
-                LineHeight = Style.LineHeight,
+                LineHeight = Style.LineHeight ?? 1,
                 
                 StartIndex = startIndex,
                 EndIndex = endIndex,
@@ -105,7 +105,7 @@ namespace QuestPDF.Elements.Text.Items
             };
         }
         
-        public void Draw(TextDrawingRequest request)
+        public virtual void Draw(TextDrawingRequest request)
         {
             var fontMetrics = Style.ToFontMetrics();
 
@@ -115,12 +115,12 @@ namespace QuestPDF.Elements.Text.Items
             request.Canvas.DrawText(text, Position.Zero, Style);
 
             // draw underline
-            if (Style.HasUnderline && fontMetrics.UnderlinePosition.HasValue)
-                DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness.Value);
+            if ((Style.HasUnderline ?? false) && fontMetrics.UnderlinePosition.HasValue)
+                DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness ?? 1);
             
             // draw stroke
-            if (Style.HasStrikethrough && fontMetrics.StrikeoutPosition.HasValue)
-                DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness.Value);
+            if ((Style.HasStrikethrough ?? false) && fontMetrics.StrikeoutPosition.HasValue)
+                DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness ?? 1);
 
             void DrawLine(float offset, float thickness)
             {

+ 6 - 1
QuestPDF/Fluent/ElementExtensions.cs

@@ -70,7 +70,12 @@ namespace QuestPDF.Fluent
         {
             return element.Element(new ShowOnce());
         }
-        
+
+        public static IContainer SkipOnce(this IContainer element)
+        {
+            return element.Element(new SkipOnce());
+        }
+
         public static IContainer ShowEntire(this IContainer element)
         {
             return element.Element(new ShowEntire());

+ 23 - 4
QuestPDF/Fluent/ImageExtensions.cs

@@ -1,4 +1,6 @@
 using System;
+using System.IO;
+using QuestPDF.Drawing.Exceptions;
 using QuestPDF.Elements;
 using QuestPDF.Infrastructure;
 using SkiaSharp;
@@ -7,12 +9,29 @@ namespace QuestPDF.Fluent
 {
     public static class ImageExtensions
     {
-        public static void Image(this IContainer parent, byte[] data, ImageScaling scaling = ImageScaling.FitWidth)
+        public static void Image(this IContainer parent, byte[] imageData, ImageScaling scaling = ImageScaling.FitWidth)
         {
-            if (data == null)
-                return;
+            var image = SKImage.FromEncodedData(imageData);
+            parent.Image(image, scaling);
+        }
+        
+        public static void Image(this IContainer parent, string filePath, ImageScaling scaling = ImageScaling.FitWidth)
+        {
+            var image = SKImage.FromEncodedData(filePath);
+            parent.Image(image, scaling);
+        }
+        
+        public static void Image(this IContainer parent, Stream fileStream, ImageScaling scaling = ImageScaling.FitWidth)
+        {
+            var image = SKImage.FromEncodedData(fileStream);
+            parent.Image(image, scaling);
+        }
+        
+        private static void Image(this IContainer parent, SKImage image, ImageScaling scaling = ImageScaling.FitWidth)
+        {
+            if (image == null)
+                throw new DocumentComposeException("Cannot load or decode provided image.");
             
-            var image = SKImage.FromEncodedData(data);
             var aspectRatio = image.Width / (float)image.Height;
             
             var imageElement = new Image

+ 50 - 0
QuestPDF/Fluent/InlinedExtensions.cs

@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using QuestPDF.Elements;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Fluent
+{
+    public class InlinedDescriptor
+    {
+        internal Inlined Inlined { get; } = new Inlined();
+        
+        public void Spacing(float value)
+        {
+            VerticalSpacing(value);
+            HorizontalSpacing(value);
+        }
+        
+        public void VerticalSpacing(float value) => Inlined.VerticalSpacing = value;
+        public void HorizontalSpacing(float value) => Inlined.HorizontalSpacing = value;
+
+        public void BaselineTop() => Inlined.BaselineAlignment = VerticalAlignment.Top;
+        public void BaselineMiddle() => Inlined.BaselineAlignment = VerticalAlignment.Middle;
+        public void BaselineBottom() => Inlined.BaselineAlignment = VerticalAlignment.Bottom;
+
+        public void AlignLeft() => Inlined.ElementsAlignment = InlinedAlignment.Left;
+        public void AlignCenter() => Inlined.ElementsAlignment = InlinedAlignment.Center;
+        public void AlignRight() => Inlined.ElementsAlignment = InlinedAlignment.Right;
+        public void AlignJustify() => Inlined.ElementsAlignment = InlinedAlignment.Justify;
+        public void AlignSpaceAround() => Inlined.ElementsAlignment = InlinedAlignment.SpaceAround;
+        
+        public IContainer Item()
+        {
+            var container = new InlinedElement();
+            Inlined.Elements.Add(container);
+            return container;
+        }
+    }
+    
+    public static class InlinedExtensions
+    {
+        public static void Inlined(this IContainer element, Action<InlinedDescriptor> handler)
+        {
+            var descriptor = new InlinedDescriptor();
+            handler(descriptor);
+            
+            element.Element(descriptor.Inlined);
+        }
+    }
+}

+ 10 - 0
QuestPDF/Fluent/PageExtensions.cs

@@ -70,6 +70,16 @@ namespace QuestPDF.Fluent
             MarginHorizontal(value);
         }
         
+        public void DefaultTextStyle(TextStyle textStyle)
+        {
+            Page.DefaultTextStyle = textStyle;
+        }
+        
+        public void Background(string color)
+        {
+            Page.BackgroundColor = color;
+        }
+        
         public IContainer Header()
         {
             var container = new Container();

+ 41 - 21
QuestPDF/Fluent/TextExtensions.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using QuestPDF.Drawing;
 using QuestPDF.Elements;
 using QuestPDF.Elements.Text;
 using QuestPDF.Elements.Text.Items;
@@ -13,7 +14,7 @@ namespace QuestPDF.Fluent
     {
         private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>();
         private TextStyle DefaultStyle { get; set; } = TextStyle.Default;
-        private HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
+        internal HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
         private float Spacing { get; set; } = 0f;
 
         public void DefaultTextStyle(TextStyle style)
@@ -49,9 +50,12 @@ namespace QuestPDF.Fluent
             TextBlocks.Last().Children.Add(item);
         }
         
-        public void Span(string text, TextStyle? style = null)
+        public void Span(string? text, TextStyle? style = null)
         {
-            style ??= DefaultStyle;
+            if (IsNullOrEmpty(text))
+                return;
+            
+            style ??= TextStyle.Default;
  
             var items = text
                 .Replace("\r", string.Empty)
@@ -75,16 +79,12 @@ namespace QuestPDF.Fluent
                 .ForEach(TextBlocks.Add);
         }
 
-        public void Line(string text, TextStyle? style = null)
+        public void Line(string? text, TextStyle? style = null)
         {
+            text ??= string.Empty;
             Span(text + Environment.NewLine, style);
         }
-        
-        public void Line(string text)
-        {
-            Span(text + Environment.NewLine);
-        }
-        
+
         public void EmptyLine()
         {
             Span(Environment.NewLine);
@@ -92,7 +92,10 @@ namespace QuestPDF.Fluent
 
         private void PageNumber(string slotName, TextStyle? style = null)
         {
-            style ??= DefaultStyle;
+            if (IsNullOrEmpty(slotName))
+                throw new ArgumentException(nameof(slotName));
+            
+            style ??= TextStyle.Default;
             
             AddItemToLastTextBlock(new TextBlockPageNumber()
             {
@@ -113,12 +116,21 @@ namespace QuestPDF.Fluent
         
         public void PageNumberOfLocation(string locationName, TextStyle? style = null)
         {
+            if (IsNullOrEmpty(locationName))
+                throw new ArgumentException(nameof(locationName));
+            
             PageNumber(locationName, style);
         }
         
-        public void InternalLocation(string text, string locationName, TextStyle? style = null)
+        public void InternalLocation(string? text, string locationName, TextStyle? style = null)
         {
-            style ??= DefaultStyle;
+            if (IsNullOrEmpty(text))
+                return;
+            
+            if (IsNullOrEmpty(locationName))
+                throw new ArgumentException(nameof(locationName));
+
+            style ??= TextStyle.Default;
             
             AddItemToLastTextBlock(new TextBlockInternalLink
             {
@@ -128,9 +140,15 @@ namespace QuestPDF.Fluent
             });
         }
         
-        public void ExternalLocation(string text, string url, TextStyle? style = null)
+        public void ExternalLocation(string? text, string url, TextStyle? style = null)
         {
-            style ??= DefaultStyle;
+            if (IsNullOrEmpty(text))
+                return;
+            
+            if (IsNullOrEmpty(url))
+                throw new ArgumentException(nameof(url));
+            
+            style ??= TextStyle.Default;
             
             AddItemToLastTextBlock(new TextBlockExternalLink
             {
@@ -156,6 +174,9 @@ namespace QuestPDF.Fluent
         {
             TextBlocks.ToList().ForEach(x => x.Alignment = Alignment);
             
+            foreach (var textBlockSpan in TextBlocks.SelectMany(x => x.Children).Where(x => x is TextBlockSpan).Cast<TextBlockSpan>())
+                textBlockSpan.Style.ApplyParentStyle(DefaultStyle);
+
             container.Stack(stack =>
             {
                 stack.Spacing(Spacing);
@@ -170,19 +191,18 @@ namespace QuestPDF.Fluent
     {
         public static void Text(this IContainer element, Action<TextDescriptor> content)
         {
-            var textBlock = new TextBlock();
-
+            var descriptor = new TextDescriptor();
+            
             if (element is Alignment alignment)
-                textBlock.Alignment = alignment.Horizontal;
+                descriptor.Alignment = alignment.Horizontal;
             
-            var descriptor = new TextDescriptor();
             content?.Invoke(descriptor);
             descriptor.Compose(element);
         }
         
-        public static void Text(this IContainer element, object text, TextStyle? style = null)
+        public static void Text(this IContainer element, object? text, TextStyle? style = null)
         {
-            element.Text(x => x.Span(text.ToString(), style));
+            element.Text(x => x.Span(text?.ToString(), style));
         }
     }
 }

+ 1 - 1
QuestPDF/Helpers/Placeholders.cs

@@ -7,7 +7,7 @@ namespace QuestPDF.Helpers
 {
     public static class Placeholders
     {
-        private static Random Random = new Random();
+        public static readonly Random Random = new Random();
         
         #region Word Cache
 

+ 3 - 3
QuestPDF/Infrastructure/Size.cs

@@ -7,8 +7,8 @@
         public readonly float Width;
         public readonly float Height;
         
-        public static Size Zero => new Size(0, 0);
-        public static Size Max => new Size(14_400, 14_400);
+        public static Size Zero { get; } = new Size(0, 0);
+        public static Size Max { get; } = new Size(14_400, 14_400);
 
         public Size(float width, float height)
         {
@@ -16,6 +16,6 @@
             Height = height;
         }
         
-        public override string ToString() => $"{Width:N2} {Height:N2}";
+        public override string ToString() => $"(W: {Width}, H: {Height})";
     }
 }

+ 54 - 15
QuestPDF/Infrastructure/TextStyle.cs

@@ -1,29 +1,68 @@
-using QuestPDF.Helpers;
+using System;
+using QuestPDF.Helpers;
 
 namespace QuestPDF.Infrastructure
 {
     public class TextStyle
     {
-        internal string Color { get; set; } = Colors.Black;
-        internal string BackgroundColor { get; set; } = Colors.Transparent;
-        internal string FontType { get; set; } = "Calibri";
-        internal float Size { get; set; } = 12;
-        internal float LineHeight { get; set; } = 1.2f;
-        internal FontWeight FontWeight { get; set; } = FontWeight.Normal;
-        internal bool IsItalic { get; set; } = false;
-        internal bool HasStrikethrough { get; set; } = false;
-        internal bool HasUnderline { get; set; } = false;
+        internal bool HasGlobalStyleApplied { get; private set; }
+        
+        internal string? Color { get; set; }
+        internal string? BackgroundColor { get; set; }
+        internal string? FontType { get; set; }
+        internal float? Size { get; set; }
+        internal float? LineHeight { get; set; }
+        internal FontWeight? FontWeight { get; set; }
+        internal bool? IsItalic { get; set; }
+        internal bool? HasStrikethrough { get; set; }
+        internal bool? HasUnderline { get; set; }
+
+        internal string? Key { get; private set; }
+        
+        internal static TextStyle LibraryDefault => new TextStyle
+        {
+            Color = Colors.Black,
+            BackgroundColor = Colors.Transparent,
+            FontType = Fonts.Calibri,
+            Size = 12,
+            LineHeight = 1.2f,
+            FontWeight = Infrastructure.FontWeight.Normal,
+            IsItalic = false,
+            HasStrikethrough = false,
+            HasUnderline = false
+        };
 
         public static TextStyle Default => new TextStyle();
+        
+        internal void ApplyGlobalStyle(TextStyle globalStyle)
+        {
+            if (HasGlobalStyleApplied)
+                return;
+            
+            HasGlobalStyleApplied = true;
 
-        private string? KeyCache { get; set; }
+            ApplyParentStyle(globalStyle);
+            Key ??= $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{FontWeight}|{IsItalic}|{HasStrikethrough}|{HasUnderline}";
+        }
         
-        public override string ToString()
+        internal void ApplyParentStyle(TextStyle parentStyle)
         {
-            KeyCache ??= $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{FontWeight}|{IsItalic}|{HasStrikethrough}|{HasUnderline}";
-            return KeyCache;
+            Color ??= parentStyle.Color;
+            BackgroundColor ??= parentStyle.BackgroundColor;
+            FontType ??= parentStyle.FontType;
+            Size ??= parentStyle.Size;
+            LineHeight ??= parentStyle.LineHeight;
+            FontWeight ??= parentStyle.FontWeight;
+            IsItalic ??= parentStyle.IsItalic;
+            HasStrikethrough ??= parentStyle.HasStrikethrough;
+            HasUnderline ??= parentStyle.HasUnderline;
         }
 
-        internal TextStyle Clone() => (TextStyle)MemberwiseClone();
+        internal TextStyle Clone()
+        {
+            var clone = (TextStyle)MemberwiseClone();
+            clone.HasGlobalStyleApplied = false;
+            return clone;
+        }
     }
 }

+ 5 - 3
QuestPDF/QuestPDF.csproj

@@ -4,9 +4,9 @@
         <Authors>MarcinZiabek</Authors>
         <Company>CodeFlint</Company>
         <PackageId>QuestPDF</PackageId>
-        <Version>2021.10.1</Version>
+        <Version>2021.11.0-beta3</Version>
         <PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
-        <PackageReleaseNotes>Enhanced text rendering capabilities. Improved rendering performance.</PackageReleaseNotes>
+        <PackageReleaseNotes>Implemented new elements: SkipOnce and Inlined. Added possibility to define global, page-wide test style. Improved exception handling experience.</PackageReleaseNotes>
         <LangVersion>8</LangVersion>
         <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
         <PackageIcon>Logo.png</PackageIcon>
@@ -15,10 +15,12 @@
         <RepositoryUrl>https://github.com/QuestPDF/library.git</RepositoryUrl>
         <RepositoryType>git</RepositoryType>
         <Copyright>Marcin Ziąbek, QuestPDF contributors</Copyright>
-        <PackageTags>pdf file export generate generation tool create creation render portable document format quest html library converter open source free standard core</PackageTags>
+        <PackageTags>pdf report file export generate generation tool create creation render portable document format quest html library converter open source free standard core</PackageTags>
         <PackageLicenseExpression>MIT</PackageLicenseExpression>
         <Nullable>enable</Nullable>
         <TargetFrameworks>net462;netstandard2.0;netcoreapp2.0;netcoreapp3.0</TargetFrameworks>
+        <IncludeSymbols>true</IncludeSymbols>
+        <SymbolPackageFormat>snupkg</SymbolPackageFormat>
     </PropertyGroup>
 
     <ItemGroup>

+ 3 - 0
readme.md

@@ -37,8 +37,11 @@ The library is available as a nuget package. You can install it as any other nug
 ## Documentation
 
 **[Release notes and roadmap](https://www.questpdf.com/documentation/releases.html)** - everything that is planned for future library iterations, description of new features and information about potential breaking changes.
+
 **[Getting started tutorial](https://www.questpdf.com/documentation/getting-started.html)** - a short and easy to follow tutorial showing how to design an invoice document under 200 lines of code.
+
 **[API Reference](https://www.questpdf.com/documentation/api-reference.html)** - a detailed description of behavior of all available components and how to use them with C# Fluent API.
+
 **[Patterns and practices](https://www.questpdf.com/documentation/patterns-and-practices.html#document-metadata)** - everything that may help you designing great reports and reusable code that is easy to maintain.
 
 ## Example invoice