Browse Source

Implemented simple cache for all canvas operations (not working)

Marcin Ziąbek 2 years ago
parent
commit
2ec467be16

+ 18 - 2
Source/QuestPDF.ReportSample/Tests.cs

@@ -22,14 +22,30 @@ namespace QuestPDF.ReportSample
             var model = DataSource.GetReport();
             Report = new StandardReport(model);
             
-            //ImagePlaceholder.Solid = true;
+            ImagePlaceholder.Solid = true;
         }
         
         [Test] 
         public void GeneratePdfAndShow()
         {
-            Settings.EnableDebugging = true;
+            //Settings.EnableDebugging = true;
             Report.GeneratePdfAndShow();
+            return;
+            
+
+            var times = Enumerable.Range(0, 1).Select(_ => MeasureTime()).ToList();
+            Console.WriteLine(string.Join(",", times));
+            Console.WriteLine(times.Sum());
+
+            long MeasureTime()
+            {
+                var sw = new Stopwatch();
+                sw.Start();
+                Report.GeneratePdf();
+                sw.Stop();
+
+                return sw.ElapsedMilliseconds;
+            }
         }
         
         [Test] 

+ 2 - 1
Source/QuestPDF.UnitTests/TestEngine/MockCanvas.cs

@@ -20,7 +20,8 @@ namespace QuestPDF.UnitTests.TestEngine
         public void DrawRectangle(Position vector, Size size, string color) => DrawRectFunc(vector, size, color);
         public void DrawText(SKTextBlob skTextBlob, Position position, TextStyle style) => throw new NotImplementedException();
         public void DrawImage(SKImage image, Position position, Size size) => DrawImageFunc(image, position, size);
-
+        public void DrawPicture(SKPicture picture)  => throw new NotImplementedException();
+        
         public void DrawHyperlink(string url, Size size) => throw new NotImplementedException();
         public void DrawSectionLink(string sectionName, Size size) => throw new NotImplementedException();
         public void DrawSection(string sectionName) => throw new NotImplementedException();

+ 2 - 1
Source/QuestPDF.UnitTests/TestEngine/OperationRecordingCanvas.cs

@@ -18,7 +18,8 @@ namespace QuestPDF.UnitTests.TestEngine
         public void DrawRectangle(Position vector, Size size, string color) => Operations.Add(new CanvasDrawRectangleOperation(vector, size, color));
         public void DrawText(SKTextBlob skTextBlob, Position position, TextStyle style) => throw new NotImplementedException();
         public void DrawImage(SKImage image, Position position, Size size) => Operations.Add(new CanvasDrawImageOperation(position, size));
-        
+        public void DrawPicture(SKPicture picture)  => throw new NotImplementedException();
+
         public void DrawHyperlink(string url, Size size) => throw new NotImplementedException();
         public void DrawSectionLink(string sectionName, Size size) => throw new NotImplementedException();
         public void DrawSection(string sectionName) => throw new NotImplementedException();

+ 7 - 1
Source/QuestPDF/Drawing/DocumentGenerator.cs

@@ -114,6 +114,8 @@ namespace QuestPDF.Drawing
 
             var content = ConfigureContent(document, settings, debuggingState, documentId, useOriginalImages);
 
+            content.ApplyCanvasCache();
+            
             var pageContext = new PageContext();
             RenderPass(pageContext, new FreeCanvas(), content, debuggingState);
             pageContext.ResetPageNumber();
@@ -280,6 +282,10 @@ namespace QuestPDF.Drawing
                     return;
                 
                 x.PageContext = pageContext;
+                
+                if (x.Canvas is SkiaPictureCanvas)
+                    return;
+                
                 x.Canvas = canvas;
             });
         }
@@ -289,7 +295,7 @@ namespace QuestPDF.Drawing
             content.VisitChildren(x =>
             {
                 if (x is ICacheable)
-                    x.CreateProxy(y => new CacheProxy(y));
+                    x.CreateProxy(y => new CalculationCacheProxy(y));
             });
         }
 

+ 5 - 0
Source/QuestPDF/Drawing/FreeCanvas.cs

@@ -49,6 +49,11 @@ namespace QuestPDF.Drawing
         public void DrawImage(SKImage image, Position position, Size size)
         {
             
+        }
+        
+        public void DrawPicture(SKPicture picture)
+        {
+            
         }
 
         public void DrawHyperlink(string url, Size size)

+ 2 - 2
Source/QuestPDF/Drawing/Proxy/CacheProxy.cs → Source/QuestPDF/Drawing/Proxy/CalculationCacheProxy.cs

@@ -3,12 +3,12 @@ using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Drawing.Proxy
 {
-    internal class CacheProxy : ElementProxy
+    internal class CalculationCacheProxy : ElementProxy
     {
         public Size? AvailableSpace { get; set; }
         public SpacePlan? MeasurementResult { get; set; }
 
-        public CacheProxy(Element child)
+        public CalculationCacheProxy(Element child)
         {
             Child = child;
         }

+ 49 - 0
Source/QuestPDF/Drawing/Proxy/CanvasCacheProxy.cs

@@ -0,0 +1,49 @@
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Drawing.Proxy;
+
+internal class CanvasCacheProxy : ElementProxy
+{
+    private SkiaPictureCanvas PictureCanvas { get; set; } = new();
+    private int? FirstPageIndex { get; set; }
+    
+    public CanvasCacheProxy(Element child)
+    {
+        Child = child;
+    }
+    
+    internal override SpacePlan Measure(Size availableSpace)
+    {
+        var snapshotIndex = PageContext.CurrentPage - FirstPageIndex;
+
+        if (snapshotIndex > 0 && snapshotIndex < PictureCanvas.Pictures.Count)
+        {
+            var isLast = snapshotIndex == PictureCanvas.Pictures.Count - 1;
+            var size = PictureCanvas.Pictures[snapshotIndex.Value].Size;
+
+            return isLast ? SpacePlan.FullRender(size) : SpacePlan.PartialRender(size);
+        }
+        
+        return Child?.Measure(availableSpace) ?? SpacePlan.FullRender(0, 0);
+    }
+        
+    internal override void Draw(Size availableSpace)
+    {
+        var snapshotIndex = PageContext.CurrentPage - FirstPageIndex;
+
+        if (snapshotIndex > 0 && snapshotIndex < PictureCanvas.Pictures.Count)
+        {
+            var picture = PictureCanvas.Pictures[snapshotIndex.Value];
+            Canvas.DrawPicture(picture.Picture);
+            return;
+        }
+        
+        FirstPageIndex ??= PageContext.CurrentPage;
+ 
+        PictureCanvas.BeginPage(availableSpace);
+        Child.VisitChildren(x => x.Canvas = PictureCanvas);
+        Child?.Draw(availableSpace);
+        PictureCanvas.EndPage();
+    }
+}

+ 62 - 0
Source/QuestPDF/Drawing/Proxy/Helpers.cs

@@ -1,5 +1,7 @@
 using System.Linq;
 using QuestPDF.Elements;
+using QuestPDF.Elements.Text;
+using QuestPDF.Elements.Text.Items;
 using QuestPDF.Helpers;
 using QuestPDF.Infrastructure;
 using QuestPDF.Previewer;
@@ -59,4 +61,64 @@ internal static class Helpers
             x.CreateProxy(y => y is ElementProxy proxy ? proxy.Child : y);
         });
     }
+    
+    public static void ApplyCanvasCache(this Element hierarchyRoot)
+    {
+        Traverse(hierarchyRoot);
+        
+        /// returns true when certain elements meets all criteria to be cached
+        bool Traverse(Element parent)
+        {
+            if (!CanBeCached(parent))
+                return false;
+            
+            if (parent is IContainer container)
+            {
+                return Traverse(container.Child as Element);
+            }
+
+            var children = parent.GetChildren().ToList();
+            var childrenAreApplicable = children.Select(Traverse).ToArray();
+
+            if (childrenAreApplicable.All(x => x))
+                return true;
+
+            var proxyIndex = 0;
+            
+            parent.CreateProxy(element =>
+            {
+                var isApplicable = childrenAreApplicable[proxyIndex];
+                proxyIndex++;
+
+                return isApplicable ? new CanvasCacheProxy(element) : element;
+            });
+
+            return false;
+        }
+
+        bool CanBeCached(IElement element)
+        {
+            if (element is TextBlock textBlock)
+            {
+                foreach (var textBlockItem in textBlock.Items)
+                {
+                    if (textBlockItem is TextBlockPageNumber)
+                        return false;
+                    
+                    if (textBlockItem is TextBlockElement textBlockElement && !Traverse(textBlockElement.Element))
+                        return false;
+                }
+
+                return true;
+            }
+            else if (element is DynamicHost dynamicHost)
+            {
+                return false;
+            }
+            else
+            {
+                return true;
+            }
+        }
+    }
 }

+ 5 - 0
Source/QuestPDF/Drawing/SkiaCanvasBase.cs

@@ -37,6 +37,11 @@ namespace QuestPDF.Drawing
         {
             Canvas.DrawImage(image, new SKRect(vector.X, vector.Y, size.Width, size.Height));
         }
+        
+        public void DrawPicture(SKPicture picture)
+        {
+            Canvas.DrawPicture(picture);
+        }
 
         public void DrawHyperlink(string url, Size size)
         {

+ 1 - 1
Source/QuestPDF/Drawing/SkiaPictureCanvas.cs

@@ -24,7 +24,7 @@ namespace QuestPDF.Drawing
         private SKPictureRecorder? PictureRecorder { get; set; }
         private Size? CurrentPageSize { get; set; }
 
-        public ICollection<PreviewerPicture> Pictures { get; } = new List<PreviewerPicture>();
+        public IList<PreviewerPicture> Pictures { get; } = new List<PreviewerPicture>();
         
         public override void BeginDocument()
         {

+ 1 - 0
Source/QuestPDF/Infrastructure/ICanvas.cs

@@ -10,6 +10,7 @@ namespace QuestPDF.Infrastructure
         void DrawRectangle(Position vector, Size size, string color);
         void DrawText(SKTextBlob skTextBlob, Position position, TextStyle style);
         void DrawImage(SKImage image, Position position, Size size);
+        void DrawPicture(SKPicture picture);
 
         void DrawHyperlink(string url, Size size);
         void DrawSectionLink(string sectionName, Size size);