5. Text beyond Latin
Before this: You can build a document with headings, tables and footers.
Latin text works with no setup, because PDF viewers ship the standard fonts. Anything else — Arabic, Thai, Japanese, Hindi — needs a font embedded in the file, since you cannot assume the reader's machine has one.
Embedding a whole font would add megabytes, so pdfnative subsets it: it embeds only the glyphs you actually used. To do that it has to know which font to reach for, which is what registration is.
import {
buildDocumentPDFBytes, registerFonts, loadFontData,
} from 'pdfnative';
// 1. Say which font handles which script. The loaders are functions, so
// nothing is downloaded yet.
registerFonts({
latin: () => import('pdfnative/fonts/noto-sans-data.js'),
th: () => import('pdfnative/fonts/noto-thai-data.js'),
ar: () => import('pdfnative/fonts/noto-arabic-data.js'),
});
// 2. Actually load the ones you need. buildDocumentPDFBytes is synchronous,
// so it cannot await a loader for you — this step is not optional.
const langs = ['latin', 'th', 'ar'];
const loaded = await Promise.all(langs.map(loadFontData));
// fontRef is written straight into the PDF as a resource name, so it must
// start with a slash — and /F1 and /F2 are reserved by the engine, so your
// fonts start at /F3. Get this wrong and viewers reject the file.
const fontEntries = loaded.map((fontData, i) => ({
fontData, fontRef: `/F${i + 3}`, lang: langs[i],
}));
// 3. Hand them to the builder. You do not tag blocks with a language —
// pdfnative detects the script per character and picks the right font.
const bytes = buildDocumentPDFBytes({
title: 'Multilingual',
blocks: [
{ type: 'paragraph', text: 'English stays on the Latin font.' },
{ type: 'paragraph', text: 'สวัสดีครับ' },
{ type: 'paragraph', text: 'مرحبا بالعالم' },
],
fontEntries,
});
Three things are worth pulling out of that.
- Registration alone does nothing.
registerFontsonly records the loaders. BecausebuildDocumentPDFBytesis synchronous, it cannot await them, so you mustawait loadFontData()and pass the result asfontEntries. Skip that step and you get blank boxes with no error. - Blocks have no
langfield. Routing is per character, decided by the codepoint. Thai and Arabic in the same paragraph work without you saying anything. - Include a Latin font whenever you include any other. Once
fontEntriesis present the builder stops using the viewer's built-in fonts, so without a Latin entry your English text is drawn with whichever font happens to cover it.
Two words you will meet
- Shaping — in many scripts, letters change form depending on their neighbours, and marks have to be positioned over specific parts of a glyph. pdfnative implements this (OpenType GSUB/GPOS) for 22 writing systems, so you pass ordinary text and get correct output.
- BiDi — Arabic and Hebrew read right to left, and mixing them with Latin needs a defined ordering algorithm (Unicode UAX #9). It is applied for you.
See the all-scripts playground for all 22 rendered in one document, in your browser.
You should now be able to produce a document in a non-Latin script, and explain why registration exists.
Tip: Alt + ← / → moves between steps.