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

Контракты слоёв 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/*Поток операторов PDFComputedStyle (неизменяемый) + геометриюВычислять геометрию; разбирать CSS; определять разрывы страниц
СлойФайлы (показательные)Роль
5 — постраничная разметкаPageBreakController, PageBorderPainter, PageRule, PageRuleParser, ParserConfiguratorОбрабатывать правила @page; вычислять разрывы и ограничения orphan/widow; делегировать оформление страницы слою отрисовки.
6 — измерение и оснасткаСкрипты-классификаторы Web Platform Tests (WPT), tests/Support/*Классифицировать результаты тестов; создавать регрессионные снимки; предоставлять вспомогательные инструменты для проверок. Не содержит логики отрисовки.

Контракт задают размещение классов и докблок HtmlStyleState. Сверяйтесь с src/Html/.

СимволСлойРоль в контракте
PropertyApplicatorInterface1Интерфейс стратегии; единственное место записи вычисленных CSS-полей.
ParserConfigurator::buildCssApplicator()1 (связывание)Регистрирует каждый аппликатор. Новое CSS-свойство регистрируют здесь.
HtmlStyleState2Контейнер с двумя группами полей; докблок класса указывает слой-владелец для каждого поля.
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.