---
title: "Fonts — Vue"
description: "List the fonts a document can be written in, see what each one allows, and draw text in a browser in the font the engine writes it with."
framework: "Vue"
source: "https://www.cloudpdf.com/docs/headless/vue/documents/fonts"
---

# Fonts

Text the engine writes, in a free text box, a form field or a redaction label, is in the font its
`fontFamily` names. Register `fontsPlugin()` to list the fonts a document can be written in, and to
draw text in a browser in the very font the engine will write it with:

**`font-select.vue`**

```vue
<script setup lang="ts">
import { computed } from 'vue';
import { useFontsState } from '@embedpdf/vue/fonts';

const font = defineModel<string>({ required: true });
const { fonts } = useFontsState();
const choosable = computed(() => fonts.value?.fonts.filter((entry) => entry.canChoose) ?? []);
</script>

<template>
  <select v-model="font">
    <option v-for="entry in choosable" :key="entry.name" :value="entry.name">
      {{ entry.name }}
    </option>
  </select>
</template>
```

The annotation and form layers use the plugin by themselves: with it, text being typed shows in
the fonts the engine writes it with; without it, in the standard font the engine falls back to.
Nothing is read when a document opens: the fonts are read the first time something asks for them.

## The fonts you can use

`fonts` has
every font, with where it comes from:

| `source`     | The fonts                                                                          |
| :----------- | :--------------------------------------------------------------------------------- |
| `'standard'` | The 14 standard PDF fonts, drawn the same everywhere. They only cover Latin text.  |
| `'engine'`   | The engine's own fonts: the ones you register locally, your server's on the cloud. |
| `'document'` | The fonts the PDF already has.                                                     |

A font's `name` is what `fontFamily` takes. `canChoose` says whether new text can be written in it,
and when it can't, `restrictions` says why:

| Restriction            | Why                                                                        |
| :--------------------- | :------------------------------------------------------------------------- |
| `'subset'`             | The PDF holds only the characters its text uses.                           |
| `'not-embedded'`       | The PDF only names the font.                                               |
| `'preview-and-print'`  | Its licence allows showing and printing, not editing.                      |
| `'unsupported-format'` | A Type 1, CFF or Type 3 font: new text is written in TrueType or OpenType. |
| `'no-unicode-map'`     | The font doesn't say which character each of its letters is.               |
| `'not-served'`         | A server font whose file stays on the server. It can still be chosen.      |

A box keeps the font it has either way. Every field of a font is on the engine's
[Fonts](https://www.cloudpdf.com/docs/engine/documents/fonts#the-fonts-you-can-use) page.

The list keeps up by itself. A write that puts a font in the document, such as Hebrew typed in a
box, which the engine fills in from a Hebrew font, reads it again:

**`App.vue`**

```vue
<script setup lang="ts">
import { DocumentGate, Viewer } from '@embedpdf/vue/runtime';
import type { OpenInput } from '@embedpdf/vue/runtime';
import { interactionPlugin } from '@embedpdf/vue/interaction';
import { annotationPlugin } from '@embedpdf/vue/annotation';
import { fontsPlugin } from '@embedpdf/vue/fonts';
import { cloudEngine } from '@cloudpdf/engine';
import Fonts from './Fonts.vue';

import '../list.css';

const engine = cloudEngine();
const plugins = [interactionPlugin(), annotationPlugin(), fontsPlugin()];

const welcome: OpenInput = { kind: 'grant', grant: 'shr_gvpJcG1ULUMon19C0D1wODSY' };
</script>

<template>
  <Viewer :engine="engine" :plugins="plugins" :initial-documents="[{ source: welcome }]">
    <DocumentGate>
      <template #fallback><p class="loading">Loading…</p></template>
      <Fonts />
    </DocumentGate>
  </Viewer>
</template>
```

**`list.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --surface: #ffffff;
  --canvas: #f7f9fc;
  --accent: #0b6bcb;
  --ready: #1f8a4c;
  --ready-tint: #e8f6ee;
  --waiting: #a86a00;
  --waiting-tint: #fdf3e1;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The fonts, in a card */
.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
  overflow: hidden;
}

.panel-header {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  background: var(--canvas);
  border-bottom: 1px solid var(--line);
}

.panel-title {
  margin: 0 auto 0 0;
  font-size: 14px;
  font-weight: 650;
  color: var(--ink);
}

.button {
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  color: var(--accent);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  padding: 5px 10px;
  cursor: pointer;
}

.button:hover {
  border-color: var(--accent);
}

/* The load state: loading, ready, forbidden or error */
.status {
  font-size: 11.5px;
  font-weight: 600;
  border-radius: 999px;
  padding: 3px 9px;
  color: var(--waiting);
  background: var(--waiting-tint);
}

.status[data-status='ready'] {
  color: var(--ready);
  background: var(--ready-tint);
}

/* A group per source, its fonts as chips */
.group {
  padding: 12px 16px;
  border-bottom: 1px solid var(--line);
}

.group:last-child {
  border-bottom: 0;
}

.group-title {
  margin: 0 0 8px;
  font-size: 12px;
  font-weight: 650;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.chip {
  display: flex;
  flex-direction: column;
  gap: 2px;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 5px 9px;
  font-size: 12.5px;
  color: var(--ink);
}

/* A font new text can't be written in: greyed, with why */
.chip[data-choosable='false'] {
  color: var(--muted);
  background: var(--canvas);
}

.chip-why {
  font-size: 11px;
}

.empty,
.loading {
  margin: 0;
  font-size: 13px;
  color: var(--muted);
}

.loading {
  padding: 24px 0;
  text-align: center;
}
```

**`Fonts.vue`**

```vue
<!-- Every font the document can be written in, grouped by where it comes from. -->
<script setup lang="ts">
import { computed } from 'vue';
import { usePageList } from '@embedpdf/vue/runtime';
import type { Font, FontRestriction } from '@embedpdf/vue/runtime';
import { useAnnotation } from '@embedpdf/vue/annotation';
import { useFontsState } from '@embedpdf/vue/fonts';

const GROUPS = [
  ['document', 'In this document'],
  ['engine', 'The engine’s fonts'],
  ['standard', 'Standard'],
] as const;

// Why a font can't be chosen for new text, by its first restriction.
const WHY: Record<FontRestriction, string> = {
  subset: 'holds only the characters its text uses',
  'not-embedded': 'the file only names it',
  'preview-and-print': 'licensed for viewing and printing only',
  'unsupported-format': 'new text is written in TrueType or OpenType',
  'no-unicode-map': 'doesn’t say which characters it draws',
  'not-served': 'stays on the server',
};

const { fonts, status } = useFontsState();
const annotation = useAnnotation();
const pages = usePageList();

const groups = computed(() =>
  GROUPS.map(([source, title]) => ({
    source,
    title,
    fonts: fonts.value?.fonts.filter((font) => font.source === source) ?? [],
  })),
);
const whyOf = (font: Font) =>
  font.canChoose ? null : WHY[font.restrictions[0] ?? 'unsupported-format'];

// The engine fills Hebrew in from a Hebrew font and puts it in the file: the list reads it.
async function writeHebrew() {
  const cover = pages.value[0]?.ref;
  if (!cover) return;
  await annotation.create(cover, {
    subtype: 'free-text',
    box: { x: 72, y: 72, width: 220, height: 40 },
    contents: 'שלום עולם',
    fontFamily: 'helvetica',
  });
}
</script>

<template>
  <section class="panel">
    <header class="panel-header">
      <h3 class="panel-title">Fonts</h3>
      <button type="button" class="button" @click="writeHebrew">Write in Hebrew</button>
      <span class="status" :data-status="status">{{ status }}</span>
    </header>
    <div v-for="group in groups" :key="group.source" class="group">
      <h4 class="group-title">{{ group.title }}</h4>
      <p v-if="group.fonts.length === 0" class="empty">None</p>
      <ul v-else class="chips">
        <li
          v-for="font in group.fonts"
          :key="font.name"
          class="chip"
          :data-choosable="font.canChoose"
        >
          <span class="chip-name">{{ font.name }}</span>
          <span v-if="whyOf(font)" class="chip-why">{{ whyOf(font) }}</span>
        </li>
      </ul>
    </div>
  </section>
</template>
```

## Draw text in a font

`useFontCss(font)`
gives the CSS a font is drawn with, and mounts its programs (the font files) while your component
lives:

**`font-preview.vue`**

```vue
<script setup lang="ts">
import { useFontCss } from '@embedpdf/vue/fonts';

const props = defineProps<{ font: string }>();
// Its programs are mounted while this component lives.
const css = useFontCss(() => props.font);
</script>

<template>
  <input
    value="Hello שלום"
    :style="{
      fontFamily: css?.fontFamily,
      fontWeight: css?.fontWeight,
      fontStyle: css?.fontStyle,
      fontSynthesis: 'none',
    }"
  />
</template>
```

Draw with `font-synthesis: none`, so the browser never fakes a bold or an italic the engine won't
draw. The CSS names the engine's own programs, then the same fallback fonts the engine fills
missing characters from, in its order, then the standard font that stands in for the font (so
Latin a font lacks shows in Helvetica), then a font that draws a character no font has as a box,
as the engine writes it. Until the browser has every program, it names the system's closest fonts
instead, and the CSS changes when they arrive.

**`App.vue`**

```vue
<script setup lang="ts">
import { DocumentGate, Viewer } from '@embedpdf/vue/runtime';
import type { OpenInput } from '@embedpdf/vue/runtime';
import { fontsPlugin } from '@embedpdf/vue/fonts';
import { cloudEngine } from '@cloudpdf/engine';
import FontPreview from './FontPreview.vue';

import '../draw.css';

const engine = cloudEngine();
const plugins = [fontsPlugin()];

const welcome: OpenInput = { kind: 'grant', grant: 'shr_gvpJcG1ULUMon19C0D1wODSY' };
</script>

<template>
  <Viewer :engine="engine" :plugins="plugins" :initial-documents="[{ source: welcome }]">
    <DocumentGate>
      <template #fallback><p class="loading">Loading…</p></template>
      <FontPreview />
    </DocumentGate>
  </Viewer>
</template>
```

**`draw.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --surface: #ffffff;
  --canvas: #f7f9fc;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The font picker and the text drawn in it, in a card */
.panel {
  display: grid;
  gap: 12px;
  padding: 16px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

.field {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
}

.field select {
  min-width: 0;
  flex: 1;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 6px 8px;
  font: inherit;
  font-weight: 400;
  color: var(--ink);
  background: var(--surface);
}

/* The text, in the chosen font */
.preview {
  min-height: 96px;
  resize: vertical;
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 12px;
  font-size: 24px;
  line-height: 1.4;
  color: var(--ink);
  background: var(--canvas);
}

.hint,
.loading {
  margin: 0;
  font-size: 12.5px;
  color: var(--muted);
}

.loading {
  padding: 24px 0;
  text-align: center;
}
```

**`FontPreview.vue`**

```vue
<!-- Type in any font the document can be written in, drawn as the engine will write it. -->
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useFontCss, useFontsState } from '@embedpdf/vue/fonts';

const { fonts } = useFontsState();
const font = ref('times-roman');
// The font's CSS, its programs mounted while this component lives.
const css = useFontCss(font);
const choosable = computed(() => fonts.value?.fonts.filter((entry) => entry.canChoose) ?? []);
</script>

<template>
  <section class="panel">
    <label class="field">
      Font
      <select v-model="font">
        <option v-for="entry in choosable" :key="entry.name" :value="entry.name">
          {{ entry.name }}
        </option>
      </select>
    </label>
    <textarea
      class="preview"
      :style="{
        fontFamily: css?.fontFamily,
        fontWeight: css?.fontWeight,
        fontStyle: css?.fontStyle,
        fontSynthesis: 'none',
      }"
    >
Hello שלום مرحبا 你好</textarea
    >
    <p class="hint">Characters no font has show as boxes, as the engine writes them.</p>
  </section>
</template>
```

## Your own editor

To lay text out yourself, ask what the engine will do. `resolve()` says which font a write will
draw each run of text in; `getRunCss(run)` gives the CSS for a run as it answered.
`getPaintFonts(font, text)` gives the fonts themselves, measured as the browser draws them: what
`@embedpdf/core-text` lays text out with, so it breaks lines where the engine will. See
[Show typed text in a browser](https://www.cloudpdf.com/docs/engine/documents/fonts#show-typed-text-in-a-browser) for what
each answer holds.

## Methods

Everything `useFonts()` gives you.

| Method                          | What it does                                                                                                     |
| :------------------------------ | :--------------------------------------------------------------------------------------------------------------- |
| `ensureLoaded()`                | Reads the document's fonts unless they're loaded or loading. Resolves once they're there.                        |
| `refresh()`                     | Reads them again. Rarely needed: the list keeps up by itself.                                                    |
| `resolve(request)`              | The fonts a write would draw each run in, without writing. [Your own editor](#your-own-editor)                   |
| `getProgram(displayHash)`       | A font file's facts: the characters it covers and its line metrics.                                              |
| `getFontCss(font)`              | The CSS text in a font is drawn with, and the programs it names. [Draw text in a font](#draw-text-in-a-font)     |
| `getFaceCss(face)`              | The same for a face, as rich text names one: `{ family, weight, italic }`.                                       |
| `getRunCss(run)`                | The same for a run as `resolve()` answered it.                                                                   |
| `getPaintFonts(font, text)`     | The fonts a box in `font` draws `text` in, measured; `null` while they load. [Your own editor](#your-own-editor) |
| `getPaintFontsEpoch()`          | A number that moves when any of the answers above may change: ask again when it does.                            |
| `getFaceOfCssFamily(cssFamily)` | The face a program's CSS family stands for, to read back text the browser drew itself.                           |

`resolve()` and `getProgram()` also take a `signal` in their options, to cancel them.

## State

Everything `useFontsState()` gives you, as refs: your template updates when a value changes. The method in the last column reads the same value once.

| Field    | What it is                                                                                                                                      | Read once       |
| :------- | :---------------------------------------------------------------------------------------------------------------------------------------------- | :-------------- |
| `fonts`  | Every font the document can be written in, as listed under [The fonts you can use](#the-fonts-you-can-use), or `null` until the list has loaded | `getFontList()` |
| `status` | `'idle'` until something asks for the list, then `'loading'`, `'ready'`, `'forbidden'` or `'error'`                                             | `getStatus()`   |



## Events

| Event               | Fires when                                                                                         | Carries |
| :------------------ | :------------------------------------------------------------------------------------------------- | :------ |
| `onFontListChanged` | The list was loaded, or changed: a write put a font in the document, or the engine's fonts changed | `fonts` |

## Good to know

### Fonts that stay on the engine

A font whose file can't go to a browser, one the PDF only names or a server font kept on the
server, is drawn in the standard font the engine falls back to for it while text is typed. The
engine's own picture of the box follows the write.

### On the cloud

Downloading a font file needs a token that may write text in the document. With a read-only token
the list is still there; the standard fonts, which come with the viewer, show as they are, and
any other font in the system's closest font.
