Font: value types, embedding, and fallback
At a glance
Section titled “At a glance”In NextPDF, a font is the immutable FontInfo value object and the technology type that tells the engine how to embed it. The engine embeds every font it uses. A legacy Base 14 reference falls back to a bundled, metric-compatible substitute.
Install
Section titled “Install”composer require nextpdf/core:^3Conceptual overview
Section titled “Conceptual overview”FontInfo is the immutable value object that gives the engine everything it needs to embed a font: family and style, the PostScript name, descriptor flags, metrics scaled to a 1000-unit em, character widths, the glyph-to-Unicode map, the forward character map (cmap, Unicode to glyph identifier), raw font bytes, and, when present, variation axes, named instances, variation selectors, kern pairs, and vertical metrics. It is final readonly. Its constructor signature and public properties are frozen, so a parsed font is a stable, shareable fact. FontInfo::encodeText() is the only method with behavior. It routes through the encoding resolver and returns an EncodedGlyphRun.
FontType enumerates the technologies the engine embeds: TrueType (single-byte encoding), TrueTypeUnicode (multi-byte character identifier (CID) encoding for Unicode-rich scripts), OpenType (Compact Font Format outlines), Type1 (PostScript Type 1, registered from a Printer Font Binary (PFB) and Adobe Font Metrics (AFM) pair), and CidFont0 (a PostScript-based CID font). The parser-assigned type determines the shape of the font dictionary the writer emits.
To keep rendering independent of installed system fonts, the engine embeds the font program — ISO 32000-2 §9. A TrueType program is embedded through the FontFile2 font-descriptor entry and must include the glyf, head, hhea, hmtx, loca, and maxp tables — ISO 32000-2 §9.6.5 (RAG digest truncated by the licence cap; recorded in _downgraded-claims-o3.md). An OpenType program with a Compact Font Format outline table is embedded through FontFile3 — ISO 32000-2 §9.6.5 (RAG digest truncated; see the same log). The subsetter rebuilds exactly this required table set, so the embedded subset remains a conforming program.
Fallback covers the legacy Base 14 case. Base14SubstituteFonts maps a normalized Base 14 key — helvetica, helveticab, times, courier, and the rest — to a bundled Liberation Fonts file. Liberation Sans, Serif, and Mono are metric-compatible with Helvetica or Arial, Times Roman, and Courier. Each is an embedded TrueType face, so it renders the full WinAnsiEncoding (Windows-1252) Latin repertoire a standard-14 reference requires — accented Latin, the Euro sign, and common typographic punctuation (ISO 32000-2 Annex D.2). Symbol and ZapfDingbats have no permissive metric-compatible replacement, so NextPDF deliberately does not substitute them; a document that needs either font must register an embeddable font. The resolver has no side effects: it answers which file a key maps to and nothing else. The caller remains responsible for registration with the registry, preserving lock semantics and the warmup pipeline.
API surface
Section titled “API surface”| Type | Kind | Key members | Stability | Since |
|---|---|---|---|---|
FontInfo | final readonly class | $family, $style, $type, $unitsPerEm, $widths, $unicodeMap, $cmapForward, $fileData, $variationAxes, $kernPairs, getKey(), encodeText() | stable | 1.0.0 |
FontType | enum (string) | TrueType, TrueTypeUnicode, OpenType, Type1, CidFont0 | stable | 1.0.0 |
Base14SubstituteFonts | final class (internal) | normalized Base 14 key to bundled Liberation file path | stable | 2.7.0 |
ShaperFactory | final class | default(), create(), wouldUseRealShaper() | stable | 3.2.0 |
ShapingResult | final readonly class | $glyphRuns, $originalText, $script, $direction, $shaperImpl | stable | 3.2.0 |
Base14SubstituteFonts is @internal: framework use only, with no backward-compatibility guarantee for its surface.
Code sample — Quick start
Section titled “Code sample — Quick start”<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Typography\FontRegistry;use NextPDF\Typography\FontType;
$registry = new FontRegistry();$font = $registry->register('/path/to/NotoSansTC-Regular.ttf', alias: 'NotoSansTC');
// FontInfo is the immutable parsed fact about the face.echo $font->family, ' / ', $font->type->value, "\n"; // e.g. "Noto Sans TC / TrueTypeUnicode"assert($font->type === FontType::TrueTypeUnicode);The parser fills FontInfo and assigns the FontType. A TrueType face with a Unicode character map becomes TrueTypeUnicode, which the writer emits as a Type 0 composite font.
Code sample — Production
Section titled “Code sample — Production”<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Typography\Base14SubstituteFonts;use NextPDF\Typography\FontRegistry;
final readonly class Base14EmbeddingResolver{ public function __construct(private FontRegistry $registry) {}
/** * Register an embeddable substitute for a legacy Base 14 key so the * output document embeds every font (PDF/A-4 and PDF/UA-2 require it). */ public function ensureEmbeddable(string $base14Key): void { $path = Base14SubstituteFonts::resolve($base14Key);
if ($path === null) { // Symbol / ZapfDingbats have no permissive substitute — the // caller must supply its own embeddable font. throw new \RuntimeException("No bundled substitute for {$base14Key}"); }
if (!$this->registry->has($base14Key)) { $this->registry->register($path, alias: $base14Key); } }}The resolver has no side effects. Registration remains explicit, so the registry’s lock and warmup contracts hold. Symbol and ZapfDingbats return no path by design.
Edge cases & gotchas
Section titled “Edge cases & gotchas”SymbolandZapfDingbatsare intentionally not substituted. A null result for those keys is the documented behavior, not a missing-font bug.FontInfoisfinal readonly. Treat a parsed font as a value: never expect to mutate widths or metrics in place; re-register if the source changes.- A Type 1 font needs both the PFB outline and the AFM metrics.
FontRegistry::registerType1()takes the pair; auto-discovery derives the AFM path from the PFB path by extension. FontType::TrueTypeandFontType::TrueTypeUnicodemark the single-byte versus multi-byte distinction. The encoding resolver uses the populated forward character map, not the family name, so a Unicode TrueType face routes to the Identity-H path automatically.- Variation font axes and named instances are parsed into
FontInfowhen present, but the worked Chinese, Japanese, and Korean (CJK) example deliberately uses the static face to keep the parsedFontInfodeterministic.
Performance
Section titled “Performance”The registry allocates FontInfo once per font per process, then shares it by reference. Raw font bytes dominate memory cost. Warm only the fonts a worker needs and track memoryUsage(). The Base 14 substitute resolver performs a constant-time map lookup with no input/output (I/O) until the caller registers the resolved file. The performance_budget of 1500 ms wall and 64 MB peak covers a typical font set warmup plus rendering. Until the subsetter runs, each font’s memory footprint scales with the font file size, not the glyph count.
Security notes
Section titled “Security notes”FontInfo itself is inert: parsed data with no behavior beyond the pure encodeText() transform. The attack surface sits upstream, at parse time, when arbitrary font bytes reach the TrueType or Type 1 parser. The parsers bounds-check every binary offset and reject stream wrappers and null bytes in paths. Before registration, untrusted font input must pass an external-resource policy that bounds size and glyph count. The bundled Liberation substitutes are trusted assets shipped with the package, so the fallback path introduces no new untrusted input.
Conformance
Section titled “Conformance”| Claim | Standard | Clause | Evidence |
|---|---|---|---|
| Every font used by the document is embedded so the document renders without relying on system fonts. | ISO 32000-2 | §9 | |
A TrueType program is embedded through FontFile2 with the glyf, head, hhea, hmtx, loca, maxp tables. | ISO 32000-2 | §9.6.5 | RAG digest truncated by licence cap; prefix 7b26f37996239b2a, see _downgraded-claims-o3.md |
An OpenType (CFF) program is embedded through FontFile3. | ISO 32000-2 | §9.6.5 | RAG digest truncated by licence cap; prefix 801549ee00623baf, see _downgraded-claims-o3.md |
The first clause is digest-pinned and corroborated by B1. The FontFile2 and FontFile3 clauses are paraphrases. The full RAG digests for those clauses were not returned (licence cap truncation), so the evidence is also corroborated by FontSubsetter (which rebuilds exactly the glyf/head/hhea/hmtx/loca/maxp set) and the FontType enum. NextPDF does not reproduce normative text. In source, Base14SubstituteFonts cites ISO 32000-2 §9.6.2.2 (standard Type 1 font handling), ISO 14289-2:2024 §8.4.5.5.1 (PDF/UA-2 font embedding), and ISO 19005-4:2020 §6.3.5 (PDF/A-4 font embedding). The accessibility and conformance pages carry the full profile conformance.
Commercial context
Section titled “Commercial context”A commercial font-licensing pack and a dynamic subsetting service build on Core FontInfo and the registry. The Core font module embeds, subsets, and falls back without a license. The omitted conversion link is intentional.
See also
Section titled “See also”- Typography: registry, subsetting, CMap, encoding, BiDi — the registry and subsetting workflow that produces and consumes
FontInfo. - Text: shaping, breaking, BiDi — the shaping layer that consumes the encoded run.
- Contracts / Typography — the
FontRegistryInterfacecontract.