PDFPipe

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.

html
<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.

Conformance is decided after a file exists. Render the document, then check it, then convert only what the checker actually flags.