Auto-Tag PDFs for Accessibility
Upload a PDF and the Foxit PDF SDK Accessibility add-on analyzes the layout and builds a full structure tree — headings, paragraphs, figures, tables, lists — then audits the result against Section 508. Explore every tag visually, page by page, and download the tagged PDF. Powered by TaggedPDF::StartTagDocument().
How it works
The Foxit PDF SDK Accessibility add-on’s TaggedPDF::StartTagDocument() auto-analyzes each page’s layout and builds a structure tree — headings, paragraphs, figures, tables, and lists — written into the PDF as standard tagged-PDF structure. A TaggedPDFCallback reports every element as it is created, which is what drives the per-page progress readout while tagging runs.
A companion extract binary (foxit_extract_tags.cpp) then walks the structure tree and runs an 11-check Section 508 audit: tagged, PDF/UA identifier, title, language, alt text, headings, table headers, tab order, annotation tagging, fonts, and unicode mapping. A third binary (foxit_render_page.cpp) renders page previews. The in-browser overlays map each tag onto its region of the page.
Pick the machine and the SDK version. The identical sources (foxit_autotag.cpp, foxit_extract_tags.cpp, foxit_render_page.cpp) are compiled four ways — Kramer (Windows) and Jerry (Linux), each on Foxit PDF SDK 11.0 and 11.1 — a controlled 2×2 for separating SDK-release differences from OS/environment differences. Same code, same input; only the OS or the SDK version changes. And for auto-tagging, the tagging output proves byte-identical across OS for a given SDK version.
Sample implementation code
The auto-tag pipeline this demo runs — initialize the library with an Accessibility-enabled license, load the document, attach a progress callback, drive StartTagDocument() to completion, then save the tagged PDF — in each SDK language binding. Condensed for clarity.
#include "common/fs_common.h"
#include "pdf/fs_pdfdoc.h"
#include "addon/accessibility/fs_taggedpdf.h"
using namespace foxit;
using namespace foxit::common;
using namespace foxit::pdf;
using namespace foxit::addon::accessibility;
// The SDK calls Report() for every structure element it creates
// (region, paragraph, figure, table, list item, artifact...).
class TagReporter : public TaggedPDFCallback {
public:
int paragraphs = 0, figures = 0, tables = 0, lists = 0;
void Release() override { delete this; }
void Report(ReportCategory category, ReportConfidence confidence,
int page_index, const RectF& rect) override {
switch (category) {
case e_ReportCategoryParagraph: paragraphs++; break;
case e_ReportCategoryFigure: figures++; break;
case e_ReportCategoryTable: tables++; break;
case e_ReportCategoryListItem: lists++; break;
default: break;
}
// page_index is what drives this demo's per-page progress readout.
}
};
int main() {
// The license must include the Accessibility module.
Library::Initialize(sn, key);
PDFDoc doc(L"input.pdf");
if (doc.Load() != e_ErrSuccess) return 1;
TaggedPDF tagger(doc);
TagReporter* reporter = new TagReporter();
tagger.SetCallback(reporter); // per-element progress reporting
// Auto-analyze the layout and build the structure tree.
Progressive progress = tagger.StartTagDocument(NULL);
while (progress.Continue() == Progressive::e_ToBeContinued) {}
// Flag figures the engine could not describe (need manual alt text).
for (int f = 0; f < tagger.GetFigureCount(); ++f)
if (tagger.GetFigureAlternateText(f).IsEmpty())
{ /* report figure f on page tagger.GetFigurePageIndex(f) */ }
doc.SaveAs(L"tagged.pdf", PDFDoc::e_SaveFlagNoOriginal);
Library::Release();
return 0;
}
using foxit;
using foxit.common;
using foxit.pdf;
using foxit.addon.accessibility;
// The SDK calls Report() for every structure element it creates
// (region, paragraph, figure, table, list item, artifact...).
class TagReporter : TaggedPDFCallback {
public int Paragraphs, Figures, Tables, Lists;
public override void Report(ReportCategory category,
ReportConfidence confidence, int pageIndex, RectF rect) {
switch (category) {
case ReportCategory.e_ReportCategoryParagraph: Paragraphs++; break;
case ReportCategory.e_ReportCategoryFigure: Figures++; break;
case ReportCategory.e_ReportCategoryTable: Tables++; break;
case ReportCategory.e_ReportCategoryListItem: Lists++; break;
}
// pageIndex is what drives this demo's per-page progress readout.
}
}
// The license must include the Accessibility module.
Library.Initialize(sn, key);
using var doc = new PDFDoc("input.pdf");
doc.Load(null);
var tagger = new TaggedPDF(doc);
tagger.SetCallback(new TagReporter()); // per-element progress reporting
// Auto-analyze the layout and build the structure tree.
var progress = tagger.StartTagDocument(null);
while (progress.Continue() == Progressive.State.e_ToBeContinued) { }
// Flag figures the engine could not describe (need manual alt text).
for (int f = 0; f < tagger.GetFigureCount(); f++)
if (string.IsNullOrEmpty(tagger.GetFigureAlternateText(f)))
Console.WriteLine($"Figure {f} needs alt text");
doc.SaveAs("tagged.pdf", (int)PDFDoc.SaveFlags.e_SaveFlagNoOriginal);
Library.Release();
import com.foxit.sdk.common.Library;
import com.foxit.sdk.common.Progressive;
import com.foxit.sdk.common.fxcrt.RectF;
import com.foxit.sdk.pdf.PDFDoc;
import com.foxit.sdk.addon.accessibility.TaggedPDF;
import com.foxit.sdk.addon.accessibility.TaggedPDFCallback;
// The SDK calls report() for every structure element it creates
// (region, paragraph, figure, table, list item, artifact...).
class TagReporter extends TaggedPDFCallback {
int paragraphs, figures, tables, lists;
@Override public void release() {}
@Override public void report(int category, int confidence,
int pageIndex, RectF rect) {
if (category == e_ReportCategoryParagraph) paragraphs++;
else if (category == e_ReportCategoryFigure) figures++;
else if (category == e_ReportCategoryTable) tables++;
else if (category == e_ReportCategoryListItem) lists++;
// pageIndex is what drives this demo's per-page progress readout.
}
}
// The license must include the Accessibility module.
Library.initialize(sn, key);
PDFDoc doc = new PDFDoc("input.pdf");
doc.load(null);
TaggedPDF tagger = new TaggedPDF(doc);
tagger.setCallback(new TagReporter()); // per-element progress reporting
// Auto-analyze the layout and build the structure tree.
// (`continue` is a Java keyword, so the binding names it resume().)
Progressive progress = tagger.startTagDocument(null);
while (progress.resume() == Progressive.e_ToBeContinued) { }
// Flag figures the engine could not describe (need manual alt text).
for (int f = 0; f < tagger.getFigureCount(); f++)
if (tagger.getFigureAlternateText(f).isEmpty())
System.out.println("Figure " + f + " needs alt text");
doc.saveAs("tagged.pdf", PDFDoc.e_SaveFlagNoOriginal);
Library.release();
from FoxitPDFSDKPython3 import *
# The SDK calls Report() for every structure element it creates
# (region, paragraph, figure, table, list item, artifact...).
class TagReporter(TaggedPDFCallback):
def __init__(self):
super().__init__()
self.paragraphs = self.figures = self.tables = self.lists = 0
def Release(self):
pass
def Report(self, category, confidence, page_index, rect):
if category == TaggedPDFCallback.e_ReportCategoryParagraph:
self.paragraphs += 1
elif category == TaggedPDFCallback.e_ReportCategoryFigure:
self.figures += 1
elif category == TaggedPDFCallback.e_ReportCategoryTable:
self.tables += 1
elif category == TaggedPDFCallback.e_ReportCategoryListItem:
self.lists += 1
# page_index is what drives this demo's per-page progress readout.
# The license must include the Accessibility module.
Library.Initialize(sn, key)
doc = PDFDoc("input.pdf")
assert doc.Load("") == e_ErrSuccess
tagger = TaggedPDF(doc)
reporter = TagReporter()
tagger.SetCallback(reporter) # per-element progress reporting
# Auto-analyze the layout and build the structure tree.
progress = tagger.StartTagDocument(None)
while progress.Continue() == Progressive.e_ToBeContinued:
pass
# Flag figures the engine could not describe (need manual alt text).
for f in range(tagger.GetFigureCount()):
if not tagger.GetFigureAlternateText(f):
print(f"Figure {f} needs alt text")
doc.SaveAs("tagged.pdf", PDFDoc.e_SaveFlagNoOriginal)
Library.Release()
// Foxit PDF SDK for Node.js (naming follows the Node binding conventions)
const { Library, PDFDoc, Progressive, accessibility } =
require('@foxitsoftware/foxit-pdf-sdk-node');
const { TaggedPDF, TaggedPDFCallback } = accessibility;
// The license must include the Accessibility module.
Library.initialize(sn, key);
const doc = new PDFDoc('input.pdf');
doc.load(null);
// The SDK invokes report() for every structure element it creates
// (region, paragraph, figure, table, list item, artifact...).
const counts = { paragraphs: 0, figures: 0, tables: 0, lists: 0 };
const reporter = new TaggedPDFCallback({
report(category, confidence, pageIndex, rect) {
// tally by category; pageIndex drives the per-page progress readout
}
});
const tagger = new TaggedPDF(doc);
tagger.setCallback(reporter); // per-element progress reporting
// Auto-analyze the layout and build the structure tree.
// (`continue` is a reserved word, so the binding names it resume().)
const progress = tagger.startTagDocument(null);
while (progress.resume() === Progressive.e_ToBeContinued) { /* keep going */ }
// Flag figures the engine could not describe (need manual alt text).
for (let f = 0; f < tagger.getFigureCount(); f++) {
if (!tagger.getFigureAlternateText(f)) console.log(`Figure ${f} needs alt text`);
}
doc.saveAs('tagged.pdf', PDFDoc.e_SaveFlagNoOriginal);
Library.release();
Samples are condensed for clarity — production code should check every return value and wrap SDK calls in the binding’s exception handling. Exact class/module names can differ slightly between SDK releases; the C++ tab matches the real source this demo runs (kept alongside each binary as foxit_autotag.cpp in server_11_0/ and server_11_1/, next to foxit_extract_tags.cpp and foxit_render_page.cpp).