跳转到内容

自定义字体:FontRegistry 扩展契约

FontRegistryInterface 是一份进程生命周期级别的契约,用于注册和查找字体。你可以从文件路径、目录或原始二进制数据注册字体,随后锁定此 registry(注册表),防止生产环境的 worker 再修改它。

Terminal window
composer require nextpdf/core:^3

字体 registry 是一个 singleton(单例),生命周期比单个 Document 实例更长。它只保存纯 PHP 数据,不持有任何资源 handle 或扩展对象。因此,它可以在长时间运行的 worker 中安全地跨多个请求共用。

此契约支持三种注册途径:

  • 从文件注册。 register() 会解析 .ttf.otf.ttc.pfb 文件,并返回解析后的元数据。对于 TrueType Collection,请传入子字体 Index(索引)。
  • 从目录注册。 addFontDirectory() 会新增一条搜索路径,引擎按名称 resolve(解析)字体家族时会扫描这条路径。
  • 从二进制数据注册。 registerFromBinary() 会解析原始 TrueType 或 OpenType 字节。这正是 @font-face 桥接器处理从 data: URI 或远程来源获取的字体时所用的途径。

若要分摊首次请求的延迟,请在 worker 启动时调用 warmup(),预先解析一批字体。随后调用 lock()。在 lock() 之后,每个修改方法都会抛出 LogicException。这些方法包括 register()addFontDirectory()warmup()registerBase14()registerFromBinary()。查找方法仍可使用:get()has()all()getSearchDirectories()。这个锁定机制是生产环境的安全防护,确保没有任何请求能修改共用的字体集合。

大多数情况下,你不需要自行实现 FontRegistryInterface。引擎已提供实现,直接调用即可。只有在需要自定义字体解析策略时,才需要自行实现它,例如以内容寻址存储作为后端的策略。在这两种情况下,契约都是边界。

NextPDF\Contracts\FontRegistryInterface(稳定,自 1.7.0 起):

方法返回用途
register(string $fontFile, string $alias, int $fontIndex)FontInfo解析并注册一个字体文件。若 registry 已锁定或文件无法解析,则会抛出异常。
registerFromBinary(string $fontData, string $alias)FontInfo从原始 TrueType 或 OpenType 字节注册一个字体。
registerBase14(string $key, FontInfo $font)void注册一个预先构建的 Base 14 标准字体。
addFontDirectory(string $directory)void新增一个字体搜索目录。
warmup(array $fontFiles)void在 worker 启动时预先解析一批字体。
lock()void冻结 registry,使其无法再被修改。
isLocked()bool报告 registry 是否已锁定。
get(string $family, string $style)FontInfo | null按字体家族与样式查找字体。
has(string $key)bool检查某个注册键是否存在。
all()array<string, FontInfo>返回每一个已注册的字体。
getSearchDirectories()list<string>按顺序返回搜索目录。
memoryUsage()MemoryReport报告 registry 当前的内存使用量。
<?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');
}

这段 worker 启动例程会预热一组字体、锁定 registry,并观测每次加载,以便进行授权跟踪。它用到的每个类型都是公开的。

<?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);
}
}
  • 已锁定的 registry。lock() 之后的任何修改都会抛出 LogicException。在复用的 worker 中,执行有条件的预热前一律先检查 isLocked()
  • 二进制注册不会按注册键缓存。 registerFromBinary() 会写入一个临时文件再进行解析。请把返回的 FontInfo 视为 handle。
  • TTC 索引。 对于 TrueType Collection,register() 的第三个参数用来选择子字体。默认值 0 会选择第一个子字体。
  • 字体家族解析。 对于未知的字体家族与样式组合,get() 会返回 null。切勿假设结果一定非 null。

warmup() 会把解析成本从首次请求移到启动阶段。registry 的方法都在纯 PHP 数据上运行。查找是常数时间的 map 读取。调用 memoryUsage(),根据你的内存预算评估 worker 常驻字体集合的大小。

已注册的字体会成为可嵌入的 PDF 内容。注册前必须验证字体的 provenance(来源信息)。在未做大小与格式检查前,切勿注册受攻击者控制的二进制数据。FontLoadedEvent hook 是强制执行字体授权合规,并记录文件嵌入了哪些字体的官方支持切入点。

没有任何签名或归档的规范性声明适用于此处。字体嵌入与子集化符合 PDF 2.0 字体模型;该符合性由内部子集化器负责,而非由此契约负责。

NextPDF Enterprise 在同一份 FontRegistryInterface 之上,叠加了字体授权证明与经审计的子集化政策。因为契约就是边界,所以你的注册代码在各版本之间都无需修改。

词汇表定义了 font registryimage registryevent listener;各项的标准定义请参阅已发布的词汇表。