Skip to main content

Add logical structure to an existing PDF

Make PDF documents accessible by adding a logical structure using the Toolbox add-on. Learn about the technical process of PDF remediation, which means adding tags and structure to existing PDF content.

If you work with a new PDF or have control over the document creation process, consider Creating an accessible PDF from scratch instead. Creating accessible documents from scratch proves more efficient and reliable than remediation.

info

This functionality is part of the Toolbox add-on, a separate SDK that you can use with the same license key as the Pdftools SDK. To use and integrate this add-on, review Getting started with the Toolbox add-on and Toolbox add-on code samples.

Quick start

Download the full sample now in C#, Java, and Python.

For background on PDF accessibility concepts and the importance of logical structure, review A primer on PDF accessibility.

Adding logical structure to an existing PDF involves analyzing the current content and selectively applying tags to create a hierarchical structure. Steps to add logical structure to an existing PDF:

  1. Opening the existing document
  2. Creating the logical structure tree
  3. Identifying and copying content
  4. Tagging specific content elements
  5. Full example

Before you begin

Opening the existing document

To begin, open the existing PDF and create a new output document. Since you work with existing content, copy the document-wide metadata before remediating individual pages.

While the main work involves applying a logical structure to the PDF visual elements, specific metadata must conform to the PDF/Universal Accessibility (PDF/UA) standard. These requirements include setting a valid document language and a title. You must also configure the PDF to instruct viewers to display the document title in the title bar. Finally, once the entire PDF has been successfully tagged, you can declare it as PDF/UA compliant.

The following code shows the high-level process for opening the input document, creating the output document, copying document-wide data, and ensuring all required metadata fields have suitable values.

// Open input document
using Stream inStream = new FileStream(inPath, FileMode.Open, FileAccess.Read);
using Document inDoc = Document.Open(inStream, null);

// Create output document
using Stream outStream = new FileStream(outPath, FileMode.Create, FileAccess.ReadWrite);
using Document outDoc = Document.Create(outStream, inDoc.Conformance, null);

CopyDocumentData(inDoc, outDoc);

// Set required metadata for PDF/UA compliance
outDoc.Language = "en";
outDoc.Metadata.Title = "TaggedPDF";
outDoc.ViewerSettings.DisplayDocumentTitle = true;

// Remediating a PDF manually requires knowledge of the input PDF's layout.
// This example assumes that the input is a single-page PDF.
if (inDoc.Pages.Count != 1)
throw new InvalidOperationException("Unexpected number of pages: " + inDoc.Pages.Count);

RemediatePage(inDoc, 0, outDoc);

outDoc.SetPdfUaConformant();

Creating the logical structure tree

The RemediatePage method handles the page-level remediation process. It begins by creating a new, empty page in the output document that matches the dimensions of the original page.

private static void RemediatePage(Document inDoc, int pageIndex, Document outDoc)
{
Page inPage = inDoc.Pages[pageIndex];
Page outPage = Page.Create(outDoc, inPage.Size);

CopyAndTagContent(inPage, outPage, outDoc);

outDoc.Pages.Add(outPage);
}

Instantiating a Tree object creates the logical structure on the first call and returns the existing tree on all later calls. The root node is always of type DocumentNode. For this example, the code wraps the entire page in a section node (Sect) to define the high-level logical structure.

Next, a ContentExtractor retrieves content elements from the source page, and a ContentGenerator re-creates them on the destination page. The for-loop iterates through each element from the source page, identifies it, applies the correct tag, and then copies it to the destination.

private static void CopyAndTagContent(Page inPage, Page outPage, Document outDoc)
{
// Create or retrieve the structure tree.
var structTree = new Tree(outDoc);
var documentNode = structTree.DocumentNode;

// Create a section node for the page content and add it to the document structure.
var section = new Node("Sect", outDoc, outPage);
documentNode.Children.Add(section);

ContentExtractor extractor = new ContentExtractor(inPage.Content);
using ContentGenerator generator = new ContentGenerator(outPage.Content, false);

foreach (ContentElement element in extractor)
{
// The logic to identify, tag, and add the element to the generator goes here.
// This process is detailed in the following examples
// and is specific to the document's layout.

// PDF/UA requires all content to be tagged.
// Throw an exception for any unrecognized element to avoid untagged content.
throw new InvalidOperationException("Unexpected content element found.");
}
}

Identifying and copying content

Identifying content elements to apply the correct tag requires specific knowledge of the document’s layout. You can use specific properties for this identification. For instance, text elements have identifiable content, while non-textual elements like images have identifiable positions and sizes on the page (for example, by their bounding box).

The following code demonstrates these two approaches. This logic goes inside the for each loop shown earlier.

if (element is TextElement textElement)
{
if (textElement.Text[0].Text == "This is a properly tagged heading")
{
CopyAndTagTextElement(textElement, section, generator, outPage, outDoc, "H1");
continue;
}
if (textElement.Text[0].Text.StartsWith("This is a properly tagged paragraph."))
{
CopyAndTagTextElement(textElement, section, generator, outPage, outDoc, "P");
continue;
}
// Add more logic here to identify other text elements.
}
else if (element is ImageElement imageElement)
{
var bbox = imageElement.Transform.TransformRectangle(element.BoundingBox);
if (Math.Abs(bbox.BottomLeft.X - 56.7) < 0.5
&& Math.Abs(bbox.BottomLeft.Y - 600.489) < 0.5
&& Math.Abs(bbox.TopRight.X - 152.489) < 0.5
&& Math.Abs(bbox.TopRight.Y - 696.489) < 0.5)
{
CopyAndTagImageElement(imageElement, documentNode, generator, outPage, outDoc, "PdfTools AG Logo");
continue;
}
// Add more logic here to identify other image elements.
}
// Remember, if an element is not identified by the logic above, the surrounding
// code will throw an exception to ensure all content is tagged.

Tagging specific content elements

The key to successful remediation involves identifying which content elements need structure tags and applying the suitable tags. The following examples show how to handle different types of content:

Tagging text elements

When tagging text, the main goal involves assigning the correct semantic role, such as a heading H1, a paragraph P, or a list item LI. Providing ActualText is also crucial for ensuring that screen readers and other assistive technologies can accurately interpret the content.

private static void CopyAndTagTextElement(
TextElement inElement, Node section, ContentGenerator generator,
Page outPage, Document outDoc, string tag)
{
// Create a text structure node (e.g., "H1"), set its ActualText
// for accessibility, and add it to the parent section.
Node headerElement = new Node(tag, outDoc, outPage);
headerElement.ActualText = inElement.Text[0].Text;
headerElement.Language = "en";

section.Children.Add(headerElement);

// Wrap the original text element in the new tag and add it to the page content.
generator.TagAs(headerElement);
generator.AppendContentElement(ContentElement.Copy(outDoc, inElement));
generator.StopTagging();
}

Tagging image elements

For images, the most important accessibility feature involves providing descriptive alternate text. Screen readers read this text aloud for users who can’t see the image. Images typically are tagged as Figure elements, and you need to define their bounding box to associate the tag with the correct location on the page.

private static void CopyAndTagImageElement(
ImageElement inElement, Node documentNode, ContentGenerator generator,
Page outPage, Document outDoc, string alternateText)
{
// Create a "Figure" structure node, set its accessibility properties
// (Alternate Text, Bounding Box), and add it to the document's structure tree.
Node imgNode = new Node("Figure", outDoc, outPage);
imgNode.AlternateText = alternateText;
imgNode.Language = "en";

Quadrilateral bbox = inElement.Transform.TransformRectangle(inElement.BoundingBox);
Rectangle rectangle = new Rectangle();
rectangle.Left = bbox.BottomLeft.X;
rectangle.Bottom = bbox.BottomLeft.Y;
rectangle.Right = bbox.TopRight.X;
rectangle.Top = bbox.TopRight.Y;
imgNode.BoundingBox = rectangle;
imgNode.SetStringAttribute("O", "Layout");

documentNode.Children.Add(imgNode);

// Wrap the original image element in the new tag and add it to the page content.
generator.TagAs(imgNode);
generator.AppendContentElement(ContentElement.Copy(outDoc, inElement));
generator.StopTagging();
}

Best practices for remediation

When adding logical structure to existing PDF documents, consider these best practices:

  • Content analysis: Use heuristics to identify content types, but always validate results manually. Automated detection of headings, paragraphs, and other elements has inherent limitations.
  • Quality assurance: Always validate the remediated PDF with accessibility checkers and screen readers to make sure the logical structure works as intended.
  • Alternative text: Meaningful alternative text for images can’t be automated. Always perform a human review before claiming PDF/UA compliance.

Full example

Download the complete remediation example:

Next steps

After adding logical structure to your PDF:

  • Validate the structure: Use PDF accessibility checkers to verify the logical structure.
  • Test with assistive technology: Ensure screen readers can navigate the document correctly.
  • Review alternative text: Manually verify that all images have meaningful descriptions.
  • Check reading order: Confirm the logical structure follows the intended reading flow.
info

Remember: adding logical structure to existing PDF documents requires technical skill, but true accessibility requires human understanding and validation. Always perform manual testing to make sure the remediated document serves its intended users effectively.