Browse Source

2021.3.0 Generating images functionality, ShowIf element, fixed mutating TextStyle

Marcin Ziąbek 4 years ago
parent
commit
43c4011f91

+ 7 - 16
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<int, string> 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)

+ 33 - 0
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);
+        }
+    }
+}

+ 2 - 2
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

+ 55 - 4
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<byte[]> 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

+ 6 - 0
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;
 
+        /// <summary>
+        /// If the number of generated pages exceeds this threshold
+        /// (likely due to infinite layout), the exception is thrown.
+        /// </summary>
+        public int DocumentLayoutExceptionThreshold { get; set; } = 250;
+        
         public static DocumentMetadata Default => new DocumentMetadata();
     }
 }

+ 5 - 18
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<T>(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();
+        }
     }
 }

+ 47 - 0
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<byte[]> GenerateImages(this IDocument document)
+        {
+            return DocumentGenerator.GenerateImages(document);
+        }
+        
+        /// <param name="filePath">Method should return fileName for given index</param>
+        public static void GenerateImages(this IDocument document, Func<int, string> filePath)
+        {
+            var index = 0;
+            
+            foreach (var imageData in document.GenerateImages())
+            {
+                var path = filePath(index);
+                File.WriteAllBytes(path, imageData);
+                index++;
+            }
+        }
+    }
+}

+ 2 - 0
QuestPDF/Fluent/TextStyleExtensions.cs

@@ -7,6 +7,8 @@ namespace QuestPDF.Fluent
     {
         private static TextStyle Mutate(this TextStyle style, Action<TextStyle> handler)
         {
+            style = style.Clone();
+            
             handler(style);
             return style;
         }

+ 2 - 0
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);

+ 6 - 1
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();
     }
 }

+ 2 - 2
QuestPDF/QuestPDF.csproj

@@ -4,9 +4,9 @@
         <Authors>MarcinZiabek</Authors>
         <Company>CodeFlint</Company>
         <PackageId>QuestPDF</PackageId>
-        <Version>2021.2.0</Version>
+        <Version>2021.3.0</Version>
         <PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
-        <PackageReleaseNotes>2021.2.0 Internal links, external links, dynamic images, font weights</PackageReleaseNotes>
+        <PackageReleaseNotes>Generating images functionality, ShowIf element, fixed mutating TextStyle</PackageReleaseNotes>
         <LangVersion>8</LangVersion>
         <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
         <PackageIcon>Logo.png</PackageIcon>