콘텐츠로 이동

HTML 엔진 레이어 계약(ADR-010)

HTML 서브시스템은 CSS 파싱, 스타일 상태, 레이아웃, 페인트를 네 개의 레이어로 분리하며, 레이어 간 계약은 한 방향으로 진행됩니다. ADR-010 은 이러한 경계와 확장 규칙을 정의합니다.

Terminal window
composer require nextpdf/core:^3

ADR-010(“Engine Layer Contracts, Hot Path Ownership, and Extension Rules”, 2026-04-12 승인)은 HTML 서브시스템의 계층 구조를 공식화합니다. 핵심 렌더링 계약은 네 개의 레이어, 즉 CSS 파싱 및 애플리케이터, 스타일 상태, 레이아웃 및 포매팅, 페인트로 구성됩니다. ADR-010 은 또한 4 계층 핵심을 감싸지만 그 데이터 흐름은 변경하지 않는 두 개의 보조 레이어, 즉 페이지 미디어와 측정 하네스도 문서화합니다. 이 핵심을 가리키는 표준 용어집 용어는 “HTML 파이프라인”이며, 이는 4 계층 파이프라인입니다.

데이터는 한 방향으로 흐릅니다. CSS 텍스트는 레이어 1 에서 타입이 지정된 값으로 변환됩니다. 레이어 1 은 그 값들을 레이어 2 의 HtmlStyleState 필드에 기록합니다. 레이어 3 은 스타일 상태 필드를 읽고 지오메트리를 계산합니다. 레이어 4 는 불변 ComputedStyle 스냅샷과 지오메트리를 읽어 PDF 연산자를 생성합니다. 어떤 레이어도 자신보다 뒤에 있는 레이어에서 값을 읽지 않습니다.

4 계층 분리는 문서화에 그치지 않습니다. ADR-010 은 코드를 올바른 레이어로 이동시키기 위해 v1.2.0 에서 적용된 제한적인 리팩터링 두 건을 기록합니다. PageBorderPainterHtmlParser에서 추출되었으며, 그 결과 페인트 연산자는 더 이상 오케스트레이터 안에 존재하지 않습니다. HtmlStyleState 클래스 docblock 은 이제 각 레이어가 어떤 필드를 기록하거나 읽을 수 있는지 명시하는 공식 레이어 계약을 담고 있습니다.

한 경계는 숨기지 않고 명확히 드러냅니다. FormattingContextFactory::startTable()는 여전히 다섯 개의 원시 CSS 키를 직접 읽습니다. ADR-010 은 이를 의도된 계약이 아니라, 향후 TableApplicator를 위해 알려진 상태로 보류된 기술 부채로 기록합니다. 예외를 문서화하는 것도 계약의 일부입니다.

레이어파일 (대표적)기록읽기해서는 안 되는 것
1 — CSS 파싱 및 애플리케이터CssValueParser, CssResolver, HtmlCssApplicator, src/Html/Applicator/*HtmlStyleState CSS 필드원시 CSS 텍스트지오메트리 계산; 연산자 생성
2 — 스타일 상태HtmlStyleState, State/ComputedStyle, State/LayoutState— (수동적 값 백)CSS 파싱; 레이아웃 결정; 연산자 생성
3 — 레이아웃 및 포매팅FormattingContextFactory, HtmlBlockHandler, FlexLayoutEngine, TableParser, FloatContext커서 지오메트리HtmlStyleState 필드원시 $css[...] 읽기; 페인트 연산자 생성
4 — 페인트 및 렌더링BorderRenderer, BackgroundImageRenderer, src/Html/Paint/*, src/Html/Gradient/*PDF 연산자 스트림ComputedStyle (불변) + 지오메트리지오메트리 계산; CSS 파싱; 페이지 나누기 결정
레이어파일 (대표적)역할
5 — 페이지 미디어PageBreakController, PageBorderPainter, PageRule, PageRuleParser, ParserConfigurator해당 @page 규칙 해석; 나누기 및 orphan/widow 제약 조건 평가; 페이지 장식을 페인트에 위임합니다.
6 — 측정 및 하네스WPT 분류기 스크립트, tests/Support/*테스트 결과 분류, 회귀 스냅샷 생성, 어서션 헬퍼 제공. 렌더링 로직을 담지 않습니다.

이 계약은 클래스 배치와 HtmlStyleState docblock 을 통해 강제됩니다. src/Html/와 대조해 검증하십시오.

심볼레이어계약 역할
PropertyApplicatorInterface1전략 인터페이스이며, CSS 계산 필드를 기록하는 유일한 지점입니다.
ParserConfigurator::buildCssApplicator()1 (와이어링)모든 애플리케이터를 등록합니다. 새 CSS 속성은 여기에 등록됩니다.
HtmlStyleState2이중 그룹 백이며, 클래스 docblock 이 필드별 소유 레이어를 명시합니다.
HtmlStyleState::toComputedStyle()2페인트 레이어를 위한 불변 ComputedStyle을 생성합니다.
FormattingContextFactory::dispatchOpenTag()3새 레이아웃 동작을 위한 단일 라우팅 지점입니다.
PageBorderPainter::buildStream()4페이지 장식이며, 레이어 5 에서 호출되고 HtmlParser에 인라인되지 않습니다.

호출자는 결코 레이어를 직접 다루지 않습니다. 4 계층 흐름은 단일 호출 내부에서 실행됩니다.

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

이 계약은 호출자보다 기여자에게 중요합니다. CSS 속성을 추가하려면 레이어 1 확장 지점을 따르십시오. 즉 애플리케이터를 생성하고, 레이어 docblock 이 있는 타입 지정 HtmlStyleState 필드를 추가한 뒤, 해당 애플리케이터를 ParserConfigurator에 등록합니다. 아래 예시는 애플리케이터 계약의 형태를 보여 줍니다. 복사할 구체적인 클래스는 src/Html/Applicator/를 참조하십시오.

<?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()는 원시 CSS 를 읽습니다. 이는 문서화된 유일한 계약 예외이며, 향후 TableApplicator로 보류되었습니다. 이 패턴을 복사하지 마십시오.
  • 여섯 개의 레이어, 4 계층 핵심. ADR-010 은 여섯 개의 레이어에 번호를 부여합니다. 데이터 흐름 계약은 4 계층 핵심이며, 페이지 미디어와 측정은 보조입니다.
  • HtmlStyleState는 이중 그룹입니다. 이는 CSS 계산 필드와 레이아웃 추적 필드를 담습니다. CSS 그룹에는 애플리케이터만 기록합니다. 페인트는 ComputedStyle을 읽으며, 레이아웃 추적 필드는 결코 읽지 않습니다.
  • HtmlParser에는 레이어가 없습니다. 이는 오케스트레이터입니다. CSS 파싱, 지오메트리 연산, 페인트 생성은 그 안에 존재해서는 안 됩니다.

레이어 계약은 구조적인 것이며 런타임 비용을 추가하지 않습니다. HtmlStyleState::toComputedStyle()은 페인트가 필요한 요소마다 하나의 불변 스냅샷을 생성합니다. 이 스냅샷 덕분에 페인트 코드는 가변 상태 백을 읽을 필요가 없습니다. 렌더링 비용은 계층화가 아니라 스트리밍 모델에 의해 좌우됩니다. 페이지별 performance_budget(wall_ms: 1500, peak_mb: 64)이 운영상 상한입니다.

레이어 분리는 보안 모델을 뒷받침합니다. 레이어 1 은 레이아웃 또는 페인트 코드가 CSS 값을 보기 전에 그 값을 파싱하고 정책에 따라 필터링하므로, DefaultHtmlSecurityPolicy::isCssPropertyAllowed()가 단일 게이트입니다. 페인트는 공격자가 제어하는 원시 CSS 를 결코 읽지 않습니다. HTML 모듈 보안 모델을 참조하십시오.

이 페이지는 외부 표준을 인용하지 않습니다. 레이어 경계는 ADR-010 과, 소스에 계약을 인코딩한 HtmlStyleState 클래스 docblock 에서 비롯됩니다. CSS 동작 적합성은 css-resolver에 문서화되어 있습니다.

Enterprise 기능. Premium CSS 기능은 문서화된 확장 지점을 통해 동일한 이 네 개의 레이어를 확장합니다. 별도의 Premium 파이프라인은 없습니다. CSS 지원 매트릭스를 참조하십시오.