diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb6aa6f --- /dev/null +++ b/.gitignore @@ -0,0 +1,222 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ +artifacts/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* +*.ncrunchsolution + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Project Rider +*.iml +.idea \ No newline at end of file diff --git a/QuestPDF.Examples/ElementExamples.cs b/QuestPDF.Examples/ElementExamples.cs new file mode 100644 index 0000000..b7ca1ce --- /dev/null +++ b/QuestPDF.Examples/ElementExamples.cs @@ -0,0 +1,138 @@ +using QuestPDF.Examples.Engine; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Examples +{ + public class ElementExamples : ExampleTestBase + { + //[ShowResult] + [ImageSize(200, 150)] + public void Placeholder(IContainer container) + { + container + .Background("#FFF") + .Padding(25) + .Placeholder(); + } + + //[ShowResult] + [ImageSize(300, 300)] + public void Section(IContainer container) + { + container + .Background("#FFF") + .Padding(25) + .Section(section => + { + section + .Header() + .Background("#888") + .Padding(10) + .Text("Notes", TextStyle.Default.Size(16).Color("#FFF")); + + section + .Content() + .Background("#DDD") + .Padding(10) + .ExtendVertical() + .Text(TextPlaceholder.LoremIpsum()); + }); + } + + //[ShowResult] + [ImageSize(298, 421)] + public void Page(IContainer container) + { + container + .Background("#FFF") + .Padding(15) + .Page(page => + { + page.Header() + .Height(60) + .Background("#BBB") + .AlignCenter() + .AlignMiddle() + .Text("Header"); + + page.Content() + .Background("#DDD") + .AlignCenter() + .AlignMiddle() + .Text("Content"); + + page.Footer() + .Height(30) + .Background("#BBB") + .AlignCenter() + .AlignMiddle() + .Text("Footer"); + }); + } + + //[ShowResult] + [ImageSize(740, 200)] + public void Row(IContainer container) + { + container + .Background("#FFF") + .Padding(20) + .Stack(stack => + { + stack.Element() + .PaddingBottom(10) + .AlignCenter() + .Text("This Row element is 700pt wide"); + + stack.Element().Row(row => + { + row.ConstantColumn(100) + .Background("#DDD") + .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(2) + .Background("#DDD") + .Padding(10) + .Text("This column takes 2/3 of the available space (400pt)"); + }); + }); + } + + //[ShowResult] + [ImageSize(500, 350)] + public void Column(IContainer container) + { + container + .Background("#FFF") + .Padding(15) + .Stack(column => + { + column.Spacing(10); + + column + .Element() + .Background("#999") + .Height(50); + + column + .Element() + .Background("#BBB") + .Height(100); + + column + .Element() + .Background("#DDD") + .Height(150); + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF.Examples/Engine/ExampleTestBase.cs b/QuestPDF.Examples/Engine/ExampleTestBase.cs new file mode 100644 index 0000000..57fa4f5 --- /dev/null +++ b/QuestPDF.Examples/Engine/ExampleTestBase.cs @@ -0,0 +1,98 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using NUnit.Framework; +using QuestPDF.Drawing; +using QuestPDF.Elements; +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(MethodInfo methodInfo) where T : Attribute + { + return methodInfo.GetCustomAttributes().FirstOrDefault(y => y is T) as T; + } + + private void HandleExample(MethodInfo methodInfo) + { + var size = GetAttribute(methodInfo)?.Size ?? DefaultImageSize; + var fileName = GetAttribute(methodInfo)?.FileName ?? methodInfo.Name; + var showResult = GetAttribute(methodInfo) != null; + var shouldIgnore = GetAttribute(methodInfo) != null; + + if (shouldIgnore) + return; + + var container = new Container(); + methodInfo.Invoke(this, new object[] {container}); + + var iteration = 1; + + while (iteration <= 1) + { + var imageData = RenderPage(container, size); + + if (imageData == null) + return; + + var path = Path.Combine(ResultPath, $"{fileName.ToLower()}-${iteration}.png"); + File.WriteAllBytes(path, imageData); + + if (showResult) + Process.Start("explorer", path); + + iteration++; + } + } + + 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 Canvas(surface.Canvas); + element?.Draw(canvas, size); + + surface.Canvas.Save(); + return surface.Snapshot().Encode(SKEncodedImageFormat.Png, 100).ToArray(); + } + } +} \ No newline at end of file diff --git a/QuestPDF.Examples/Engine/FileNameAttribute.cs b/QuestPDF.Examples/Engine/FileNameAttribute.cs new file mode 100644 index 0000000..a569ddf --- /dev/null +++ b/QuestPDF.Examples/Engine/FileNameAttribute.cs @@ -0,0 +1,14 @@ +using System; + +namespace QuestPDF.Examples.Engine +{ + public class FileNameAttribute : Attribute + { + public string FileName { get; } + + public FileNameAttribute(string fileName) + { + FileName = fileName; + } + } +} \ No newline at end of file diff --git a/QuestPDF.Examples/Engine/IgnoreAttribute.cs b/QuestPDF.Examples/Engine/IgnoreAttribute.cs new file mode 100644 index 0000000..ba1fc47 --- /dev/null +++ b/QuestPDF.Examples/Engine/IgnoreAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace QuestPDF.Examples.Engine +{ + public class IgnoreAttribute : Attribute + { + + } +} \ No newline at end of file diff --git a/QuestPDF.Examples/Engine/ImageSizeAttribute.cs b/QuestPDF.Examples/Engine/ImageSizeAttribute.cs new file mode 100644 index 0000000..d0b17ae --- /dev/null +++ b/QuestPDF.Examples/Engine/ImageSizeAttribute.cs @@ -0,0 +1,19 @@ +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; + } + } +} \ No newline at end of file diff --git a/QuestPDF.Examples/Engine/ShowResultAttribute.cs b/QuestPDF.Examples/Engine/ShowResultAttribute.cs new file mode 100644 index 0000000..44ff885 --- /dev/null +++ b/QuestPDF.Examples/Engine/ShowResultAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace QuestPDF.Examples.Engine +{ + public class ShowResultAttribute : Attribute + { + + } +} \ No newline at end of file diff --git a/QuestPDF.Examples/FrameExample.cs b/QuestPDF.Examples/FrameExample.cs new file mode 100644 index 0000000..ae0e121 --- /dev/null +++ b/QuestPDF.Examples/FrameExample.cs @@ -0,0 +1,45 @@ +using System.Linq; +using QuestPDF.Examples.Engine; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Examples +{ + static class SimpleExtension + { + private static IContainer Cell(this IContainer container, bool dark) + { + return container + .Border(1) + .Background(dark ? "#EEE" : "#FFF") + .Padding(10); + } + + public static IContainer LabelCell(this IContainer container) => container.Cell(true); + public static IContainer ValueCell(this IContainer container) => container.Cell(false); + } + + public class FrameExample: ExampleTestBase + { + [ImageSize(550, 400)] + [ShowResult] + public void Frame(IContainer container) + { + container + .Background("#FFF") + .Padding(25) + .Stack(stack => + { + for(var i=1; i<=4; i++) + { + stack.Element().Row(row => + { + row.RelativeColumn(2).LabelCell().Text(TextPlaceholder.Label()); + row.RelativeColumn(3).ValueCell().Text(TextPlaceholder.Paragraph()); + }); + } + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF.Examples/LoremPicsumExample.cs b/QuestPDF.Examples/LoremPicsumExample.cs new file mode 100644 index 0000000..d629beb --- /dev/null +++ b/QuestPDF.Examples/LoremPicsumExample.cs @@ -0,0 +1,54 @@ +using System.Net; +using QuestPDF.Examples.Engine; +using QuestPDF.Fluent; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Examples +{ + public class LoremPicsum : IComponent + { + public bool Greyscale { get; } + + public LoremPicsum(bool greyscale) + { + Greyscale = greyscale; + } + + public void Compose(IContainer container) + { + var url = "https://picsum.photos/300/200"; + + if(Greyscale) + url += "?grayscale"; + + using var client = new WebClient(); + var response = client.DownloadData(url); + container.Image(response); + } + } + + public class LoremPicsumExample : ExampleTestBase + { + [ShowResult] + [ImageSize(350, 280)] + public void LoremPicsum(IContainer container) + { + container + .Background("#FFF") + .Padding(25) + .Stack(column => + { + column.Spacing(10); + + column + .Element() + .Component(new LoremPicsum(true)); + + column + .Element() + .AlignRight() + .Text("From Lorem Picsum"); + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF.Examples/Padding.cs b/QuestPDF.Examples/Padding.cs new file mode 100644 index 0000000..57fa6c8 --- /dev/null +++ b/QuestPDF.Examples/Padding.cs @@ -0,0 +1,134 @@ +using QuestPDF.Examples.Engine; +using QuestPDF.Fluent; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Examples +{ + public class Examples : ExampleTestBase + { + public void Padding(IContainer container) + { + container + .Background("#FDD") + .Padding(50) + + .Background("#AFA") + .PaddingVertical(50) + + .Background("#77F") + .PaddingHorizontal(50) + + .Background("#444"); + } + + public void Border(IContainer container) + { + container + .Background("#EEE") + .Padding(25) + + .AlignBottom() + .AlignCenter() + .BorderBottom(2) + .BorderColor("#000") + + .Background("FFF") + .Padding(5) + .Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji").Alignment(HorizontalAlignment.Center)); + } + + public void Alignment(IContainer container) + { + container + .Stack(column => + { + column + .Element() + .Height(100) + .Background("#FFF") + + .AlignLeft() + .AlignMiddle() + + .Width(50) + .Height(50) + .Background("#444"); + + column + .Element() + .Height(100) + .Background("#DDD") + + .AlignCenter() + .AlignMiddle() + + .Width(50) + .Height(50) + .Background("#222"); + + column + .Element() + .Height(100) + .Background("#BBB") + + .AlignRight() + .AlignMiddle() + + .Width(50) + .Height(50) + .Background("#000"); + }); + } + + public void Expand(IContainer container) + { + container + .Stack(column => + { + column + .Element() + .Height(150) + .Row(row => + { + row.RelativeColumn() + .Extend() + .Background("FFF") + + .Height(50) + .Width(50) + .Background("444"); + + row.RelativeColumn() + .Extend() + .Background("BBB") + + .Height(50) + .ExtendHorizontal() + .Background("444"); + }); + + column + .Element() + .Height(150) + .Row(row => + { + row.RelativeColumn() + .Extend() + .Background("BBB") + + .ExtendVertical() + .Width(50) + .Background("444"); + + row.RelativeColumn() + .Extend() + .Background("BBB") + + .ExtendVertical() + .ExtendHorizontal() + .Background("444"); + }); + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF.Examples/QuestPDF.Examples.csproj b/QuestPDF.Examples/QuestPDF.Examples.csproj new file mode 100644 index 0000000..e7fea32 --- /dev/null +++ b/QuestPDF.Examples/QuestPDF.Examples.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.1 + false + + + + + + + + + + + + + + + diff --git a/QuestPDF.ReportSample/DataSource.cs b/QuestPDF.ReportSample/DataSource.cs new file mode 100644 index 0000000..ee130c2 --- /dev/null +++ b/QuestPDF.ReportSample/DataSource.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using QuestPDF.Helpers; +using QuestPDF.ReportSample.Layouts; + +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 + { + Title = "Sample Report Document", + HeaderFields = HeaderFields(), + + LogoData = Helpers.GetImage("logo.png"), + Sections = Enumerable.Range(0, 10).Select(x => GenerateSection()).ToList(), + Photos = Enumerable.Range(0, 10).Select(x => GetReportPhotos()).ToList() + }; + + List HeaderFields() + { + return new List + { + new ReportHeaderField() + { + Label = "Scope", + Value = "Internal activities" + }, + new ReportHeaderField() + { + Label = "Author", + Value = "Marcin Ziąbek" + }, + new ReportHeaderField() + { + Label = "Date", + Value = DateTime.Now.ToString("g") + } + }; + } + + ReportSection GenerateSection() + { + var sectionLength = Helpers.Random.NextDouble() > 0.75 + ? Helpers.Random.Next(10, 20) + : Helpers.Random.Next(3, 11); + + return new ReportSection + { + Title = TextPlaceholder.Label(), + Parts = Enumerable.Range(0, sectionLength).Select(x => GetRandomElement()).ToList() + }; + } + + ReportSectionElement GetRandomElement() + { + var random = Helpers.Random.NextDouble(); + + if (random < 0.8f) + return GetTextElement(); + + if (random < 0.9f) + return GetMapElement(); + + return GetPhotosElement(); + } + + ReportSectionText GetTextElement() + { + return new ReportSectionText + { + Label = TextPlaceholder.Label(), + Text = TextPlaceholder.Paragraph() + }; + } + + ReportSectionMap GetMapElement() + { + var rnd = Helpers.Random.Next(0, 64); + + return new ReportSectionMap + { + Label = "Location", + ImageSource = x => Helpers.GetDocumentMap($"{rnd}.jpg"), + Location = Helpers.RandomLocation() + }; + } + + ReportSectionPhotos GetPhotosElement() + { + return new ReportSectionPhotos + { + Label = "Photos", + Photos = Enumerable + .Range(0, Helpers.Random.Next(1, 10)) + .Select(x => Helpers.Random.Next(0, 128)) + .Select(x => Helpers.GetPhoto($"{x}.jpg")) + .ToList() + }; + } + + ReportPhoto GetReportPhotos() + { + var photoId = Helpers.Random.Next(0, 128); + var mapId = Helpers.Random.Next(0, 64); + + return new ReportPhoto() + { + PhotoData = Helpers.GetPhoto($"{photoId}.jpg"), + + Comments = TextPlaceholder.Paragraph(), + Date = DateTime.Now - TimeSpan.FromDays(Helpers.Random.NextDouble() * 100), + Location = Helpers.RandomLocation(), + + MapContextSource = x => Helpers.GetContextMap($"{mapId}.jpg"), + MapDetailsSource = x => Helpers.GetDetailsMap($"{mapId}.jpg") + }; + } + } + } +} \ No newline at end of file diff --git a/QuestPDF.ReportSample/Helpers.cs b/QuestPDF.ReportSample/Helpers.cs new file mode 100644 index 0000000..3c5e1c4 --- /dev/null +++ b/QuestPDF.ReportSample/Helpers.cs @@ -0,0 +1,35 @@ +using System; +using System.IO; +using QuestPDF.ReportSample.Layouts; +using SkiaSharp; + +namespace QuestPDF.ReportSample +{ + public static class Helpers + { + public static Random Random { get; } = new Random(); + + public static string GetTestItem(string path) => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", path); + + public static byte[] GetDocumentMap(string name) => GetImage(Path.Combine("Maps", "Document", name)); + public static byte[] GetDetailsMap(string name) => GetImage(Path.Combine("Maps", "Details", name)); + public static byte[] GetContextMap(string name) => GetImage(Path.Combine("Maps", "Context", name)); + + public static byte[] GetPhoto(string name) => GetImage(Path.Combine("Photos", name)); + + public static byte[] GetImage(string name) + { + var photoPath = GetTestItem(name); + return SKImage.FromEncodedData(photoPath).EncodedData.ToArray(); + } + + public static Location RandomLocation() + { + return new Location + { + Longitude = Helpers.Random.NextDouble() * 360f - 180f, + Latitude = Helpers.Random.NextDouble() * 180f - 90f + }; + } + } +} \ No newline at end of file diff --git a/QuestPDF.ReportSample/Layouts/Helpers.cs b/QuestPDF.ReportSample/Layouts/Helpers.cs new file mode 100644 index 0000000..930e268 --- /dev/null +++ b/QuestPDF.ReportSample/Layouts/Helpers.cs @@ -0,0 +1,44 @@ +using System; +using QuestPDF.Fluent; +using QuestPDF.Infrastructure; + +namespace QuestPDF.ReportSample.Layouts +{ + public static class Helpers + { + static IContainer Cell(this IContainer container, string color) + { + return container + .Border(0.5f) + .Background(color) + .Padding(5); + } + + public static IContainer LightCell(this IContainer container) + { + return container.Cell("#0000"); + } + + public static IContainer DarkCell(this IContainer container) + { + return container.Cell("#1000"); + } + + public static string Format(this Location location) + { + if (location == null) + return string.Empty; + + var lon = location.Longitude; + var lat = location.Latitude; + + var typeLon = lon > 0 ? "E" : "W"; + lon = Math.Abs(lon); + + var typeLat = lat > 0 ? "N" : "S"; + lat = Math.Abs(lat); + + return $"{lat:F5}° {typeLat} {lon:F5}° {typeLon}"; + } + } +} \ No newline at end of file diff --git a/QuestPDF.ReportSample/Layouts/ImageTemplate.cs b/QuestPDF.ReportSample/Layouts/ImageTemplate.cs new file mode 100644 index 0000000..673d470 --- /dev/null +++ b/QuestPDF.ReportSample/Layouts/ImageTemplate.cs @@ -0,0 +1,31 @@ +using System; +using QuestPDF.Fluent; +using QuestPDF.Infrastructure; + +namespace QuestPDF.ReportSample.Layouts +{ + public class ImageTemplate : IComponent + { + private Func Source { get; } + private float AspectRatio { get; } + + public ImageTemplate(byte[] source, float aspectRatio = 1.333333f) : this(_ => source, aspectRatio) + { + + } + + public ImageTemplate(Func source, float aspectRatio = 1.333333f) + { + Source = source; + AspectRatio = aspectRatio; + } + + public void Compose(IContainer container) + { + container + .AspectRatio(AspectRatio) + .Background("#EEEEEE") + .Image(Source(Size.Zero)); + } + } +} \ No newline at end of file diff --git a/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs b/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs new file mode 100644 index 0000000..b0dcfca --- /dev/null +++ b/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs @@ -0,0 +1,62 @@ +using QuestPDF.Fluent; +using QuestPDF.Infrastructure; + +namespace QuestPDF.ReportSample.Layouts +{ + public class PhotoTemplate : IComponent + { + public ReportPhoto Model { get; set; } + + public PhotoTemplate(ReportPhoto model) + { + Model = model; + } + + public void Compose(IContainer container) + { + container + .Stack(stack => + { + stack.Element(PhotoWithMaps); + stack.Element(PhotoDetails); + }); + } + + void PhotoWithMaps(IContainer container) + { + container + .Padding(-3) + .PaddingBottom(3) + .Row(row => + { + row.RelativeColumn(2).Padding(3).Component(new ImageTemplate(Model.PhotoData)); + + row.RelativeColumn().Stack(stack => + { + stack.Element().Padding(3).Component(new ImageTemplate(Model.MapDetailsSource)); + stack.Element().Padding(3).Component(new ImageTemplate(Model.MapContextSource)); + }); + }); + } + + void PhotoDetails(IContainer container) + { + container.Stack(stack => + { + stack.Element().Row(row => + { + row.RelativeColumn().DarkCell().Text("Date", Typography.Normal); + row.RelativeColumn(2).LightCell().Text(Model.Date?.ToString("g") ?? string.Empty, Typography.Normal); + row.RelativeColumn().DarkCell().Text("Location", Typography.Normal); + row.RelativeColumn(2).LightCell().Text(Model.Location.Format(), Typography.Normal); + }); + + stack.Element().Row(row => + { + row.RelativeColumn().DarkCell().Text("Comments", Typography.Normal); + row.RelativeColumn(5).LightCell().Text(Model.Comments, Typography.Normal); + }); + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF.ReportSample/Layouts/SectionTemplate.cs b/QuestPDF.ReportSample/Layouts/SectionTemplate.cs new file mode 100644 index 0000000..c37b070 --- /dev/null +++ b/QuestPDF.ReportSample/Layouts/SectionTemplate.cs @@ -0,0 +1,94 @@ +using System; +using System.Linq; +using QuestPDF.Fluent; +using QuestPDF.Infrastructure; + +namespace QuestPDF.ReportSample.Layouts +{ + public class SectionTemplate : IComponent + { + public ReportSection Model { get; set; } + + public SectionTemplate(ReportSection model) + { + Model = model; + } + + public void Compose(IContainer container) + { + container + .MinHeight(100) + .Section(section => + { + section + .Header() + .PaddingBottom(5) + .Text(Model.Title, Typography.Headline); + + section.Content().PageableStack(column => + { + foreach (var part in Model.Parts) + { + column.Element().Row(row => + { + row.ConstantColumn(150).DarkCell().Text(part.Label, Typography.Normal); + var frame = row.RelativeColumn().LightCell(); + + if (part is ReportSectionText text) + frame.Text(text.Text, Typography.Normal); + + if (part is ReportSectionMap map) + frame.Element(container => MapElement(container, map)); + + if (part is ReportSectionPhotos photos) + frame.Element(container => PhotosElement(container, photos)); + }); + } + }); + }); + } + + void MapElement(IContainer container, ReportSectionMap model) + { + if (model.ImageSource == null || model.Location == null) + { + container.Text("No location provided", Typography.Normal); + return; + } + + container.Stack(stack => + { + stack.Spacing(5); + + stack.Element().Component(new ImageTemplate(model.ImageSource)); + stack.Element().Text(model.Location.Format(), Typography.Normal); + }); + } + + void PhotosElement(IContainer container, ReportSectionPhotos model) + { + if (model.Photos.Count == 0) + { + container.Text("No photos", Typography.Normal); + return; + } + + var rowCount = (int) Math.Ceiling(model.Photos.Count / 3f); + + container.Padding(-2).Stack(stack => + { + foreach (var rowId in Enumerable.Range(0, rowCount)) + { + stack.Element().Row(row => + { + foreach (var id in Enumerable.Range(0, 3)) + { + var data = model.Photos.ElementAtOrDefault(rowId + id); + row.RelativeColumn().Padding(2).Component(new ImageTemplate(data)); + } + }); + } + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF.ReportSample/Layouts/StandardReport.cs b/QuestPDF.ReportSample/Layouts/StandardReport.cs new file mode 100644 index 0000000..faa39bc --- /dev/null +++ b/QuestPDF.ReportSample/Layouts/StandardReport.cs @@ -0,0 +1,94 @@ +using QuestPDF.Drawing; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace QuestPDF.ReportSample.Layouts +{ + public class StandardReport : IDocument + { + private ReportModel Model { get; } + + public StandardReport(ReportModel model) + { + Model = model; + } + + public DocumentMetadata GetMetadata() + { + return new DocumentMetadata() + { + Title = Model.Title, + Size = PageSizes.A4 + }; + } + + public void Compose(IContainer container) + { + container + .PaddingVertical(40) + .PaddingHorizontal(50) + + .Page(page => + { + page.Header(ComposeHeader); + page.Content(ComposeContent); + page.Footer().AlignCenter().PageNumber("Page {number}"); + }); + } + + private void ComposeHeader(IContainer container) + { + container.Row(row => + { + row.RelativeColumn().MaxWidth(300).Stack(stack => + { + stack.Spacing(5); + + stack + .Element() + .PaddingBottom(5) + .Text(Model.Title, Typography.Title); + + stack.Element().ShowOnce().Stack(table => + { + table.Spacing(5); + + foreach (var field in Model.HeaderFields) + { + table.Element().Row(row => + { + row.ConstantColumn(50) + .AlignLeft() + .Text($"{field.Label}:", Typography.Normal); + + row.RelativeColumn() + .PaddingLeft(10) + .Text(field.Value, Typography.Normal); + }); + } + }); + + }); + + row.ConstantColumn(150).Image(Model.LogoData); + }); + } + + void ComposeContent(IContainer container) + { + container.PaddingVertical(20).PageableStack(stack => + { + stack.Spacing(20); + + foreach (var section in Model.Sections) + stack.Element().Component(new SectionTemplate(section)); + + stack.Element().PageBreak(); + + foreach (var photo in Model.Photos) + stack.Element().Component(new PhotoTemplate(photo)); + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF.ReportSample/Models.cs b/QuestPDF.ReportSample/Models.cs new file mode 100644 index 0000000..593b1e6 --- /dev/null +++ b/QuestPDF.ReportSample/Models.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using QuestPDF.Infrastructure; + +namespace QuestPDF.ReportSample +{ + public class ReportModel + { + public string Title { get; set; } + public byte[] LogoData { get; set; } + public List HeaderFields { get; set; } + + public List Sections { get; set; } + public List Photos { get; set; } + } + + public class ReportHeaderField + { + public string Label { get; set; } + public string Value { get; set; } + } + + public class Location + { + public double Longitude { get; set; } + public double Latitude { get; set; } + } + + public class ReportSection + { + public string Title { get; set; } + public List Parts { get; set; } + } + + public abstract class ReportSectionElement + { + public string Label { get; set; } + } + + public class ReportSectionText : ReportSectionElement + { + public string Text { get; set; } + } + + public class ReportSectionMap : ReportSectionElement + { + public Func ImageSource { get; set; } + public Location Location { get; set; } + } + + public class ReportSectionPhotos : ReportSectionElement + { + public List Photos { get; set; } + } + + public class ReportPhoto + { + public byte[] PhotoData { get; set; } + + public Func MapDetailsSource { get; set; } + public Func MapContextSource { get; set; } + + public Location Location { get; set; } + + public DateTime? Date { get; set; } + public string Comments { get; set; } + } +} diff --git a/QuestPDF.ReportSample/QuestPDF.ReportSample.csproj b/QuestPDF.ReportSample/QuestPDF.ReportSample.csproj new file mode 100644 index 0000000..c8c8e6f --- /dev/null +++ b/QuestPDF.ReportSample/QuestPDF.ReportSample.csproj @@ -0,0 +1,32 @@ + + + + netcoreapp3.1 + false + 8 + QuestPDF.ReportSample + + + + + + + + + + + + + + + + + + PreserveNewest + + + + + + + diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/0.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/0.jpg new file mode 100644 index 0000000..9e8a5a4 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/0.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/1.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/1.jpg new file mode 100644 index 0000000..0342939 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/1.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/10.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/10.jpg new file mode 100644 index 0000000..34aae57 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/10.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/11.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/11.jpg new file mode 100644 index 0000000..b35bfb3 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/11.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/12.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/12.jpg new file mode 100644 index 0000000..c86b92d Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/12.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/13.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/13.jpg new file mode 100644 index 0000000..e8bea6e Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/13.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/14.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/14.jpg new file mode 100644 index 0000000..f0baea7 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/14.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/15.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/15.jpg new file mode 100644 index 0000000..f513817 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/15.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/16.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/16.jpg new file mode 100644 index 0000000..f0a1a20 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/16.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/17.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/17.jpg new file mode 100644 index 0000000..ff41d5d Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/17.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/18.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/18.jpg new file mode 100644 index 0000000..e4b3a31 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/18.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/19.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/19.jpg new file mode 100644 index 0000000..8aec636 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/19.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/2.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/2.jpg new file mode 100644 index 0000000..ec0c991 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/2.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/20.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/20.jpg new file mode 100644 index 0000000..726859d Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/20.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/21.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/21.jpg new file mode 100644 index 0000000..4a609dd Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/21.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/22.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/22.jpg new file mode 100644 index 0000000..63e56f6 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/22.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/23.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/23.jpg new file mode 100644 index 0000000..3a01da3 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/23.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/24.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/24.jpg new file mode 100644 index 0000000..eea1047 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/24.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/25.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/25.jpg new file mode 100644 index 0000000..337584a Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/25.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/26.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/26.jpg new file mode 100644 index 0000000..bc7c475 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/26.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/27.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/27.jpg new file mode 100644 index 0000000..db23c97 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/27.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/28.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/28.jpg new file mode 100644 index 0000000..cfcdb2e Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/28.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/29.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/29.jpg new file mode 100644 index 0000000..193328d Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/29.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/3.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/3.jpg new file mode 100644 index 0000000..c28934f Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/3.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/30.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/30.jpg new file mode 100644 index 0000000..f5aa805 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/30.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/31.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/31.jpg new file mode 100644 index 0000000..38006e5 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/31.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/32.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/32.jpg new file mode 100644 index 0000000..937d214 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/32.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/33.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/33.jpg new file mode 100644 index 0000000..ca6a859 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/33.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/34.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/34.jpg new file mode 100644 index 0000000..1d44501 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/34.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/35.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/35.jpg new file mode 100644 index 0000000..13f46e8 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/35.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/36.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/36.jpg new file mode 100644 index 0000000..9d8146c Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/36.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/37.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/37.jpg new file mode 100644 index 0000000..3ee9d5b Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/37.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/38.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/38.jpg new file mode 100644 index 0000000..c7c5714 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/38.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/39.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/39.jpg new file mode 100644 index 0000000..3ce566c Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/39.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/4.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/4.jpg new file mode 100644 index 0000000..5fd67bc Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/4.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/40.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/40.jpg new file mode 100644 index 0000000..8ccd119 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/40.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/41.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/41.jpg new file mode 100644 index 0000000..d791f4c Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/41.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/42.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/42.jpg new file mode 100644 index 0000000..0408fe5 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/42.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/43.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/43.jpg new file mode 100644 index 0000000..0f1efaf Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/43.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/44.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/44.jpg new file mode 100644 index 0000000..fe3ba52 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/44.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/45.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/45.jpg new file mode 100644 index 0000000..fa976ab Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/45.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/46.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/46.jpg new file mode 100644 index 0000000..40552c5 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/46.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/47.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/47.jpg new file mode 100644 index 0000000..92404e3 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/47.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/48.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/48.jpg new file mode 100644 index 0000000..a00db13 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/48.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/49.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/49.jpg new file mode 100644 index 0000000..05ef1e8 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/49.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/5.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/5.jpg new file mode 100644 index 0000000..a0d03b4 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/5.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/50.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/50.jpg new file mode 100644 index 0000000..5d8e9e1 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/50.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/51.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/51.jpg new file mode 100644 index 0000000..72c744b Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/51.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/52.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/52.jpg new file mode 100644 index 0000000..6542ffe Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/52.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/53.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/53.jpg new file mode 100644 index 0000000..0ff8aa6 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/53.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/54.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/54.jpg new file mode 100644 index 0000000..65d62aa Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/54.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/55.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/55.jpg new file mode 100644 index 0000000..959ff87 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/55.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/56.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/56.jpg new file mode 100644 index 0000000..8b0b566 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/56.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/57.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/57.jpg new file mode 100644 index 0000000..8d1901a Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/57.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/58.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/58.jpg new file mode 100644 index 0000000..cb220b4 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/58.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/59.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/59.jpg new file mode 100644 index 0000000..dbc538e Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/59.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/6.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/6.jpg new file mode 100644 index 0000000..4e9bf1b Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/6.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/60.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/60.jpg new file mode 100644 index 0000000..a364b6a Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/60.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/61.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/61.jpg new file mode 100644 index 0000000..eece408 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/61.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/62.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/62.jpg new file mode 100644 index 0000000..7536fc5 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/62.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/63.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/63.jpg new file mode 100644 index 0000000..f433db1 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/63.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/7.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/7.jpg new file mode 100644 index 0000000..8cf93b2 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/7.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/8.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/8.jpg new file mode 100644 index 0000000..967d594 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/8.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Context/9.jpg b/QuestPDF.ReportSample/Resources/Maps/Context/9.jpg new file mode 100644 index 0000000..b7b0131 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Context/9.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/0.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/0.jpg new file mode 100644 index 0000000..e3f5877 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/0.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/1.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/1.jpg new file mode 100644 index 0000000..5a1bc8d Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/1.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/10.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/10.jpg new file mode 100644 index 0000000..c479c40 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/10.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/11.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/11.jpg new file mode 100644 index 0000000..cdb0c0e Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/11.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/12.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/12.jpg new file mode 100644 index 0000000..c0602b1 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/12.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/13.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/13.jpg new file mode 100644 index 0000000..241fa2c Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/13.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/14.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/14.jpg new file mode 100644 index 0000000..88f9798 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/14.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/15.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/15.jpg new file mode 100644 index 0000000..b9bcb44 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/15.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/16.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/16.jpg new file mode 100644 index 0000000..c169c9e Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/16.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/17.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/17.jpg new file mode 100644 index 0000000..b8066d0 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/17.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/18.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/18.jpg new file mode 100644 index 0000000..c405e4e Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/18.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/19.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/19.jpg new file mode 100644 index 0000000..48ec173 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/19.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/2.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/2.jpg new file mode 100644 index 0000000..f2ccfc9 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/2.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/20.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/20.jpg new file mode 100644 index 0000000..c5747aa Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/20.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/21.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/21.jpg new file mode 100644 index 0000000..0b70eb3 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/21.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/22.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/22.jpg new file mode 100644 index 0000000..2e5f356 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/22.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/23.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/23.jpg new file mode 100644 index 0000000..dc21830 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/23.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/24.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/24.jpg new file mode 100644 index 0000000..92ba04d Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/24.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/25.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/25.jpg new file mode 100644 index 0000000..ea98761 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/25.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/26.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/26.jpg new file mode 100644 index 0000000..00d8a36 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/26.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/27.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/27.jpg new file mode 100644 index 0000000..28f621b Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/27.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/28.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/28.jpg new file mode 100644 index 0000000..6da9602 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/28.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/29.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/29.jpg new file mode 100644 index 0000000..6730c73 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/29.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/3.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/3.jpg new file mode 100644 index 0000000..40b6238 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/3.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/30.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/30.jpg new file mode 100644 index 0000000..10f6965 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/30.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/31.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/31.jpg new file mode 100644 index 0000000..5a4dec7 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/31.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/32.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/32.jpg new file mode 100644 index 0000000..d49fb6b Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/32.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/33.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/33.jpg new file mode 100644 index 0000000..9053eaf Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/33.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/34.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/34.jpg new file mode 100644 index 0000000..e44cbd7 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/34.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/35.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/35.jpg new file mode 100644 index 0000000..d624f18 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/35.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/36.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/36.jpg new file mode 100644 index 0000000..f3a7f85 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/36.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/37.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/37.jpg new file mode 100644 index 0000000..cf3077f Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/37.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/38.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/38.jpg new file mode 100644 index 0000000..b68e675 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/38.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/39.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/39.jpg new file mode 100644 index 0000000..1cc44b9 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/39.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/4.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/4.jpg new file mode 100644 index 0000000..89554c8 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/4.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/40.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/40.jpg new file mode 100644 index 0000000..bc757b0 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/40.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/41.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/41.jpg new file mode 100644 index 0000000..8eb5bf7 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/41.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/42.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/42.jpg new file mode 100644 index 0000000..df80ad4 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/42.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/43.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/43.jpg new file mode 100644 index 0000000..dad91c8 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/43.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/44.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/44.jpg new file mode 100644 index 0000000..af5717f Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/44.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/45.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/45.jpg new file mode 100644 index 0000000..1a0e86d Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/45.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/46.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/46.jpg new file mode 100644 index 0000000..d56d6e3 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/46.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/47.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/47.jpg new file mode 100644 index 0000000..5a6583a Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/47.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/48.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/48.jpg new file mode 100644 index 0000000..a5e7ee6 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/48.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/49.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/49.jpg new file mode 100644 index 0000000..44af56f Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/49.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/5.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/5.jpg new file mode 100644 index 0000000..aa46ae0 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/5.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/50.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/50.jpg new file mode 100644 index 0000000..12d9ab2 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/50.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/51.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/51.jpg new file mode 100644 index 0000000..af41266 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/51.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/52.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/52.jpg new file mode 100644 index 0000000..77b2983 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/52.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/53.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/53.jpg new file mode 100644 index 0000000..551c392 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/53.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/54.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/54.jpg new file mode 100644 index 0000000..4e2a8d7 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/54.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/55.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/55.jpg new file mode 100644 index 0000000..a40c45d Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/55.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/56.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/56.jpg new file mode 100644 index 0000000..c99da4d Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/56.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/57.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/57.jpg new file mode 100644 index 0000000..0271518 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/57.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/58.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/58.jpg new file mode 100644 index 0000000..d22a4a4 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/58.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/59.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/59.jpg new file mode 100644 index 0000000..e2942d1 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/59.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/6.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/6.jpg new file mode 100644 index 0000000..2a45f93 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/6.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/60.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/60.jpg new file mode 100644 index 0000000..6306b6f Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/60.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/61.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/61.jpg new file mode 100644 index 0000000..7df6ac6 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/61.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/62.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/62.jpg new file mode 100644 index 0000000..042dac2 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/62.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/63.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/63.jpg new file mode 100644 index 0000000..e52de1f Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/63.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/7.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/7.jpg new file mode 100644 index 0000000..c71042a Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/7.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/8.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/8.jpg new file mode 100644 index 0000000..400b412 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/8.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Details/9.jpg b/QuestPDF.ReportSample/Resources/Maps/Details/9.jpg new file mode 100644 index 0000000..b3d8224 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Details/9.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/0.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/0.jpg new file mode 100644 index 0000000..6de2a92 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/0.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/1.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/1.jpg new file mode 100644 index 0000000..cdcb31a Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/1.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/10.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/10.jpg new file mode 100644 index 0000000..ac917f2 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/10.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/11.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/11.jpg new file mode 100644 index 0000000..86c703a Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/11.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/12.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/12.jpg new file mode 100644 index 0000000..766decb Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/12.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/13.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/13.jpg new file mode 100644 index 0000000..6516a1c Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/13.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/14.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/14.jpg new file mode 100644 index 0000000..149bc74 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/14.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/15.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/15.jpg new file mode 100644 index 0000000..0a544fd Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/15.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/16.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/16.jpg new file mode 100644 index 0000000..977cde9 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/16.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/17.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/17.jpg new file mode 100644 index 0000000..a0fe48e Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/17.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/18.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/18.jpg new file mode 100644 index 0000000..a967861 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/18.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/19.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/19.jpg new file mode 100644 index 0000000..813a178 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/19.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/2.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/2.jpg new file mode 100644 index 0000000..963114e Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/2.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/20.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/20.jpg new file mode 100644 index 0000000..375a12d Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/20.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/21.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/21.jpg new file mode 100644 index 0000000..3cdf0b6 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/21.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/22.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/22.jpg new file mode 100644 index 0000000..39b9159 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/22.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/23.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/23.jpg new file mode 100644 index 0000000..25f2237 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/23.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/24.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/24.jpg new file mode 100644 index 0000000..91943c0 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/24.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/25.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/25.jpg new file mode 100644 index 0000000..8562ac2 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/25.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/26.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/26.jpg new file mode 100644 index 0000000..3890ce7 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/26.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/27.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/27.jpg new file mode 100644 index 0000000..987ae2d Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/27.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/28.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/28.jpg new file mode 100644 index 0000000..f288748 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/28.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/29.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/29.jpg new file mode 100644 index 0000000..e9827e3 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/29.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/3.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/3.jpg new file mode 100644 index 0000000..cddb19d Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/3.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/30.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/30.jpg new file mode 100644 index 0000000..aa51fc2 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/30.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/31.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/31.jpg new file mode 100644 index 0000000..a33c247 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/31.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/32.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/32.jpg new file mode 100644 index 0000000..62ab65d Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/32.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/33.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/33.jpg new file mode 100644 index 0000000..40c4eb2 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/33.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/34.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/34.jpg new file mode 100644 index 0000000..a067651 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/34.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/35.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/35.jpg new file mode 100644 index 0000000..725ad9f Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/35.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/36.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/36.jpg new file mode 100644 index 0000000..434152f Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/36.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/37.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/37.jpg new file mode 100644 index 0000000..b3026ec Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/37.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/38.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/38.jpg new file mode 100644 index 0000000..dd7ce7f Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/38.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/39.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/39.jpg new file mode 100644 index 0000000..6dd299d Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/39.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/4.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/4.jpg new file mode 100644 index 0000000..e12abf7 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/4.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/40.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/40.jpg new file mode 100644 index 0000000..ca0d34e Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/40.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/41.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/41.jpg new file mode 100644 index 0000000..45ff233 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/41.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/42.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/42.jpg new file mode 100644 index 0000000..ecf6e87 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/42.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/43.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/43.jpg new file mode 100644 index 0000000..261d8e8 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/43.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/44.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/44.jpg new file mode 100644 index 0000000..b499818 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/44.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/45.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/45.jpg new file mode 100644 index 0000000..cc35b53 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/45.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/46.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/46.jpg new file mode 100644 index 0000000..95e4c47 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/46.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/47.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/47.jpg new file mode 100644 index 0000000..d35dd79 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/47.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/48.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/48.jpg new file mode 100644 index 0000000..89d786d Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/48.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/49.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/49.jpg new file mode 100644 index 0000000..bb61941 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/49.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/5.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/5.jpg new file mode 100644 index 0000000..504b0ea Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/5.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/50.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/50.jpg new file mode 100644 index 0000000..ed22206 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/50.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/51.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/51.jpg new file mode 100644 index 0000000..f9eaed5 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/51.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/52.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/52.jpg new file mode 100644 index 0000000..1a4e7c0 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/52.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/53.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/53.jpg new file mode 100644 index 0000000..cc2ec47 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/53.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/54.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/54.jpg new file mode 100644 index 0000000..17d0c82 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/54.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/55.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/55.jpg new file mode 100644 index 0000000..6ae763d Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/55.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/56.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/56.jpg new file mode 100644 index 0000000..b660d56 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/56.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/57.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/57.jpg new file mode 100644 index 0000000..2672946 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/57.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/58.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/58.jpg new file mode 100644 index 0000000..df262cb Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/58.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/59.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/59.jpg new file mode 100644 index 0000000..5eb2d55 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/59.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/6.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/6.jpg new file mode 100644 index 0000000..f3b242c Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/6.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/60.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/60.jpg new file mode 100644 index 0000000..25a5311 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/60.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/61.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/61.jpg new file mode 100644 index 0000000..1e6c327 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/61.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/62.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/62.jpg new file mode 100644 index 0000000..b26ef02 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/62.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/63.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/63.jpg new file mode 100644 index 0000000..c5c6c7c Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/63.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/7.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/7.jpg new file mode 100644 index 0000000..7580864 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/7.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/8.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/8.jpg new file mode 100644 index 0000000..0824df6 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/8.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Maps/Document/9.jpg b/QuestPDF.ReportSample/Resources/Maps/Document/9.jpg new file mode 100644 index 0000000..c810f2a Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Maps/Document/9.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/0.jpg b/QuestPDF.ReportSample/Resources/Photos/0.jpg new file mode 100644 index 0000000..abce4ef Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/0.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/1.jpg b/QuestPDF.ReportSample/Resources/Photos/1.jpg new file mode 100644 index 0000000..4e9209d Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/1.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/10.jpg b/QuestPDF.ReportSample/Resources/Photos/10.jpg new file mode 100644 index 0000000..bcdef63 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/10.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/100.jpg b/QuestPDF.ReportSample/Resources/Photos/100.jpg new file mode 100644 index 0000000..44f1612 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/100.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/101.jpg b/QuestPDF.ReportSample/Resources/Photos/101.jpg new file mode 100644 index 0000000..394b2c5 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/101.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/102.jpg b/QuestPDF.ReportSample/Resources/Photos/102.jpg new file mode 100644 index 0000000..d6bb007 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/102.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/103.jpg b/QuestPDF.ReportSample/Resources/Photos/103.jpg new file mode 100644 index 0000000..87377f6 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/103.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/104.jpg b/QuestPDF.ReportSample/Resources/Photos/104.jpg new file mode 100644 index 0000000..ab85e34 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/104.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/105.jpg b/QuestPDF.ReportSample/Resources/Photos/105.jpg new file mode 100644 index 0000000..f32370b Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/105.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/106.jpg b/QuestPDF.ReportSample/Resources/Photos/106.jpg new file mode 100644 index 0000000..3e88516 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/106.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/107.jpg b/QuestPDF.ReportSample/Resources/Photos/107.jpg new file mode 100644 index 0000000..28ba5c9 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/107.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/108.jpg b/QuestPDF.ReportSample/Resources/Photos/108.jpg new file mode 100644 index 0000000..6159a9b Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/108.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/109.jpg b/QuestPDF.ReportSample/Resources/Photos/109.jpg new file mode 100644 index 0000000..abce4ef Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/109.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/11.jpg b/QuestPDF.ReportSample/Resources/Photos/11.jpg new file mode 100644 index 0000000..ca90967 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/11.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/110.jpg b/QuestPDF.ReportSample/Resources/Photos/110.jpg new file mode 100644 index 0000000..2d358f9 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/110.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/111.jpg b/QuestPDF.ReportSample/Resources/Photos/111.jpg new file mode 100644 index 0000000..5d7edc4 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/111.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/112.jpg b/QuestPDF.ReportSample/Resources/Photos/112.jpg new file mode 100644 index 0000000..8624a9c Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/112.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/113.jpg b/QuestPDF.ReportSample/Resources/Photos/113.jpg new file mode 100644 index 0000000..6ad8b8d Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/113.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/114.jpg b/QuestPDF.ReportSample/Resources/Photos/114.jpg new file mode 100644 index 0000000..8307a70 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/114.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/115.jpg b/QuestPDF.ReportSample/Resources/Photos/115.jpg new file mode 100644 index 0000000..e6ef0fc Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/115.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/116.jpg b/QuestPDF.ReportSample/Resources/Photos/116.jpg new file mode 100644 index 0000000..af163ee Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/116.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/117.jpg b/QuestPDF.ReportSample/Resources/Photos/117.jpg new file mode 100644 index 0000000..f9816cc Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/117.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/118.jpg b/QuestPDF.ReportSample/Resources/Photos/118.jpg new file mode 100644 index 0000000..63b342d Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/118.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/119.jpg b/QuestPDF.ReportSample/Resources/Photos/119.jpg new file mode 100644 index 0000000..9cbdbc0 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/119.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/12.jpg b/QuestPDF.ReportSample/Resources/Photos/12.jpg new file mode 100644 index 0000000..db066be Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/12.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/120.jpg b/QuestPDF.ReportSample/Resources/Photos/120.jpg new file mode 100644 index 0000000..525f643 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/120.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/121.jpg b/QuestPDF.ReportSample/Resources/Photos/121.jpg new file mode 100644 index 0000000..0a6dee4 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/121.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/122.jpg b/QuestPDF.ReportSample/Resources/Photos/122.jpg new file mode 100644 index 0000000..f4d6a1e Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/122.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/123.jpg b/QuestPDF.ReportSample/Resources/Photos/123.jpg new file mode 100644 index 0000000..b7364f7 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/123.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/124.jpg b/QuestPDF.ReportSample/Resources/Photos/124.jpg new file mode 100644 index 0000000..4e8d0c0 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/124.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/125.jpg b/QuestPDF.ReportSample/Resources/Photos/125.jpg new file mode 100644 index 0000000..6ab65ad Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/125.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/126.jpg b/QuestPDF.ReportSample/Resources/Photos/126.jpg new file mode 100644 index 0000000..86acebf Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/126.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/127.jpg b/QuestPDF.ReportSample/Resources/Photos/127.jpg new file mode 100644 index 0000000..af70e03 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/127.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/13.jpg b/QuestPDF.ReportSample/Resources/Photos/13.jpg new file mode 100644 index 0000000..6ca4d79 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/13.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/14.jpg b/QuestPDF.ReportSample/Resources/Photos/14.jpg new file mode 100644 index 0000000..35c41df Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/14.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/15.jpg b/QuestPDF.ReportSample/Resources/Photos/15.jpg new file mode 100644 index 0000000..8ff2a3f Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/15.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/16.jpg b/QuestPDF.ReportSample/Resources/Photos/16.jpg new file mode 100644 index 0000000..03bec9b Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/16.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/17.jpg b/QuestPDF.ReportSample/Resources/Photos/17.jpg new file mode 100644 index 0000000..fa0211b Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/17.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/18.jpg b/QuestPDF.ReportSample/Resources/Photos/18.jpg new file mode 100644 index 0000000..c39907e Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/18.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/19.jpg b/QuestPDF.ReportSample/Resources/Photos/19.jpg new file mode 100644 index 0000000..3651c56 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/19.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/2.jpg b/QuestPDF.ReportSample/Resources/Photos/2.jpg new file mode 100644 index 0000000..f7f7da3 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/2.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/20.jpg b/QuestPDF.ReportSample/Resources/Photos/20.jpg new file mode 100644 index 0000000..f748423 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/20.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/21.jpg b/QuestPDF.ReportSample/Resources/Photos/21.jpg new file mode 100644 index 0000000..5d7edc4 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/21.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/22.jpg b/QuestPDF.ReportSample/Resources/Photos/22.jpg new file mode 100644 index 0000000..ed3c584 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/22.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/23.jpg b/QuestPDF.ReportSample/Resources/Photos/23.jpg new file mode 100644 index 0000000..2970ee7 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/23.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/24.jpg b/QuestPDF.ReportSample/Resources/Photos/24.jpg new file mode 100644 index 0000000..5008c4e Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/24.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/25.jpg b/QuestPDF.ReportSample/Resources/Photos/25.jpg new file mode 100644 index 0000000..a02c8fc Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/25.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/26.jpg b/QuestPDF.ReportSample/Resources/Photos/26.jpg new file mode 100644 index 0000000..56e8b29 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/26.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/27.jpg b/QuestPDF.ReportSample/Resources/Photos/27.jpg new file mode 100644 index 0000000..eb5b149 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/27.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/28.jpg b/QuestPDF.ReportSample/Resources/Photos/28.jpg new file mode 100644 index 0000000..aa7a065 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/28.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/29.jpg b/QuestPDF.ReportSample/Resources/Photos/29.jpg new file mode 100644 index 0000000..fa6a3fa Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/29.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/3.jpg b/QuestPDF.ReportSample/Resources/Photos/3.jpg new file mode 100644 index 0000000..0672813 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/3.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/30.jpg b/QuestPDF.ReportSample/Resources/Photos/30.jpg new file mode 100644 index 0000000..d334b88 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/30.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/31.jpg b/QuestPDF.ReportSample/Resources/Photos/31.jpg new file mode 100644 index 0000000..7df2c15 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/31.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/32.jpg b/QuestPDF.ReportSample/Resources/Photos/32.jpg new file mode 100644 index 0000000..7ac38f5 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/32.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/33.jpg b/QuestPDF.ReportSample/Resources/Photos/33.jpg new file mode 100644 index 0000000..e2b6eee Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/33.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/34.jpg b/QuestPDF.ReportSample/Resources/Photos/34.jpg new file mode 100644 index 0000000..673aaee Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/34.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/35.jpg b/QuestPDF.ReportSample/Resources/Photos/35.jpg new file mode 100644 index 0000000..5d8236b Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/35.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/36.jpg b/QuestPDF.ReportSample/Resources/Photos/36.jpg new file mode 100644 index 0000000..30399c1 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/36.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/37.jpg b/QuestPDF.ReportSample/Resources/Photos/37.jpg new file mode 100644 index 0000000..a0415af Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/37.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/38.jpg b/QuestPDF.ReportSample/Resources/Photos/38.jpg new file mode 100644 index 0000000..c985eb6 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/38.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/39.jpg b/QuestPDF.ReportSample/Resources/Photos/39.jpg new file mode 100644 index 0000000..0d0e3a4 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/39.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/4.jpg b/QuestPDF.ReportSample/Resources/Photos/4.jpg new file mode 100644 index 0000000..613015c Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/4.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/40.jpg b/QuestPDF.ReportSample/Resources/Photos/40.jpg new file mode 100644 index 0000000..25f0523 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/40.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/41.jpg b/QuestPDF.ReportSample/Resources/Photos/41.jpg new file mode 100644 index 0000000..293bb9a Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/41.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/42.jpg b/QuestPDF.ReportSample/Resources/Photos/42.jpg new file mode 100644 index 0000000..3bee557 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/42.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/43.jpg b/QuestPDF.ReportSample/Resources/Photos/43.jpg new file mode 100644 index 0000000..2e4d5c7 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/43.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/44.jpg b/QuestPDF.ReportSample/Resources/Photos/44.jpg new file mode 100644 index 0000000..49493f8 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/44.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/45.jpg b/QuestPDF.ReportSample/Resources/Photos/45.jpg new file mode 100644 index 0000000..152cf77 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/45.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/46.jpg b/QuestPDF.ReportSample/Resources/Photos/46.jpg new file mode 100644 index 0000000..dc63258 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/46.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/47.jpg b/QuestPDF.ReportSample/Resources/Photos/47.jpg new file mode 100644 index 0000000..ddfb8af Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/47.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/48.jpg b/QuestPDF.ReportSample/Resources/Photos/48.jpg new file mode 100644 index 0000000..8360c0f Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/48.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/49.jpg b/QuestPDF.ReportSample/Resources/Photos/49.jpg new file mode 100644 index 0000000..93dbb18 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/49.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/5.jpg b/QuestPDF.ReportSample/Resources/Photos/5.jpg new file mode 100644 index 0000000..dc2dbed Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/5.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/50.jpg b/QuestPDF.ReportSample/Resources/Photos/50.jpg new file mode 100644 index 0000000..4e2d200 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/50.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/51.jpg b/QuestPDF.ReportSample/Resources/Photos/51.jpg new file mode 100644 index 0000000..f1ff47a Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/51.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/52.jpg b/QuestPDF.ReportSample/Resources/Photos/52.jpg new file mode 100644 index 0000000..54ecd21 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/52.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/53.jpg b/QuestPDF.ReportSample/Resources/Photos/53.jpg new file mode 100644 index 0000000..c5ee848 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/53.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/54.jpg b/QuestPDF.ReportSample/Resources/Photos/54.jpg new file mode 100644 index 0000000..c484a4e Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/54.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/55.jpg b/QuestPDF.ReportSample/Resources/Photos/55.jpg new file mode 100644 index 0000000..f9d007a Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/55.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/56.jpg b/QuestPDF.ReportSample/Resources/Photos/56.jpg new file mode 100644 index 0000000..b816279 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/56.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/57.jpg b/QuestPDF.ReportSample/Resources/Photos/57.jpg new file mode 100644 index 0000000..08d23f2 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/57.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/58.jpg b/QuestPDF.ReportSample/Resources/Photos/58.jpg new file mode 100644 index 0000000..5449b7b Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/58.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/59.jpg b/QuestPDF.ReportSample/Resources/Photos/59.jpg new file mode 100644 index 0000000..152cf77 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/59.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/6.jpg b/QuestPDF.ReportSample/Resources/Photos/6.jpg new file mode 100644 index 0000000..673ca41 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/6.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/60.jpg b/QuestPDF.ReportSample/Resources/Photos/60.jpg new file mode 100644 index 0000000..9967d6c Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/60.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/61.jpg b/QuestPDF.ReportSample/Resources/Photos/61.jpg new file mode 100644 index 0000000..339279b Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/61.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/62.jpg b/QuestPDF.ReportSample/Resources/Photos/62.jpg new file mode 100644 index 0000000..3b27d56 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/62.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/63.jpg b/QuestPDF.ReportSample/Resources/Photos/63.jpg new file mode 100644 index 0000000..cf3f0bb Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/63.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/64.jpg b/QuestPDF.ReportSample/Resources/Photos/64.jpg new file mode 100644 index 0000000..7b9a32e Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/64.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/65.jpg b/QuestPDF.ReportSample/Resources/Photos/65.jpg new file mode 100644 index 0000000..28ba5c9 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/65.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/66.jpg b/QuestPDF.ReportSample/Resources/Photos/66.jpg new file mode 100644 index 0000000..f5cc738 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/66.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/67.jpg b/QuestPDF.ReportSample/Resources/Photos/67.jpg new file mode 100644 index 0000000..494d61b Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/67.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/68.jpg b/QuestPDF.ReportSample/Resources/Photos/68.jpg new file mode 100644 index 0000000..3e77149 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/68.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/69.jpg b/QuestPDF.ReportSample/Resources/Photos/69.jpg new file mode 100644 index 0000000..a9c988a Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/69.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/7.jpg b/QuestPDF.ReportSample/Resources/Photos/7.jpg new file mode 100644 index 0000000..68a2be1 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/7.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/70.jpg b/QuestPDF.ReportSample/Resources/Photos/70.jpg new file mode 100644 index 0000000..74fdb91 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/70.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/71.jpg b/QuestPDF.ReportSample/Resources/Photos/71.jpg new file mode 100644 index 0000000..56fd846 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/71.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/72.jpg b/QuestPDF.ReportSample/Resources/Photos/72.jpg new file mode 100644 index 0000000..039737a Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/72.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/73.jpg b/QuestPDF.ReportSample/Resources/Photos/73.jpg new file mode 100644 index 0000000..eb3d0b0 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/73.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/74.jpg b/QuestPDF.ReportSample/Resources/Photos/74.jpg new file mode 100644 index 0000000..ec2dc96 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/74.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/75.jpg b/QuestPDF.ReportSample/Resources/Photos/75.jpg new file mode 100644 index 0000000..a46717a Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/75.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/76.jpg b/QuestPDF.ReportSample/Resources/Photos/76.jpg new file mode 100644 index 0000000..e2b6eee Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/76.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/77.jpg b/QuestPDF.ReportSample/Resources/Photos/77.jpg new file mode 100644 index 0000000..e32bdce Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/77.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/78.jpg b/QuestPDF.ReportSample/Resources/Photos/78.jpg new file mode 100644 index 0000000..aa7a065 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/78.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/79.jpg b/QuestPDF.ReportSample/Resources/Photos/79.jpg new file mode 100644 index 0000000..d597ed2 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/79.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/8.jpg b/QuestPDF.ReportSample/Resources/Photos/8.jpg new file mode 100644 index 0000000..0b3737a Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/8.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/80.jpg b/QuestPDF.ReportSample/Resources/Photos/80.jpg new file mode 100644 index 0000000..60beca7 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/80.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/81.jpg b/QuestPDF.ReportSample/Resources/Photos/81.jpg new file mode 100644 index 0000000..34eacb9 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/81.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/82.jpg b/QuestPDF.ReportSample/Resources/Photos/82.jpg new file mode 100644 index 0000000..de8b226 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/82.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/83.jpg b/QuestPDF.ReportSample/Resources/Photos/83.jpg new file mode 100644 index 0000000..d8ff38b Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/83.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/84.jpg b/QuestPDF.ReportSample/Resources/Photos/84.jpg new file mode 100644 index 0000000..abce4ef Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/84.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/85.jpg b/QuestPDF.ReportSample/Resources/Photos/85.jpg new file mode 100644 index 0000000..b9af83b Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/85.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/86.jpg b/QuestPDF.ReportSample/Resources/Photos/86.jpg new file mode 100644 index 0000000..0887f69 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/86.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/87.jpg b/QuestPDF.ReportSample/Resources/Photos/87.jpg new file mode 100644 index 0000000..55bca6e Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/87.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/88.jpg b/QuestPDF.ReportSample/Resources/Photos/88.jpg new file mode 100644 index 0000000..bff4365 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/88.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/89.jpg b/QuestPDF.ReportSample/Resources/Photos/89.jpg new file mode 100644 index 0000000..2fc5259 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/89.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/9.jpg b/QuestPDF.ReportSample/Resources/Photos/9.jpg new file mode 100644 index 0000000..cea2573 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/9.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/90.jpg b/QuestPDF.ReportSample/Resources/Photos/90.jpg new file mode 100644 index 0000000..5be4bf4 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/90.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/91.jpg b/QuestPDF.ReportSample/Resources/Photos/91.jpg new file mode 100644 index 0000000..9dc4a08 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/91.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/92.jpg b/QuestPDF.ReportSample/Resources/Photos/92.jpg new file mode 100644 index 0000000..36ae31f Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/92.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/93.jpg b/QuestPDF.ReportSample/Resources/Photos/93.jpg new file mode 100644 index 0000000..493e78e Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/93.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/94.jpg b/QuestPDF.ReportSample/Resources/Photos/94.jpg new file mode 100644 index 0000000..51f2792 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/94.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/95.jpg b/QuestPDF.ReportSample/Resources/Photos/95.jpg new file mode 100644 index 0000000..90f449e Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/95.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/96.jpg b/QuestPDF.ReportSample/Resources/Photos/96.jpg new file mode 100644 index 0000000..5accae9 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/96.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/97.jpg b/QuestPDF.ReportSample/Resources/Photos/97.jpg new file mode 100644 index 0000000..86cd253 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/97.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/98.jpg b/QuestPDF.ReportSample/Resources/Photos/98.jpg new file mode 100644 index 0000000..f462072 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/98.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/99.jpg b/QuestPDF.ReportSample/Resources/Photos/99.jpg new file mode 100644 index 0000000..c795eb1 Binary files /dev/null and b/QuestPDF.ReportSample/Resources/Photos/99.jpg differ diff --git a/QuestPDF.ReportSample/Resources/Photos/readme.txt b/QuestPDF.ReportSample/Resources/Photos/readme.txt new file mode 100644 index 0000000..ce42e6f --- /dev/null +++ b/QuestPDF.ReportSample/Resources/Photos/readme.txt @@ -0,0 +1 @@ +Images from https://picsum.photos/ \ No newline at end of file diff --git a/QuestPDF.ReportSample/Resources/logo.png b/QuestPDF.ReportSample/Resources/logo.png new file mode 100644 index 0000000..a76efca Binary files /dev/null and b/QuestPDF.ReportSample/Resources/logo.png differ diff --git a/QuestPDF.ReportSample/Resources/logo.svg b/QuestPDF.ReportSample/Resources/logo.svg new file mode 100644 index 0000000..9a6330e --- /dev/null +++ b/QuestPDF.ReportSample/Resources/logo.svg @@ -0,0 +1,11 @@ + + + + + + + comp + any + + + diff --git a/QuestPDF.ReportSample/Tests.cs b/QuestPDF.ReportSample/Tests.cs new file mode 100644 index 0000000..aae8d8a --- /dev/null +++ b/QuestPDF.ReportSample/Tests.cs @@ -0,0 +1,64 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using NUnit.Framework; +using QuestPDF.Fluent; +using QuestPDF.Infrastructure; +using QuestPDF.ReportSample.Layouts; + +namespace QuestPDF.ReportSample +{ + public class ReportGeneration + { + [Test] + public void GenerateAndShow() + { + var reportModel = DataSource.GetReport(); + var report = new StandardReport(reportModel); + + var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf"); + using var stream = new FileStream(path, FileMode.Create); + report.Generate(stream); + + Process.Start("explorer.exe", path); + } + + [Test] + public void PerformanceBenchmark() + { + // test size + const int testSize = 1000; + const decimal performanceTarget = 5; // documents per second + + // create report models + var reports = Enumerable + .Range(0, testSize) + .Select(x => + { + var reportModel = DataSource.GetReport(); + return new StandardReport(reportModel); + }) + .ToList(); + + // generate documents + var sw = new Stopwatch(); + + sw.Start(); + var totalSize = reports.Select(x => x.Generate()).Sum(x => (long)x.Length); + sw.Stop(); + + // show summary + Console.WriteLine($"Total size: {totalSize:N0} bytes"); + + var performance = sw.ElapsedMilliseconds / (decimal)testSize; + var speed = 1000M / performance; + Console.WriteLine($"Test time: {sw.ElapsedMilliseconds} ms"); + Console.WriteLine($"Time per document: {performance:N} ms"); + Console.WriteLine($"Documents per second: {speed:N} d/s"); + + if (speed < performanceTarget) + throw new Exception("Rendering algorithm is too slow."); + } + } +} \ No newline at end of file diff --git a/QuestPDF.ReportSample/Typography.cs b/QuestPDF.ReportSample/Typography.cs new file mode 100644 index 0000000..b454d1b --- /dev/null +++ b/QuestPDF.ReportSample/Typography.cs @@ -0,0 +1,12 @@ +using QuestPDF.Fluent; +using QuestPDF.Infrastructure; + +namespace QuestPDF.ReportSample +{ + public static class Typography + { + public static TextStyle Title => TextStyle.Default.FontType("Helvetica").Color("#000000").Size(20); + public static TextStyle Headline => TextStyle.Default.FontType("Helvetica").Color("#047AED").Size(14); + public static TextStyle Normal => TextStyle.Default.FontType("Helvetica").Color("#000000").Size(10).LineHeight(1.25f).AlignLeft(); + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/AlignmentTests.cs b/QuestPDF.UnitTests/AlignmentTests.cs new file mode 100644 index 0000000..185dde1 --- /dev/null +++ b/QuestPDF.UnitTests/AlignmentTests.cs @@ -0,0 +1,107 @@ +using NUnit.Framework; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; +using QuestPDF.UnitTests.MeasureTest; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class AlignmentTests + { + [Test] + public void Measure_ShouldHandleNullChild() => new Alignment().MeasureWithoutChild(); + + [Test] + public void Draw_ShouldHandleNullChild() => new Alignment().DrawWithoutChild(); + + [Test] + public void Measure_ShouldReturnWrap_WhenChildReturnsWrap() + { + TestPlan + .For(x => new Alignment + { + Child = x.CreateChild("child") + }) + .MeasureElement(new Size(1000, 500)) + .ExpectChildMeasure("child", expectedInput: new Size(1000, 500), returns: new Wrap()) + .CheckMeasureResult(new Wrap()); + } + + [Test] + public void Draw_HorizontalCenter_VerticalCenter() + { + TestPlan + .For(x => new Alignment + { + Horizontal = HorizontalAlignment.Center, + Vertical = VerticalAlignment.Middle, + + Child = x.CreateChild("child") + }) + .DrawElement(new Size(1000, 500)) + .ExpectChildMeasure("child", expectedInput: new Size(1000, 500), returns: new PartialRender(new Size(400, 200))) + .ExpectCanvasTranslate(new Position(300, 150)) + .ExpectChildDraw("child", new Size(400, 200)) + .ExpectCanvasTranslate(new Position(-300, -150)) + .CheckDrawResult(); + } + + [Test] + public void Draw_HorizontalLeft_VerticalCenter() + { + TestPlan + .For(x => new Alignment + { + Horizontal = HorizontalAlignment.Left, + Vertical = VerticalAlignment.Middle, + + Child = x.CreateChild("child") + }) + .DrawElement(new Size(400, 300)) + .ExpectChildMeasure("child", expectedInput: new Size(400, 300), returns: new FullRender(new Size(100, 50))) + .ExpectCanvasTranslate(new Position(0, 125)) + .ExpectChildDraw("child", new Size(100, 50)) + .ExpectCanvasTranslate(new Position(0, -125)) + .CheckDrawResult(); + } + + [Test] + public void Draw_HorizontalCenter_VerticalBottom() + { + TestPlan + .For(x => new Alignment + { + Horizontal = HorizontalAlignment.Center, + Vertical = VerticalAlignment.Bottom, + + Child = x.CreateChild("child") + }) + .DrawElement(new Size(400, 300)) + .ExpectChildMeasure("child", expectedInput: new Size(400, 300), returns: new FullRender(new Size(100, 50))) + .ExpectCanvasTranslate(new Position(150, 250)) + .ExpectChildDraw("child", new Size(100, 50)) + .ExpectCanvasTranslate(new Position(-150, -250)) + .CheckDrawResult(); + } + + [Test] + public void Draw_HorizontalRight_VerticalTop() + { + TestPlan + .For(x => new Alignment + { + Horizontal = HorizontalAlignment.Right, + Vertical = VerticalAlignment.Top, + + Child = x.CreateChild("child") + }) + .DrawElement(new Size(400, 300)) + .ExpectChildMeasure("child", expectedInput: new Size(400, 300), returns: new FullRender(new Size(100, 50))) + .ExpectCanvasTranslate(new Position(300, 0)) + .ExpectChildDraw("child", new Size(100, 50)) + .ExpectCanvasTranslate(new Position(-300, 0)) + .CheckDrawResult(); + } + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/AspectRatioTests.cs b/QuestPDF.UnitTests/AspectRatioTests.cs new file mode 100644 index 0000000..d1ffd84 --- /dev/null +++ b/QuestPDF.UnitTests/AspectRatioTests.cs @@ -0,0 +1,19 @@ +using Moq; +using NUnit.Framework; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; +using QuestPDF.UnitTests.MeasureTest; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class AspectRatioTests + { + [Test] + public void Measure_ShouldHandleNullChild() => new AspectRatio().MeasureWithoutChild(); + + [Test] + public void Draw_ShouldHandleNullChild() => new AspectRatio().DrawWithoutChild(); + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/BackgroundTests.cs b/QuestPDF.UnitTests/BackgroundTests.cs new file mode 100644 index 0000000..b13a4d3 --- /dev/null +++ b/QuestPDF.UnitTests/BackgroundTests.cs @@ -0,0 +1,42 @@ +using NUnit.Framework; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; +using QuestPDF.UnitTests.MeasureTest; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class BackgroundTests + { + [Test] + public void Measure_ShouldHandleNullChild() => new Background().MeasureWithoutChild(); + + [Test] + public void Draw_ShouldHandleNullChild() + { + TestPlan + .For(x => new Background + { + Color = "#F00" + }) + .DrawElement(new Size(400, 300)) + .ExpectCanvasDrawRectangle(new Position(0, 0), new Size(400, 300), "#F00") + .CheckDrawResult(); + } + + [Test] + public void Draw_ShouldHandleChild() + { + TestPlan + .For(x => new Background + { + Color = "#F00", + Child = x.CreateChild("a") + }) + .DrawElement(new Size(400, 300)) + .ExpectCanvasDrawRectangle(new Position(0, 0), new Size(400, 300), "#F00") + .ExpectChildDraw("a", new Size(400, 300)) + .CheckDrawResult(); + } + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/BorderTests.cs b/QuestPDF.UnitTests/BorderTests.cs new file mode 100644 index 0000000..d73bcda --- /dev/null +++ b/QuestPDF.UnitTests/BorderTests.cs @@ -0,0 +1,58 @@ +using NUnit.Framework; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; +using QuestPDF.UnitTests.MeasureTest; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class BorderTests + { + [Test] + public void Measure_ShouldHandleNullChild() => new Border().MeasureWithoutChild(); + + [Test] + public void ComponentShouldNotAffectLayout() + { + TestPlan + .For(x => new Border + { + Top = 10, + Right = 20, + Bottom = 30, + Left = 40, + + Child = x.CreateChild("child") + }) + .MeasureElement(new Size(400, 300)) + .ExpectChildMeasure("child", expectedInput: new Size(400, 300), returns: new FullRender(new Size(100, 50))) + .CheckMeasureResult( new FullRender(new Size(100, 50))); + } + + [Test] + public void Draw_HorizontalRight_VerticalTop() + { + TestPlan + .For(x => new Border + { + Top = 10, + Right = 20, + Bottom = 30, + Left = 40, + + Color = "#FF0000", + + Child = x.CreateChild("child") + }) + .DrawElement(new Size(400, 300)) + .ExpectChildDraw("child", new Size(400, 300)) + .ExpectCanvasDrawRectangle(new Position(-20, -5), new Size(430, 10), "#FF0000") // top + .ExpectCanvasDrawRectangle(new Position(-20, -5), new Size(40, 320), "#FF0000") // left + .ExpectCanvasDrawRectangle(new Position(-20, 285), new Size(430, 30), "#FF0000") // bottom + .ExpectCanvasDrawRectangle(new Position(390, -5), new Size(20, 320), "#FF0000") // right + .ExpectChildDraw("child", new Size(400, 300)) + .CheckDrawResult(); + } + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/ConstrainedTests.cs b/QuestPDF.UnitTests/ConstrainedTests.cs new file mode 100644 index 0000000..9b3dbf0 --- /dev/null +++ b/QuestPDF.UnitTests/ConstrainedTests.cs @@ -0,0 +1,98 @@ +using NUnit.Framework; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; +using QuestPDF.UnitTests.MeasureTest; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class ConstrainedTests + { + [Test] + public void Measure_ShouldHandleNullChild() => new Constrained().MeasureWithoutChild(); + + [Test] + public void Draw_ShouldHandleNullChild() => new Constrained().DrawWithoutChild(); + + [Test] + public void Measure_MinHeight_ExpectWrap() + { + TestPlan + .For(x => new Constrained + { + MinHeight = 100 + }) + .MeasureElement(new Size(400, 50)) + .CheckMeasureResult(new Wrap()); + } + + [Test] + public void Measure_MinHeight_ExtendHeight() + { + TestPlan + .For(x => new Constrained + { + MinHeight = 100, + Child = x.CreateChild("a") + }) + .MeasureElement(new Size(400, 200)) + .ExpectChildMeasure("a", new Size(400, 200), new FullRender(400, 50)) + .CheckMeasureResult(new FullRender(400, 100)); + } + + [Test] + public void Measure_MinHeight_PassHeight() + { + TestPlan + .For(x => new Constrained + { + MinHeight = 100, + Child = x.CreateChild("a") + }) + .MeasureElement(new Size(400, 200)) + .ExpectChildMeasure("a", new Size(400, 200), new FullRender(400, 150)) + .CheckMeasureResult(new FullRender(400, 150)); + } + + [Test] + public void Measure_MaxHeight_Empty() + { + TestPlan + .For(x => new Constrained + { + MaxHeight = 100 + }) + .MeasureElement(new Size(400, 150)) + .CheckMeasureResult(new FullRender(0, 0)); + } + + [Test] + public void Measure_MaxHeight_PartialRender() + { + TestPlan + .For(x => new Constrained + { + MaxHeight = 100, + Child = x.CreateChild("a") + }) + .MeasureElement(new Size(400, 200)) + .ExpectChildMeasure("a", new Size(400, 100), new PartialRender(400, 75)) + .CheckMeasureResult(new PartialRender(400, 75)); + } + + [Test] + public void Measure_MaxHeight_ExpectWrap() + { + TestPlan + .For(x => new Constrained + { + MaxHeight = 100, + Child = x.CreateChild("a") + }) + .MeasureElement(new Size(400, 200)) + .ExpectChildMeasure("a", new Size(400, 100), new Wrap()) + .CheckMeasureResult(new Wrap()); + } + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/DebugTests.cs b/QuestPDF.UnitTests/DebugTests.cs new file mode 100644 index 0000000..97d9662 --- /dev/null +++ b/QuestPDF.UnitTests/DebugTests.cs @@ -0,0 +1,13 @@ +using NUnit.Framework; +using QuestPDF.Elements; +using QuestPDF.UnitTests.MeasureTest; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class DebugTests + { + [Test] + public void Measure_ShouldHandleNullChild() => new Debug().MeasureWithoutChild(); + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/DocumentTests.cs b/QuestPDF.UnitTests/DocumentTests.cs new file mode 100644 index 0000000..4a4742c --- /dev/null +++ b/QuestPDF.UnitTests/DocumentTests.cs @@ -0,0 +1,10 @@ +using NUnit.Framework; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class DocumentTests + { + + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/DynamicImageTests.cs b/QuestPDF.UnitTests/DynamicImageTests.cs new file mode 100644 index 0000000..1c06ae5 --- /dev/null +++ b/QuestPDF.UnitTests/DynamicImageTests.cs @@ -0,0 +1,10 @@ +using NUnit.Framework; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class DynamicImageTests + { + + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/EmptyTests.cs b/QuestPDF.UnitTests/EmptyTests.cs new file mode 100644 index 0000000..4826216 --- /dev/null +++ b/QuestPDF.UnitTests/EmptyTests.cs @@ -0,0 +1,10 @@ +using NUnit.Framework; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class EmptyTests + { + + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/ExtendTests.cs b/QuestPDF.UnitTests/ExtendTests.cs new file mode 100644 index 0000000..a523c62 --- /dev/null +++ b/QuestPDF.UnitTests/ExtendTests.cs @@ -0,0 +1,16 @@ +using NUnit.Framework; +using QuestPDF.Elements; +using QuestPDF.UnitTests.MeasureTest; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class ExtendTests + { + [Test] + public void Measure_ShouldHandleNullChild() => new Extend().MeasureWithoutChild(); + + [Test] + public void Draw_ShouldHandleNullChild() => new Extend().DrawWithoutChild(); + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/Helpers.cs b/QuestPDF.UnitTests/Helpers.cs new file mode 100644 index 0000000..b12db9e --- /dev/null +++ b/QuestPDF.UnitTests/Helpers.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using QuestPDF.Infrastructure; +using QuestPDF.UnitTests.MeasureTest; + +namespace QuestPDF.UnitTests +{ + public static class Helpers + { + public static Random Random = new Random(); + + public static Size RandomSize => new Size(Random.Next(200, 400), Random.Next(100, 200)); + + public static void ShouldEqual(this IEnumerable commands, IEnumerable expected) + { + commands.Should().HaveSameCount(expected); + + commands + .Zip(expected) + .ToList() + .ForEach(x => + { + x.First.Should().BeOfType(x.Second.GetType()); + x.First.Should().BeEquivalentTo(x.Second, y => y.RespectingRuntimeTypes()); + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/ImageTests.cs b/QuestPDF.UnitTests/ImageTests.cs new file mode 100644 index 0000000..5a828f8 --- /dev/null +++ b/QuestPDF.UnitTests/ImageTests.cs @@ -0,0 +1,25 @@ +using Moq; +using NUnit.Framework; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class ImageTests + { + [Test] + public void Draw_ShouldHandleNullChild() + { + Assert.DoesNotThrow(() => + { + var image = new Image() + { + Data = null + }; + + image.Draw(It.IsAny(), Size.Zero); + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/MeasureTest/MeasureTestMock.cs b/QuestPDF.UnitTests/MeasureTest/MeasureTestMock.cs new file mode 100644 index 0000000..6ea6007 --- /dev/null +++ b/QuestPDF.UnitTests/MeasureTest/MeasureTestMock.cs @@ -0,0 +1,405 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using FluentAssertions; +using NUnit.Framework; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; +using SkiaSharp; + +namespace QuestPDF.UnitTests.MeasureTest +{ + public abstract class Operation + { + + } + + public class ElementMeasureOperation : Operation + { + public ElementMeasureOperation(Size input) + { + + } + } + + internal class ChildMeasureOperation : Operation + { + public string ChildId { get; } + public Size Input { get; } + public ISpacePlan Output { get; } + + public ChildMeasureOperation(string childId, Size input, ISpacePlan output) + { + ChildId = childId; + Input = input; + Output = output; + } + } + + public class ChildDrawOperation : Operation + { + public string ChildId { get; } + public Size Input { get; } + + public ChildDrawOperation(string childId, Size input) + { + ChildId = childId; + Input = input; + } + } + + internal class CanvasTranslateOperation : Operation + { + public Position Position { get; } + + public CanvasTranslateOperation(Position position) + { + Position = position; + } + } + + internal class CanvasDrawRectangleOperation : Operation + { + public Position Position { get; } + public Size Size { get; } + public string Color { get; } + + public CanvasDrawRectangleOperation(Position position, Size size, string color) + { + Position = position; + Size = size; + Color = color; + } + } + + internal class CanvasDrawTextOperation : Operation + { + public string Text { get; } + public Position Position { get; } + public TextStyle Style { get; } + + public CanvasDrawTextOperation(string text, Position position, TextStyle style) + { + Text = text; + Position = position; + Style = style; + } + } + + internal class CanvasDrawImageOperation : Operation + { + public Position Position { get; } + public Size Size { get; } + + public CanvasDrawImageOperation(Position position, Size size) + { + Position = position; + Size = size; + } + } + + internal class ElementMock : Element + { + public string Id { get; set; } + public Func MeasureFunc { get; set; } + public Action DrawFunc { get; set; } + + internal override ISpacePlan Measure(Size availableSpace) => MeasureFunc(availableSpace); + internal override void Draw(ICanvas canvas, Size availableSpace) => DrawFunc(availableSpace); + } + + internal class CanvasMock : ICanvas + { + public Action TranslateFunc { get; set; } + public Action DrawImageFunc { get; set; } + public Action DrawTextFunc { get; set; } + public Action DrawRectFunc { get; set; } + + public void Translate(Position vector) => TranslateFunc(vector); + public void DrawRectangle(Position vector, Size size, string color) => DrawRectFunc(vector, size, color); + public void DrawText(string text, Position position, TextStyle style) => DrawTextFunc(text, position, style); + public void DrawImage(SKImage image, Position position, Size size) => DrawImageFunc(image, position, size); + + public void DrawLink(string url, Size size) + { + throw new NotImplementedException(); + } + + public Size MeasureText(string text, TextStyle style) + { + return new Size(text.Length * style.Size, style.Size); + } + } + + internal class TestPlan + { + private Element Element { get; set; } + private ICanvas Canvas { get; } + + private Size OperationInput { get; set; } + private Queue Operations { get; } = new Queue(); + + public TestPlan() + { + Canvas = CreateCanvas(); + } + + public static TestPlan For(Func create) + { + var plan = new TestPlan(); + plan.Element = create(plan); + + return plan; + } + + private T GetExpected() where T : Operation + { + if (Operations.TryDequeue(out var value) && value is T result) + return result; + + var gotType = value?.GetType()?.Name ?? "null"; + Assert.Fail($"Expected: {typeof(T).Name}, got {gotType}: {JsonSerializer.Serialize(value)}"); + return null; + } + + private ICanvas CreateCanvas() + { + return new CanvasMock + { + TranslateFunc = position => + { + var expected = GetExpected(); + + Assert.AreEqual(expected.Position.X, position.X); + Assert.AreEqual(expected.Position.Y, position.Y); + + //position.Should().BeEquivalentTo(expected.Position); + }, + DrawRectFunc = (position, size, color) => + { + var expected = GetExpected(); + + Assert.AreEqual(expected.Position.X, position.X); + Assert.AreEqual(expected.Position.Y, position.Y); + + Assert.AreEqual(expected.Size.Width, size.Width); + Assert.AreEqual(expected.Size.Height, size.Height); + + Assert.AreEqual(expected.Color, color); + + /*position.Should().BeEquivalentTo(expected.Position); + size.Should().BeEquivalentTo(expected.Size); + color.Should().Be(expected.Color);*/ + }, + DrawTextFunc = (text, position, style) => + { + var expected = GetExpected(); + + Assert.AreEqual(expected.Text, text); + + Assert.AreEqual(expected.Position.X, position.X); + Assert.AreEqual(expected.Position.Y, position.Y); + + Assert.AreEqual(expected.Style.Color, style.Color); + Assert.AreEqual(expected.Style.FontType, style.FontType); + Assert.AreEqual(expected.Style.Size, style.Size); + + /*text.Should().Be(expected.Text); + position.Should().BeEquivalentTo(expected.Position); + style.Should().BeEquivalentTo(expected.Style);*/ + }, + DrawImageFunc = (image, position, size) => + { + var expected = GetExpected(); + + Assert.AreEqual(expected.Position.X, position.X); + Assert.AreEqual(expected.Position.Y, position.Y); + + Assert.AreEqual(expected.Size.Width, size.Width); + Assert.AreEqual(expected.Size.Height, size.Height); + + /*position.Should().BeEquivalentTo(expected.Position); + size.Should().BeEquivalentTo(expected.Size);*/ + } + }; + } + + public Element CreateChild(string id) + { + return new ElementMock + { + Id = id, + MeasureFunc = availableSpace => + { + var expected = GetExpected(); + + Assert.AreEqual(expected.ChildId, id); + + Assert.AreEqual(expected.Input.Width, availableSpace.Width); + Assert.AreEqual(expected.Input.Height, availableSpace.Height); + + // id.Should().Be(expected.ChildId); + // availableSpace.Should().Be(expected.Input); + + return expected.Output; + }, + DrawFunc = availableSpace => + { + var expected = GetExpected(); + + Assert.AreEqual(expected.ChildId, id); + + Assert.AreEqual(expected.Input.Width, availableSpace.Width); + Assert.AreEqual(expected.Input.Height, availableSpace.Height); + + /*id.Should().Be(expected.ChildId); + availableSpace.Should().Be(expected.Input);*/ + } + }; + } + + public TestPlan MeasureElement(Size input) + { + OperationInput = input; + return this; + } + + public TestPlan DrawElement(Size input) + { + OperationInput = input; + return this; + } + + private TestPlan AddOperation(Operation operation) + { + Operations.Enqueue(operation); + return this; + } + + public TestPlan ExpectChildMeasure(string child, Size expectedInput, ISpacePlan returns) + { + return AddOperation(new ChildMeasureOperation(child, expectedInput, returns)); + } + + public TestPlan ExpectChildDraw(string child, Size expectedInput) + { + return AddOperation(new ChildDrawOperation(child, expectedInput)); + } + + public TestPlan ExpectCanvasTranslate(Position position) + { + return AddOperation(new CanvasTranslateOperation(position)); + } + + public TestPlan ExpectCanvasTranslate(float left, float top) + { + return AddOperation(new CanvasTranslateOperation(new Position(left, top))); + } + + public TestPlan ExpectCanvasDrawRectangle(Position position, Size size, string color) + { + return AddOperation(new CanvasDrawRectangleOperation(position, size, color)); + } + + public TestPlan ExpectCanvasDrawText(string text, Position position, TextStyle style) + { + return AddOperation(new CanvasDrawTextOperation(text, position, style)); + } + + public TestPlan ExpectCanvasDrawImage(Position position, Size size) + { + return AddOperation(new CanvasDrawImageOperation(position, size)); + } + + public TestPlan CheckMeasureResult(ISpacePlan expected) + { + var actual = Element.Measure(OperationInput); + + Assert.AreEqual(expected.GetType(), actual.GetType()); + + var expectedSize = expected as Size; + var actualSize = actual as Size; + + if (expectedSize != null) + { + Assert.AreEqual(expectedSize.Width, actualSize.Width); + Assert.AreEqual(expectedSize.Height, actualSize.Height); + } + + return this; + } + + public TestPlan CheckDrawResult() + { + Element.Draw(Canvas, OperationInput); + return this; + } + } + + public static class SingleChildTests + { + internal static void MeasureWithoutChild(this T element) where T : ContainerElement + { + element.Child = null; + element.Measure(Size.Zero).Should().BeEquivalentTo(new FullRender(Size.Zero)); + } + + internal static void DrawWithoutChild(this T element) where T : ContainerElement + { + // component does not throw an exception when called with null child + Assert.DoesNotThrow(() => + { + element.Child = null; + + // component does not perform any canvas operation when called with null child + TestPlan + .For(x => element) + .DrawElement(new Size(200, 100)) + .CheckDrawResult(); + }); + } + } + + [TestFixture] + public class LetsTest + { + [Test] + public void TestExample() + { + TestPlan + .For(x => new Padding + { + Top = 5, + Right = 10, + Bottom = 15, + Left = 20, + + Child = x.CreateChild("child") + }) + .MeasureElement(new Size(200, 100)) + .ExpectChildMeasure("child", expectedInput: new Size(170, 80), returns: new FullRender(new Size(100, 50))) + .CheckMeasureResult(new FullRender(130, 70)); + } + + [Test] + public void TestExample2() + { + TestPlan + .For(x => new Padding + { + Top = 5, + Right = 10, + Bottom = 15, + Left = 20, + + Child = x.CreateChild("child") + }) + .DrawElement(new Size(200, 100)) + .ExpectCanvasTranslate(new Position(20, 5)) + .ExpectChildDraw("child", expectedInput: new Size(170, 80)) + .ExpectCanvasTranslate(new Position(-20, -5)) + .CheckDrawResult(); + } + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/PaddingTests.cs b/QuestPDF.UnitTests/PaddingTests.cs new file mode 100644 index 0000000..2bd9946 --- /dev/null +++ b/QuestPDF.UnitTests/PaddingTests.cs @@ -0,0 +1,78 @@ +using FluentAssertions; +using Moq; +using NUnit.Framework; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; +using QuestPDF.UnitTests.MeasureTest; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class PaddingTests + { + [Test] + public void Measure_ShouldHandleNullChild() => new Padding().MeasureWithoutChild(); + + [Test] + public void Draw_ShouldHandleNullChild() => new Padding().DrawWithoutChild(); + + private Padding GetPadding(Element child) + { + return new Padding() + { + Top = 5, + Right = 10, + Bottom = 15, + Left = 20, + + Child = child + }; + } + + [Test] + public void Measure_WhenChildReturnsWrap_ReturnsWrap() + { + var child = new Mock(); + + child + .Setup(x => x.Measure(It.IsAny())) + .Returns(() => new Wrap()); + + GetPadding(child.Object) + .Measure(Size.Zero) + .Should() + .BeOfType(); + } + + [Test] + public void Measure_WhenChildReturnsFullRender_ReturnsFullRender() + { + var child = new Mock(); + + child + .Setup(x => x.Measure(It.IsAny())) + .Returns(() => new FullRender(Size.Zero)); + + GetPadding(child.Object) + .Measure(Size.Zero) + .Should() + .BeOfType(); + } + + [Test] + public void Measure_WhenChildReturnsPartialRender_ReturnsPartialRender() + { + var child = new Mock(); + + child + .Setup(x => x.Measure(It.IsAny())) + .Returns(() => new PartialRender(Size.Zero)); + + GetPadding(child.Object) + .Measure(Size.Zero) + .Should() + .BeOfType(); + } + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/PageBreakTests.cs b/QuestPDF.UnitTests/PageBreakTests.cs new file mode 100644 index 0000000..7bc48a9 --- /dev/null +++ b/QuestPDF.UnitTests/PageBreakTests.cs @@ -0,0 +1,22 @@ +using FluentAssertions; +using NUnit.Framework; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class PageBreakTests + { + [Test] + public void Measure_ShouldReturnWrapThenFullRender() + { + var pageBreak = new PageBreak(); + + pageBreak.Measure(Helpers.RandomSize).Should().BeOfType(); + pageBreak.Draw(null, Size.Zero); + pageBreak.Measure(Helpers.RandomSize).Should().BeEquivalentTo(new FullRender(Size.Zero)); + } + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/PageNumberTests.cs b/QuestPDF.UnitTests/PageNumberTests.cs new file mode 100644 index 0000000..4efd17b --- /dev/null +++ b/QuestPDF.UnitTests/PageNumberTests.cs @@ -0,0 +1,10 @@ +using NUnit.Framework; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class PageNumberTests + { + + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/PageTests.cs b/QuestPDF.UnitTests/PageTests.cs new file mode 100644 index 0000000..c364d89 --- /dev/null +++ b/QuestPDF.UnitTests/PageTests.cs @@ -0,0 +1,10 @@ +using NUnit.Framework; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class PageTests + { + + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/PageableColumnTests.cs b/QuestPDF.UnitTests/PageableColumnTests.cs new file mode 100644 index 0000000..140838c --- /dev/null +++ b/QuestPDF.UnitTests/PageableColumnTests.cs @@ -0,0 +1,10 @@ +using NUnit.Framework; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class PageableColumnTests + { + + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/PlaceholderTests.cs b/QuestPDF.UnitTests/PlaceholderTests.cs new file mode 100644 index 0000000..c9be9d6 --- /dev/null +++ b/QuestPDF.UnitTests/PlaceholderTests.cs @@ -0,0 +1,10 @@ +using NUnit.Framework; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class PlaceholderTests + { + + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/QuestPDF.UnitTests.csproj b/QuestPDF.UnitTests/QuestPDF.UnitTests.csproj new file mode 100644 index 0000000..1e93bd5 --- /dev/null +++ b/QuestPDF.UnitTests/QuestPDF.UnitTests.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp3.1 + false + + + + + + + + + + + + + + + + diff --git a/QuestPDF.UnitTests/RowTests.cs b/QuestPDF.UnitTests/RowTests.cs new file mode 100644 index 0000000..ab83f45 --- /dev/null +++ b/QuestPDF.UnitTests/RowTests.cs @@ -0,0 +1,10 @@ +using NUnit.Framework; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class RowTests + { + + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/SectionTests.cs b/QuestPDF.UnitTests/SectionTests.cs new file mode 100644 index 0000000..ebbe56c --- /dev/null +++ b/QuestPDF.UnitTests/SectionTests.cs @@ -0,0 +1,10 @@ +using NUnit.Framework; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class SectionTests + { + + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/ShowOnceTest.cs b/QuestPDF.UnitTests/ShowOnceTest.cs new file mode 100644 index 0000000..c87ed7b --- /dev/null +++ b/QuestPDF.UnitTests/ShowOnceTest.cs @@ -0,0 +1,81 @@ +using Moq; +using NUnit.Framework; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; +using QuestPDF.UnitTests.MeasureTest; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class ShowOnceTest + { + [Test] + public void Measure_ShouldHandleNullChild() => new ShowOnce().MeasureWithoutChild(); + + [Test] + public void Draw_ShouldHandleNullChild() => new ShowOnce().DrawWithoutChild(); + + [Test] + public void ShouldRenderOnce_WhenRenderingCalledMultipleTimes() + { + var child = new Mock(); + + child + .Setup(x => x.Measure(It.IsAny())) + .Returns(() => new FullRender(Size.Zero)); + + var element = new ShowOnce() + { + Child = child.Object + }; + + element.Draw(null, Size.Zero); + element.Draw(null, Size.Zero); + + child.Verify(x => x.Draw(It.IsAny(), It.IsAny()), Times.Once); + } + + [Test] + public void Draw_HorizontalRight_VerticalTop() + { + TestPlan + .For(x => new ShowOnce() + { + Child = x.CreateChild("child") + }) + + // Measure the element and return result + .MeasureElement(new Size(300, 200)) + .ExpectChildMeasure("child", new Size(300, 200), new PartialRender(new Size(200, 200))) + .CheckMeasureResult(new PartialRender(new Size(200, 200))) + + // Draw element partially + .DrawElement(new Size(200, 200)) + .ExpectChildMeasure("child", new Size(200, 200), new PartialRender(new Size(200, 200))) + .ExpectChildDraw("child", new Size(200, 200)) + .CheckDrawResult() + + // Element was not fully drawn + // It should be measured again for rendering on next page + .MeasureElement(new Size(800, 200)) + .ExpectChildMeasure("child", new Size(800, 200), new FullRender(new Size(400, 200))) + .CheckMeasureResult(new FullRender(new Size(400, 200))) + + // Draw element on next page + // Element was fully drawn at this point + .DrawElement(new Size(400, 200)) + .ExpectChildMeasure("child", new Size(400, 200), new FullRender(new Size(400, 200))) + .ExpectChildDraw("child", new Size(400, 200)) + .CheckDrawResult() + + // In the next attempt of measuring element, it should behave like empty parent. + .MeasureElement(new Size(600, 200)) + .CheckMeasureResult(new FullRender(Size.Zero)) + + // In the next attempt of measuring element, it should not draw its child + .DrawElement(new Size(600, 200)) + .CheckDrawResult(); + } + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/StackTests.cs b/QuestPDF.UnitTests/StackTests.cs new file mode 100644 index 0000000..3fea63f --- /dev/null +++ b/QuestPDF.UnitTests/StackTests.cs @@ -0,0 +1,291 @@ +using NUnit.Framework; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; +using QuestPDF.UnitTests.MeasureTest; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class StackTests + { + [Test] + public void Measure_NoChildren_Empty() + { + TestPlan + .For(x => new Stack + { + Spacing = 100 + }) + .MeasureElement(new Size(500, 1000)) + .CheckMeasureResult(new FullRender(Size.Zero)); + } + + [Test] + public void Measure_ReturnsWrap_WhenPageable_AndAnyChildReturnsWrap() + { + TestPlan + .For(x => new Stack + { + Spacing = 100, + Pageable = true, + Children = new [] + { + x.CreateChild("a"), + x.CreateChild("b"), + x.CreateChild("c"), + x.CreateChild("d") + } + }) + .MeasureElement(new Size(500, 1000)) + .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(500, 200)) + .ExpectChildMeasure("b", expectedInput: new Size(500, 700), returns: new FullRender(500, 300)) + .ExpectChildMeasure("c", expectedInput: new Size(500, 300), returns: new Wrap()) + .CheckMeasureResult(new PartialRender(500, 600)); + } + + [Test] + public void Measure_ReturnsWrap_WhenPageable_AndFirstChildReturnsWrap() + { + TestPlan + .For(x => new Stack + { + Spacing = 100, + Pageable = true, + Children = new [] + { + x.CreateChild("a"), + x.CreateChild("b"), + x.CreateChild("c") + } + }) + .MeasureElement(new Size(500, 1000)) + .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new Wrap()) + .CheckMeasureResult(new Wrap()); + } + + [Test] + public void Measure_ReturnsWrap_WhenNotPageable_AndAnyChildReturnsPartialRender() + { + TestPlan + .For(x => new Stack + { + Spacing = 50, + Pageable = false, + Children = new [] + { + x.CreateChild("a"), + x.CreateChild("b"), + x.CreateChild("d"), + x.CreateChild("e") + } + }) + .MeasureElement(new Size(500, 1000)) + .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(400, 200)) + .ExpectChildMeasure("b", expectedInput: new Size(500, 750), returns: new PartialRender(300, 500)) + .CheckMeasureResult(new Wrap()); + } + + [Test] + public void Measure_DoNotApplySpacing_WhenNotPageable_AndChildReturnsNoContent() + { + TestPlan + .For(x => new Stack + { + Spacing = 50, + Pageable = false, + Children = new [] + { + x.CreateChild("a"), + x.CreateChild("b"), + x.CreateChild("c"), + x.CreateChild("d"), + x.CreateChild("e") + } + }) + .MeasureElement(new Size(500, 1000)) + .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(400, 200)) + .ExpectChildMeasure("b", expectedInput: new Size(500, 750), returns: new FullRender(Size.Zero)) + .ExpectChildMeasure("c", expectedInput: new Size(500, 750), returns: new FullRender(Size.Zero)) + .ExpectChildMeasure("d", expectedInput: new Size(500, 750), returns: new FullRender(300, 300)) + .ExpectChildMeasure("e", expectedInput: new Size(500, 400), returns: new FullRender(300, 100)) + .CheckMeasureResult(new FullRender(500, 700)); + } + + [Test] + public void MultipleDraw_WhenNotPageable() + { + TestPlan + .For(x => new Stack + { + Spacing = 50, + Pageable = false, + Children = new [] + { + x.CreateChild("a") + } + }) + .MeasureElement(new Size(500, 1000)) + .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(400, 200)) + .CheckMeasureResult(new FullRender(500, 200)) + + // second measure resets element state + .MeasureElement(new Size(500, 1000)) + .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(400, 200)) + .CheckMeasureResult(new FullRender(500, 200)); + } + + [Test] + public void WhenPageable_AndChildReturnsWrap() + { + TestPlan + .For(x => new Stack + { + Spacing = 50, + Pageable = true, + Children = new[] + { + x.CreateChild("a"), + x.CreateChild("b"), + x.CreateChild("c") + } + }) + + // page 1: measure + .MeasureElement(new Size(500, 1000)) + .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(400, 200)) + .ExpectChildMeasure("b", expectedInput: new Size(500, 750), returns: new Wrap()) + .CheckMeasureResult(new PartialRender(500, 200)) + + // page 1: draw + .DrawElement(new Size(500, 1000)) + + .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(400, 200)) + .ExpectCanvasTranslate(0, 0) + .ExpectChildDraw("a", new Size(500, 200)) + .ExpectCanvasTranslate(0, 0) + + .ExpectChildMeasure("b", expectedInput: new Size(500, 750), returns: new Wrap()) + + .CheckDrawResult() + + // // page 2: measure + .MeasureElement(new Size(500, 900)) + .ExpectChildMeasure("b", expectedInput: new Size(500, 900), returns: new FullRender(300, 100)) + .ExpectChildMeasure("c", expectedInput: new Size(500, 750), returns: new FullRender(300, 250)) + .CheckMeasureResult(new FullRender(500, 400)) + + // page 2: draw + .DrawElement(new Size(500, 900)) + + .ExpectChildMeasure("b", expectedInput: new Size(500, 900), returns: new FullRender(300, 100)) + .ExpectCanvasTranslate(0, 0) + .ExpectChildDraw("b", new Size(500, 100)) + .ExpectCanvasTranslate(0, 0) + + .ExpectChildMeasure("c", expectedInput: new Size(500, 750), returns: new FullRender(300, 250)) + .ExpectCanvasTranslate(0, 150) + .ExpectChildDraw("c", new Size(500, 250)) + .ExpectCanvasTranslate(0, -150) + + .CheckDrawResult(); + } + + [Test] + public void Draw_ChildrenArePlacedCorrectly_WhenNotPageable() + { + TestPlan + .For(x => new Stack + { + Spacing = 50, + Pageable = false, + Children = new[] + { + x.CreateChild("a"), + x.CreateChild("b"), + x.CreateChild("c"), + x.CreateChild("d") + } + }) + .DrawElement(new Size(500, 1000)) + + .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(400, 200)) + .ExpectCanvasTranslate(0, 0) + .ExpectChildDraw("a", new Size(500, 200)) + .ExpectCanvasTranslate(0, 0) + + .ExpectChildMeasure("b", expectedInput: new Size(500, 750), returns: new FullRender(300, 300)) + .ExpectCanvasTranslate(0, 250) + .ExpectChildDraw("b", new Size(500, 300)) + .ExpectCanvasTranslate(0, -250) + + .ExpectChildMeasure("c", expectedInput: new Size(500, 400), returns: new FullRender(Size.Zero)) + + .ExpectChildMeasure("d", expectedInput: new Size(500, 400), returns: new FullRender(200, 400)) + .ExpectCanvasTranslate(0, 600) + .ExpectChildDraw("d", new Size(500, 400)) + .ExpectCanvasTranslate(0, -600) + + .CheckDrawResult(); + } + + [Test] + public void WhenPageable_AndChildReturnsPartialRender() + { + TestPlan + .For(x => new Stack + { + Spacing = 50, + Pageable = true, + Children = new[] + { + x.CreateChild("a"), + x.CreateChild("b"), + x.CreateChild("c") + } + }) + + // page 1: measure + .MeasureElement(new Size(500, 1000)) + .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(400, 200)) + .ExpectChildMeasure("b", expectedInput: new Size(500, 750), returns: new PartialRender(300, 300)) + .CheckMeasureResult(new PartialRender(500, 550)) + + // page 1: draw + .DrawElement(new Size(500, 1000)) + + .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(400, 200)) + .ExpectCanvasTranslate(0, 0) + .ExpectChildDraw("a", new Size(500, 200)) + .ExpectCanvasTranslate(0, 0) + + .ExpectChildMeasure("b", expectedInput: new Size(500, 750), returns: new PartialRender(300, 300)) + .ExpectCanvasTranslate(0, 250) + .ExpectChildDraw("b", new Size(500, 300)) + .ExpectCanvasTranslate(0, -250) + + .CheckDrawResult() + + // // page 2: measure + .MeasureElement(new Size(500, 900)) + .ExpectChildMeasure("b", expectedInput: new Size(500, 900), returns: new FullRender(300, 100)) + .ExpectChildMeasure("c", expectedInput: new Size(500, 750), returns: new FullRender(300, 250)) + .CheckMeasureResult(new FullRender(500, 400)) + + // page 2: draw + .DrawElement(new Size(500, 900)) + + .ExpectChildMeasure("b", expectedInput: new Size(500, 900), returns: new FullRender(300, 100)) + .ExpectCanvasTranslate(0, 0) + .ExpectChildDraw("b", new Size(500, 100)) + .ExpectCanvasTranslate(0, 0) + + .ExpectChildMeasure("c", expectedInput: new Size(500, 750), returns: new FullRender(300, 250)) + .ExpectCanvasTranslate(0, 150) + .ExpectChildDraw("c", new Size(500, 250)) + .ExpectCanvasTranslate(0, -150) + + .CheckDrawResult(); + } + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/TemplateTests.cs b/QuestPDF.UnitTests/TemplateTests.cs new file mode 100644 index 0000000..c4f6b86 --- /dev/null +++ b/QuestPDF.UnitTests/TemplateTests.cs @@ -0,0 +1,10 @@ +using NUnit.Framework; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class TemplateTests + { + + } +} \ No newline at end of file diff --git a/QuestPDF.UnitTests/TextTests.cs b/QuestPDF.UnitTests/TextTests.cs new file mode 100644 index 0000000..95dc0bb --- /dev/null +++ b/QuestPDF.UnitTests/TextTests.cs @@ -0,0 +1,10 @@ +using NUnit.Framework; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class TextTests + { + + } +} \ No newline at end of file diff --git a/QuestPDF.sln b/QuestPDF.sln new file mode 100644 index 0000000..db070d2 --- /dev/null +++ b/QuestPDF.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.ReportSample", "QuestPDF.ReportSample\QuestPDF.ReportSample.csproj", "{6718C16B-CB31-46E0-A9A6-920EBC36BE4D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF", "QuestPDF\QuestPDF.csproj", "{1FA7DFE3-8FBF-4D14-887F-03231B75F93F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.UnitTests", "QuestPDF.UnitTests\QuestPDF.UnitTests.csproj", "{4D0CC9F4-46D9-4015-AE24-08A5B5CC7AE7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.Examples", "QuestPDF.Examples\QuestPDF.Examples.csproj", "{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6718C16B-CB31-46E0-A9A6-920EBC36BE4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6718C16B-CB31-46E0-A9A6-920EBC36BE4D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6718C16B-CB31-46E0-A9A6-920EBC36BE4D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6718C16B-CB31-46E0-A9A6-920EBC36BE4D}.Release|Any CPU.Build.0 = Release|Any CPU + {1FA7DFE3-8FBF-4D14-887F-03231B75F93F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FA7DFE3-8FBF-4D14-887F-03231B75F93F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FA7DFE3-8FBF-4D14-887F-03231B75F93F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FA7DFE3-8FBF-4D14-887F-03231B75F93F}.Release|Any CPU.Build.0 = Release|Any CPU + {4D0CC9F4-46D9-4015-AE24-08A5B5CC7AE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D0CC9F4-46D9-4015-AE24-08A5B5CC7AE7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D0CC9F4-46D9-4015-AE24-08A5B5CC7AE7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D0CC9F4-46D9-4015-AE24-08A5B5CC7AE7}.Release|Any CPU.Build.0 = Release|Any CPU + {8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/QuestPDF/Assembly.cs b/QuestPDF/Assembly.cs new file mode 100644 index 0000000..1078607 --- /dev/null +++ b/QuestPDF/Assembly.cs @@ -0,0 +1,5 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +[assembly: InternalsVisibleTo("QuestPDF.UnitTests")] +[assembly: InternalsVisibleTo("QuestPDF.Examples")] \ No newline at end of file diff --git a/QuestPDF/Drawing/Canvas.cs b/QuestPDF/Drawing/Canvas.cs new file mode 100644 index 0000000..46bf8f9 --- /dev/null +++ b/QuestPDF/Drawing/Canvas.cs @@ -0,0 +1,49 @@ +using QuestPDF.Infrastructure; +using SkiaSharp; + +namespace QuestPDF.Drawing +{ + internal class Canvas : ICanvas + { + private SKCanvas SkiaCanvas { get; } + + public Canvas(SKCanvas skiaCanvas) + { + SkiaCanvas = skiaCanvas; + } + + ~Canvas() + { + SkiaCanvas.Dispose(); + } + + public void Translate(Position vector) + { + SkiaCanvas.Translate(vector.X, vector.Y); + } + + public void DrawRectangle(Position vector, Size size, string color) + { + if (size.Width < Size.Epsilon || size.Height < Size.Epsilon) + return; + + var paint = color.ColorToPaint(); + SkiaCanvas.DrawRect(vector.X, vector.Y, size.Width, size.Height, paint); + } + + public void DrawText(string text, Position vector, TextStyle style) + { + SkiaCanvas.DrawText(text, vector.X, vector.Y, style.ToPaint()); + } + + public void DrawImage(SKImage image, Position vector, Size size) + { + SkiaCanvas.DrawImage(image, new SKRect(vector.X, vector.Y, size.Width, size.Height)); + } + + public void DrawLink(string url, Size size) + { + SkiaCanvas.DrawUrlAnnotation(new SKRect(0, 0, size.Width, size.Height), url); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Drawing/CanvasCache.cs b/QuestPDF/Drawing/CanvasCache.cs new file mode 100644 index 0000000..8319e27 --- /dev/null +++ b/QuestPDF/Drawing/CanvasCache.cs @@ -0,0 +1,62 @@ +using System.Collections.Concurrent; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Infrastructure; +using SkiaSharp; + +namespace QuestPDF.Drawing +{ + internal static class CanvasCache + { + private static ConcurrentDictionary Fonts = new ConcurrentDictionary(); + private static ConcurrentDictionary Paints = new ConcurrentDictionary(); + private static ConcurrentDictionary ColorPaint = new ConcurrentDictionary(); + + internal static SKPaint ColorToPaint(this string color) + { + return ColorPaint.GetOrAdd(color, Convert); + + static SKPaint Convert(string color) + { + return new SKPaint + { + Color = SKColor.Parse(color) + }; + } + } + + internal static SKPaint ToPaint(this TextStyle style) + { + return Paints.GetOrAdd(style.ToString(), key => Convert(style)); + + static SKPaint Convert(TextStyle style) + { + return new SKPaint + { + Color = SKColor.Parse(style.Color), + Typeface = Fonts.GetOrAdd(style.FontType, SKTypeface.FromFamilyName), + TextSize = style.Size, + IsLinearText = true, + + TextAlign = style.Alignment switch + { + HorizontalAlignment.Left => SKTextAlign.Left, + HorizontalAlignment.Center => SKTextAlign.Center, + HorizontalAlignment.Right => SKTextAlign.Right, + _ => SKTextAlign.Left + } + }; + } + } + + internal static TextMeasurement BreakText(this TextStyle style, string text, float availableWidth) + { + var index = (int)style.ToPaint().BreakText(text, availableWidth, out var width); + + return new TextMeasurement() + { + LineIndex = index, + FragmentWidth = width + }; + } + } +} \ No newline at end of file diff --git a/QuestPDF/Drawing/DocumentGenerator.cs b/QuestPDF/Drawing/DocumentGenerator.cs new file mode 100644 index 0000000..2578554 --- /dev/null +++ b/QuestPDF/Drawing/DocumentGenerator.cs @@ -0,0 +1,84 @@ +using System; +using System.IO; +using QuestPDF.Drawing.Exceptions; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Elements; +using QuestPDF.Fluent; +using QuestPDF.Infrastructure; +using SkiaSharp; + +namespace QuestPDF.Drawing +{ + static class DocumentGenerator + { + const int DocumentLayoutExceptionThreshold = 250; + private static readonly Watermark Watermark = new Watermark(); + + internal static void Generate(Stream stream, IDocument document) + { + var content = ElementExtensions.Create(document.Compose); + var metadata = document.GetMetadata(); + + using var pdf = SKDocument.CreatePdf(stream, MapMetadata(metadata)); + var totalPages = 1; + + while(true) + { + var spacePlan = content.Measure(metadata.Size); + + using var skiaCanvas = pdf.BeginPage(metadata.Size.Width, metadata.Size.Height); + var canvas = new Canvas(skiaCanvas); + + try + { + content.Draw(canvas, metadata.Size); + } + catch (Exception exception) + { + pdf.Close(); + stream.Close(); + + throw new DocumentDrawingException("An exception occured during document drawing.", exception); + } + + pdf.EndPage(); + + if (totalPages >= DocumentLayoutExceptionThreshold) + { + pdf.Close(); + stream.Close(); + + throw new DocumentLayoutException("Composed layout generates infinite document."); + } + + if (spacePlan is FullRender) + break; + + totalPages++; + } + + pdf.Close(); + stream.Dispose(); + } + + private static SKDocumentPdfMetadata MapMetadata(DocumentMetadata metadata) + { + return new SKDocumentPdfMetadata + { + Title = metadata.Title, + Author = metadata.Author, + Subject = metadata.Subject, + Keywords = metadata.Keywords, + Creator = metadata.Creator, + Producer = metadata.Producer, + + Creation = metadata.CreationDate, + Modified = metadata.ModifiedDate, + + RasterDpi = metadata.RasterDpi, + EncodingQuality = metadata.ImageQuality, + PdfA = metadata.PdfA + }; + } + } +} \ No newline at end of file diff --git a/QuestPDF/Drawing/DocumentMetadata.cs b/QuestPDF/Drawing/DocumentMetadata.cs new file mode 100644 index 0000000..f23ba27 --- /dev/null +++ b/QuestPDF/Drawing/DocumentMetadata.cs @@ -0,0 +1,26 @@ +using System; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Drawing +{ + public class DocumentMetadata + { + public Size Size { get; set; } = PageSizes.A4; + public int ImageQuality { get; set; } = 101; + public int RasterDpi { get; set; } = 72; + public bool PdfA { get; set; } + + public string? Title { get; set; } + public string? Author { get; set; } + public string? Subject { get; set; } + public string? Keywords { get; set; } + public string? Creator { get; set; } + public string? Producer { get; set; } + + public DateTime CreationDate { get; set; } = DateTime.Now; + public DateTime ModifiedDate { get; set; } = DateTime.Now; + + public static DocumentMetadata Default => new DocumentMetadata(); + } +} \ No newline at end of file diff --git a/QuestPDF/Drawing/Exceptions/DocumentComposeException.cs b/QuestPDF/Drawing/Exceptions/DocumentComposeException.cs new file mode 100644 index 0000000..972dbe8 --- /dev/null +++ b/QuestPDF/Drawing/Exceptions/DocumentComposeException.cs @@ -0,0 +1,22 @@ +using System; + +namespace QuestPDF.Drawing.Exceptions +{ + public class DocumentComposeException : Exception + { + public DocumentComposeException() + { + + } + + public DocumentComposeException(string message) : base(message) + { + + } + + public DocumentComposeException(string message, Exception inner) : base(message, inner) + { + + } + } +} \ No newline at end of file diff --git a/QuestPDF/Drawing/Exceptions/DocumentDrawingException.cs b/QuestPDF/Drawing/Exceptions/DocumentDrawingException.cs new file mode 100644 index 0000000..cebb76c --- /dev/null +++ b/QuestPDF/Drawing/Exceptions/DocumentDrawingException.cs @@ -0,0 +1,22 @@ +using System; + +namespace QuestPDF.Drawing.Exceptions +{ + public class DocumentDrawingException : Exception + { + public DocumentDrawingException() + { + + } + + public DocumentDrawingException(string message) : base(message) + { + + } + + public DocumentDrawingException(string message, Exception inner) : base(message, inner) + { + + } + } +} \ No newline at end of file diff --git a/QuestPDF/Drawing/Exceptions/DocumentLayoutException.cs b/QuestPDF/Drawing/Exceptions/DocumentLayoutException.cs new file mode 100644 index 0000000..4f8c6c2 --- /dev/null +++ b/QuestPDF/Drawing/Exceptions/DocumentLayoutException.cs @@ -0,0 +1,22 @@ +using System; + +namespace QuestPDF.Drawing.Exceptions +{ + public class DocumentLayoutException : Exception + { + public DocumentLayoutException() + { + + } + + public DocumentLayoutException(string message) : base(message) + { + + } + + public DocumentLayoutException(string message, Exception inner) : base(message, inner) + { + + } + } +} \ No newline at end of file diff --git a/QuestPDF/Drawing/SpacePlan/FullRender.cs b/QuestPDF/Drawing/SpacePlan/FullRender.cs new file mode 100644 index 0000000..bdb721d --- /dev/null +++ b/QuestPDF/Drawing/SpacePlan/FullRender.cs @@ -0,0 +1,19 @@ +using QuestPDF.Infrastructure; + +namespace QuestPDF.Drawing.SpacePlan +{ + internal class FullRender : Size, ISpacePlan + { + public FullRender(Size size) : this(size.Width, size.Height) + { + + } + + public FullRender(float width, float height) : base(width, height) + { + + } + + public override string ToString() => $"FullRender {base.ToString()}"; + } +} \ No newline at end of file diff --git a/QuestPDF/Drawing/SpacePlan/ISpacePlan.cs b/QuestPDF/Drawing/SpacePlan/ISpacePlan.cs new file mode 100644 index 0000000..133d469 --- /dev/null +++ b/QuestPDF/Drawing/SpacePlan/ISpacePlan.cs @@ -0,0 +1,7 @@ +namespace QuestPDF.Drawing.SpacePlan +{ + internal interface ISpacePlan + { + + } +} \ No newline at end of file diff --git a/QuestPDF/Drawing/SpacePlan/PartialRender.cs b/QuestPDF/Drawing/SpacePlan/PartialRender.cs new file mode 100644 index 0000000..1dc42bb --- /dev/null +++ b/QuestPDF/Drawing/SpacePlan/PartialRender.cs @@ -0,0 +1,19 @@ +using QuestPDF.Infrastructure; + +namespace QuestPDF.Drawing.SpacePlan +{ + internal class PartialRender : Size, ISpacePlan + { + public PartialRender(Size size) : this(size.Width, size.Height) + { + + } + + public PartialRender(float width, float height) : base(width, height) + { + + } + + public override string ToString() => $"PartialRender {base.ToString()}"; + } +} \ No newline at end of file diff --git a/QuestPDF/Drawing/SpacePlan/TextMeasurement.cs b/QuestPDF/Drawing/SpacePlan/TextMeasurement.cs new file mode 100644 index 0000000..33495b2 --- /dev/null +++ b/QuestPDF/Drawing/SpacePlan/TextMeasurement.cs @@ -0,0 +1,8 @@ +namespace QuestPDF.Drawing.SpacePlan +{ + internal struct TextMeasurement + { + public int LineIndex { get; set; } + public float FragmentWidth { get; set; } + } +} \ No newline at end of file diff --git a/QuestPDF/Drawing/SpacePlan/Wrap.cs b/QuestPDF/Drawing/SpacePlan/Wrap.cs new file mode 100644 index 0000000..5dff402 --- /dev/null +++ b/QuestPDF/Drawing/SpacePlan/Wrap.cs @@ -0,0 +1,7 @@ +namespace QuestPDF.Drawing.SpacePlan +{ + internal class Wrap : ISpacePlan + { + public override string ToString() => "Wrap"; + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Alignment.cs b/QuestPDF/Elements/Alignment.cs new file mode 100644 index 0000000..a12cbc5 --- /dev/null +++ b/QuestPDF/Elements/Alignment.cs @@ -0,0 +1,55 @@ +using System; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class Alignment : ContainerElement + { + public VerticalAlignment Vertical { get; set; } = VerticalAlignment.Top; + public HorizontalAlignment Horizontal { get; set; } = HorizontalAlignment.Left; + + internal override void Draw(ICanvas canvas, Size availableSpace) + { + if (Child == null) + return; + + var childSize = Child.Measure(availableSpace) as Size; + + if (childSize == null) + return; + + var top = GetTopOffset(availableSpace, childSize); + var left = GetLeftOffset(availableSpace, childSize); + + canvas.Translate(new Position(left, top)); + Child.Draw(canvas, childSize); + canvas.Translate(new Position(-left, -top)); + } + + private float GetTopOffset(Size availableSpace, Size childSize) + { + var difference = availableSpace.Height - childSize.Height; + + return Vertical switch + { + VerticalAlignment.Top => 0, + VerticalAlignment.Middle => difference / 2, + VerticalAlignment.Bottom => difference, + _ => throw new NotSupportedException() + }; + } + + private float GetLeftOffset(Size availableSpace, Size childSize) + { + var difference = availableSpace.Width - childSize.Width; + + return Horizontal switch + { + HorizontalAlignment.Left => 0, + HorizontalAlignment.Center => difference / 2, + HorizontalAlignment.Right => difference, + _ => throw new NotSupportedException() + }; + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/AspectRatio.cs b/QuestPDF/Elements/AspectRatio.cs new file mode 100644 index 0000000..40e2323 --- /dev/null +++ b/QuestPDF/Elements/AspectRatio.cs @@ -0,0 +1,41 @@ +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class AspectRatio : ContainerElement + { + public float Ratio { get; set; } = 1; + + internal override ISpacePlan Measure(Size availableSpace) + { + if(Child == null) + return new FullRender(Size.Zero); + + var size = GetSize(availableSpace); + + if (size.Height > availableSpace.Height + Size.Epsilon) + return new Wrap(); + + return new FullRender(size); + } + + internal override void Draw(ICanvas canvas, Size availableSpace) + { + if (Child == null) + return; + + var size = GetSize(availableSpace); + + if (size.Height > availableSpace.Height) + return; + + Child.Draw(canvas, size); + } + + private Size GetSize(Size availableSpace) + { + return new Size(availableSpace.Width, (int)(availableSpace.Width / Ratio)); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Background.cs b/QuestPDF/Elements/Background.cs new file mode 100644 index 0000000..c8f9b97 --- /dev/null +++ b/QuestPDF/Elements/Background.cs @@ -0,0 +1,15 @@ +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class Background : ContainerElement + { + public string Color { get; set; } = "#00000000"; + + internal override void Draw(ICanvas canvas, Size availableSpace) + { + canvas.DrawRectangle(Position.Zero, availableSpace, Color); + Child?.Draw(canvas, availableSpace); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Border.cs b/QuestPDF/Elements/Border.cs new file mode 100644 index 0000000..8420a1c --- /dev/null +++ b/QuestPDF/Elements/Border.cs @@ -0,0 +1,39 @@ +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class Border : ContainerElement + { + public string Color { get; set; } = "#000000"; + + public float Top { get; set; } + public float Right { get; set; } + public float Bottom { get; set; } + public float Left { get; set; } + + internal override void Draw(ICanvas canvas, Size availableSpace) + { + Child?.Draw(canvas, availableSpace); + + canvas.DrawRectangle( + new Position(-Left/2, -Top/2), + new Size(availableSpace.Width + Left/2 + Right/2, Top), + Color); + + canvas.DrawRectangle( + new Position(-Left/2, -Top/2), + new Size(Left, availableSpace.Height + Top/2 + Bottom/2), + Color); + + canvas.DrawRectangle( + new Position(-Left/2, availableSpace.Height-Bottom/2), + new Size(availableSpace.Width + Left/2 + Right/2, Bottom), + Color); + + canvas.DrawRectangle( + new Position(availableSpace.Width-Right/2, -Top/2), + new Size(Right, availableSpace.Height + Top/2 + Bottom/2), + Color); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Constrained.cs b/QuestPDF/Elements/Constrained.cs new file mode 100644 index 0000000..f0b326a --- /dev/null +++ b/QuestPDF/Elements/Constrained.cs @@ -0,0 +1,69 @@ +using System; +using System.Linq; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class Constrained : ContainerElement + { + public float? MinWidth { get; set; } + public float? MaxWidth { get; set; } + + public float? MinHeight { get; set; } + public float? MaxHeight { get; set; } + + internal override ISpacePlan Measure(Size availableSpace) + { + if (MinWidth > availableSpace.Width + Size.Epsilon) + return new Wrap(); + + if (MinHeight > availableSpace.Height + Size.Epsilon) + return new Wrap(); + + var available = new Size( + MathHelpers.Min(MaxWidth, availableSpace.Width), + MathHelpers.Min(MaxHeight, availableSpace.Height)); + + var measurement = Child?.Measure(available) ?? new FullRender(Size.Zero); + var size = measurement as Size; + + if (measurement is Wrap) + return new Wrap(); + + var actualSize = new Size( + MathHelpers.Max(MinWidth, size.Width), + MathHelpers.Max(MinHeight, size.Height)); + + if (size is FullRender) + return new FullRender(actualSize); + + if (size is PartialRender) + return new PartialRender(actualSize); + + throw new NotSupportedException(); + } + + internal override void Draw(ICanvas canvas, Size availableSpace) + { + var available = new Size( + MathHelpers.Min(MaxWidth, availableSpace.Width), + MathHelpers.Min(MaxHeight, availableSpace.Height)); + + Child?.Draw(canvas, available); + } + } + + static class MathHelpers + { + public static float Min(params float?[] values) + { + return values.Where(x => x.HasValue).Min().Value; + } + + public static float Max(params float?[] values) + { + return values.Where(x => x.HasValue).Max().Value; + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Container.cs b/QuestPDF/Elements/Container.cs new file mode 100644 index 0000000..6f1731e --- /dev/null +++ b/QuestPDF/Elements/Container.cs @@ -0,0 +1,12 @@ +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class Container : ContainerElement + { + internal Container() + { + Child = new Empty(); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Debug.cs b/QuestPDF/Elements/Debug.cs new file mode 100644 index 0000000..5f48976 --- /dev/null +++ b/QuestPDF/Elements/Debug.cs @@ -0,0 +1,25 @@ +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class Debug : ContainerElement + { + internal override void Draw(ICanvas canvas, Size availableSpace) + { + var textStyle = new TextStyle + { + Color = "#FF0000", + FontType = "Consolas", + Size = 10 + }; + + Child?.Draw(canvas, availableSpace); + + canvas.DrawRectangle(Position.Zero, availableSpace, "#FF0000"); + canvas.DrawRectangle(Position.Zero, availableSpace, "#FF00FF"); + + canvas.DrawText($"W: {availableSpace.Width}", new Position(5, 12), textStyle); + canvas.DrawText($"H: {availableSpace.Height}", new Position(5, 22), textStyle); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/DynamicImage.cs b/QuestPDF/Elements/DynamicImage.cs new file mode 100644 index 0000000..0351d4e --- /dev/null +++ b/QuestPDF/Elements/DynamicImage.cs @@ -0,0 +1,29 @@ +using System; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class DynamicImage : Element + { + public Func? Source { get; set; } + + internal override ISpacePlan Measure(Size availableSpace) + { + if (availableSpace.Width < 0 || availableSpace.Height < 0) + return new Wrap(); + + return new FullRender(availableSpace.Width, availableSpace.Height); + } + + internal override void Draw(ICanvas canvas, Size availableSpace) + { + var imageElement = new Image() + { + Data = Source?.Invoke(availableSpace) + }; + + imageElement.Draw(canvas, availableSpace); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Empty.cs b/QuestPDF/Elements/Empty.cs new file mode 100644 index 0000000..9719912 --- /dev/null +++ b/QuestPDF/Elements/Empty.cs @@ -0,0 +1,18 @@ +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class Empty : Element + { + internal override ISpacePlan Measure(Size availableSpace) + { + return new FullRender(Size.Zero); + } + + internal override void Draw(ICanvas canvas, Size availableSpace) + { + + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Extend.cs b/QuestPDF/Elements/Extend.cs new file mode 100644 index 0000000..04e582f --- /dev/null +++ b/QuestPDF/Elements/Extend.cs @@ -0,0 +1,37 @@ +using System; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class Extend : ContainerElement + { + public bool ExtendVertical { get; set; } + public bool ExtendHorizontal { get; set; } + + internal override ISpacePlan Measure(Size availableSpace) + { + var childSize = Child?.Measure(availableSpace) ?? new FullRender(Size.Zero); + + if (childSize is Wrap) + return childSize; + + var targetSize = GetTargetSize(availableSpace, childSize as Size); + + if (childSize is PartialRender) + return new PartialRender(targetSize); + + if (childSize is FullRender) + return new FullRender(targetSize); + + throw new NotSupportedException(); + } + + private Size GetTargetSize(Size availableSpace, Size childSize) + { + return new Size( + ExtendHorizontal ? availableSpace.Width : childSize.Width, + ExtendVertical ? availableSpace.Height : childSize.Height); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Image.cs b/QuestPDF/Elements/Image.cs new file mode 100644 index 0000000..31e2529 --- /dev/null +++ b/QuestPDF/Elements/Image.cs @@ -0,0 +1,58 @@ +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Infrastructure; +using SkiaSharp; + +namespace QuestPDF.Elements +{ + internal class Image : Element + { + public byte[]? Data { get; set; } + private SKImage? InternalImage { get; set; } + + ~Image() + { + InternalImage?.Dispose(); + } + + private void Initialize() + { + if (Data != null) + InternalImage ??= SKImage.FromEncodedData(Data); + } + + internal override ISpacePlan Measure(Size availableSpace) + { + Initialize(); + + if (InternalImage == null) + return new FullRender(Size.Zero); + + if (availableSpace.Width < 0 || availableSpace.Height < 0) + return new Wrap(); + + var size = GetTargetSize(availableSpace); + return new FullRender(size); + } + + internal override void Draw(ICanvas canvas, Size availableSpace) + { + Initialize(); + + if (InternalImage == null) + return; + + var size = GetTargetSize(availableSpace); + canvas.DrawImage(InternalImage, Position.Zero, size); + } + + private Size GetTargetSize(Size availableSpace) + { + var imageRatio = InternalImage.Width / (float)InternalImage.Height; + var spaceRatio = availableSpace.Width / (float) availableSpace.Height; + + return imageRatio < spaceRatio + ? new Size((int)(availableSpace.Height * imageRatio), availableSpace.Height) + : new Size(availableSpace.Width, (int)(availableSpace.Width / imageRatio)); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Padding.cs b/QuestPDF/Elements/Padding.cs new file mode 100644 index 0000000..0694fd1 --- /dev/null +++ b/QuestPDF/Elements/Padding.cs @@ -0,0 +1,57 @@ +using System; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class Padding : ContainerElement + { + public float Top { get; set; } + public float Right { get; set; } + public float Bottom { get; set; } + public float Left { get; set; } + + internal override ISpacePlan Measure(Size availableSpace) + { + if (Child == null) + return new FullRender(Size.Zero); + + var internalSpace = InternalSpace(availableSpace); + var measure = Child.Measure(internalSpace) as Size; + + if (measure == null) + return new Wrap(); + + var newSize = new Size( + measure.Width + Left + Right, + measure.Height + Top + Bottom); + + if (measure is PartialRender) + return new PartialRender(newSize); + + if (measure is FullRender) + return new FullRender(newSize); + + throw new NotSupportedException(); + } + + internal override void Draw(ICanvas canvas, Size availableSpace) + { + if (Child == null) + return; + + var internalSpace = InternalSpace(availableSpace); + + canvas.Translate(new Position(Left, Top)); + Child?.Draw(canvas, internalSpace); + canvas.Translate(new Position(-Left, -Top)); + } + + private Size InternalSpace(Size availableSpace) + { + return new Size( + availableSpace.Width - Left - Right, + availableSpace.Height - Top - Bottom); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Page.cs b/QuestPDF/Elements/Page.cs new file mode 100644 index 0000000..8e34553 --- /dev/null +++ b/QuestPDF/Elements/Page.cs @@ -0,0 +1,57 @@ +using System; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class Page : Element + { + public Element? Header { get; set; } + public Element? Content { get; set; } + public Element? Footer { get; set; } + + internal override ISpacePlan Measure(Size availableSpace) + { + var headerSize = Header?.Measure(availableSpace) as Size; + var footerSize = Footer?.Measure(availableSpace) as Size; + + var contentHeight = availableSpace.Height - (headerSize?.Height ?? 0) - (footerSize?.Height ?? 0); + + var required = Content.Measure(new Size(availableSpace.Width, contentHeight)); + + if (required is FullRender) + return new FullRender(availableSpace); + + if (required is PartialRender) + return new PartialRender(availableSpace); + + if (required is Wrap) + return new Wrap(); + + throw new NotSupportedException(); + } + + internal override void Draw(ICanvas canvas, Size availableSpace) + { + var headerHeight = (Header?.Measure(availableSpace) as Size)?.Height ?? 0; + var footerHeight = (Footer?.Measure(availableSpace) as Size)?.Height ?? 0; + + var contentHeight = availableSpace.Height - headerHeight - footerHeight; + var contentSize = new Size(availableSpace.Width, contentHeight); + + if (headerHeight > 0) + { + Header?.Draw(canvas, new Size(availableSpace.Width, headerHeight)); + canvas.Translate(new Position(0, headerHeight)); + } + + Content.Draw(canvas, new Size(availableSpace.Width, contentSize.Height)); + canvas.Translate(new Position(0, contentSize.Height)); + + if (footerHeight > 0) + { + Footer?.Draw(canvas, new Size(availableSpace.Width, footerHeight)); + } + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/PageBreak.cs b/QuestPDF/Elements/PageBreak.cs new file mode 100644 index 0000000..1cee029 --- /dev/null +++ b/QuestPDF/Elements/PageBreak.cs @@ -0,0 +1,23 @@ +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class PageBreak : Element + { + private bool IsRendered { get; set; } + + internal override ISpacePlan Measure(Size availableSpace) + { + if (IsRendered) + return new FullRender(Size.Zero); + + return new PartialRender(availableSpace.Width, 1); + } + + internal override void Draw(ICanvas canvas, Size availableSpace) + { + IsRendered = true; + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/PageNumber.cs b/QuestPDF/Elements/PageNumber.cs new file mode 100644 index 0000000..89956e3 --- /dev/null +++ b/QuestPDF/Elements/PageNumber.cs @@ -0,0 +1,39 @@ +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Infrastructure; +using Size = QuestPDF.Infrastructure.Size; + +namespace QuestPDF.Elements +{ + internal class PageNumber : Element + { + public string TextFormat { get; set; } = ""; + public TextStyle? TextStyle { get; set; } + private Text? TextElement { get; set; } + + private int Number { get; set; } = 1; + + internal override ISpacePlan Measure(Size availableSpace) + { + InitializeTextElement(); + + TextElement.Value = TextFormat.Replace("{number}", Number.ToString()); + return TextElement.Measure(availableSpace); + } + + internal override void Draw(ICanvas canvas, Size availableSpace) + { + InitializeTextElement(); + + TextElement.Draw(canvas, availableSpace); + Number++; + } + + private void InitializeTextElement() + { + TextElement ??= new Text() + { + Style = TextStyle + }; + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Placeholder.cs b/QuestPDF/Elements/Placeholder.cs new file mode 100644 index 0000000..45b7572 --- /dev/null +++ b/QuestPDF/Elements/Placeholder.cs @@ -0,0 +1,25 @@ +using QuestPDF.Fluent; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class Placeholder : IComponent + { + private static readonly byte[] ImageData; + + static Placeholder() + { + ImageData = Helpers.Helpers.LoadEmbeddedResource("QuestPDF.Resources.ImagePlaceholder.png"); + } + + public void Compose(IContainer container) + { + container + .Background("CCC") + .AlignMiddle() + .AlignCenter() + .MaxHeight(32) + .Image(ImageData); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Row.cs b/QuestPDF/Elements/Row.cs new file mode 100644 index 0000000..87a3cbf --- /dev/null +++ b/QuestPDF/Elements/Row.cs @@ -0,0 +1,82 @@ +using System.Collections.Generic; +using System.Linq; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class Row : Element + { + public List? Children { get; set; } = new List(); + + public float? ConstantWidthSum { get; set; } + public float? RelativeWidthSum { get; set; } + + internal override ISpacePlan Measure(Size availableSpace) + { + var sizes = Children + .Select(x => + { + var space = GetTargetSize(x, availableSpace); + return x.Child.Measure(space); + }) + .ToList(); + + if (sizes.Any(x => x is Wrap)) + return new Wrap(); + + var height = sizes + .Where(x => x is Size) + .Cast() + .DefaultIfEmpty(Size.Zero) + .Max(x => x.Height); + + if (sizes.All(x => x is FullRender)) + return new FullRender(availableSpace.Width, height); + + if (sizes.Any(x => x is PartialRender)) + return new PartialRender(availableSpace.Width, height); + + return new FullRender(Size.Zero); + } + + internal override void Draw(ICanvas canvas, Size availableSpace) + { + var targetSpace = Measure(availableSpace) as Size; + + if (targetSpace == null) + return; + + var offset = 0f; + + foreach (var column in Children) + { + var space = GetTargetSize(column, targetSpace); + + canvas.Translate(new Position(offset, 0)); + column.Child.Draw(canvas, space); + canvas.Translate(new Position(-offset, 0)); + + offset += space.Width; + } + } + + private Size GetTargetSize(RowElement rowElement, Size availableSpace) + { + if (rowElement is ConstantRowElement) + return new Size(rowElement.Width, availableSpace.Height); + + ConstantWidthSum ??= Children + .Where(x => x is ConstantRowElement) + .Cast() + .Sum(x => x.Width); + + RelativeWidthSum ??= Children + .Where(x => x is RelativeRowElement) + .Cast() + .Sum(x => x.Width); + + return new Size((availableSpace.Width - ConstantWidthSum.Value) * rowElement.Width / RelativeWidthSum.Value, availableSpace.Height); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/RowElement.cs b/QuestPDF/Elements/RowElement.cs new file mode 100644 index 0000000..75069aa --- /dev/null +++ b/QuestPDF/Elements/RowElement.cs @@ -0,0 +1,27 @@ +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal abstract class RowElement : ContainerElement + { + public float Width { get; set; } = 1; + + internal override void Draw(ICanvas canvas, Size availableSpace) + { + if (Child == null) + return; + + Child.Draw(canvas, availableSpace); + } + } + + internal class ConstantRowElement : RowElement + { + + } + + internal class RelativeRowElement : RowElement + { + + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Section.cs b/QuestPDF/Elements/Section.cs new file mode 100644 index 0000000..9ed70ba --- /dev/null +++ b/QuestPDF/Elements/Section.cs @@ -0,0 +1,62 @@ +using System; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class Section : Element + { + public Element? Header { get; set; } + public Element? Content { get; set; } + + internal override ISpacePlan Measure(Size availableSpace) + { + var headerMeasure = Header?.Measure(availableSpace); + + if (headerMeasure is Wrap || headerMeasure is PartialRender) + return new Wrap(); + + var headerSize = headerMeasure as Size ?? Size.Zero; + var contentMeasure = Content?.Measure(new Size(availableSpace.Width, availableSpace.Height - headerSize.Height)) ?? new FullRender(Size.Zero); + + if (contentMeasure is Wrap) + return new Wrap(); + + var contentSize = contentMeasure as Size ?? Size.Zero; + + var newSize = new Size( + availableSpace.Width, + headerSize.Height + contentSize.Height); + + if (contentSize is PartialRender) + return new PartialRender(newSize); + + if (contentSize is FullRender) + return new FullRender(newSize); + + throw new NotSupportedException(); + } + + internal override void Draw(ICanvas canvas, Size availableSpace) + { + var measurement = Measure(availableSpace); + + if (measurement is Wrap) + return; + + var headerSize = Header?.Measure(availableSpace) as Size ?? Size.Zero; + + var contentAvailableSize = new Size(availableSpace.Width, availableSpace.Height - headerSize.Height); + var contentSize = Content?.Measure(contentAvailableSize) as Size; + + Header?.Draw(canvas, new Size(availableSpace.Width, headerSize.Height)); + + canvas.Translate(new Position(0, headerSize.Height)); + + if (contentSize != null) + Content?.Draw(canvas, new Size(availableSpace.Width, contentSize.Height)); + + canvas.Translate(new Position(0, -headerSize.Height)); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/ShowOnce.cs b/QuestPDF/Elements/ShowOnce.cs new file mode 100644 index 0000000..7c8bbcf --- /dev/null +++ b/QuestPDF/Elements/ShowOnce.cs @@ -0,0 +1,29 @@ +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class ShowOnce : ContainerElement + { + private bool IsRendered { get; set; } + + internal override ISpacePlan Measure(Size availableSpace) + { + if (Child == null || IsRendered) + return new FullRender(Size.Zero); + + return Child.Measure(availableSpace); + } + + internal override void Draw(ICanvas canvas, Size availableSpace) + { + if (Child == null || IsRendered) + return; + + if (Child.Measure(availableSpace) is FullRender) + IsRendered = true; + + Child.Draw(canvas, availableSpace); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Stack.cs b/QuestPDF/Elements/Stack.cs new file mode 100644 index 0000000..e82ed63 --- /dev/null +++ b/QuestPDF/Elements/Stack.cs @@ -0,0 +1,104 @@ +using System.Collections.Generic; +using System.Linq; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class Stack : Element + { + public float Spacing { get; set; } + public bool Pageable { get; set; } = true; + + public ICollection Children { get; internal set; } = new List(); + private Queue ChildrenQueue { get; set; } + + private void Initialize() + { + if (!Pageable) + ChildrenQueue = null; + + ChildrenQueue ??= new Queue(Children.Where(x => x != null)); + } + + internal override ISpacePlan Measure(Size availableSpace) + { + Initialize(); + + if(!ChildrenQueue.Any()) + return new FullRender(Size.Zero); + + var heightOnCurrentPage = 0f; + + foreach (var renderer in ChildrenQueue) + { + var space = renderer.Measure(new Size(availableSpace.Width, availableSpace.Height - heightOnCurrentPage)); + + if (space is Wrap) + { + if (!Pageable) + return new Wrap(); + + if (heightOnCurrentPage < Size.Epsilon) + return new Wrap(); + + return new PartialRender(availableSpace.Width, heightOnCurrentPage - Spacing); + } + + var size = space as Size; + + if (size.Height < Size.Epsilon) + continue; + + heightOnCurrentPage += size.Height + Spacing; + + if (space is PartialRender) + { + if (!Pageable) + return new Wrap(); + + return new PartialRender(availableSpace.Width, heightOnCurrentPage - Spacing); + } + } + + return new FullRender(availableSpace.Width, heightOnCurrentPage - Spacing); + } + + internal override void Draw(ICanvas canvas, Size availableSpace) + { + Initialize(); + + var topOffset = 0f; + + while (ChildrenQueue.Any()) + { + var child = ChildrenQueue.Peek(); + + var restSpace = new Size(availableSpace.Width, availableSpace.Height - topOffset); + var space = child.Measure(restSpace); + + if (space is Wrap) + break; + + var size = space as Size; + + if (size.Height < Size.Epsilon) + { + ChildrenQueue.Dequeue(); + continue; + } + + canvas.Translate(new Position(0, topOffset)); + child.Draw(canvas, new Size(availableSpace.Width, size.Height)); + canvas.Translate(new Position(0, -topOffset)); + + topOffset += size.Height + Spacing; + + if (space is PartialRender) + break; + + ChildrenQueue.Dequeue(); + } + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Text.cs b/QuestPDF/Elements/Text.cs new file mode 100644 index 0000000..6e611ca --- /dev/null +++ b/QuestPDF/Elements/Text.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using QuestPDF.Drawing; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Infrastructure; +using Size = QuestPDF.Infrastructure.Size; + +namespace QuestPDF.Elements +{ + internal class Text : Element + { + public string? Value { get; set; } + public TextStyle? Style { get; set; } = new TextStyle(); + + private float LineHeight => Style.Size * Style.LineHeight; + + internal override ISpacePlan Measure(Size availableSpace) + { + var lines = BreakLines(availableSpace.Width); + + var realWidth = lines + .Select(line => Style.BreakText(line, availableSpace.Width).FragmentWidth) + .DefaultIfEmpty(0) + .Max(); + + var realHeight = lines.Count * LineHeight; + + if (realHeight > availableSpace.Height + Size.Epsilon) + return new Wrap(); + + return new FullRender(realWidth, realHeight); + } + + internal override void Draw(ICanvas canvas, Size availableSpace) + { + var lines = BreakLines(availableSpace.Width); + + var offsetTop = 0f; + var offsetLeft = GetLeftOffset(); + + canvas.Translate(new Position(0, Style.Size)); + + foreach (var line in lines) + { + canvas.DrawText(line, new Position(offsetLeft, offsetTop), Style); + offsetTop += LineHeight; + } + + canvas.Translate(new Position(0, -Style.Size)); + + float GetLeftOffset() + { + return Style.Alignment switch + { + HorizontalAlignment.Left => 0, + HorizontalAlignment.Center => availableSpace.Width / 2, + HorizontalAlignment.Right => availableSpace.Width, + _ => throw new NotSupportedException() + }; + } + } + + #region Word Wrap + + private List BreakLines(float maxWidth) + { + var lines = new List (); + + var remainingText = Value.Trim(); + + while(true) + { + if (string.IsNullOrEmpty(remainingText)) + break; + + var breakPoint = BreakLinePoint(remainingText, maxWidth); + + if (breakPoint == 0) + break; + + var lastLine = remainingText.Substring(0, breakPoint).Trim(); + lines.Add(lastLine); + + remainingText = remainingText.Substring(breakPoint).Trim(); + } + + return lines; + } + + private int BreakLinePoint(string text, float width) + { + var index = 0; + var lengthBreak = Style.BreakText(text, width).LineIndex; + + while (index <= text.Length) + { + var next = text.IndexOfAny (new [] { ' ', '\n' }, index); + + if (next <= 0) + return index == 0 || lengthBreak == text.Length ? lengthBreak : index; + + if (next > lengthBreak) + return index; + + if (text[next] == '\n') + return next; + + index = next + 1; + } + + return index; + } + + #endregion + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Watermark.cs b/QuestPDF/Elements/Watermark.cs new file mode 100644 index 0000000..fd8ccfc --- /dev/null +++ b/QuestPDF/Elements/Watermark.cs @@ -0,0 +1,62 @@ +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class Watermark : Element + { + private Position Offset { get; set; } = new Position(36, 36); + private const float ImageHeight = 28; + private const string TargetUrl = "https://www.questpdf.com"; + + private Image Image { get; set; } + private static readonly byte[] ImageData; + + static Watermark() + { + ImageData = Helpers.Helpers.LoadEmbeddedResource("QuestPDF.Resources.Watermark.png"); + } + + public Watermark() + { + Image = new Image() + { + Data = ImageData + }; + } + + internal void AdjustPosition(Element? element) + { + while (element != null) + { + if (element is Padding padding) + { + if (padding.Left > 0 && padding.Bottom > 0) + Offset = new Position(padding.Left, padding.Bottom); + + return; + } + + element = (element as ContainerElement)?.Child; + } + } + + internal override ISpacePlan Measure(Size availableSpace) + { + return Image.Measure(availableSpace); + } + + internal override void Draw(ICanvas canvas, Size availableSpace) + { + var offset = new Position(Offset.X, availableSpace.Height - Offset.Y - ImageHeight); + canvas.Translate(offset); + + availableSpace = new Size(availableSpace.Width, ImageHeight); + var targetSize = Image.Measure(availableSpace) as FullRender; + Image.Draw(canvas, targetSize); + canvas.DrawLink(TargetUrl, targetSize); + + canvas.Translate(offset.Reverse()); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/AlignmentExtensions.cs b/QuestPDF/Fluent/AlignmentExtensions.cs new file mode 100644 index 0000000..6aae1d3 --- /dev/null +++ b/QuestPDF/Fluent/AlignmentExtensions.cs @@ -0,0 +1,47 @@ +using System; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Fluent +{ + public static class AlignmentExtensions + { + private static IContainer Alignment(this IContainer element, Action handler) + { + var alignment = element as Alignment ?? new Alignment(); + handler(alignment); + + return element.Element(alignment); + } + + public static IContainer AlignLeft(this IContainer element) + { + return element.Alignment(x => x.Horizontal = HorizontalAlignment.Left); + } + + public static IContainer AlignCenter(this IContainer element) + { + return element.Alignment(x => x.Horizontal = HorizontalAlignment.Center); + } + + public static IContainer AlignRight(this IContainer element) + { + return element.Alignment(x => x.Horizontal = HorizontalAlignment.Right); + } + + public static IContainer AlignTop(this IContainer element) + { + return element.Alignment(x => x.Vertical = VerticalAlignment.Top); + } + + public static IContainer AlignMiddle(this IContainer element) + { + return element.Alignment(x => x.Vertical = VerticalAlignment.Middle); + } + + public static IContainer AlignBottom(this IContainer element) + { + return element.Alignment(x => x.Vertical = VerticalAlignment.Bottom); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/BorderExtensions.cs b/QuestPDF/Fluent/BorderExtensions.cs new file mode 100644 index 0000000..deafa13 --- /dev/null +++ b/QuestPDF/Fluent/BorderExtensions.cs @@ -0,0 +1,57 @@ +using System; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Fluent +{ + public static class BorderExtensions + { + private static IContainer Border(this IContainer element, Action handler) + { + var border = element as Border ?? new Border(); + handler(border); + + return element.Element(border); + } + + public static IContainer Border(this IContainer element, float value) + { + return element.BorderHorizontal(value).BorderVertical(value); + } + + public static IContainer BorderVertical(this IContainer element, float value) + { + return element.BorderLeft(value).BorderRight(value); + } + + public static IContainer BorderHorizontal(this IContainer element, float value) + { + return element.BorderTop(value).BorderBottom(value); + } + + public static IContainer BorderLeft(this IContainer element, float value) + { + return element.Border(x => x.Left = value); + } + + public static IContainer BorderRight(this IContainer element, float value) + { + return element.Border(x => x.Right = value); + } + + public static IContainer BorderTop(this IContainer element, float value) + { + return element.Border(x => x.Top = value); + } + + public static IContainer BorderBottom(this IContainer element, float value) + { + return element.Border(x => x.Bottom = value); + } + + public static IContainer BorderColor(this IContainer element, string color) + { + return element.Border(x => x.Color = color); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/ComponentExtentions.cs b/QuestPDF/Fluent/ComponentExtentions.cs new file mode 100644 index 0000000..fe57ea1 --- /dev/null +++ b/QuestPDF/Fluent/ComponentExtentions.cs @@ -0,0 +1,73 @@ +using System; +using System.Linq.Expressions; +using QuestPDF.Drawing.Exceptions; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Fluent +{ + class ComponentDescriptor where T : IComponent + { + public T Component { get; } + + public ComponentDescriptor(T component) + { + Component = component; + } + + public IContainer Slot(Expression> selector) + { + try + { + var existingValue = Component.GetPropertyValue(selector); + + if (existingValue != null) + throw new DocumentComposeException($"The slot {selector.GetPropertyName()} of the component {(typeof( T).Name)} was already used."); + + var slot = new Slot(); + Component.SetPropertyValue(selector, slot); + return slot; + } + catch (DocumentComposeException) + { + throw; + } + catch + { + throw new DocumentComposeException("Every slot in a component should be a public property with getter and setter."); + } + } + } + + public static class ComponentExtensions + { + public static void Component(this IContainer element, T component) where T : IComponent + { + element.Component(component, null); + } + + public static void Component(this IContainer element) where T : IComponent, new() + { + element.Component(new T(), null); + } + + static void Component(this IContainer element, T component, Action>? handler = null) where T : IComponent + { + var descriptor = new ComponentDescriptor(component); + handler?.Invoke(descriptor); + + component.Compose(element.Container()); + } + + static void Component(this IContainer element, Action>? handler = null) where T : IComponent, new() + { + element.Component(new T(), handler); + } + + static void Slot(this IContainer element, ISlot slot) + { + var child = (slot as Slot)?.Child; + element.Element(child); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/ConstrainedExtensions.cs b/QuestPDF/Fluent/ConstrainedExtensions.cs new file mode 100644 index 0000000..bb30da2 --- /dev/null +++ b/QuestPDF/Fluent/ConstrainedExtensions.cs @@ -0,0 +1,47 @@ +using System; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Fluent +{ + public static class ConstrainedExtensions + { + private static IContainer Constrained(this IContainer element, Action handler) + { + var constrained = element as Constrained ?? new Constrained(); + handler(constrained); + + return element.Element(constrained); + } + + public static IContainer Width(this IContainer element, float value) + { + return element.MinWidth(value).MaxWidth(value); + } + + public static IContainer MinWidth(this IContainer element, float value) + { + return element.Constrained(x => x.MinWidth = value); + } + + public static IContainer MaxWidth(this IContainer element, float value) + { + return element.Constrained(x => x.MaxWidth = value); + } + + public static IContainer Height(this IContainer element, float value) + { + return element.MinHeight(value).MaxHeight(value); + } + + public static IContainer MinHeight(this IContainer element, float value) + { + return element.Constrained(x => x.MinHeight = value); + } + + public static IContainer MaxHeight(this IContainer element, float value) + { + return element.Constrained(x => x.MaxHeight = value); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/ElementExtensions.cs b/QuestPDF/Fluent/ElementExtensions.cs new file mode 100644 index 0000000..902907c --- /dev/null +++ b/QuestPDF/Fluent/ElementExtensions.cs @@ -0,0 +1,138 @@ +using System; +using System.IO; +using QuestPDF.Drawing; +using QuestPDF.Drawing.Exceptions; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Fluent +{ + public static class ElementExtensions + { + internal static Container Create(Action factory) + { + var container = new Container(); + factory(container); + return container; + } + + public static byte[] Generate(this IDocument document) + { + using var stream = new MemoryStream(); + document.Generate(stream); + return stream.ToArray(); + } + + public static void Generate(this IDocument document, string filePath) + { + using var stream = new FileStream(filePath, FileMode.Create); + document.Generate(stream); + } + + public static void Generate(this IDocument document, Stream stream) + { + DocumentGenerator.Generate(stream, document); + } + + public static T Element(this IContainer element, T child) where T : IElement + { + if (element?.Child != null && element.Child is Empty == false) + { + var message = $"Container {element.GetType().Name} already contains an {element.Child.GetType().Name} child. " + + $"Tried to assign {child?.GetType()?.Name}." + + $"You should not assign multiple elements to single-child containers."; + + throw new DocumentComposeException(message); + } + + if (element != child as Element) + element.Child = child as Element; + + return child; + } + + public static void Element(this IContainer element, Action handler) + { + var container = new Container(); + element.Element(container); + handler?.Invoke(container); + } + + public static void Image(this IContainer element, byte[] data) + { + element.Element(new Image + { + Data = data + }); + } + + public static void PageNumber(this IContainer element, string textFormat = "{number}", TextStyle? style = null) + { + element.Element(new PageNumber + { + TextFormat = textFormat, + TextStyle = style ?? TextStyle.Default + }); + } + + public static IContainer AspectRatio(this IContainer element, float ratio) + { + return element.Element(new AspectRatio + { + Ratio = ratio + }); + } + + public static IContainer Background(this IContainer element, string color) + { + return element.Element(new Background + { + Color = color + }); + } + + public static void Placeholder(this IContainer element) + { + element.Component(); + } + + public static IContainer ShowOnce(this IContainer element) + { + var alignment = new ShowOnce(); + + return element.Element(alignment); + } + + public static void Text(this IContainer element, object text, TextStyle? style = null) + { + style ??= TextStyle.Default; + + if (element is Alignment alignment) + style.Alignment = alignment.Horizontal; + + element.Element(new Text() + { + Value = text.ToString(), + Style = style + }); + } + + public static void PageBreak(this IContainer element) + { + element.Element(new PageBreak()); + } + + public static IContainer Container(this IContainer element) + { + return element.Element(new Container()); + } + + private static void DynamicImage(this IContainer element, Func handler) + { + element.Element(new DynamicImage() + { + Source = handler + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/ExtendExtensions.cs b/QuestPDF/Fluent/ExtendExtensions.cs new file mode 100644 index 0000000..c6586c3 --- /dev/null +++ b/QuestPDF/Fluent/ExtendExtensions.cs @@ -0,0 +1,32 @@ +using System; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Fluent +{ + public static class ExtendExtensions + { + private static IContainer Extend(this IContainer element, Action handler) + { + var extend = element as Extend ?? new Extend(); + handler(extend); + + return element.Element(extend); + } + + public static IContainer Extend(this IContainer element) + { + return element.ExtendVertical().ExtendHorizontal(); + } + + public static IContainer ExtendVertical(this IContainer element) + { + return element.Extend(x => x.ExtendVertical = true); + } + + public static IContainer ExtendHorizontal(this IContainer element) + { + return element.Extend(x => x.ExtendHorizontal = true); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/PaddingExtensions.cs b/QuestPDF/Fluent/PaddingExtensions.cs new file mode 100644 index 0000000..6f4b579 --- /dev/null +++ b/QuestPDF/Fluent/PaddingExtensions.cs @@ -0,0 +1,52 @@ +using System; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Fluent +{ + public static class PaddingExtensions + { + private static IContainer Padding(this IContainer element, Action handler) + { + var padding = element as Padding ?? new Padding(); + handler(padding); + + return element.Element(padding); + } + + public static IContainer Padding(this IContainer element, float value) + { + return element.PaddingVertical(value).PaddingHorizontal(value); + } + + public static IContainer PaddingHorizontal(this IContainer element, float value) + { + return element.PaddingLeft(value).PaddingRight(value); + } + + public static IContainer PaddingVertical(this IContainer element, float value) + { + return element.PaddingTop(value).PaddingBottom(value); + } + + public static IContainer PaddingTop(this IContainer element, float value) + { + return element.Padding(x => x.Top = value); + } + + public static IContainer PaddingBottom(this IContainer element, float value) + { + return element.Padding(x => x.Bottom = value); + } + + public static IContainer PaddingLeft(this IContainer element, float value) + { + return element.Padding(x => x.Left = value); + } + + public static IContainer PaddingRight(this IContainer element, float value) + { + return element.Padding(x => x.Right = value); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/PageExtensions.cs b/QuestPDF/Fluent/PageExtensions.cs new file mode 100644 index 0000000..3248cf1 --- /dev/null +++ b/QuestPDF/Fluent/PageExtensions.cs @@ -0,0 +1,64 @@ +using System; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Fluent +{ + public class PageDescriptor + { + private Page Page { get; } + + internal PageDescriptor(Page page) + { + Page = page; + } + + public IContainer Header() + { + var container = new Container(); + Page.Header = container; + return container; + } + + public void Header(Action handler) + { + handler?.Invoke(Header()); + } + + public IContainer Content() + { + var container = new Container(); + Page.Content = container; + return container; + } + + public void Content(Action handler) + { + handler?.Invoke(Content()); + } + + public IContainer Footer() + { + var container = new Container(); + Page.Footer = container; + return container; + } + + public void Footer(Action handler) + { + handler?.Invoke(Footer()); + } + } + + public static class PageExtensions + { + public static void Page(this IContainer document, Action handler) + { + var page = new Page(); + document.Element(page); + + var descriptor = new PageDescriptor(page); + handler(descriptor); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/RowExtensions.cs b/QuestPDF/Fluent/RowExtensions.cs new file mode 100644 index 0000000..e2fce05 --- /dev/null +++ b/QuestPDF/Fluent/RowExtensions.cs @@ -0,0 +1,50 @@ +using System; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Fluent +{ + public class RowDescriptor + { + private Row Row { get; } + + internal RowDescriptor(Row row) + { + Row = row; + } + + public IContainer ConstantColumn(float width) + { + var element = new ConstantRowElement() + { + Width = width + }; + + Row.Children.Add(element); + return element; + } + + public IContainer RelativeColumn(float width = 1) + { + var element = new RelativeRowElement() + { + Width = width + }; + + Row.Children.Add(element); + return element; + } + } + + public static class RowExtensions + { + public static void Row(this IContainer element, Action handler) + { + var row = new Row(); + element.Element(row); + + var descriptor = new RowDescriptor(row); + handler(descriptor); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/SectionExtensions.cs b/QuestPDF/Fluent/SectionExtensions.cs new file mode 100644 index 0000000..d10a5a5 --- /dev/null +++ b/QuestPDF/Fluent/SectionExtensions.cs @@ -0,0 +1,52 @@ +using System; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Fluent +{ + public class SectionDescriptor + { + private Section Section { get; } + + internal SectionDescriptor(Section section) + { + Section = section; + } + + public IContainer Header() + { + var container = new Container(); + Section.Header = container; + return container; + } + + public void Header(Action handler) + { + handler?.Invoke(Header()); + } + + public IContainer Content() + { + var container = new Container(); + Section.Content = container; + return container; + } + + public void Content(Action handler) + { + handler?.Invoke(Content()); + } + } + + public static class SectionExtensions + { + public static void Section(this IContainer element, Action handler) + { + var section = new Section(); + element.Element(section); + + var descriptor = new SectionDescriptor(section); + handler(descriptor); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/StackExtensions.cs b/QuestPDF/Fluent/StackExtensions.cs new file mode 100644 index 0000000..45524cb --- /dev/null +++ b/QuestPDF/Fluent/StackExtensions.cs @@ -0,0 +1,69 @@ +using System; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Fluent +{ + public class StackDescriptor + { + private Stack Stack { get; } + + internal StackDescriptor(Stack stack) + { + Stack = stack; + } + + public void Spacing(float value) + { + Stack.Spacing = value; + } + + public IContainer Element() + { + var container = new Container(); + Stack.Children.Add(container); + return container; + } + + public void Element(Action handler) + { + var container = new Container(); + Stack.Children.Add(container); + handler?.Invoke(container); + } + + public void Element(IElement element) + { + Stack.Children.Add(element as Element); + } + } + + public static class StackExtensions + { + public static void PageableStack(this IContainer element, Action handler) + { + var column = new Stack + { + Pageable = true + }; + + element.Element(column); + + var descriptor = new StackDescriptor(column); + handler(descriptor); + } + + public static void Stack(this IContainer element, Action handler) + { + var column = new Stack + { + Pageable = false + }; + + element.Element(column); + + var descriptor = new StackDescriptor(column); + handler(descriptor); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/TextStyleExtensions.cs b/QuestPDF/Fluent/TextStyleExtensions.cs new file mode 100644 index 0000000..7aa4c03 --- /dev/null +++ b/QuestPDF/Fluent/TextStyleExtensions.cs @@ -0,0 +1,54 @@ +using System; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Fluent +{ + public static class TextStyleExtensions + { + private static TextStyle Mutate(this TextStyle style, Action handler) + { + handler(style); + return style; + } + + public static TextStyle Color(this TextStyle style, string value) + { + return style.Mutate(x => x.Color = value); + } + + public static TextStyle FontType(this TextStyle style, string value) + { + return style.Mutate(x => x.FontType = value); + } + + public static TextStyle Size(this TextStyle style, float value) + { + return style.Mutate(x => x.Size = value); + } + + public static TextStyle LineHeight(this TextStyle style, float value) + { + return style.Mutate(x => x.LineHeight = value); + } + + public static TextStyle Alignment(this TextStyle style, HorizontalAlignment value) + { + return style.Mutate(x => x.Alignment = value); + } + + public static TextStyle AlignLeft(this TextStyle style) + { + return style.Mutate(x => x.Alignment = HorizontalAlignment.Left); + } + + public static TextStyle AlignCenter(this TextStyle style) + { + return style.Mutate(x => x.Alignment = HorizontalAlignment.Center); + } + + public static TextStyle AlignRight(this TextStyle style) + { + return style.Mutate(x => x.Alignment = HorizontalAlignment.Right); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Helpers/Helpers.cs b/QuestPDF/Helpers/Helpers.cs new file mode 100644 index 0000000..2c6f40a --- /dev/null +++ b/QuestPDF/Helpers/Helpers.cs @@ -0,0 +1,39 @@ +using System; +using System.IO; +using System.Linq.Expressions; +using System.Reflection; + +namespace QuestPDF.Helpers +{ + internal static class Helpers + { + internal static byte[] LoadEmbeddedResource(string resourceName) + { + using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName); + using var reader = new BinaryReader(stream); + + return reader.ReadBytes((int) stream.Length); + } + + private static PropertyInfo? ToPropertyInfo(this Expression> selector) + { + return (selector.Body as MemberExpression)?.Member as PropertyInfo; + } + + internal static string? GetPropertyName(this Expression> selector) where TValue : class + { + return selector.ToPropertyInfo()?.Name; + } + + internal static TValue? GetPropertyValue(this T target, Expression> selector) where TValue : class + { + return selector.ToPropertyInfo()?.GetValue(target) as TValue; + } + + internal static void SetPropertyValue(this T target, Expression> selector, TValue value) + { + var property = selector.ToPropertyInfo() ?? throw new Exception("Expected property with getter and setter."); + property?.SetValue(target, value); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Helpers/PageSizes.cs b/QuestPDF/Helpers/PageSizes.cs new file mode 100644 index 0000000..32445d9 --- /dev/null +++ b/QuestPDF/Helpers/PageSizes.cs @@ -0,0 +1,29 @@ +using QuestPDF.Infrastructure; + +namespace QuestPDF.Helpers +{ + public static class PageSizes + { + public static Size A0 => new Size(2384, 3370); + public static Size A1 => new Size(1684, 2384); + public static Size A2 => new Size(1190, 1684); + public static Size A3 => new Size(842, 1190); + public static Size A4 => new Size(595, 842); + public static Size A5 => new Size(420, 595); + public static Size A6 => new Size(298, 420); + public static Size A7 => new Size(210, 298); + public static Size A8 => new Size(148, 210); + + public static Size B0 => new Size(2835, 4008); + public static Size B1 => new Size(2004, 2835); + public static Size B2 => new Size(1417, 2004); + public static Size B3 => new Size(1001, 1417); + public static Size B4 => new Size(709, 1001); + public static Size B5 => new Size(499, 709); + public static Size B6 => new Size(354, 499); + public static Size B7 => new Size(249, 354); + public static Size B8 => new Size(176, 249); + public static Size B9 => new Size(125, 176); + public static Size B10 => new Size(88, 125); + } +} diff --git a/QuestPDF/Helpers/TextPlaceholder.cs b/QuestPDF/Helpers/TextPlaceholder.cs new file mode 100644 index 0000000..e057c80 --- /dev/null +++ b/QuestPDF/Helpers/TextPlaceholder.cs @@ -0,0 +1,152 @@ +using System; +using System.Linq; + +namespace QuestPDF.Helpers +{ + public static class TextPlaceholder + { + private static Random Random = new Random(); + + #region Word Cache + + private const string CommonParagraph = + "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod " + + "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim " + + "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea " + + "commodo consequat. Duis aute irure dolor in reprehenderit in voluptate " + + "velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint " + + "occaecat cupidatat non proident, sunt in culpa qui officia deserunt " + + "mollit anim id est laborum."; + + private static readonly string[] LatinWords = + { + "exercitationem", "perferendis", "perspiciatis", "laborum", "eveniet", + "sunt", "iure", "nam", "nobis", "eum", "cum", "officiis", "excepturi", + "odio", "consectetur", "quasi", "aut", "quisquam", "vel", "eligendi", + "itaque", "non", "odit", "tempore", "quaerat", "dignissimos", + "facilis", "neque", "nihil", "expedita", "vitae", "vero", "ipsum", + "nisi", "animi", "cumque", "pariatur", "velit", "modi", "natus", + "iusto", "eaque", "sequi", "illo", "sed", "ex", "et", "voluptatibus", + "tempora", "veritatis", "ratione", "assumenda", "incidunt", "nostrum", + "placeat", "aliquid", "fuga", "provident", "praesentium", "rem", + "necessitatibus", "suscipit", "adipisci", "quidem", "possimus", + "voluptas", "debitis", "sint", "accusantium", "unde", "sapiente", + "voluptate", "qui", "aspernatur", "laudantium", "soluta", "amet", + "quo", "aliquam", "saepe", "culpa", "libero", "ipsa", "dicta", + "reiciendis", "nesciunt", "doloribus", "autem", "impedit", "minima", + "maiores", "repudiandae", "ipsam", "obcaecati", "ullam", "enim", + "totam", "delectus", "ducimus", "quis", "voluptates", "dolores", + "molestiae", "harum", "dolorem", "quia", "voluptatem", "molestias", + "magni", "distinctio", "omnis", "illum", "dolorum", "voluptatum", "ea", + "quas", "quam", "corporis", "quae", "blanditiis", "atque", "deserunt", + "laboriosam", "earum", "consequuntur", "hic", "cupiditate", + "quibusdam", "accusamus", "ut", "rerum", "error", "minus", "eius", + "ab", "ad", "nemo", "fugit", "officia", "at", "in", "id", "quos", + "reprehenderit", "numquam", "iste", "fugiat", "sit", "inventore", + "beatae", "repellendus", "magnam", "recusandae", "quod", "explicabo", + "doloremque", "aperiam", "consequatur", "asperiores", "commodi", + "optio", "dolor", "labore", "temporibus", "repellat", "veniam", + "architecto", "est", "esse", "mollitia", "nulla", "a", "similique", + "eos", "alias", "dolore", "tenetur", "deleniti", "porro", "facere", + "maxime", "corrupti" + }; + + private static readonly string[] LongLatinWords = LatinWords.Where(x => x.Length > 8).ToArray(); + + #endregion + + #region Text + + private static string RandomWord() + { + var index = Random.Next(0, LatinWords.Length); + return LatinWords[index]; + } + + private static string LongRandomWord() + { + var index = Random.Next(0, LongLatinWords.Length); + return LongLatinWords[index]; + } + + private static string RandomWords(int min, int max) + { + var length = Random.Next(min, max + 1); + + var words = Enumerable + .Range(0, length) + .Select(x => RandomWord()); + + return string.Join(" ", words); + } + + public static string LoremIpsum() => CommonParagraph; + + public static string Label() => RandomWords(2, 3).FirstCharToUpper(); + public static string Sentence() => RandomWords(6, 12).FirstCharToUpper() + "."; + public static string Question() => RandomWords(4, 8).FirstCharToUpper() + "?"; + + public static string Paragraph() + { + var length = Random.Next(3, 6); + + var sentences = Enumerable + .Range(0, length) + .Select(x => Sentence()); + + return string.Join(" ", sentences); + } + + public static string Paragraphs() + { + var length = Random.Next(2, 5); + + var sentences = Enumerable + .Range(0, length) + .Select(x => Paragraph()); + + return string.Join("\n", sentences); + } + + public static string Email() + { + return $"{LongRandomWord()}{Random.Next(10, 99)}@{LongRandomWord()}.com"; + } + + public static string Name() + { + return LongRandomWord().FirstCharToUpper() + " " + LongRandomWord().FirstCharToUpper(); + } + + public static string PhoneNumber() + { + return $"{Random.Next(100, 999)}-{Random.Next(100, 999)}-{Random.Next(1000, 9999)}"; + } + + private static string FirstCharToUpper(this string text) + { + return text.First().ToString().ToUpper() + text.Substring(1); + } + + #endregion + + #region Time + + private static DateTime RandomDate() => System.DateTime.Now - TimeSpan.FromDays(Random.NextDouble()); + + public static string Time() => RandomDate().ToString("T"); + public static string ShortDate() => RandomDate().ToString("d"); + public static string LongDate() => RandomDate().ToString("D"); + public static string DateTime() => RandomDate().ToString("G"); + + #endregion + + #region Numbers + + public static string Integer() => Random.Next(0, 10_000).ToString(); + public static string Decimal() => (Random.NextDouble() * Random.Next(0, 100)).ToString("N2"); + public static string Percent() => (Random.NextDouble() * 100).ToString("N0") + "%"; + + #endregion + } +} \ No newline at end of file diff --git a/QuestPDF/Infrastructure/ContainerElement.cs b/QuestPDF/Infrastructure/ContainerElement.cs new file mode 100644 index 0000000..831670d --- /dev/null +++ b/QuestPDF/Infrastructure/ContainerElement.cs @@ -0,0 +1,26 @@ +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Elements; + +namespace QuestPDF.Infrastructure +{ + internal abstract class ContainerElement : Element, IContainer + { + internal Element? Child { get; set; } = new Empty(); + + IElement? IContainer.Child + { + get => Child; + set => Child = value as Element; + } + + internal override ISpacePlan Measure(Size availableSpace) + { + return Child?.Measure(availableSpace) ?? new FullRender(Size.Zero); + } + + internal override void Draw(ICanvas canvas, Size availableSpace) + { + Child?.Draw(canvas, availableSpace); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Infrastructure/Element.cs b/QuestPDF/Infrastructure/Element.cs new file mode 100644 index 0000000..857f633 --- /dev/null +++ b/QuestPDF/Infrastructure/Element.cs @@ -0,0 +1,10 @@ +using QuestPDF.Drawing.SpacePlan; + +namespace QuestPDF.Infrastructure +{ + internal abstract class Element : IElement + { + internal abstract ISpacePlan Measure(Size availableSpace); + internal abstract void Draw(ICanvas canvas, Size availableSpace); + } +} \ No newline at end of file diff --git a/QuestPDF/Infrastructure/HorizontalAlignment.cs b/QuestPDF/Infrastructure/HorizontalAlignment.cs new file mode 100644 index 0000000..1341ca1 --- /dev/null +++ b/QuestPDF/Infrastructure/HorizontalAlignment.cs @@ -0,0 +1,9 @@ +namespace QuestPDF.Infrastructure +{ + public enum HorizontalAlignment + { + Left, + Center, + Right + } +} \ No newline at end of file diff --git a/QuestPDF/Infrastructure/ICanvas.cs b/QuestPDF/Infrastructure/ICanvas.cs new file mode 100644 index 0000000..a94a18c --- /dev/null +++ b/QuestPDF/Infrastructure/ICanvas.cs @@ -0,0 +1,15 @@ +using SkiaSharp; + +namespace QuestPDF.Infrastructure +{ + internal interface ICanvas + { + void Translate(Position vector); + + void DrawRectangle(Position vector, Size size, string color); + void DrawText(string text, Position position, TextStyle style); + void DrawImage(SKImage image, Position position, Size size); + + void DrawLink(string url, Size size); + } +} \ No newline at end of file diff --git a/QuestPDF/Infrastructure/IComponent.cs b/QuestPDF/Infrastructure/IComponent.cs new file mode 100644 index 0000000..dc03736 --- /dev/null +++ b/QuestPDF/Infrastructure/IComponent.cs @@ -0,0 +1,19 @@ +using QuestPDF.Elements; + +namespace QuestPDF.Infrastructure +{ + interface ISlot + { + + } + + class Slot : Container, ISlot + { + + } + + public interface IComponent + { + void Compose(IContainer container); + } +} \ No newline at end of file diff --git a/QuestPDF/Infrastructure/IContainer.cs b/QuestPDF/Infrastructure/IContainer.cs new file mode 100644 index 0000000..e39d681 --- /dev/null +++ b/QuestPDF/Infrastructure/IContainer.cs @@ -0,0 +1,7 @@ +namespace QuestPDF.Infrastructure +{ + public interface IContainer + { + IElement? Child { get; set; } + } +} \ No newline at end of file diff --git a/QuestPDF/Infrastructure/IDocument.cs b/QuestPDF/Infrastructure/IDocument.cs new file mode 100644 index 0000000..c021287 --- /dev/null +++ b/QuestPDF/Infrastructure/IDocument.cs @@ -0,0 +1,10 @@ +using QuestPDF.Drawing; + +namespace QuestPDF.Infrastructure +{ + public interface IDocument + { + DocumentMetadata GetMetadata(); + void Compose(IContainer container); + } +} \ No newline at end of file diff --git a/QuestPDF/Infrastructure/IElement.cs b/QuestPDF/Infrastructure/IElement.cs new file mode 100644 index 0000000..1b93c2c --- /dev/null +++ b/QuestPDF/Infrastructure/IElement.cs @@ -0,0 +1,7 @@ +namespace QuestPDF.Infrastructure +{ + public interface IElement + { + + } +} \ No newline at end of file diff --git a/QuestPDF/Infrastructure/Position.cs b/QuestPDF/Infrastructure/Position.cs new file mode 100644 index 0000000..f71dd38 --- /dev/null +++ b/QuestPDF/Infrastructure/Position.cs @@ -0,0 +1,21 @@ +namespace QuestPDF.Infrastructure +{ + internal class Position + { + public float X { get; set; } + public float Y { get; set; } + + public static Position Zero => new Position(0, 0); + + public Position(float x, float y) + { + X = x; + Y = y; + } + + public Position Reverse() + { + return new Position(-X, -Y); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Infrastructure/Size.cs b/QuestPDF/Infrastructure/Size.cs new file mode 100644 index 0000000..e1fbfb6 --- /dev/null +++ b/QuestPDF/Infrastructure/Size.cs @@ -0,0 +1,41 @@ +namespace QuestPDF.Infrastructure +{ + public class Size + { + public const float Epsilon = 0.001f; + + public float Width { get; } + public float Height { get; } + + public static Size Zero => new Size(0, 0); + + public Size(float width, float height) + { + Width = width; + Height = height; + } + + public override string ToString() => $"(W: {Width}, H: {Height})"; + + protected bool Equals(Size other) + { + return Width.Equals(other.Width) && Height.Equals(other.Height); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Size) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (Width.GetHashCode() * 397) ^ Height.GetHashCode(); + } + } + } +} \ No newline at end of file diff --git a/QuestPDF/Infrastructure/TextStyle.cs b/QuestPDF/Infrastructure/TextStyle.cs new file mode 100644 index 0000000..70b989f --- /dev/null +++ b/QuestPDF/Infrastructure/TextStyle.cs @@ -0,0 +1,18 @@ +namespace QuestPDF.Infrastructure +{ + public class TextStyle + { + public string Color { get; set; } = "#000000"; + public string FontType { get; set; } = "Helvetica"; + public float Size { get; set; } = 12; + public float LineHeight { get; set; } = 1.2f; + public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left; + + public static TextStyle Default => new TextStyle(); + + public override string ToString() + { + return $"{Color}|{FontType}|{Size}|{LineHeight}|{Alignment}"; + } + } +} \ No newline at end of file diff --git a/QuestPDF/Infrastructure/VerticalAlignment.cs b/QuestPDF/Infrastructure/VerticalAlignment.cs new file mode 100644 index 0000000..73c92b8 --- /dev/null +++ b/QuestPDF/Infrastructure/VerticalAlignment.cs @@ -0,0 +1,9 @@ +namespace QuestPDF.Infrastructure +{ + public enum VerticalAlignment + { + Top, + Middle, + Bottom + } +} \ No newline at end of file diff --git a/QuestPDF/QuestPDF.csproj b/QuestPDF/QuestPDF.csproj new file mode 100644 index 0000000..e0f98f3 --- /dev/null +++ b/QuestPDF/QuestPDF.csproj @@ -0,0 +1,38 @@ + + + + MarcinZiabek + CodeFlint + QuestPDF + 2021.1.0 + 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. + The library is open-source and totally free now. Removed watermak. + 8 + true + Logo.png + https://www.questpdf.com/images/package-logo.png + https://www.questpdf.com/ + https://github.com/QuestPDF/library.git + git + QuestPDF contributors + PDF file export generate create render portable document format quest free + MIT + enable + net462;netstandard2.0;netcoreapp2.0;netcoreapp3.0 + + + + + + + + + + + true + false + \ + + + + diff --git a/QuestPDF/Resources/Description.md b/QuestPDF/Resources/Description.md new file mode 100644 index 0000000..81d3c3e --- /dev/null +++ b/QuestPDF/Resources/Description.md @@ -0,0 +1,27 @@ +## Rely on solid fundamentals + +This library is created specifically for designing and arranging document layouts, with full paging support. Alternative solutions, such as HTML-based converters, are not designed for this purpose and therefore are often unpredictable and do not produce desired results. + +## Work with organized self-explanatory code + +The entire process of implementing PDF document, takes place in your code. Free yourself from slow visual designers and strange technological limitations. Follow simple yet highly effective approaches to create maintainable, high-quality code. + +## Compose simple components into complex documents + +Do you remember the feeling when your code just works? When your ideas are becoming real without any effort? Working with simple, easy to understand, self-explanatory and highly composable layout elements is the key here! + +## Create and reuse components + +Feel no fear of complex documents! Create custom, reusable components and divide the document's layout into easy to maintain pieces. Inject data to customize content and use slots to enhance composability. Decide how complex approaches your solution needs and follow the best path. + +## Prototype with ease + +We understand that document generation is often tricky and require multiple iterations. The library offers additional prototyping tools such as random text generator or image placeholder element. By following best practices, you can develop a document without having data. + +## Enjoy fast PDF generation + +QuestPDF is created upon SkiaSharp, a well-known graphical library, and converts your data into PDF documents. It offers a highly optimized layouting engine capable of generating over 1000 PDF files per minute per core. The entire process is thread-safe. + +## Write less, achieve more + +Do you believe that creating a complete invoice document can take less than 200 lines of code? We have prepared for you a step-by-step instruction that shows every detail of this implementation and describes the best patterns and practices. [Learn more](https://www.questpdf.com/documentation/getting-started.html) \ No newline at end of file diff --git a/QuestPDF/Resources/ImagePlaceholder.png b/QuestPDF/Resources/ImagePlaceholder.png new file mode 100644 index 0000000..2f1b9d0 Binary files /dev/null and b/QuestPDF/Resources/ImagePlaceholder.png differ diff --git a/QuestPDF/Resources/Logo.png b/QuestPDF/Resources/Logo.png new file mode 100644 index 0000000..a0f1a19 Binary files /dev/null and b/QuestPDF/Resources/Logo.png differ