Browse Source

Lazy Element + IDisposable integration + GC Optimizations (#1104)

* Optimization: implemented Lazy element

* 2025.1.0-alpha0

* Performance optimization: disposing native objects as early as possible (paragraph, images)

* Skia Native: added GC.SuppressFinalize(this)

* Document generation: increased level of parallelism from 2 to 4

* Document generation: increased level of parallelism from 2 to 4

* Text: improved performance in certain scenarios

* VisitChildren: performance optimization

* Table: performance optimization

* TextBlock: performance optimization

* SnapshotRecorder: performance optimization

* Optimization: GC.SuppressFinalize(this)

* Implemented IDisposable in missing elements

* Further IDisposable improvements

* 2025.1.0-rc0

* Update main.yml
Marcin Ziąbek 11 tháng trước cách đây
mục cha
commit
c42d9cbc8a
37 tập tin đã thay đổi với 333 bổ sung79 xóa
  1. 34 8
      Source/QuestPDF/Drawing/DocumentGenerator.cs
  2. 9 2
      Source/QuestPDF/Drawing/ImageCanvas.cs
  3. 8 1
      Source/QuestPDF/Drawing/Proxy/SnapshotRecorder.cs
  4. 10 2
      Source/QuestPDF/Drawing/SkiaDocumentCanvasBase.cs
  5. 10 2
      Source/QuestPDF/Drawing/SvgCanvas.cs
  6. 1 0
      Source/QuestPDF/Elements/Dynamic.cs
  7. 10 1
      Source/QuestPDF/Elements/DynamicImage.cs
  8. 11 1
      Source/QuestPDF/Elements/DynamicSvgImage.cs
  9. 9 1
      Source/QuestPDF/Elements/Image.cs
  10. 81 0
      Source/QuestPDF/Elements/Lazy.cs
  11. 16 5
      Source/QuestPDF/Elements/RepeatContent.cs
  12. 9 1
      Source/QuestPDF/Elements/SvgImage.cs
  13. 4 5
      Source/QuestPDF/Elements/Table/Table.cs
  14. 13 2
      Source/QuestPDF/Elements/Text/TextBlock.cs
  15. 39 1
      Source/QuestPDF/Fluent/ElementExtensions.cs
  16. 3 0
      Source/QuestPDF/Fluent/ImageExtensions.cs
  17. 23 6
      Source/QuestPDF/Helpers/Helpers.cs
  18. 11 3
      Source/QuestPDF/Infrastructure/Image.cs
  19. 9 2
      Source/QuestPDF/Infrastructure/SvgImage.cs
  20. 1 1
      Source/QuestPDF/QuestPDF.csproj
  21. 6 35
      Source/QuestPDF/Resources/ReleaseNotes.txt
  22. 1 0
      Source/QuestPDF/Skia/SkBitmap.cs
  23. 1 0
      Source/QuestPDF/Skia/SkCanvas.cs
  24. 1 0
      Source/QuestPDF/Skia/SkData.cs
  25. 1 0
      Source/QuestPDF/Skia/SkDocument.cs
  26. 1 0
      Source/QuestPDF/Skia/SkImage.cs
  27. 1 0
      Source/QuestPDF/Skia/SkPicture.cs
  28. 1 0
      Source/QuestPDF/Skia/SkPictureRecorder.cs
  29. 1 0
      Source/QuestPDF/Skia/SkSvgImage.cs
  30. 1 0
      Source/QuestPDF/Skia/SkText.cs
  31. 1 0
      Source/QuestPDF/Skia/SkWriteStream.cs
  32. 1 0
      Source/QuestPDF/Skia/Text/SkFontCollection.cs
  33. 1 0
      Source/QuestPDF/Skia/Text/SkParagraph.cs
  34. 1 0
      Source/QuestPDF/Skia/Text/SkParagraphBuilder.cs
  35. 1 0
      Source/QuestPDF/Skia/Text/SkTextStyle.cs
  36. 1 0
      Source/QuestPDF/Skia/Text/SkTypeface.cs
  37. 1 0
      Source/QuestPDF/Skia/Text/SkTypefaceProvider.cs

+ 34 - 8
Source/QuestPDF/Drawing/DocumentGenerator.cs

@@ -27,7 +27,7 @@ namespace QuestPDF.Drawing
             
             
             var metadata = document.GetMetadata();
             var metadata = document.GetMetadata();
             var settings = document.GetSettings();
             var settings = document.GetSettings();
-            var canvas = new PdfCanvas(stream, metadata, settings);
+            using var canvas = new PdfCanvas(stream, metadata, settings);
             RenderDocument(canvas, document, settings);
             RenderDocument(canvas, document, settings);
         }
         }
         
         
@@ -36,7 +36,7 @@ namespace QuestPDF.Drawing
             ValidateLicense();
             ValidateLicense();
             
             
             var settings = document.GetSettings();
             var settings = document.GetSettings();
-            var canvas = new XpsCanvas(stream, settings);
+            using var canvas = new XpsCanvas(stream, settings);
             RenderDocument(canvas, document, settings);
             RenderDocument(canvas, document, settings);
         }
         }
         
         
@@ -47,7 +47,7 @@ namespace QuestPDF.Drawing
             var documentSettings = document.GetSettings();
             var documentSettings = document.GetSettings();
             documentSettings.ImageRasterDpi = imageGenerationSettings.RasterDpi;
             documentSettings.ImageRasterDpi = imageGenerationSettings.RasterDpi;
             
             
-            var canvas = new ImageCanvas(imageGenerationSettings);
+            using var canvas = new ImageCanvas(imageGenerationSettings);
             RenderDocument(canvas, document, documentSettings);
             RenderDocument(canvas, document, documentSettings);
 
 
             return canvas.Images;
             return canvas.Images;
@@ -57,7 +57,7 @@ namespace QuestPDF.Drawing
         {
         {
             ValidateLicense();
             ValidateLicense();
             
             
-            var canvas = new SvgCanvas();
+            using var canvas = new SvgCanvas();
             RenderDocument(canvas, document, document.GetSettings());
             RenderDocument(canvas, document, document.GetSettings());
 
 
             return canvas.Images;
             return canvas.Images;
@@ -119,7 +119,7 @@ namespace QuestPDF.Drawing
         /// - Investigate the underlying causes of these scalability issues in future releases.
         /// - Investigate the underlying causes of these scalability issues in future releases.
         /// - Explore optimizations for reducing locking contention, improving memory efficiency, and enhancing CPU cache utilization.
         /// - Explore optimizations for reducing locking contention, improving memory efficiency, and enhancing CPU cache utilization.
         /// </summary>
         /// </summary>
-        private static SemaphoreSlim RenderDocumentSemaphore = new(2);
+        private static readonly SemaphoreSlim RenderDocumentSemaphore = new(4);
         
         
         private static void RenderDocument<TCanvas>(TCanvas canvas, IDocument document, DocumentSettings settings) where TCanvas : ICanvas, IRenderingCanvas
         private static void RenderDocument<TCanvas>(TCanvas canvas, IDocument document, DocumentSettings settings) where TCanvas : ICanvas, IRenderingCanvas
         {
         {
@@ -160,6 +160,8 @@ namespace QuestPDF.Drawing
             
             
             if (canvas is CompanionCanvas companionCanvas)
             if (canvas is CompanionCanvas companionCanvas)
                 companionCanvas.Hierarchy = content.ExtractHierarchy();
                 companionCanvas.Hierarchy = content.ExtractHierarchy();
+            
+            content.ReleaseDisposableChildren();
         }
         }
         
         
         private static void RenderMergedDocument<TCanvas>(TCanvas canvas, MergedDocument document, DocumentSettings settings)
         private static void RenderMergedDocument<TCanvas>(TCanvas canvas, MergedDocument document, DocumentSettings settings)
@@ -191,7 +193,8 @@ namespace QuestPDF.Drawing
                 foreach (var documentPart in documentParts)
                 foreach (var documentPart in documentParts)
                 {
                 {
                     documentPageContext.SetDocumentId(documentPart.DocumentId);
                     documentPageContext.SetDocumentId(documentPart.DocumentId);
-                    RenderPass(documentPageContext, canvas, documentPart.Content);   
+                    RenderPass(documentPageContext, canvas, documentPart.Content);
+                    documentPart.Content.ReleaseDisposableChildren();
                 }
                 }
             }
             }
             else
             else
@@ -204,6 +207,8 @@ namespace QuestPDF.Drawing
                     RenderPass(pageContext, new FreeCanvas(), documentPart.Content);
                     RenderPass(pageContext, new FreeCanvas(), documentPart.Content);
                     pageContext.ProceedToNextRenderingPhase();
                     pageContext.ProceedToNextRenderingPhase();
                     RenderPass(pageContext, canvas, documentPart.Content);
                     RenderPass(pageContext, canvas, documentPart.Content);
+                    
+                    documentPart.Content.ReleaseDisposableChildren();
                 }
                 }
             }
             }
         }
         }
@@ -270,6 +275,7 @@ namespace QuestPDF.Drawing
             
             
             void ApplyLayoutDebugging()
             void ApplyLayoutDebugging()
             {
             {
+                content.VisitChildren(x => (x as SnapshotRecorder)?.Dispose());
                 content.RemoveExistingProxiesOfType<SnapshotRecorder>();
                 content.RemoveExistingProxiesOfType<SnapshotRecorder>();
 
 
                 content.ApplyLayoutOverflowDetection();
                 content.ApplyLayoutOverflowDetection();
@@ -387,6 +393,9 @@ namespace QuestPDF.Drawing
                     return true;
                     return true;
                 }
                 }
 
 
+                if (content is Lazy lazy)
+                    return lazy.IsCacheable;
+
                 if (content is DynamicHost)
                 if (content is DynamicHost)
                     return false;
                     return false;
                 
                 
@@ -467,6 +476,13 @@ namespace QuestPDF.Drawing
                     dynamicHost.ImageCompressionQuality ??= imageCompressionQuality;
                     dynamicHost.ImageCompressionQuality ??= imageCompressionQuality;
                     dynamicHost.UseOriginalImage |= useOriginalImages;
                     dynamicHost.UseOriginalImage |= useOriginalImages;
                 }
                 }
+                
+                if (x is Lazy lazy)
+                {
+                    lazy.ImageTargetDpi ??= imageRasterDpi;
+                    lazy.ImageCompressionQuality ??= imageCompressionQuality;
+                    lazy.UseOriginalImage |= useOriginalImages;
+                }
 
 
                 if (x is TextBlock textBlock)
                 if (x is TextBlock textBlock)
                 {
                 {
@@ -502,11 +518,21 @@ namespace QuestPDF.Drawing
             if (content is DynamicHost dynamicHost)
             if (content is DynamicHost dynamicHost)
                 dynamicHost.TextStyle = dynamicHost.TextStyle.ApplyInheritedStyle(documentDefaultTextStyle);
                 dynamicHost.TextStyle = dynamicHost.TextStyle.ApplyInheritedStyle(documentDefaultTextStyle);
             
             
+            if (content is Lazy lazy)
+                lazy.TextStyle = lazy.TextStyle.ApplyInheritedStyle(documentDefaultTextStyle);
+            
             if (content is DefaultTextStyle defaultTextStyleElement)
             if (content is DefaultTextStyle defaultTextStyleElement)
                documentDefaultTextStyle = defaultTextStyleElement.TextStyle.ApplyInheritedStyle(documentDefaultTextStyle);
                documentDefaultTextStyle = defaultTextStyleElement.TextStyle.ApplyInheritedStyle(documentDefaultTextStyle);
 
 
-            foreach (var child in content.GetChildren())
-                ApplyInheritedAndGlobalTexStyle(child, documentDefaultTextStyle);
+            if (content is ContainerElement containerElement)
+            {
+                ApplyInheritedAndGlobalTexStyle(containerElement.Child, documentDefaultTextStyle);
+            }
+            else
+            {
+                foreach (var child in content.GetChildren())
+                    ApplyInheritedAndGlobalTexStyle(child, documentDefaultTextStyle);
+            }
         }
         }
     }
     }
 }
 }

+ 9 - 2
Source/QuestPDF/Drawing/ImageCanvas.cs

@@ -6,7 +6,7 @@ using QuestPDF.Skia;
 
 
 namespace QuestPDF.Drawing
 namespace QuestPDF.Drawing
 {
 {
-    internal sealed class ImageCanvas : SkiaCanvasBase
+    internal sealed class ImageCanvas : SkiaCanvasBase, IDisposable
     {
     {
         private ImageGenerationSettings Settings { get; }
         private ImageGenerationSettings Settings { get; }
         private SkBitmap Bitmap { get; set; }
         private SkBitmap Bitmap { get; set; }
@@ -21,9 +21,15 @@ namespace QuestPDF.Drawing
         }
         }
         
         
         ~ImageCanvas()
         ~ImageCanvas()
+        {
+            Dispose();
+        }
+
+        public void Dispose()
         {
         {
             Canvas?.Dispose();
             Canvas?.Dispose();
             Bitmap?.Dispose();
             Bitmap?.Dispose();
+            GC.SuppressFinalize(this);
         }
         }
         
         
         public override void BeginDocument()
         public override void BeginDocument()
@@ -33,7 +39,8 @@ namespace QuestPDF.Drawing
 
 
         public override void EndDocument()
         public override void EndDocument()
         {
         {
-            
+            Canvas?.Dispose();
+            Bitmap?.Dispose();
         }
         }
 
 
         public override void BeginPage(Size size)
         public override void BeginPage(Size size)

+ 8 - 1
Source/QuestPDF/Drawing/Proxy/SnapshotRecorder.cs

@@ -6,7 +6,7 @@ using QuestPDF.Skia;
 
 
 namespace QuestPDF.Drawing.Proxy;
 namespace QuestPDF.Drawing.Proxy;
 
 
-internal class SnapshotRecorder : ElementProxy
+internal class SnapshotRecorder : ElementProxy, IDisposable
 {
 {
     SnapshotRecorderCanvas RecorderCanvas { get; } = new();
     SnapshotRecorderCanvas RecorderCanvas { get; } = new();
     SkPictureRecorder PictureRecorder { get; } = new();
     SkPictureRecorder PictureRecorder { get; } = new();
@@ -14,11 +14,18 @@ internal class SnapshotRecorder : ElementProxy
     Dictionary<int, SkPicture> DrawCache { get; } = new();
     Dictionary<int, SkPicture> DrawCache { get; } = new();
 
 
     ~SnapshotRecorder()
     ~SnapshotRecorder()
+    {
+        Dispose();
+    }
+
+    public void Dispose()
     {
     {
         PictureRecorder.Dispose();
         PictureRecorder.Dispose();
         
         
         foreach (var cacheValue in DrawCache.Values)
         foreach (var cacheValue in DrawCache.Values)
             cacheValue.Dispose();
             cacheValue.Dispose();
+        
+        GC.SuppressFinalize(this);
     }
     }
     
     
     public SnapshotRecorder(Element child)
     public SnapshotRecorder(Element child)

+ 10 - 2
Source/QuestPDF/Drawing/SkiaDocumentCanvasBase.cs

@@ -1,9 +1,10 @@
-using QuestPDF.Infrastructure;
+using System;
+using QuestPDF.Infrastructure;
 using QuestPDF.Skia;
 using QuestPDF.Skia;
 
 
 namespace QuestPDF.Drawing
 namespace QuestPDF.Drawing
 {
 {
-    internal class SkiaDocumentCanvasBase : SkiaCanvasBase
+    internal class SkiaDocumentCanvasBase : SkiaCanvasBase, IDisposable
     {
     {
         private SkDocument? Document { get; }
         private SkDocument? Document { get; }
 
 
@@ -14,7 +15,14 @@ namespace QuestPDF.Drawing
 
 
         ~SkiaDocumentCanvasBase()
         ~SkiaDocumentCanvasBase()
         {
         {
+            Dispose();
+        }
+
+        public void Dispose()
+        {
+            Canvas?.Dispose();
             Document?.Dispose();
             Document?.Dispose();
+            GC.SuppressFinalize(this);
         }
         }
         
         
         public override void BeginDocument()
         public override void BeginDocument()

+ 10 - 2
Source/QuestPDF/Drawing/SvgCanvas.cs

@@ -9,14 +9,21 @@ using QuestPDF.Skia;
 
 
 namespace QuestPDF.Drawing
 namespace QuestPDF.Drawing
 {
 {
-    internal sealed class SvgCanvas : SkiaCanvasBase
+    internal sealed class SvgCanvas : SkiaCanvasBase, IDisposable
     {
     {
         internal SkWriteStream WriteStream { get; set; }
         internal SkWriteStream WriteStream { get; set; }
         internal ICollection<string> Images { get; } = new List<string>();
         internal ICollection<string> Images { get; } = new List<string>();
         
         
         ~SvgCanvas()
         ~SvgCanvas()
         {
         {
+            Dispose();
+        }
+
+        public void Dispose()
+        {
+            Canvas?.Dispose();
             WriteStream?.Dispose();
             WriteStream?.Dispose();
+            GC.SuppressFinalize(this);
         }
         }
         
         
         public override void BeginDocument()
         public override void BeginDocument()
@@ -26,7 +33,8 @@ namespace QuestPDF.Drawing
 
 
         public override void EndDocument()
         public override void EndDocument()
         {
         {
-            
+            Canvas?.Dispose();
+            WriteStream?.Dispose();
         }
         }
 
 
         public override void BeginPage(Size size)
         public override void BeginPage(Size size)

+ 1 - 0
Source/QuestPDF/Elements/Dynamic.cs

@@ -47,6 +47,7 @@ namespace QuestPDF.Elements
             var composeResult = ComposeContent(availableSpace, acceptNewState: true);
             var composeResult = ComposeContent(availableSpace, acceptNewState: true);
             var content = composeResult.Content as Element; 
             var content = composeResult.Content as Element; 
             content?.Draw(availableSpace);
             content?.Draw(availableSpace);
+            content.ReleaseDisposableChildren();
             
             
             if (!composeResult.HasMoreContent)
             if (!composeResult.HasMoreContent)
                 IsRendered = true;
                 IsRendered = true;

+ 10 - 1
Source/QuestPDF/Elements/DynamicImage.cs

@@ -24,7 +24,7 @@ namespace QuestPDF.Elements
     /// <returns>An image in PNG, JPEG, or WEBP image format returned as byte array.</returns>
     /// <returns>An image in PNG, JPEG, or WEBP image format returned as byte array.</returns>
     public delegate byte[]? GenerateDynamicImageDelegate(GenerateDynamicImageDelegatePayload payload);
     public delegate byte[]? GenerateDynamicImageDelegate(GenerateDynamicImageDelegatePayload payload);
     
     
-    internal sealed class DynamicImage : Element, IStateful
+    internal sealed class DynamicImage : Element, IStateful, IDisposable
     {
     {
         internal int? TargetDpi { get; set; }
         internal int? TargetDpi { get; set; }
         internal ImageCompressionQuality? CompressionQuality { get; set; }
         internal ImageCompressionQuality? CompressionQuality { get; set; }
@@ -37,9 +37,16 @@ namespace QuestPDF.Elements
         private int DrawnImageSize { get; set; }
         private int DrawnImageSize { get; set; }
         
         
         ~DynamicImage()
         ~DynamicImage()
+        {
+            Dispose();
+        }
+
+        public void Dispose()
         {
         {
             foreach (var cacheItem in Cache)
             foreach (var cacheItem in Cache)
                 cacheItem.Image?.Dispose();
                 cacheItem.Image?.Dispose();
+            
+            GC.SuppressFinalize(this);
         }
         }
         
         
         internal override SpacePlan Measure(Size availableSpace)
         internal override SpacePlan Measure(Size availableSpace)
@@ -68,6 +75,8 @@ namespace QuestPDF.Elements
             if (targetImage != null)
             if (targetImage != null)
                 Canvas.DrawImage(targetImage, availableSpace);
                 Canvas.DrawImage(targetImage, availableSpace);
             
             
+            targetImage?.Dispose();
+            
             GenerationTime += (float) stopWatch.Elapsed.TotalMilliseconds;
             GenerationTime += (float) stopWatch.Elapsed.TotalMilliseconds;
             DrawnImageSize += targetImage?.EncodedDataSize ?? 0;
             DrawnImageSize += targetImage?.EncodedDataSize ?? 0;
             
             

+ 11 - 1
Source/QuestPDF/Elements/DynamicSvgImage.cs

@@ -1,3 +1,4 @@
+using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using QuestPDF.Drawing;
 using QuestPDF.Drawing;
@@ -8,16 +9,23 @@ using QuestPDF.Skia;
 
 
 namespace QuestPDF.Elements;
 namespace QuestPDF.Elements;
 
 
-internal class DynamicSvgImage : Element, IStateful
+internal class DynamicSvgImage : Element, IStateful, IDisposable
 {
 {
     public GenerateDynamicSvgDelegate SvgSource { get; set; }
     public GenerateDynamicSvgDelegate SvgSource { get; set; }
 
 
     private List<(Size Size, SkSvgImage? Image)> Cache { get; } = new(1);
     private List<(Size Size, SkSvgImage? Image)> Cache { get; } = new(1);
     
     
     ~DynamicSvgImage()
     ~DynamicSvgImage()
+    {
+        Dispose();
+    }
+    
+    public void Dispose()
     {
     {
         foreach (var cacheItem in Cache)
         foreach (var cacheItem in Cache)
             cacheItem.Image?.Dispose();
             cacheItem.Image?.Dispose();
+            
+        GC.SuppressFinalize(this);
     }
     }
     
     
     internal override SpacePlan Measure(Size availableSpace)
     internal override SpacePlan Measure(Size availableSpace)
@@ -49,6 +57,8 @@ internal class DynamicSvgImage : Element, IStateful
             Canvas.Scale(widthScale,  heightScale);
             Canvas.Scale(widthScale,  heightScale);
             Canvas.DrawSvg(targetImage, availableSpace);
             Canvas.DrawSvg(targetImage, availableSpace);
             Canvas.Restore();
             Canvas.Restore();
+            
+            targetImage.Dispose();
         }
         }
             
             
         IsRendered = true;
         IsRendered = true;

+ 9 - 1
Source/QuestPDF/Elements/Image.cs

@@ -6,7 +6,7 @@ using QuestPDF.Skia;
 
 
 namespace QuestPDF.Elements
 namespace QuestPDF.Elements
 {
 {
-    internal sealed class Image : Element, IStateful
+    internal sealed class Image : Element, IStateful, IDisposable
     {
     {
         public Infrastructure.Image? DocumentImage { get; set; }
         public Infrastructure.Image? DocumentImage { get; set; }
 
 
@@ -16,6 +16,14 @@ namespace QuestPDF.Elements
  
  
         private int DrawnImageSize { get; set; }
         private int DrawnImageSize { get; set; }
         
         
+        public void Dispose()
+        {
+            if (DocumentImage == null || DocumentImage.IsShared)
+                return;
+            
+            DocumentImage?.Dispose();
+        }
+        
         internal override SpacePlan Measure(Size availableSpace)
         internal override SpacePlan Measure(Size availableSpace)
         {
         {
             if (IsRendered)
             if (IsRendered)

+ 81 - 0
Source/QuestPDF/Elements/Lazy.cs

@@ -0,0 +1,81 @@
+using System;
+using QuestPDF.Drawing;
+using QuestPDF.Drawing.Proxy;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Elements;
+
+internal sealed class Lazy : ContainerElement, IContentDirectionAware, IStateful
+{
+    public Action<IContainer> ContentSource { get; set; }
+    public bool IsCacheable { get; set; }
+
+    internal TextStyle TextStyle { get; set; } = TextStyle.Default;
+    public ContentDirection ContentDirection { get; set; }
+        
+    internal int? ImageTargetDpi { get; set; }
+    internal ImageCompressionQuality? ImageCompressionQuality { get; set; }
+    internal bool UseOriginalImage { get; set; }
+
+    internal bool ClearCacheAfterFullRender { get; set; } = true;
+    
+    internal override SpacePlan Measure(Size availableSpace)
+    {
+        if (IsRendered)
+            return SpacePlan.Empty();
+        
+        PopulateContent();
+        return Child.Measure(availableSpace);
+    }
+        
+    internal override void Draw(Size availableSpace)
+    {
+        if (IsRendered)
+            return;
+        
+        PopulateContent();
+        
+        var isFullyRendered = Child?.Measure(availableSpace).Type == SpacePlanType.FullRender;
+        Child?.Draw(availableSpace);
+        
+        if (isFullyRendered && ClearCacheAfterFullRender)
+        {
+            IsRendered = true;
+            Child.ReleaseDisposableChildren();
+            Child = Empty.Instance;
+        }
+    }
+    
+    private void PopulateContent()
+    {
+        if (Child is not Empty)
+            return;
+        
+        var container = new Container();
+        Child = container;
+        ContentSource(container);
+        
+        container.ApplyInheritedAndGlobalTexStyle(TextStyle);
+        container.ApplyContentDirection(ContentDirection);
+        container.ApplyDefaultImageConfiguration(ImageTargetDpi.Value, ImageCompressionQuality.Value, UseOriginalImage);
+            
+        container.InjectDependencies(PageContext, Canvas);
+        container.VisitChildren(x => (x as IStateful)?.ResetState());
+    }
+    
+    #region IStateful
+        
+    private bool IsRendered { get; set; }
+
+    public void ResetState(bool hardReset = false)
+    {
+        if (hardReset)
+            IsRendered = false;
+    }
+        
+    public object GetState() => IsRendered;
+    public void SetState(object state) => IsRendered = (bool) state;
+    
+    #endregion
+}

+ 16 - 5
Source/QuestPDF/Elements/RepeatContent.cs

@@ -9,7 +9,7 @@ internal sealed class RepeatContent : ContainerElement
 {
 {
     internal override void Draw(Size availableSpace)
     internal override void Draw(Size availableSpace)
     {
     {
-        OptimizeTextCacheBehavior();
+        OptimizeContentCacheBehavior();
         
         
         var childMeasurement = Child?.Measure(availableSpace);
         var childMeasurement = Child?.Measure(availableSpace);
         base.Draw(availableSpace);
         base.Draw(availableSpace);
@@ -22,25 +22,36 @@ internal sealed class RepeatContent : ContainerElement
     
     
     #region Text Optimization
     #region Text Optimization
 
 
-    private bool IsTextOptimizationExecuted { get; set; } = false;
+    private bool IsContentOptimizationExecuted { get; set; } = false;
     
     
     /// <summary>
     /// <summary>
+    /// <para>
     /// The TextBlock element uses SkParagraph cache to enhance rendering speed.
     /// The TextBlock element uses SkParagraph cache to enhance rendering speed.
     /// This cache uses a significant amount of memory and is cleared after FullRender.
     /// This cache uses a significant amount of memory and is cleared after FullRender.
     /// However, when using the RepeatContent element, the cache is cleared after each repetition.
     /// However, when using the RepeatContent element, the cache is cleared after each repetition.
     /// To avoid performance issues, the default behavior is disabled.
     /// To avoid performance issues, the default behavior is disabled.
+    /// </para>
+    ///
+    /// <para>
+    /// Similarly, the Lazy element builds entire content on demand, waits to fully render it and then removes it.
+    /// This aims to optimize managed memory usage.
+    /// However, it may not be the most optimal solution in repeating context.
+    /// </para>
     /// </summary>
     /// </summary>
-    private void OptimizeTextCacheBehavior()
+    private void OptimizeContentCacheBehavior()
     {
     {
-        if (IsTextOptimizationExecuted)
+        if (IsContentOptimizationExecuted)
             return;
             return;
         
         
-        IsTextOptimizationExecuted = true;
+        IsContentOptimizationExecuted = true;
         
         
         Child.VisitChildren(x =>
         Child.VisitChildren(x =>
         {
         {
             if (x is TextBlock text)
             if (x is TextBlock text)
                 text.ClearInternalCacheAfterFullRender = false;
                 text.ClearInternalCacheAfterFullRender = false;
+            
+            if (x is Lazy lazy)
+                lazy.ClearCacheAfterFullRender = false;
         });
         });
     }
     }
     
     

+ 9 - 1
Source/QuestPDF/Elements/SvgImage.cs

@@ -6,10 +6,18 @@ using QuestPDF.Skia;
 
 
 namespace QuestPDF.Elements;
 namespace QuestPDF.Elements;
 
 
-internal class SvgImage : Element, IStateful
+internal class SvgImage : Element, IStateful, IDisposable
 {
 {
     public Infrastructure.SvgImage Image { get; set; }
     public Infrastructure.SvgImage Image { get; set; }
     
     
+    public void Dispose()
+    {
+        if (Image == null || Image.IsShared)
+            return;
+            
+        Image?.Dispose();
+    }
+    
     internal override SpacePlan Measure(Size availableSpace)
     internal override SpacePlan Measure(Size availableSpace)
     {
     {
         if (IsRendered)
         if (IsRendered)

+ 4 - 5
Source/QuestPDF/Elements/Table/Table.cs

@@ -139,13 +139,12 @@ namespace QuestPDF.Elements.Table
 
 
         private int CalculateCurrentRow(ICollection<TableCellRenderingCommand> commands)
         private int CalculateCurrentRow(ICollection<TableCellRenderingCommand> commands)
         {
         {
-            var lastFullyRenderedRow = commands
+            return commands
                 .GroupBy(x => x.Cell.Row)
                 .GroupBy(x => x.Cell.Row)
                 .Where(x => x.All(y => y.Cell.IsRendered || y.Measurement.Type is SpacePlanType.Empty or SpacePlanType.FullRender))
                 .Where(x => x.All(y => y.Cell.IsRendered || y.Measurement.Type is SpacePlanType.Empty or SpacePlanType.FullRender))
-                .Select(x => x.Key)
-                .ToArray();
-            
-            return lastFullyRenderedRow.Any() ? lastFullyRenderedRow.Max() + 1 : CurrentRow;
+                .Select(x => x.Key + 1)
+                .DefaultIfEmpty(CurrentRow)
+                .Max();
         }
         }
         
         
         private void UpdateColumnsWidth(float availableWidth)
         private void UpdateColumnsWidth(float availableWidth)

+ 13 - 2
Source/QuestPDF/Elements/Text/TextBlock.cs

@@ -12,7 +12,7 @@ using QuestPDF.Skia.Text;
 
 
 namespace QuestPDF.Elements.Text
 namespace QuestPDF.Elements.Text
 {
 {
-    internal sealed class TextBlock : Element, IStateful, IContentDirectionAware
+    internal sealed class TextBlock : Element, IStateful, IContentDirectionAware, IDisposable
     {
     {
         // content
         // content
         public List<ITextBlockItem> Items { get; set; } = new();
         public List<ITextBlockItem> Items { get; set; } = new();
@@ -47,8 +47,14 @@ namespace QuestPDF.Elements.Text
         public string Text => string.Join(" ", Items.OfType<TextBlockSpan>().Select(x => x.Text));
         public string Text => string.Join(" ", Items.OfType<TextBlockSpan>().Select(x => x.Text));
 
 
         ~TextBlock()
         ~TextBlock()
+        {
+            Dispose();
+        }
+
+        public void Dispose()
         {
         {
             Paragraph?.Dispose();
             Paragraph?.Dispose();
+            GC.SuppressFinalize(this);
         }
         }
 
 
         internal override SpacePlan Measure(Size availableSpace)
         internal override SpacePlan Measure(Size availableSpace)
@@ -476,7 +482,10 @@ namespace QuestPDF.Elements.Text
 
 
         private void CalculateParagraphMetrics(Size availableSpace)
         private void CalculateParagraphMetrics(Size availableSpace)
         {
         {
-            if (Math.Abs(WidthForLineMetricsCalculation - availableSpace.Width) > Size.Epsilon)
+            // optimization, invalidate cache when both:
+            // 1) the width for which cache was calculated is different from the provided width
+            // 2) provided width is different from the target width of the text component
+            if (AreDifferent(WidthForLineMetricsCalculation, availableSpace.Width) && AreDifferent(MaximumWidth, availableSpace.Width))
                 AreParagraphMetricsValid = false;
                 AreParagraphMetricsValid = false;
             
             
             if (AreParagraphMetricsValid) 
             if (AreParagraphMetricsValid) 
@@ -492,6 +501,8 @@ namespace QuestPDF.Elements.Text
             MaximumWidth = LineMetrics.Any() ? LineMetrics.Max(x => x.Width) : 0;
             MaximumWidth = LineMetrics.Any() ? LineMetrics.Max(x => x.Width) : 0;
             
             
             AreParagraphMetricsValid = true;
             AreParagraphMetricsValid = true;
+            
+            static bool AreDifferent(float x, float y) => Math.Abs(x - y) > Size.Epsilon;
         }
         }
         
         
         private void CheckUnresolvedGlyphs()
         private void CheckUnresolvedGlyphs()

+ 39 - 1
Source/QuestPDF/Fluent/ElementExtensions.cs

@@ -8,6 +8,9 @@ using QuestPDF.Skia;
 
 
 namespace QuestPDF.Fluent
 namespace QuestPDF.Fluent
 {
 {
+    /// <summary>
+    /// Provides extension methods for manipulating and enhancing elements within a container.
+    /// </summary>
     public static class ElementExtensions
     public static class ElementExtensions
     {
     {
         static ElementExtensions()
         static ElementExtensions()
@@ -415,7 +418,42 @@ namespace QuestPDF.Fluent
         {
         {
             return element.Element(new RepeatContent());
             return element.Element(new RepeatContent());
         }
         }
-
+        
+        /// <summary>
+        /// <para>
+        /// Delays the creation of document content and reduces its lifetime, significantly lowering memory usage in large documents containing thousands of pages. 
+        /// This approach also enhances garbage collection efficiency in memory-constrained environments.
+        /// </para>
+        /// <para>
+        /// The provided <paramref name="contentBuilder"/> delegate is invoked later in the document generation process.
+        /// For optimal performance, divide your document into smaller sections, each encapsulated within its own Lazy element.
+        /// Further optimizations can be achieved by nesting Lazy elements within each other. 
+        /// However, note that this technique may increase the overall document generation time due to deferred content processing.
+        /// </para>
+        /// </summary>
+        public static void Lazy(this IContainer element, Action<IContainer> contentBuilder)
+        {
+            element.Element(new Lazy
+            {
+                ContentSource = contentBuilder,
+                IsCacheable = false
+            });
+        }
+        
+        /// <summary>
+        /// Functions similarly to the Lazy element but enables the library to use caching mechanisms for the content.
+        /// This can help optimize managed memory usage, although native memory usage may remain high.
+        /// Use LazyWithCache only when the increased generation time associated with the Lazy element is unacceptable.
+        /// </summary>
+        public static void LazyWithCache(this IContainer element, Action<IContainer> contentBuilder)
+        {
+            element.Element(new Lazy
+            {
+                ContentSource = contentBuilder,
+                IsCacheable = true
+            });
+        }
+        
         #region Canvas [Obsolete]
         #region Canvas [Obsolete]
 
 
         private const string CanvasDeprecatedMessage = "The Canvas API has been deprecated since version 2024.3.0. Please use the .Svg(stringContent) API to provide custom content, and consult documentation webpage regarding integrating SkiaSharp with QuestPDF: https://www.questpdf.com/concepts/skia-sharp-integration.html";
         private const string CanvasDeprecatedMessage = "The Canvas API has been deprecated since version 2024.3.0. Please use the .Svg(stringContent) API to provide custom content, and consult documentation webpage regarding integrating SkiaSharp with QuestPDF: https://www.questpdf.com/concepts/skia-sharp-integration.html";

+ 3 - 0
Source/QuestPDF/Fluent/ImageExtensions.cs

@@ -136,6 +136,7 @@ namespace QuestPDF.Fluent
         public static ImageDescriptor Image(this IContainer parent, byte[] imageData)
         public static ImageDescriptor Image(this IContainer parent, byte[] imageData)
         {
         {
             var image = Infrastructure.Image.FromBinaryData(imageData);
             var image = Infrastructure.Image.FromBinaryData(imageData);
+            image.IsShared = false;
             return parent.Image(image);
             return parent.Image(image);
         }
         }
         
         
@@ -148,6 +149,7 @@ namespace QuestPDF.Fluent
         public static ImageDescriptor Image(this IContainer parent, string filePath)
         public static ImageDescriptor Image(this IContainer parent, string filePath)
         {
         {
             var image = Infrastructure.Image.FromFile(filePath);
             var image = Infrastructure.Image.FromFile(filePath);
+            image.IsShared = false;
             return parent.Image(image);
             return parent.Image(image);
         }
         }
         
         
@@ -160,6 +162,7 @@ namespace QuestPDF.Fluent
         public static ImageDescriptor Image(this IContainer parent, Stream fileStream)
         public static ImageDescriptor Image(this IContainer parent, Stream fileStream)
         {
         {
             var image = Infrastructure.Image.FromStream(fileStream);
             var image = Infrastructure.Image.FromStream(fileStream);
+            image.IsShared = false;
             return parent.Image(image);
             return parent.Image(image);
         }
         }
         
         

+ 23 - 6
Source/QuestPDF/Helpers/Helpers.cs

@@ -52,15 +52,32 @@ namespace QuestPDF.Helpers
             return Regex.Replace(text, @"([a-z])([A-Z])", "$1 $2", RegexOptions.Compiled);
             return Regex.Replace(text, @"([a-z])([A-Z])", "$1 $2", RegexOptions.Compiled);
         }
         }
 
 
-        internal static void VisitChildren(this Element? element, Action<Element?> handler)
+        internal static void VisitChildren(this Element? root, Action<Element?> handler)
         {
         {
-            if (element == null)
-                return;
+            Traverse(root);
+
+            void Traverse(Element? element)
+            {
+                if (element == null)
+                    return;
+                
+                if (element is ContainerElement containerElement)
+                {
+                    Traverse(containerElement.Child);
+                }
+                else
+                {
+                    foreach (var child in element.GetChildren())
+                        Traverse(child);  
+                }
             
             
-            foreach (var child in element.GetChildren())
-                VisitChildren(child, handler);
+                handler(element);
+            }
+        }
 
 
-            handler(element);
+        internal static void ReleaseDisposableChildren(this Element? element)
+        {
+            element.VisitChildren(x => (x as IDisposable)?.Dispose());
         }
         }
 
 
         internal static bool IsNegative(this Size size)
         internal static bool IsNegative(this Size size)

+ 11 - 3
Source/QuestPDF/Infrastructure/Image.cs

@@ -22,13 +22,14 @@ namespace QuestPDF.Infrastructure
     /// <remarks>
     /// <remarks>
     /// This class is thread safe.
     /// This class is thread safe.
     /// </remarks>
     /// </remarks>
-    public class Image
+    public class Image : IDisposable
     {
     {
         static Image()
         static Image()
         {
         {
             SkNativeDependencyCompatibilityChecker.Test();
             SkNativeDependencyCompatibilityChecker.Test();
         }
         }
-        
+
+        internal bool IsShared { get; set; } = true;
         internal SkImage SkImage { get; }
         internal SkImage SkImage { get; }
         internal ImageSize Size { get; }
         internal ImageSize Size { get; }
 
 
@@ -41,13 +42,20 @@ namespace QuestPDF.Infrastructure
         }
         }
         
         
         ~Image()
         ~Image()
+        {
+            Dispose();
+        }
+        
+        public void Dispose()
         {
         {
             SkImage?.Dispose();
             SkImage?.Dispose();
             
             
             foreach (var cacheKey in ScaledImageCache)
             foreach (var cacheKey in ScaledImageCache)
                 cacheKey.image?.Dispose();
                 cacheKey.image?.Dispose();
+            
+            GC.SuppressFinalize(this);
         }
         }
-        
+
         #region Scaling Image
         #region Scaling Image
 
 
         internal SkImage GetVersionOfSize(GetImageVersionRequest request)
         internal SkImage GetVersionOfSize(GetImageVersionRequest request)

+ 9 - 2
Source/QuestPDF/Infrastructure/SvgImage.cs

@@ -12,9 +12,10 @@ namespace QuestPDF.Infrastructure;
 /// <remarks>
 /// <remarks>
 /// This class is thread safe.
 /// This class is thread safe.
 /// </remarks>
 /// </remarks>
-public class SvgImage
+public class SvgImage : IDisposable
 {
 {
     internal SkSvgImage SkSvgImage { get; }
     internal SkSvgImage SkSvgImage { get; }
+    internal bool IsShared { get; set; } = true;
     
     
     private SvgImage(string content)
     private SvgImage(string content)
     {
     {
@@ -22,10 +23,16 @@ public class SvgImage
     }
     }
 
 
     ~SvgImage()
     ~SvgImage()
+    {
+        Dispose();
+    }
+        
+    public void Dispose()
     {
     {
         SkSvgImage?.Dispose();
         SkSvgImage?.Dispose();
+        GC.SuppressFinalize(this);
     }
     }
-    
+
     /// <summary>
     /// <summary>
     /// Loads the SVG image from a file with specified path.
     /// Loads the SVG image from a file with specified path.
     /// <a href="https://www.questpdf.com/api-reference/image.html">Learn more</a>
     /// <a href="https://www.questpdf.com/api-reference/image.html">Learn more</a>

+ 1 - 1
Source/QuestPDF/QuestPDF.csproj

@@ -3,7 +3,7 @@
         <Authors>MarcinZiabek</Authors>
         <Authors>MarcinZiabek</Authors>
         <Company>CodeFlint</Company>
         <Company>CodeFlint</Company>
         <PackageId>QuestPDF</PackageId>
         <PackageId>QuestPDF</PackageId>
-        <Version>2024.12.3</Version>
+        <Version>2025.1.0-rc0</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. Easily generate PDF reports, invoices, exports, etc.</PackageDescription>
         <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. Easily generate PDF reports, invoices, exports, etc.</PackageDescription>
         <PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
         <PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
         <LangVersion>12</LangVersion>
         <LangVersion>12</LangVersion>

+ 6 - 35
Source/QuestPDF/Resources/ReleaseNotes.txt

@@ -1,36 +1,7 @@
-Version 2024.12.0
+Version 2025.1.0-alpha0
+- Implemented a Lazy element that delays content creation to reduce memory usage and enhance garbage collection efficiency in large documents.
 
 
-This release introduces several long-awaited features releated to document operations such as:
-- Assemble: Rearrange, select, and remove specific pages within a document.
-- Merge: Combine multiple PDF files into a single document.
-- Add Attachments: Embed additional files (such as text or images) as attachments within the PDF.
-- Underlay and Overlay: Apply other PDF as a background (underlay) or foreground (overlay) to another, adding layered content.
-- Linearize: Optimize files for fast web viewing, allowing compatible PDF readers to display the document before it’s fully downloaded.
-- Encrypt with Access Restrictions: Secure PDFs with user and owner passwords, applying access controls (like permissions for printing, filling forms, extracting or modifying content).
-
-These features are built using the qpdf library, available under the "Apache-2.0" license.
-The qpdf library is available at: https://github.com/qpdf/qpdf
-We extend our thanks to the authors of qpdf for their contributions to the open-source community.
-
-
-Further improvements:
-- Adjusted the default path for loading font files to prevent rare application hangs in certain environments
-- Performance optimization: improved scalability by limiting the maximum level of parallelism
-- Performance optimization for legacy .NET Framework: collect stack trace only when running with the Companion App
-- TextBlock: slightly reduced memory usage
-
-
-Version 2024.12.1
-- Fixed: The library now provides hints when an additional dependency is required.
-- Improved license-related exception.
-
-
-Version 2024.12.2
-- Enhanced the EnsureSpace element to work dynamically without a pre-configured vertical space threshold.
-- Improved table rendering stability in rare scenarios.
-- Fixed an exception that occurred when rendering an image with a zero target size.
-
-
-Version 2024.12.3
-- Updated the native dependency: qpdf.
-- Reduced Linux requirements by removing the GnuTLS dependency.
+Version 2025.1.0-rc0
+- Optimization: releasing native objects as early as possible to reduce peak memory usage,
+- Various performance and memory optimizations,
+- Increased maximum level of parallelism from 2 to 4.

+ 1 - 0
Source/QuestPDF/Skia/SkBitmap.cs

@@ -43,6 +43,7 @@ internal sealed class SkBitmap : IDisposable
         
         
         API.bitmap_delete(Instance);
         API.bitmap_delete(Instance);
         Instance = IntPtr.Zero;
         Instance = IntPtr.Zero;
+        GC.SuppressFinalize(this);
     }
     }
     
     
     private static class API
     private static class API

+ 1 - 0
Source/QuestPDF/Skia/SkCanvas.cs

@@ -135,6 +135,7 @@ internal sealed class SkCanvas : IDisposable
             API.canvas_delete(Instance);
             API.canvas_delete(Instance);
         
         
         Instance = IntPtr.Zero;
         Instance = IntPtr.Zero;
+        GC.SuppressFinalize(this);
     }
     }
     
     
     public struct CanvasMatrix
     public struct CanvasMatrix

+ 1 - 0
Source/QuestPDF/Skia/SkData.cs

@@ -68,6 +68,7 @@ internal sealed class SkData : IDisposable
         
         
         API.data_unref(Instance);
         API.data_unref(Instance);
         Instance = IntPtr.Zero;
         Instance = IntPtr.Zero;
+        GC.SuppressFinalize(this);
     }
     }
     
     
     private static class API
     private static class API

+ 1 - 0
Source/QuestPDF/Skia/SkDocument.cs

@@ -41,6 +41,7 @@ internal sealed class SkDocument : IDisposable
         
         
         API.document_unref(Instance);
         API.document_unref(Instance);
         Instance = IntPtr.Zero;
         Instance = IntPtr.Zero;
+        GC.SuppressFinalize(this);
     }
     }
     
     
     private static class API
     private static class API

+ 1 - 0
Source/QuestPDF/Skia/SkImage.cs

@@ -69,6 +69,7 @@ internal sealed class SkImage : IDisposable
         
         
         API.image_unref(Instance);
         API.image_unref(Instance);
         Instance = IntPtr.Zero;
         Instance = IntPtr.Zero;
+        GC.SuppressFinalize(this);
     }
     }
     
     
     private static class API
     private static class API

+ 1 - 0
Source/QuestPDF/Skia/SkPicture.cs

@@ -37,6 +37,7 @@ internal sealed class SkPicture : IDisposable
         
         
         API.picture_unref(Instance);
         API.picture_unref(Instance);
         Instance = IntPtr.Zero;
         Instance = IntPtr.Zero;
+        GC.SuppressFinalize(this);
     }
     }
     
     
     private static class API
     private static class API

+ 1 - 0
Source/QuestPDF/Skia/SkPictureRecorder.cs

@@ -37,6 +37,7 @@ internal sealed class SkPictureRecorder : IDisposable
         
         
         API.picture_recorder_delete(Instance);
         API.picture_recorder_delete(Instance);
         Instance = IntPtr.Zero;
         Instance = IntPtr.Zero;
+        GC.SuppressFinalize(this);
     }
     }
     
     
     private static class API
     private static class API

+ 1 - 0
Source/QuestPDF/Skia/SkSvgImage.cs

@@ -68,6 +68,7 @@ internal sealed class SkSvgImage : IDisposable
         
         
         API.svg_unref(Instance);
         API.svg_unref(Instance);
         Instance = IntPtr.Zero;
         Instance = IntPtr.Zero;
+        GC.SuppressFinalize(this);
     }
     }
     
     
     private static class API
     private static class API

+ 1 - 0
Source/QuestPDF/Skia/SkText.cs

@@ -46,5 +46,6 @@ internal class SkText : IDisposable
         
         
         Marshal.FreeHGlobal(Instance);
         Marshal.FreeHGlobal(Instance);
         Instance = IntPtr.Zero;
         Instance = IntPtr.Zero;
+        GC.SuppressFinalize(this);
     }
     }
 }
 }

+ 1 - 0
Source/QuestPDF/Skia/SkWriteStream.cs

@@ -31,6 +31,7 @@ internal sealed class SkWriteStream : IDisposable
         
         
         API.write_stream_delete(Instance);
         API.write_stream_delete(Instance);
         Instance = IntPtr.Zero;
         Instance = IntPtr.Zero;
+        GC.SuppressFinalize(this);
     }
     }
     
     
     private static class API
     private static class API

+ 1 - 0
Source/QuestPDF/Skia/Text/SkFontCollection.cs

@@ -31,6 +31,7 @@ internal sealed class SkFontCollection : IDisposable
         
         
         API.font_collection_unref(Instance);
         API.font_collection_unref(Instance);
         Instance = IntPtr.Zero;
         Instance = IntPtr.Zero;
+        GC.SuppressFinalize(this);
     }
     }
     
     
     private static class API
     private static class API

+ 1 - 0
Source/QuestPDF/Skia/Text/SkParagraph.cs

@@ -95,6 +95,7 @@ internal sealed class SkParagraph : IDisposable
         
         
         API.paragraph_delete(Instance);
         API.paragraph_delete(Instance);
         Instance = IntPtr.Zero;
         Instance = IntPtr.Zero;
+        GC.SuppressFinalize(this);
     }
     }
     
     
     private static class API
     private static class API

+ 1 - 0
Source/QuestPDF/Skia/Text/SkParagraphBuilder.cs

@@ -151,6 +151,7 @@ internal sealed class SkParagraphBuilder : IDisposable
         
         
         API.paragraph_builder_delete(Instance);
         API.paragraph_builder_delete(Instance);
         Instance = IntPtr.Zero;
         Instance = IntPtr.Zero;
+        GC.SuppressFinalize(this);
     }
     }
     
     
     private static class API
     private static class API

+ 1 - 0
Source/QuestPDF/Skia/Text/SkTextStyle.cs

@@ -99,6 +99,7 @@ internal sealed class SkTextStyle : IDisposable
         
         
         API.text_style_delete(Instance);
         API.text_style_delete(Instance);
         Instance = IntPtr.Zero;
         Instance = IntPtr.Zero;
+        GC.SuppressFinalize(this);
     }
     }
     
     
     private static class API
     private static class API

+ 1 - 0
Source/QuestPDF/Skia/Text/SkTypeface.cs

@@ -25,6 +25,7 @@ internal sealed class SkTypeface : IDisposable
         
         
         API.typeface_unref(Instance);
         API.typeface_unref(Instance);
         Instance = IntPtr.Zero;
         Instance = IntPtr.Zero;
+        GC.SuppressFinalize(this);
     }
     }
     
     
     private static class API
     private static class API

+ 1 - 0
Source/QuestPDF/Skia/Text/SkTypefaceProvider.cs

@@ -35,6 +35,7 @@ internal sealed class SkTypefaceProvider : IDisposable
         
         
         API.typeface_font_provider_unref(Instance);
         API.typeface_font_provider_unref(Instance);
         Instance = IntPtr.Zero;
         Instance = IntPtr.Zero;
+        GC.SuppressFinalize(this);
     }
     }
     
     
     private static class API
     private static class API