Recursive AI

PDF/UA and PDF/A level A

All onepdf docs

OnePDF can write PDF/UA-1 (ISO 14289-1), PDF/UA-2 (ISO 14289-2, on PDF 2.0; see PDF/UA-2) and PDF/A-2a / 3a (ISO 19005-2 and -3, level A: tagged), alone or in one file, and can check a document against the requirements it is able to verify.

What is promised, and what is not

  • Only for tagged documents: ones OnePDF laid out or created with tags, and existing files tagged through the tagging API. The input must already be a tagged document: a structure tree, marked content on every painted mark, alternate text where it is needed, a title. OnePDF checks that and repairs the clerical parts; it does not tag on its own judgement, guess alternate text, invent titles or guess a language. The tagging API writes the structure a caller decides on (the caller supplies every role and every alternate text); OnePDF never works the roles out. It is not a converter for arbitrary files. An untagged file is refused with the reasons.
  • A clean check is not proof of conformance. CheckPdfUA verifies the rules listed below and nothing else; PDF/UA also asks for things only a person can judge (a logical reading order, meaningful alternate text, sufficient contrast, correct heading use). The tests run the veraPDF validator (PDF/UA-1, PDF/A-2a, PDF/A-3a) as a black box on the documents OnePDF writes; passing it is evidence, not certification. Use a validator and a screen-reader review before claiming conformance for a document.
  • It writes the identification only when every check it makes passes.

API

C#
LayoutDocument layout = LayoutDocument.Compose(doc =>
{
    doc.Title("Annual report").Author("A. Writer").Language("en-US");
    doc.Page(page => { /* headings, tables with header cells, figures with .AltText(...) */ });
});
 
using var file = File.Create("report.pdf");
layout.Save(file, new PdfSaveOptions
{
    PdfUA = new PdfUAOptions(),                                                  // PDF/UA-1
    PdfA = new PdfAOptions { Part = PdfAPart.Part2, Conformance = PdfAConformance.A }, // optional: PDF/A-2a as well
});
PdfSaveOptions.PdfUA (PdfUAOptions)A full save prepares, checks and writes, or throws PdfUAException and writes nothing. With PdfA too, the failures of both come in one PdfAException (PDF/UA entries have clauses starting PDF/UA-1). Needs an editable document. Not available for SaveIncremental.
PdfUAOptions.LanguageA /Lang to write when the catalog has none. Without it, a missing language is a violation.
PdfUAOptions.EmbedLibraryFontsDefault true: standard fonts added by AddStandardFont become embedded Liberation faces when the Fonts package is registered (as for PDF/A).
PdfAConformance.A, PdfAOptions.LanguagePDF/A level A; Language is the same fallback.
PdfDocument.PreparePdfUA(options)Makes the repairs below in memory and returns what is still wrong.
PdfDocument.CheckPdfUA()Reports every violation as the document stands; changes nothing; works on read-only documents.
PdfUAException.ViolationsPdfUAViolation: Checkpoint (Matterhorn Protocol checkpoint, such as 13 for graphics), Clause (ISO 14289-1), Message, ObjectNumber, PageIndex. At most 2000 are listed; more than twenty of one kind are cut off.
LayoutDocument.Author, Subject, KeywordsInformation dictionary entries (and XMP dc:creator, dc:description, pdf:Keywords in a PDF/A or PDF/UA save). Title and Language existed already.

A layout document reaches these through LayoutDocument.Save(stream, PdfSaveOptions). That path lays the document out through the streaming writer, then reopens the finished file and saves it through PdfDocument; the whole file is held in memory while it is checked and rewritten. The streaming Save(stream, PdfDocumentWriterOptions) cannot do this: refusing a document needs the finished structure tree, every page's content and the final font set, which the streaming writer by design does not keep. For documents too big for that, write with the streaming path and check the result (CheckPdfUA) rather than rewriting.

Repairs (no judgement needed)

RepairRequirement
/MarkInfo << /Marked true >> when a structure tree existsISO 14289-1 7.1
/Lang from PdfUAOptions.Language when the catalog has none7.2
/ViewerPreferences /DisplayDocTitle true when there is a title7.1
XMP packet with pdfuaid:part 1 and dc:title taken from the Info title (declared as an extension schema when PDF/A is also requested, ISO 19005-2 6.6.2.3)5, 7.1
/Tabs /S on pages that have annotations7.18.3
/Contents on a link annotation that has none: the structure element's /Alt or /ActualText, else the link's address (layout links already carry the text they cover)7.18.5
Liberation faces for standard fonts OnePDF added7.21.4.1

An XMP packet that already claims everything requested is kept; any other packet is replaced by one built from the Info dictionary (as for PDF/A).

What is checked (and refused)

Each finding names the Matterhorn checkpoint and the ISO 14289-1 clause.

  • Tagging (01, 06; 7.1): /MarkInfo /Marked true, /Suspects not true, a /StructTreeRoot; every painted mark (paths, text, images, shadings, forms, inline images) is inside marked content that is either an /Artifact or carries an /MCID; tagged content is not nested in an artifact; every /MCID used on a page is referenced by the structure tree; a page with tagged content has /StructParents. Content inside form XObjects is taken to inherit the tagging of the Do that paints it and is not scanned.
  • Structure types (02; 7.1): every type is one of the ISO 32000-1 standard types or reaches one through the /RoleMap; no circular mapping; no standard type remapped. PDF 2.0-only types (Aside, Title, …) need a mapping.
  • Metadata and dictionaries (06, 07; 7.1): XMP packet with pdfuaid:part 1 and a non-empty dc:title; DisplayDocTitle true.
  • Language (11; 7.2): a catalog /Lang that is a plausible BCP 47 tag.
  • Figures (13; 7.3, 7.7): Figure and Formula elements have /Alt or /ActualText.
  • Headings (14; 7.4): the first numbered heading is H1; a numbered heading is at most one level deeper than the one before; H and H1…H6 are not mixed.
  • Tables (15; 7.5): at least one TH cell; TR in Table/THead/TBody/TFoot; cells in TR; every row covers the same number of columns once ColSpan and RowSpan are counted.
  • Lists (16; 7.6): L contains LI (or a Caption); LI is in L; Lbl and LBody are in LI.
  • Annotations (28; 7.18): every link annotation is an object reference inside a Link structure element and has /Contents (or its element has /Alt); widgets are in Form elements and have a /TU; other annotations (except Popup, TrapNet, PrinterMark) are in the tree and have /Contents; each has a /StructParent; pages with annotations have /Tabs /S.
  • Fonts (31; 7.21): every font other than Type 3, in page and form resources, has an embedded font program; a font has a /ToUnicode map, or an encoding that determines Unicode (a standard encoding without /Differences, or an Adobe CJK ordering).
  • Content that a save would lose (31 for fonts, else 01; 7.21.4.1, 7.1): a stream that cannot be read, decrypted or decoded (corrupt or truncated filter data) and a reference to a needed part that leads to no object, such as a missing /FontFile2. The finding names the object and what it is. A PDF/UA save, like a PDF/A save, never drops content: it fails with these findings instead (the full list of what counts is in pdfa.md). In PDF/UA-2 the clause is that of the matching checkpoint.
  • Security (26; 7.16): an encrypted document, or new encryption in the save options, must keep the accessibility-extraction permission.

Not checked: reading order, contrast and colour use, the meaning of alternate text, heading semantics beyond the level sequence, Headers/ID association of complex tables, scope attributes, optional content names, embedded file descriptions, media and flicker rules, XFA and JavaScript rules, fonts used only in invisible text, the Unicode mapping of foreign symbolic fonts, forms inside XObjects. Bookmarks for long documents (ISO 14289-1 7.17) are not required by this check.

PDF/UA-2

PDF/UA-2 (ISO 14289-2) is built on PDF 2.0. Choose it with PdfUAOptions.Part:

C#
layout.Save(file, new PdfSaveOptions { PdfUA = new PdfUAOptions { Part = PdfUAPart.Part2 } });

The promises are those of part 1 (above): tagged documents only (laid out or created by OnePDF, or tagged through the tagging API with roles the caller supplies), no tagging on OnePDF's own judgement, no invented titles, alternate text or language, and a clean check is not proof of conformance. A part-2 save is checked with veraPDF's ua2 flavour as a black box in the tests; passing it is evidence, not certification.

PdfUAOptions.PartPdfUAPart.Part1 (default) or PdfUAPart.Part2. Part 1 output is unchanged.
PdfDocument.CheckPdfUA(PdfUAPart part, ct)The check against the given part. A part-2 check of a file that is not PDF 2.0, or whose elements name no namespace, says so.
PdfDocument.PreparePdfUA(options)With Part2, also makes the namespace repairs below (the version is written by the save).
PdfUAViolation.Part, .Clause1 or 2, and the clause of that part.
PdfDocumentWriter.SetBookmarkElement, .SetDestinationElementTie a bookmark or named destination to the structure element its target is in; a writer producing PDF 2.0 (Version "2.0") then writes structure destinations.
StructureGroup, Slot.Aside(), .Footnote(), .Title(), .Group(type)Layout: group content in a Sect, Div, Part, BlockQuote, Aside, FENote or Title element.

Repairs for part 2 (no judgement):

RepairRequirement
The file is written as PDF 2.0 (%PDF-2.0)ISO 14289-2 requires a PDF 2.0 file
/Namespaces on the structure tree root and /NS on every element: the PDF 2.0 namespace (http://iso.org/pdf2/ssn) for the types it defines, the PDF 1.7 namespace for the 1.7 types it lacks (TOC, TOCI, Reference, Art, BlockQuote, Code, Index, Quote, BibEntry, Private)8.2.4 (ISO 32000-2 14.7.4.2)
Note becomes FENoteISO 32000-2 14.8.4.4.2
/Ref on a TOCI taken from the structure destination of the link inside it8.2.5.8
XMP pdfuaid:part 2 and pdfuaid:rev 2024, with the title5, 8.11.1
Everything the part-1 repairs do that part 2 also asks for: /MarkInfo, /Lang from PdfUAOptions.Language, DisplayDocTitle, /Tabs /S, link /Contents, Liberation faces for standard fonts6.2, 8.4.4, 8.11.2, 8.9.3.3

Refused for part 2 (each finding names the clause of ISO 14289-2; the clause numbers are those that veraPDF's ua2 profile reports the rule under, and a rule it does not check is filed under the nearest heading):

  • Tagging (8.2.2): real content outside marked content or an artifact; MCIDs missing from the tree; a missing /StructParents (and the part-1 tagging findings).
  • Namespaces (8.2.4): an element naming no namespace or a namespace that is neither the PDF 2.0 nor the PDF 1.7 one; a type its namespace does not define. Custom types and namespace role maps are not written: they are refused.
  • Headings (8.2.5.12): the unnumbered H is not allowed; use H1…H6. Heading level order is not checked for part 2 (neither does veraPDF's profile): that is a person's judgement.
  • Table of contents (8.2.5.8): a TOCI with no /Ref that cannot be filled from its link.
  • Links (8.2.5.20): a link annotation inside a Link or Reference element; no /Contents demand (the repair supplies it).
  • Lists (8.2.5.25): a list with labels names its numbering (ListNumbering other than None). Nesting rules of the structure table (L contains LI, LI contains Lbl and LBody, rows in tables) are checked as in part 1.
  • Tables (8.2.5.26): regular tables (rows cover the same number of columns). A header cell is not demanded (veraPDF's profile does not demand it; layout tables still write one).
  • Figures (8.2.5.28.2): Figure has /Alt or /ActualText; Formula as in part 1.
  • Destinations (8.8): every link, GoTo action and outline item that goes inside the document is a structure destination, not a page. OnePDF cannot choose the element for an existing page destination, so a file with one is refused; layout and PdfDocumentWriter (version 2.0, SetBookmarkElement/SetDestinationElement) write structure destinations.
  • Language, metadata, viewer preferences, fonts (8.4.4, 8.11, 8.4.5.5.1): as in part 1, with pdfuaid:rev.
  • Pages with annotations (8.9.3.3): a /Tabs entry (A, W or S; the repair writes S).
  • PDF 2.0 file (general): a check of a file that is not PDF 2.0 says so; a save always writes 2.0.

With PDF/A: PDF/A-2 and PDF/A-3 are PDF 1.7 formats, so PdfA together with Part2 throws PdfAException (one violation) and writes nothing. PDF/A-4 is not supported.

Layout. A layout document saved with Part2 is laid out as PDF 2.0: bookmarks, anchors and table-of-contents links go to the structure element they point at (for an anchor around a heading, the heading; for content with no element of its own, the element around it). Headings are H1…H6 as before. Aside(), Footnote() and Title() write the 2.0-only types Aside, FENote and Title, which part 1 refuses (they have no ISO 32000-1 equivalent and OnePDF writes no role map for them). Layout has no inline Em/Strong. Author-supplied things stay the author's: title, language, alternate text.

Inline, caption, ruby, warichu and MathML structure (part 2). Each finding names its clause of ISO 14289-2; all but the associated-file rule are in veraPDF's ua2 profile too, and the tests compare both.

RuleClause
Ruby has exactly RB, RT or RB, RP, RT, RP, in that order8.2.5.23
Warichu has exactly WP, WT, WP8.2.5.24
A Caption is the first or the last child of its parent8.2.5.27
MathML elements (namespace http://www.w3.org/1998/Math/MathML, accepted by the namespace check) occur only below a Formula or another MathML element8.2.5.29
A file in a Formula's /AF has an /AFRelationship and embeds its data (/EF)8.2.5.29 (ISO 32000-2 7.11.3, 14.13)
The structure type table (ISO 14289-2 / ISO/TS 32005 Table 5): a parent may not contain certain children (this is what keeps Em and Strong inline: they may not hold P, L, Table, Sect and the other block elements, and Document, Sect, Table, L and the like may not hold them directly); some children occur at most once (Caption, THead, TFoot, H, Sect below a heading); Document, Sect, Table, TR, L, TOC and similar do not hold marked content directlyTable 5

The table is data in PdfUA2Table.cs. Only standard elements are compared (custom types are refused by the namespace rule). An element counts as a grouping element, for the Link/Annot/FENote/Reference caption rule, when it holds no marked content itself.

Not checked, because they need a person's judgement or the clause text is not fixed enough to decide: whether Em and Strong express real emphasis; whether a Caption belongs to what it sits beside; whether ruby text is the reading of its base; whether MathML is a faithful or complete rendering of the formula, and whether the associated file is MathML at all; the 51 table rows that depend on whether an element is used as a grouping or non-grouping element (Annot, Reference, Form, Link, ... below), and StructTreeRoot holding exactly one Document; the rest of "Not checked" above.

PDF/A level A

Level A is level U plus a tagged logical structure (ISO 19005-2, 6.7). A save as 2a or 3a repairs /MarkInfo and the language fallback, and checks: /MarkInfo (6.7.2.2), a structure tree (6.7.3.3), standard or role-mapped types and no circular or remapped types (6.7.3.4), a language (6.7.4), content that is tagged or an artifact, and alternate text for figures. Everything in pdfa.md applies too. XMP carries pdfaid:conformance A.

Verification

tests/OnePdf.Layout.Tests/PdfUATests.cs lays out a document with headings, a multi-page table with header cells and spans, nested lists, figures with alternate text, internal and external links, a header and a footer with page totals, and saves it as PDF/UA-1, PDF/A-2a, PDF/A-3a, and 2a and 3a combined with PDF/UA-1; each file passes veraPDF (the combined ones under both profiles). PdfUA2Tests.cs in both test projects does the same for PDF/UA-2 under the ua2 flavour: a richer layout document (title, table of contents, bookmarks, aside, footnote, inline objects, lists, a table with row and column headers, figures, links) and writer-made documents, a refusal for each rule above that veraPDF also reports, a check that a file damaged in each way veraPDF finds is also found by CheckPdfUA, and the committed reference testdata/layout-references/documents/accessible-ua2.pdf. tests/OnePdf.Tests/PdfUA does the same with documents made by PdfDocumentWriter, and has a test for every refusal above and for CheckPdfUA listing all violations of a deliberately bad document. veraPDF is run through ONEPDF_VERAPDF; the tests skip without it. The pdfa fuzz target also covers PDF/UA and level A: a file that is written must pass the checkers.