From 43c4011f91d0412388db842e6d01339642130bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Mon, 1 Mar 2021 16:24:59 +0100 Subject: [PATCH] 2021.3.0 Generating images functionality, ShowIf element, fixed mutating TextStyle --- QuestPDF.Examples/Engine/ExampleTestBase.cs | 23 +++----- QuestPDF.Examples/Engine/SimpleDocument.cs | 33 ++++++++++++ QuestPDF.ReportSample/Tests.cs | 4 +- QuestPDF/Drawing/DocumentGenerator.cs | 59 +++++++++++++++++++-- QuestPDF/Drawing/DocumentMetadata.cs | 6 +++ QuestPDF/Fluent/ElementExtensions.cs | 23 ++------ QuestPDF/Fluent/GenerateExtensions.cs | 47 ++++++++++++++++ QuestPDF/Fluent/TextStyleExtensions.cs | 2 + QuestPDF/Helpers/PageSizes.cs | 2 + QuestPDF/Infrastructure/TextStyle.cs | 7 ++- QuestPDF/QuestPDF.csproj | 4 +- 11 files changed, 167 insertions(+), 43 deletions(-) create mode 100644 QuestPDF.Examples/Engine/SimpleDocument.cs create mode 100644 QuestPDF/Fluent/GenerateExtensions.cs diff --git a/QuestPDF.Examples/Engine/ExampleTestBase.cs b/QuestPDF.Examples/Engine/ExampleTestBase.cs index 57fa4f5..5914953 100644 --- a/QuestPDF.Examples/Engine/ExampleTestBase.cs +++ b/QuestPDF.Examples/Engine/ExampleTestBase.cs @@ -6,6 +6,7 @@ using System.Reflection; using NUnit.Framework; using QuestPDF.Drawing; using QuestPDF.Elements; +using QuestPDF.Fluent; using QuestPDF.Infrastructure; using SkiaSharp; @@ -60,23 +61,13 @@ namespace QuestPDF.Examples.Engine var container = new Container(); methodInfo.Invoke(this, new object[] {container}); - var iteration = 1; + Func fileNameSchema = i => $"{fileName.ToLower()}-${i}.png"; - 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++; - } + var document = new SimpleDocument(container, size); + document.GenerateImages(fileNameSchema); + + if (showResult) + Process.Start("explorer", fileNameSchema(0)); } private byte[] RenderPage(Element element, Size size) diff --git a/QuestPDF.Examples/Engine/SimpleDocument.cs b/QuestPDF.Examples/Engine/SimpleDocument.cs new file mode 100644 index 0000000..9838252 --- /dev/null +++ b/QuestPDF.Examples/Engine/SimpleDocument.cs @@ -0,0 +1,33 @@ +using QuestPDF.Drawing; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Examples.Engine +{ + public class SimpleDocument : IDocument + { + private IContainer Container { get; } + private Size Size { get; } + + public SimpleDocument(IContainer container, Size size) + { + Container = container; + Size = size; + } + + public DocumentMetadata GetMetadata() + { + return new DocumentMetadata() + { + RasterDpi = PageSizes.PointsPerInch * 2, + Size = Size + }; + } + + public void Compose(IContainer container) + { + container.Element(Container.Child); + } + } +} \ No newline at end of file diff --git a/QuestPDF.ReportSample/Tests.cs b/QuestPDF.ReportSample/Tests.cs index aae8d8a..3d9128a 100644 --- a/QuestPDF.ReportSample/Tests.cs +++ b/QuestPDF.ReportSample/Tests.cs @@ -19,7 +19,7 @@ namespace QuestPDF.ReportSample var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf"); using var stream = new FileStream(path, FileMode.Create); - report.Generate(stream); + report.GeneratePdf(stream); Process.Start("explorer.exe", path); } @@ -45,7 +45,7 @@ namespace QuestPDF.ReportSample var sw = new Stopwatch(); sw.Start(); - var totalSize = reports.Select(x => x.Generate()).Sum(x => (long)x.Length); + var totalSize = reports.Select(x => x.GeneratePdf()).Sum(x => (long)x.Length); sw.Stop(); // show summary diff --git a/QuestPDF/Drawing/DocumentGenerator.cs b/QuestPDF/Drawing/DocumentGenerator.cs index 89221ba..bce6c08 100644 --- a/QuestPDF/Drawing/DocumentGenerator.cs +++ b/QuestPDF/Drawing/DocumentGenerator.cs @@ -1,9 +1,11 @@ using System; +using System.Collections.Generic; using System.IO; using QuestPDF.Drawing.Exceptions; using QuestPDF.Drawing.SpacePlan; using QuestPDF.Elements; using QuestPDF.Fluent; +using QuestPDF.Helpers; using QuestPDF.Infrastructure; using SkiaSharp; @@ -11,9 +13,7 @@ namespace QuestPDF.Drawing { static class DocumentGenerator { - const int DocumentLayoutExceptionThreshold = 250; - - internal static void Generate(Stream stream, IDocument document) + internal static void GeneratePdf(Stream stream, IDocument document) { var content = ElementExtensions.Create(document.Compose); var metadata = document.GetMetadata(); @@ -42,7 +42,7 @@ namespace QuestPDF.Drawing pdf.EndPage(); - if (totalPages >= DocumentLayoutExceptionThreshold) + if (totalPages >= metadata.DocumentLayoutExceptionThreshold) { pdf.Close(); stream.Close(); @@ -60,6 +60,57 @@ namespace QuestPDF.Drawing stream.Dispose(); } + internal static IEnumerable GenerateImages(IDocument document) + { + var content = ElementExtensions.Create(document.Compose); + var metadata = document.GetMetadata(); + + var totalPages = 1; + + while (true) + { + var spacePlan = content.Measure(metadata.Size); + byte[] result; + + try + { + result = RenderPage(content); + } + catch (Exception exception) + { + throw new DocumentDrawingException("An exception occured during document drawing.", exception); + } + + yield return result; + + if (totalPages >= metadata.DocumentLayoutExceptionThreshold) + { + throw new DocumentLayoutException("Composed layout generates infinite document."); + } + + if (spacePlan is FullRender) + break; + + totalPages++; + } + + byte[] RenderPage(Element element) + { + // scale the result so it is more readable + var scalingFactor = metadata.RasterDpi / (float) PageSizes.PointsPerInch; + + var imageInfo = new SKImageInfo((int) (metadata.Size.Width * scalingFactor), (int) (metadata.Size.Height * scalingFactor)); + using var surface = SKSurface.Create(imageInfo); + surface.Canvas.Scale(scalingFactor); + + var canvas = new Canvas(surface.Canvas); + element?.Draw(canvas, metadata.Size); + + surface.Canvas.Save(); + return surface.Snapshot().Encode(SKEncodedImageFormat.Png, 100).ToArray(); + } + } + private static SKDocumentPdfMetadata MapMetadata(DocumentMetadata metadata) { return new SKDocumentPdfMetadata diff --git a/QuestPDF/Drawing/DocumentMetadata.cs b/QuestPDF/Drawing/DocumentMetadata.cs index f23ba27..2a56516 100644 --- a/QuestPDF/Drawing/DocumentMetadata.cs +++ b/QuestPDF/Drawing/DocumentMetadata.cs @@ -21,6 +21,12 @@ namespace QuestPDF.Drawing public DateTime CreationDate { get; set; } = DateTime.Now; public DateTime ModifiedDate { get; set; } = DateTime.Now; + /// + /// If the number of generated pages exceeds this threshold + /// (likely due to infinite layout), the exception is thrown. + /// + public int DocumentLayoutExceptionThreshold { get; set; } = 250; + public static DocumentMetadata Default => new DocumentMetadata(); } } \ No newline at end of file diff --git a/QuestPDF/Fluent/ElementExtensions.cs b/QuestPDF/Fluent/ElementExtensions.cs index e03504a..ae66721 100644 --- a/QuestPDF/Fluent/ElementExtensions.cs +++ b/QuestPDF/Fluent/ElementExtensions.cs @@ -16,24 +16,6 @@ namespace QuestPDF.Fluent 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) @@ -158,5 +140,10 @@ namespace QuestPDF.Fluent LocationName = locationName }); } + + public static IContainer ShowIf(this IContainer element, bool condition) + { + return condition ? element : new Container(); + } } } \ No newline at end of file diff --git a/QuestPDF/Fluent/GenerateExtensions.cs b/QuestPDF/Fluent/GenerateExtensions.cs new file mode 100644 index 0000000..1d698ee --- /dev/null +++ b/QuestPDF/Fluent/GenerateExtensions.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.IO; +using QuestPDF.Drawing; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Fluent +{ + public static class GenerateExtensions + { + public static byte[] GeneratePdf(this IDocument document) + { + using var stream = new MemoryStream(); + document.GeneratePdf(stream); + return stream.ToArray(); + } + + public static void GeneratePdf(this IDocument document, string filePath) + { + using var stream = new FileStream(filePath, FileMode.Create); + document.GeneratePdf(stream); + } + + public static void GeneratePdf(this IDocument document, Stream stream) + { + DocumentGenerator.GeneratePdf(stream, document); + } + + public static IEnumerable GenerateImages(this IDocument document) + { + return DocumentGenerator.GenerateImages(document); + } + + /// Method should return fileName for given index + public static void GenerateImages(this IDocument document, Func filePath) + { + var index = 0; + + foreach (var imageData in document.GenerateImages()) + { + var path = filePath(index); + File.WriteAllBytes(path, imageData); + index++; + } + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/TextStyleExtensions.cs b/QuestPDF/Fluent/TextStyleExtensions.cs index 0acf5d4..4a74abc 100644 --- a/QuestPDF/Fluent/TextStyleExtensions.cs +++ b/QuestPDF/Fluent/TextStyleExtensions.cs @@ -7,6 +7,8 @@ namespace QuestPDF.Fluent { private static TextStyle Mutate(this TextStyle style, Action handler) { + style = style.Clone(); + handler(style); return style; } diff --git a/QuestPDF/Helpers/PageSizes.cs b/QuestPDF/Helpers/PageSizes.cs index 32445d9..7d6a777 100644 --- a/QuestPDF/Helpers/PageSizes.cs +++ b/QuestPDF/Helpers/PageSizes.cs @@ -4,6 +4,8 @@ namespace QuestPDF.Helpers { public static class PageSizes { + public const int PointsPerInch = 72; + public static Size A0 => new Size(2384, 3370); public static Size A1 => new Size(1684, 2384); public static Size A2 => new Size(1190, 1684); diff --git a/QuestPDF/Infrastructure/TextStyle.cs b/QuestPDF/Infrastructure/TextStyle.cs index e01b136..05f6b8e 100644 --- a/QuestPDF/Infrastructure/TextStyle.cs +++ b/QuestPDF/Infrastructure/TextStyle.cs @@ -1,4 +1,7 @@ -namespace QuestPDF.Infrastructure +using System; +using QuestPDF.Elements; + +namespace QuestPDF.Infrastructure { public class TextStyle { @@ -16,5 +19,7 @@ { return $"{Color}|{FontType}|{Size}|{LineHeight}|{Alignment}|{FontWeight}|{IsItalic}"; } + + internal TextStyle Clone() => (TextStyle)MemberwiseClone(); } } \ No newline at end of file diff --git a/QuestPDF/QuestPDF.csproj b/QuestPDF/QuestPDF.csproj index cdec3ac..c6f67de 100644 --- a/QuestPDF/QuestPDF.csproj +++ b/QuestPDF/QuestPDF.csproj @@ -4,9 +4,9 @@ MarcinZiabek CodeFlint QuestPDF - 2021.2.0 + 2021.3.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. - 2021.2.0 Internal links, external links, dynamic images, font weights + Generating images functionality, ShowIf element, fixed mutating TextStyle 8 true Logo.png