Foxit PDF SDK Auto-Tagging

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().

Machine
Foxit PDF SDK version
100%
Upload a PDF to auto-tag with the Foxit PDF SDK and visualize its accessibility structure

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).

Processing PDF...