---
title: "Fonts"
description: "Write text in any font and any language, use the fonts a document already has, and show typed text in a browser exactly as the engine will draw it."
source: "https://www.embedpdf.com/docs/engine/documents/fonts"
---

# Fonts

Text the engine draws (a free text annotation, a filled-in form field, a redaction label) is
written in the font you name in `fontFamily`. Characters that font doesn't have come from
fallback fonts, so text in any language shows, and the PDF looks the same in every PDF app:

```ts
await page.annotations.create({
  subtype: 'free-text',
  box: { x: 60, y: 132, width: 300, height: 60 },
  contents: 'Order 66 שולם',
  fontFamily: 'helvetica',
});
```

"Order 66" is drawn in Helvetica, the Hebrew in a Hebrew fallback font, right to left.

## The fonts you can use

`doc.fonts.list()` returns every font you can name in a document:

```ts
const { fonts } = await doc.fonts.list();

for (const font of fonts) {
  console.log(font.name, font.source, font.canChoose);
  // 'helvetica' 'standard' true
  // 'noto-sc' 'engine' true
  // 'MinionPro-Regular' 'document' false
}
```

| `source`     | The fonts                                                                                          | Their `name`                                                                                                    |
| :----------- | :------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------- |
| `'standard'` | The 14 standard PDF fonts. They only cover Latin text.                                             | `'helvetica'`, `'times-bold'`, …                                                                                |
| `'engine'`   | The engine's fonts: the ones you [register](#register-a-font) locally, your server's on the cloud. | the `key` they were given                                                                                       |
| `'document'` | Fonts the PDF already has.                                                                         | its PostScript name, such as `'MinionPro-Regular'`; without one, its family and style, such as `'Inter,Medium'` |

A PDF can hold one font several times: each editor that wrote text in it may have added its own
copy. Every copy under one name is one entry, with the copies in its `programs`, so no two fonts in
the list share a name.

Pass a font's `name` as `fontFamily` to write with it. A font with `canChoose: false` can't be
named for new text; `restrictions` says why (see [What a font allows](#what-a-font-allows)). A
name that isn't in the list fails with `InvalidArg`: the engine doesn't quietly swap in another
font.

| Field            | What it is                                                                                  |
| :--------------- | :------------------------------------------------------------------------------------------ |
| `name`           | What you pass as `fontFamily`.                                                              |
| `source`         | `'standard'`, `'engine'` or `'document'`.                                                   |
| `familyName`     | The family, such as `'Minion Pro'`.                                                         |
| `weight`         | `100` to `900`.                                                                             |
| `italic`         | Whether it's an italic.                                                                     |
| `postScriptName` | The font's PostScript name, such as `'MinionPro-Regular'`; `null` when it has none.         |
| `canChoose`      | Whether new text can be written in it.                                                      |
| `restrictions`   | Why it can't be chosen, or why its program can't be downloaded; empty when neither applies. |
| `programs`       | The font files behind it; the first is the one new text uses. See [Programs](#programs).    |

Next to `fonts`, the list has `fallbacks`, the names of the fonts that
[fill in missing characters](#text-in-any-language) in the order they were added, and
`lastResort`, the program that draws a box for a character no font has.

The list changes when a write adds a font to the document, and, on the local engine, when you
register fonts. Each change raises the document event `fonts.changed`:

```ts
doc.events.on('fonts.changed', async () => {
  const { fonts } = await doc.fonts.list();
});
```

## Register a font

To write Chinese, Cyrillic, Greek or your brand typeface, register the font with the engine and
use it by a key you choose:

```ts
const response = await fetch('/fonts/NotoSansSC-Regular.otf');

await engine.fonts.register({
  key: 'noto-sc',
  familyName: 'Noto Sans SC',
  data: new Uint8Array(await response.arrayBuffer()),
});

await page.annotations.create({
  subtype: 'free-text',
  box: { x: 60, y: 132, width: 300, height: 60 },
  contents: '请检查这一部分',
  fontFamily: 'noto-sc',
});
```

`register()` takes the font file (TTF or OTF) and a `key`:

| Field        | What it is                                                   | Default            |
| :----------- | :----------------------------------------------------------- | :----------------- |
| `key`        | Your name for the font. You use it wherever you pick a font. | required           |
| `data`       | The font file's bytes, a `Uint8Array` or an `ArrayBuffer`.   | required           |
| `familyName` | The family the PDF names the font by.                        | read from the file |
| `weight`     | `100` to `900`.                                              | read from the file |
| `italic`     | Whether it's the italic style.                               | read from the file |

Registered fonts belong to the engine, not to one document: every document opened on the engine
can use them, until you clear them or destroy the engine. Registering a key a second time does
nothing and returns the first font, so it's safe to call on every page load. A file the engine
can't read fails with `InvalidArg`, and so does one of the 14 standard names as a key.

To load fonts while the engine starts, before the first document opens, pass them to
`localEngine()` with a `url` or with `data`:

```ts
const engine = localEngine({
  fonts: [{ key: 'brand-sans', url: '/fonts/BrandSans-Regular.ttf' }],
  fallbackFonts: [{ key: 'noto-sc', url: '/fonts/NotoSansSC-Regular.otf' }],
});
```

`fonts` are only used when you name them; `fallbackFonts` also
[fill in missing characters](#text-in-any-language).

```ts
engine.fonts.list(); // the fonts you registered, in order (no await: it's in memory)
await engine.fonts.clearFallbacks(); // empty the fallback list; the fonts stay registered
await engine.fonts.clear(); // unregister every font and empty the fallback list
```

> The engine keeps every registered font in memory, and a CJK font is several megabytes. Register
> the few fonts you use rather than a whole library.

## Text in any language

The engine picks a font for each character:

- the font you named, when it has the character;
- otherwise the first fallback font that has it. Yours come before the ones the engine comes with;
  among each, the closest in style (weight, then italic) first, then the order they were added in.

It then lays the text out the way the language needs: Hebrew and Arabic right to left, mixed
with numbers and Latin words in the right order; Arabic letters joined, Devanagari letters
combined. The PDF holds the text in the order it was typed, so copying and searching it works.

Hebrew and Arabic work with no setup: the engine has Noto Sans Hebrew and Noto Naskh Arabic as
fallback fonts. For other languages, add a fallback font:

```ts
await engine.fonts.register({ key: 'noto-sc', familyName: 'Noto Sans SC', data });
await engine.fonts.addFallback('noto-sc');

// "Hello " is drawn in Helvetica, "世界" in Noto Sans SC.
await page.annotations.create({
  subtype: 'free-text',
  box: { x: 60, y: 192, width: 300, height: 60 },
  contents: 'Hello 世界',
});
```

`clearFallbacks()` removes the Hebrew and Arabic fonts along with yours, and
`localEngine({ defaultFallbackFonts: false })` never loads them.

A character no font has is drawn as a box, a small empty rectangle, so the text never silently
loses a character. The write tells you which ones:

```ts
const { meta } = await page.annotations.create({
  subtype: 'free-text',
  box: { x: 60, y: 252, width: 300, height: 60 },
  contents: 'Hello 世界',
});

meta.missingCharacters; // ['世', '界'] when no font has them
```

Every write that draws text says so in `meta.missingCharacters`: creating or changing a free text
annotation or a redaction's label, filling in or resetting a form field, applying a redaction whose
label wasn't drawn yet. It's left out when every character was drawn. In a change
(`doc.apply`), each item's `meta` says what its op drew, and the change's `meta` what they all did.

## Fonts the document already has

`doc.fonts.list()` includes every font the document uses, on its pages, in its annotations and in
its form. Text in a document's font can be added when the document holds the whole font and its
licence allows editing:

```ts
const { fonts } = await doc.fonts.list();
const minion = fonts.find((font) => font.familyName === 'Minion Pro' && font.canChoose);

if (minion) {
  await doc.forms.updateWidget(widget.ref, { fontFamily: minion.name });
}
```

Most PDFs carry only part of a font, the characters their text uses (a subset). Such a font can't
be chosen for new text (`restrictions: ['subset']`): a character it lacks has nowhere to come
from.

A box that already uses a font keeps it, whatever the font is, as long as the font has the
characters you type: filling in a form field whose font is a subset of Minion Pro writes Minion
Pro. When you type a character the font lacks, the box moves to another font as a whole: the
same family from the engine's fonts if it has it, then the whole font in the document, then the
standard font closest to it. [`resolve()`](#show-typed-text-in-a-browser) tells you before the
write.

## Show typed text in a browser

The viewer shows text being typed in the font the engine will write it in, and so do the headless
adapters' layers with the [fonts plugin](https://www.embedpdf.com/docs/headless/documents/fonts), which also gives you a
font's CSS in one call. If you build your own editor without them, ask the engine the same question
the viewer does: which fonts will it draw this text in?

```ts
const { runs, missingCharacters } = await doc.fonts.resolve({
  annotation: annotation.ref, // the box being edited; leave it out for a new one
  runs: [{ text: 'Order 66 שולם', fontFamily: 'helvetica' }],
});
```

Each run you send comes back with the font the engine will use for it:

| Field         | What it is                                                                                   |
| :------------ | :------------------------------------------------------------------------------------------- |
| `font`        | The font's `name`: the one you asked for, or the one the box moves to.                       |
| `weight`      | Its weight.                                                                                  |
| `italic`      | Whether it's an italic.                                                                      |
| `substituted` | `true` when it isn't the font you asked for.                                                 |
| `program`     | The font file the engine will use, by hash; `null` when a browser can't get one.             |
| `fallbacks`   | The fallback fonts' files, by hash, in the order the engine tries them for this run's style. |

`missingCharacters` lists the characters no font has, which the engine will draw as boxes.

A run's text can also name its font by family, the way [rich text](https://www.embedpdf.com/docs/engine/annotations/free-text#rich-text)
does: `{ text: '世界', family: 'Noto Sans SC', weight: 400, italic: false }`.

Then download each file, add it to the browser under a family of its own, and list the families
in the same order, ending with the last-resort font that draws the boxes:

```ts
const { lastResort } = await doc.fonts.list();
const [run] = runs;
const hashes = [run.program, ...run.fallbacks, lastResort.displayHash].filter((hash) => hash !== null);

for (const hash of hashes) {
  const face = new FontFace(`font-${hash}`, await doc.fonts.downloadProgram(hash));
  document.fonts.add(await face.load());
}

const input = document.querySelector('textarea')!; // your editor
input.style.fontFamily = hashes.map((hash) => `"font-${hash}"`).join(', ');
input.style.fontSynthesis = 'none'; // never fake a bold or italic the engine won't draw
```

Ask again when the text gets a character outside what its fonts cover; `doc.fonts.getProgram()`
says what a file covers. After the write, the engine's own picture of the box is exact.

### Programs

A font's file is its program. `programs` on each font, and `getProgram(hash)`, describe them:

| Field                 | What it is                                                                                                         |
| :-------------------- | :----------------------------------------------------------------------------------------------------------------- |
| `displayHash`         | SHA-256 of the file a browser loads. You download and name programs by it.                                         |
| `sourceHash`          | SHA-256 of the file the engine lays text out with and puts in the PDF; `null` when the document doesn't hold one.  |
| `embedded`            | `'whole'`, `'subset'`, or `'none'` when the PDF only names the font.                                               |
| `format`              | `'truetype'`, `'opentype'`, `'cff'`, `'type1'` or `'type3'`; `null` when nothing is embedded.                      |
| `embeddingPermission` | What the font's licence says: `'installable'`, `'editable'` or `'preview-and-print'`; `null` when it says nothing. |
| `canDownload`         | Whether `downloadProgram()` hands out the file.                                                                    |

`getProgram(hash)` adds what an editor needs to lay text out the way the engine does:

| Field     | What it is                                                                                 |
| :-------- | :----------------------------------------------------------------------------------------- |
| `covers`  | The characters the engine can write with it, as ranges of code points: `{ start, count }`. |
| `ascent`  | How far the font reaches above the baseline, in thousandths of the font size.              |
| `descent` | How far below, as a negative number.                                                       |

`downloadProgram(hash)` refuses a program with `canDownload: false` with `InvalidArg`, and a hash
the document's fonts don't name with `NotFound`.

## Document font settings

Two settings per document shape the text the engine draws from then on. They're not saved in the
file:

```ts
await doc.fonts.setEmbeddingPolicy('full');
await doc.fonts.setTypographicFeatures(true);
```

| Setting                  | What it does                                                                                                            | Default     |
| :----------------------- | :---------------------------------------------------------------------------------------------------------------------- | :---------- |
| `setEmbeddingPolicy`     | How much of a font goes into the PDF: `'subset'` (only the characters used), `'full'` (the whole font), or `'default'`. | `'default'` |
| `setTypographicFeatures` | Kerning and ligatures for Latin text in rich text.                                                                      | off         |

`'default'` embeds only the characters used for annotation text, and the whole font for form
fields. A font whose licence doesn't allow a partial copy is always embedded whole, and a font
already in the document is never added again.

Kerning and ligatures are off by default because Acrobat draws annotation text without them, and
the engine matches what Acrobat draws.

## Good to know

### What a font allows

Three questions, answered separately for every font:

- **What its licence says** (`embeddingPermission`). A font file says whether it may be embedded
  for editing or only for showing.
- **Whether new text can be written in it** (`canChoose`).
- **Whether its program can go to a browser** (`canDownload`).

| The font                                                         | `canChoose`           | `canDownload` | `restrictions`                   |
| :--------------------------------------------------------------- | :-------------------- | :------------ | :------------------------------- |
| A standard font                                                  | yes                   | yes           |                                  |
| An engine font                                                   | yes                   | yes           |                                  |
| An engine font whose licence is preview-and-print                | once you authorize it | yes           | `'preview-and-print'` until then |
| A server font your server keeps to itself                        | yes                   | no            | `'not-served'`                   |
| A whole TrueType or OpenType font in the document                | yes                   | yes           |                                  |
| The same, preview-and-print                                      | no                    | no            | `'preview-and-print'`            |
| A subset in the document                                         | no                    | yes           | `'subset'`                       |
| A Type 1 or CFF font in the document                             | no                    | yes           | `'unsupported-format'`           |
| A font whose characters can't be told apart (no Unicode mapping) | no                    | no            | `'no-unicode-map'`               |
| A Type 3 font (its letters are drawings)                         | no                    | no            | `'unsupported-format'`           |
| A font the PDF only names                                        | no                    | no            | `'not-embedded'`                 |

A font you can't choose is still kept by a box that has it (see
[Fonts the document already has](#fonts-the-document-already-has)). A program with
`canDownload: false` is shown while typing in a similar font, and the engine's picture follows
the write.

A font whose licence is preview-and-print can show text a document already has, but can't be
used for new text until you say your licence allows it:

```ts
await engine.fonts.authorizeEditing('brand-serif');
```

A font whose licence forbids embedding is refused when you register it.

### The standard fonts look the same everywhere

The engine draws the 14 standard fonts with its own built-in faces, on every computer and every
server, whatever fonts are installed. Their programs come from the same faces, so a browser shows
them exactly as the engine draws them.

### Fonts a browser can't load

PDFs, forms especially, often carry their fonts as Type 1 or CFF, which browsers can't load. For
those the engine builds a program a browser can load, with the same letters, the same widths and
the same characters, and `displayHash` names it. That's why a program has two hashes: they're the
same for a TrueType or OpenType font and differ for these.

Two of a PDF's fonts can read one such file with other widths or character codes. Each gets a
program of its own, so the font lists the file twice under one `sourceHash`, and `resolve()` gives a
box the program its own font reads.

### A hash is not a permission

The same file in two documents has the same hash, but each document decides what may be done with
it: a font allowed in one document isn't allowed in another because its hash matches.

### Variable fonts

A variable font is fixed to one style when you register it: the `weight` you give, or the font's
default. The PDF then carries an ordinary font of that style, the font's `instanced` field is
`true`, and its program is that fixed font.

### Several fonts at once

`registerAll([...])` registers a list of fonts in order and returns them in the same order.
