From 704256510eb1a675784d01a8907c28f505279bbd Mon Sep 17 00:00:00 2001 From: MarcinZiabek Date: Mon, 26 Sep 2022 22:40:12 +0200 Subject: [PATCH] Implemented experiment --- QuestPDF.Examples/TableBenchmark.cs | 2 +- QuestPDF.ReportSample/DataSource.cs | 4 +- QuestPDF.ReportSample/Tests.cs | 5 +- QuestPDF.UnitTests/TestEngine/MockCanvas.cs | 1 + .../TestEngine/OperationRecordingCanvas.cs | 3 +- QuestPDF/Drawing/DocumentGenerator.cs | 16 ++++- QuestPDF/Drawing/FreeCanvas.cs | 5 ++ QuestPDF/Drawing/SkiaCanvasBase.cs | 5 ++ QuestPDF/Drawing/SkiaCaptureCanvas.cs | 39 ++++++++++++ QuestPDF/Elements/DrawingCache.cs | 59 +++++++++++++++++++ QuestPDF/Elements/Page.cs | 1 + .../Fluent/TextSpanDescriptorExtensions.cs | 11 ---- QuestPDF/Infrastructure/ICanvas.cs | 1 + 13 files changed, 134 insertions(+), 18 deletions(-) create mode 100644 QuestPDF/Drawing/SkiaCaptureCanvas.cs create mode 100644 QuestPDF/Elements/DrawingCache.cs diff --git a/QuestPDF.Examples/TableBenchmark.cs b/QuestPDF.Examples/TableBenchmark.cs index 9cba9f3..cb22bd1 100644 --- a/QuestPDF.Examples/TableBenchmark.cs +++ b/QuestPDF.Examples/TableBenchmark.cs @@ -17,7 +17,7 @@ namespace QuestPDF.Examples .PageSize(PageSizes.A4) .ShowResults() .MaxPages(10_000) - .EnableCaching(true) + //.EnableCaching(true) .EnableDebugging(false) .Render(container => { diff --git a/QuestPDF.ReportSample/DataSource.cs b/QuestPDF.ReportSample/DataSource.cs index ba18930..473b869 100644 --- a/QuestPDF.ReportSample/DataSource.cs +++ b/QuestPDF.ReportSample/DataSource.cs @@ -15,8 +15,8 @@ namespace QuestPDF.ReportSample HeaderFields = HeaderFields(), LogoData = Helpers.GetImage("Logo.png"), - Sections = Enumerable.Range(0, 40).Select(x => GenerateSection()).ToList(), - Photos = Enumerable.Range(0, 25).Select(x => GetReportPhotos()).ToList() + Sections = Enumerable.Range(0, 2000).Select(x => GenerateSection()).ToList(), + Photos = Enumerable.Range(0, 200).Select(x => GetReportPhotos()).ToList() }; List HeaderFields() diff --git a/QuestPDF.ReportSample/Tests.cs b/QuestPDF.ReportSample/Tests.cs index b460e9e..ae5fb08 100644 --- a/QuestPDF.ReportSample/Tests.cs +++ b/QuestPDF.ReportSample/Tests.cs @@ -24,7 +24,10 @@ namespace QuestPDF.ReportSample [Test] public void GenerateAndShowPdf() { - //ImagePlaceholder.Solid = true; + Settings.DocumentLayoutExceptionThreshold = 10_000; + Settings.EnableCaching = true; + Settings.EnableDebugging = false; + ImagePlaceholder.Solid = true; var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf"); Report.GeneratePdf(path); diff --git a/QuestPDF.UnitTests/TestEngine/MockCanvas.cs b/QuestPDF.UnitTests/TestEngine/MockCanvas.cs index 68bbc07..737d6ec 100644 --- a/QuestPDF.UnitTests/TestEngine/MockCanvas.cs +++ b/QuestPDF.UnitTests/TestEngine/MockCanvas.cs @@ -20,6 +20,7 @@ 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(); diff --git a/QuestPDF.UnitTests/TestEngine/OperationRecordingCanvas.cs b/QuestPDF.UnitTests/TestEngine/OperationRecordingCanvas.cs index 5ca55aa..84f499b 100644 --- a/QuestPDF.UnitTests/TestEngine/OperationRecordingCanvas.cs +++ b/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(); diff --git a/QuestPDF/Drawing/DocumentGenerator.cs b/QuestPDF/Drawing/DocumentGenerator.cs index b64baac..a9c12d9 100644 --- a/QuestPDF/Drawing/DocumentGenerator.cs +++ b/QuestPDF/Drawing/DocumentGenerator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using QuestPDF.Drawing.Exceptions; @@ -67,13 +68,24 @@ namespace QuestPDF.Drawing ApplyDefaultTextStyle(content, TextStyle.LibraryDefault); var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null; + debuggingState = null; - if (Settings.EnableCaching) - ApplyCaching(content); + //if (Settings.EnableCaching) + //ApplyCaching(content); var pageContext = new PageContext(); + + var stopwatch = new Stopwatch(); + + stopwatch.Restart(); RenderPass(pageContext, new FreeCanvas(), content, debuggingState); + stopwatch.Stop(); + Console.WriteLine($"Cold free: {stopwatch.Elapsed}"); + + stopwatch.Restart(); RenderPass(pageContext, canvas, content, debuggingState); + stopwatch.Stop(); + Console.WriteLine($"Canvas: {stopwatch.Elapsed}"); } internal static void RenderPass(PageContext pageContext, TCanvas canvas, Container content, DebuggingState? debuggingState) diff --git a/QuestPDF/Drawing/FreeCanvas.cs b/QuestPDF/Drawing/FreeCanvas.cs index bc2c68e..4b37052 100644 --- a/QuestPDF/Drawing/FreeCanvas.cs +++ b/QuestPDF/Drawing/FreeCanvas.cs @@ -51,6 +51,11 @@ namespace QuestPDF.Drawing } + public void DrawPicture(SKPicture picture) + { + + } + public void DrawHyperlink(string url, Size size) { diff --git a/QuestPDF/Drawing/SkiaCanvasBase.cs b/QuestPDF/Drawing/SkiaCanvasBase.cs index 9d1851a..ba27e96 100644 --- a/QuestPDF/Drawing/SkiaCanvasBase.cs +++ b/QuestPDF/Drawing/SkiaCanvasBase.cs @@ -38,6 +38,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) { Canvas.DrawUrlAnnotation(new SKRect(0, 0, size.Width, size.Height), url); diff --git a/QuestPDF/Drawing/SkiaCaptureCanvas.cs b/QuestPDF/Drawing/SkiaCaptureCanvas.cs new file mode 100644 index 0000000..65b30e5 --- /dev/null +++ b/QuestPDF/Drawing/SkiaCaptureCanvas.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; +using SkiaSharp; + +namespace QuestPDF.Drawing +{ + internal class SkiaCaptureCanvas : SkiaCanvasBase + { + private SKPictureRecorder? PictureRecorder { get; set; } + private Size? CurrentPageSize { get; set; } + public SKPicture? CurrentPicture { get; set; } + + public override void BeginDocument() + { + + } + + public override void BeginPage(Size size) + { + CurrentPageSize = size; + PictureRecorder = new SKPictureRecorder(); + + Canvas = PictureRecorder.BeginRecording(new SKRect(0, 0, size.Width, size.Height)); + } + + public override void EndPage() + { + CurrentPicture = PictureRecorder?.EndRecording(); + + PictureRecorder?.Dispose(); + PictureRecorder = null; + } + + public override void EndDocument() { } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/DrawingCache.cs b/QuestPDF/Elements/DrawingCache.cs new file mode 100644 index 0000000..3072ec7 --- /dev/null +++ b/QuestPDF/Elements/DrawingCache.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using QuestPDF.Drawing; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; +using SkiaSharp; + +namespace QuestPDF.Elements +{ + internal class DrawingCache : ContainerElement + { + private SkiaCaptureCanvas CaptureCanvas { get; } = new(); + private Dictionary MeasureCache { get; } = new(); + private Dictionary DrawCache { get; } = new(); + + ~DrawingCache() + { + foreach (var picture in DrawCache.Values) + picture.Dispose(); + + DrawCache.Clear(); + } + + internal override void Initialize(IPageContext pageContext, ICanvas canvas) + { + Child.VisitChildren(x => x.Canvas = CaptureCanvas); + base.Initialize(pageContext, canvas); + } + + internal override SpacePlan Measure(Size availableSpace) + { + var cacheKey = PageContext.CurrentPage; + + if (MeasureCache.TryGetValue(cacheKey, out var result)) + return result; + + var childSize = Child?.Measure(availableSpace) ?? SpacePlan.FullRender(Size.Zero); + MeasureCache.Add(cacheKey, childSize); + return childSize; + } + + internal override void Draw(Size availableSpace) + { + var cacheKey = PageContext.CurrentPage; + + if (DrawCache.TryGetValue(cacheKey, out var result)) + { + Canvas.DrawPicture(result); + return; + } + + CaptureCanvas.BeginPage(availableSpace); + Child?.Draw(availableSpace); + CaptureCanvas.EndPage(); + + var picture = CaptureCanvas.CurrentPicture; + DrawCache.Add(cacheKey, picture); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Page.cs b/QuestPDF/Elements/Page.cs index 275d876..71ea4a9 100644 --- a/QuestPDF/Elements/Page.cs +++ b/QuestPDF/Elements/Page.cs @@ -30,6 +30,7 @@ namespace QuestPDF.Elements public void Compose(IContainer container) { container + //.Element(new DrawingCache()) .Background(BackgroundColor) .Layers(layers => { diff --git a/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs b/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs index 23cda24..5bc12e8 100644 --- a/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs +++ b/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs @@ -15,17 +15,6 @@ namespace QuestPDF.Fluent return descriptor; } - public static T Fallback(this T descriptor, TextStyle? value = null) where T : TextSpanDescriptor - { - descriptor.TextStyle.Fallback = value; - return descriptor; - } - - public static T Fallback(this T descriptor, Func handler) where T : TextSpanDescriptor - { - return descriptor.Fallback(handler(TextStyle.Default)); - } - public static T FontColor(this T descriptor, string value) where T : TextSpanDescriptor { descriptor.MutateTextStyle(x => x.FontColor(value)); diff --git a/QuestPDF/Infrastructure/ICanvas.cs b/QuestPDF/Infrastructure/ICanvas.cs index 8a65ef1..3357585 100644 --- a/QuestPDF/Infrastructure/ICanvas.cs +++ b/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);