Ga naar inhoud

Laagcontracten voor de HTML-engine (ADR-010)

Het subsysteem voor Hypertext Markup Language (HTML) verdeelt Cascading Style Sheets (CSS) parsen, stijlstatus, lay-out en tekenen over vier lagen. Gegevens stromen maar één kant op door die lagen. Architecture Decision Record 010 (ADR-010) definieert de grenzen en de uitbreidingsregels.

Terminal window
composer require nextpdf/core:^3

Architecture Decision Record 010 (ADR-010) (“Engine Layer Contracts, Hot Path Ownership, and Extension Rules”, aanvaard op 2026-04-12) formaliseert hoe het HTML-subsysteem in lagen is opgebouwd. Het kerncontract voor weergave heeft vier lagen: CSS parsen en applicators, stijlstatus, lay-out en opmaak, en tekenen. ADR-010 documenteert ook twee adjunctlagen: paged media en het meet- en testharnas. Ze omgeven de vierlaagse kern zonder de gegevensstroom ervan te wijzigen. De canonieke woordenlijstterm voor de kern is “HTML pipeline”, een vierlaagse pijplijn.

Gegevens stromen in één richting. CSS-tekst wordt in laag 1 omgezet in getypeerde waarden. Laag 1 schrijft die waarden naar de HtmlStyleState-velden in laag 2. Laag 3 leest stijlstatusvelden en berekent geometrie. Laag 4 leest een onveranderlijke ComputedStyle-momentopname plus geometrie en zendt Portable Document Format-operatoren (PDF) uit. Geen enkele laag leest gegevens uit een latere laag.

De vierlaagse scheiding is meer dan documentatie. ADR-010 legt twee afgebakende refactors vast die in v1.2.0 zijn doorgevoerd en code naar de juiste laag hebben verplaatst. PageBorderPainter is uit HtmlParser gehaald, zodat tekenoperatoren niet langer in de orkestrator zitten. Het docblock van de klasse HtmlStyleState bevat nu het formele laagcontract en vermeldt welke velden elke laag mag schrijven of lezen.

Eén grens wordt expliciet gemaakt. FormattingContextFactory::startTable() leest nog steeds rechtstreeks vijf ruwe CSS-sleutels. ADR-010 legt dit vast als bekende, uitgestelde technische schuld voor een toekomstige TableApplicator, en niet als het beoogde contract. De uitzondering documenteren maakt deel uit van het contract.

LaagBestanden (representatief)SchrijftLeestMag niet
1 — CSS parsen en applicatorsCssValueParser, CssResolver, HtmlCssApplicator, src/Html/Applicator/*HtmlStyleState-CSS-veldenRuwe CSS-tekstGeometrie berekenen; operatoren uitzenden
2 — StijlstatusHtmlStyleState, State/ComputedStyle, State/LayoutState— (passieve waardecontainer)CSS parsen; lay-out bepalen; operatoren uitzenden
3 — Lay-out en opmaakFormattingContextFactory, HtmlBlockHandler, FlexLayoutEngine, TableParser, FloatContextCursorgeometrieHtmlStyleState-veldenRuwe $css[...] lezen; tekenoperatoren uitzenden
4 — Tekenen en weergevenBorderRenderer, BackgroundImageRenderer, src/Html/Paint/*, src/Html/Gradient/*PDF-operatorstreamComputedStyle (onveranderlijk) + geometrieGeometrie berekenen; CSS parsen; pagina-einden bepalen
LaagBestanden (representatief)Rol
5 — Paged mediaPageBreakController, PageBorderPainter, PageRule, PageRuleParser, ParserConfigurator@page-regels oplossen; break- en orphan/widow-beperkingen evalueren; paginadecoratie delegeren aan de tekenlaag.
6 — Meting en testharnasClassificatiescripts voor Web Platform Tests (WPT), tests/Support/*Testuitkomsten classificeren; regressiemomentopnamen produceren; assertiehelpers leveren. Bevat geen weergavelogica.

Het contract wordt afgedwongen door de plaatsing van klassen en het HtmlStyleState-docblock. Controleer dit in src/Html/.

SymboolLaagContractrol
PropertyApplicatorInterface1Strategie-interface; de enige plek die berekende CSS-velden schrijft.
ParserConfigurator::buildCssApplicator()1 (bedrading)Registreert elke applicator. Een nieuwe CSS-eigenschap registreert zich hier.
HtmlStyleState2Container met twee groepen; het docblock van de klasse vermeldt per veld de eigenaarlaag.
HtmlStyleState::toComputedStyle()2Produceert de onveranderlijke ComputedStyle voor de tekenlaag.
FormattingContextFactory::dispatchOpenTag()3Eén routeringspunt voor nieuw lay-outgedrag.
PageBorderPainter::buildStream()4Paginadecoratie; aangeroepen vanuit laag 5, niet inline in HtmlParser opgenomen.

Je raakt de lagen nooit rechtstreeks aan. De vierlaagse stroom verloopt via één aanroep.

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

Het contract doet ertoe wanneer je bijdraagt, niet wanneer je de bibliotheek aanroept. Gebruik het uitbreidingspunt van laag 1 om een CSS-eigenschap toe te voegen: maak een applicator, voeg een getypeerd HtmlStyleState-veld met een laagdocblock toe en registreer de applicator in ParserConfigurator. Het onderstaande voorbeeld toont de vorm van het applicatorcontract. Gebruik src/Html/Applicator/ als model voor een concrete klasse.

<?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() leest ruwe CSS. Dit is de enige gedocumenteerde uitzondering op het contract; deze is uitgesteld tot een toekomstige TableApplicator. Neem dit patroon niet over.
  • Zes lagen, vierlaagse kern. ADR-010 nummert zes lagen. Het gegevensstroomcontract betreft de vierlaagse kern; paged media en meting zijn adjuncten.
  • HtmlStyleState heeft twee groepen. Het bevat berekende CSS-velden en lay-outvolgvelden. Alleen applicators schrijven naar de CSS-groep. De tekenlaag leest ComputedStyle, nooit de lay-outvolgvelden.
  • HtmlParser heeft geen laag. Het is de orkestrator. CSS parsen, geometrie berekenen en tekeninstructies uitzenden horen daar niet thuis.

Het laagcontract is structureel en heeft dus geen runtimekosten. HtmlStyleState::toComputedStyle() produceert één onveranderlijke momentopname voor elk element waarvoor tekenwerk nodig is. Dankzij die momentopname hoeft de tekencode de veranderlijke statuscontainer niet te gebruiken. De weergavekosten worden bepaald door het streamingmodel, niet door de gelaagdheid. Het performance_budget per pagina (wall_ms: 1500, peak_mb: 64) blijft het operationele plafond.

De laagscheiding ondersteunt het beveiligingsmodel. Laag 1 parseert CSS-waarden en filtert ze volgens het beleid voordat lay-out- of tekencode ze ziet, zodat DefaultHtmlSecurityPolicy::isCssPropertyAllowed() de enige toegangspoort blijft. De tekenlaag leest nooit door een aanvaller gecontroleerde ruwe CSS. Zie het beveiligingsmodel van de HTML-module.

Deze pagina citeert geen externe standaard. De laaggrenzen komen voort uit ADR-010 en het docblock van de klasse HtmlStyleState, waarmee het contract in de broncode is vastgelegd. CSS-gedragsconformiteit is gedocumenteerd in css-resolver.

Enterprise-functionaliteit. Premium CSS-functies gebruiken dezelfde vier lagen via de gedocumenteerde uitbreidingspunten. Er is geen aparte Premium-pijplijn. Zie de CSS-ondersteuningsmatrix.