Zum Inhalt springen

Schichtverträge für die HTML-Engine (ADR-010)

Das HTML-Subsystem trennt CSS-Parsing, Style-State, Layout und Paint in vier Schichten. Der Vertrag zwischen ihnen verläuft in eine Richtung. ADR-010 legt die Grenzen und Erweiterungsregeln fest.

Terminal-Fenster
composer require nextpdf/core:^3

ADR-010 („Engine Layer Contracts, Hot Path Ownership, and Extension Rules“, angenommen am 2026-04-12) formalisiert die Schichtung des HTML-Subsystems. Der zentrale Rendering-Vertrag umfasst vier Schichten: CSS-Parsing und Applicators, Style-State, Layout und Formatierung sowie Paint. ADR-010 dokumentiert außerdem zwei Zusatzschichten — Paged Media und das Mess-Harness —, die den vierschichtigen Kern ergänzen, ohne dessen Datenfluss zu verändern. Der kanonische Glossarbegriff für den Kern ist „HTML-Pipeline“, eine vierschichtige Pipeline.

Daten fließen in eine Richtung. In Schicht 1 wird CSS-Text in typisierte Werte umgewandelt. Schicht 1 schreibt diese Werte in Schicht 2 in HtmlStyleState-Felder. Schicht 3 liest Style-State-Felder und berechnet die Geometrie. Schicht 4 liest einen unveränderlichen ComputedStyle-Snapshot plus Geometrie und gibt PDF-Operatoren aus. Keine Schicht liest aus einer nachgelagerten Schicht.

Die Trennung in vier Schichten ist mehr als Dokumentation. ADR-010 hält zwei eng begrenzte Refactorings fest, die in v1.2.0 Code in die richtige Schicht verschoben haben. PageBorderPainter wurde aus HtmlParser herausgelöst, damit Paint-Operatoren nicht länger im Orchestrator liegen. Der Klassen-Docblock von HtmlStyleState enthält jetzt den formalen Schichtvertrag, der festlegt, welche Felder jede Schicht schreiben oder lesen darf.

Eine Grenze wird bewusst offengelegt, statt versteckt zu werden. FormattingContextFactory::startTable() liest weiterhin fünf rohe CSS-Schlüssel direkt. ADR-010 hält dies als bekannte, aufgeschobene technische Schuld für einen künftigen TableApplicator fest, nicht als beabsichtigten Vertrag. Die Ausnahme zu dokumentieren, ist Teil des Vertrags.

SchichtDateien (repräsentativ)SchreibtLiestDarf nicht
1 — CSS-Parsing & ApplicatorsCssValueParser, CssResolver, HtmlCssApplicator, src/Html/Applicator/*HtmlStyleState-CSS-FelderRoher CSS-TextGeometrie berechnen; Operatoren ausgeben
2 — Style-StateHtmlStyleState, State/ComputedStyle, State/LayoutState— (passive Sammlung von Werten)CSS parsen; über Layout entscheiden; Operatoren ausgeben
3 — Layout & FormatierungFormattingContextFactory, HtmlBlockHandler, FlexLayoutEngine, TableParser, FloatContextCursor-GeometrieHtmlStyleState-FelderRohes $css[...] lesen; Paint-Operatoren ausgeben
4 — Paint & RenderingBorderRenderer, BackgroundImageRenderer, src/Html/Paint/*, src/Html/Gradient/*PDF-Operator-StreamComputedStyle (unveränderlich) + GeometrieGeometrie berechnen; CSS parsen; über Seitenumbrüche entscheiden
SchichtDateien (repräsentativ)Rolle
5 — Paged MediaPageBreakController, PageBorderPainter, PageRule, PageRuleParser, ParserConfigurator@page-Regeln auflösen; Umbruch- und orphan/widow-Constraints auswerten; Seitendekoration an Paint delegieren.
6 — Messung & HarnessWPT-Klassifizierer-Skripte, tests/Support/*Testergebnisse klassifizieren; Regressions-Snapshots erzeugen; Assertion-Hilfen bereitstellen. Trägt keine Rendering-Logik.

Der Vertrag wird durch die Platzierung der Klassen und durch den Docblock von HtmlStyleState erzwungen. Prüfen Sie dies anhand von src/Html/.

SymbolSchichtVertragsrolle
PropertyApplicatorInterface1Strategieinterface; die einzige Stelle, die CSS-Computed-Felder schreibt.
ParserConfigurator::buildCssApplicator()1 (Verdrahtung)Registriert jeden Applicator. Eine neue CSS-Eigenschaft wird hier registriert.
HtmlStyleState2Sammlung mit zwei Gruppen; der Klassen-Docblock nennt die pro Feld zuständige Schicht.
HtmlStyleState::toComputedStyle()2Erzeugt das unveränderliche ComputedStyle für die Paint-Schicht.
FormattingContextFactory::dispatchOpenTag()3Einziger Routingpunkt für neues Layout-Verhalten.
PageBorderPainter::buildStream()4Seitendekoration; wird aus Schicht 5 aufgerufen, nicht in HtmlParser eingebettet.

Aufrufender Code berührt die Schichten nie. Der vierschichtige Ablauf läuft innerhalb eines einzigen Aufrufs.

<?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');

Der Vertrag ist für Mitwirkende wichtig, nicht für aufrufenden Code. Um eine CSS-Eigenschaft hinzuzufügen, folgen Sie dem Erweiterungspunkt von Schicht 1: Legen Sie einen Applicator an, ergänzen Sie ein typisiertes HtmlStyleState-Feld mit einem Schicht-Docblock und registrieren Sie den Applicator in ParserConfigurator. Die folgende Darstellung zeigt die Form des Applicator-Vertrags. Sehen Sie sich src/Html/Applicator/ an, um eine konkrete Klasse zum Kopieren zu finden.

<?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() liest rohes CSS. Dies ist die einzige dokumentierte Vertragsausnahme, aufgeschoben auf einen künftigen TableApplicator. Kopieren Sie dieses Muster nicht.
  • Sechs Schichten, vierschichtiger Kern. ADR-010 nummeriert sechs Schichten. Der Datenflussvertrag ist der vierschichtige Kern; Paged Media und Messung sind Zusatzschichten.
  • HtmlStyleState hat zwei Gruppen. Es trägt CSS-Computed-Felder und Layout-Tracking-Felder. Nur Applicators schreiben in die CSS-Gruppe. Paint liest ComputedStyle, nie die Layout-Tracking-Felder.
  • HtmlParser hat keine Schicht. Er ist der Orchestrator. CSS-Parsing, Geometrie-Mathematik und Paint-Ausgabe gehören nicht in ihn.

Der Schichtvertrag ist strukturell und verursacht keine Laufzeitkosten. HtmlStyleState::toComputedStyle() erzeugt einen unveränderlichen Snapshot für jedes Element, das Paint benötigt. Durch den Snapshot muss Paint-Code nicht aus der veränderlichen State-Sammlung lesen. Die Rendering-Kosten werden vom Streaming-Modell bestimmt, nicht von der Schichtung. Das performance_budget pro Seite (wall_ms: 1500, peak_mb: 64) ist die operative Obergrenze.

Die Schichttrennung stützt das Sicherheitsmodell. Schicht 1 parst und filtert CSS-Werte per Policy, bevor sie Layout- oder Paint-Code erreichen, sodass DefaultHtmlSecurityPolicy::isCssPropertyAllowed() der einzige Entscheidungspunkt ist. Paint liest nie angreiferkontrolliertes rohes CSS. Siehe das Sicherheitsmodell des HTML-Moduls.

Diese Seite zitiert keinen externen Standard. Die Schichtgrenzen leiten sich aus ADR-010 und aus dem Klassen-Docblock von HtmlStyleState ab, der den Vertrag im Quellcode abbildet. Die CSS-Verhaltenskonformität ist auf css-resolver dokumentiert.

Enterprise-Funktion. Premium-CSS-Funktionen erweitern dieselben vier Schichten über die dokumentierten Erweiterungspunkte. Es gibt keine separate Premium-Pipeline. Siehe die CSS-Support-Matrix.