PDF/UA tagging
htmlbag emits PDF logical-structure elements when the underlying
document is set to document.FormatPDFUA (PDF/UA-1, ISO 14289-1, on
PDF 1.7) or document.FormatPDFUA2 (PDF/UA-2, ISO 14289-2, on PDF
2.0). Tagging is automatic — the tree of structure elements is
built as content is laid out, and each VList carries a
back-reference to the structure node it should attach to.
Activation
Set the format on the frontend.Document before constructing the
CSSBuilder:
// PDF/UA-1 (ISO 14289-1)
fd.Doc.Format = document.FormatPDFUA
// or PDF/UA-2 (ISO 14289-2, PDF 2.0)
fd.Doc.Format = document.FormatPDFUA2
cb, _ := htmlbag.New(fd, css)After New(), cb.enableTagging is true, cb.structureRoot
points to a root structure element (created on fd.Doc if not
already present), and cb.structureCurrent tracks the current
parent during walk.
For UA-2, New() also declares the PDF 2.0 Standard Structure
Namespace and an HTML5 namespace with a complete RoleMapNS on
fd.Doc, so every emitted structure element is properly
namespace-qualified (required by ISO 14289-2 §8.2.4).
Element-to-role mapping
Each HTML tag maps to a canonical PDF Standard Structure role; the final emission depends on the active format.
| HTML | PDF SSN (UA-1) | HTML5 NS (UA-2) |
|---|---|---|
<h1> … <h6> |
H1 … H6 |
h1 … h6 |
<p>, <pre> |
P |
p |
<div> |
Div |
div |
<span> |
Span |
span |
<a> |
Link |
a |
<img> |
Figure |
img |
<figure> |
Figure |
figure |
<table> |
Table |
table |
<thead>/<tbody>/<tfoot> |
THead/TBody/TFoot |
thead/tbody/tfoot |
<tr>, <th>, <td> |
TR, TH, TD |
tr, th, td |
<ul>/<ol> |
L |
ul/ol |
<li> |
LI |
li |
<blockquote> |
BlockQuote |
blockquote |
<code> |
Code |
code |
<section> |
Sect |
section |
<article> |
Art |
article |
<math> |
Formula |
Formula |
Roles that have no HTML5 equivalent (Document, Note, LBody,
Lbl, L, Part, Formula) stay in the PDF 2.0 Standard Structure
Namespace under UA-2.
Internally, canonicalRoleForTag resolves the HTML tag to the
canonical PDF SSN name, then newSE(canonical, format) builds the
StructureElement with the right Role and NS. The two helpers
live in htmlbag/tagging.go.
Footnotes attach as Note structure elements.
Repeated table headers
When a table breaks across pages, the repeated header rows on
continuation pages are tagged as artifacts (not as TH again) to
avoid duplication in the structure tree.
Form XObjects (imported PDFs)
A <img src="…pdf"> (an imported PDF page) is wrapped as a Form
XObject with /StructParent and an OBJR-entry in the parent
Figure’s /K array (PDF 1.7 §14.7.4.4 + PDF/UA-1 §7.1 Note 1).
This stops Acrobat’s tag inspector from exploding the imported
content stream’s path operators into a “Pfad / Path” list under
the Figure tag.
Mathematics (Formula)
An inline <math> element (see Mathematics (MathML))
is tagged as a Formula structure element with two accessibility
payloads:
/Alt— a plain-text fallback. It comes from the MathMLalttextattribute when the author supplies one, otherwise from a plain-text rendering of the token content (<mi>/<mn>/<mo>concatenated in document order)./AF— under PDF/UA-2 only — the MathML source embedded as an associated file (application/mathml+xml,/AFRelationship /Supplement, PDF 2.0 §14.13). Thexmlns="http://www.w3.org/1998/Math/MathML"namespace is re-attached to the root<math>so the file is self-describing XML. A MathML-aware reader consumes this for full semantics; PDF/UA-1 (PDF 1.7) emits theFormulaelement and/Altbut no associated file, since associated files are a PDF 2.0 feature.
Because a formula usually sits inside a sentence, the renderer splits
the surrounding paragraph’s marked content so the reading order is
text · formula · text, with the Formula element nested between the
two text runs of its paragraph:
<P>
├─ MCID 0 "The relation " (text before)
├─ <Formula> /Alt "a 2" /AF → formula.mml
│ └─ MCID 1 (formula glyphs)
└─ MCID 2 " matters." (text after)The split relies on a /K array serialized in document reading order:
each marked-content and object reference carries a document-wide
reading-order stamp, and a child element inherits the smallest stamp in
its subtree, so the three kinds of /K entry (marked content, object
references, child elements) interleave correctly. The paragraph’s own
ActualText is suppressed when it contains a formula — ActualText
would otherwise replace the element’s entire content and hide the
formula’s /Alt from assistive technology.
Outline destinations under UA-2
PDF/UA-2 §8.8 requires intra-document destinations to be structure
destinations, not page destinations. When the document is
FormatPDFUA2, glu/markdown’s outline generator emits
/Dest [ <SE-objref> /Fit ] for each heading instead of
[ <page-objref> /Fit ]. The link from each HeadingEntry to its
backing StructureElement is set in vlistbuilder.go at the time
the heading SE is constructed.
Custom roles
The mapping in canonicalToHTML5 (htmlbag/tagging.go) is the
single source of truth for the PDF SSN ↔ HTML5 correspondence. To
add a custom mapping, extend that map; html5RoleMap() builds the
UA-2 RoleMapNS from it automatically.
Verification
Both profiles are validated against veraPDF as part of the showcase examples:
verapdf --flavour ua1 boxesandglue-examples/glu/xslfo/10-pdfua/result.pdf
verapdf --flavour ua2 boxesandglue-examples/glu/xslfo/11-pdfua2/result.pdfBoth report isCompliant=true.
The MathML showcase
boxesandglue-examples/glu/html/mathml
renders as PDF/UA-2 and passes pdfa11y, including the dedicated
MathML checks (Formula has a math representation; the associated file
declares Subtype = application/mathml+xml, AFRelationship = /Supplement,
and is well-formed XML rooted at a namespaced <math>):
pdfa11y boxesandglue-examples/glu/html/mathml/result.pdf
# Verdict: PASS