Browse Source

Mark content that is repeated on consequtive pages (e.g. page header, table header) as artifacts

Marcin Ziąbek 2 months ago
parent
commit
29da9b6b9f

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

@@ -50,6 +50,7 @@ namespace QuestPDF.UnitTests.TestEngine
         public void DrawSectionLink(Size size, string sectionName, string? description) => throw new NotImplementedException();
         public void DrawSection(string sectionName) => throw new NotImplementedException();
         
+        public void MarkCurrentContentAsArtifact(bool isArtifact) => throw new NotImplementedException();
         public void SetSemanticNodeId(int nodeId) => throw new NotImplementedException();
     }
 }

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

@@ -47,6 +47,7 @@ namespace QuestPDF.UnitTests.TestEngine
         public void DrawSectionLink(Size size, string sectionName, string? description) => throw new NotImplementedException();
         public void DrawSection(string sectionName) => throw new NotImplementedException();
         
+        public void MarkCurrentContentAsArtifact(bool isArtifact) => throw new NotImplementedException();
         public void SetSemanticNodeId(int nodeId) => throw new NotImplementedException();
     }
 }

+ 5 - 0
Source/QuestPDF/Drawing/DrawingCanvases/FreeDrawingCanvas.cs

@@ -148,6 +148,11 @@ namespace QuestPDF.Drawing.DrawingCanvases
             
         }
         
+        public void MarkCurrentContentAsArtifact(bool isArtifact)
+        {
+            
+        }
+        
         public void SetSemanticNodeId(int nodeId)
         {
             

+ 5 - 0
Source/QuestPDF/Drawing/DrawingCanvases/ProxyDrawingCanvas.cs

@@ -162,6 +162,11 @@ internal sealed class ProxyDrawingCanvas : IDrawingCanvas, IDisposable
         Target.DrawSection(sectionName);
     }
     
+    public void MarkCurrentContentAsArtifact(bool isArtifact)
+    {
+        Target.MarkCurrentContentAsArtifact(isArtifact);
+    }
+    
     public void SetSemanticNodeId(int nodeId)
     {
         Target.SetSemanticNodeId(nodeId);

+ 12 - 0
Source/QuestPDF/Drawing/DrawingCanvases/SkiaDrawingCanvas.cs

@@ -230,9 +230,21 @@ namespace QuestPDF.Drawing.DrawingCanvases
         {
             CurrentCanvas.AnnotateDestination(sectionName);
         }
+
+        private bool IsCurrentContentArtifact { get; set; } = false;
+
+        public void MarkCurrentContentAsArtifact(bool isArtifact)
+        {
+            IsCurrentContentArtifact = isArtifact;
+        }
         
         public void SetSemanticNodeId(int nodeId)
         {
+            var isContentNodeId = nodeId > 0; // artifact is less than 0, nothing is 0
+            
+            if (IsCurrentContentArtifact && isContentNodeId)
+                return;
+            
             CurrentCanvas.SetSemanticNodeId(nodeId);
         }
         

+ 58 - 0
Source/QuestPDF/Elements/MarkRepeatedContentAsArtifact.cs

@@ -0,0 +1,58 @@
+using System;
+using QuestPDF.Infrastructure;
+using QuestPDF.Skia;
+
+namespace QuestPDF.Elements;
+
+internal class MarkRepeatedContentAsArtifact : ContainerElement, IStateful
+{
+    public PaginationType Type { get; set; } = PaginationType.Other;
+    
+    public enum PaginationType
+    {
+        Other,
+        Header,
+        Footer
+    }
+    
+    internal override void Draw(Size availableSpace)
+    {
+        if (IsFirstPageRendered)
+        {
+            Canvas.MarkCurrentContentAsArtifact(true);
+        }
+        else
+        {
+            var paginationNodeId = Type switch
+            {
+                PaginationType.Header => SkSemanticNodeSpecialId.PaginationHeaderArtifact,
+                PaginationType.Footer => SkSemanticNodeSpecialId.PaginationFooterArtifact,
+                _ => SkSemanticNodeSpecialId.PaginationArtifact
+            };
+        
+            Canvas.SetSemanticNodeId(paginationNodeId);
+        }
+        
+        base.Draw(availableSpace);
+        
+        if (IsFirstPageRendered)
+            Canvas.MarkCurrentContentAsArtifact(false);
+        
+        IsFirstPageRendered = true;
+    }
+    
+    #region IStateful
+        
+    private bool IsFirstPageRendered { get; set; }
+
+    public void ResetState(bool hardReset = false)
+    {
+        if (hardReset)
+            IsFirstPageRendered = false;
+    }
+    
+    public object GetState() => IsFirstPageRendered;
+    public void SetState(object state) => IsFirstPageRendered = (bool) state;
+    
+    #endregion
+}

+ 2 - 2
Source/QuestPDF/Elements/Page.cs

@@ -61,9 +61,10 @@ namespace QuestPDF.Elements
                 
                         .Decoration(decoration =>
                         {
+                            decoration.ApplyPageSpecificSemanticMeaning();
+                            
                             decoration
                                 .Before()
-                                .ArtifactPaginationHeader()
                                 .DebugPointer(DebugPointerType.DocumentStructure, DocumentStructureTypes.Header.ToString())
                                 .Element(Header);
 
@@ -76,7 +77,6 @@ namespace QuestPDF.Elements
 
                             decoration
                                 .After()
-                                .ArtifactPaginationFooter()
                                 .DebugPointer(DebugPointerType.DocumentStructure, DocumentStructureTypes.Footer.ToString())
                                 .Element(Footer);
                         });

+ 19 - 2
Source/QuestPDF/Fluent/DecorationExtensions.cs

@@ -8,6 +8,13 @@ namespace QuestPDF.Fluent
     public sealed class DecorationDescriptor
     {
         internal Decoration Decoration { get; } = new Decoration();
+
+        private bool ApplyPageSpecificSemanticMeaningToggle { get; set; }
+        
+        internal void ApplyPageSpecificSemanticMeaning()
+        {
+            ApplyPageSpecificSemanticMeaningToggle = true;
+        }
         
         /// <summary>
         /// Returns a container for the section positioned before (above) the primary main content.
@@ -22,7 +29,11 @@ namespace QuestPDF.Fluent
 
             var container = new Container();
             Decoration.Before = container;
-            return container.DebugPointer(DebugPointerType.ElementStructure, "Before").Repeat();
+            
+            return container
+                .DebugPointer(DebugPointerType.ElementStructure, "Before")
+                .Repeat()
+                .MarkRepeatedContentAsArtifact(ApplyPageSpecificSemanticMeaningToggle ? MarkRepeatedContentAsArtifact.PaginationType.Header : MarkRepeatedContentAsArtifact.PaginationType.Other);
         }
         
         /// <summary>
@@ -76,7 +87,13 @@ namespace QuestPDF.Fluent
             
             var container = new Container();
             Decoration.After = container;
-            return container.DebugPointer(DebugPointerType.ElementStructure, "After").Repeat();
+            
+            return container
+                .DebugPointer(DebugPointerType.ElementStructure, "After")
+                .Repeat()
+                .Element(x => ApplyPageSpecificSemanticMeaningToggle
+                    ? x.ArtifactPaginationFooter() 
+                    : x.MarkRepeatedContentAsArtifact(MarkRepeatedContentAsArtifact.PaginationType.Other));
         }
         
         /// <summary>

+ 9 - 0
Source/QuestPDF/Fluent/SemanticExtensions.cs

@@ -1,4 +1,5 @@
 using System;
+using QuestPDF.Elements;
 using QuestPDF.Infrastructure;
 using QuestPDF.Skia;
 
@@ -10,6 +11,14 @@ namespace QuestPDF.Fluent;
 
 public static class SemanticExtensions
 {
+    internal static IContainer MarkRepeatedContentAsArtifact(this IContainer container, MarkRepeatedContentAsArtifact.PaginationType type)
+    {
+        return container.Element(new Elements.MarkRepeatedContentAsArtifact()
+        {
+            Type = type
+        });
+    }
+    
     private static IContainer SemanticTag(this IContainer container, string type, string? alternativeText = null, string? language = null)
     {
         return container.Element(new Elements.SemanticTag

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

@@ -41,6 +41,7 @@ namespace QuestPDF.Infrastructure
         void DrawSectionLink(Size size, string sectionName, string? description);
         void DrawSection(string sectionName);
         
+        void MarkCurrentContentAsArtifact(bool isArtifact);
         void SetSemanticNodeId(int nodeId);
     }
 }