Resolvedor de CSS: cascata e especificidade
Visão geral
Seção intitulada “Visão geral”O CssResolver faz a correspondência de seletores com o fluxo de tokens, ordena as regras correspondentes por camada de cascata, especificidade e ordem no documento e, em seguida, aplica !important em uma segunda passagem.
Instalação
Seção intitulada “Instalação”composer require nextpdf/core:^3Visão conceitual
Seção intitulada “Visão conceitual”CssResolver é o componente de Layer 1 (conforme ADR-010). Ele recebe as regras Cascading Style Sheets (CSS) analisadas e decide quais declarações se aplicam a cada elemento. A classe foi extraída de HtmlParser para manter a estrutura clara e é interna, não uma application programming interface (API) pública.
O resolvedor não precisa de uma árvore do documento. A correspondência de seletores lê o fluxo plano de tokens e usa os mapas de índice que HtmlChildScanner constrói no Estágio 3 do pipeline: contagens de filhos, contagens de mesma tag e vacuidade. Esses mapas atendem às pseudoclasses estruturais. O seletor relacional :has() usa a pré-varredura limitada descrita em restrições de streaming.
A resolução da cascata é executada em duas passagens dentro de CssResolver::resolveMatchingProperties(). A Passagem 1 aplica declarações normais na ordem da cascata: primeiro o peso da camada de cascata, depois a especificidade e, em seguida, a ordem no documento. A Passagem 2 aplica as declarações !important por especificidade. Uma declaração !important sobrescreve qualquer declaração normal, independentemente da especificidade. Essa divisão em duas passagens é a estratégia de implementação e produz o conjunto resolvido de propriedades que a camada de layout consome.
A ordem da cascata implementada pelo resolvedor está alinhada com a especificação CSS Cascading and Inheritance do World Wide Web Consortium (W3C). As declarações são ordenadas primeiro por origem e importância e, depois, pela especificidade do seletor. Quando a especificidade é igual, vence a última declaração na ordem do documento (CSS Cascade 5 §6.4; consulte Conformidade). O comentário no código-fonte em CssResolver cita a mesma cláusula, então você tem uma terceira forma de verificar esse comportamento, além da especificação e do glossário.
A especificidade é calculada como uma tripla (A, B, C) a partir das contagens de componentes de ID, classe e tipo, e as triplas são comparadas componente por componente (Selectors Level 4 §16). O NextPDF calcula a especificidade de cada regra correspondida antes de ordenar a cascata.
Há uma restrição importante. A regra de inversão de camadas §6.4.3 aplica-se às declarações !important entre camadas de cascata, e o código-fonte a registra como pendente para o cluster de trabalho de camadas de cascata. Quando camadas de cascata são declaradas e !important cruza camadas, a ordem resolvida pode diferir do comportamento completo da especificação. A matriz de suporte a CSS é a autoridade para o estado de suporte por recurso, e esta página não reapresenta o suporte por propriedade.
Superfície da API
Seção intitulada “Superfície da API”| Símbolo | Localização | Função |
|---|---|---|
CssResolver::parseStyleBlock(string $css, bool $nestingEnabled = false): void | src/Html/CssResolver.php | Analisa um bloco <style> em regras. |
CssResolver::resolveMatchingProperties(...) | src/Html/CssResolver.php | Faz a correspondência de seletores e resolve a cascata em duas passagens. |
CssResolver::resolveHasSelectors(array $tokens): array | src/Html/CssResolver.php | Pré-varredura limitada de :has() (controlada por gate). |
CssResolver::resolveFirstLetterProperties(...) | src/Html/CssResolver.php | Resolve as propriedades de ::first-letter. |
CssResolver::resolvePseudoElementProperties(...) | src/Html/CssResolver.php | Resolve as propriedades de ::before / ::after. |
CssResolver::getLayerRegistry(): LayerRegistry | src/Html/CssResolver.php | Camadas de cascata declaradas. |
Exemplo de código — Início rápido
Seção intitulada “Exemplo de código — Início rápido”Você não chama o resolvedor diretamente. Você escreve CSS, e o resolvedor é executado dentro de writeHtml(). Na cascata abaixo, p é resolvido como vermelho porque a regra de classe tem especificidade maior que a regra de tipo.
<?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');Exemplo de código — Produção
Seção intitulada “Exemplo de código — Produção”Este exemplo mostra a segunda passagem de !important. A declaração de tipo !important sobrescreve a declaração de classe equivalente inline, mesmo que o seletor de classe tenha especificidade maior.
<?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');Casos extremos e pegadinhas
Seção intitulada “Casos extremos e pegadinhas”!importantignora a especificidade. A Passagem 2 aplica as declarações!importantpor especificidade, e essas declarações sempre sobrescrevem as declarações normais.- Camadas de cascata +
!importantentre camadas. O código-fonte registra como pendente a regra de inversão de camadas §6.4.3 para declarações important. Verifique o comportamento na matriz de suporte a CSS antes de depender dele. - Nenhuma camada declarada é o caminho rápido. Sem
@layer, a ordenação se reduz a um comportamento baseado apenas em especificidade e é idêntica bit a bit ao comportamento anterior às camadas. :has()é controlado por gate. A pré-varredura relacional é executada somente quando o recurso experimentalcss.hasestá ativado.- A correspondência de seletores é baseada em fluxo. Os seletores estruturais usam mapas de índice, não um percurso de árvore. Um seletor que exigisse navegação arbitrária na árvore além dos mapas de índice não é resolvível neste modelo.
Desempenho
Seção intitulada “Desempenho”No pior caso, a correspondência de seletores é O(regras × elementos), limitada pelos limites de streaming. As duas ordenações da cascata são O(regras correspondidas · log regras correspondidas) por elemento. O caminho sem camadas ignora completamente a resolução de camadas. O performance_budget por página (wall_ms: 1500, peak_mb: 64) define o teto operacional. O benchmark do pipeline de renderização de HTML protege contra regressões (trabalho integrado, PR #564).
Notas de segurança
Seção intitulada “Notas de segurança”O resolvedor considera apenas o CSS que DefaultHtmlSecurityPolicy::isCssPropertyAllowed() admite. A lista de permissões define o teto de segurança, e a tabela de suporte em tempo de execução define um teto de capacidade separado. Uma propriedade bloqueada pela política nunca chega à cascata. Consulte o modelo de segurança do módulo HTML.
Conformidade
Seção intitulada “Conformidade”| Comportamento | Especificação | Cláusula | reference_id |
|---|---|---|---|
| Ordenação da cascata: origin/importance → especificidade → ordem de aparição | W3C CSS Cascading and Inheritance Level 5 | §6.4 (css_cascade_5#x1.x7.x1.p21) | |
| Especificidade como uma tripla (A,B,C) a partir das contagens de ID/classe/tipo | W3C Selectors Level 4 | §16 (selectors_4#x1.x16.p2) | |
| Análise determinística e recuperação de erros de análise | W3C CSS Syntax Level 3 | §4 (css_syntax_3#x1.x4.p2) |
O material do W3C é CC-BY 4.0. As afirmações acima são paráfrases. Os identificadores de cláusula e de chunk são fornecidos para verificação. O NextPDF não reivindica conformidade total com esses módulos; consulte a matriz de suporte a CSS para obter o status verificado por módulo.
Contexto comercial
Seção intitulada “Contexto comercial”Recurso Enterprise. O Premium amplia o conjunto de propriedades correspondidas e aplicadas. O algoritmo de cascata e o modelo de duas passagens de
!importantsão idênticos entre as edições. Consulte a matriz de suporte a CSS.