Skip to content

PDF/A

PDF/A is the archival subset of PDF: self-contained files that will still render the same way in twenty years. UniPDF implements it in model/pdfa as a set of profiles, one per part and conformance variant. Every profile does two separate jobs. Handed to a PdfWriter, it rewrites the document toward the standard as the file is written. Handed a CompliancePdfReader, it checks an existing file and reports the rules it breaks.

The pdfa package is documented as experimental, and its API may change in a minor release.

Profiles

PartProfilesConstructorsBase PDF version
PDF/A-1 (ISO 19005-1)1A, 1BNewProfile1A, NewProfile1B1.4
PDF/A-2 (ISO 19005-2)2A, 2B, 2UNewProfile2A, NewProfile2B, NewProfile2U1.7
PDF/A-3 (ISO 19005-3)3A, 3B, 3UNewProfile3A, NewProfile3B, NewProfile3U1.7
PDF/A-4 (ISO 19005-4)4, 4E, 4FNewProfile4, NewProfile4E, NewProfile4F2.0

Every constructor takes a pointer to that part’s options struct and accepts nil, which fills in the defaults. All of them satisfy pdfa.Profile, which is model.StandardImplementer (ApplyStandard, ValidateStandard, StandardName) plus Part() and Conformance().

Choosing a conformance level

For parts 1 to 3 the trailing letter is the conformance level, and it is cumulative:

LevelRequires
BReliable visual reproduction. Fonts embedded, color unambiguous.
UEverything in B, plus text that maps to Unicode through ToUnicode.
AEverything in U, plus a tagged logical structure and reading order.

Validation honors that hierarchy through the pdfaid:conformance check: a file marked A passes the B and U checks, and a file marked U passes the B check. The reverse is not true, which is why running one file against all the profiles of a part usually reports failures for the stricter ones.

Part 4 drops the letters and uses a different scheme. Plain PDF/A-4 always requires Unicode-mappable text but does not require tagging, so it sits roughly where 2U and 3U did. 4E targets engineering content, and 4F targets files carrying embedded attachments. There is no hierarchy between them: the pdfaid:conformance value has to match the profile exactly, so a plain PDF/A-4 file always reports a violation of rule 6.7.3-3 when validated against Profile4E or Profile4F.

Picking a part is mostly about what the document contains. PDF/A-1 is the most restrictive and the most widely demanded by archives and regulators; it forbids transparency, layers, LZW and embedded files outright. PDF/A-2 lifts those restrictions, allowing transparency, optional content and JPEG 2000, and permits attachments as long as they are themselves PDF/A. PDF/A-3 is PDF/A-2 with that last restriction removed, which is the reason it exists: it is the part used for hybrid invoices such as ZUGFeRD and Factur-X, where an XML payload rides along inside the PDF. PDF/A-4 is built on PDF 2.0 and is the one to pick for new documents when nothing downstream insists on an older part.

Applying a standard

PdfWriter.ApplyStandard registers the profile. Nothing happens at that point; the document is rewritten during Write, after the writer has assembled all its parts.

reader, file, err := model.NewPdfReaderFromFile(inputPath, nil)
if err != nil {
    return err
}
defer file.Close()

pdfWriter, err := reader.ToWriter(nil)
if err != nil {
    return err
}

pdfWriter.ApplyStandard(pdfa.NewProfile2B(nil))

return pdfWriter.WriteToFile(outputPath)

What every profile’s applier does, regardless of part: raises the PDF version to the part’s minimum, fills in CreationDate and ModDate on the document information dictionary, switches the trailer to a content-hash-based /ID when the input has none, writes an XMP packet carrying the pdfaid part and conformance, adds an output intent, embeds fonts that were only referenced, rewrites annotation flags, strips forbidden actions, and clears NeedAppearances on the AcroForm dictionary.

Three of those are worth knowing about before running it on a batch.

Font substitution reads the host system. When a font is referenced but not embedded, the applier looks for a replacement among the fonts installed on the machine doing the conversion, trying the original name first and then Times New Roman, Arial and DejaVu Sans. Only .ttf and .ttc files are considered. If none of those turn up, ApplyStandard fails with no matching font found in the system and the error surfaces from Write. The substitute is not metrically matched to the original, so line breaks can move. Fonts using Identity-H or Identity-V, and CIDFontType2 fonts that carry a CIDToGIDMap, are left alone.

Annotation flags are rewritten on every annotation on every page, not only on the ones that break a rule. The Print bit is set, and Invisible, Hidden, NoView and ToggleNoView are cleared. Annotations that were deliberately hidden become visible and printable in the output.

The output intent is only added when the catalog does not already have one. Which intent gets added depends on the device color spaces the document uses, and when it uses more than one, the applier converts everything to a single space first. CMYKDefaultColorSpace in the options struct selects the target for that conversion: false, the default, converts to RGB and adds an sRGB output intent, and true converts to CMYK. Gray counts as present in every document, so any file that also uses RGB or CMYK content takes this conversion path.

The rest of the options struct is small. Now overrides the clock, which is what you want for reproducible output. Xmp is an XmpOptions value carrying copyright, the document and instance identifiers, and the XMP marshaling indent.

Validating a standard

Validation needs a CompliancePdfReader rather than a plain PdfReader. It parses the file with compliance mode set, which records the low-level syntactic detail the rules about headers, trailers and cross-reference formatting depend on. A regular reader throws that away and will not type-check here.

detailedReader, err := model.NewCompliancePdfReader(inputFile)
if err != nil {
    return err
}

if err := pdfa.NewProfile2B(nil).ValidateStandard(detailedReader); err != nil {
    fmt.Printf("not conforming: %v\n", err)
}

A nil return means the profile found nothing wrong. Otherwise the error is a pdfa.VerificationError, whose ViolatedRules field holds one ViolatedRule per failed check, with the clause number in RuleNo and the requirement text in Detail. Rules are sorted by clause number. The error’s Error() method prints the standard name followed by every violation, which is what the examples show.

One reader can be validated against several profiles in turn, which is what the example programs do. In v5, NewCompliancePdfReader takes an io.ReaderAt and derives the size itself; NewCompliancePdfReaderAt is the explicit-size form for sources where that fails.

Limitations

The implementation is explicitly experimental, and validation is not complete. A number of clauses are present in the source as unimplemented placeholders, among them parts of the object syntax checks, the rendering intent check, some interactive form appearance rules, and the graphics state nesting and CID range implementation limits. A file that passes ValidateStandard has passed the rules UniPDF checks, which is a subset of the standard. For a conformance claim, run the output through an independent validator such as veraPDF.

Applying a standard is best effort. It fixes what can be fixed by rewriting structure and metadata, and it cannot invent content that was never there. The clearest case is the A conformance levels: the applier sets MarkInfo Marked to true and inserts an empty StructTreeRoot when the catalog has none, but it does not tag the page content. An untagged document run through NewProfile1A or NewProfile2A comes out with the catalog entries a tagged file would have and no tags. Genuine A-level conformance means producing tagged content in the first place, which is what the accessibility guides cover.

Applying a profile and then validating with the same profile is not a guarantee of a clean result, and is worth doing as a check rather than assuming.

Where to look

GuideCovers
PDF/A-1The most restrictive part. No transparency, no layers, no attachments.
PDF/A-2Transparency, optional content, JPEG 2000, PDF/A attachments.
PDF/A-3PDF/A-2 with arbitrary embedded files, for hybrid invoices.
PDF/A-4The PDF 2.0 part, with the 4E and 4F variants.

For accessible tagged output and PDF/UA, see accessibility. For reducing file size, which is a separate concern from conformance, see PDF optimization.

Last updated on