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

Встраивание шрифта TrueType и создание его подмножества

Зарегистрируйте шрифт TrueType, отрисуйте с ним текст и позвольте средству записи встроить только нужное подмножество. В рецепте используется тот же путь обработки содержимого, что и в examples/04-text-and-fonts.php, но с дополнительно зарегистрированным шрифтом TrueType (.ttf).

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

Эта зависимость устанавливает пакет nextpdf/core. Пример рассчитан на PHP 8.4, а входящая в комплект тестовая фикстура LiberationSans-Regular.ttf делает рецепт самодостаточным.

Зарегистрируйте начертание с помощью FontRegistry::register($path, $alias). Реестр анализирует файл и возвращает FontInfo, используя TrueTypeParser для файлов .ttf и .otf. Чтобы выбрать начертание, укажите его псевдоним через setFont($alias, ...). Этот вызов также записывает используемые кодовые точки.

Создание подмножества выполняется автоматически при save(). Средство записи шрифтов PDF собирает используемые кодовые точки и вызывает FontSubsetter::subset() или CffSubsetter для начертаний Compact Font Format (CFF) или OpenType. Если подмножество меньше полной программы, средство записи встраивает именно его. Оно также переписывает BaseFont и FontName, добавляя тег подмножества из шести прописных букв, присоединённый через плюс. Это форма ABCDEF+FontName, которую ISO 32000-2 требует для подмножества шрифта. Средство записи сохраняет встроенную программу TrueType как FontFile2 в дескрипторе шрифта (ISO 32000-2).

Префикс подмножества детерминированно генерируется из имени PostScript, поэтому детерминированная сборка получает стабильный тег. Поэтому профиль воспроизводимости этого рецепта — structural. Структурный профиль нормализует префикс подмножества и трейлер /ID вместо их побайтовой проверки.

  • FontRegistry::register(string $fontFile, string $alias = '', int $fontIndex = 0): FontInfoNextPDF\Typography\FontRegistry.
  • setFont(string $family, string $style = '', float $size = 12.0): staticNextPDF\Core\Concerns\HasTypography; передайте зарегистрированный псевдоним как $family.
  • Создание подмножества — внутренняя операция средства записи (NextPDF\Writer\PdfFontWriter -> NextPDF\Typography\FontSubsetter). Публичного переключателя для включения или отключения нет: средство записи всегда создаёт подмножество, когда кодовые точки известны и подмножество меньше.

Полная таблица PHPDoc генерируется из исходного кода.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
use NextPDF\Typography\FontRegistry;
$registry = new FontRegistry();
$registry->register(__DIR__ . '/MyFont-Regular.ttf', alias: 'MyFont');
$doc = Document::createStandalone();
$doc->addPage();
$doc->setFont('MyFont', '', 14);
$doc->cell(0, 10, 'Rendered with an embedded, subset TrueType face.', newLine: true);
$doc->save(__DIR__ . '/out.pdf');

Document::createStandalone() создаёт собственный реестр. Чтобы использовать реестр, который вы заполнили самостоятельно, создайте документ через DocumentFactory, как показано в рабочем примере. Фабрика обеспечивает чтение зарегистрированного вами начертания средством записи.

Этот пример самодостаточен и подходит для запуска в тестовой обвязке. Он регистрирует входящий в комплект LiberationSans-Regular.ttf и выполняет отрисовку через DocumentFactory, поэтому используется именно заполненный реестр. Он использует автоматическое создание подмножества при сохранении.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\DocumentFactory;
use NextPDF\Graphics\ImageRegistry;
use NextPDF\Typography\FontRegistry;
// A bundled TrueType test fixture keeps this recipe self-contained.
// Replace with a font you have the right to embed.
$fontPath = __DIR__ . '/../../fonts/test-fixtures/LiberationSans/LiberationSans-Regular.ttf';
if (!is_file($fontPath)) {
// Fall back to the repository-relative fixture location.
$fontPath = dirname(__DIR__, 2) . '/fonts/test-fixtures/LiberationSans/LiberationSans-Regular.ttf';
}
$fontRegistry = new FontRegistry();
$fontRegistry->register($fontPath, alias: 'LiberationSans');
$imageRegistry = new ImageRegistry(maxCacheBytes: 0);
$documentFactory = new DocumentFactory($fontRegistry, $imageRegistry);
$doc = $documentFactory->create();
$doc->setTitle('Embedded Subset Font');
$doc->addPage();
$doc->setFont('LiberationSans', '', 20);
$doc->cell(0, 14, 'Embedded TrueType face', newLine: true);
$doc->setFont('LiberationSans', '', 12);
$doc->multiCell(0, 7, 'Only the glyphs used by this document are embedded. '
. 'The writer subsets the font program and rewrites the BaseFont with a '
. 'deterministic six-letter subset prefix, for example ABCDEF+LiberationSans.');
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');
$doc->save($out !== false ? $out : __DIR__ . '/embed-and-subset-fonts.pdf');
echo "Wrote embed-and-subset-fonts.pdf\n";

Ожидаемый STDOUT:

Wrote embed-and-subset-fonts.pdf

Чтобы убедиться, что подмножество создано, откройте выходной файл и просмотрите словарь шрифта. В BaseFont указано <TAG>+LiberationSans, а дескриптор содержит FontFile2. Запуск qpdf --check не сообщает о структурных ошибках.

  • Владение реестром. Document::createStandalone() создаёт собственный реестр. Шрифт, который вы зарегистрировали в отдельном FontRegistry, ему не виден. Используйте DocumentFactory, чтобы передать ваш реестр, как это делает рабочий пример.
  • Права на встраивание. Создание подмножества не меняет условия лицензирования. Встраивайте только те шрифты, на встраивание которых у вас есть лицензия. В некоторых шрифтах заданы биты ограничения встраивания; средство разбора считывает эти биты, но ответственность за соблюдение требований остаётся за вами.
  • Путь CFF/OpenType. Для начертаний .otf и CFF используется CffSubsetter, а не FontSubsetter. Поведение и переписывание тега подмножества эквивалентны. Различается только путь выполнения кода.
  • Нет выигрыша в размере. Иногда подмножество не меньше оригинала. Это может произойти с очень маленькими шрифтами или когда используются все глифы. В этом случае средство записи встраивает исходную программу без тега подмножества. Это правильное поведение, а не сбой.
  • Шрифты CJK. Крупные начертания для китайского, японского и корейского языков (CJK) используют многоступенчатую стратегию создания подмножеств согласно ADR-008, с изолированным подпроцессом и резервным вариантом на PHP. Подробности по CJK и текущее состояние конвейера см. в Размещение текста CJK с кодировкой на основе cmap.

Разбор выполняется за один проход по таблицам шрифта, а затраты на создание подмножества растут с количеством глифов. Латинские начертания, не относящиеся к CJK, обрабатываются внутри процесса в пределах бюджета wall_ms: 1500, peak_mb: 96. Крупные начертания CJK направляются в изолированный подпроцесс с тайм-аутом реального времени в две секунды и резервным вариантом на PHP (ADR-008). Такая маршрутизация означает, что медленное или аварийно завершающееся создание подмножества не может заблокировать вызывающий код.

Файл шрифта — это недоверенные двоичные входные данные. Средство разбора отклоняет пути с stream-обёртками и нулевые байты. Подпроцесс создания подмножеств CJK работает без унаследованных соединений с базой данных, файловых дескрипторов или состояния фреймворка. Он безопасно переключается на резервный вариант при сбое или тайм-ауте (ADR-008). Проверяйте происхождение шрифтов, принимаемых от конечных пользователей.

УтверждениеСпецификацияРазделreference_id (идентификатор ссылки)
BaseFont/FontName подмножества шрифта содержит префикс подмножества из шести прописных букв, присоединённый через плюс.ISO 32000-2iso32000_2_sec9#x1.x66.p2 (раздел)
Встроенная программа шрифта TrueType хранится как FontFile2 в дескрипторе шрифта.ISO 32000-2iso32000_2_sec9#x1.x65.p15 (раздел)

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

Неприменимо.