Custom fonts: the FontRegistry extension contract
At a glance
Section titled “At a glance”FontRegistryInterface defines the process-lifetime contract for registering and finding fonts. Register fonts from a file path, a directory, or raw binary data, then lock the registry so production workers cannot mutate it.
Install
Section titled “Install”composer require nextpdf/core:^3Conceptual overview
Section titled “Conceptual overview”The font registry is a singleton that outlives each Document instance. It stores only pure PHP data, with no resource handles or extension objects, so you can share it across requests in a long-running worker.
Use one of three registration paths:
- From a file.
register()parses a.ttf,.otf,.ttc, or.pfbfile and returns metadata. For a TrueType Collection, pass the sub-font index. - From a directory.
addFontDirectory()adds a search path that the engine scans when it resolves a family by name. - From binary data.
registerFromBinary()parses raw TrueType or OpenType bytes. Use this path for the@font-facebridge when fonts come from adata:Uniform Resource Identifier (URI) or a remote source.
To reduce first-request latency, call warmup() at worker boot to pre-parse a batch of fonts. Then call lock(). After lock(), every mutation method throws LogicException: register(), addFontDirectory(), warmup(), registerBase14(), and registerFromBinary(). Lookup methods stay available: get(), has(), all(), and getSearchDirectories(). This lock protects production workers by ensuring no request can change the shared font set.
In most cases, you do not implement FontRegistryInterface. The engine provides the implementation, and you call it. Implement it only when you need a custom font-resolution strategy, such as one backed by a content-addressed store. In both cases, the contract remains the boundary.
API surface
Section titled “API surface”NextPDF\Contracts\FontRegistryInterface (stable, since 1.7.0):
| Method | Returns | Purpose |
|---|---|---|
register(string $fontFile, string $alias, int $fontIndex) | FontInfo | Parse and register a font file. Throws on a locked registry or unparsable file. |
registerFromBinary(string $fontData, string $alias) | FontInfo | Register a font from raw TrueType or OpenType bytes. |
registerBase14(string $key, FontInfo $font) | void | Register a prebuilt Base 14 standard font. |
addFontDirectory(string $directory) | void | Add a font search directory. |
warmup(array $fontFiles) | void | Pre-parse a batch of fonts at worker boot. |
lock() | void | Freeze the registry to prevent further mutation. |
isLocked() | bool | Report whether the registry is locked. |
get(string $family, string $style) | FontInfo | null | Look up a font by family and style. |
has(string $key) | bool | Check whether a registration key exists. |
all() | array<string, FontInfo> | Return every registered font. |
getSearchDirectories() | list<string> | Return search directories in order. |
memoryUsage() | MemoryReport | Report current registry memory use. |
Code sample — Quick start
Section titled “Code sample — Quick start”<?php
declare(strict_types=1);
use NextPDF\Contracts\FontRegistryInterface;
/** @var FontRegistryInterface $fonts */$info = $fonts->register('/srv/fonts/Inter-Regular.ttf', 'Inter');
if (!$fonts->has('inter')) { throw new RuntimeException('Inter failed to register');}Code sample — Production
Section titled “Code sample — Production”At worker boot, warm the font set, lock the registry, and observe each load for license tracking. This example uses only public types.
<?php
declare(strict_types=1);
use NextPDF\Contracts\FontRegistryInterface;use NextPDF\Event\Content\FontLoadedEvent;use NextPDF\Event\EventDispatcher;use NextPDF\Event\ListenerProvider;use Psr\Log\LoggerInterface;
final class FontWarmup{ /** @param list<string> $fontFiles */ public function __construct( private readonly FontRegistryInterface $fonts, private readonly LoggerInterface $logger, private readonly array $fontFiles, ) {}
public function boot(): EventDispatcher { $listeners = new ListenerProvider(); $listeners->addListener( FontLoadedEvent::class, function (FontLoadedEvent $event): void { $this->logger->info('font.loaded', [ 'family' => $event->family, 'style' => $event->style, 'type' => $event->fontType->name, ]); }, );
if (!$this->fonts->isLocked()) { $this->fonts->warmup($this->fontFiles); $this->fonts->lock(); }
return new EventDispatcher($listeners); }}Edge cases & gotchas
Section titled “Edge cases & gotchas”- Locked registry. After
lock(), any mutation throwsLogicException. CheckisLocked()before a conditional warmup in a recycled worker. - Binary registration is uncached by key.
registerFromBinary()writes to a temporary file and parses it. Use the returnedFontInfoas the handle. - TrueType Collection (TTC) index. For a TrueType Collection, the third argument to
register()selects the sub-font. The default0selects the first face. - Family resolution.
get()returnsnullfor an unknown family-and-style pair. Never assume a non-null result.
Performance
Section titled “Performance”warmup() moves parsing cost from the first request to worker boot. Registry methods use pure PHP data, and lookups are constant-time map reads. Call memoryUsage() to size a worker’s resident font set against your memory budget.
Security notes
Section titled “Security notes”A registered font can be embedded in Portable Document Format (PDF) content. Validate font provenance before registration. Do not register attacker-controlled binary data without size and format checks. Use the FontLoadedEvent hook to enforce font-licensing compliance and record which faces a document embeds.
Conformance
Section titled “Conformance”No signing or archival normative claims apply. Font embedding and subsetting conform to the PDF 2.0 font model. The internal subsetter owns that conformance; this contract does not.
Commercial context
Section titled “Commercial context”NextPDF Enterprise adds font-license attestation and an audited subsetting policy on top of the same FontRegistryInterface. Your registration code works the same across editions because the contract is the boundary.
See also
Section titled “See also”- Extension authoring overview
- Action triggers and event listeners
- Custom layout and text interception
- SPI stability rules
Related contracts and modules
Section titled “Related contracts and modules”- Font module reference — the registry implementation, parsing, and subsetting internals.
- Typography contracts reference — the catalog entry for
FontRegistryInterface. - Action triggers and event listeners —
FontLoadedEventand the dispatcher. - Custom layout and text interception — the sibling render-time strategy contract.
- SPI stability rules — the interface promise behind
FontRegistryInterface.
The glossary defines font registry, image registry, and event listener; see the published glossary for the canonical definitions.