Skip to main content

M28 — PDF 2.0 conformance: PDF/UA-2, WTPDF, PDF/A-4

State: to do — Depends on: M03, M13, M17, M20 — On the PDF 2.0 writer by ADR 40, which kept PDF/UA-2, Well-Tagged PDF and PDF/A-4 for this milestone

Goal​

Generate accessible and archival documents on PDF 2.0 — PDF/UA-2 and Well-Tagged PDF on the PDF 2.0 structure namespace, PDF/A-4, 4f and 4e — with their identifications, their PDF Declarations and structure destinations for every internal link; read and merge structure trees that mix the PDF 1.7 and 2.0 namespaces as ISO/TS 32005 says; and judge a third party's PDF/UA-2 and Well-Tagged PDF claims on M20's engine.

ADR 40 put PDF/UA-1 first because Factur-X needs PDF/A-3, which is a PDF 1.7 format, and an accessible invoice must carry both. Everything else that asks for accessible or archival PDF is moving to PDF 2.0: ISO 14289-2 (PDF/UA-2, 2024) and the PDF Association's Well-Tagged PDF 1.0 (2024) are what accessibility checkers now implement, and ISO 19005-4 (PDF/A-4, 2020) is the archival part built on PDF 2.0, with 4f carrying attachments as part 3 did. The structure M13 writes is correct PDF 1.7 and, in 2.0 output, sits in the default namespace without claiming anything; this milestone writes it in PDF 2.0's own namespace, with the types PDF 2.0 added — Em, Strong, Aside, Title, FENote, headings past H6 — and says so in the file. The corpus holds PDF/A-4 and 4f documents from veraPDF and WeasyPrint and one hand-written tagged PDF 2.0 file, and no PDF/UA-2 or Well-Tagged PDF document at all: what the library writes here is judged first by veraPDF, then by documents from other producers as the corpus gains them.

Scope​

In:

  • the PDF 2.0 structure namespace in the core's structure writer (M13): namespace dictionaries, /NS on elements, /Namespaces on the structure tree root, RoleMapNS, the PDF 2.0 standard types and attributes — NoteType, Ref, ContinuedList, the NSO owner —, the Artifact structure element, and pronunciation hints (PhoneticAlphabet, Phoneme);
  • ISO/TS 32005: the PDF 1.7 and PDF 2.0 namespaces in one tree — the containment rules enforced by the builder in 2.0 output, the mapping between the two namespaces as data, used by the reader (M15), by the merge (M06) and by validation;
  • PDF/UA-2 output from the builder and from the HTML engine: M13's emitter writing the 2.0 namespace, PDF 2.0's types where HTML has an equivalent, the 1.7 types PDF 2.0 left out kept in the 1.7 namespace, the pre-write checks under PdfConformancePolicy, and pdfuaid part 2 with its revision;
  • structure destinations for every internal link, bookmark, contents entry and note call, beside a page destination for readers that predate them;
  • forms and annotations under PDF/UA-2: M17's fields as Form in the 2.0 namespace, M11's annotations as Annot, links as Link;
  • PDF Declarations: read and written through M14's XMP model, their claim data from the caller, their extension schema under PDF/A-1 to 3;
  • Well-Tagged PDF 1.0, reuse and accessibility, identified by declarations;
  • PDF/A-4, 4f and 4e generation on the 2.0 writer: the identification, /Info reduced, output intents, embedded files by level, 4e's 3D and RichMedia preserved from a received document and never authored (ADR 37), and PDF/A-4 as a merge target under M20's preservation;
  • validation profiles for PDF/UA-2 and Well-Tagged PDF in AdCodicem.Pdf.Conformance, on M20's public API, with review items for the conditions only a person can judge, as the roadmap's line says, since M20 leaves these profiles to this milestone;
  • the tool: html2pdf --pdf-ua 2, --wtpdf reuse|accessibility, --pdf-a 4|4f|4e; validate --profile pdf-ua-2|wtpdf-reuse|wtpdf-accessibility; metadata declarations.

Out, explicitly:

  • MathML — layout, the MathML namespace's elements in our output, formulae as associated files — M30. The MathML namespace is recognized here when reading and role-mapping;
  • ruby and vertical writing — M30; warichu — not planned: no CSS specification lays it out (M30);
  • Factur-X on PDF/A-4f — read and reported (M14), never written: Factur-X requires PDF/A-3, and ADR 40 names its acceptance of 4f as what would reopen the choice;
  • PDF/UA-2, WTPDF or PDF/A-4 together with PDF/A-1 to 3 or PDF/UA-1 — refused when the options are built: different base versions of ISO 32000;
  • converting received documents to PDF/A-4 — not planned (M21 converts to parts 2 and 3);
  • authoring 3D, RichMedia or multimedia for 4e — never (ADR 37); 4e keeps what a received document carried;
  • heuristic tagging of untagged documents and editing a received document's structure — neither in the roadmap;
  • PDF/X-6, the PDF 2.0 print part — M29, if a print shop asks for it;
  • ISO/TS 32001 to 32004 (hashes, ECC, AES-GCM, the integrity MAC) — M16 reads them, M27 verifies with them;
  • declaring a person's review: a declaration's claim data says who claimed and when, as the caller supplies it; the library never claims on a person's behalf that the human conditions were judged.

Dependencies. The roadmap gives M03 (the 2.0 writer and its version policy), M13 (the structure writer and the HTML emitter) and M20 (the public rule engine, claims, verdicts, review items, merge preservation and the PDF/A-4 profile). Through M13 come M12's layout, links, bookmarks and contents (M12.6) and footnotes (M12.7). Also used: M14's XMP model and associated files, M15's structure reader, M06's merge, M09's pagination artifacts in 2.0 output, M11's annotations — all earlier in the chain — and M17's form fields, which M17 itself defers to this milestone under PDF/UA-2, hence M17 in the roadmap's table.

Design​

Where it lives​

PartWhereWhy
Namespaces, the 2.0 types and attributes, RoleMapNS, containment, the ISO/TS 32005 tableCore, Structure/The structure writer is the core's (M13); the table serves the reader and the merge as well
Namespace resolution when reading; mapping a 1.7 part into a 2.0 treeCore, Structure/ (M15's reader) and Documents/ (M06's merge)
Structure destinationsCore (the actions and destinations writer), fed by M12.6Any caller of the builder links to an element
PDF DeclarationsCore, Metadata/ (M14's XMP model)Declarations are metadata; the core writes those of its own targets
PDF/A-4 targets and part 4's constraint setCore, beside M14's and M20'sThe writer, fonts and builder enforce them
The 2.0 mapping of HTML, the PDF/UA-2 and WTPDF pre-write checksAdCodicem.Pdf.Html, Tagging/It reads computed style and the box tree
PDF/UA-2 and WTPDF profiles, their review conditionsAdCodicem.Pdf.ConformanceM20's satellite

No new package, no new dependency.

The PDF 2.0 structure namespace​

ISO 32000-2 §14.8.6 defines the PDF 2.0 standard structure namespace beside PDF 1.7's; an element without /NS is in the default namespace, which is PDF 1.7's — so M13's tree in 2.0 output claims nothing, and a 2.0 tree names its namespace on every element.

PdfStructureNamespace Pdf17 (http://iso.org/pdf/ssn), Pdf20 (http://iso.org/pdf2/ssn),
MathML (http://www.w3.org/1998/Math/MathML), or a caller's URI with its RoleMapNS
PdfStructureType gains PDF 2.0's types: DocumentFragment, Aside, Title, FENote, Sub, Em, Strong,
Artifact, and headings H7 and beyond; each constant knows its namespaces
PdfStructureOptions gains Namespace, Ref (related elements), PhoneticAlphabet and Phoneme
PdfStructureAttributes gains NoteType (Footnote, Endnote, None), ContinuedList and ContinuedFrom, and
the NSO owner for attributes of another namespace
  • Writing. Under a PDF 2.0 tagged target the builder writes one namespace dictionary per namespace used, lists them in /Namespaces, and gives every element its /NS — a reference, a few bytes per element. The containment checks M13 enforces for 1.7 grow into the table ISO 32000-2's Annex L gives and ISO/TS 32005 makes normative across namespaces: a violation is the caller's mistake (InvalidOperationException), as in M13.
  • The types PDF 2.0 left out of its namespace — Art, BlockQuote, Quote, Note, Reference, BibEntry, Code, TOC, TOCI, Index, Private, as §14.8.6 lists them when the list is encoded — are never retyped into something poorer: an element of one of them is written in the 1.7 namespace, explicitly, and ISO/TS 32005 says how a reader resolves it. The semantics a template gave survive the change of version.
  • A caller's namespace carries its RoleMapNS, mapping each of its types to a type of the 2.0, 1.7 or MathML namespace; PdfStructureBuilder refuses a mapping that does not end in one of them, and a chain that loops.
  • Memory stays M13's: open elements and a few bytes per page; the namespace dictionaries are written once.

ISO/TS 32005: two namespaces in one tree​

  • The table. The mapping between PDF 1.7 and PDF 2.0 types, and the containment of one namespace's elements in the other's, are data transcribed from ISO/TS 32005, each row checked against the text when encoded and pinned by a test that fails when the table and the constants disagree.
  • Reading. M15's structure reader resolves each element to a standard type: through /RoleMap for the default namespace, through each namespace's RoleMapNS, across namespaces, until a type of 1.7, 2.0 or MathML. A chain is followed iteratively and stops when it revisits a (namespace, type) pair — a cycle, which no valid file has, so no constant bounds it — reported as structure.role-map-cycle and resolved to nothing. An unknown namespace without a role map is reported (structure.namespace-unknown), its elements treated as NonStruct for extraction.
  • Merging (M06). A 1.7-tagged part in a 2.0 output keeps its elements in the 1.7 namespace, now named explicitly, its role map moved into the 1.7 namespace dictionary's RoleMapNS; M06's union of namespaces, role maps and ID trees applies. Under a PDF/UA-2 target, the part is wrapped in a DocumentFragment — ISO 32000-2's element for content taken from another document — and the claim is kept only when M20's checker, running this milestone's profile, passes the merged tree.
  • Extraction (M15) and redaction (M19) read a 2.0 tree through the same resolution, so that Em is inline, FENote a note and Aside a block, whatever namespace said so.

PDF/UA-2 from HTML​

M13's emitter, under a PDF 2.0 tagged target, maps as follows; what is not listed maps as in M13:

HTMLM13 (1.7 namespace)M28 (2.0 namespace)
The rootDocumentDocument, the tree's single root, as PDF/UA-2 asks
h1–h6, role=heading with aria-level above 6H1–H6, levels past 6 capped and reportedH1–Hn, uncapped
em, strongtransparentEm, Strong
b, i, mark, small, sub, sup, cite, dfn, var, timetransparenttransparent — sub and sup are not PDF 2.0's Sub, which is a sub-division of a block such as one line of an address
aside in the flow, role=complementaryDivAside
Footnotes (float: footnote, M12.7) and DPUB doc-footnote, doc-endnoteNote with /IDFENote with NoteType, related to its call through Ref both ways, the call a Lbl or Link
blockquote, q, code, doc-toc and its entries, doc-biblioentryBlockQuote, Quote, Code, TOC, TOCI, BibEntryThe same, in the 1.7 namespace, explicitly
A visible document titleP or a headingTitle, by -adc-pdf-tag: Title only: HTML has no element for it
A PDF page placed as a letterhead or an image (M12.6)an artifact or a Figurethe same
Merged parts (M06), in 2.0 outputPartDocumentFragment
LinksLink with OBJR, /Contentsthe same, and a structure destination
FiguresFigure with /Alt; Caption besideFigure with /Alt; Caption inside it where the containment table allows it
ListsL, LI, Lbl, LBody, ListNumberingthe same, with ContinuedList and ContinuedFrom for a list resumed after an interruption — an ol whose start continues the previous list's numbering
Pronunciation—-adc-pdf-phoneme and -adc-pdf-phonetic-alphabet on an element: Phoneme and PhoneticAlphabet

Pre-write checks. M13's checks, each against ISO 14289-2's clause rather than 14289-1's, and those PDF/UA-2 adds: every element resolves to a type of the 2.0, MathML or — through ISO/TS 32005 — 1.7 namespace; the containment table; one Document root; every FENote related to its call; every internal link with a structure destination; pdfuaid:part 2 with its rev; dc:title, DisplayDocTitle, Lang as for UA-1. Where ISO 14289-2 relaxes a UA-1 requirement — the heading sequence among them — the check follows 14289-2's text, read again at encoding. A conflict follows PdfConformancePolicy, as in M13: Refuse collects them all and throws, RemoveClaim writes the document tagged without the claim and reports tagging.conformance-claim-removed.

Structure destinations​

  • A structure destination (ISO 32000-2 §12.3.2.3) names a structure element rather than a page: /SD [element /XYZ left top zoom] in a GoTo action. Under a 2.0 tagged target every internal link, bookmark, contents entry and note call gets one, and keeps its page destination /D beside it, so that a reader that predates structure destinations still navigates.
  • From HTML: href="#id" resolves to the target element's structure element; the page and position are M12.6's, from the same layout — the two destinations cannot disagree, and a test checks that they never do.
  • Through merges and splits: M06 renumbers elements and remaps /SD with them; M07's rewriting of GoToR into GoTo keeps /SD when the target part is tagged; a destination whose element is removed (a page excluded) keeps its /D and is reported (link.structure-destination-dropped).

Forms and annotations under PDF/UA-2​

M17's fields are written as Form elements of the 2.0 namespace with their OBJR, /TU as their accessible name, and PrintField for static controls; M11's annotations as Annot with /Contents; links as Link. /Tabs /S on every page with annotations, as for UA-1.

PDF Declarations​

PdfDeclaration ConformsTo (an absolute URI); ClaimData: By, Date, Credentials, Report — each optional,
supplied by the caller
PdfDocumentMetadata gains Declarations, read and written through M14's XMP model
PdfDeclarationUris the URIs the PDF Association's registry defines that the library writes or reads —
Well-Tagged PDF's reuse and accessibility levels among them — as data
  • The pdfd namespace and its properties — pdfd:declarations, a bag of structures of pdfd:conformsTo and pdfd:claimData (claimBy, claimDate, claimCredentials, claimReport) — as the specification defines them, checked against it when encoded.
  • The library writes a declaration only for a level its own checks enforced (the WTPDF targets below). A declaration the caller supplies — an organization's own profile, a review by a named person — is written as given and reported as not verified (declaration.unverified): the file says who claimed it.
  • Under a PDF/A-1, 2 or 3 claim, pdfd needs an extension schema, which M14's generator writes; under PDF/A-4 none: veraPDF's part-4 profile checks the identification schema and no extension schema (ISO 19005-4's text read again at the slice).
  • The claim date comes from the caller, never from the clock (invariant 6).
  • Reading: M20's claim reader lists declarations; PdfConformanceValidator checks a WTPDF declaration against its profile, and the verdict says when a declared level does not hold.

Well-Tagged PDF​

WTPDF 1.0 defines two levels: reuse — a structure fit for extraction and reflow, with semantic types, reading order, Unicode and language — and accessibility — reuse plus what assistive technology needs, aligned with PDF/UA-2. Its only identification is a declaration. PdfConformanceTarget gains WtpdfReuse and WtpdfAccessibility, on 2.0 output, combinable with PdfUA2 (the accessibility level with UA-2 is the common case) and with part 4.

PDF/A-4, 4f and 4e​

PdfConformanceTarget gains PdfA4, PdfA4F and PdfA4E, on 2.0 output only. M20's constraint data gets part 4's set; each row is read again against ISO 19005-4 and veraPDF's part-4 profile before it is encoded:

AreaRequirementWhere enforcedWhen it cannot be met
FileA %PDF-2.n header with a binary comment, trailer /ID, no encryption, no LZWM03's 2.0 writerEncryption with the target: ArgumentException when the options are built
Identificationpdfaid:part 4, pdfaid:rev the edition's year, pdfaid:conformance F or E for 4f and 4e, absent for 4M14's model—
MetadataXMP authoritative (ADR 40); no document information dictionary unless the catalog has /PieceInfo, and then /ModDate alone (veraPDF's part-4 profile, 6.1.3 tests 4 and 5) — M03's 2.0 output already moves its entries to XMPM03, M14—
Output intentsA GTS_PDFA1 intent with an embedded profile; page-level intents (PDF 2.0) kept consistent with it when a merge brings themThe builder, M06A page intent that contradicts the document's: a conflict
Fonts and UnicodeEmbedded; widths consistent; no .notdef; ToUnicode values valid where presentM08M08's fallback, reported
Color, transparency, graphics state, XObjectsAs part 2, on PDF 2.0's definitionsM08, M12A conflict
Annotations and actionsAs parts 2 and 3; 3D and RichMedia only under 4eM11, M12.6A conflict
Embedded files4: only PDF/A-1, PDF/A-2 or PDF/A-4 documents — not PDF/A-3 (veraPDF's part-4 profile, 6.9 test 3) —, each with /F, /UF, AFRelationship and a MIME type; 4f: any file as an associated file, with the same entries; 4e: as 4f, and the files 3D and RichMedia annotations useM06's and M14's associated filesAn attachment the level forbids: a conflict
Logical structureNone required: part 4 has no level a, and accessibility is PDF/UA-2's——
  • 4e is claimed only for what the library can stand behind: our generator never authors 3D or RichMedia (ADR 37). A 4e document built from the builder is 4f's rules under the E identification; the allowances of 4e matter when a merge (M06) brings in a received engineering document's 3D annotations and RichMedia, kept byte for byte and checked against part 4's annex for 4e, never rewritten.
  • PDF/A-4 as a merge target (M20's preservation): parts claiming PDF/A-1, 2 or 3 are rewritten to 2.0 — M03 moves their /Info into XMP — and checked against part 4 before the claim is written; a part whose attachments part 4's level forbids is a conflict, named.
  • With PDF/UA-2 and WTPDF: one document may claim PDF/A-4f, PDF/UA-2 and WTPDF accessibility together — three identifications, one structure, one set of fonts.

Validation profiles: PDF/UA-2 and Well-Tagged PDF​

  • pdf-ua-2, wtpdf-reuse-1.0 and wtpdf-accessibility-1.0 join M20's profiles. A requirement PDF/UA-1 and 2 share keeps its identifier — pdfua-font.not-embedded runs in both, with a reference to each clause —, as the PDF/A rules share theirs across parts; what only PDF/UA-2 asks gets a rule of its own in the same families (pdfua-structure.namespace-unresolved, pdfua-structure.containment-violated, pdfua-navigation.structure-destination-missing), and what only WTPDF asks takes wtpdf-* families (wtpdf-declaration.missing).
  • Human conditions: PDF/UA-2 has no protocol of its own comparable to Matterhorn as far as this milestone knows when written; its conditions are transcribed from ISO 14289-2's text as data, with the same review questions as M20's where the requirement is the same, and replaced by a published protocol if one appears. The verdict of a UA-2 or WTPDF-accessibility claim is never Conforms from a machine, as for UA-1.
  • veraPDF's ua2, wt1r and wt1a flavors are the referees; the checklist test of M20 covers their clauses and tests too.

Diagnostics​

In PdfDiagnosticCodes, disjoint from rule identifiers (ADR 36):

CodeSeverityMeaning
structure.role-map-cycleWarningA role-map chain loops, across namespaces or within one; its elements resolve to no type
structure.namespace-unknownInformationAn element's namespace is none the reader knows and has no role map
tagging.namespace-unresolvedWarningUnder a 2.0 tagged target, an element that resolves to no standard type: a conflict
tagging.phoneme-invalidWarningA pronunciation override the alphabet does not allow; not written
link.structure-destination-droppedInformationA structure destination's element was removed; the page destination kept
declaration.unverifiedInformationA caller's declaration written as given
declaration.malformedWarningA declaration read that the specification does not allow: a relative URI, a claim date that is not a date
write.structure-namespace-declaredInformationA merged part's 1.7 elements given an explicit namespace in a 2.0 output

Referees​

All in containers (ADR 27), pinned by digest:

RefereeConfirms
veraPDF (ua2, wt1r, wt1a, 4, 4f, 4e)Every output at the claimed level; every claim in the corpus, against our profiles
pikepdfNamespaces, /NS, RoleMapNS, containment walked independently; each /SD resolving to the element whose first page is /D's; declarations and pdfaid in open_metadata()
poppler (pdfinfo -struct-text, pdftotext)The structure's text order equals the 1.7 tree's for the same source; extraction unchanged by the namespace
pdf.js and MuPDFEvery internal link navigates, through its page destination
qpdf (--check)Every output sound in 2.0
ExifToolDeclarations, pdfaid:rev, pdfuaid:rev as an independent XMP parser reads them

The command-line tool​

html2pdf --pdf-ua 2 [--wtpdf reuse|accessibility] [--pdf-a 4|4f|4e] [--declare URI]; validate --profile pdf-ua-2|wtpdf-reuse|wtpdf-accessibility; metadata declarations FILE [--json] and metadata declare FILE --uri URI [--by …] [--date …]; merge --conformance pdf-a-4.

Slices​

Each slice ends on a green commit, with its codes documented and its measurements recorded in docs/status.md.

  1. The 2.0 namespace in the structure writer. Delivers PdfStructureNamespace, the 2.0 types and attributes, namespace dictionaries, /NS and /Namespaces, RoleMapNS for callers' namespaces, the containment table and its refusals, the Artifact element, pronunciation hints. Proved by unit tests per type, attribute and refusal; an FsCheck property — any tree the builder accepts in 2.0 output resolves every element to a standard type in a finite chain and satisfies the containment table —; integration: pikepdf walks a builder-made three-page document and finds the namespaces and tree built, qpdf --check is silent, M02's object-shape rules generated from the Arlington model (ADR 44) report nothing on its 2.0 dictionaries, and veraPDF's ua2 profile finds no failure in its structure clauses. Leaves ISO/TS 32005.
  2. ISO/TS 32005: reading and merging two namespaces. Delivers the 1.7–2.0 table, resolution across namespaces in M15's reader with cycles reported, the 1.7 types kept in their namespace, M06's merge of a 1.7 part into a 2.0 tree in a DocumentFragment, extraction and redaction through the resolution. Proved by unit tests over the table and hostile role maps (a 100,000-link cycle, a namespace chain through every type); integration: the remote hand-written PDF 2.0 file's tree resolves as pikepdf reads it; the three committed PDF/UA-1 documents merged into a 2.0 volume keep every element resolvable, their text in order in pdfinfo -struct-text. Leaves the HTML engine.
  3. PDF/UA-2 from HTML. Delivers the 2.0 mapping table in the emitter, FENote with NoteType and Ref, headings past six, Title by override, the pre-write checks against ISO 14289-2, pdfuaid part 2, html2pdf --pdf-ua 2. Proved by unit tests over box trees per mapping row and per conflict; integration: veraPDF's ua2 profile passes the reference documents; their structure's text order equals that of M13's 1.7 rendering of the same sources. Leaves navigation.
  4. Structure destinations. Delivers /SD beside /D for links, bookmarks, contents entries and note calls, their remapping by M06 and M07, the dropped-destination report. Proved by unit tests, and a property — for every internal link written, /SD's element's first page is /D's page —; integration: pikepdf resolves every /SD in the reference documents; pdf.js and MuPDF navigate every link; veraPDF's ua2 passes. Leaves forms.
  5. Forms and annotations under PDF/UA-2. Delivers M17's fields as Form in the 2.0 namespace, Annot for M11's annotations, Link, /Tabs /S. Proved by unit tests; integration: M17's onboarding pack under PDF/UA-2 passes veraPDF's ua2, and qpdf and pikepdf list its fields with their names, types and values. Leaves declarations.
  6. PDF Declarations. Delivers PdfDeclaration, PdfDeclarationUris, reading and writing through M14's model, the extension schema under parts 1 to 3, claim data from the caller, metadata declarations and declare. Proved by unit tests (a relative URI, a missing claim date, several declarations, a declaration in a superseded packet); integration: ExifTool reads every declaration we write; libreoffice-report-pdfa2b.pdf given a declaration keeps the PDF/A-2b claim veraPDF upheld. Leaves Well-Tagged PDF.
  7. Well-Tagged PDF. Delivers the WtpdfReuse and WtpdfAccessibility targets and their pre-write checks, their declarations, and --wtpdf. Proved by unit tests per check; integration: veraPDF's wt1r and wt1a pass the reference documents under each level. Leaves PDF/A-4.
  8. PDF/A-4, 4f and 4e. Delivers the targets, part 4's constraint set, the identification, output intents including page-level ones kept by merges, attachments by level, 4e for preserved engineering content, the triple claim with PDF/UA-2 and WTPDF, --pdf-a 4|4f|4e, PDF/A-4 as a merge target. Proved by unit tests per row and per refusal; integration: veraPDF's 4, 4f and 4e pass the reference documents, the invoice with its source HTML and a CSV of its lines attached passes 4f, and the triple claim passes 4f, ua2 and wt1a together. Leaves validation.
  9. Profiles for PDF/UA-2 and Well-Tagged PDF. Delivers the three profiles, the shared and new rules, the UA-2 human conditions as review data, the checklist test extended to veraPDF's ua2, wt1r and wt1a clauses. Proved by unit tests per rule (M20's triad); integration: on every output of this milestone and every document of the corpus that claims PDF/UA-2 or declares WTPDF — none today (below) —, our verdicts and failed clauses equal veraPDF's. Leaves the corpus closed.
  10. The corpus closed. Delivers the acceptance conditions below, the outputs committed to the corpus, Pdf20TaggingBenchmarks, the documentation. Proved by the acceptance conditions.

Tests required​

Unit —

  • The writer: each 2.0 type and attribute; /NS on every element and the namespace dictionaries written once; a caller's namespace and its RoleMapNS; every containment refusal; the 1.7 types kept in their namespace; the Artifact element; pronunciation hints and their alphabets.
  • ISO/TS 32005: every row of the table; resolution through RoleMap and RoleMapNS in both directions; a cycle; an unknown namespace; a merge's 1.7 part given its namespace explicitly and its role map moved.
  • The emitter: every row of the 2.0 mapping; sub and sup staying transparent; FENote related to its call both ways; headings H7 and beyond; Title only by override; each PDF/UA-2 conflict under Refuse and RemoveClaim.
  • Structure destinations: /SD and /D agreeing; remapped by a merge; dropped with an excluded page; kept by M07's GoToR rewriting.
  • Declarations: every property; the extension schema under parts 1 to 3 and none under part 4; caller dates only; a declaration never written by the library for a level it did not check.
  • PDF/A-4: every row of the requirement table; the identification per level; a PDF/A-3 part in a 4f merge; a 4e document's preserved 3D annotations byte for byte; the refusal of 1.7-based claims beside a 2.0 target.
  • Profiles: each rule, a triggering, a passing and a legal-but-unusual document; the shared identifiers with both references; the review conditions' applicability.
  • Hostile: a tree of 1,000,000 elements each in a namespace of its own; a RoleMapNS chain through 100,000 namespaces ending in a cycle; /Namespaces holding a million entries; an /SD pointing at a page object, at a deleted element, at itself; a declaration packet with 100,000 claim data entries — each bounded in time and memory, never an exception.
  • Properties (FsCheck): resolution is total and terminates on any role-map graph; any accepted tree satisfies the containment table; two runs give identical bytes.

Integration — in containers (ADR 27): veraPDF (ua2, wt1r, wt1a, 4, 4f, 4e) on every output and every claim in the corpus; pikepdf, poppler, qpdf and ExifTool on every output; pdf.js and MuPDF on every link.

Acceptance conditions​

"The reference documents" are M12's renderings of sources/invoice-fr.html, report-fr.html and contract-fr.html, and M13's accessibility reference, here rendered on the PDF 2.0 writer under the targets named. Rows naming remote documents close only on a green Remote corpus run.

DocumentsBehaviorVerified by
The reference documents under PDF/UA-2veraPDF's ua2 reports no error; our pdf-ua-2 profile finds no machine failure and returns ConformsPendingReview; pdfuaid part 2 with its revisionPdf20TaggingRefereeTests.Reference_documents_pass_pdf_ua_2 (new)
The same, beside M13's PDF/UA-1 renderings of the same sourcespdfinfo -struct-text gives the same text in the same order; em and strong are Em and Strong, aside is Aside, the accessibility reference's footnotes are FENotes each related to its call, and nothing is lost of the 1.7 types PDF 2.0 droppedCorpusPdf20StructureTests.The_2_0_tree_says_what_the_1_7_tree_said_and_more (new)
The reference documents under WTPDF reuse, and under WTPDF accessibility with PDF/UA-2veraPDF's wt1r, and wt1a with ua2, report no error; each carries the declaration of its level, as ExifTool reads itPdf20TaggingRefereeTests.Reference_documents_pass_wtpdf_at_each_level (new)
The reference documents under PDF/A-4 and 4f — the invoice with its source HTML (Source) and a CSV of its lines (Data) attached —, and under 4everaPDF's 4, 4f and 4e report no error; pdfaid part 4 with its rev and level; /Info as part 4 allowsPdfA4RefereeTests.Reference_documents_pass_pdf_a_4_at_every_level (new)
The invoice under PDF/A-4f, PDF/UA-2 and WTPDF accessibility togetherveraPDF passes 4f, ua2 and wt1a in one run; our three verdicts agreePdfA4RefereeTests.The_triple_claim_passes_every_profile (new)
Every internal link, bookmark, contents entry and note call in the reference documents/SD resolves, by pikepdf, to the element whose first page is /D's page; pdf.js and MuPDF navigate each through /DPdf20TaggingRefereeTests.Every_internal_link_has_a_structure_destination_that_agrees (new)
M17's onboarding pack under PDF/UA-2veraPDF's ua2 passes; qpdf and pikepdf list every field with its name, type and value; each widget in a Form of the 2.0 namespacePdf20TaggingRefereeTests.Forms_pass_pdf_ua_2 (new)
vendor/pdf-association/pdflib-pps-kraxi-pdfa2a-pdfua1-invoice.pdf, indesign13-pdfua1-german-book-chapter.pdf and indesign15-pdfua1-form.pdf (PDF/UA-1, the 1.7 namespace) merged with the reference report under PDF/UA-2Each part in a DocumentFragment, its elements in the 1.7 namespace named explicitly; every element resolves (pikepdf agrees); the UA-2 claim kept when our profile and veraPDF's ua2 both find no machine failure, and otherwise removed with the rule namedCorpusPdf20StructureTests.Merging_1_7_tagged_parts_into_a_2_0_document (new)
vendor/eu-publications/pdflib-oj-exchange-rates-greek.pdf (PDF/A-2a), documents/archival/libreoffice-report-pdfa2b.pdf (PDF/A-2b), vendor/verapdf/pdfa3b-embedded-pass.pdf (PDF/A-3b) merged with the reference invoice under PDF/A-4fEach part rewritten to 2.0 and checked against part 4 first; veraPDF's 4f passes the volume; the part-3 attachment kept as an associated fileCorpusPdf20StructureTests.Pdf_a_parts_merge_into_a_pdf_a_4f_volume (new)
Remote remote/opf-format-corpus/acrobat9-portfolio-signed-3d.pdf: its members with U3D and PRC models, unpacked by M07 and assembled under PDF/A-4eThe 3D annotations and their streams byte for byte as they came, nothing authored; veraPDF's 4e passes, or — since one model carries a script — the plan names what part 4's annex forbids, the claim is not written, and veraPDF fails exactly those clausesCorpusPdf20StructureTests.Engineering_content_is_preserved_under_pdf_a_4e (new)
vendor/verapdf/pdfa4-metadata-pass.pdf, vendor/verapdf/pdf20-version-mismatch.pdf; remote remote/mustang/dwc-weasyprint-facturx-extended-pdfa4f.pdf and remote/pdf20examples/handwritten-pdf20-utf8-strings.pdfRead: claims, declarations and namespaces as pikepdf and ExifTool see them; the DWC invoice's Factur-X on PDF/A-4f reported, never written; a stamp (M09) and a merge keep each PDF/A-4 claim veraPDF upheldCorpusPdf20StructureTests.Pdf_2_0_documents_read_and_keep_their_claims (new)
Every document of the corpus that claims PDF/UA-2 or declares WTPDF, once the corpus holds them (below), and every output aboveOur pdf-ua-2, wtpdf-reuse and wtpdf-accessibility verdicts and failed clauses equal veraPDF's; each disagreement fixed or recorded in the manifest with its reasonUa2DifferentialTests.Our_ua_2_and_wtpdf_verdicts_are_verapdfs (new)
The reference invoice asked for PDF/A-3b with PDF/UA-2, and for Factur-X on PDF/A-4fRefused when the options are built, naming the base versions and ADR 40Pdf20TaggingTests.Claims_on_different_base_versions_are_refused (new)
M12's long report at 1,000 pages under PDF/UA-2 and PDF/A-4Memory flat as pages grow, the overhead over M13's 1.7 tree measured (the /NS references) and recordedCorpusPdf20StructureTests.Tagging_in_2_0_holds_its_budget (new), Pdf20TaggingBenchmarks (new)
Every output aboveTwo runs give identical bytesCorpusPdf20StructureTests.Pdf_2_0_output_is_deterministic (new)
The same operations through the toolThe verbs produce the API's bytes and reportsCorpusToolTests.Pdf_2_0_conformance_verbs_match_the_api (new)

Corpus​

What the corpus holds​

  • PDF/A-4: veraPDF's passing metadata fixture and its PDF 2.0 version-mismatch fixture (a part-4 failure whose claim the manifest does not record), and remote WeasyPrint's PDF/A-4f Factur-X invoice from DWC's generator — two rdf:RDF blocks in one packet, factur-x.xml twice in /AF, /Info down to ModDate.
  • Tagged PDF 2.0: remote, the PDF Association's hand-written UTF-8 example — a tagged page, UTF-8 alternate text, a duplicated MCID.
  • PDF/UA-1 in the 1.7 namespace to merge into 2.0 volumes: PDFlib's invoice, InDesign's book chapter and form, committed; AbleDocs' two documents and InDesign CS6's brochure, remote — role maps, class maps, custom types, ActualText, notes, tables, lists, links with OBJR.
  • Engineering content, remote: Acrobat 9's portfolio with U3D and PRC models inside its members.
  • PDF/A-1 to 3 claims veraPDF upholds, from part 1 to 3u, for PDF/A-4 merges (M20's list).

What it lacks​

NeedWhyPriorityLikely source
PDF/UA-2 documents from other producers, each with veraPDF's ua2 verdictThe pdf-ua-2 profile cannot be accepted on our own outputs alone (invariant 10), and the reader's namespace resolution has one hand-written file to go on1Generated here: WeasyPrint (a Python producer; the version and whether it writes PDF/UA-2 checked when run) and LibreOffice's PDF/UA export if its pinned version offers part 2; a public source: the veraPDF corpus's PDF/UA-2 test files (CC BY 4.0), where it publishes them
Documents declaring Well-Tagged PDF, reuse and accessibilityThe WTPDF profiles and the declaration reader have nothing third-party to judge1A public source: the veraPDF corpus's WTPDF test files, and the PDF Association's own publications that declare WTPDF — their license checked, remote if it is not attribution-only
Pass and fail fixtures for each clause of veraPDF's ua2, wt1r and wt1a profilesA profile of dozens of rules needs a pair per rule, as M20's gap says for the other parts1A public source: the veraPDF corpus (CC BY 4.0), a selection committed or a pinned archive in the remote corpus (ADR 33)
Our own PDF/UA-2, WTPDF and PDF/A-4, 4f and 4e renderings of the reference documents, committedLater milestones — M30 on PDF/UA-2 above all — accept on them1Generated here, by this milestone, recorded in build_corpus.py
The PDF/A-4 claim of vendor/verapdf/pdf20-version-mismatch.pdf recorded in the manifest, with veraPDF's verdictThe fixture escapes every claimed-document row today1Generated here: the list-valued claim field M20 asks for
PDF/A-4e documents with 3D or RichMedia that veraPDF upholds, and a committed one4e's allowances are checked against one remote portfolio whose members make no part-4 claim2A public source: the veraPDF corpus's PDF/A-4e files; a contribution
A PDF 2.0 document using structure destinations from another producerThe reader's /SD resolution and M07's rewriting are checked on our output only2A public source: the pdf20examples set (CC BY-SA 4.0, so remote), or a producer's sample
A PDF 2.0 document whose tree mixes the 1.7 and 2.0 namespaces with RoleMapNS, from another producerISO/TS 32005 reading is otherwise proven on trees we built2Generated here with a producer that writes both, if one in the container does; else a public source
A PDF 2.0 document with page-level output intentsPart 4 allows them and the merge keeps them consistent; no document has one2A public source: the pdf20examples set's page-level output intent example (remote)
PDF Declarations other than WTPDF's, from the registryThe reader's handling of declarations it does not know3Generated here with pikepdf's XMP editing, on a committed document, recorded as a derived variant

Traps​

  • An element without /NS is in PDF 1.7's namespace, even in a PDF 2.0 file. M13's tree in 2.0 output claims nothing for that reason; a 2.0 tree must name its namespace on every element, or it is a 1.7 tree.
  • PDF 2.0's Sub is not a subscript. It is a sub-division of a block — one line of an address, of a poem — and mapping HTML's sub or sup to it would say something false about the content. M13's mapping table's note that M28 "adds Sub" must not be read that way.
  • Types PDF 2.0 dropped are not wrong: a BlockQuote stays a BlockQuote in the 1.7 namespace. Retyping it as a Div to "be 2.0" loses what the author said.
  • Note became FENote, with NoteType and a relation to its call: a footnote that is only an FENote without its Ref is a note nobody can reach from the text.
  • Role maps cross namespaces now. A chain can go through RoleMapNS of three namespaces and loop; it is followed iteratively and stopped at the first revisited pair, never by recursion.
  • Structure destinations need their page destination too: most readers deployed today navigate by /D, and a link with /SD alone goes nowhere in them.
  • /SD and /D must agree. They come from one layout; a merge that renumbers elements and not destinations, or pages and not elements, makes them disagree — which no viewer reports.
  • A declaration is a claim someone makes. The library writes one only for what its own checks enforced; a caller's declaration is written as given and reported as unverified, never presented as ours.
  • PDF/A-4 dropped levels b, u and a: a pdfaid:conformance of B under part 4 is wrong, and so is inferring accessibility from a part-4 claim — that is PDF/UA-2's.
  • pdfaid:rev and pdfuaid:rev are required by their parts and absent from earlier ones; a validator that looks for conformance under part 4 or for rev under part 3 is reading the wrong edition.
  • 4f is not Factur-X's base. DWC's invoice is a valid PDF/A-4f and not a Factur-X a French or German platform must accept; writing one would mislead the invoice's recipient (ADR 40).
  • 4e allows engineering content; it does not create it. A 4e claim on a document with no 3D is legal and says nothing; one on a merged 3D model promises that model is kept exactly, which is all the library does with it.
  • Page-level output intents can contradict the document's: a merge that brings in a page with its own intent must keep it consistent, or the page's color means something else than it did.
  • Moving /Info into XMP for a PDF/A-4 merge changes a PDF/A-1 to 3 part's metadata model; its old claim is gone once the part is in a 2.0 file, and only the part-4 check may put a claim back.
  • veraPDF's PDF 2.0 profiles are younger than its part 1 to 3 ones, and the standards' own errata still move; a disagreement is recorded with the clause's text, and the veraPDF version pinned, never absorbed.

Documentation​

  • docs/website/docs/concepts/pdf-2-0-tagging.md (new): the two namespaces, what PDF 2.0 added and dropped, ISO/TS 32005, structure destinations, pronunciation hints.
  • docs/website/docs/guides/accessible-documents.md (M13's): PDF/UA-2 and WTPDF targets, choosing between UA-1 and UA-2 (Factur-X keeps UA-1), what veraPDF checks and what a person must.
  • docs/website/docs/concepts/pdf-a.md (M14's): PDF/A-4, 4f and 4e, 4e's preservation-only rule, PDF/A-4 as a merge target, and why an invoice stays on part 3.
  • docs/website/docs/guides/declarations.md (new): reading and writing PDF Declarations, who claims what.
  • docs/website/docs/reference/html-to-structure.md (M13's): the 2.0 column of the mapping.
  • docs/website/docs/reference/css-tagging-properties.md (M13's): the pronunciation properties and Title.
  • docs/website/docs/reference/conformance-rules.md (M20's, generated): the PDF/UA-2 and WTPDF rules.
  • docs/website/docs/reference/diagnostics.md: the structure.*, tagging.*, link.*, declaration.* and write.* codes added.
  • docs/website/docs/reference/tool/: html2pdf --pdf-ua 2, --wtpdf, --pdf-a 4*, validate --profile pdf-ua-2|wtpdf-*, metadata declarations and declare, merge --conformance pdf-a-4.
  • docs/website/docs/introduction.md and docs/features/features.json: the pdf2-conformance entry brought to its state.
  • docs/architecture.md: namespaces in Structure/, declarations in Metadata/.
  • docs/corpus.md: veraPDF's PDF 2.0 flavors as referees; the claim and declaration fields.
  • docs/status.md: the measurements, the tagging overhead in 2.0, and the agreement with veraPDF's PDF 2.0 profiles.

Exit criteria​

  • The structure writer writes the PDF 2.0 namespace, its types and attributes, RoleMapNS and the containment table; the ISO/TS 32005 table is data, checked against the text and held to the constants.
  • The reader resolves elements across namespaces, cycles reported; M06 merges 1.7 parts into 2.0 trees.
  • PDF/UA-2 is generated from the builder and from HTML, with structure destinations on every internal link.
  • M17's forms and M11's annotations are tagged in the 2.0 namespace.
  • PDF Declarations are read and written; the library declares only what its checks enforced.
  • WTPDF reuse and accessibility, and PDF/A-4, 4f and 4e, are generated; 4e preserves and never authors.
  • The pdf-ua-2 and WTPDF profiles ship in AdCodicem.Pdf.Conformance, every clause and test of veraPDF's ua2, wt1r and wt1a mapped to a rule or a recorded reason.
  • The priority-1 gaps above are filled; each remaining gap is recorded in docs/corpus-contributions.md.
  • The acceptance conditions above pass on the corpus, in CI, with no document skipped, and the remote rows on a green Remote corpus run recorded in status.md.
  • Pdf20TaggingBenchmarks measures the 1,000-page report under PDF/UA-2 and PDF/A-4 with MemoryDiagnoser; the budget and the overhead over 1.7 are recorded.
  • Unit tests cover each behavior, its degenerate and hostile cases; the FsCheck properties hold.
  • Integration tests run veraPDF, pikepdf, poppler, pdf.js, MuPDF, qpdf and ExifTool in containers.
  • The documentation site publishes the 2.0 tagging concepts, the targets, declarations and the rule reference.
  • Every page of Documentation is written in its Diátaxis section, one mode per page (ADR 47).