Перейти к содержимому

Пользовательские шрифты: контракт расширения FontRegistry

FontRegistryInterface определяет контракт уровня процесса для регистрации и поиска шрифтов. Регистрируйте шрифты по пути к файлу, из каталога или из необработанных двоичных данных, а затем блокируйте реестр, чтобы рабочие процессы в продакшене не могли изменять его.

Окно терминала
composer require nextpdf/core:^3

Реестр шрифтов — это синглтон, который живёт дольше любого отдельного экземпляра Document. Он хранит только чистые данные PHP, без дескрипторов ресурсов или объектов расширений, поэтому его можно совместно использовать между запросами в долгоживущем рабочем процессе.

Используйте один из трёх способов регистрации:

  • Из файла. register() разбирает файл .ttf, .otf, .ttc или .pfb и возвращает метаданные. Для TrueType Collection передайте индекс вложенного шрифта.
  • Из каталога. addFontDirectory() добавляет путь поиска, который движок сканирует при разрешении семейства по имени.
  • Из двоичных данных. registerFromBinary() разбирает необработанные байты TrueType или OpenType. Используйте этот способ для моста @font-face, когда шрифты поступают из идентификатора ресурса (URI) data: или удалённого источника.

Чтобы снизить задержку первого запроса, вызовите 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Разобрать и зарегистрировать файл шрифта. Выбрасывает исключение, если реестр заблокирован или файл невозможно разобрать.
registerFromBinary(string $fontData, string $alias)FontInfoЗарегистрировать шрифт из необработанных байтов TrueType или OpenType.
registerBase14(string $key, FontInfo $font)voidЗарегистрировать готовый стандартный шрифт Base 14.
addFontDirectory(string $directory)voidДобавить каталог поиска шрифтов.
warmup(array $fontFiles)voidЗаранее разобрать пакет шрифтов при запуске рабочего процесса.
lock()voidЗаморозить реестр, чтобы предотвратить дальнейшие изменения.
isLocked()boolСообщить, заблокирован ли реестр.
get(string $family, string $style)FontInfo | nullНайти шрифт по семейству и начертанию.
has(string $key)boolПроверить, существует ли ключ регистрации.
all()array<string, FontInfo>Вернуть все зарегистрированные шрифты.
getSearchDirectories()list<string>Вернуть каталоги поиска в порядке обхода.
memoryUsage()MemoryReportСообщить о текущем использовании памяти реестром.
<?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');
}

При запуске рабочего процесса прогрейте набор шрифтов, заблокируйте реестр и отслеживайте каждую загрузку для учёта лицензий. В примере используются только публичные типы.

<?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);
}
}
  • Заблокированный реестр. После lock() любое изменение выбрасывает LogicException. Проверяйте isLocked() перед условным прогревом в повторно используемом рабочем процессе.
  • Двоичная регистрация не кэшируется по ключу. registerFromBinary() записывает данные во временный файл и разбирает его. Используйте возвращённый FontInfo как дескриптор.
  • Индекс TrueType Collection (TTC). Для TrueType Collection третий аргумент метода register() выбирает вложенный шрифт. Значение по умолчанию 0 выбирает первое начертание.
  • Разрешение семейства. get() возвращает null для неизвестной пары семейства и начертания. Никогда не рассчитывайте на ненулевой результат.

warmup() переносит затраты на разбор с первого запроса на запуск рабочего процесса. Методы реестра работают с чистыми данными PHP, а поиск выполняется как чтение из карты за постоянное время. Вызовите memoryUsage(), чтобы сопоставить резидентный набор шрифтов рабочего процесса с вашим бюджетом памяти.

Зарегистрированный шрифт может быть встроен в содержимое формата PDF (Portable Document Format). Проверяйте происхождение шрифта перед регистрацией. Не регистрируйте двоичные данные, контролируемые злоумышленником, без проверки размера и формата. Используйте хук FontLoadedEvent, чтобы обеспечивать соблюдение лицензий на шрифты и фиксировать, какие начертания встраивает документ.

Нормативные требования к подписанию или архивированию не применяются. Встраивание шрифтов и создание подмножеств соответствуют модели шрифтов PDF 2.0. За это соответствие отвечает внутренний механизм создания подмножеств, а не этот контракт.

NextPDF Enterprise добавляет аттестацию лицензий на шрифты и аудируемую политику создания подмножеств поверх того же FontRegistryInterface. Ваш код регистрации работает одинаково во всех редакциях, потому что границей служит контракт.

В глоссарии определены термины font registry, image registry и event listener; канонические определения см. в опубликованном глоссарии.