Browse Source

Add support for table cell attributes (colspan + rowspan)

Marcin Ziąbek 3 months ago
parent
commit
ed927ca0c2

+ 12 - 12
Source/QuestPDF.DocumentationExamples/SemanticExamples.cs

@@ -69,16 +69,16 @@ public class SemanticExamples
                                 // please be sure to call the 'header' handler!
 
                                 header.Cell().RowSpan(2).Element(CellStyle).ExtendHorizontal().AlignLeft()
-                                    .SemanticParagraph().Text("Document type").Bold();
+                                    .Text("Document type").Bold();
 
-                                header.Cell().ColumnSpan(2).Element(CellStyle).SemanticParagraph().Text("Inches").Bold();
-                                header.Cell().ColumnSpan(2).Element(CellStyle).SemanticParagraph().Text("Points").Bold();
+                                header.Cell().ColumnSpan(2).Element(CellStyle).Text("Inches").Bold();
+                                header.Cell().ColumnSpan(2).Element(CellStyle).Text("Points").Bold();
 
-                                header.Cell().Element(CellStyle).SemanticParagraph().Text("Width");
-                                header.Cell().Element(CellStyle).SemanticParagraph().Text("Height");
+                                header.Cell().Element(CellStyle).Text("Width");
+                                header.Cell().Element(CellStyle).Text("Height");
 
-                                header.Cell().Element(CellStyle).SemanticParagraph().Text("Width");
-                                header.Cell().Element(CellStyle).SemanticParagraph().Text("Height");
+                                header.Cell().Element(CellStyle).Text("Width");
+                                header.Cell().Element(CellStyle).Text("Height");
 
                                 // you can extend existing styles by creating additional methods
                                 IContainer CellStyle(IContainer container) =>
@@ -87,15 +87,15 @@ public class SemanticExamples
 
                             foreach (var page in pageSizes)
                             {
-                                table.Cell().Element(CellStyle).ExtendHorizontal().AlignLeft().SemanticParagraph().Text(page.name);
+                                table.Cell().Element(CellStyle).ExtendHorizontal().AlignLeft().Text(page.name);
 
                                 // inches
-                                table.Cell().Element(CellStyle).SemanticParagraph().Text(page.width);
-                                table.Cell().Element(CellStyle).SemanticParagraph().Text(page.height);
+                                table.Cell().Element(CellStyle).Text(page.width);
+                                table.Cell().Element(CellStyle).Text(page.height);
 
                                 // points
-                                table.Cell().Element(CellStyle).SemanticParagraph().Text(page.width * inchesToPoints);
-                                table.Cell().Element(CellStyle).SemanticParagraph().Text(page.height * inchesToPoints);
+                                table.Cell().Element(CellStyle).Text(page.width * inchesToPoints);
+                                table.Cell().Element(CellStyle).Text(page.height * inchesToPoints);
 
                                 IContainer CellStyle(IContainer container) =>
                                     DefaultCellStyle(container, Colors.White).ShowOnce();

+ 9 - 0
Source/QuestPDF/Drawing/DocumentCanvases/PdfDocumentCanvas.cs

@@ -109,6 +109,15 @@ namespace QuestPDF.Drawing.DocumentCanvases
                 var children = node.Children.Select(Convert).ToArray();
                 result.SetChildren(children);
                 
+                foreach (var nodeAttribute in node.Attributes)
+                {
+                    if (nodeAttribute.Value is int intValue)
+                        result.AddAttribute(nodeAttribute.Owner, nodeAttribute.Name, intValue);
+
+                    else
+                        throw new NotSupportedException($"Attribute value of type '{nodeAttribute.Value.GetType()}' is not supported in PDF semantic tags implementation.");
+                }
+                
                 return result;
             }
         }

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

@@ -9,6 +9,14 @@ class SemanticTreeNode
     public string? Alt { get; set; }
     public string? Lang { get; set; }
     public ICollection<SemanticTreeNode> Children { get; } = [];
+    public ICollection<Attribute> Attributes { get; } = [];
+
+    public class Attribute
+    {
+        public string Owner { get; set; }
+        public string Name { get; set; }
+        public object Value { get; set; }
+    }
 }
 
 class SemanticTreeManager

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

@@ -9,7 +9,7 @@ namespace QuestPDF.Elements;
 internal class SemanticTag : ContainerElement
 {
     public SemanticTreeManager SemanticTreeManager { get; set; }
-    public SemanticTreeNode? SemanticTreeNode { get; set; }
+    public SemanticTreeNode? SemanticTreeNode { get; private set; }
 
     public string TagType { get; set; }
     public string? Alt { get; set; }

+ 25 - 0
Source/QuestPDF/Elements/Table/Table.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using QuestPDF.Drawing;
 using QuestPDF.Infrastructure;
@@ -380,10 +381,34 @@ namespace QuestPDF.Elements.Table
                         continue;
                     
                     semanticTag.RegisterCurrentSemanticNode();
+                    AssignCellAttributes(tableCell, semanticTag);
                 }
                 
                 SemanticTreeManager.PopStack();
             }
+
+            void AssignCellAttributes(TableCell tableCell, SemanticTag semanticTag)
+            {
+                if (tableCell.ColumnSpan > 1)
+                {
+                    semanticTag.SemanticTreeNode.Attributes.Add(new SemanticTreeNode.Attribute
+                    {
+                        Owner = "Table",
+                        Name = "ColSpan",
+                        Value = tableCell.ColumnSpan
+                    });
+                }
+
+                if (tableCell.RowSpan > 1)
+                {
+                    semanticTag.SemanticTreeNode.Attributes.Add(new SemanticTreeNode.Attribute
+                    {
+                        Owner = "Table",
+                        Name = "RowSpan",
+                        Value = tableCell.RowSpan
+                    });
+                }
+            }
         }
         
         #endregion

BIN
Source/QuestPDF/Runtimes/osx-arm64/native/libQuestPdfSkia.dylib


+ 12 - 0
Source/QuestPDF/Skia/SkPdfTag.cs

@@ -2,6 +2,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.InteropServices;
+using System.Text;
 
 namespace QuestPDF.Skia;
 
@@ -39,6 +40,14 @@ internal sealed class SkPdfTag : IDisposable
         Marshal.FreeHGlobal(unmanagedArray);
     }
 
+    public void AddAttribute(string owner, string name, int value)
+    {
+        // for some reason, other marshaling approaches do not work 
+        var ownerBytes = Encoding.ASCII.GetBytes(owner + "\0");
+        var nameBytes = Encoding.ASCII.GetBytes(name + "\0");
+        API.pdf_structure_element_add_attribute_integer(Instance, ownerBytes, nameBytes, value);
+    }
+
     ~SkPdfTag()
     {
         this.WarnThatFinalizerIsReached();
@@ -70,6 +79,9 @@ internal sealed class SkPdfTag : IDisposable
         [DllImport(SkiaAPI.LibraryName, CallingConvention = CallingConvention.Cdecl)]
         public static extern void pdf_structure_element_set_children(IntPtr element, IntPtr children, int count);
         
+        [DllImport(SkiaAPI.LibraryName, CallingConvention = CallingConvention.Cdecl)]
+        public static extern void pdf_structure_element_add_attribute_integer(IntPtr element, byte[] owner, byte[] name, int value);
+        
         [DllImport(SkiaAPI.LibraryName, CallingConvention = CallingConvention.Cdecl)]
         public static extern void pdf_structure_element_delete(IntPtr element);
     }