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

Шрифт: типы значений, встраивание и резервная замена

В NextPDF шрифт описывается неизменяемым объектом-значением FontInfo и типом технологии: по нему движок понимает, как встроить шрифт. Движок встраивает каждый используемый шрифт. Устаревшая ссылка на Base 14 заменяется встроенным в пакет метрически совместимым аналогом.

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

FontInfo — неизменяемый объект-значение, который содержит всё необходимое движку для встраивания шрифта: семейство и начертание, имя PostScript, флаги дескриптора, метрики, приведённые к em из 1000 единиц, ширины символов, отображение глифов в Unicode, прямое отображение символов (cmap, из Unicode в идентификатор глифа), необработанные байты шрифта, а также, при наличии, оси вариаций, именованные экземпляры, селекторы вариаций, пары кернинга и вертикальные метрики. Класс объявлен как final readonly. Сигнатура его конструктора и публичные свойства зафиксированы, поэтому разобранный шрифт становится стабильным фактом, пригодным для совместного использования. FontInfo::encodeText() — единственный метод с поведением. Он обращается к резолверу кодировки и возвращает EncodedGlyphRun.

FontType перечисляет шрифтовые технологии, которые встраивает движок: TrueType (однобайтовая кодировка), TrueTypeUnicode (многобайтовая кодировка по идентификатору символа (CID) для письменностей с большим набором Unicode-символов), OpenType (контуры в формате Compact Font Format), Type1 (PostScript Type 1, регистрируемый из пары Printer Font Binary (PFB) и Adobe Font Metrics (AFM)) и CidFont0 (CID-шрифт на основе PostScript). Назначенный парсером тип определяет структуру словаря шрифта, который выводит модуль записи.

Чтобы отрисовка не зависела от установленных системных шрифтов, движок встраивает программу шрифта — ISO 32000-2 §9. Программа TrueType встраивается через запись дескриптора шрифта FontFile2 и должна включать таблицы glyf, head, hhea, hmtx, loca и maxp — ISO 32000-2 §9.6.5 (дайджест RAG усечён из-за лицензионного ограничения; это зафиксировано в _downgraded-claims-o3.md). Программа OpenType с таблицей контуров в формате Compact Font Format встраивается через FontFile3 — ISO 32000-2 §9.6.5 (дайджест RAG усечён; см. тот же журнал). Модуль создания подмножеств перестраивает именно этот обязательный набор таблиц, поэтому встроенное подмножество остаётся программой, соответствующей требованиям.

Резервная замена покрывает устаревший сценарий Base 14. Base14SubstituteFonts сопоставляет нормализованный ключ Base 14 — helvetica, helveticab, times, courier и остальные — с файлом Liberation Fonts из пакета. Liberation Sans, Serif и Mono метрически совместимы с Helvetica или Arial, Times Roman и Courier. Каждый из них — встроенное начертание TrueType, поэтому он отрисовывает полный латинский репертуар WinAnsiEncoding (Windows-1252), который требует эталон стандартных 14 шрифтов: латиницу с диакритикой, знак евро и распространённую типографскую пунктуацию (ISO 32000-2 Annex D.2). Для Symbol и ZapfDingbats нет метрически совместимой замены со свободной лицензией, поэтому NextPDF намеренно их не подставляет; документ, которому нужен любой из этих шрифтов, должен зарегистрировать встраиваемый шрифт. Резолвер не имеет побочных эффектов: он только сообщает, какому файлу соответствует ключ. Вызывающая сторона по-прежнему отвечает за регистрацию в реестре, сохраняя семантику блокировки и конвейер прогрева.

ТипРазновидностьКлючевые членыСтабильностьС версии
FontInfofinal readonly class$family, $style, $type, $unitsPerEm, $widths, $unicodeMap, $cmapForward, $fileData, $variationAxes, $kernPairs, getKey(), encodeText()стабильно1.0.0
FontTypeenum (string)TrueType, TrueTypeUnicode, OpenType, Type1, CidFont0стабильно1.0.0
Base14SubstituteFontsfinal class (внутренний)нормализованный ключ Base 14 в путь к файлу Liberation из пакетастабильно2.7.0
ShaperFactoryfinal classdefault(), create(), wouldUseRealShaper()стабильно3.2.0
ShapingResultfinal readonly class$glyphRuns, $originalText, $script, $direction, $shaperImplстабильно3.2.0

Base14SubstituteFonts помечен как @internal: только для внутреннего использования фреймворком, без гарантии обратной совместимости этой API-поверхности.

examples/35-cjk-cmap-demo.php
<?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);

Парсер заполняет FontInfo и назначает FontType. Начертание TrueType с символьной картой Unicode становится типом TrueTypeUnicode, который модуль записи выводит как составной шрифт Type 0.

examples/font/base14-fallback.php
<?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);
}
}
}

Резолвер не имеет побочных эффектов. Регистрация остаётся явной, поэтому контракты блокировки и прогрева реестра соблюдаются. Для Symbol и ZapfDingbats путь намеренно не возвращается.

  • Symbol и ZapfDingbats намеренно не подставляются. Возврат null для этих ключей — задокументированное поведение, а не ошибка из-за отсутствующего шрифта.
  • FontInfo объявлен как final readonly. Относитесь к разобранному шрифту как к значению: не рассчитывайте изменять ширины или метрики на месте; перерегистрируйте, если источник меняется.
  • Шрифту Type 1 нужны и контур PFB, и метрики AFM. FontRegistry::registerType1() принимает эту пару; автоопределение выводит путь AFM из пути PFB по расширению.
  • FontType::TrueType и FontType::TrueTypeUnicode фиксируют различие между однобайтовым и многобайтовым вариантами. Резолвер кодировки использует заполненное прямое отображение символов, а не имя семейства, поэтому начертание Unicode TrueType автоматически направляется по пути Identity-H.
  • Оси вариаций шрифта и именованные экземпляры разбираются в FontInfo при их наличии, но подробный пример для китайского, японского и корейского языков (CJK) намеренно использует статическое начертание, чтобы разобранный FontInfo оставался детерминированным.

Реестр создаёт FontInfo один раз на шрифт в каждом процессе, а затем предоставляет его по ссылке. Основная часть памяти уходит на необработанные байты шрифта. Прогревайте только те шрифты, которые нужны воркеру, и отслеживайте memoryUsage(). Резолвер замены Base 14 выполняет поиск по карте за константное время без операций ввода-вывода (I/O), пока вызывающая сторона не зарегистрирует найденный файл. Параметр performance_budget в 1500 мс реального времени и 64 МБ пикового потребления покрывает прогрев типичного набора шрифтов и отрисовку. До запуска модуля создания подмножеств объём занимаемой памяти каждого шрифта зависит от размера файла шрифта, а не от количества глифов.

FontInfo сам по себе инертен: это разобранные данные без какого-либо поведения, кроме чистого преобразования encodeText(). Поверхность атаки находится раньше в конвейере, на этапе разбора, когда произвольные байты шрифта попадают в парсер TrueType или Type 1. Парсеры проверяют границы каждого двоичного смещения и отклоняют обёртки потоков и нулевые байты в путях. Перед регистрацией недоверенные входные данные шрифта должны пройти проверку политики внешних ресурсов, которая ограничивает размер и количество глифов. Входящие в пакет заменители Liberation — это доверенные ресурсы, поставляемые вместе с пакетом, поэтому путь резервной замены не вносит новых недоверенных входных данных.

УтверждениеСтандартПунктПодтверждение
Каждый шрифт, используемый документом, встроен, поэтому документ отображается без опоры на системные шрифты.ISO 32000-2§9
Программа TrueType встраивается через FontFile2 с таблицами glyf, head, hhea, hmtx, loca, maxp.ISO 32000-2§9.6.5дайджест RAG усечён из-за лицензионного ограничения; префикс 7b26f37996239b2a, см. _downgraded-claims-o3.md
Программа OpenType (CFF) встраивается через FontFile3.ISO 32000-2§9.6.5дайджест RAG усечён из-за лицензионного ограничения; префикс 801549ee00623baf, см. _downgraded-claims-o3.md

Первый пункт закреплён дайджестом и подтверждён B1. Пункты о FontFile2 и FontFile3 являются перефразированиями. Полные дайджесты RAG для этих пунктов не были возвращены (усечение из-за лицензионного ограничения), поэтому подтверждение также обеспечивается FontSubsetter (который перестраивает именно набор glyf/head/hhea/hmtx/loca/maxp) и перечислением FontType. NextPDF не воспроизводит нормативный текст. В исходном коде Base14SubstituteFonts ссылается на ISO 32000-2 §9.6.2.2 (обработка стандартных шрифтов Type 1), ISO 14289-2:2024 §8.4.5.5.1 (встраивание шрифтов PDF/UA-2) и ISO 19005-4:2020 §6.3.5 (встраивание шрифтов PDF/A-4). Полное соответствие профилям описано на страницах о доступности и соответствии требованиям.

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