Контракты слоёв HTML-движка (ADR-010)
Краткий обзор
Заголовок раздела «Краткий обзор»Подсистема HTML разделяет разбор CSS, состояние стилей, макет и отрисовку на четыре слоя. Данные проходят через эти слои в одном направлении. ADR-010 определяет границы и правила расширения.
Установка
Заголовок раздела «Установка»composer require nextpdf/core:^3Концептуальный обзор
Заголовок раздела «Концептуальный обзор»ADR-010 (“Engine Layer Contracts, Hot Path Ownership, and Extension Rules”, принята 2026-04-12) формализует разбиение подсистемы HTML на слои. Базовый контракт отрисовки состоит из четырёх слоёв: разбор CSS и аппликаторы, состояние стилей, макет и форматирование, а также отрисовка. ADR-010 также документирует два вспомогательных слоя: постраничную разметку и инструментарий измерений. Они окружают четырёхслойное ядро, не меняя его поток данных. Канонический термин глоссария для ядра — “HTML pipeline”, четырёхслойный конвейер.
Данные движутся в одном направлении. Текст CSS преобразуется в типизированные значения в слое 1. Слой 1 записывает эти значения в поля HtmlStyleState в слое 2. Слой 3 читает поля состояния стилей и вычисляет геометрию. Слой 4 читает неизменяемый снимок ComputedStyle вместе с геометрией и выдаёт операторы PDF. Ни один слой не читает данные из последующего слоя.
Разделение на четыре слоя — не просто документация. ADR-010 фиксирует два ограниченных рефакторинга, выполненных в v1.2.0: они переместили код в правильный слой. PageBorderPainter был выделен из HtmlParser, поэтому операторы отрисовки больше не находятся в оркестраторе. Докблок класса HtmlStyleState теперь содержит формальный контракт слоёв и указывает, какие поля каждый слой может записывать или читать.
Одна граница обозначена явно. FormattingContextFactory::startTable() по-прежнему напрямую читает пять необработанных CSS-ключей. ADR-010 фиксирует это как известный отложенный технический долг для будущего TableApplicator, а не как предусмотренный контракт. Документирование этого исключения — часть контракта.
Четыре базовых слоя
Заголовок раздела «Четыре базовых слоя»| Слой | Файлы (показательные) | Записывает | Читает | Не должен |
|---|---|---|---|---|
| 1 — разбор CSS и аппликаторы | CssValueParser, CssResolver, HtmlCssApplicator, src/Html/Applicator/* | HtmlStyleState (CSS-поля) | Необработанный текст CSS | Вычислять геометрию; выдавать операторы |
| 2 — состояние стилей | HtmlStyleState, State/ComputedStyle, State/LayoutState | — (пассивный контейнер значений) | — | Разбирать CSS; определять макет; выдавать операторы |
| 3 — макет и форматирование | FormattingContextFactory, HtmlBlockHandler, FlexLayoutEngine, TableParser, FloatContext | Геометрию курсора | HtmlStyleState (поля) | Читать необработанный $css[...]; выдавать операторы отрисовки |
| 4 — отрисовка и отображение | BorderRenderer, BackgroundImageRenderer, src/Html/Paint/*, src/Html/Gradient/* | Поток операторов PDF | ComputedStyle (неизменяемый) + геометрию | Вычислять геометрию; разбирать CSS; определять разрывы страниц |
Два вспомогательных слоя
Заголовок раздела «Два вспомогательных слоя»| Слой | Файлы (показательные) | Роль |
|---|---|---|
| 5 — постраничная разметка | PageBreakController, PageBorderPainter, PageRule, PageRuleParser, ParserConfigurator | Обрабатывать правила @page; вычислять разрывы и ограничения orphan/widow; делегировать оформление страницы слою отрисовки. |
| 6 — измерение и оснастка | Скрипты-классификаторы Web Platform Tests (WPT), tests/Support/* | Классифицировать результаты тестов; создавать регрессионные снимки; предоставлять вспомогательные инструменты для проверок. Не содержит логики отрисовки. |
Поверхность API
Заголовок раздела «Поверхность API»Контракт задают размещение классов и докблок HtmlStyleState. Сверяйтесь с src/Html/.
| Символ | Слой | Роль в контракте |
|---|---|---|
PropertyApplicatorInterface | 1 | Интерфейс стратегии; единственное место записи вычисленных CSS-полей. |
ParserConfigurator::buildCssApplicator() | 1 (связывание) | Регистрирует каждый аппликатор. Новое CSS-свойство регистрируют здесь. |
HtmlStyleState | 2 | Контейнер с двумя группами полей; докблок класса указывает слой-владелец для каждого поля. |
HtmlStyleState::toComputedStyle() | 2 | Создаёт неизменяемый ComputedStyle для слоя отрисовки. |
FormattingContextFactory::dispatchOpenTag() | 3 | Единая точка маршрутизации нового поведения макета. |
PageBorderPainter::buildStream() | 4 | Оформление страницы; вызывается из слоя 5, не встраивается в HtmlParser. |
Пример кода — быстрый старт
Заголовок раздела «Пример кода — быстрый старт»Со слоями напрямую не работают. Четырёхслойный поток выполняется внутри одного вызова.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->addPage();$doc->writeHtml('<p style="color:#1E3A8A;border:1px solid #999;">Layered render.</p>');$doc->save(__DIR__ . '/output/layers.pdf');Пример кода — для продакшена
Заголовок раздела «Пример кода — для продакшена»Контракт важен, когда вы изменяете код, а не когда вызываете библиотеку. Чтобы добавить CSS-свойство, используйте точку расширения слоя 1: создайте аппликатор, добавьте типизированное поле HtmlStyleState с докблоком слоя и зарегистрируйте аппликатор в ParserConfigurator. Ниже показана форма контракта аппликатора. Используйте src/Html/Applicator/ как образец для конкретного класса.
<?php
declare(strict_types=1);
// Layer 1 extension contract (see ADR-010 §C "New CSS property").// A new property group ships as a PropertyApplicatorInterface// implementation registered in ParserConfigurator::buildCssApplicator().// It writes a typed HtmlStyleState field and never computes geometry// or emits PDF operators — those belong to Layers 3 and 4.Граничные случаи и подводные камни
Заголовок раздела «Граничные случаи и подводные камни»FormattingContextFactory::startTable()читает необработанный CSS. Это единственное задокументированное исключение из контракта, отложенное до будущегоTableApplicator. Не копируйте этот шаблон.- Шесть слоёв, четырёхслойное ядро. ADR-010 нумерует шесть слоёв. Контракт потока данных относится к четырёхслойному ядру; постраничная разметка и измерение — вспомогательные.
- В
HtmlStyleStateдве группы полей. Он содержит вычисленные CSS-поля и поля отслеживания макета. CSS-группу записывают только аппликаторы. Отрисовка читаетComputedStyle, но никогда — поля отслеживания макета. HtmlParserне относится ни к одному слою. Это оркестратор. Разбор CSS, геометрические вычисления и выдача операторов отрисовки не должны находиться в нём.
Производительность
Заголовок раздела «Производительность»Контракт слоёв структурный, поэтому он не добавляет накладных расходов во время выполнения. HtmlStyleState::toComputedStyle() создаёт один неизменяемый снимок для каждого элемента, которому нужна отрисовка. Этот снимок избавляет код отрисовки от обращения к изменяемому контейнеру состояния. Стоимость отрисовки определяется потоковой моделью, а не разбиением на слои. Постраничный performance_budget (wall_ms: 1500, peak_mb: 64) остаётся рабочим верхним пределом.
Замечания по безопасности
Заголовок раздела «Замечания по безопасности»Разделение на слои поддерживает модель безопасности. Слой 1 разбирает значения CSS и фильтрует их по политике до того, как их увидит код макета или отрисовки, поэтому DefaultHtmlSecurityPolicy::isCssPropertyAllowed() остаётся единственной точкой контроля. Отрисовка никогда не читает необработанный CSS, контролируемый злоумышленником. См. модель безопасности модуля HTML.
Соответствие
Заголовок раздела «Соответствие»Эта страница не ссылается на внешние стандарты. Границы слоёв определяются ADR-010 и докблоком класса HtmlStyleState, который закрепляет контракт в исходном коде. Поведенческое соответствие CSS задокументировано на странице css-resolver.
Коммерческий контекст
Заголовок раздела «Коммерческий контекст»Возможность корпоративного уровня. Premium-функции CSS используют те же четыре слоя через задокументированные точки расширения. Отдельного конвейера Premium не существует. См. матрицу поддержки CSS.