Résolveur CSS : cascade et spécificité
CssResolver fait correspondre les sélecteurs au flux de tokens, ordonne les règles correspondantes selon la couche de cascade, la spécificité et l’ordre du document, puis applique !important lors d’une seconde passe.
Installation
Section intitulée « Installation »composer require nextpdf/core:^3Vue d’ensemble conceptuelle
Section intitulée « Vue d’ensemble conceptuelle »CssResolver est le composant de la couche 1 (selon ADR-010). Il détient les règles CSS analysées et détermine quelles déclarations s’appliquent à chaque élément. Il a été extrait de HtmlParser pour clarifier la structure ; il s’agit d’une classe interne plutôt que d’une API publique.
Le résolveur fonctionne sans arbre de document. La correspondance des sélecteurs lit le flux de tokens à plat et s’appuie aussi sur les cartes d’index que HtmlChildScanner construit à l’étape 3 du pipeline : nombre d’enfants, nombre de balises identiques et vacuité. Les pseudo-classes structurelles sont résolues à partir de ces cartes. Le sélecteur relationnel :has() repose sur le pré-balayage borné décrit dans les contraintes de streaming.
La résolution de la cascade s’exécute en deux passes à l’intérieur de CssResolver::resolveMatchingProperties(). La passe 1 applique les déclarations normales dans l’ordre de la cascade, c’est-à-dire d’abord le poids de la couche de cascade, puis la spécificité, puis l’ordre du document. La passe 2 applique ensuite les déclarations !important dans l’ordre de spécificité, et une déclaration !important l’emporte sur toute déclaration normale, quelle que soit sa spécificité. Cette séparation en deux passes est la stratégie d’implémentation ; elle produit l’ensemble de propriétés résolu que la couche de mise en page consomme ensuite.
L’ordre de cascade implémenté par le résolveur est aligné sur la spécification W3C CSS Cascading and Inheritance. Les déclarations sont triées d’abord par origine et importance, puis par spécificité du sélecteur. À spécificité égale, la dernière déclaration dans l’ordre du document l’emporte (CSS Cascade 5 §6.4 ; voir Conformité). Le commentaire dans le code source de CssResolver cite la même clause, ce qui te donne une troisième piste de vérification pour ce comportement, en plus de la spécification et du glossaire.
La spécificité est calculée sous forme de triplet (A, B, C) à partir du nombre de composants ID, classe et type, et les triplets sont comparés composant par composant (Selectors Level 4 §16). NextPDF calcule la spécificité de chaque règle correspondante avant le tri de la cascade.
Une contrainte mérite d’être formulée clairement. La règle d’inversion de couche du §6.4.3 s’applique aux déclarations !important à travers les couches de cascade, et le code source la consigne comme en suspens pour le lot de travail sur les couches de cascade. Lorsque des couches de cascade sont déclarées et que !important traverse les couches, l’ordre résolu peut différer du comportement complet de la spécification. La matrice de prise en charge CSS fait autorité sur l’état de prise en charge par fonctionnalité, et cette page ne reprend pas la prise en charge propriété par propriété.
Surface d’API
Section intitulée « Surface d’API »| Symbole | Emplacement | Rôle |
|---|---|---|
CssResolver::parseStyleBlock(string $css, bool $nestingEnabled = false): void | src/Html/CssResolver.php | Analyse un bloc <style> en règles. |
CssResolver::resolveMatchingProperties(...) | src/Html/CssResolver.php | Fait correspondre les sélecteurs et résout la cascade en deux passes. |
CssResolver::resolveHasSelectors(array $tokens): array | src/Html/CssResolver.php | Pré-balayage borné de :has() (sous condition). |
CssResolver::resolveFirstLetterProperties(...) | src/Html/CssResolver.php | Résout les propriétés ::first-letter. |
CssResolver::resolvePseudoElementProperties(...) | src/Html/CssResolver.php | Résout les propriétés ::before / ::after. |
CssResolver::getLayerRegistry(): LayerRegistry | src/Html/CssResolver.php | Couches de cascade déclarées. |
Exemple de code — Démarrage rapide
Section intitulée « Exemple de code — Démarrage rapide »Les appelants n’invoquent pas directement le résolveur ; ils écrivent du CSS, et le résolveur s’exécute à l’intérieur de writeHtml(). La cascade ci-dessous résout p en rouge, car la règle de classe a une spécificité plus élevée que la règle de type.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->addPage();$doc->writeHtml( '<style>p { color: blue; } .lead { color: red; }</style>' . '<p class="lead">Higher-specificity class wins.</p>');$doc->save(__DIR__ . '/output/cascade.pdf');Exemple de code — Production
Section intitulée « Exemple de code — Production »Démontre la seconde passe !important. La déclaration de classe équivalente à l’inline est remplacée par la déclaration de type !important, même si le sélecteur de classe a une spécificité plus élevée.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->addPage();$doc->writeHtml( '<style>p { color: green !important; } .lead { color: red; }</style>' . '<p class="lead">!important overrides higher specificity.</p>');$doc->save(__DIR__ . '/output/important.pdf');Cas limites et pièges
Section intitulée « Cas limites et pièges »!importantignore la spécificité. La passe 2 applique les déclarations!importantdans l’ordre de spécificité, et elles l’emportent toujours sur les déclarations normales.- Couches de cascade +
!importantà travers les couches. La règle d’inversion de couche du §6.4.3 pour les déclarations importantes est enregistrée comme en suspens dans le code source. Vérifie le comportement avec la matrice de prise en charge CSS avant de t’y fier. - L’absence de couche déclarée est le chemin rapide. Sans
@layer, l’ordonnancement se réduit à la seule spécificité et est identique bit pour bit au comportement antérieur aux couches. :has()est sous condition. Le pré-balayage relationnel ne s’exécute que lorsque la fonctionnalité expérimentalecss.hasest activée.- La correspondance des sélecteurs est basée sur le flux. Les sélecteurs structurels utilisent des cartes d’index, pas un parcours d’arbre. Un sélecteur qui nécessiterait une navigation arbitraire dans l’arbre au-delà des cartes d’index n’est pas résolvable dans ce modèle.
Performances
Section intitulée « Performances »La correspondance des sélecteurs est en O(règles × éléments) dans le pire des cas, bornée par les plafonds de streaming. Les deux tris de cascade sont en O(règles correspondantes · log règles correspondantes) par élément. Le chemin sans couche saute entièrement la résolution des couches. Le performance_budget par page (wall_ms: 1500, peak_mb: 64) est le plafond opérationnel. Les régressions sont protégées par le benchmark du pipeline de rendu HTML (travail fusionné, PR #564).
Notes de sécurité
Section intitulée « Notes de sécurité »Le résolveur ne voit que le CSS admis par DefaultHtmlSecurityPolicy::isCssPropertyAllowed(). La liste d’autorisation est le plafond de sécurité, et la table de prise en charge à l’exécution est un plafond de capacité distinct. Une propriété bloquée par la politique n’atteint jamais la cascade. Voir le modèle de sécurité du module HTML.
Conformité
Section intitulée « Conformité »| Comportement | Spécification | Clause | reference_id |
|---|---|---|---|
| Tri de cascade : origin/importance → spécificité → ordre d’apparition | W3C CSS Cascading and Inheritance Level 5 | §6.4 (css_cascade_5#x1.x7.x1.p21) | |
| Spécificité sous forme de triplet (A,B,C) à partir du nombre d’ID/classes/types | W3C Selectors Level 4 | §16 (selectors_4#x1.x16.p2) | |
| Analyse déterministe et récupération sur erreur d’analyse | W3C CSS Syntax Level 3 | §4 (css_syntax_3#x1.x4.p2) |
Le matériel W3C est sous licence CC-BY 4.0. Les affirmations ci-dessus sont paraphrasées. Les identifiants de clause et de chunk sont fournis à des fins de vérification. NextPDF ne revendique pas une conformité complète à ces modules — voir la matrice de prise en charge CSS pour l’état vérifié par module.
Contexte commercial
Section intitulée « Contexte commercial »Capacité Enterprise. Premium élargit l’ensemble des propriétés correspondantes et appliquées. L’algorithme de cascade et le modèle
!importanten deux passes sont identiques entre les éditions. Voir la matrice de prise en charge CSS.