Hi, How to add alternative text for fully compliance with PDF/UA, WCAG ?
Regards
Pawel
Hi Pawel,
Adding an alternative text is easy:
// Add alternative field name entry.
signatureField.Tooltip = signatureField.Name;
However, based on your screenshot, the issue is also that the signature field is not contained in the document’s logical structure.
GemBox.Pdf currently doesn’t expose the document’s logical structure using the high-level API, so the low-level API and intimate knowledge of the PDF specification will have to be used to accomplish this, like in the following code snippet:
// If the document contains the logical structure, then add the signature field to the logical structure.
// See: https://opensource.adobe.com/dc-acrobat-sdk-docs/standards/pdfstandards/pdf/PDF32000_2008.pdf#page=565
if (document.GetStructTreeRoot() is PdfDictionary structTreeRoot)
{
// Find the most appropriate parent for the 'Form' structure element that will hold the reference to the signature field.
var parentStructureElement = structTreeRoot.SearchStructureElementParent(GemBoxPdfTaggedExtensions.FormParentTypes, fromEnd: true);
if (parentStructureElement is null)
parentStructureElement = structTreeRoot;
// Create the 'Form' structure element and specify its Placement and Height attributes.
// See: https://opensource.adobe.com/dc-acrobat-sdk-docs/standards/pdfstandards/pdf/PDF32000_2008.pdf#page=600
// and https://opensource.adobe.com/dc-acrobat-sdk-docs/standards/pdfstandards/pdf/PDF32000_2008.pdf#page=615
var formStructureElement = GemBoxPdfTaggedExtensions.CreateFormStructureElement();
formStructureElement.Add(PdfName.Create("A"), PdfDictionary.Create(
new PdfDictionaryEntry("O", PdfName.Create("Layout")),
new PdfDictionaryEntry("Placement", PdfName.Create("Block")),
new PdfDictionaryEntry("Height", PdfNumber.Create(signatureField.Bounds.Height))
));
// Add the 'Form' structure element to its parent structure element.
parentStructureElement.AddStructureElementChild(formStructureElement.Indirect);
// Create the object reference dictionary and add it as a child of the 'Form' structure element.
// See: https://opensource.adobe.com/dc-acrobat-sdk-docs/standards/pdfstandards/pdf/PDF32000_2008.pdf#page=572
var objectRefrence = signatureField.GetDictionary().Indirect.CreateObjectReference(document.Pages[0]);
formStructureElement.AddStructureElementChild(objectRefrence);
// Associate an integer with the 'Form' structure element and set that integer as a value of the 'StructParent' entry in the signature field dictionary.
// This is used in finding the structure elements to which content items belong.
// See: https://opensource.adobe.com/dc-acrobat-sdk-docs/standards/pdfstandards/pdf/PDF32000_2008.pdf#page=572
var structParentId = structTreeRoot.AddToParentTree(formStructureElement.Indirect);
if (structParentId >= 0)
signatureField.GetDictionary()[PdfName.Create("StructParent")] = PdfInteger.Create(structParentId);
else
throw new InvalidOperationException("StructTreeRoot.ParentTree is too complex.");
}
This code snippet should be used just after the signature field is created, and it uses the following utility class:
using System;
using System.Collections.Generic;
using System.Linq;
using GemBox.Pdf.Objects;
namespace GemBox.Pdf
{
internal static class GemBoxPdfTaggedExtensions
{
public static readonly PdfName[] FormParentTypes = [
PdfName.Create("Document"), PdfName.Create("DocumentFragment"),
PdfName.Create("Sect"), PdfName.Create("ASide"), PdfName.Create("Title"), PdfName.Create("Sub"),
PdfName.Create("P")];
private static readonly PdfName
RootName = PdfName.Create("Root"),
StructTreeRootName = PdfName.Create("StructTreeRoot"),
KName = PdfName.Create("K"),
SName = PdfName.Create("S"),
TypeName = PdfName.Create("Type"),
OBJRName = PdfName.Create("OBJR"),
PgName = PdfName.Create("Pg"),
ObjName = PdfName.Create("Obj"),
FormName = PdfName.Create("Form"),
PName = PdfName.Create("P"),
ParentTreeName = PdfName.Create("ParentTree"),
ParentTreeNextKeyName = PdfName.Create("ParentTreeNextKey"),
NumsName = PdfName.Create("Nums");
public static PdfDictionary? GetStructTreeRoot(this PdfDocument document)
{
if (document.GetDictionary() is PdfDictionary fileTrailer &&
fileTrailer.GetEntry<PdfDictionary>(RootName, required: true, indirect: true) is PdfDictionary catalog)
{
var structTreeRoot = catalog.GetEntry<PdfDictionary>(StructTreeRootName);
if (structTreeRoot is not null)
{
if (structTreeRoot.Indirect is null)
{
catalog.Remove(StructTreeRootName);
catalog.Add(StructTreeRootName, PdfIndirectObject.Create(structTreeRoot));
}
}
return structTreeRoot;
}
else
return null;
}
public static PdfDictionary? SearchStructureElementParent(this PdfDictionary structTreeRoot, PdfName[] structureTypes, bool fromEnd = false)
{
var queue = new Queue<PdfDictionary>();
queue.Enqueue(structTreeRoot);
while (queue.Count > 0)
{
var structureElement = queue.Dequeue();
if (structureElement.GetEntry<PdfName>(SName) is PdfName structureType && Array.IndexOf(structureTypes, structureType) >= 0)
return structureElement;
var children = structureElement.GetStructureElementChildren();
if (fromEnd)
children = children.Reverse();
foreach (var childObj in children)
if (childObj is PdfDictionary child)
queue.Enqueue(child);
}
return null;
}
public static PdfDictionary CreateObjectReference(this PdfIndirectObject obj, PdfPage? page = null)
{
var dictionary = PdfDictionary.Create(3);
dictionary.Add(TypeName, OBJRName);
if (page is not null && page.GetDictionary() is PdfDictionary pageDict && pageDict.Indirect is PdfIndirectObject pageIndirectObj)
dictionary.Add(PgName, pageIndirectObj);
dictionary.Add(ObjName, obj);
return dictionary;
}
public static PdfDictionary CreateFormStructureElement()
{
var dictionary = PdfDictionary.Create(2);
dictionary.Add(SName, FormName);
PdfIndirectObject.Create(dictionary);
return dictionary;
}
public static void AddStructureElementChild(this PdfDictionary structureElement, PdfBasicObject childObj)
{
PdfBasicObject child = childObj.ObjectType == PdfBasicObjectType.IndirectObject ? ((PdfIndirectObject)childObj).Value : childObj;
if (child is PdfDictionary childDict && childDict.GetEntry<PdfName>(SName) is not null)
childDict[PName] = structureElement.Indirect ?? throw new NullReferenceException();
var k = structureElement.GetEntry<PdfBasicObject>(KName);
if (k is null)
structureElement.Add(KName, childObj);
else if (k is PdfArray array)
array.Add(childObj);
else
{
array = PdfArray.Create(2);
structureElement.Remove(KName);
array.Add(k is PdfBasicContainer kContainer ? (kContainer.Indirect ?? kContainer) : k);
array.Add(childObj);
structureElement.Add(KName, array);
}
}
public static int AddToParentTree(this PdfDictionary structTreeRoot, PdfIndirectObject structureElementIndirectObj)
{
var parentTreeNextKey = structTreeRoot.GetEntry<PdfInteger>(ParentTreeNextKeyName)?.Value ?? 0;
var parentTree = structTreeRoot.GetEntry<PdfDictionary>(ParentTreeName);
if (parentTree is null)
{
parentTree = PdfDictionary.Create(1);
var nums = PdfArray.Create(2);
nums.Add(PdfInteger.Create(parentTreeNextKey));
nums.Add(structureElementIndirectObj);
parentTree.Add(NumsName, nums);
structTreeRoot.Add(ParentTreeName, parentTree);
}
else
{
if (parentTree.GetEntry<PdfArray>(NumsName) is PdfArray nums)
{
nums.Add(PdfInteger.Create(parentTreeNextKey));
nums.Add(structureElementIndirectObj);
}
else
return -1;
}
structTreeRoot[ParentTreeNextKeyName] = PdfInteger.Create(parentTreeNextKey + 1);
return parentTreeNextKey;
}
private static IEnumerable<PdfBasicObject> GetStructureElementChildren(this PdfDictionary structureElement)
{
var k = structureElement.GetEntry<PdfBasicObject>(KName);
if (k is null)
yield break;
else if (k is PdfArray array)
foreach (var element in array)
yield return element.ObjectType == PdfBasicObjectType.IndirectObject ? ((PdfIndirectObject)element).Value : element;
else
yield return k;
}
private static TPdfBasicObject? GetEntry<TPdfBasicObject>(this PdfDictionary dictionary, PdfName key, bool required = false, bool? indirect = null) where TPdfBasicObject : PdfBasicObject
{
PdfBasicObject value;
if (required)
value = dictionary[key];
else if (!dictionary.TryGetValue(key, out value))
return null;
if (indirect is null)
{
if (value.ObjectType == PdfBasicObjectType.IndirectObject)
return (TPdfBasicObject)((PdfIndirectObject)value).Value;
else
return (TPdfBasicObject)value;
}
else if (indirect.GetValueOrDefault())
return (TPdfBasicObject)((PdfIndirectObject)value).Value;
else
return (TPdfBasicObject)value;
}
}
}
Regards,
Stipo
Hi,
in this PDF i have the Alternative Text to Signature
signatureField.Tooltip = signatureField.Name;

but I want add to Appearance of Signature. See screenshot.
Regards
Pawel
update : I checked the GemBoxPdfTaggedExtensions and it Works ![]()
Thanks Stipo !