Browse Source

Improve semantic tagging for simple tables (ones that do not contain cells spanning multiple rows/columns)

Marcin Ziąbek 4 tháng trước cách đây
mục cha
commit
2813f9acfe

+ 45 - 3
Source/QuestPDF/Elements/Table/Table.cs

@@ -358,10 +358,21 @@ namespace QuestPDF.Elements.Table
         
         #region Semantic
         
+        internal bool EnableAutomatedSemanticTagging { get; set; }
+        internal bool IsSemanticTaggingApplied { get; set; }
         internal SemanticTreeManager SemanticTreeManager { get; set; } = new();
+        internal bool IsTableHeader { get; set; }
 
         private void RegisterSemanticTree()
         {
+            if (!EnableAutomatedSemanticTagging)
+                return;
+            
+            if (IsSemanticTaggingApplied)
+                return;
+            
+            IsSemanticTaggingApplied = true;
+            
             foreach (var tableRow in Cells.GroupBy(x => x.Row))
             {
                 var rowSemanticTreeNode = new SemanticTreeNode()
@@ -375,11 +386,21 @@ namespace QuestPDF.Elements.Table
                 
                 foreach (var tableCell in tableRow.OrderBy(x => x.Column))
                 {
-                    var semanticTag = tableCell.Child as SemanticTag;
-                    
-                    if (semanticTag == null)
+                    tableCell.CreateProxy(x => new SemanticTag
+                    {
+                        SemanticTreeManager = SemanticTreeManager,
+                        Canvas = Canvas,
+                        
+                        TagType = "TD",
+                        Child = x
+                    });
+
+                    if (tableCell.Child is not SemanticTag semanticTag)
                         continue;
                     
+                    if (IsTableHeader || tableCell.IsSemanticHorizontalHeader)
+                        semanticTag.TagType = "TH";
+                    
                     semanticTag.RegisterCurrentSemanticNode();
                     AssignCellAttributes(tableCell, semanticTag);
                 }
@@ -408,6 +429,27 @@ namespace QuestPDF.Elements.Table
                         Value = tableCell.RowSpan
                     });
                 }
+
+                if (semanticTag.TagType == "TH")
+                {
+                    var scopeValue = (IsTableHeader, tableCell.IsSemanticHorizontalHeader) switch
+                    {
+                        (true, true) => "Both",
+                        (true, false) => "Column",
+                        (false, true) => "Row",
+                        (false, false) => null
+                    };
+
+                    if (scopeValue != null)
+                    {
+                        semanticTag.SemanticTreeNode.Attributes.Add(new SemanticTreeNode.Attribute
+                        {
+                            Owner = "Table", 
+                            Name = "Scope", 
+                            Value = scopeValue
+                        });
+                    }
+                }
             }
         }
         

+ 2 - 0
Source/QuestPDF/Elements/Table/TableCell.cs

@@ -12,6 +12,8 @@ namespace QuestPDF.Elements.Table
         
         public int ZIndex { get; set; }
         
+        public bool IsSemanticHorizontalHeader { get; set; }
+        
         public bool IsRendered { get; set; }
     }
 }

+ 15 - 12
Source/QuestPDF/Fluent/TableExtensions.cs

@@ -198,18 +198,9 @@ namespace QuestPDF.Fluent
             
                 table.PlanCellPositions();
                 table.ValidateCellPositions();
-
-                if (EnableAutomatedSemanticTagging)
-                {
-                    foreach (var tableCell in table.Cells)
-                    {
-                        tableCell.CreateProxy(x => new SemanticTag()
-                        {
-                            Child = x,
-                            TagType = isHeader ? "TH" : "TD"
-                        });
-                    }
-                }
+                
+                table.EnableAutomatedSemanticTagging = EnableAutomatedSemanticTagging;
+                table.IsTableHeader = isHeader;
             }
         }
     }
@@ -288,5 +279,17 @@ namespace QuestPDF.Fluent
 
             return tableCellContainer;
         }
+
+        /// <summary>
+        /// Marks the specified table cell as a semantic horizontal header.
+        /// This allows assistive technologies to recognize the cell as a header, improving accessibility and semantic structure.
+        /// </summary>
+        public static ITableCellContainer AsSemanticHorizontalHeader(this ITableCellContainer tableCellContainer)
+        {
+            if (tableCellContainer is TableCell tableCell)
+                tableCell.IsSemanticHorizontalHeader = true;
+
+            return tableCellContainer;
+        }
     }
 }