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 withFonts() 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:
import { Component, computed, inject, model } from '@angular/core';
import { EpdfFonts } from '@embedpdf/angular/fonts';
@Component({
selector: 'app-font-select',
template: `
<select #field (change)="value.set(field.value)">
@for (font of choosable(); track font.name) {
<option [value]="font.name" [selected]="font.name === value()">{{ font.name }}</option>
}
</select>
`,
})
export class FontSelect {
readonly value = model.required<string>();
private readonly fonts = inject(EpdfFonts);
protected readonly choosable = computed(
() => this.fonts.list()?.fonts.filter((font) => font.canChoose) ?? [],
);
}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.list() 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 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:
Loading live preview…
import {
ChangeDetectionStrategy,
Component,
computed,
inject,
ViewEncapsulation,
} from '@angular/core';
import { EpdfDocument, EpdfDocumentGate, provideEmbedPdf } from '@embedpdf/angular/runtime';
import type { Font, FontRestriction, OpenInput } from '@embedpdf/angular/runtime';
import { withInteraction } from '@embedpdf/angular/interaction';
import { EpdfAnnotation, withAnnotation } from '@embedpdf/angular/annotation';
import { EpdfFonts, withFonts } from '@embedpdf/angular/fonts';
import { localEngine } from '@embedpdf/engine';
const welcome = async (): Promise<OpenInput> => {
const response = await fetch('/pdfs/welcome.pdf');
return { kind: 'bytes', id: 'welcome', bytes: new Uint8Array(await response.arrayBuffer()) };
};
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',
};
// Every font the document can be written in, grouped by where it comes from.
@Component({
selector: 'demo-root',
imports: [EpdfDocumentGate],
providers: [
provideEmbedPdf(
{ engine: () => localEngine(), initialDocuments: [{ source: welcome }] },
withInteraction(),
withAnnotation(),
withFonts(),
),
],
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrl: './list.css',
encapsulation: ViewEncapsulation.None,
template: `
<section *epdfDocumentGate="let document; fallback: loading" 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" [attr.data-status]="fonts.status()">{{ fonts.status() }}</span>
</header>
@for (group of groups(); track group.source) {
<div class="group">
<h4 class="group-title">{{ group.title }}</h4>
@if (group.fonts.length === 0) {
<p class="empty">None</p>
} @else {
<ul class="chips">
@for (font of group.fonts; track font.name) {
@let why = whyOf(font);
<li class="chip" [attr.data-choosable]="font.canChoose">
<span class="chip-name">{{ font.name }}</span>
@if (why) {
<span class="chip-why">{{ why }}</span>
}
</li>
}
</ul>
}
</div>
}
</section>
<ng-template #loading><p class="loading">Loading…</p></ng-template>
`,
})
export class App {
protected readonly fonts = inject(EpdfFonts);
private readonly annotation = inject(EpdfAnnotation);
private readonly document = inject(EpdfDocument);
protected readonly groups = computed(() =>
GROUPS.map(([source, title]) => ({
source,
title,
fonts: this.fonts.list()?.fonts.filter((font) => font.source === source) ?? [],
})),
);
protected whyOf(font: Font) {
return 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.
protected async writeHebrew() {
const cover = this.document.pages()[0]?.ref;
if (!cover) return;
await this.annotation.create(cover, {
subtype: 'free-text',
box: { x: 72, y: 72, width: 220, height: 40 },
contents: 'שלום עולם',
fontFamily: 'helvetica',
});
}
}Draw text in a font#
injectFontCss(() => font)
gives the CSS a font is drawn with, and mounts its programs (the font files) while your component
lives:
import { Component, input } from '@angular/core';
import { injectFontCss } from '@embedpdf/angular/fonts';
@Component({
selector: 'app-font-preview',
template: `
<input
value="Hello שלום"
[style.font-family]="css()?.fontFamily"
[style.font-weight]="css()?.fontWeight"
[style.font-style]="css()?.fontStyle"
style="font-synthesis: none"
/>
`,
})
export class FontPreview {
readonly font = input.required<string>();
// Its programs are mounted while this component lives.
protected readonly css = injectFontCss(() => this.font());
}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.
Loading live preview…
import {
ChangeDetectionStrategy,
Component,
computed,
inject,
signal,
ViewEncapsulation,
} from '@angular/core';
import { EpdfDocumentGate, provideEmbedPdf } from '@embedpdf/angular/runtime';
import type { OpenInput } from '@embedpdf/angular/runtime';
import { EpdfFonts, injectFontCss, withFonts } from '@embedpdf/angular/fonts';
import { localEngine } from '@embedpdf/engine';
const welcome = async (): Promise<OpenInput> => {
const response = await fetch('/pdfs/welcome.pdf');
return { kind: 'bytes', id: 'welcome', bytes: new Uint8Array(await response.arrayBuffer()) };
};
// Type in any font the document can be written in, drawn as the engine will write it.
@Component({
selector: 'demo-root',
imports: [EpdfDocumentGate],
providers: [
provideEmbedPdf(
{ engine: () => localEngine(), initialDocuments: [{ source: welcome }] },
withFonts(),
),
],
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrl: './draw.css',
encapsulation: ViewEncapsulation.None,
template: `
<section *epdfDocumentGate="let document; fallback: loading" class="panel">
<label class="field">
Font
<select #fontField (change)="font.set(fontField.value)">
@for (entry of choosable(); track entry.name) {
<option [value]="entry.name" [selected]="entry.name === font()">
{{ entry.name }}
</option>
}
</select>
</label>
<textarea
class="preview"
[style.font-family]="css()?.fontFamily"
[style.font-weight]="css()?.fontWeight"
[style.font-style]="css()?.fontStyle"
style="font-synthesis: none"
>
Hello שלום مرحبا 你好</textarea
>
<p class="hint">Characters no font has show as boxes, as the engine writes them.</p>
</section>
<ng-template #loading><p class="loading">Loading…</p></ng-template>
`,
})
export class App {
private readonly fonts = inject(EpdfFonts);
protected readonly font = signal('times-roman');
// The font's CSS, its programs mounted while this component lives.
protected readonly css = injectFontCss(() => this.font());
protected readonly choosable = computed(
() => this.fonts.list()?.fonts.filter((entry) => entry.canChoose) ?? [],
);
}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 for what
each answer holds.
Methods#
Everything inject(EpdfFonts) 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 |
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 |
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 |
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#
Every value is a signal on inject(EpdfFonts): read it in a template or a computed(), and your view updates when it 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, or null until the list has loaded | getFontList() |
status() | 'idle' until something asks for the list, then 'loading', 'ready', 'forbidden' or 'error' | getStatus() |
fonts is list().
Events#
| Event | Fires when | Carries |
|---|---|---|
fontListChanged$ | 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.
Your feedback goes directly to the documentation team.