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

Contracts / Типографика

Домен типографики определяет контракты реестра шрифтов и предварительной обработки текста: FontRegistryInterface, TextPreprocessorInterface и неизменяемые объекты-значения TextPreprocessResult и TextSegment. Все они имеют статус stable.

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

FontRegistryInterface — это хранилище шрифтов на время жизни процесса. Реестр регистрирует шрифт TrueType, OpenType, TrueType Collection (TTC) или Printer Font Binary (PFB) и возвращает разобранные метаданные FontInfo. Поскольку реестр живёт дольше отдельных документов, обработчик разбирает каждый шрифт только один раз. Можно прогреть набор шрифтов при загрузке, а затем заблокировать реестр, чтобы рабочий трафик не мог его изменить. Заблокированный реестр выбрасывает LogicException при вызове register(), addFontDirectory() или warmup(); поиск остаётся доступным. Реестр также принимает необработанные байты шрифта через registerFromBinary(). Мост @font-face использует этот метод для регистрации шрифта, полученного из удалённого источника или data URI (uniform resource identifier). Реестр хранит только чистые данные PHP, без дескрипторов ресурсов, поэтому им можно совместно пользоваться в пуле обработчиков.

Движок встраивает каждый используемый шрифт и создаёт для него подмножество. Встроенная программа шрифта помещается внутри файла Portable Document Format (PDF), поэтому документ отображается одинаково в любом средстве просмотра, независимо от установленных системных шрифтов — ISO 32000-2 §9. Подмножество шрифта содержит только те глифы, на которые документ действительно ссылается. Это особенно важно для китайского, японского и корейского (CJK) или другого содержимого с большим числом Unicode-символов — ISO 32000-2 §9. Контракт реестра предоставляет разобранные метаданные, которые используются на этапах создания подмножества и встраивания.

TextPreprocessorInterface перехватывает текст до того, как он попадёт в раскладку глифов, создание подмножества шрифта, символьную карту ToUnicode (CMap) и дерево структуры. Именно это размещение даёт свойство безопасности: препроцессор, который скрывает содержимое, удаляет его до того, как оно сможет попасть в поток содержимого, подмножество шрифта или метаданные. Контракт задаёт два инварианта. Препроцессор не должен вводить символы, влияющие на раскладку, и должен сохранять логический порядок чтения; его задача — замена содержимого, а не раскладка. Результат — неизменяемый TextPreprocessResult с упорядоченным списком значений TextSegment. Сегмент бывает либо сквозным, либо скрытым. Для скрытого сегмента отображаемый текст зависит от режима маскирования: пустой для прямоугольника-плашки, звёздочки по исходной длине или фиксированная подпись. Значение originalCharCount в сегменте — это необратимая подсказка для измерения, используемая только для задания размера прямоугольника скрытия. Оно никогда не должно использоваться для восстановления исходного содержимого.

ТипВидКлючевые членыСтабильностьС версии
FontRegistryInterfaceinterfaceregister(), get(), has(), all(), addFontDirectory(), warmup(), lock(), isLocked(), registerBase14(), registerFromBinary(), memoryUsage()стабильный (stable)1.7.0
TextPreprocessorInterfaceinterfaceprocess(string): TextPreprocessResultстабильный (stable)1.9.0
TextPreprocessResultfinal readonly class$segments, hasRedactions(), getDisplayText()стабильный (stable)1.9.0
TextSegmentfinal readonly class$displayText, $isRedacted, $originalCharCount, $fillColorстабильный (stable)1.9.0

TextPreprocessResult и TextSegment фиксируют сигнатуры своих конструкторов и публичные свойства; новые методы могут быть добавлены, но свойства изменять нельзя.

examples/04-text-and-fonts.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->addPage();
$doc->setFont('helvetica', 'B', 18);
$doc->cell(0, 12, 'Bold heading', newLine: true);
$doc->setFont('helvetica', '', 11);
$doc->multiCell(0, 7, 'Body text rendered with a registered font.');
$doc->save(__DIR__ . '/output/04-text-and-fonts.pdf');

setFont() разрешает семейство через FontRegistryInterface. Автономный документ использует приватный реестр. В обработчике следует совместно использовать один реестр; см. страницу о документе.

examples/contracts/typography-production.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\FontRegistryInterface;
use NextPDF\Contracts\TextPreprocessorInterface;
use NextPDF\Exception\NextPdfException;
use Psr\Log\LoggerInterface;
final readonly class FontWarmupService
{
public function __construct(
private FontRegistryInterface $fonts,
private TextPreprocessorInterface $preprocessor,
private LoggerInterface $logger,
) {}
/**
* Warm a font set at boot, then lock the registry.
*
* @param list<string> $fontFiles Absolute paths to font files.
*/
public function boot(array $fontFiles): void
{
try {
$this->fonts->warmup($fontFiles);
$this->fonts->lock();
} catch (NextPdfException $e) {
$this->logger->error('Font warmup failed', ['error' => $e->getMessage()]);
throw $e;
}
}
public function redact(string $text): string
{
$result = $this->preprocessor->process($text);
return $result->hasRedactions()
? $result->getDisplayText()
: $text;
}
}

warmup() с последующим lock() — это последовательность загрузки обработчика. После lock() попытка изменения выбрасывает исключение. Поиск продолжает обслуживать трафик.

  • Заблокированный реестр отклоняет вызов любого изменяющего метода. Прогрейте и заблокируйте реестр при загрузке; никогда не вызывайте register() во время обработки запроса.
  • registerFromBinary() записывает байты шрифта во временный файл перед разбором. Недоверенные данные шрифта представляют собой поверхность атаки для анализатора — ограничьте их через ExternalResourcePolicyInterface (см. страницу политики безопасности).
  • Объект TextPreprocessor не должен добавлять переводы строки, возвраты каретки или табуляции. Эти символы изменяют раскладку и нарушают первый инвариант контракта.
  • TextSegment::$originalCharCount — это только подсказка о ширине. Использование его для выведения исходного содержимого сводит на нет скрытие и нарушает третий инвариант контракта.
  • TextPreprocessResult::getDisplayText() по замыслу возвращает пустую строку для сегментов-плашек. Не следует считать пустой сегмент сбоем предварительной обработки.

Разбор шрифта доминирует при первом использовании; реестр сводит эту стоимость к одному разу на процесс. После прогрева get() и has() представляют собой поиск по карте за O(1). memoryUsage() возвращает MemoryReport, чтобы обработчик мог отслеживать кэш шрифтов относительно своего бюджета. Предварительная обработка текста линейна по длине входных данных. Список сегментов добавляет ограниченные накладные расходы, пропорциональные числу совпадений скрытия. Значение performance_budget в 1500 мс фактического времени и 64 МБ пика покрывает прогрев типичного набора шрифтов плюс отрисовку документа. Стоимость создания подмножества масштабируется с числом фактически используемых глифов, а не с полной таблицей глифов шрифта. Поэтому создание подмножества уменьшает размер вывода и стоимость отрисовки для содержимого CJK.

Домен типографики имеет две поверхности, связанные с безопасностью. Первая — это входные данные шрифта: registerFromBinary() разбирает произвольные байты. Недоверенные данные шрифта должны пройти через ExternalResourcePolicyInterface, который ограничивает размер файла и число глифов до передачи данных анализатору. Вторая — это скрытие: TextPreprocessorInterface выполняется до раскладки глифов, создания подмножества шрифта, CMap ToUnicode и дерева структуры, поэтому скрытое содержимое никогда не попадает в отрисованный артефакт. Скрытие наложением во время отрисовки приводит к утечке исходного текста в поток содержимого и в подмножество. Такое размещение контракта предотвращает этот класс дефектов. Подсказка для измерения в сегменте намеренно необратима. Любой шрифт или текст, предоставленный извне, следует считать недоверенным.

УтверждениеСтандартПунктСвидетельство
Каждый шрифт, используемый документом, встроен, поэтому документ отображается, не полагаясь на системные шрифты.ISO 32000-2§9
Встроенный шрифт сокращён до глифов, на которые ссылается документ.ISO 32000-2§9

Оба пункта изложены своими словами. NextPDF не воспроизводит нормативный текст. PDF/A-4 предписывает встраивание для каждого шрифта. Это соответствие задокументировано на страницах об извлечении и доступности.