Pular para o conteúdo

Resolvedor de CSS: cascata e especificidade

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.

Terminal window
composer require nextpdf/core:^3

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.

SímboloLocalizaçãoFunção
CssResolver::parseStyleBlock(string $css, bool $nestingEnabled = false): voidsrc/Html/CssResolver.phpAnalisa um bloco <style> em regras.
CssResolver::resolveMatchingProperties(...)src/Html/CssResolver.phpFaz a correspondência de seletores e resolve a cascata em duas passagens.
CssResolver::resolveHasSelectors(array $tokens): arraysrc/Html/CssResolver.phpPré-varredura limitada de :has() (controlada por gate).
CssResolver::resolveFirstLetterProperties(...)src/Html/CssResolver.phpResolve as propriedades de ::first-letter.
CssResolver::resolvePseudoElementProperties(...)src/Html/CssResolver.phpResolve as propriedades de ::before / ::after.
CssResolver::getLayerRegistry(): LayerRegistrysrc/Html/CssResolver.phpCamadas de cascata declaradas.

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

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');
  • !important ignora a especificidade. A Passagem 2 aplica as declarações !important por especificidade, e essas declarações sempre sobrescrevem as declarações normais.
  • Camadas de cascata + !important entre 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 experimental css.has está 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.

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).

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.

ComportamentoEspecificaçãoCláusulareference_id
Ordenação da cascata: origin/importance → especificidade → ordem de apariçãoW3C 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/tipoW3C Selectors Level 4§16 (selectors_4#x1.x16.p2)
Análise determinística e recuperação de erros de análiseW3C 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.

Recurso Enterprise. O Premium amplia o conjunto de propriedades correspondidas e aplicadas. O algoritmo de cascata e o modelo de duas passagens de !important são idênticos entre as edições. Consulte a matriz de suporte a CSS.