Forme now 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 (the configuration
government/education/healthcare archives actually require).
⚠️ Correction — PDF/A output was never conformant before this release
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 embedded sRGB profile (srgb2014.icc) was never an ICC profile. A 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 and validated in CI — and a
test asserts the embedded bytes are a valid ICC profile so this can't recur.
How to check: any file produced with pdfa before 0.16.0 will fail veraPDF's
PDF/A profile — verapdf -f 2b your-file.pdf reports FAIL. Re-render those
files with 0.16.0 (same input, same options) and they'll pass.
PDF/A-2b / 2u / 2a — verified
The same nine-document corpus the PDF/UA gate uses (five shipped templates + four
HTML fixtures) passes veraPDF's PDF/A-2b and PDF/A-2a profiles. Getting there:
- A real sRGB OutputIntent — a generated, license-clean sRGB profile (~588
bytes, ICC v4.3, Little CMS / MIT) embedded in the engine, replacing the
invalid one above. - PDF/A XMP metadata — pdfaid part/level, plus the pdfaExtension description
for the PDF/UA identification schema so PDF/A and PDF/UA compose. - A deterministic trailer
/ID— derived from a content hash, not 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. /F(Print) on link annotations, and the font-embedding check now accepts
the pdfUa metric-compatible substitution — sopdfA + pdfUano longer throws.- No width-consistency issue: the AFM-widths-by-construction font design
(with its carve-out) satisfies PDF/A's width check — geometry stays identical
across standard and archival output.
Archival and accessible
PDF/A composes with PDF/UA-1. Set both:
import { standardFonts } from '@formepdf/fonts-standard';
<Document pdfa="2a" pdfUa lang="en-US" fonts={standardFonts()}>…</Document>Every corpus file is CI-gated against PDF/A-2b, PDF/A-2a, and PDF/UA-1
together (scripts/verify-pdfa.mjs) — the claim is enforced, not observed.
API
- JSX:
<Document pdfa="2b" | "2u" | "2a">(new:"2u"). - HTML:
pdfAinrenderHtmloptions;--pdf-a <level>on theforme-htmlCLI. - Needs an embeddable font (
@formepdf/fonts-standard). If none is registered, a
PDF/A render fails by name with the remedy rather than emitting a file that
falsely claims conformance.