Browse Source

New elements: rotate, scale, translate

Marcin Ziąbek 4 years ago
parent
commit
14f11232be

+ 525 - 203
QuestPDF.Examples/ElementExamples.cs

@@ -1,4 +1,6 @@
+using System;
 using System.Linq;
+using NUnit.Framework;
 using QuestPDF.Examples.Engine;
 using QuestPDF.Fluent;
 using QuestPDF.Helpers;
@@ -7,215 +9,379 @@ using SkiaSharp;
 
 namespace QuestPDF.Examples
 {
-    public class ElementExamples : ExampleTestBase
+    [TestFixture]
+    public class ElementExamples
     {
-        //[ShowResult]
-        [ImageSize(200, 150)]
-        public void Placeholder(IContainer container)
+        [Test]
+        public void Placeholder()
         {
-            container
-                .Background("#FFF")
-                .Padding(25)
-                .Placeholder();
+            RenderingTest
+                .Create()
+                .PageSize(200, 150)
+                .Render(container =>
+                {
+                    container
+                        .Background("#FFF")
+                        .Padding(25)
+                        .Placeholder();
+                });
         }
         
-        //[ShowResult]
-        [ImageSize(300, 300)]
-        public void Decoration(IContainer container)
+        [Test]
+        public void Decoration()
         {
-            container
-                .Background("#FFF")
-                .Padding(25)
-                .Decoration(decoration =>
+            RenderingTest
+                .Create()
+                .PageSize(300, 300)
+                .Render(container =>
                 {
-                    decoration
-                        .Header()
-                        .Background("#888")
-                        .Padding(10)
-                        .Text("Notes", TextStyle.Default.Size(16).Color("#FFF"));
+                    container
+                        .Background("#FFF")
+                        .Padding(25)
+                        .Decoration(decoration =>
+                        {
+                            decoration
+                                .Header()
+                                .Background(Colors.Grey.Medium)
+                                .Padding(10)
+                                .Text("Notes", TextStyle.Default.Size(16).Color("#FFF"));
                     
-                    decoration
-                        .Content()
-                        .Background("#DDD")
-                        .Padding(10)
-                        .ExtendVertical()
-                        .Text(Helpers.Placeholders.LoremIpsum());
+                            decoration
+                                .Content()
+                                .Background(Colors.Grey.Lighten3)
+                                .Padding(10)
+                                .ExtendVertical()
+                                .Text(Helpers.Placeholders.LoremIpsum());
+                        });
                 });
         }
         
-        //[ShowResult]
-        [ImageSize(298, 421)]
-        public void Page(IContainer container)
+        [Test]
+        public void Page()
         {
-            container
-                .Background("#FFF")
-                .Padding(15)
-                .Page(page =>
+            RenderingTest
+                .Create()
+                .PageSize(298, 421)
+                .Render(container =>
                 {
-                    page.Header()
-                        .Height(60)
-                        .Background("#BBB")
-                        .AlignCenter()
-                        .AlignMiddle()
-                        .Text("Header");
+                    container
+                        .Background("#FFF")
+                        .Padding(15)
+                        .Page(page =>
+                        {
+                            page.Header()
+                                .Height(60)
+                                .Background(Colors.Grey.Lighten1)
+                                .AlignCenter()
+                                .AlignMiddle()
+                                .Text("Header");
                     
-                    page.Content()
-                        .Background("#DDD")
-                        .AlignCenter()
-                        .AlignMiddle()
-                        .Text("Content");
+                            page.Content()
+                                .Background(Colors.Grey.Lighten2)
+                                .AlignCenter()
+                                .AlignMiddle()
+                                .Text("Content");
                         
-                    page.Footer()
-                        .Height(30)
-                        .Background("#BBB")
-                        .AlignCenter()
-                        .AlignMiddle()
-                        .Text("Footer");
+                            page.Footer()
+                                .Height(30)
+                                .Background(Colors.Grey.Lighten1)
+                                .AlignCenter()
+                                .AlignMiddle()
+                                .Text("Footer");
+                        });
                 });
         }
         
-        //[ShowResult]
-        [ImageSize(740, 200)]
-        public void Row(IContainer container)
+        [Test]
+        public void Row()
         {
-            container
-                .Background("#FFF")
-                .Padding(20)
-                .Stack(stack =>
+            RenderingTest
+                .Create()
+                .PageSize(740, 200)
+                .Render(container =>
                 {
-                    stack.Item()
-                        .PaddingBottom(10)
-                        .AlignCenter()
-                        .Text("This Row element is 700pt wide");
+                    container
+                        .Background("#FFF")
+                        .Padding(20)
+                        .Stack(stack =>
+                        {
+                            stack.Item()
+                                .PaddingBottom(10)
+                                .AlignCenter()
+                                .Text("This Row element is 700pt wide");
 
-                    stack.Item().Row(row =>
-                    {
-                        row.ConstantColumn(100)
-                            .Background("#DDD")
-                            .Padding(10)
-                            .ExtendVertical()
-                            .Text("This column is 100 pt wide");
+                            stack.Item().Row(row =>
+                            {
+                                row.ConstantColumn(100)
+                                    .Background(Colors.Grey.Lighten1)
+                                    .Padding(10)
+                                    .ExtendVertical()
+                                    .Text("This column is 100 pt wide");
 
-                        row.RelativeColumn()
-                            .Background("#BBB")
-                            .Padding(10)
-                            .Text("This column takes 1/3 of the available space (200pt)");
+                                row.RelativeColumn()
+                                    .Background(Colors.Grey.Lighten2)
+                                    .Padding(10)
+                                    .Text("This column takes 1/3 of the available space (200pt)");
 
-                        row.RelativeColumn(2)
-                            .Background("#DDD")
-                            .Padding(10)
-                            .Text("This column takes 2/3 of the available space (400pt)");
-                    });
+                                row.RelativeColumn(2)
+                                    .Background(Colors.Grey.Lighten3)
+                                    .Padding(10)
+                                    .Text("This column takes 2/3 of the available space (400pt)");
+                            });
+                        });
                 });
         }
         
-        //[ShowResult]
-        [ImageSize(500, 350)]
-        public void Column(IContainer container)
+        [Test]
+        public void RowSpacing()
         {
-            container
-                .Background("#FFF")
-                .Padding(15)
-                .Stack(column =>
+            RenderingTest
+                .Create()
+                .PageSize(740, 200)
+                .Render(container =>
                 {
-                    column.Spacing(10);
-                    
-                    column
-                        .Item()
-                        .Background("#999")
-                        .Height(50);
-                    
-                    column
-                        .Item()
-                        .Background("#BBB")
-                        .Height(100);
+                    container
+                        .Background("#FFF")
+                        .Padding(20)
+                        .Row(row =>
+                        {
+                            row.Spacing(20);
+                            row.RelativeColumn(2).Border(1).Background(Colors.Grey.Lighten1);
+                            row.RelativeColumn(3).Border(1).Background(Colors.Grey.Lighten2);
+                            row.RelativeColumn(4).Border(1).Background(Colors.Grey.Lighten3);
+                        });
+                });
+        }
+    
+        [Test]
+        public void Stack()
+        {
+            RenderingTest
+                .Create()
+                .PageSize(500, 360)
+                .Render(container =>
+                {
+                    container
+                        .Background("#FFF")
+                        .Padding(15)
+                        .Stack(stack =>
+                        {
+                            stack.Spacing(15);
                     
-                    column
-                        .Item()
-                        .Background("#DDD")
-                        .Height(150);
+                            stack.Item().Background(Colors.Grey.Medium).Height(50);
+                            stack.Item().Background(Colors.Grey.Lighten1).Height(100);
+                            stack.Item().Background(Colors.Grey.Lighten2).Height(150);
+                        });
                 });
         }
         
-        [ShowResult]
-        [ImageSize(210, 210)]
-        public void Debug(IContainer container)
+        [Test]
+        public void Debug()
         {
-            container
-                .Padding(25)
-                .Debug("Grid example", Colors.Blue.Medium)
-                .Grid(grid =>
+            RenderingTest
+                .Create()
+                .PageSize(210, 210)
+                .Render(container =>
                 {
-                    grid.Columns(3);
-                    grid.Spacing(5);
+                    container
+                        .Padding(25)
+                        .Debug("Grid example", Colors.Blue.Medium)
+                        .Grid(grid =>
+                        {
+                            grid.Columns(3);
+                            grid.Spacing(5);
 
-                    foreach (var _ in Enumerable.Range(0, 8))
-                        grid.Item().Height(50).Placeholder();
+                            foreach (var _ in Enumerable.Range(0, 8))
+                                grid.Item().Height(50).Placeholder();
+                        });
                 });
         }
         
-        //[ShowResult]
-        [ImageSize(300, 200)]
-        public void ElementEnd(IContainer container)
+        [Test]
+        public void ElementEnd()
         {
-            var text = "";
-            
-            container
-                .Padding(10)
-                .Element(x =>
+            RenderingTest
+                .Create()
+                .PageSize(300, 200)
+                .Render(container =>
                 {
-                    if (string.IsNullOrWhiteSpace(text))
-                        x.Height(10).Width(50).Background("#DDD");
-                    else
-                        x.Text(text);
+                    var text = "";
+            
+                    container
+                        .Padding(10)
+                        .Element(x =>
+                        {
+                            if (string.IsNullOrWhiteSpace(text))
+                                x.Height(10).Width(50).Background("#DDD");
+                            else
+                                x.Text(text);
+                        });
                 });
         }
         
-        //[ShowResult]
-        [ImageSize(300, 200)]
-        public void GridExample(IContainer container)
+        [Test]
+        public void GridExample()
         {
-            var textStyle = TextStyle.Default.Size(14);
+            RenderingTest
+                .Create()
+                .PageSize(400, 230)
+                .Render(container =>
+                {
+                    var textStyle = TextStyle.Default.Size(14);
             
-            container
-                .Padding(20)
-                .AlignRight()
-                .Grid(grid =>
+                    container
+                        .Padding(15)
+                        .AlignRight()
+                        .Grid(grid =>
+                        {
+                            grid.VerticalSpacing(15);
+                            grid.HorizontalSpacing(15);
+                            grid.AlignCenter();
+                            grid.Columns(10); // 12 by default
+
+                            grid.Item(6).Background(Colors.Blue.Lighten1).Height(50);
+                            grid.Item(4).Background(Colors.Blue.Lighten3).Height(50);
+                    
+                            grid.Item(2).Background(Colors.Teal.Lighten1).Height(70);
+                            grid.Item(3).Background(Colors.Teal.Lighten2).Height(70);
+                            grid.Item(5).Background(Colors.Teal.Lighten3).Height(70);
+                    
+                            grid.Item(2).Background(Colors.Green.Lighten1).Height(50);
+                            grid.Item(2).Background(Colors.Green.Lighten2).Height(50);
+                            grid.Item(2).Background(Colors.Green.Lighten3).Height(50);
+                        });
+                });
+        }
+        
+        [Test]
+        public void Canvas()
+        {
+            RenderingTest
+                .Create()
+                .PageSize(300, 200)
+                .Render(container =>
+                {
+                    container
+                        .Background("#FFF")
+                        .Padding(25)
+                        .Canvas((canvas, size) =>
+                        {
+                            using var paint = new SKPaint
+                            {
+                                Color = SKColors.Red,
+                                StrokeWidth = 10,
+                                IsStroke = true
+                            };
+                        
+                            // move origin to the center of the available space
+                            canvas.Translate(size.Width / 2, size.Height / 2);
+                    
+                            // draw a circle
+                            canvas.DrawCircle(0, 0, 50, paint);
+                        });
+                });
+        }
+ 
+        [Test]
+        public void LayersExample()
+        {
+            RenderingTest
+                .Create()
+                .PageSize(400, 250)
+                .Render(container =>
                 {
-                    grid.VerticalSpacing(20);
-                    grid.HorizontalSpacing(10);
-                    grid.Columns(12);
+                    container
+                        .Padding(25)
+                        .Layers(layers =>
+                        {
+                            // layer below main content
+                            layers
+                                .Layer()
+                                .Height(100)
+                                .Width(100)
+                                .Background(Colors.Grey.Lighten3);
+
+                            layers
+                                .PrimaryLayer()
+                                .Padding(25)
+                                .Stack(stack =>
+                                {
+                                    stack.Spacing(5);
+                            
+                                    foreach (var _ in Enumerable.Range(0, 7))
+                                        stack.Item().Text(Placeholders.Sentence());
+                                });
+                        
+                            // layer above the main content    
+                            layers
+                                .Layer()
+                                .AlignCenter()
+                                .AlignMiddle()
+                                .Text("Watermark", TextStyle.Default.Size(48).Bold().Color(Colors.Green.Lighten3));
 
-                    grid.Item(8).Background("#DDD").Height(50).Padding(5).Text("This is a short text", textStyle);
-                    grid.Item(4).Background("#BBB").Padding(5).Text("Showing how to...", textStyle);
-                    grid.Item(2).Background("#999").Height(50);
-                    grid.Item(4).Background("#AAA").Border(2).BorderColor("#666").Padding(5).Text("...generate", textStyle);
-                    grid.Item(6).Background("#CCC").Padding(5).Text("simple grids", textStyle.Size(18).Bold());
-                    grid.Item(8).Background("#DDD").Height(50);
+                            layers
+                                .Layer()
+                                .AlignBottom()
+                                .PageNumber("Page {number}", TextStyle.Default.Size(16).Color(Colors.Green.Medium));
+                        });
                 });
         }
 
-        //[ShowResult]
-        [ImageSize(300, 300)]
-        public void RandomColorMatrix(IContainer container)
+        [Test]
+        public void EnsureSpace()
         {
-            container
-                .Padding(25)
-                .Grid(grid =>
+            RenderingTest
+                .Create()
+                .PageSize(300, 400)
+                .Render(container =>
                 {
-                    grid.Columns(5);
+                    container
+                        .Padding(50)
+                        .Page(page =>
+                        {
+                            page.Header().PageNumber("Page {number}");
                     
-                    Enumerable
-                        .Range(0, 25)
-                        .Select(x => Placeholders.BackgroundColor())
-                        .ToList()
-                        .ForEach(x => grid.Item().Height(50).Background(x));
+                            page.Content().Height(300).Stack(content =>
+                            {
+                                content.Item().Height(200).Background(Colors.Grey.Lighten2);
+                        
+                                content.Item().EnsureSpace(100).Stack(stack =>
+                                {
+                                    stack.Spacing(10);
+                            
+                                    foreach (var _ in Enumerable.Range(0, 4))
+                                        stack.Item().Height(50).Background(Colors.Green.Lighten1);
+                                }); 
+                            });
+                        });
                 });
         }
-        
-        //[ShowResult]
-        [ImageSize(450, 150)]
-        public void DefinedColors(IContainer container)
+
+        [Test]
+        public void RandomColorMatrix()
+        {
+            RenderingTest
+                .Create()
+                .PageSize(300, 300)
+                .Render(container =>
+                {
+                    container
+                        .Padding(25)
+                        .Grid(grid =>
+                        {
+                            grid.Columns(5);
+                    
+                            Enumerable
+                                .Range(0, 25)
+                                .Select(x => Placeholders.BackgroundColor())
+                                .ToList()
+                                .ForEach(x => grid.Item().Height(50).Background(x));
+                        });
+                });
+        }
+    
+        [Test]
+        public void DefinedColors()
         {
             var colors = new[]
             {
@@ -238,19 +404,24 @@ namespace QuestPDF.Examples
                 Colors.Green.Accent4,
             };
             
-            container
-                .Padding(25)
-                .Height(100)
-                .Row(row =>
+            RenderingTest
+                .Create()
+                .PageSize(450, 150)
+                .Render(container =>
                 {
-                    foreach (var color in colors)
-                        row.RelativeColumn().Background(color);
+                    container
+                        .Padding(25)
+                        .Height(100)
+                        .Row(row =>
+                        {
+                            foreach (var color in colors)
+                                row.RelativeColumn().Background(color);
+                        });
                 });
         }
-        
-        //[ShowResult]
-        [ImageSize(500, 175)]
-        public void DefinedFonts(IContainer container)
+
+        [Test]
+        public void DefinedFonts()
         {
             var fonts = new[]
             {
@@ -265,54 +436,205 @@ namespace QuestPDF.Examples
                 Fonts.ComicSans
             };
             
-            container
-                .Padding(25)
-                .Grid(grid =>
+            RenderingTest
+                .Create()
+                .PageSize(500, 175)
+                .Render(container =>
                 {
-                    grid.Columns(3);
+                    container
+                        .Padding(25)
+                        .Grid(grid =>
+                        {
+                            grid.Columns(3);
 
-                    foreach (var font in fonts)
-                    {
-                        grid.Item()
-                            .Border(1)
-                            .BorderColor(Colors.Grey.Medium)
-                            .Padding(10)
-                            .Text(font, TextStyle.Default.FontType(font).Size(16));
-                    }
+                            foreach (var font in fonts)
+                            {
+                                grid.Item()
+                                    .Border(1)
+                                    .BorderColor(Colors.Grey.Medium)
+                                    .Padding(10)
+                                    .Text(font, TextStyle.Default.FontType(font).Size(16));
+                            }
+                        });
                 });
         }
-        
-        //[ShowResult]
-        [ImageSize(300, 300)]
-        public void Layers(IContainer container)
+
+        [Test]
+        public void Layers()
         {
-            container
-                .Background("#FFF")
-                .Padding(25)
-                .Layers(layers =>
+            RenderingTest
+                .Create()
+                .PageSize(300, 300)
+                .Render(container =>
                 {
-                    layers.Layer().Text("Something else");
+                    container
+                        .Background("#FFF")
+                        .Padding(25)
+                        .Layers(layers =>
+                        {
+                            layers.Layer().Text("Something else");
                     
-                    layers.PrimaryLayer().Stack(stack =>
-                    {
-                        stack.Item().PaddingTop(20).Text("Text 1");
-                        stack.Item().PaddingTop(40).Text("Text 2");
-                    });
+                            layers.PrimaryLayer().Stack(stack =>
+                            {
+                                stack.Item().PaddingTop(20).Text("Text 1");
+                                stack.Item().PaddingTop(40).Text("Text 2");
+                            });
                     
-                    layers.Layer().Canvas((canvas, size) =>
-                    {
-                        using var paint = new SKPaint
-                        {
-                            Color = SKColors.Red,
-                            StrokeWidth = 5
-                        };
+                            layers.Layer().Canvas((canvas, size) =>
+                            {
+                                using var paint = new SKPaint
+                                {
+                                    Color = SKColors.Red,
+                                    StrokeWidth = 5
+                                };
                         
-                        canvas.Translate(size.Width / 2, size.Height / 2);
-                        canvas.DrawCircle(0, 0, 50, paint);
-                    });
+                                canvas.Translate(size.Width / 2, size.Height / 2);
+                                canvas.DrawCircle(0, 0, 50, paint);
+                            });
                     
-                    layers.Layer().Background("#8F00").Extend();
-                    layers.Layer().PaddingTop(40).Text("It works!", TextStyle.Default.Size(24));
+                            layers.Layer().Background("#8F00").Extend();
+                            layers.Layer().PaddingTop(40).Text("It works!", TextStyle.Default.Size(24));
+                        });
+                });
+        }
+
+        [Test]
+        public void Box()
+        {
+            RenderingTest
+                .Create()
+                .PageSize(300, 150)
+                .Render(container =>
+                {
+                    container
+                        .Background("#FFF")
+                        .Padding(15)
+                        .Border(4)
+                        .BorderColor(Colors.Blue.Medium)
+                        //.Box()
+                        .Background(Colors.Grey.Lighten2)
+                        .Padding(15)
+                        .Text("Test of the \n box element", TextStyle.Default.Size(20));
+                });
+        }
+
+        [Test]
+        public void Scale()
+        {
+            RenderingTest
+                .Create()
+                .PageSize(400, 400)
+                .Render(container =>
+                {
+                    var style = TextStyle.Default.Size(20);
+            
+                    container
+                        .Background("#FFF")
+                        .Padding(25)
+                        .Stack(stack =>
+                        {
+                            stack.Spacing(5);
+                            stack.Item().Background(Placeholders.BackgroundColor()).Scale(0.5f).Text("Smaller text", style);
+                            stack.Item().Background(Placeholders.BackgroundColor()).Text("Normal text", style);
+                            stack.Item().Background(Placeholders.BackgroundColor()).Scale(2f).Text("Bigger text", style);
+                            stack.Item().Background(Placeholders.BackgroundColor()).Scale(-1.5f).Text("Flipped text", style);
+                        });
+                });
+        }
+
+        [Test]
+        public void Translate()
+        {
+            RenderingTest
+                .Create()
+                .PageSize(500, 300)
+                .Render(container =>
+                {
+                    container
+                        .Background("#FFF")
+                        .Padding(25)
+                        .Box()
+                        .Background(Colors.Green.Lighten4)
+                        .Padding(5)
+                        .TranslateX(15)
+                        .TranslateY(15)
+                        .Text("Text outside of bounds");
+                });
+        }
+
+        [Test]
+        public void Rotate()
+        {
+            RenderingTest
+                .Create()
+                .PageSize(450, 450)
+                .Render(container =>
+                {
+                    container
+                        .Background("#FFF")
+                        .Padding(20)
+                        .Grid(grid =>
+                        {
+                            grid.Columns(2);
+                            grid.Spacing(10);
+                            
+                            foreach (var turns in Enumerable.Range(0, 4))
+                            {
+                                grid.Item()
+                                    .Width(200)
+                                    .Height(200)
+                                    .Background(Colors.Grey.Lighten3)
+                                    .Padding(10)
+                                    //.Box()
+                                    .Element(element =>
+                                    {
+                                        foreach (var x in Enumerable.Range(0, turns))
+                                            element = element.RotateRight();
+
+                                        return element;
+                                    })
+                                    .Text($"Rotated {turns * 90} degrees.", TextStyle.Default.Size(14));
+                            }
+                        });
+                });
+        }
+        
+        [Test]
+        public void Flip()
+        {
+            RenderingTest
+                .Create()
+                .PageSize(450, 450)
+                .Render(container =>
+                {
+                    container
+                        .Background("#FFF")
+                        .Padding(20)
+                        .Grid(grid =>
+                        {
+                            grid.Columns(2);
+                            grid.Spacing(10);
+                            
+                            foreach (var turns in Enumerable.Range(0, 4))
+                            {
+                                grid.Item()
+                                    .Width(200)
+                                    .Height(200)
+                                    .Background(Colors.Grey.Lighten3)
+                                    .Padding(10)
+                                    .Element(element =>
+                                    {
+                                        if (turns == 1 || turns == 2)
+                                            element = element.FlipX();
+
+                                        if (turns == 2 || turns == 3)
+                                            element = element.FlipY();
+                                        
+                                        return element;
+                                    })
+                                    .Text($"Flipped.", TextStyle.Default.Size(14));
+                            }
+                        });
                 });
         }
     }

+ 0 - 95
QuestPDF.Examples/Engine/ExampleTestBase.cs

@@ -1,95 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using NUnit.Framework;
-using QuestPDF.Elements;
-using QuestPDF.Fluent;
-using QuestPDF.Infrastructure;
-using SkiaSharp;
-
-namespace QuestPDF.Examples.Engine
-{
-    [TestFixture]
-    public class ExampleTestBase
-    {
-        private readonly Size DefaultImageSize = new Size(400, 300);
-        private const string ResultPath = "Result";
-
-        [SetUp]
-        public void Setup()
-        {
-            if (Directory.Exists(ResultPath))
-                Directory.Delete(ResultPath, true);
-            
-            Directory.CreateDirectory(ResultPath);
-        }
-        
-        [Test]
-        public void RunTest()
-        {
-            GetType()
-                .GetMethods()
-                .Where(IsExampleMethod)
-                .ToList()
-                .ForEach(HandleExample);
-        }
-
-        private bool IsExampleMethod(MethodInfo method)
-        {
-            var parameters = method.GetParameters();
-            return parameters.Length == 1 && parameters.First().ParameterType == typeof(IContainer);
-        }
-        
-        private T GetAttribute<T>(MethodInfo methodInfo) where T : Attribute
-        {
-            return methodInfo.GetCustomAttributes().FirstOrDefault(y => y is T) as T;
-        }
-        
-        private void HandleExample(MethodInfo methodInfo)
-        {
-            var size = GetAttribute<ImageSizeAttribute>(methodInfo)?.Size ?? DefaultImageSize;
-            var fileName = GetAttribute<FileNameAttribute>(methodInfo)?.FileName ?? methodInfo.Name;
-            var showResult = GetAttribute<ShowResultAttribute>(methodInfo) != null;
-            var shouldIgnore = GetAttribute<IgnoreAttribute>(methodInfo) != null;
-
-            if (shouldIgnore)
-                return;
-            
-            var container = new Container();
-            methodInfo.Invoke(this, new object[] {container});
-
-            Func<int, string> fileNameSchema = i => $"{fileName.ToLower()}-${i}.png";
-
-            try
-            {
-                var document = new SimpleDocument(container, size);
-                document.GenerateImages(fileNameSchema);
-            }
-            catch (Exception e)
-            {
-                throw new Exception($"Cannot perform test {fileName}", e);
-            }
-
-            if (showResult)
-                Process.Start("explorer", fileNameSchema(0));
-        }
-        
-        private byte[] RenderPage(Element element, Size size)
-        {
-            // scale the result so it is more readable
-            const float scalingFactor = 2;
-            
-            var imageInfo = new SKImageInfo((int)(size.Width * scalingFactor), (int)(size.Height * scalingFactor));
-            using var surface = SKSurface.Create(imageInfo);
-            surface.Canvas.Scale(scalingFactor);
-
-            var canvas = new Drawing.Canvas(surface.Canvas);
-            element?.Draw(canvas, size);
-
-            surface.Canvas.Save();
-            return surface.Snapshot().Encode(SKEncodedImageFormat.Png, 100).ToArray();
-        }
-    }
-}

+ 0 - 14
QuestPDF.Examples/Engine/FileNameAttribute.cs

@@ -1,14 +0,0 @@
-using System;
-
-namespace QuestPDF.Examples.Engine
-{
-    public class FileNameAttribute : Attribute
-    {
-        public string FileName { get; }
-
-        public FileNameAttribute(string fileName)
-        {
-            FileName = fileName;
-        }
-    }
-}

+ 0 - 9
QuestPDF.Examples/Engine/IgnoreAttribute.cs

@@ -1,9 +0,0 @@
-using System;
-
-namespace QuestPDF.Examples.Engine
-{
-    public class IgnoreAttribute : Attribute
-    {
-        
-    }
-}

+ 0 - 19
QuestPDF.Examples/Engine/ImageSizeAttribute.cs

@@ -1,19 +0,0 @@
-using System;
-using QuestPDF.Infrastructure;
-
-namespace QuestPDF.Examples.Engine
-{
-    public class ImageSizeAttribute : Attribute
-    {
-        private int Width { get; }
-        private int Height { get; }
-
-        public Size Size => new Size(Width, Height);
-        
-        public ImageSizeAttribute(int width, int height)
-        {
-            Width = width;
-            Height = height;
-        }
-    }
-}

+ 56 - 0
QuestPDF.Examples/Engine/RenderingTest.cs

@@ -0,0 +1,56 @@
+using System;
+using System.Diagnostics;
+using QuestPDF.Elements;
+using QuestPDF.Fluent;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Examples.Engine
+{
+    public class RenderingTest
+    {
+        private string FileNamePrefix = "test";
+        private decimal ImageDpi = 2;
+        private Size Size { get; set; }
+        
+        private RenderingTest()
+        {
+            
+        }
+
+        public static RenderingTest Create()
+        {
+            return new RenderingTest();
+        }
+
+        public RenderingTest FileName(string fileName)
+        {
+            FileNamePrefix = fileName;
+            return this;
+        }
+        
+        public RenderingTest Dpi(decimal value = 2)
+        {
+            ImageDpi = value;
+            return this;
+        }
+        
+        public RenderingTest PageSize(int width, int height)
+        {
+            Size = new Size(width, height);
+            return this;
+        }
+        
+        public void Render(Action<IContainer> content)
+        {
+            var container = new Container();
+            content.Invoke(container);
+
+            Func<int, string> fileNameSchema = i => $"{FileNamePrefix}-${i}.png";
+
+            var document = new SimpleDocument(container, Size);
+            document.GenerateImages(fileNameSchema);
+
+            Process.Start("explorer", fileNameSchema(0));
+        }
+    }
+}

+ 0 - 9
QuestPDF.Examples/Engine/ShowResultAttribute.cs

@@ -1,9 +0,0 @@
-using System;
-
-namespace QuestPDF.Examples.Engine
-{
-    public class ShowResultAttribute : Attribute
-    {
-        
-    }
-}

+ 2 - 1
QuestPDF.Examples/Engine/SimpleDocument.cs

@@ -21,7 +21,8 @@ namespace QuestPDF.Examples.Engine
             return new DocumentMetadata()
             {
                 RasterDpi = PageSizes.PointsPerInch * 2,
-                Size = Size
+                Size = Size,
+                DocumentLayoutExceptionThreshold = 10
             };
         }
 

+ 19 - 13
QuestPDF.Examples/FrameExample.cs

@@ -1,3 +1,4 @@
+using NUnit.Framework;
 using QuestPDF.Examples.Engine;
 using QuestPDF.Fluent;
 using QuestPDF.Helpers;
@@ -19,25 +20,30 @@ namespace QuestPDF.Examples
         public static IContainer ValueCell(this IContainer container) => container.Cell(false);
     }
     
-    public class FrameExample: ExampleTestBase
+    public class FrameExample
     {
-        [ImageSize(550, 400)]
-        [ShowResult]
+        [Test]
         public void Frame(IContainer container)
         {
-            container
-                .Background("#FFF")
-                .Padding(25)
-                .Stack(stack =>
+            RenderingTest
+                .Create()
+                .PageSize(550, 400)
+                .Render(container =>
                 {
-                    for(var i=1; i<=4; i++)
-                    {
-                        stack.Item().Row(row =>
+                    container
+                        .Background("#FFF")
+                        .Padding(25)
+                        .Stack(stack =>
                         {
-                            row.RelativeColumn(2).LabelCell().Text(Placeholders.Label());
-                            row.RelativeColumn(3).ValueCell().Text(Placeholders.Paragraph());
+                            for(var i=1; i<=4; i++)
+                            {
+                                stack.Item().Row(row =>
+                                {
+                                    row.RelativeColumn(2).LabelCell().Text(Placeholders.Label());
+                                    row.RelativeColumn(3).ValueCell().Text(Placeholders.Paragraph());
+                                });
+                            }
                         });
-                    }
                 });
         }
     }

+ 22 - 16
QuestPDF.Examples/LoremPicsumExample.cs

@@ -1,4 +1,5 @@
 using System.Net;
+using NUnit.Framework;
 using QuestPDF.Examples.Engine;
 using QuestPDF.Fluent;
 using QuestPDF.Infrastructure;
@@ -27,27 +28,32 @@ namespace QuestPDF.Examples
         }
     }
     
-    public class LoremPicsumExample : ExampleTestBase
+    public class LoremPicsumExample
     {
-        [ShowResult]
-        [ImageSize(350, 280)]
-        public void LoremPicsum(IContainer container)
+        [Test]
+        public void LoremPicsum()
         {
-            container
-                .Background("#FFF")
-                .Padding(25)
-                .Stack(column =>
+            RenderingTest
+                .Create()
+                .PageSize(350, 280)
+                .Render(container =>
                 {
-                    column.Spacing(10);
+                    container
+                        .Background("#FFF")
+                        .Padding(25)
+                        .Stack(column =>
+                        {
+                            column.Spacing(10);
 
-                    column
-                        .Item()
-                        .Component(new LoremPicsum(true));
+                            column
+                                .Item()
+                                .Component(new LoremPicsum(true));
                     
-                    column
-                        .Item()
-                        .AlignRight()
-                        .Text("From Lorem Picsum");
+                            column
+                                .Item()
+                                .AlignRight()
+                                .Text("From Lorem Picsum");
+                        });
                 });
         }
     }

+ 118 - 89
QuestPDF.Examples/Padding.cs

@@ -1,132 +1,161 @@
-using QuestPDF.Examples.Engine;
+using NUnit.Framework;
+using QuestPDF.Examples.Engine;
 using QuestPDF.Fluent;
 using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Examples
 {
-    public class Examples : ExampleTestBase
+    public class Examples
     {
-        public void Padding(IContainer container)
+        [Test]
+        public void Padding()
         {
-            container
-                .Background("#FDD")
-                .Padding(50)
+            RenderingTest
+                .Create()
+                .PageSize(200, 150)
+                .Render(container =>
+                {
+                    container
+                        .Background("#FDD")
+                        .Padding(50)
 
-                .Background("#AFA")
-                .PaddingVertical(50)
+                        .Background("#AFA")
+                        .PaddingVertical(50)
 
-                .Background("#77F")
-                .PaddingHorizontal(50)
+                        .Background("#77F")
+                        .PaddingHorizontal(50)
 
-                .Background("#444");
+                        .Background("#444");
+                });
         }
         
-        public void Border(IContainer container)
+        [Test]
+        public void Border()
         {
-            container
-                .Background("#EEE")
-                .Padding(25)
+            RenderingTest
+                .Create()
+                .PageSize(200, 150)
+                .Render(container =>
+                {
+                    container
+                        .Background("#EEE")
+                        .Padding(25)
 
-                .AlignBottom()
-                .AlignCenter()
-                .BorderBottom(2)
-                .BorderColor("#000")
+                        .AlignBottom()
+                        .AlignCenter()
+                        .BorderBottom(2)
+                        .BorderColor("#000")
                 
-                .Background("FFF")
-                .Padding(5)
-                .Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji").Alignment(HorizontalAlignment.Center));
+                        .Background("FFF")
+                        .Padding(5)
+                        .Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji").Alignment(HorizontalAlignment.Center));
+                });
         }
         
-        public void Alignment(IContainer container)
+        [Test]
+        public void Alignment()
         {
-            container
-                .Stack(column =>
+            RenderingTest
+                .Create()
+                .PageSize(200, 150)
+                .Render(container =>
                 {
-                    column
-                        .Item()
-                        .Height(100)
-                        .Background("#FFF")
+                    container
+                        .Stack(column =>
+                        {
+                            column
+                                .Item()
+                                .Height(100)
+                                .Background("#FFF")
                         
-                        .AlignLeft()
-                        .AlignMiddle()
+                                .AlignLeft()
+                                .AlignMiddle()
 
-                        .Width(50)
-                        .Height(50)
-                        .Background("#444");
+                                .Width(50)
+                                .Height(50)
+                                .Background("#444");
                     
-                    column
-                        .Item()
-                        .Height(100)
-                        .Background("#DDD")
+                            column
+                                .Item()
+                                .Height(100)
+                                .Background("#DDD")
                         
-                        .AlignCenter()
-                        .AlignMiddle()
+                                .AlignCenter()
+                                .AlignMiddle()
 
-                        .Width(50)
-                        .Height(50)
-                        .Background("#222");
+                                .Width(50)
+                                .Height(50)
+                                .Background("#222");
                     
-                    column
-                        .Item()
-                        .Height(100)
-                        .Background("#BBB")
+                            column
+                                .Item()
+                                .Height(100)
+                                .Background("#BBB")
                         
-                        .AlignRight()
-                        .AlignMiddle()
+                                .AlignRight()
+                                .AlignMiddle()
 
-                        .Width(50)
-                        .Height(50)
-                        .Background("#000");
+                                .Width(50)
+                                .Height(50)
+                                .Background("#000");
+                        });
                 });
         }
         
-        public void Expand(IContainer container)
+        [Test]
+        public void Expand()
         {
-            container
-                .Stack(column =>
+            RenderingTest
+                .Create()
+                .PageSize(200, 150)
+                .Render(container =>
                 {
-                    column
-                        .Item()
-                        .Height(150)
-                        .Row(row =>
+                    container
+                        .Stack(column =>
                         {
-                            row.RelativeColumn()
-                                .Extend()
-                                .Background("FFF")
+                            column
+                                .Item()
+                                .Height(150)
+                                .Row(row =>
+                                {
+                                    row.RelativeColumn()
+                                        .Extend()
+                                        .Background("FFF")
 
-                                .Height(50)
-                                .Width(50)
-                                .Background("444");
+                                        .Height(50)
+                                        .Width(50)
+                                        .Background("444");
                             
-                            row.RelativeColumn()
-                                .Extend()
-                                .Background("BBB")
+                                    row.RelativeColumn()
+                                        .Extend()
+                                        .Background("BBB")
 
-                                .Height(50)
-                                .ExtendHorizontal()
-                                .Background("444");
-                        });
+                                        .Height(50)
+                                        .ExtendHorizontal()
+                                        .Background("444");
+                                });
                     
-                    column
-                        .Item()
-                        .Height(150)
-                        .Row(row =>
-                        {
-                            row.RelativeColumn()
-                                .Extend()
-                                .Background("BBB")
+                            column
+                                .Item()
+                                .Height(150)
+                                .Row(row =>
+                                {
+                                    row.RelativeColumn()
+                                        .Extend()
+                                        .Background("BBB")
 
-                                .ExtendVertical()
-                                .Width(50)
-                                .Background("444");
+                                        .ExtendVertical()
+                                        .Width(50)
+                                        .Background("444");
                             
-                            row.RelativeColumn()
-                                .Extend()
-                                .Background("BBB")
+                                    row.RelativeColumn()
+                                        .Extend()
+                                        .Background("BBB")
 
-                                .ExtendVertical()
-                                .ExtendHorizontal()
-                                .Background("444");
+                                        .ExtendVertical()
+                                        .ExtendHorizontal()
+                                        .Background("444");
+                                });
                         });
                 });
         }

+ 55 - 0
QuestPDF/Elements/Rotate.cs

@@ -0,0 +1,55 @@
+using System;
+using System.Linq;
+using QuestPDF.Drawing.SpacePlan;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Elements
+{
+    internal class Rotate : ContainerElement
+    {
+        public int TurnCount { get; set; }
+        public int NormalizedTurnCount => (TurnCount % 4 + 4) % 4;
+        
+        internal override ISpacePlan Measure(Size availableSpace)
+        {
+            if (NormalizedTurnCount == 0 || NormalizedTurnCount == 2)
+                return base.Measure(availableSpace);
+            
+            availableSpace = new Size(availableSpace.Height, availableSpace.Width);
+            var childSpace = base.Measure(availableSpace) as Size;
+
+            if (childSpace == null)
+                return new Wrap();
+
+            var targetSpace = new Size(childSpace.Height, childSpace.Width);
+
+            if (childSpace is FullRender)
+                return new FullRender(targetSpace);
+            
+            if (childSpace is PartialRender)
+                return new PartialRender(targetSpace);
+
+            throw new ArgumentException();
+        }
+        
+        internal override void Draw(ICanvas canvas, Size availableSpace)
+        {
+            var skiaCanvas = (canvas as Drawing.Canvas)?.SkiaCanvas;
+            
+            if (skiaCanvas == null)
+                return;
+
+            var currentMatrix = skiaCanvas.TotalMatrix;
+
+            if (NormalizedTurnCount % 4 == 1 || NormalizedTurnCount % 4 == 2)
+                skiaCanvas.Translate(availableSpace.Width, 0);
+            
+            if (NormalizedTurnCount % 4 == 2  || NormalizedTurnCount % 4 == 3)
+                skiaCanvas.Translate(0, availableSpace.Height);
+            
+            skiaCanvas.RotateRadians(TurnCount * (float) Math.PI / 2f);
+            Child?.Draw(canvas, availableSpace);
+            skiaCanvas.SetMatrix(currentMatrix);
+        }
+    }
+}

+ 60 - 0
QuestPDF/Elements/Scale.cs

@@ -0,0 +1,60 @@
+using System;
+using QuestPDF.Drawing.SpacePlan;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Elements
+{
+    internal class Scale : ContainerElement
+    {
+        public float ScaleX { get; set; } = 1;
+        public float ScaleY { get; set; } = 1;
+        
+        internal override ISpacePlan Measure(Size availableSpace)
+        {
+            var targetSpace = new Size(
+                Math.Abs(availableSpace.Width / ScaleX), 
+                Math.Abs(availableSpace.Height / ScaleY));
+            
+            var measure = base.Measure(targetSpace) as Size;
+
+            if (measure == null)
+                return new Wrap();
+
+            var targetSize = new Size(
+                Math.Abs(measure.Width * ScaleX), 
+                Math.Abs(measure.Height * ScaleY));
+
+            if (measure is PartialRender)
+                return new PartialRender(targetSize);
+            
+            if (measure is FullRender)
+                return new FullRender(targetSize);
+            
+            throw new ArgumentException();
+        }
+        
+        internal override void Draw(ICanvas canvas, Size availableSpace)
+        {
+            var skiaCanvas = (canvas as Drawing.Canvas)?.SkiaCanvas;
+            
+            if (skiaCanvas == null)
+                return;
+            
+            var targetSpace = new Size(
+                Math.Abs(availableSpace.Width / ScaleX), 
+                Math.Abs(availableSpace.Height / ScaleY));
+
+            var currentMatrix = skiaCanvas.TotalMatrix;
+            
+            if (ScaleX < 0)
+                skiaCanvas.Translate(availableSpace.Width, 0);
+            
+            if (ScaleY < 0)
+                skiaCanvas.Translate(0, availableSpace.Height);
+            
+            skiaCanvas.Scale(ScaleX, ScaleY);
+            Child?.Draw(canvas, targetSpace);
+            skiaCanvas.SetMatrix(currentMatrix);
+        }
+    }
+}

+ 23 - 0
QuestPDF/Elements/Translate.cs

@@ -0,0 +1,23 @@
+
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Elements
+{
+    internal class Translate : ContainerElement
+    {
+        public float TranslateX { get; set; } = 1;
+        public float TranslateY { get; set; } = 1;
+
+        internal override void Draw(ICanvas canvas, Size availableSpace)
+        {
+            var skiaCanvas = (canvas as Drawing.Canvas)?.SkiaCanvas;
+            
+            if (skiaCanvas == null)
+                return;
+            
+            skiaCanvas.Translate(TranslateX, TranslateY);
+            base.Draw(canvas, availableSpace);
+            skiaCanvas.Translate(-TranslateX, -TranslateY);
+        }
+    }
+}

+ 1 - 1
QuestPDF/Fluent/ElementExtensions.cs

@@ -152,7 +152,7 @@ namespace QuestPDF.Fluent
         
         public static void Canvas(this IContainer element, DrawOnCanvas handler)
         {
-            element.Element(new Elements.Canvas
+            element.Element(new Canvas
             {
                 Handler = handler
             });

+ 27 - 0
QuestPDF/Fluent/RotateExtensions.cs

@@ -0,0 +1,27 @@
+using System;
+using QuestPDF.Elements;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Fluent
+{
+    public static class RotateExtensions
+    {
+        private static IContainer Rotate(this IContainer element, Action<Rotate> handler)
+        {
+            var scale = element as Rotate ?? new Rotate();
+            handler(scale);
+            
+            return element.Element(scale);
+        }
+        
+        public static IContainer RotateLeft(this IContainer element)
+        {
+            return element.Rotate(x => x.TurnCount--);
+        }
+        
+        public static IContainer RotateRight(this IContainer element)
+        {
+            return element.Rotate(x => x.TurnCount++);
+        }
+    }
+}

+ 47 - 0
QuestPDF/Fluent/ScaleExtensions.cs

@@ -0,0 +1,47 @@
+using System;
+using QuestPDF.Elements;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Fluent
+{
+    public static class ScaleExtensions
+    {
+        private static IContainer Scale(this IContainer element, Action<Scale> handler)
+        {
+            var scale = element as Scale ?? new Scale();
+            handler(scale);
+            
+            return element.Element(scale);
+        }
+        
+        public static IContainer Scale(this IContainer element, float value)
+        {
+            return element.ScaleX(value).ScaleY(value);
+        }
+        
+        public static IContainer ScaleX(this IContainer element, float value)
+        {
+            return element.Scale(x => x.ScaleX = value);
+        }
+        
+        public static IContainer FlipX(this IContainer element)
+        {
+            return element.ScaleX(-1);
+        }
+        
+        public static IContainer ScaleY(this IContainer element, float value)
+        {
+            return element.Scale(x => x.ScaleY = value);
+        }
+        
+        public static IContainer FlipY(this IContainer element)
+        {
+            return element.ScaleY(-1);
+        }
+        
+        public static IContainer FlipOver(this IContainer element)
+        {
+            return element.FlipX().FlipY();
+        }
+    }
+}

+ 32 - 0
QuestPDF/Fluent/TranslateExtensions.cs

@@ -0,0 +1,32 @@
+using System;
+using QuestPDF.Elements;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Fluent
+{
+    public static class TranslateExtensions
+    {
+        private static IContainer Translate(this IContainer element, Action<Translate> handler)
+        {
+            var translate = element as Translate ?? new Translate();
+            handler(translate);
+            
+            return element.Element(translate);
+        }
+        
+        public static IContainer Translate(this IContainer element, float value)
+        {
+            return element.TranslateX(value).TranslateY(value);
+        }
+        
+        public static IContainer TranslateX(this IContainer element, float value)
+        {
+            return element.Translate(x => x.TranslateX = value);
+        }
+        
+        public static IContainer TranslateY(this IContainer element, float value)
+        {
+            return element.Translate(x => x.TranslateY = value);
+        }
+    }
+}