← Back to blog

Forme 0.16.0: real PDF/A conformance, and a correction

PDF/A-2 at levels 2b, 2u, and 2a, verified by veraPDF in CI — plus an honest correction: PDF/A output before 0.16.0 was never conformant.

Forme 0.16.0 produces PDF/A-2 conforming archival documents — levels 2b, 2u, and 2a — verified by veraPDF in CI, and composable with PDF/UA-1 so a single file can be both archival and accessible. That combination is the configuration government, education, and healthcare archives actually require.

Before the feature tour, a correction that matters more than the feature.

The correction: PDF/A output before 0.16.0 was not valid PDF/A

If you used pdfa: "2b" (or "2a") in 0.15.0 or earlier, those files are not valid PDF/A. They carry an OutputIntent whose ICC profile was invalid, so no conformant PDF/A reader would have accepted them as archival.

The cause is almost embarrassing enough to be instructive. The embedded sRGB profile (srgb2014.icc) was never an ICC profile at all — the curl that fetched it around 0.9.0 returned a Cloudflare "Just a moment…" HTML challenge page instead of the binary. It was embedded unchecked and shipped in every build since. Nothing caught it because nothing validated the output.

It's now replaced with a real, generated sRGB profile (~588 bytes, ICC v4.3, Little CMS / MIT-licensed) and validated in CI — and a test asserts the embedded bytes parse as a valid ICC profile, so this specific failure can't recur.

How to check your files: anything produced with pdfa before 0.16.0 fails veraPDF's PDF/A profile — verapdf -f 2b your-file.pdf reports FAIL. Re-render with 0.16.0 (same input, same options) and it passes.

This is also why the compliance claims on this site now come with receipts: every claim names the validator, the corpus, and the CI gate that enforces it. "Nothing caught it because nothing validated it" is a failure mode we've opted out of permanently.

What verified means here

The same nine-document corpus the PDF/UA gate uses — five shipped templates plus four HTML fixtures — passes veraPDF's PDF/A-2b and PDF/A-2a profiles, rendered as PDF/A and PDF/UA-1 together, on every commit. The claim is enforced, not observed. Getting there took:

  • A real sRGB OutputIntent — the fix above.
  • PDF/A XMP metadatapdfaid part and conformance, plus the pdfaExtension description for the PDF/UA identification schema, so both standards' metadata coexist.
  • A deterministic trailer /ID — derived from a content hash rather than a timestamp or random bytes. Identical input still produces byte-identical output, so Forme's determinism guarantee (native == WASM, reproducible builds) holds in PDF/A mode too — a property archival pipelines care about.
  • Composition fixes/F (Print) on link annotations, and the font-embedding check accepts the PDF/UA metric-compatible substitution, so pdfa + pdfUa no longer conflict.

Layout is untouched: archival output is geometry-identical to standard output.

Using it

import { standardFonts } from '@formepdf/fonts-standard';

<Document pdfa="2a" pdfUa lang="en-US" fonts={standardFonts()}>

</Document>

From the HTML path: pdfA in renderHtml options, or --pdf-a <level> on the forme-html CLI.

PDF/A requires every font to be embedded, which the built-in standard fonts aren't — that's what @formepdf/fonts-standard provides: metric-compatible replacements, so the archival document lays out exactly like the one you previewed. If no embeddable font is registered, a PDF/A render fails by name with the remedy rather than emitting a file whose metadata claims conformance it doesn't have. The refusal is the feature.

Docs