Browse Source

Made element state accessible via the IStateful interface

Marcin Ziąbek 1 year ago
parent
commit
5597f10a74

+ 1 - 1
Source/QuestPDF.LayoutTests/TestEngine/LayoutTestExecutor.cs

@@ -34,7 +34,7 @@ internal static class LayoutTestExecutor
             container.ApplyDefaultImageConfiguration(DocumentSettings.Default.ImageRasterDpi, DocumentSettings.Default.ImageCompressionQuality, true);
         
             // render
-            container.VisitChildren(x => (x as IStateResettable)?.ResetState());
+            container.VisitChildren(x => (x as IStateful)?.ResetState());
         
             canvas.BeginDocument();
             

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

@@ -187,7 +187,7 @@ namespace QuestPDF.Drawing
             where TCanvas : ICanvas, IRenderingCanvas
         {
             content.InjectDependencies(pageContext, canvas);
-            content.VisitChildren(x => (x as IStateResettable)?.ResetState(hardReset: true));
+            content.VisitChildren(x => (x as IStateful)?.ResetState(hardReset: true));
 
             while(true)
             {

+ 11 - 8
Source/QuestPDF/Elements/Column.cs

@@ -13,18 +13,11 @@ namespace QuestPDF.Elements
         public Position Offset { get; set; }
     }
 
-    internal sealed class Column : Element, IStateResettable
+    internal sealed class Column : Element, IStateful
     {
         internal List<Element> Items { get; } = new();
         internal float Spacing { get; set; }
         
-        internal int CurrentRenderingIndex { get; set; }
-
-        public void ResetState(bool hardReset)
-        {
-            CurrentRenderingIndex = 0;
-        }
-        
         internal override IEnumerable<Element?> GetChildren()
         {
             return Items;
@@ -128,5 +121,15 @@ namespace QuestPDF.Elements
 
             return commands;
         }
+        
+        #region IStateful
+        
+        internal int CurrentRenderingIndex { get; set; }
+    
+        public void ResetState(bool hardReset = false) => CurrentRenderingIndex = 0;
+        public object GetState() => CurrentRenderingIndex;
+        public void SetState(object state) => CurrentRenderingIndex = (int) state;
+        
+        #endregion
     }
 }

+ 18 - 11
Source/QuestPDF/Elements/Dynamic.cs

@@ -6,10 +6,8 @@ using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Elements
 {
-    internal sealed class DynamicHost : Element, IStateResettable, IContentDirectionAware
+    internal sealed class DynamicHost : Element, IStateful, IContentDirectionAware
     {
-        private bool IsRendered { get; set; }
-        
         private DynamicComponentProxy Child { get; }
         private object InitialComponentState { get; set; }
 
@@ -26,13 +24,7 @@ namespace QuestPDF.Elements
             
             InitialComponentState = Child.GetState();
         }
-
-        public void ResetState(bool hardReset)
-        {
-            IsRendered = false;
-            Child.SetState(InitialComponentState);
-        }
-        
+ 
         internal override SpacePlan Measure(Size availableSpace)
         {
             if (IsRendered)
@@ -88,6 +80,21 @@ namespace QuestPDF.Elements
 
             return result;
         }
+        
+        #region IStateful
+        
+        private bool IsRendered { get; set; }
+    
+        public void ResetState(bool hardReset = false)
+        {
+            IsRendered = false;
+            Child.SetState(InitialComponentState);
+        }
+
+        public object GetState() => IsRendered;
+        public void SetState(object state) => IsRendered = (bool) state;
+    
+        #endregion
     }
 
     /// <summary>
@@ -142,7 +149,7 @@ namespace QuestPDF.Elements
             container.ApplyDefaultImageConfiguration(ImageTargetDpi, ImageCompressionQuality, UseOriginalImage);
             
             container.InjectDependencies(PageContext, Canvas);
-            container.VisitChildren(x => (x as IStateResettable)?.ResetState());
+            container.VisitChildren(x => (x as IStateful)?.ResetState());
 
             container.Size = container.Measure(Size.Max);
             

+ 11 - 8
Source/QuestPDF/Elements/DynamicImage.cs

@@ -23,10 +23,8 @@ namespace QuestPDF.Elements
     /// <returns>An image in PNG, JPEG, or WEBP image format returned as byte array.</returns>
     public delegate byte[]? GenerateDynamicImageDelegate(GenerateDynamicImageDelegatePayload payload);
     
-    internal sealed class DynamicImage : Element, IStateResettable
+    internal sealed class DynamicImage : Element, IStateful
     {
-        private bool IsRendered { get; set; }
-        
         internal int? TargetDpi { get; set; }
         internal ImageCompressionQuality? CompressionQuality { get; set; }
         internal bool UseOriginalImage { get; set; }
@@ -40,11 +38,6 @@ namespace QuestPDF.Elements
                 cacheItem.Image?.Dispose();
         }
         
-        public void ResetState(bool hardReset = false)
-        {
-            IsRendered = false;
-        }
-        
         internal override SpacePlan Measure(Size availableSpace)
         {
             if (IsRendered)
@@ -117,5 +110,15 @@ namespace QuestPDF.Elements
                 (int)(availableSize.Height * scalingFactor)
             );
         }
+        
+        #region IStateful
+        
+        private bool IsRendered { get; set; }
+    
+        public void ResetState(bool hardReset = false) => IsRendered = false;
+        public object GetState() => IsRendered;
+        public void SetState(object state) => IsRendered = (bool) state;
+    
+        #endregion
     }
 }

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

@@ -8,10 +8,8 @@ using QuestPDF.Skia;
 
 namespace QuestPDF.Elements;
 
-internal class DynamicSvgImage : Element, IStateResettable
+internal class DynamicSvgImage : Element, IStateful
 {
-    private bool IsRendered { get; set; }
-    
     public GenerateDynamicSvgDelegate SvgSource { get; set; }
 
     private List<(Size Size, SkSvgImage? Image)> Cache { get; } = new(1);
@@ -22,11 +20,6 @@ internal class DynamicSvgImage : Element, IStateResettable
             cacheItem.Image?.Dispose();
     }
     
-    public void ResetState(bool hardReset = false)
-    {
-        IsRendered = false;
-    }
-    
     internal override SpacePlan Measure(Size availableSpace)
     {
         if (IsRendered)
@@ -63,4 +56,14 @@ internal class DynamicSvgImage : Element, IStateResettable
 
         return new SkSvgImage(svg, SkResourceProvider.CurrentResourceProvider, FontManager.CurrentFontManager);
     }
+    
+    #region IStateful
+    
+    private bool IsRendered { get; set; }
+    
+    public void ResetState(bool hardReset = false) => IsRendered = false;
+    public object GetState() => IsRendered;
+    public void SetState(object state) => IsRendered = (bool) state;
+    
+    #endregion
 }

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

@@ -5,21 +5,14 @@ using QuestPDF.Skia;
 
 namespace QuestPDF.Elements
 {
-    internal sealed class Image : Element, IStateResettable
+    internal sealed class Image : Element, IStateful
     {
-        private bool IsRendered { get; set; }
-        
         public Infrastructure.Image? DocumentImage { get; set; }
 
         internal bool UseOriginalImage { get; set; }
         internal int? TargetDpi { get; set; }
         internal ImageCompressionQuality? CompressionQuality { get; set; }
-        
-        public void ResetState(bool hardReset = false)
-        {
-            IsRendered = false;
-        }
-        
+ 
         internal override SpacePlan Measure(Size availableSpace)
         {
             if (IsRendered)
@@ -77,5 +70,15 @@ namespace QuestPDF.Elements
 
             return targetResolution;
         }
+        
+        #region IStateful
+        
+        private bool IsRendered { get; set; }
+    
+        public void ResetState(bool hardReset = false) => IsRendered = false;
+        public object GetState() => IsRendered;
+        public void SetState(object state) => IsRendered = (bool) state;
+    
+        #endregion
     }
 }

+ 13 - 9
Source/QuestPDF/Elements/Inlined.cs

@@ -20,24 +20,18 @@ namespace QuestPDF.Elements
         public SpacePlan Size { get; set; }
     }
 
-    internal sealed class Inlined : Element, IContentDirectionAware, IStateResettable
+    internal sealed class Inlined : Element, IContentDirectionAware, IStateful
     {
-        public ContentDirection ContentDirection { get; set; }
-        
         public List<Element> Elements { get; internal set; } = new();
-        private int CurrentRenderingIndex { get; set; }
 
+        public ContentDirection ContentDirection { get; set; }
+        
         internal float VerticalSpacing { get; set; }
         internal float HorizontalSpacing { get; set; }
         
         internal InlinedAlignment? ElementsAlignment { get; set; }
         internal VerticalAlignment BaselineAlignment { get; set; }
         
-        public void ResetState(bool hardReset)
-        {
-            CurrentRenderingIndex = 0;
-        }
-        
         internal override IEnumerable<Element?> GetChildren()
         {
             return Elements;
@@ -258,5 +252,15 @@ namespace QuestPDF.Elements
                 };
             }
         }
+        
+        #region IStateful
+        
+        private int CurrentRenderingIndex { get; set; }
+    
+        public void ResetState(bool hardReset = false) => CurrentRenderingIndex = 0;
+        public object GetState() => CurrentRenderingIndex;
+        public void SetState(object state) => CurrentRenderingIndex = (int) state;
+        
+        #endregion
     }
 }

+ 11 - 8
Source/QuestPDF/Elements/Line.cs

@@ -16,19 +16,12 @@ namespace QuestPDF.Elements
         Horizontal
     }
 
-    internal sealed class Line : Element, ILine, IStateResettable
+    internal sealed class Line : Element, ILine, IStateful
     {
-        private bool IsRendered { get; set; }
-        
         public LineType Type { get; set; } = LineType.Vertical;
         public Color Color { get; set; } = Colors.Black;
         public float Thickness { get; set; } = 1;
         
-        public void ResetState(bool hardReset)
-        {
-            IsRendered = false;
-        }
-        
         internal override SpacePlan Measure(Size availableSpace)
         {
             if (IsRendered)
@@ -72,5 +65,15 @@ namespace QuestPDF.Elements
             
             IsRendered = true;
         }
+        
+        #region IStateful
+        
+        private bool IsRendered { get; set; }
+    
+        public void ResetState(bool hardReset = false) => IsRendered = false;
+        public object GetState() => IsRendered;
+        public void SetState(object state) => IsRendered = (bool) state;
+    
+        #endregion
     }
 }

+ 11 - 8
Source/QuestPDF/Elements/PageBreak.cs

@@ -4,15 +4,8 @@ using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Elements
 {
-    internal sealed class PageBreak : Element, IStateResettable
+    internal sealed class PageBreak : Element, IStateful
     {
-        private bool IsRendered { get; set; }
-        
-        public void ResetState(bool hardReset)
-        {
-            IsRendered = false;
-        }
-
         internal override SpacePlan Measure(Size availableSpace)
         {
             if (availableSpace.IsNegative())
@@ -28,5 +21,15 @@ namespace QuestPDF.Elements
         {
             IsRendered = true;
         }
+        
+        #region IStateful
+        
+        private bool IsRendered { get; set; }
+    
+        public void ResetState(bool hardReset = false) => IsRendered = false;
+        public object GetState() => IsRendered;
+        public void SetState(object state) => IsRendered = (bool) state;
+    
+        #endregion
     }
 }

+ 1 - 1
Source/QuestPDF/Elements/RepeatContent.cs

@@ -13,7 +13,7 @@ internal sealed class RepeatContent : ContainerElement
 
         if (childMeasurement?.Type == SpacePlanType.FullRender)
         {
-            Child.VisitChildren(x => (x as IStateResettable)?.ResetState(false));
+            Child.VisitChildren(x => (x as IStateful)?.ResetState(false));
         }
     }
 }

+ 30 - 6
Source/QuestPDF/Elements/Row.cs

@@ -30,18 +30,13 @@ namespace QuestPDF.Elements
         public Position Offset { get; set; }
     }
 
-    internal sealed class Row : Element, IStateResettable, IContentDirectionAware
+    internal sealed class Row : Element, IStateful, IContentDirectionAware
     {
         public ContentDirection ContentDirection { get; set; }
         
         internal List<RowItem> Items { get; } = new();
         internal float Spacing { get; set; }
 
-        public void ResetState(bool hardReset)
-        {
-            Items.ForEach(x => x.IsRendered = false);
-        }
-        
         internal override IEnumerable<Element?> GetChildren()
         {
             return Items;
@@ -169,5 +164,34 @@ namespace QuestPDF.Elements
             
             return renderingCommands;
         }
+        
+        #region IStateful
+        
+        // State is stored in the RowItem instances
+    
+        public void ResetState(bool hardReset = false)
+        {
+            Items.ForEach(x => x.IsRendered = false);
+        }
+
+        public object GetState()
+        {
+            var result = new bool[Items.Count];
+            
+            for (var i = 0; i < Items.Count; i++)
+                result[i] = Items[i].IsRendered;
+            
+            return result;
+        }
+
+        public void SetState(object state)
+        {
+            var states = (bool[]) state;
+            
+            for (var i = 0; i < Items.Count; i++)
+                Items[i].IsRendered = states[i];
+        }
+    
+        #endregion
     }
 }

+ 12 - 8
Source/QuestPDF/Elements/Section.cs

@@ -2,16 +2,10 @@
 
 namespace QuestPDF.Elements
 {
-    internal sealed class Section : ContainerElement, IStateResettable
+    internal sealed class Section : ContainerElement, IStateful
     {
         public string SectionName { get; set; }
-        private bool IsRendered { get; set; }
-        
-        public void ResetState(bool hardReset)
-        {
-            IsRendered = false;
-        }
-        
+
         internal override void Draw(Size availableSpace)
         {
             if (!IsRendered)
@@ -24,5 +18,15 @@ namespace QuestPDF.Elements
             PageContext.SetSectionPage(SectionName);
             base.Draw(availableSpace);
         }
+        
+        #region IStateful
+        
+        private bool IsRendered { get; set; }
+    
+        public void ResetState(bool hardReset = false) => IsRendered = false;
+        public object GetState() => IsRendered;
+        public void SetState(object state) => IsRendered = (bool) state;
+    
+        #endregion
     }
 }

+ 11 - 9
Source/QuestPDF/Elements/ShowOnce.cs

@@ -3,16 +3,8 @@ using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Elements
 {
-    internal sealed class ShowOnce : ContainerElement, IStateResettable
+    internal sealed class ShowOnce : ContainerElement, IStateful
     {
-        private bool IsRendered { get; set; }
-
-        public void ResetState(bool hardReset)
-        {
-            if (hardReset)
-                IsRendered = false;
-        }
-
         internal override SpacePlan Measure(Size availableSpace)
         {
             if (IsRendered)
@@ -31,5 +23,15 @@ namespace QuestPDF.Elements
             
             base.Draw(availableSpace);
         }
+        
+        #region IStateful
+        
+        private bool IsRendered { get; set; }
+    
+        public void ResetState(bool hardReset = false) => IsRendered = false;
+        public object GetState() => IsRendered;
+        public void SetState(object state) => IsRendered = (bool) state;
+    
+        #endregion
     }
 }

+ 16 - 9
Source/QuestPDF/Elements/SkipOnce.cs

@@ -3,16 +3,8 @@ using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Elements
 {
-    internal sealed class SkipOnce : ContainerElement, IStateResettable
+    internal sealed class SkipOnce : ContainerElement, IStateful
     {
-        private bool FirstPageWasSkipped { get; set; }
-
-        public void ResetState(bool hardReset)
-        {
-            if (hardReset)
-                FirstPageWasSkipped = false;
-        }
-
         internal override SpacePlan Measure(Size availableSpace)
         {
             if (!FirstPageWasSkipped)
@@ -28,5 +20,20 @@ namespace QuestPDF.Elements
 
             FirstPageWasSkipped = true;
         }
+        
+        #region IStateful
+        
+        private bool FirstPageWasSkipped { get; set; }
+    
+        public void ResetState(bool hardReset = false)
+        {
+            if (hardReset)
+                FirstPageWasSkipped = false;
+        }
+
+        public object GetState() => FirstPageWasSkipped;
+        public void SetState(object state) => FirstPageWasSkipped = (bool) state;
+        
+        #endregion
     }
 }

+ 11 - 8
Source/QuestPDF/Elements/SvgImage.cs

@@ -7,17 +7,10 @@ using static QuestPDF.Skia.SkSvgImageSize.Unit;
 
 namespace QuestPDF.Elements;
 
-internal class SvgImage : Element, IStateResettable
+internal class SvgImage : Element, IStateful
 {
-    private bool IsRendered { get; set; }
-    
     public Infrastructure.SvgImage Image { get; set; }
     
-    public void ResetState(bool hardReset = false)
-    {
-        IsRendered = false;
-    }
-    
     internal override SpacePlan Measure(Size availableSpace)
     {
         if (IsRendered)
@@ -77,4 +70,14 @@ internal class SvgImage : Element, IStateResettable
             return points * PointToPixel;
         }
     }
+    
+    #region IStateful
+    
+    private bool IsRendered { get; set; }
+    
+    public void ResetState(bool hardReset = false) => IsRendered = false;
+    public object GetState() => IsRendered;
+    public void SetState(object state) => IsRendered = (bool) state;
+    
+    #endregion
 }

+ 12 - 9
Source/QuestPDF/Elements/SvgPath.cs

@@ -5,18 +5,11 @@ using QuestPDF.Skia;
 
 namespace QuestPDF.Elements;
 
-internal class SvgPath : Element, IStateResettable
+internal class SvgPath : Element, IStateful
 {
-    private bool IsRendered { get; set; }
-    
     public string Path { get; set; } = string.Empty;
     public Color FillColor { get; set; } = Colors.Black;
-    
-    public void ResetState(bool hardReset = false)
-    {
-        IsRendered = false;
-    }
-    
+
     internal override SpacePlan Measure(Size availableSpace)
     {
         if (IsRendered)
@@ -36,4 +29,14 @@ internal class SvgPath : Element, IStateResettable
         Canvas.DrawSvgPath(Path, FillColor);
         IsRendered = true;
     }
+    
+    #region IStateful
+    
+    private bool IsRendered { get; set; }
+    
+    public void ResetState(bool hardReset = false) => IsRendered = false;
+    public object GetState() => IsRendered;
+    public void SetState(object state) => IsRendered = (bool) state;
+    
+    #endregion
 }

+ 58 - 17
Source/QuestPDF/Elements/Table/Table.cs

@@ -6,41 +6,33 @@ using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Elements.Table
 {
-    internal sealed class Table : Element, IStateResettable, IContentDirectionAware
+    internal sealed class Table : Element, IStateful, IContentDirectionAware
     {
-        public ContentDirection ContentDirection { get; set; }
-        
+        // configuration
         public List<TableColumnDefinition> Columns { get; set; } = new();
         public List<TableCell> Cells { get; set; } = new();
         public bool ExtendLastCellsToTableBottom { get; set; }
         
+        public ContentDirection ContentDirection { get; set; }
+        
+        // cache
         private bool CacheInitialized { get; set; }
-        private bool IsRendered => CurrentRow > StartingRowsCount;
         private int StartingRowsCount { get; set; }
         private int RowsCount { get; set; }
-        private int CurrentRow { get; set; }
+        private int MaxRow { get; set; }
+        private int MaxRowSpan { get; set; }
         
         // cache that stores all cells
         // first index: row number
         // inner table: list of all cells that ends at the corresponding row
         private TableCell[][] CellsCache { get; set; }
-        private int MaxRow { get; set; }
-        private int MaxRowSpan { get; set; }
+        
+        private bool IsRendered => CurrentRow > StartingRowsCount;
         
         internal override IEnumerable<Element?> GetChildren()
         {
             return Cells;
         }
-
-        public void ResetState(bool hardReset)
-        {
-            Initialize();
-            
-            foreach (var x in Cells)
-                x.IsRendered = false;
-            
-            CurrentRow = 1;
-        }
         
         private void Initialize()
         {
@@ -84,6 +76,8 @@ namespace QuestPDF.Elements.Table
         
         internal override SpacePlan Measure(Size availableSpace)
         {
+            Initialize();
+            
             if (!Cells.Any())
                 return SpacePlan.Empty();
             
@@ -110,6 +104,8 @@ namespace QuestPDF.Elements.Table
 
         internal override void Draw(Size availableSpace)
         {
+            Initialize();
+            
             if (IsRendered)
                 return;
             
@@ -308,5 +304,50 @@ namespace QuestPDF.Elements.Table
                 return columnOffsets[cell.Column + cell.ColumnSpan - 1] - columnOffsets[cell.Column - 1];
             }
         }
+        
+        #region IStateful
+        
+        private int CurrentRow { get; set; }
+        // state is also stored in TableCell instances
+    
+        public struct TableState
+        {
+            public bool[] CellsRenderingState;
+            public int CurrentRow;
+        }
+        
+        public void ResetState(bool hardReset = false)
+        {
+            foreach (var x in Cells)
+                x.IsRendered = false;
+            
+            CurrentRow = 1;
+        }
+
+        public object GetState()
+        {
+            var cellsRenderingState = new bool[Cells.Count];
+            
+            for (var i = 0; i < Cells.Count; i++)
+                cellsRenderingState[i] = Cells[i].IsRendered;
+            
+            return new TableState
+            {
+                CellsRenderingState = cellsRenderingState,
+                CurrentRow = CurrentRow
+            };
+        }
+
+        public void SetState(object state)
+        {
+            var tableState = (TableState) state;
+            
+            for (var i = 0; i < Cells.Count; i++)
+                Cells[i].IsRendered = tableState.CellsRenderingState[i];
+            
+            CurrentRow = tableState.CurrentRow;
+        }
+    
+        #endregion
     }
 }

+ 1 - 1
Source/QuestPDF/Elements/Text/Items/TextBlockElement.cs

@@ -13,7 +13,7 @@ namespace QuestPDF.Elements.Text.Items
 
         public void ConfigureElement(IPageContext pageContext, ICanvas canvas)
         {
-            Element.VisitChildren(x => (x as IStateResettable)?.ResetState());
+            Element.VisitChildren(x => (x as IStateful)?.ResetState());
             Element.InjectDependencies(pageContext, canvas);
         }
         

+ 52 - 19
Source/QuestPDF/Elements/Text/TextBlock.cs

@@ -12,50 +12,42 @@ using QuestPDF.Skia.Text;
 
 namespace QuestPDF.Elements.Text
 {
-    internal sealed class TextBlock : Element, IStateResettable, IContentDirectionAware
+    internal sealed class TextBlock : Element, IStateful, IContentDirectionAware
     {
-        public ContentDirection ContentDirection { get; set; }
+        // content
+        public List<ITextBlockItem> Items { get; set; } = new();
         
+        // configuration
         public TextHorizontalAlignment? Alignment { get; set; }
+        public ContentDirection ContentDirection { get; set; }
         
         public int? LineClamp { get; set; }
         public string LineClampEllipsis { get; set; }
 
         public float ParagraphSpacing { get; set; }
         public float ParagraphFirstLineIndentation { get; set; }
-
-        public List<ITextBlockItem> Items { get; set; } = new();
-
-        private SkParagraph Paragraph { get; set; }
         
+        // cache
         private bool RebuildParagraphForEveryPage { get; set; }
         private bool AreParagraphMetricsValid { get; set; }
         private bool AreParagraphItemsTransformedWithSpacingAndIndentation { get; set; }
         
         private SkSize[] LineMetrics { get; set; }
         private float WidthForLineMetricsCalculation { get; set; }
-        private SkRect[] PlaceholderPositions { get; set; }
         private float MaximumWidth { get; set; }
-        
-        private bool IsRendered { get; set; }
+        private SkRect[] PlaceholderPositions { get; set; }
         private bool? ContainsOnlyWhiteSpace { get; set; }
-        private int CurrentLineIndex { get; set; }
-        private float CurrentTopOffset { get; set; }
         
+        // native objects
+        private SkParagraph Paragraph { get; set; }
+
         public string Text => string.Join(" ", Items.OfType<TextBlockSpan>().Select(x => x.Text));
 
         ~TextBlock()
         {
             Paragraph?.Dispose();
         }
-        
-        public void ResetState(bool hardReset)
-        {
-            IsRendered = false;
-            CurrentLineIndex = 0;
-            CurrentTopOffset = 0;
-        }
-        
+
         internal override SpacePlan Measure(Size availableSpace)
         {
             if (Items.Count == 0)
@@ -582,5 +574,46 @@ namespace QuestPDF.Elements.Text
         }
         
         #endregion
+        
+        #region IStateful
+        
+        private bool IsRendered { get; set; }
+        private int CurrentLineIndex { get; set; }
+        private float CurrentTopOffset { get; set; }
+    
+        public struct TextBlockState
+        {
+            public bool IsRendered;
+            public int CurrentLineIndex;
+            public float CurrentTopOffset;
+        }
+        
+        public void ResetState(bool hardReset = false)
+        {
+            IsRendered = false;
+            CurrentLineIndex = 0;
+            CurrentTopOffset = 0;
+        }
+
+        public object GetState()
+        {
+            return new TextBlockState
+            {
+                IsRendered = IsRendered,
+                CurrentLineIndex = CurrentLineIndex,
+                CurrentTopOffset = CurrentTopOffset
+            };
+        }
+
+        public void SetState(object state)
+        {
+            var textBlockState = (TextBlockState) state;
+            
+            IsRendered = textBlockState.IsRendered;
+            CurrentLineIndex = textBlockState.CurrentLineIndex;
+            CurrentTopOffset = textBlockState.CurrentTopOffset;
+        }
+    
+        #endregion
     }
 }

+ 3 - 1
Source/QuestPDF/Infrastructure/IStateResettable.cs → Source/QuestPDF/Infrastructure/IStateful.cs

@@ -1,7 +1,9 @@
 namespace QuestPDF.Infrastructure
 {
-    internal interface IStateResettable
+    internal interface IStateful
     {
         void ResetState(bool hardReset = true);
+        object GetState();
+        void SetState(object state);
     }
 }