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:
import { useFontsState } from '@embedpdf/react/fonts';
export function FontSelect({
value,
onChange,
}: {
value: string;
onChange: (font: string) => void;
}) {
const { fonts } = useFontsState();
const choosable = fonts?.fonts.filter((font) => font.canChoose) ?? [];
return (
<select value={value} onChange={(event) => onChange(event.target.value)}>
{choosable.map((font) => (
<option key={font.name} value={font.name}>
{font.name}
</option>
))}
</select>
);
}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 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 { Viewer, DocumentGate, usePageList } from '@embedpdf/react/runtime';
import type { Font, FontRestriction, OpenInput } from '@embedpdf/react/runtime';
import { interactionPlugin } from '@embedpdf/react/interaction';
import { annotationPlugin, useAnnotation } from '@embedpdf/react/annotation';
import { fontsPlugin, useFontsState } from '@embedpdf/react/fonts';
import { cloudEngine } from '@cloudpdf/engine';
import './list.css';
const engine = cloudEngine();
const plugins = [interactionPlugin(), annotationPlugin(), fontsPlugin()];
const welcome: OpenInput = { kind: 'grant', grant: 'shr_gvpJcG1ULUMon19C0D1wODSY' };
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',
};
function FontChip({ font }: { font: Font }) {
const why = font.canChoose ? null : WHY[font.restrictions[0] ?? 'unsupported-format'];
return (
<li className="chip" data-choosable={font.canChoose}>
<span className="chip-name">{font.name}</span>
{why && <span className="chip-why">{why}</span>}
</li>
);
}
// Every font the document can be written in, grouped by where it comes from.
function Fonts() {
const { fonts, status } = useFontsState();
const annotation = useAnnotation();
const cover = usePageList()[0]?.ref;
// The engine fills Hebrew in from a Hebrew font and puts it in the file: the list reads it.
const writeHebrew = async () => {
if (!cover) return;
await annotation.create(cover, {
subtype: 'free-text',
box: { x: 72, y: 72, width: 220, height: 40 },
contents: 'שלום עולם',
fontFamily: 'helvetica',
});
};
return (
<section className="panel">
<header className="panel-header">
<h3 className="panel-title">Fonts</h3>
<button type="button" className="button" onClick={writeHebrew}>
Write in Hebrew
</button>
<span className="status" data-status={status}>
{status}
</span>
</header>
{GROUPS.map(([source, title]) => {
const group = fonts?.fonts.filter((font) => font.source === source) ?? [];
return (
<div className="group" key={source}>
<h4 className="group-title">{title}</h4>
{group.length === 0 ? (
<p className="empty">None</p>
) : (
<ul className="chips">
{group.map((font) => (
<FontChip key={font.name} font={font} />
))}
</ul>
)}
</div>
);
})}
</section>
);
}
export default function App() {
return (
<Viewer engine={engine} plugins={plugins} initialDocuments={[{ source: welcome }]}>
<DocumentGate fallback={<p className="loading">Loading…</p>}>
<Fonts />
</DocumentGate>
</Viewer>
);
}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:
import { useFontCss } from '@embedpdf/react/fonts';
export function FontPreview({ font }: { font: string }) {
// Its programs are mounted while this component lives.
const css = useFontCss(font);
return (
<input
defaultValue="Hello שלום"
style={{
fontFamily: css?.fontFamily,
fontWeight: css?.fontWeight,
fontStyle: css?.fontStyle,
fontSynthesis: 'none',
}}
/>
);
}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 { useState } from 'react';
import { Viewer, DocumentGate } from '@embedpdf/react/runtime';
import type { OpenInput } from '@embedpdf/react/runtime';
import { fontsPlugin, useFontCss, useFontsState } from '@embedpdf/react/fonts';
import { cloudEngine } from '@cloudpdf/engine';
import './draw.css';
const engine = cloudEngine();
const plugins = [fontsPlugin()];
const welcome: OpenInput = { kind: 'grant', grant: 'shr_gvpJcG1ULUMon19C0D1wODSY' };
// Type in any font the document can be written in, drawn as the engine will write it.
function FontPreview() {
const { fonts } = useFontsState();
const [font, setFont] = useState('times-roman');
// The font's CSS, its programs mounted while this component lives.
const css = useFontCss(font);
const choosable = fonts?.fonts.filter((entry) => entry.canChoose) ?? [];
return (
<section className="panel">
<label className="field">
Font
<select value={font} onChange={(event) => setFont(event.target.value)}>
{choosable.map((entry) => (
<option key={entry.name} value={entry.name}>
{entry.name}
</option>
))}
</select>
</label>
<textarea
className="preview"
defaultValue={'Hello שלום مرحبا 你好'}
style={{
fontFamily: css?.fontFamily,
fontWeight: css?.fontWeight,
fontStyle: css?.fontStyle,
fontSynthesis: 'none',
}}
/>
<p className="hint">Characters no font has show as boxes, as the engine writes them.</p>
</section>
);
}
export default function App() {
return (
<Viewer engine={engine} plugins={plugins} initialDocuments={[{ source: welcome }]}>
<DocumentGate fallback={<p className="loading">Loading…</p>}>
<FontPreview />
</DocumentGate>
</Viewer>
);
}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 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 |
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#
Everything useFontsState() gives you. Your component re-renders 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, 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.