Browse Source

Semantic: improved support for text spans, languages and alt text

Marcin Ziąbek 3 months ago
parent
commit
9baad024b7

+ 22 - 9
Source/QuestPDF/Drawing/DocumentGenerator.cs

@@ -592,18 +592,31 @@ namespace QuestPDF.Drawing
             }
         }
 
-        internal static SkPdfTag ExtractStructuralInformation(this Element element)
+        internal static SkPdfTag ExtractStructuralInformation(this Element root)
         {
-            var semanticTags = element.ExtractElementsOfType<SemanticTag>();
-            return GetSkiaTagFor(semanticTags.First());
+            return GetSkiaTagFor(root).First();
 
-            static SkPdfTag GetSkiaTagFor(TreeNode<SemanticTag> treeNode)
+            static IEnumerable<SkPdfTag> GetSkiaTagFor(Element element)
             {
-                var tagElement = treeNode.Value;
-                var result = SkPdfTag.Create(tagElement.Id, tagElement.TagType, tagElement.Alt, tagElement.Lang ?? "en-US");
-                var children = treeNode.Children.Select(GetSkiaTagFor).ToArray();
-                result.SetChildren(children);
-                return result;
+                if (element is SemanticTag semanticTag)
+                {
+                    if (semanticTag.TagType == "Span")
+                        semanticTag.UpdateAlternativeText();
+                    
+                    var result = SkPdfTag.Create(semanticTag.Id, semanticTag.TagType, semanticTag.Alt, semanticTag.Lang);
+                    result.SetChildren(GetSkiaTagFor(semanticTag.Child).ToArray());
+                    yield return result;
+                }
+                else if (element is ContainerElement container)
+                {
+                    foreach (var child in GetSkiaTagFor(container.Child))
+                        yield return child;
+                }
+                else
+                {
+                    foreach (var child in element.GetChildren().SelectMany(GetSkiaTagFor))
+                        yield return child;
+                }
             }
         }
     }

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

@@ -1,4 +1,6 @@
 using System;
+using System.Text;
+using QuestPDF.Elements.Text;
 using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Elements;
@@ -12,8 +14,34 @@ internal class SemanticTag : ContainerElement
     
     internal override void Draw(Size availableSpace)
     {
-        Console.WriteLine($"{TagType}: {Id}");
         Canvas.SetSemanticNodeId(Id);
         Child?.Draw(availableSpace);
     }
+
+    internal void UpdateAlternativeText()
+    {
+        if (!string.IsNullOrWhiteSpace(Alt))
+            return;
+        
+        var builder = new StringBuilder();
+        Traverse(builder, Child);
+        Alt = builder.ToString();
+        
+        static void Traverse(StringBuilder builder, Element element)
+        {
+            if (element is TextBlock textBlock)
+            {
+                builder.Append(textBlock.Text).Append(' ');
+            }
+            else if (element is ContainerElement container)
+            {
+                Traverse(builder, container);
+            }
+            else
+            {
+                foreach (var child in element.GetChildren())
+                    Traverse(builder, child);
+            }
+        }
+    }
 }

+ 7 - 6
Source/QuestPDF/Fluent/SemanticExtensions.cs

@@ -5,12 +5,13 @@ namespace QuestPDF.Fluent;
 
 public static class SemanticExtensions
 {
-    private static IContainer SemanticTag(this IContainer container, string type, string? alt = null)
+    private static IContainer SemanticTag(this IContainer container, string type, string? alternativeText = null, string? language = null)
     {
         return container.Element(new Elements.SemanticTag
         {
             TagType = type, 
-            Alt = alt
+            Alt = alternativeText,
+            Lang = language
         });
     }
     
@@ -77,9 +78,9 @@ public static class SemanticExtensions
     /// <summary>
     /// 
     /// </summary>
-    public static IContainer SemanticLanguage(this IContainer container, string lang)
+    public static IContainer SemanticLanguage(this IContainer container, string language)
     {
-        return container.SemanticTag("Caption");
+        return container.SemanticTag("NonStruct", language: language);
     }
     
     #region Table of Contents
@@ -249,9 +250,9 @@ public static class SemanticExtensions
     /// A generic inline portion of text having no particular inherent characteristics.
     /// It can be used, for example, to delimit a range of text with a given set of styling attributes.
     /// </summary>
-    public static IContainer SemanticSpan(this IContainer container)
+    public static IContainer SemanticSpan(this IContainer container, string? alternativeText = null)
     {
-        return container.SemanticTag("Span");
+        return container.SemanticTag("Span", alternativeText);
     }
     
     /// <summary>