Table of Contents

PDF/A and tagged PDF

Two independent switches on the same PDF writer. PDF/A-2b makes a file suitable for long-term archiving. Tagged PDF adds a structure tree, so screen readers get headings, lists, tables and reading order. Use either, or both.

Both at once

using DocWright;
using DocWright.Renderers.Pdf;

var options = new ConvertOptions
{
    TaggedPdf = true,            // structure tree for screen readers
    DefaultLanguage = "en-NZ",   // used only when the document declares no language
};
options.Metadata.Conformance = PdfConformanceLevel.PdfA2b;
options.Metadata.Title = "Letterhead sample";
options.Metadata.CreationDate = new DateTimeOffset(2026, 10, 1, 9, 0, 0, TimeSpan.Zero);

using (FileStream input = File.OpenRead("letterhead.docx"))
using (FileStream output = File.Create("letterhead-archive.pdf"))
{
    converter.Convert(input, output, options);
}

Output (the sample then inspects the file, and checks what happens without a date)

PDF/A output intent:    True
Structure tree present: True
PDF/A-2b: PDF/A-2b requires a creation date, and DocWright never supplies one implicitly because ambient timestamps would break byte-identical output. Set PdfRenderMetadata.CreationDate to the timestamp the document should carry.

The file this produces passes veraPDF as PDF/A-2b.

PDF/A-2b

Set Metadata.Conformance = PdfConformanceLevel.PdfA2b. The writer then adds an sRGB output intent, an XMP metadata packet, and a file identifier computed from the file's own bytes. Page content, fonts and images are produced by the same code as ordinary PDF.

Important

PDF/A requires a creation date, and DocWright will not invent one. Reading the clock would make output differ from run to run. Without Metadata.CreationDate, conversion throws OutputConformanceException:

using DocWright;
using DocWright.Core;
using DocWright.Renderers.Pdf;

var noDate = new ConvertOptions();
noDate.Metadata.Conformance = PdfConformanceLevel.PdfA2b;   // but no CreationDate

try
{
    using FileStream input = File.OpenRead("letterhead.docx");
    converter.Convert(input, Stream.Null, noDate);
}
catch (OutputConformanceException ex)
{
    Console.WriteLine($"{ex.ConformanceLevel}: {ex.Message}");
}

PDF/A-1, PDF/A-3, PDF/A-4 and PDF/UA are not offered.

Tagged PDF

TaggedPdf = true adds a structure tree:

  • Paragraphs and headings. H1 to H6 come from each paragraph's outline level, which Word's heading styles set.
  • Lists and tables, including header cells, column spans and row spans. A table nested in a cell is its own table.
  • Figures, with alternative text from the picture's description in Word (Picture Format › Alt Text).
  • Links, tagged with their text as the description.
  • Artifacts. Headers, footers and repeated table header rows are marked as artifacts, so they are not read out on every page.

Reading order comes from the layout, not from where things sit on the page, so multi-column text and floating objects read in the right order.

The language is yours to supply. DocWright never guesses a language from the text. A wrong guess makes a screen reader mispronounce everything. DefaultLanguage applies when the document itself declares none.

Note

Footnotes and list labels are not tagged yet, so full PDF/UA conformance is not claimed. Run over the whole test corpus, veraPDF's PDF/UA profile reports only one remaining finding: a missing document title. Set Metadata.Title and that one is covered too.

Checking the result

Validate with veraPDF, the reference PDF/A and PDF/UA validator:

verapdf --flavour 2b letterhead-archive.pdf