Explorar el Código

Fix semantic tagging in advanced layouts (like MultiColumn) and refactor canvas-related code

Marcin Ziąbek hace 1 mes
padre
commit
0577e2e401

+ 73 - 0
Source/QuestPDF.ConformanceTests/OrderOfSemanticItemsTests.cs

@@ -0,0 +1,73 @@
+using QuestPDF.ConformanceTests.TestEngine;
+using QuestPDF.Drawing;
+using QuestPDF.Fluent;
+
+namespace QuestPDF.ConformanceTests;
+
+internal class OrderOfSemanticItemsTests : ConformanceTestBase
+{
+    protected override Document GetDocumentUnderTest()
+    {
+        // this test checks if SemanticTag registers semantic content only in actual Skia drawing canvas
+        
+        return Document
+            .Create(document =>
+            {
+                document.Page(page =>
+                {
+                    page.Margin(60);
+
+                    page.Content()
+                        .Column(column =>
+                        {
+                            column.Item()
+                                .SemanticHeader1()
+                                .Text("1 - H1");
+                            
+                            column.Item()
+                                .SemanticHeader2()
+                                .Text("2 - H2");
+                            
+                            column.Item()
+                                .SemanticHeader2()
+                                .Text("3 - H2");
+                            
+                            column.Item().MultiColumn(multiColumn =>
+                            {
+                                multiColumn.Spacing(75);
+                            
+                                multiColumn.Content().Column(column =>
+                                {
+                                    column.Item()
+                                        .SemanticHeader2()
+                                        .Text("4 - H2");
+                                    
+                                    column.Item()
+                                        .SemanticHeader3()
+                                        .Text("5 - H3");
+                                    
+                                    column.Item()
+                                        .SemanticHeader3()
+                                        .Text("6 - H3");
+                                });
+                            });
+                        });
+                });
+            });
+    }
+    
+    protected override SemanticTreeNode? GetExpectedSemanticTree()
+    {
+        return ExpectedSemanticTree.DocumentRoot(root =>
+        {
+            root.Child("H1", h1 => h1.Alt("1 - H1"));
+            
+            root.Child("H2", h2 => h2.Alt("2 - H2"));
+            root.Child("H2", h2 => h2.Alt("3 - H2"));
+            
+            root.Child("H2", h2 => h2.Alt("4 - H2"));
+            root.Child("H3", h3 => h3.Alt("5 - H3"));
+            root.Child("H3", h3 => h3.Alt("6 - H3"));
+        });
+    }
+}

+ 2 - 11
Source/QuestPDF.LayoutTests/TestEngine/ElementObserver.cs

@@ -2,6 +2,7 @@ using System.Diagnostics;
 using QuestPDF.Drawing.DrawingCanvases;
 using QuestPDF.Drawing.Proxy;
 using QuestPDF.Elements;
+using QuestPDF.Helpers;
 
 namespace QuestPDF.LayoutTests.TestEngine;
 
@@ -27,7 +28,7 @@ internal class ElementObserver : ContainerElement
             Size = ObserverId == "$document" ? Child.Measure(availableSpace) : availableSpace
         };
         
-        if (!IsDiscardDrawingCanvas())
+        if (!Canvas.IsDiscardDrawingCanvas())
             DrawingRecorder?.Record(drawingEvent);
         
         var matrixBeforeDraw = Canvas.GetCurrentMatrix().ToMatrix4x4();
@@ -63,14 +64,4 @@ internal class ElementObserver : ContainerElement
         
         return result;
     }
-
-    private bool IsDiscardDrawingCanvas()
-    {
-        var canvasUnderTest = Canvas;
-
-        while (canvasUnderTest is ProxyDrawingCanvas proxy)
-            canvasUnderTest = proxy.Target;
-
-        return canvasUnderTest is DiscardDrawingCanvas;
-    }
 }

+ 2 - 1
Source/QuestPDF.LayoutTests/TestEngine/LayoutTest.cs

@@ -1,6 +1,7 @@
 using System.Runtime.CompilerServices;
 using System.Text;
 using NUnit.Framework.Constraints;
+using QuestPDF.Drawing.DocumentCanvases;
 using QuestPDF.Drawing.Exceptions;
 using QuestPDF.Elements;
 using QuestPDF.Helpers;
@@ -182,7 +183,7 @@ internal class LayoutTest
                     page.Content().Element(Content);
                 });
             })
-            .Generate(new LayoutTestDocumentCanvas());
+            .Generate(new DiscardDocumentCanvas());
     }
 
     public LayoutTest VisualizeOutput()

+ 0 - 153
Source/QuestPDF.LayoutTests/TestEngine/LayoutTestDrawingCanvas.cs

@@ -1,153 +0,0 @@
-using System.Numerics;
-using QuestPDF.Drawing;
-using QuestPDF.Skia;
-using QuestPDF.Skia.Text;
-
-namespace QuestPDF.LayoutTests.TestEngine;
-
-internal sealed class LayoutTestDrawingCanvas : IDrawingCanvas
-{
-    private Stack<Matrix4x4> MatrixStack { get; } = new();
-    private Matrix4x4 CurrentMatrix { get; set; } = Matrix4x4.Identity;
-    private int CurrentZIndex { get; set; } = 0;
-    
-    public DocumentPageSnapshot GetSnapshot()
-    {
-        return new DocumentPageSnapshot();
-    }
-
-    public void DrawSnapshot(DocumentPageSnapshot snapshot)
-    {
-        
-    }
-
-    public void Save()
-    {
-        MatrixStack.Push(CurrentMatrix);
-    }
-
-    public void Restore()
-    {
-        CurrentMatrix = MatrixStack.Pop();
-    }
-
-    public void SetZIndex(int index)
-    {
-        CurrentZIndex = index;
-    }
-
-    public int GetZIndex()
-    {
-        return CurrentZIndex;
-    }
-    
-    public SkCanvasMatrix GetCurrentMatrix()
-    {
-        return SkCanvasMatrix.FromMatrix4x4(CurrentMatrix);
-    }
-
-    public void SetMatrix(SkCanvasMatrix matrix)
-    {
-        CurrentMatrix = matrix.ToMatrix4x4();
-    }
-
-    public void Translate(Position vector)
-    {
-        CurrentMatrix = Matrix4x4.CreateTranslation(vector.X, vector.Y, 0) * CurrentMatrix;
-    }
-    
-    public void Scale(float scaleX, float scaleY)
-    {
-        CurrentMatrix = Matrix4x4.CreateScale(scaleX, scaleY, 1) * CurrentMatrix;
-    }
-    
-    public void Rotate(float angle)
-    {
-        CurrentMatrix = Matrix4x4.CreateRotationZ((float)Math.PI * angle / 180f) * CurrentMatrix;
-    }
-
-    public void DrawLine(Position start, Position end, SkPaint paint)
-    {
-        
-    }
-
-    public void DrawRectangle(Position vector, Size size, SkPaint paint)
-    {
-        
-    }
-
-    public void DrawComplexBorder(SkRoundedRect innerRect, SkRoundedRect outerRect, SkPaint paint)
-    {
-        
-    }
-    
-    public void DrawShadow(SkRoundedRect shadowRect, SkBoxShadow shadow)
-    {
-        
-    }
-    
-    public void DrawParagraph(SkParagraph paragraph, int lineFrom, int lineTo)
-    {
-        
-    }
-
-    public void DrawImage(SkImage image, Size size)
-    {
-        
-    }
-
-    public void DrawPicture(SkPicture picture)
-    {
-        
-    }
-
-    public void DrawSvgPath(string path, Color color)
-    {
-        
-    }
-
-    public void DrawSvg(SkSvgImage svgImage, Size size)
-    {
-        
-    }
-
-    public void DrawOverflowArea(SkRect area)
-    {
-        
-    }
-
-    public void ClipOverflowArea(SkRect availableSpace, SkRect requiredSpace)
-    {
-        
-    }
-
-    public void ClipRectangle(SkRect clipArea)
-    {
-        
-    }
-    
-    public void ClipRoundedRectangle(SkRoundedRect clipArea)
-    {
-        
-    }
-    
-    public void DrawHyperlink(Size size, string url, string? description)
-    {
-       
-    }
-
-    public void DrawSectionLink(Size size, string sectionName, string? description)
-    {
-        
-    }
-
-    public void DrawSection(string sectionName)
-    {
-        
-    }
-    
-    public void SetSemanticNodeId(int nodeId)
-    {
-        
-    }
-}

+ 1 - 1
Source/QuestPDF/Drawing/DocumentCanvases/FreeDocumentCanvas.cs → Source/QuestPDF/Drawing/DocumentCanvases/DiscardDocumentCanvas.cs

@@ -3,7 +3,7 @@ using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Drawing.DocumentCanvases;
 
-internal sealed class FreeDocumentCanvas : IDocumentCanvas
+internal sealed class DiscardDocumentCanvas : IDocumentCanvas
 {
     private DiscardDrawingCanvas DrawingCanvas { get; } = new();
         

+ 5 - 4
Source/QuestPDF.LayoutTests/TestEngine/LayoutTestDocumentCanvas.cs → Source/QuestPDF/Drawing/DocumentCanvases/SemanticDocumentCanvas.cs

@@ -1,10 +1,11 @@
-using QuestPDF.Drawing;
+using QuestPDF.Drawing.DrawingCanvases;
+using QuestPDF.Infrastructure;
 
-namespace QuestPDF.LayoutTests.TestEngine;
+namespace QuestPDF.Drawing.DocumentCanvases;
 
-internal sealed class LayoutTestDocumentCanvas : IDocumentCanvas
+internal sealed class SemanticDocumentCanvas : IDocumentCanvas
 {
-    private LayoutTestDrawingCanvas DrawingCanvas { get; } = new();
+    private SemanticDrawingCanvas DrawingCanvas { get; } = new();
         
     public void SetSemanticTree(SemanticTreeNode? semanticTree)
     {

+ 3 - 2
Source/QuestPDF/Drawing/DocumentGenerator.cs

@@ -115,7 +115,7 @@ namespace QuestPDF.Drawing
             try
             {
                 var pageContext = new PageContext();
-                RenderPass(pageContext, new FreeDocumentCanvas(), content);
+                RenderPass(pageContext, new SemanticDocumentCanvas(), content);
                 pageContext.ProceedToNextRenderingPhase();
 
                 canvas.ConfigureWithSemanticTree(semanticTreeManager);
@@ -141,6 +141,7 @@ namespace QuestPDF.Drawing
             var useSharedPageContext = document.PageNumberStrategy == MergedDocumentPageNumberStrategy.Continuous;
 
             var semanticTreeManager = CreateSemanticTreeManager(settings);
+            var semanticDocumentCanvas = new SemanticDocumentCanvas();
             
             var documentParts = Enumerable
                 .Range(0, document.Documents.Count)
@@ -159,7 +160,7 @@ namespace QuestPDF.Drawing
                 
                 foreach (var documentPart in documentParts)
                 {
-                    RenderPass(documentPart.PageContext, new FreeDocumentCanvas(), documentPart.Content);
+                    RenderPass(documentPart.PageContext, semanticDocumentCanvas, documentPart.Content);
                     documentPart.PageContext.ProceedToNextRenderingPhase();
                 }
 

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

@@ -0,0 +1,156 @@
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using QuestPDF.Infrastructure;
+using QuestPDF.Skia;
+using QuestPDF.Skia.Text;
+
+namespace QuestPDF.Drawing.DrawingCanvases
+{
+    internal sealed class SemanticDrawingCanvas : IDrawingCanvas
+    {
+        private Stack<Matrix4x4> MatrixStack { get; } = new();
+        private Matrix4x4 CurrentMatrix { get; set; } = Matrix4x4.Identity;
+        private int CurrentZIndex { get; set; } = 0;
+        
+        public DocumentPageSnapshot GetSnapshot()
+        {
+            return new DocumentPageSnapshot();
+        }
+
+        public void DrawSnapshot(DocumentPageSnapshot snapshot)
+        {
+            
+        }
+
+        public void Save()
+        {
+            MatrixStack.Push(CurrentMatrix);
+        }
+
+        public void Restore()
+        {
+            CurrentMatrix = MatrixStack.Pop();
+        }
+
+        public void SetZIndex(int index)
+        {
+            CurrentZIndex = index;
+        }
+
+        public int GetZIndex()
+        {
+            return CurrentZIndex;
+        }
+        
+        public SkCanvasMatrix GetCurrentMatrix()
+        {
+            return SkCanvasMatrix.FromMatrix4x4(CurrentMatrix);
+        }
+
+        public void SetMatrix(SkCanvasMatrix matrix)
+        {
+            CurrentMatrix = matrix.ToMatrix4x4();
+        }
+
+        public void Translate(Position vector)
+        {
+            CurrentMatrix = Matrix4x4.CreateTranslation(vector.X, vector.Y, 0) * CurrentMatrix;
+        }
+        
+        public void Scale(float scaleX, float scaleY)
+        {
+            CurrentMatrix = Matrix4x4.CreateScale(scaleX, scaleY, 1) * CurrentMatrix;
+        }
+        
+        public void Rotate(float angle)
+        {
+            CurrentMatrix = Matrix4x4.CreateRotationZ((float)Math.PI * angle / 180f) * CurrentMatrix;
+        }
+
+        public void DrawLine(Position start, Position end, SkPaint paint)
+        {
+            
+        }
+
+        public void DrawRectangle(Position vector, Size size, SkPaint paint)
+        {
+            
+        }
+
+        public void DrawComplexBorder(SkRoundedRect innerRect, SkRoundedRect outerRect, SkPaint paint)
+        {
+            
+        }
+        
+        public void DrawShadow(SkRoundedRect shadowRect, SkBoxShadow shadow)
+        {
+            
+        }
+        
+        public void DrawParagraph(SkParagraph paragraph, int lineFrom, int lineTo)
+        {
+            
+        }
+
+        public void DrawImage(SkImage image, Size size)
+        {
+            
+        }
+
+        public void DrawPicture(SkPicture picture)
+        {
+            
+        }
+
+        public void DrawSvgPath(string path, Color color)
+        {
+            
+        }
+
+        public void DrawSvg(SkSvgImage svgImage, Size size)
+        {
+            
+        }
+
+        public void DrawOverflowArea(SkRect area)
+        {
+            
+        }
+
+        public void ClipOverflowArea(SkRect availableSpace, SkRect requiredSpace)
+        {
+            
+        }
+
+        public void ClipRectangle(SkRect clipArea)
+        {
+            
+        }
+        
+        public void ClipRoundedRectangle(SkRoundedRect clipArea)
+        {
+            
+        }
+        
+        public void DrawHyperlink(Size size, string url, string? description)
+        {
+           
+        }
+
+        public void DrawSectionLink(Size size, string sectionName, string? description)
+        {
+            
+        }
+
+        public void DrawSection(string sectionName)
+        {
+            
+        }
+        
+        public void SetSemanticNodeId(int nodeId)
+        {
+            
+        }
+    }
+}

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

@@ -1,7 +1,9 @@
 using System;
 using System.Text;
 using QuestPDF.Drawing;
+using QuestPDF.Drawing.DrawingCanvases;
 using QuestPDF.Elements.Text;
+using QuestPDF.Helpers;
 using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Elements;
@@ -17,7 +19,12 @@ internal class SemanticTag : ContainerElement, ISemanticAware
 
     internal override void Draw(Size availableSpace)
     {
-        if (SemanticTreeManager == null || SemanticTreeManager.IsCurrentContentArtifact())
+        var shouldIgnoreSemanticMeaning =
+            Canvas.IsDiscardDrawingCanvas() ||
+            SemanticTreeManager == null ||
+            SemanticTreeManager.IsCurrentContentArtifact();
+        
+        if (shouldIgnoreSemanticMeaning)
         {
             Child?.Draw(availableSpace);
             return;       

+ 11 - 0
Source/QuestPDF/Helpers/Helpers.cs

@@ -6,6 +6,7 @@ using System.Linq.Expressions;
 using System.Reflection;
 using System.Text.RegularExpressions;
 using QuestPDF.Drawing;
+using QuestPDF.Drawing.DrawingCanvases;
 using QuestPDF.Infrastructure;
 using QuestPDF.Skia;
 using static QuestPDF.Skia.SkSvgImageSize.Unit;
@@ -202,5 +203,15 @@ namespace QuestPDF.Helpers
         {
             return value.ToString("0.#", CultureInfo.InvariantCulture);
         }
+        
+        public static bool IsDiscardDrawingCanvas(this IDrawingCanvas canvas)
+        {
+            var canvasUnderTest = canvas;
+
+            while (canvasUnderTest is ProxyDrawingCanvas proxy)
+                canvasUnderTest = proxy.Target;
+
+            return canvasUnderTest is DiscardDrawingCanvas;
+        }
     }
 }