HTML 엔진 레이어 계약(ADR-010)
한눈에 보기
섹션 제목: “한눈에 보기”HTML 서브시스템은 CSS 파싱, 스타일 상태, 레이아웃, 페인트를 네 개의 레이어로 분리하며, 레이어 간 계약은 한 방향으로 진행됩니다. ADR-010 은 이러한 경계와 확장 규칙을 정의합니다.
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 에서 적용된 제한적인 리팩터링 두 건을 기록합니다. PageBorderPainter는 HtmlParser에서 추출되었으며, 그 결과 페인트 연산자는 더 이상 오케스트레이터 안에 존재하지 않습니다. 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/* | 테스트 결과 분류, 회귀 스냅샷 생성, 어서션 헬퍼 제공. 렌더링 로직을 담지 않습니다. |
API 표면
섹션 제목: “API 표면”이 계약은 클래스 배치와 HtmlStyleState docblock 을 통해 강제됩니다. src/Html/와 대조해 검증하십시오.
| 심볼 | 레이어 | 계약 역할 |
|---|---|---|
PropertyApplicatorInterface | 1 | 전략 인터페이스이며, CSS 계산 필드를 기록하는 유일한 지점입니다. |
ParserConfigurator::buildCssApplicator() | 1 (와이어링) | 모든 애플리케이터를 등록합니다. 새 CSS 속성은 여기에 등록됩니다. |
HtmlStyleState | 2 | 이중 그룹 백이며, 클래스 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 지원 매트릭스를 참조하십시오.