Accessibility
A PDF page is a set of drawing instructions. Nothing in it says that one run of
glyphs is a heading and the next is a table cell, so a screen reader has no
reliable way to read the page in a sensible order or describe a picture. A tagged
PDF adds a second, parallel description of the document: a tree of structure
elements (Document, H1, P, Table, TR, TD, L, LI, Figure) whose
leaves point back at the marked content on the page. Assistive technology walks
that tree instead of the page, which is where reading order, alternate text, table
header association and language all come from.
The tree lives in the document catalog as StructTreeRoot. Each element is a
dictionary with a structure type /S, a parent, the page it appears on /Pg, and
children that are either more elements or marked content identifiers. A page’s
content stream marks each tagged region with BDC/EMC and an MCID; the
ParentTree maps those MCIDs back to structure elements. UniPDF models this with
model.StructTreeRoot and model.KDict.
What the creator does for you
Call TagComponents(true) on a Creator and it starts building the tree itself:
c := creator.New()
c.TagComponents(true)
c.SetLanguage("en-US")From that point every component you Draw is tagged. The creator creates a
StructTreeRoot with a Document element at its root, allocates a per-page MCID
for each component, wraps the component’s content in marked content operators,
assigns /Pg, builds the ParentTree, sets the StructParents key on each page,
and writes /MarkInfo << /Marked true >> into the catalog.
Components carry a default structure type, so a correct tree comes out without any per-component configuration:
| Component | Default structure type |
|---|---|
StyledParagraph | P |
TextChunk | Span |
Image, Chart, GraphicSVG | Figure |
Table, Grid | Table, with TR rows and TD cells |
List | L, with LI items |
Division | Div |
TOC | TOC |
Chapter | Sect, with the heading as H |
SetStructureType overrides the default on any component that needs a different
role, most often turning a paragraph into H1 through H6.
TagComponents has to be called before the components you want tagged. Anything
drawn before it is untagged, and an untagged component in an otherwise tagged
document is a PDF/UA failure.
What you have to do yourself
Everything that needs a human decision. UniPDF can tell that a component is an image; it cannot tell what the image shows.
Alternate text for figures, and the ActualText replacement for content whose
glyphs don’t spell what they mean, come from KDict.Alt and KDict.ActualText,
which you set. Heading levels are yours to choose. So is reading order, which is
the order you draw in - if you place blocks with absolute positioning in a
different order from how they should be read, the tree records the drawing order.
Language goes on the catalog through SetLanguage, and per-element overrides go on
KDict.Lang.
Content that carries no meaning has to be kept out of the tree rather than tagged
badly. Block.MarkAsArtifact(artifactType) wraps the block’s content in an
/Artifact marked content sequence and excludes it from the structure tree, which
is what running headers, footers and decorative rules need. Block is also the one
component that cannot be tagged at all: its SetStructureType,
SetMarkedContentID and GenerateKDict exist only to satisfy the Drawable
interface and do nothing.
Annotations, form fields and links need their own treatment, because an annotation is not page content and is not reached through an MCID. Each gets an object reference child in the tree instead. The guides below cover each case.
PDF/UA validation and repair
v5 adds model/pdfua, which implements PDF/UA-1 (ISO 14289-1) and PDF/UA-2
(ISO 14289-2) as standard profiles. pdfua.NewProfile1(nil) and
pdfua.NewProfile2(nil) return profiles that both validate and apply; the options
structs (Profile1Options, Profile2Options) carry a DefaultLanguage used when
the document declares none, and XMP marshalling options.
Validation runs against a model.CompliancePdfReader through
ValidateStandard, and repair runs through PdfWriter.ApplyStandard. The split
matters: the package documentation is explicit that applying a PDF/UA profile can
only fix mechanical issues such as identification metadata, viewer preferences and
catalog entries. A complete and correct structure tree, alternate descriptions and
reading order have to be authored, and ApplyStandard returns an error when the
document lacks the semantic foundation it cannot synthesize. Tagging is not
something you can bolt on afterwards.
PDF/UA-2 documents also need the PDF 2.0 structure namespace, which is
c.SetStructureNamespace(model.StructureNamespacePDF2). That stamps a shared /NS
on every generated structure element, declares the namespace on the
StructTreeRoot, and writes the file as PDF 2.0. It only takes effect when a
structure tree is actually produced.
Working examples for both parts live in the pdfua folder of the examples
repository: pdfua_validate_standard.go, pdfua_apply_standard.go,
pdfua2_validate_standard.go and pdfua2_apply_standard.go.
Where to look
| Guide | What it covers |
|---|---|
| Add Image Alternative Text | Attaching Alt to a Figure element so a screen reader can describe an image. |
| Tagging Tables | Table, TR, TH and TD elements, and associating header cells with data cells. |
| Tagging Lists | L, LI, Lbl and LBody, and how nesting is represented. |
| Tagging Links | Link elements, the object reference to the link annotation, and its Contents text. |
| Tagging Forms | Form elements for text fields and buttons, and the TU alternate name widgets need. |
| Tagging Annotations | Annot elements for markup annotations that are not links or widgets. |
| Tagging Grids | THead, TBody and TFoot sections driven by row sections, and where Grid differs from Table. |
| Set Language Identifier | The catalog Lang entry and per-element language overrides. |
| Copy Page with Accessibility | Copying pages between documents while keeping their part of the structure tree intact. |