Quellcode durchsuchen

Improve handling on semantic and artifact context

Marcin Ziąbek vor 2 Wochen
Ursprung
Commit
0331f0c8ce

+ 5 - 0
Source/QuestPDF.ConformanceTests/TestEngine/SemanticAwareDrawingCanvas.cs

@@ -183,6 +183,11 @@ internal class SemanticAwareDrawingCanvas : IDrawingCanvas
     {
         
     }
+    
+    public int GetSemanticNodeId()
+    {
+        return CurrentSemanticNodeId;
+    }
 
     public void SetSemanticNodeId(int nodeId)
     {

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

@@ -50,7 +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 int GetSemanticNodeId() => throw new NotImplementedException();
         public void SetSemanticNodeId(int nodeId) => throw new NotImplementedException();
     }
 }

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

@@ -47,7 +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 int GetSemanticNodeId() => throw new NotImplementedException();
         public void SetSemanticNodeId(int nodeId) => throw new NotImplementedException();
     }
 }

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

@@ -583,15 +583,23 @@ namespace QuestPDF.Drawing
                     if (container.Child is TextBlock textBlock)
                     {
                         var textBlockContainsPageNumber = textBlock.Items.Any(x => x is TextBlockPageNumber);
-                        
+
                         if (isFooterContext && textBlockContainsPageNumber)
-                            return;
-                        
-                        container.CreateProxy(x => new SemanticTag
                         {
-                            Child = x,
-                            TagType = "P"
-                        });
+                            container.CreateProxy(x => new ArtifactTag
+                            {
+                                Child = x,
+                                Id = SkSemanticNodeSpecialId.PaginationArtifact
+                            });
+                        }
+                        else
+                        {
+                            container.CreateProxy(x => new SemanticTag
+                            {
+                                Child = x,
+                                TagType = "P"
+                            });
+                        }
                     }
                     else
                     {

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

@@ -148,6 +148,11 @@ namespace QuestPDF.Drawing.DrawingCanvases
             
         }
         
+        public int GetSemanticNodeId()
+        {
+            return 0;
+        }
+        
         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 int GetSemanticNodeId()
+    {
+        return Target.GetSemanticNodeId();
+    }
+    
     public void SetSemanticNodeId(int nodeId)
     {
         Target.SetSemanticNodeId(nodeId);

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

@@ -148,6 +148,11 @@ namespace QuestPDF.Drawing.DrawingCanvases
             
         }
         
+        public int GetSemanticNodeId()
+        {
+            return 0;
+        }
+        
         public void SetSemanticNodeId(int nodeId)
         {
             

+ 7 - 10
Source/QuestPDF/Drawing/DrawingCanvases/SkiaDrawingCanvas.cs

@@ -231,22 +231,19 @@ namespace QuestPDF.Drawing.DrawingCanvases
             CurrentCanvas.AnnotateDestination(sectionName);
         }
 
-        private int ArtifactNestingDepth { get; set; } = 0;
+        private int CurrentSemanticNodeId { get; set; } = 0;
 
-        public void MarkCurrentContentAsArtifact(bool isArtifact)
+        public int GetSemanticNodeId()
         {
-            ArtifactNestingDepth += isArtifact ? 1 : -1;
+            return CurrentSemanticNodeId;
         }
-
+        
         public void SetSemanticNodeId(int nodeId)
         {
-            var isInsideArtifact = ArtifactNestingDepth > 0;
-            var isArtifactNode = nodeId < 0;
-            
-            if (isInsideArtifact && !isArtifactNode)
-                return;
+            CurrentSemanticNodeId = nodeId;
             
-            CurrentCanvas.SetSemanticNodeId(nodeId);
+            foreach (var canvas in ZIndexCanvases)
+                canvas.Value.Canvas.SetSemanticNodeId(nodeId);
         }
         
         #endregion

+ 26 - 0
Source/QuestPDF/Drawing/SemanticTreeManager.cs

@@ -168,4 +168,30 @@ class SemanticTreeSnapshots(SemanticTreeManager? semanticTreeManager, IPageConte
             GC.SuppressFinalize(this);
         }
     }
+}
+
+internal readonly ref struct SemanticScope : IDisposable
+{
+    private IDrawingCanvas DrawingCanvas { get; }
+    private int OriginalSemanticNodeId { get; }
+
+    public SemanticScope(IDrawingCanvas drawingCanvas, int nodeId)
+    {
+        DrawingCanvas = drawingCanvas;
+        OriginalSemanticNodeId = drawingCanvas.GetSemanticNodeId();
+        DrawingCanvas.SetSemanticNodeId(nodeId);
+    }
+    
+    public void Dispose()
+    {
+        DrawingCanvas.SetSemanticNodeId(OriginalSemanticNodeId);
+    }
+}
+
+internal static class SemanticCanvasExtensions
+{
+    public static SemanticScope StartSemanticScopeWithNodeId(this IDrawingCanvas canvas, int nodeId)
+    {
+        return new SemanticScope(canvas, nodeId);
+    }
 }

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

@@ -17,7 +17,7 @@ internal class ArtifactTag : ContainerElement, ISemanticAware
             return;       
         }
         
-        Canvas.SetSemanticNodeId(Id);
+        using var semanticScope = Canvas.StartSemanticScopeWithNodeId(Id);
         
         SemanticTreeManager.BeginArtifactContent();
         Child?.Draw(availableSpace);

+ 2 - 1
Source/QuestPDF/Elements/Line.cs

@@ -73,9 +73,10 @@ namespace QuestPDF.Elements
             var offset = Type == LineType.Vertical
                 ? new Position(Thickness / 2, 0)
                 : new Position(0, Thickness / 2);
+
+            using var semanticScope = Canvas.StartSemanticScopeWithNodeId(SkSemanticNodeSpecialId.LayoutArtifact);
             
             Canvas.Translate(offset);
-            Canvas.SetSemanticNodeId(SkSemanticNodeSpecialId.LayoutArtifact);
             Canvas.DrawLine(start, end, paint);
             Canvas.Translate(offset.Reverse());
             

+ 8 - 6
Source/QuestPDF/Elements/RepeatContent.cs

@@ -41,14 +41,16 @@ internal sealed class RepeatContent : ContainerElement, IStateful, ISemanticAwar
                 _ => SkSemanticNodeSpecialId.PaginationArtifact
             };
         
-            Canvas.SetSemanticNodeId(paginationNodeId);
+            using var semanticScope = Canvas.StartSemanticScopeWithNodeId(paginationNodeId);
+            
             SemanticTreeManager.BeginArtifactContent();
-        }
-        
-        base.Draw(availableSpace);
-        
-        if (IsFullyRendered)
+            base.Draw(availableSpace);
             SemanticTreeManager.EndArtifactContent();
+        }
+        else
+        {
+            base.Draw(availableSpace);
+        }
 
         ResetChildrenIfNecessary();
 

+ 2 - 1
Source/QuestPDF/Elements/SemanticTag.cs

@@ -32,8 +32,9 @@ internal class SemanticTag : ContainerElement, ISemanticAware
         
         RegisterCurrentSemanticNode();
         
+        using var semanticScope = Canvas.StartSemanticScopeWithNodeId(SemanticTreeNode.NodeId);
+        
         SemanticTreeManager.PushOnStack(SemanticTreeNode);
-        Canvas.SetSemanticNodeId(SemanticTreeNode.NodeId);
         Child?.Draw(availableSpace);
         SemanticTreeManager.PopStack();
     }

+ 6 - 5
Source/QuestPDF/Elements/StyledBox.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using QuestPDF.Drawing;
 using QuestPDF.Drawing.DrawingCanvases;
 using QuestPDF.Helpers;
 using QuestPDF.Infrastructure;
@@ -90,7 +91,7 @@ namespace QuestPDF.Elements
                 // optimization: draw a simple rectangle with border
                 if (backgroundPaint != null)
                 {
-                    Canvas.SetSemanticNodeId(SkSemanticNodeSpecialId.BackgroundArtifact);
+                    using var semanticScope = Canvas.StartSemanticScopeWithNodeId(SkSemanticNodeSpecialId.BackgroundArtifact);
                     Canvas.DrawRectangle(Position.Zero, availableSpace, backgroundPaint);
                 }
                 
@@ -100,7 +101,7 @@ namespace QuestPDF.Elements
                 {
                     borderPaint.SetStroke(BorderLeft);
                     
-                    Canvas.SetSemanticNodeId(SkSemanticNodeSpecialId.LayoutArtifact);
+                    using var semanticScope = Canvas.StartSemanticScopeWithNodeId(SkSemanticNodeSpecialId.LayoutArtifact);
                     Canvas.DrawRectangle(Position.Zero, availableSpace, borderPaint);
                 }
                 
@@ -123,7 +124,7 @@ namespace QuestPDF.Elements
                     Color = Shadow.Color
                 };
                 
-                Canvas.SetSemanticNodeId(SkSemanticNodeSpecialId.BackgroundArtifact);
+                using var semanticScope = Canvas.StartSemanticScopeWithNodeId(SkSemanticNodeSpecialId.BackgroundArtifact);
                 Canvas.DrawShadow(shadowRect, canvasShadow);
             }
 
@@ -135,7 +136,7 @@ namespace QuestPDF.Elements
 
             if (backgroundPaint != null)
             {
-                Canvas.SetSemanticNodeId(SkSemanticNodeSpecialId.BackgroundArtifact);
+                using var semanticScope = Canvas.StartSemanticScopeWithNodeId(SkSemanticNodeSpecialId.BackgroundArtifact);
                 Canvas.DrawRectangle(Position.Zero, availableSpace, backgroundPaint);
             }
             
@@ -146,7 +147,7 @@ namespace QuestPDF.Elements
 
             if (borderPaint != null)
             {
-                Canvas.SetSemanticNodeId(SkSemanticNodeSpecialId.LayoutArtifact);
+                using var semanticScope = Canvas.StartSemanticScopeWithNodeId(SkSemanticNodeSpecialId.LayoutArtifact);
                 Canvas.DrawComplexBorder(borderInnerRect, borderOuterRect, borderPaint);
             }
         }

+ 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);
         
+        int GetSemanticNodeId();
         void SetSemanticNodeId(int nodeId);
     }
 }

+ 1 - 1
Source/QuestPDF/QuestPDF.csproj

@@ -6,7 +6,7 @@
         <Version>2025.12.2</Version>
         <PackageDescription>QuestPDF is a modern library for PDF document generation. Its fluent C# API lets you design complex layouts with clean, readable code. Create invoices, reports, and documents using a flexible, component-based approach.</PackageDescription>
         <PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
-        <LangVersion>12</LangVersion>
+        <LangVersion>13</LangVersion>
         <Deterministic>true</Deterministic>
         <EnablePackageValidation>true</EnablePackageValidation>
         <PackageIcon>Logo.png</PackageIcon>