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

Текст: граница формирования, CJK и обработка прогонов

Модуль текста задаёт границу формирования. Он предоставляет небольшой интерфейс, который преобразует прогон в 8-битном формате преобразования Unicode (UTF-8) в позиционированные глифы, выбирает реальный бэкенд OpenType, если он доступен, детерминированно переходит на запасной вариант, если он недоступен, и предоставляет реестр шейперов для отдельных письменностей.

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

ShaperInterface связывает конвейер компоновки текста с движком формирования OpenType. Он намеренно остаётся небольшим: один метод shape() принимает ShaperInput и возвращает ShapingResult. Возвращаемый тип — единственный результат, который видят потребители. Реализации не должны раскрывать внутреннее устройство движка формирования, а типизированный возврат обеспечивает соблюдение этой границы. ShapingResult содержит список записей GlyphRun, возвращённый исходный текст, письменность и направление, а также тег shaperImpl, который указывает, какой бэкенд создал результат.

Выбор бэкенда явный и сообщает о возможностях без догадок. ShaperFactory выполняет одну проверку возможностей. Если на хосте есть рабочая привязка HarfBuzz, create() возвращает шейпер на основе HarfBuzz. В противном случае он возвращает NullShaper. NullShaper — это сквозной запасной вариант. Он выдаёт один синтетический глиф на каждую кодовую точку Unicode с нулевыми приращениями и нулевыми смещениями. Он помечает результат тегом, чтобы средства наблюдаемости могли обнаружить запасной вариант, и оставляет вычисление приращений модулю метрик шрифтов. Этот путь — задокументированная деградация, а не полноценное формирование. Подстановка, лигатуры, позиционирование знаков и контекстные формы требуют реального бэкенда. wouldUseRealShaper() — диагностический предикат. Рабочий код должен вместо этого ветвиться по тегу shaperImpl в результате.

Формирование для отдельных письменностей — это интерфейс поставщика службы (SPI), а не встроенная реализация. ScriptShaperRegistry — это реестр по образцу PHP Standards Recommendation 11 (PSR-11), который разрешает MongolianShaperInterface или TibetanShaperInterface по тегу письменности International Organization for Standardization (ISO) 15924. Реестр хранит ключи без учёта регистра и в вопросе допустимости кодов письменностей опирается на единый источник истины. Реестр и интерфейсы шейперов письменностей — замороженный контракт, поэтому расширение может зарегистрировать поставщика Phase-12, не затрагивая места вызова. Движок предоставляет границу. Потребители предоставляют поставщиков для сложных письменностей.

Обработка прогонов на китайском, японском и корейском (CJK) находится на границе кодирования типографики. Встроенная гарнитура TrueType для CJK выводится как шрифт Type 0 с CMap Identity-H и потомком CIDFontType2, как это описано в ISO 32000-2 §9.7.4 (дайджест retrieval-augmented generation (RAG) усечён лицензионным ограничением; зафиксировано в _downgraded-claims-o3.md). Когда программа TrueType встроена, CIDFont типа Type 2 сопоставляет идентификаторы символов с индексами глифов через запись CIDToGIDMap, как это описано в ISO 32000-2 §9 (дайджест закреплён страницей контракта B1). Сабсеттер сохраняет исходную нумерацию глифов, поэтому /CIDToGIDMap /Identity остаётся допустимым для подмножества. CjkFontValidator проверяет, покрывает ли шрифт-кандидат блоки Unicode, необходимые для письменности, прежде чем этот шрифт будет выбран.

ТипВидОсновные членыСтабильностьС версии
ShaperInterfaceinterfaceshape(ShaperInput): ShapingResultстабильно3.2.0
ShaperFactoryfinal classdefault(), create(), wouldUseRealShaper()стабильно3.2.0
NullShaperfinal readonly classсквозной запасной шейперстабильно3.2.0
ShapingResultfinal readonly class$glyphRuns, $originalText, $script, $direction, $shaperImplстабильно3.2.0
ScriptShaperRegistryfinal classregisterMongolian(), getMongolian(), hasMongolian() и тибетские эквивалентыстабильно3.1.0
CjkFontValidatorfinal classvalidateCoverage(), detectScript(), isCjkCodepoint()стабильно1.0.0

Форма методов register*, get* и has* у ScriptShaperRegistry и интерфейсов шейперов письменностей — замороженный контракт. По замыслу ShapingResult остаётся единственным результатом шейпера, который видят потребители.

examples/text/shaper-factory.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Font\Shaper\ShaperFactory;
use NextPDF\Font\Shaper\ShaperImpl;
$factory = ShaperFactory::default();
$shaper = $factory->create();
// Branch on the result tag, not on the concrete class.
$wouldShape = $factory->wouldUseRealShaper()
? 'HarfBuzz backend available'
: 'NullShaper fallback (degraded — no substitution or positioning)';
echo $wouldShape, "\n";

ShaperFactory::default() подключает рабочую проверку возможностей. create() запоминает выбранный бэкенд на всё время жизни фабрики. Используйте wouldUseRealShaper() и тег shaperImpl в каждом результате, чтобы проверять возможности.

examples/text/script-shaper-registry.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Text\Shaping\MongolianShaperInterface;
use NextPDF\Text\Shaping\ScriptShaperRegistry;
final readonly class ComplexScriptBootstrap
{
public function __construct(private ScriptShaperRegistry $registry) {}
/**
* Register a consumer-supplied Mongolian shaper provider at boot so
* the layout pipeline can resolve it by ISO 15924 script tag.
*/
public function register(MongolianShaperInterface $mongolian): void
{
$this->registry->registerMongolian($mongolian);
}
public function hasMongolian(): bool
{
return $this->registry->hasMongolian();
}
}

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

  • Результат NullShaper имеет нулевые приращения и нулевые смещения. Не передавайте эти позиции напрямую в компоновку текста. Вычисляйте приращения из модуля метрик шрифтов и обнаруживайте запасной вариант по тегу shaperImpl.
  • Пустой ввод даёт пустой список glyphRuns, а не пустой прогон. Коду, который перебирает результат на стороне потребителя, не нужен отдельный случай для прогона нулевой длины.
  • ScriptShaperRegistry не реализует Psr\Container\ContainerInterface напрямую, поэтому типизированные методы доступа сохраняют свой суженный возвращаемый тип при статическом анализе. Используйте getMongolian() и getTibetan(), а не обобщённый get().
  • Теги письменностей сопоставляются по каноническому четырёхбуквенному значению ISO 15924 и хранятся без учёта регистра. Передайте Mong или Tibt. Регистр не влияет на поиск.
  • Символы CJK Extension B находятся в плоскости 2 Unicode и требуют подтаблицы cmap Format 12 в подмножестве. Путь кодирования обрабатывает это. Не предполагайте, что базовая многоязычная плоскость покрывает весь текст CJK.

Проверка возможностей выполняется один раз на экземпляр ShaperFactory, а бэкенд запоминается, поэтому повторные вызовы create() бесплатны. NullShaper линеен по количеству кодовых точек во входном прогоне и не выполняет операций input/output (I/O). Разрешение ScriptShaperRegistry — это поиск по ключу за постоянное время. CjkFontValidator выбирает кодовые точки с шагом, а не проверяет каждую, поэтому проверка покрытия остаётся недорогой даже для шрифта CJK с 20,000 глифами. performance_budget в 1500 мс реального времени и 64 МБ пикового использования покрывает типичный запуск. При реальном формировании основные затраты приходятся на бэкенд OpenType. Когда активен запасной вариант, эти затраты находятся вне области действия этого модуля.

Граница шейпера принимает строку UTF-8. NullShaper терпимо относится к некорректному UTF-8: разбивает его по мере возможности вместо выбрасывания исключения, поскольку задокументированный контракт запасного варианта уже подразумевает «отсутствие настоящего формирования». Вызывающая сторона уже готова к выводу низкого качества. Контракт кластеров по байтовым смещениям использует длину в байтах, что корректно для многобайтового ввода и помогает избежать ошибки сопоставления кластеров со сдвигом на кодовую точку. Когда настоящий бэкенд присутствует, это сторонняя нативная библиотека. Считайте его ввод недоверенным и ограничивайте длину прогона выше по конвейеру. Реестр шейперов письменностей хранит поставщиков, предоставленных потребителем. Эти реализации находятся внутри границы доверия потребителя, а не движка.

УтверждениеСтандартПунктСвидетельство
Встроенная гарнитура TrueType для CJK выводится как шрифт Type 0 с CMap Identity-H и потомком CIDFontType2.ISO 32000-2§9.7.4Дайджест retrieval-augmented generation (RAG) усечён лицензионным ограничением; префикс 7a5258772f508e3b, см. _downgraded-claims-o3.md
Встроенный CIDFont типа Type 2 сопоставляет идентификаторы символов с индексами глифов через CIDToGIDMap.ISO 32000-2§9

Оба пункта изложены в пересказе. Второй закреплён дайджестом (повторно используется со страницы контракта B1), а первый подтверждается ADR-013 и обзором cmap-кодировщика для разработчиков. NextPDF не воспроизводит нормативный текст. Бэкенд шейпера не зависит от соответствия Portable Document Format (PDF). Утверждения о соответствии здесь касаются вывода словаря шрифтов CJK, создаваемого границей кодирования. ADR-013 и обзор cmap-кодировщика для разработчиков документируют этот путь подробнее.

Расширенный конвейер предобработки текста и службы извлечения строятся на границе шейпера Core и типах-значениях обработки прогонов. Модуль текста Core предоставляет границу, запасной вариант и реестр шейперов письменностей без лицензии. Отсутствие ссылки на конверсию сделано намеренно.