콘텐츠로 이동

CSS 리졸버: 캐스케이드와 명시도

CssResolver는 토큰 스트림에서 선택자를 매칭하고, 매칭된 규칙을 캐스케이드 레이어, 명시도, 문서 순서에 따라 정렬한 다음, 두 번째 패스에서 !important를 적용합니다.

Terminal window
composer require nextpdf/core:^3

CssResolver는 Layer 1 구성 요소입니다(ADR-010 참조). 이 구성 요소는 파싱된 CSS 규칙을 소유하고, 각 요소에 어떤 선언이 적용되는지 결정합니다. 구조적 명료성을 위해 HtmlParser에서 분리되었으며, 공개 API가 아닌 내부 클래스입니다.

리졸버는 문서 트리 없이 동작합니다. 선택자 매칭은 평탄한 토큰 스트림을 읽고, HtmlChildScanner파이프라인 스테이지 3에서 구축하는 인덱스 맵도 활용합니다. 이 맵에는 자식 개수, 동일 태그 개수, 비어 있음 여부가 포함됩니다. 구조적 의사 클래스는 이러한 맵에서 처리됩니다. 관계형 :has() 선택자는 스트리밍 제약에 설명된 제한적 사전 스캔에 의존합니다.

캐스케이드 해석은 CssResolver::resolveMatchingProperties() 내부에서 두 번의 패스로 실행됩니다. 패스 1은 일반 선언을 캐스케이드 순서대로 적용합니다. 이 순서는 먼저 캐스케이드 레이어 가중치, 그다음 명시도, 그다음 문서 순서입니다. 이어서 패스 2는 !important 선언을 명시도 순서로 적용하며, !important 선언은 명시도와 관계없이 모든 일반 선언을 재정의합니다. 이러한 2 패스 분리는 구현 전략이며, 이를 통해 레이아웃 레이어가 이후 소비할 해석된 속성 집합을 생성합니다.

리졸버가 구현하는 캐스케이드 순서는 W3C CSS Cascading and Inheritance 명세를 따릅니다. 선언은 먼저 출처와 중요도에 따라 정렬되고, 그다음 선택자 명시도에 따라 정렬됩니다. 명시도가 같으면 문서 순서상 마지막 선언이 우선합니다(CSS Cascade 5 §6.4; 적합성 참조). CssResolver의 소스 내 주석도 같은 절을 인용하므로, 이 동작에 대해 명세와 용어집에 더해 세 번째 증거 경로를 제공합니다.

명시도는 ID, 클래스, 타입 구성 요소의 개수로부터 (A, B, C) 3 요소로 계산되며, 이 3 요소는 구성 요소별로 비교됩니다(Selectors Level 4 §16). NextPDF는 캐스케이드 정렬 전에 매칭된 규칙별로 명시도를 계산합니다.

한 가지 제약은 명확히 짚고 넘어갈 필요가 있습니다. §6.4.3 레이어 반전 규칙은 캐스케이드 레이어 전반의 !important 선언에 적용되며, 소스에서는 이를 캐스케이드 레이어 작업 클러스터의 미해결 사항으로 기록하고 있습니다. 캐스케이드 레이어가 선언되고 !important가 여러 레이어에 걸쳐 있는 경우, 해석된 순서가 전체 명세 동작과 다를 수 있습니다. CSS 지원 매트릭스가 기능별 지원 상태에 대한 권위 있는 출처이며, 이 페이지에서는 속성별 지원을 다시 설명하지 않습니다.

심볼위치역할
CssResolver::parseStyleBlock(string $css, bool $nestingEnabled = false): voidsrc/Html/CssResolver.php주어진 <style> 블록을 규칙으로 파싱합니다.
CssResolver::resolveMatchingProperties(...)src/Html/CssResolver.php선택자를 매칭하고 2 패스 캐스케이드를 해석합니다.
CssResolver::resolveHasSelectors(array $tokens): arraysrc/Html/CssResolver.php제한적 :has() 사전 스캔(게이팅됨).
CssResolver::resolveFirstLetterProperties(...)src/Html/CssResolver.php지정된 ::first-letter 속성을 해석합니다.
CssResolver::resolvePseudoElementProperties(...)src/Html/CssResolver.php지정된 ::before / ::after 속성을 해석합니다.
CssResolver::getLayerRegistry(): LayerRegistrysrc/Html/CssResolver.php선언된 캐스케이드 레이어입니다.

호출자는 리졸버를 직접 호출하지 않습니다. 호출자는 CSS를 작성하고, 리졸버는 writeHtml() 내부에서 실행됩니다. 아래 캐스케이드는 클래스 규칙이 타입 규칙보다 명시도가 높기 때문에 p를 빨간색으로 해석합니다.

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

다음은 !important 두 번째 패스를 보여줍니다. 클래스 선택자의 명시도가 더 높더라도, 인라인에 해당하는 클래스 선언은 !important 타입 선언에 의해 재정의됩니다.

<?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는 명시도를 무시합니다. 패스 2는 !important 선언을 명시도 순서로 적용하며, 이 선언들은 항상 일반 선언을 재정의합니다.
  • 캐스케이드 레이어 + 레이어 전반의 !important. important 선언에 대한 §6.4.3 레이어 반전 규칙은 소스에서 미해결 사항으로 기록되어 있습니다. 이에 의존하기 전에 CSS 지원 매트릭스와 대조하여 동작을 검증하십시오.
  • 레이어가 선언되지 않은 경우가 빠른 경로입니다. @layer가 없으면 순서는 명시도만으로 단순화되며, 레이어 도입 이전 동작과 비트 단위로 동일합니다.
  • :has()는 게이팅됩니다. 관계형 사전 스캔은 css.has 실험적 기능이 활성화된 경우에만 실행됩니다.
  • 선택자 매칭은 스트림 기반입니다. 구조적 선택자는 트리 순회가 아니라 인덱스 맵을 사용합니다. 인덱스 맵을 넘어서는 임의의 트리 탐색이 필요한 선택자는 이 모델에서 해석할 수 없습니다.

선택자 매칭은 최악의 경우 O(규칙 수 × 요소 수)이며, 스트리밍 상한으로 제한됩니다. 두 번의 캐스케이드 정렬은 요소당 O(매칭된 규칙 수 · log 매칭된 규칙 수)입니다. 레이어가 없는 경로는 레이어 해석을 완전히 건너뜁니다. 페이지별 performance_budget(wall_ms: 1500, peak_mb: 64)이 운영 상한입니다. 회귀는 HTML 렌더 파이프라인 벤치마크로 방지합니다(병합된 작업, PR #564).

리졸버는 DefaultHtmlSecurityPolicy::isCssPropertyAllowed()가 허용하는 CSS만 봅니다. 허용 목록은 보안 상한이고, 런타임 지원 테이블은 별도의 기능 상한입니다. 정책에 의해 차단된 속성은 캐스케이드에 절대 도달하지 않습니다. HTML 모듈 보안 모델을 참조하십시오.

동작명세참조 ID
캐스케이드 정렬: origin/importance → 명시도 → 등장 순서W3C CSS Cascading and Inheritance Level 5 명세§6.4 (css_cascade_5#x1.x7.x1.p21)
ID/클래스/타입 개수로부터 (A,B,C) 3 요소로 표현되는 명시도W3C Selectors Level 4 명세§16 (selectors_4#x1.x16.p2)
결정적 파싱 및 파싱 오류 복구W3C CSS Syntax Level 3 명세§4 (css_syntax_3#x1.x4.p2)

W3C 자료는 CC-BY 4.0입니다. 위의 주장은 의역된 것입니다. 절 및 청크 식별자는 검증을 위해 제공됩니다. NextPDF는 이러한 모듈에 대한 완전한 적합성을 주장하지 않습니다. 검증된 모듈별 상태는 CSS 지원 매트릭스를 참조하십시오.

Enterprise 기능. Premium은 매칭되고 적용되는 속성 집합을 확장합니다. 캐스케이드 알고리즘과 2 패스 !important 모델은 모든 에디션에서 동일합니다. CSS 지원 매트릭스를 참조하십시오.