Aller au contenu

Contrats des couches du moteur HTML (ADR-010)

Le sous-système HTML sépare l’analyse CSS, l’état de style, la mise en page et la peinture en quatre couches. Le flux défini par leur contrat ne circule que dans un seul sens. L’ADR-010 fixe leurs frontières et leurs règles d’extension.

Fenêtre de terminal
composer require nextpdf/core:^3

L’ADR-010 (« Contrats des couches du moteur, propriété du chemin critique et règles d’extension », acceptée le 2026-04-12) formalise le découpage en couches du sous-système HTML. Le contrat de rendu du cœur s’articule autour de quatre couches : analyse CSS et applicateurs, état de style, mise en page et formatage, puis peinture. L’ADR-010 documente aussi deux couches adjointes — le média paginé et le harnais de mesure — qui enveloppent ce cœur à quatre couches sans modifier son flux de données. Dans le glossaire, le terme canonique pour ce cœur est « HTML pipeline », un pipeline à quatre couches.

Les données ne circulent que dans un seul sens. Le texte CSS devient des valeurs typées dans la couche 1. La couche 1 écrit ces valeurs dans les champs de HtmlStyleState, dans la couche 2. La couche 3 lit les champs d’état de style et calcule la géométrie. La couche 4 lit un instantané ComputedStyle immuable, accompagné de la géométrie, puis émet des opérateurs PDF. Aucune couche ne lit de données issues d’une couche située après elle.

La séparation en quatre couches n’est pas seulement documentaire. L’ADR-010 consigne deux refactorisations bornées, appliquées dans v1.2.0, qui ont replacé du code dans la bonne couche. PageBorderPainter a été extrait de HtmlParser afin que les opérateurs de peinture ne résident plus dans l’orchestrateur. Le docbloc de la classe HtmlStyleState porte désormais le contrat de couche formel, qui précise quels champs chaque couche peut écrire ou lire.

Une frontière est explicitée plutôt que dissimulée. FormattingContextFactory::startTable() lit encore directement cinq clés CSS brutes. L’ADR-010 consigne cette exception comme une dette technique connue, reportée à un futur TableApplicator, et non comme le contrat prévu. Documenter l’exception fait partie du contrat.

CoucheFichiers (représentatifs)ÉcritLitNe doit pas
1 — analyse CSS et applicateursCssValueParser, CssResolver, HtmlCssApplicator, src/Html/Applicator/*HtmlStyleState (champs CSS)Texte CSS brutCalculer la géométrie ; émettre des opérateurs
2 — état de styleHtmlStyleState, State/ComputedStyle, State/LayoutState— (sac de valeurs passif)Analyser le CSS ; décider de la mise en page ; émettre des opérateurs
3 — mise en page et formatageFormattingContextFactory, HtmlBlockHandler, FlexLayoutEngine, TableParser, FloatContextGéométrie du curseurHtmlStyleState (champs)Lire le $css[...] brut ; émettre des opérateurs de peinture
4 — peinture et renduBorderRenderer, BackgroundImageRenderer, src/Html/Paint/*, src/Html/Gradient/*Flux d’opérateurs PDFComputedStyle (immuable) + géométrieCalculer la géométrie ; analyser le CSS ; décider des sauts de page
CoucheFichiers (représentatifs)Rôle
5 — média paginéPageBreakController, PageBorderPainter, PageRule, PageRuleParser, ParserConfiguratorRésoudre les règles @page ; évaluer les contraintes de saut et orphan/widow ; déléguer la décoration de page à la peinture.
6 — mesure et harnaisScripts de classification WPT, tests/Support/*Classer les résultats de test ; produire des instantanés de régression ; fournir des assistants d’assertion. Ne contient aucune logique de rendu.

Le contrat est imposé à la fois par l’emplacement des classes et par le docbloc de HtmlStyleState. Vérifie-le dans src/Html/.

SymboleCoucheRôle dans le contrat
PropertyApplicatorInterface1Interface de stratégie ; le seul endroit qui écrit les champs CSS calculés.
ParserConfigurator::buildCssApplicator()1 (câblage)Enregistre chaque applicateur. Toute nouvelle propriété CSS y est enregistrée.
HtmlStyleState2Sac de valeurs à double groupe ; le docbloc de la classe indique la couche propriétaire de chaque champ.
HtmlStyleState::toComputedStyle()2Produit le ComputedStyle immuable pour la couche de peinture.
FormattingContextFactory::dispatchOpenTag()3Point de routage unique pour tout nouveau comportement de mise en page.
PageBorderPainter::buildStream()4Décoration de page appelée depuis la couche 5, sans intégration en ligne dans HtmlParser.

Les appelants n’ont jamais à manipuler les couches. Le flux à quatre couches s’exécute dans un seul appel.

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

Ce contrat concerne les contributeurs, pas les appelants. Pour ajouter une propriété CSS, suis le point d’extension de la couche 1 : crée un applicateur, ajoute un champ HtmlStyleState typé avec un docbloc de couche, et enregistre l’applicateur dans ParserConfigurator. L’extrait ci-dessous illustre la forme du contrat d’applicateur. Consulte src/Html/Applicator/ pour une classe concrète à copier.

<?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() lit du CSS brut. C’est la seule exception documentée au contrat ; elle est reportée à un futur TableApplicator. Ne copie pas ce modèle.
  • Six couches, un cœur à quatre couches. L’ADR-010 numérote six couches. Le contrat de flux de données correspond au cœur à quatre couches ; le média paginé et la mesure sont des adjoints.
  • HtmlStyleState est à double groupe. Il contient des champs CSS calculés et des champs de suivi de mise en page. Seuls les applicateurs écrivent le groupe CSS. La peinture lit ComputedStyle, jamais les champs de suivi de mise en page.
  • HtmlParser n’a pas de couche. C’est l’orchestrateur. L’analyse CSS, le calcul de géométrie et l’émission de peinture ne doivent pas y résider.

Le contrat de couche est structurel et n’ajoute aucun coût à l’exécution. HtmlStyleState::toComputedStyle() produit un seul instantané immuable par élément qui doit être peint. L’instantané évite au code de peinture d’avoir à lire le sac d’état mutable. Le coût de rendu est régi par le modèle de streaming, pas par le découpage en couches. Le performance_budget par page (wall_ms: 1500, peak_mb: 64) est le plafond opérationnel.

La séparation en couches soutient le modèle de sécurité. La couche 1 analyse et filtre les valeurs CSS selon la politique avant qu’elles ne soient visibles par le moindre code de mise en page ou de peinture, si bien que DefaultHtmlSecurityPolicy::isCssPropertyAllowed() est l’unique point de contrôle. La peinture ne lit jamais de CSS brut contrôlé par un attaquant. Consulte le modèle de sécurité du module HTML.

Cette page ne cite aucune norme externe. Les frontières des couches dérivent de l’ADR-010 et du docbloc de la classe HtmlStyleState, qui encode le contrat dans le code source. La conformité comportementale du CSS est documentée dans css-resolver.

Capacité Enterprise. Les fonctionnalités CSS Premium étendent ces mêmes quatre couches au moyen des points d’extension documentés. Il n’existe pas de pipeline Premium distinct. Consulte la matrice de prise en charge CSS.