Salta ai contenuti

Contratti dei layer del motore HTML (ADR-010)

Il sottosistema HTML separa parsing CSS, stato di stile, impaginazione e paint in quattro layer, con un contratto tra layer che procede in una sola direzione. ADR-010 ne definisce i confini e le regole di estensione.

Terminal window
composer require nextpdf/core:^3

ADR-010 («Engine Layer Contracts, Hot Path Ownership, and Extension Rules», accettato il 2026-04-12) formalizza la suddivisione in layer del sottosistema HTML. Il contratto di rendering principale comprende quattro layer: parsing CSS e applicator, stato di stile, impaginazione e formattazione, e paint. ADR-010 documenta inoltre due layer accessori — paged media e harness di misurazione — che racchiudono il nucleo a quattro layer senza alterarne il flusso di dati. Il termine canonico del glossario per il nucleo è «HTML pipeline», una pipeline a quattro layer.

I dati scorrono in una sola direzione. Il testo CSS diventa valori tipizzati nel Layer 1. Il Layer 1 scrive tali valori nei campi di HtmlStyleState nel Layer 2. Il Layer 3 legge i campi dello stato di stile e calcola la geometria. Il Layer 4 legge uno snapshot immutabile di ComputedStyle insieme alla geometria ed emette operatori PDF. Nessun layer legge da un layer successivo.

La separazione in quattro layer non è solo documentazione. ADR-010 registra due refactor circoscritti applicati nella v1.2.0, che hanno spostato il codice nel layer corretto. PageBorderPainter è stato estratto da HtmlParser affinché gli operatori di paint non risiedano più nell’orchestratore. Il docblock della classe HtmlStyleState riporta ora il contratto formale dei layer, indicando quali campi ciascun layer può scrivere o leggere.

Un confine è dichiarato esplicitamente, non nascosto. FormattingContextFactory::startTable() legge ancora direttamente cinque chiavi CSS grezze. ADR-010 lo registra come debito tecnico noto, rinviato in attesa di un futuro TableApplicator, e non come contratto previsto. Documentare l’eccezione fa parte del contratto.

LayerFile (rappresentativi)ScriveLeggeNon deve
1 — Parsing CSS e applicatorCssValueParser, CssResolver, HtmlCssApplicator, src/Html/Applicator/*Campi CSS di HtmlStyleStateTesto CSS grezzoCalcolare la geometria; emettere operatori
2 — Stato di stileHtmlStyleState, State/ComputedStyle, State/LayoutState— (contenitore passivo di valori)Effettuare il parsing del CSS; decidere l’impaginazione; emettere operatori
3 — Impaginazione e formattazioneFormattingContextFactory, HtmlBlockHandler, FlexLayoutEngine, TableParser, FloatContextGeometria del cursoreCampi di HtmlStyleStateLeggere $css[...] grezzo; emettere operatori di paint
4 — Paint e renderingBorderRenderer, BackgroundImageRenderer, src/Html/Paint/*, src/Html/Gradient/*Flusso di operatori PDFComputedStyle (immutabile) + geometriaCalcolare la geometria; effettuare il parsing del CSS; decidere le interruzioni di pagina
LayerFile (rappresentativi)Ruolo
5 — Paged mediaPageBreakController, PageBorderPainter, PageRule, PageRuleParser, ParserConfiguratorRisolvere le regole @page; valutare i vincoli di interruzione e orphan/widow; delegare al paint la decorazione della pagina.
6 — Misurazione e harnessScript classificatori WPT, tests/Support/*Classificare gli esiti dei test; produrre snapshot di regressione; fornire helper di asserzione. Non contiene logica di rendering.

Il contratto è imposto dalla collocazione delle classi e dal docblock di HtmlStyleState. Verificare rispetto a src/Html/.

SimboloLayerRuolo nel contratto
PropertyApplicatorInterface1Interfaccia strategy; unico punto in cui vengono scritti i campi CSS calcolati.
ParserConfigurator::buildCssApplicator()1 (wiring)Registra ogni applicator. Le nuove proprietà CSS vanno registrate qui.
HtmlStyleState2Contenitore a doppio gruppo; il docblock della classe indica il layer proprietario di ogni campo.
HtmlStyleState::toComputedStyle()2Produce il ComputedStyle immutabile per il layer di paint.
FormattingContextFactory::dispatchOpenTag()3Punto unico di instradamento per i nuovi comportamenti di impaginazione.
PageBorderPainter::buildStream()4Decorazione della pagina; richiamata dal Layer 5, non inserita inline in HtmlParser.

I chiamanti non interagiscono mai con i layer. Il flusso a quattro layer avviene all’interno di una singola chiamata.

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

Il contratto riguarda i contributor, non i chiamanti. Per aggiungere una proprietà CSS, seguire il punto di estensione del Layer 1: creare un applicator, aggiungere un campo HtmlStyleState tipizzato con un docblock di layer e registrare l’applicator in ParserConfigurator. L’esempio seguente mostra la forma del contratto dell’applicator. Consultare src/Html/Applicator/ per una classe concreta da copiare.

<?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() legge CSS grezzo. È l’unica eccezione documentata al contratto, rinviata a un futuro TableApplicator. Non replicare questo pattern.
  • Sei layer, nucleo a quattro layer. ADR-010 numera sei layer. Il contratto del flusso di dati coincide con il nucleo a quattro layer; paged media e misurazione sono accessori.
  • HtmlStyleState è a doppio gruppo. Contiene campi calcolati CSS e campi di tracciamento dell’impaginazione. Solo gli applicator scrivono il gruppo CSS. Il paint legge ComputedStyle, mai i campi di tracciamento dell’impaginazione.
  • HtmlParser non ha alcun layer. È l’orchestratore. Il parsing CSS, i calcoli geometrici e l’emissione del paint non devono risiedere al suo interno.

Il contratto dei layer è strutturale e non aggiunge alcun costo a runtime. HtmlStyleState::toComputedStyle() produce uno snapshot immutabile per ogni elemento che richiede il paint. Lo snapshot evita al codice di paint di leggere il contenitore di stato mutabile. Il costo del rendering è governato dal modello di streaming, non dalla suddivisione in layer. Il performance_budget per pagina (wall_ms: 1500, peak_mb: 64) è il limite operativo.

La separazione in layer sostiene il modello di sicurezza. Il Layer 1 effettua il parsing e applica i filtri di policy ai valori CSS prima che qualunque codice di impaginazione o paint li veda; di conseguenza, DefaultHtmlSecurityPolicy::isCssPropertyAllowed() è l’unico gate. Il paint non legge mai CSS grezzo controllato da un utente malintenzionato. Vedere il modello di sicurezza del modulo HTML.

Questa pagina non cita alcuno standard esterno. I confini tra i layer derivano da ADR-010 e dal docblock della classe HtmlStyleState, che codifica il contratto nel sorgente. La conformità comportamentale CSS è documentata in css-resolver.

Funzionalità Enterprise. Le funzionalità CSS Premium estendono questi stessi quattro layer attraverso i punti di estensione documentati. Non esiste una pipeline Premium separata. Vedere la matrice di supporto CSS.