Archiving and long-term storage / Embedded fonts in an archival PDF
Why an archival PDF has to embed every font it uses
Every conformance standard requires the actual font program to be inside the file, because a font named but not embedded is a promise that some other machine will have it, and archiving exists precisely because that promise fails.
Where this API stands
This API does part of what the standard asks for, and the paragraph below is the whole of it rather than a summary of it. The API has no font embedding option, and it does not need one: what gets embedded follows from what the document actually loaded during the render. This is the archival requirement your own markup controls most directly. A stylesheet that declares @font-face with a real WOFF or TTF file served over HTTPS gives the renderer a font program to embed. A stylesheet that writes font-family: Helvetica, Arial, sans-serif and stops is asking for whatever the rendering environment happens to have, which is a different thing on every machine and is exactly what the requirement forbids relying on. The `inject_css` option is a practical way to add a @font-face block to markup you do not control.
What the standard requires
The requirements as a checker enforces them, rather than as a procurement document paraphrases them. A file satisfies all of these or it satisfies none of them.
- Every font used for visible text embedded as a font program, including the fourteen standard faces that PDF once let a reader substitute.
- Subsetting permitted and normal. A subset is identified by a six uppercase letter tag and a plus sign before the family name, as in ABCDEE+SourceSans.
- The embedded program legally embeddable: a font whose licence bits forbid embedding fails validation regardless of what your licence agreement says.
- Consistent width information, so the widths declared in the font dictionary match the widths in the embedded program.
- A CIDToGIDMap for composite fonts, and for conformance levels a and u a ToUnicode map as well.
- No font referenced by name only. This is the check that fails documents styled with a bare font-family and no @font-face rule.
Who asks for this
Every archiving standard and every print handover standard, which is why this is the requirement that fails the most files in practice. It is also the one that fails silently on a screen: a missing font is substituted by the reader, the document opens, the layout shifts slightly, and nobody notices until a validator or a printer says no.
What it looks like in a file
The concrete form of the thing being described, so you can recognise it in a document you have been handed rather than only in a specification.
<style>
/* Embeddable because there is a real file behind it. Self-hosted, so no
third-party stylesheet can fail and leave you with a substitution. */
@font-face {
font-family: "Source Sans 3";
src: url("https://assets.example.com/fonts/SourceSans3-Regular.woff2") format("woff2");
font-weight: 400;
font-style: normal;
/* swap keeps the render from stalling, but a fallback that gets used is a
fallback that gets embedded, so keep the stack short and deliberate. */
font-display: swap;
}
body {
/* The generic at the end is a safety net, not a plan. If it is ever reached,
the document you archived is not the document you designed. */
font-family: "Source Sans 3", sans-serif;
}
</style>What to do about it
Serve the fonts yourself and declare them. Host the files rather than pointing at a third party stylesheet, because a font that fails to load produces a substituted render rather than an error, and set a generous enough timeout that a slow font fetch does not silently fall back. Then verify on the output rather than trusting the source: the list of fonts in the finished PDF is the only evidence that matters.
How to check a file rather than assume
pdffonts prints one line per font with an embedded column, and that single command answers the question faster than any validator. Anything showing as not embedded, or any name without a subset tag where you expected one, is the problem. Acrobat's document properties fonts tab shows the same information if you would rather look than type.
The mistake people make here
Assuming a font loaded because it renders correctly during development. The machine that renders your document is not your laptop, and a family installed locally will silently substitute in a rendering environment that has never heard of it. The tell is subtle: correct-looking text with slightly different line breaks. Check pdffonts on a file rendered by the real pipeline, not by your browser.
Frequently asked
Does this API produce Embedded fonts in an archival PDF?
Partly, and the page says how far. The API has no font embedding option, and it does not need one: what gets embedded follows from what the document actually loaded during the render. This is the archival requirement your own markup controls most directly. A stylesheet that declares @font-face with a real WOFF or TTF file served over HTTPS gives the renderer a font program to embed. A stylesheet that writes font-family: Helvetica, Arial, sans-serif and stops is asking for whatever the rendering environment happens to have, which is a different thing on every machine and is exactly what the requirement forbids relying on. The `inject_css` option is a practical way to add a @font-face block to markup you do not control.
Can I turn on the pdf_a option and be done?
No. The option writes an XMP packet declaring PDF/A-1b into the document catalogue, and the render response comes back carrying a warning that says in as many words that this is best-effort and that full conformance requires tagged structure and an embedded ICC profile. It is a claim written into the file, not a certificate earned by it, and a validator checks the claim against the bytes. Read the warning array in the response rather than taking the absence of an error as a pass.
Should I convert before rendering or after?
After, always. A conformance converter rewrites colour, embeds profiles, sets boxes and stamps metadata on a finished PDF. Trying to satisfy a conformance target from the source side means guessing at what the converter would have done, and the guesses that go wrong are expensive: flattening transparency you did not need to flatten changes what the page looks like.
Standards this one is confused with
The neighbours that matter are the ones people mistake for this, not the ones that sort next to it.
What PDF/A-1b requires, and how far this API gets you
The oldest and strictest archiving profile: a PDF 1.4 file with everything it needs to render sealed inside it, and nothing in it that a future reader might not understand.
PDF/A-2u and the Unicode rule behind broken text extraction
Level b plus one extra rule: every character in the document must map to a Unicode value, so the text can be searched, indexed and copied rather than merely looked at.
Checking a PDF really is PDF/A before you trust it
Conformance is a property of the bytes, not of the metadata that claims it, and the only way to know which one you have is to run the file through a validator that reports clauses.
PDF/A-1a: the tagging requirement that 1b does not have
Everything PDF/A-1b requires, plus a complete logical structure tree, a declared document language and a Unicode mapping for every glyph, so the file is readable by a machine and not only by an eye.
PDF/A-2b: what archiving allows once it moves to PDF 1.7
The second part of the archiving standard, rebased on PDF 1.7, which quietly permits most of the things PDF/A-1 banned and is therefore the realistic target for a document produced from a stylesheet.
Every standard, with what this API does about it
The full list, grouped by what the requirement is for, each marked with how far this API gets.
What this API actually does
One page per option and endpoint that exists, with nothing that does not.
Conformance is decided after a file exists. Render the document, then check it, then convert only what the checker actually flags.