콘텐츠로 이동

HTML 렌더링 파이프라인

writeHtml()은 단일 정방향 패스를 실행합니다. 이 과정에서 토큰화, @page와 스타일 해석, 레이아웃 배치, PDF 연산자 페인트를 순서대로 수행합니다. 단계 사이에 요소 트리는 유지하지 않습니다.

Terminal window
composer require nextpdf/core:^3

HTML 렌더링 파이프라인은 단일 정방향 패스에서 HTML+CSS를 PDF 콘텐츠 스트림 연산자로 변환합니다. 지속적으로 유지되는 문서 트리는 만들지 않습니다. 아래 단계 순서는 HtmlParser::parse()main에 구현된 실제 흐름을 그대로 반영합니다.

1 단계 — 정제 및 정규화. HtmlParser::parse()는 10 MB를 초과하는 입력을 거부하고, 제어 문자를 제거하며, 줄 끝을 정규화합니다. CRLF와 단독 CR은 모두 LF가 되며, 이는 소스가 따르는 HTML 줄 끝 정규화 규칙입니다. 그런 다음 이전 호출의 상태가 남지 않도록 모든 인스턴스 필드를 재설정합니다.

2 단계 — @page 및 스타일 블록 추출. 파서는 먼저 <style> 블록을 추출한 다음, 발견된 @page 규칙을 적용하여 페이지 형상을 재구성합니다. 이 작업은 토큰이 하나라도 처리되기 전에 일어나며, 페이지 크기가 이후의 모든 레이아웃 결정에 영향을 주기 때문입니다.

3 단계 — 토큰화. HtmlTokenizer::cleanHtml()<pre> 콘텐츠를 보존하면서 공백을 정규화합니다. 이어서 tokenize()가 평탄한 list<HtmlToken>을 생성합니다. 이는 토큰 목록이지 노드 그래프가 아닙니다. 공백만 포함한 텍스트 토큰은 즉시 폐기됩니다. HtmlChildScanner::scan()은 평탄한 목록을 기준으로 인덱스 맵(자식 수, 태그 수, 비어 있음)을 구성하므로 구조적 선택자에 트리가 필요하지 않습니다.

4 단계 — 선택적 :has() 사전 스캔. css.has 실험적 기능이 활성화되면, CssResolver::resolveHasSelectors()가 관계형 선택자를 해석하기 위해 토큰 목록을 대상으로 제한된 사전 스캔을 한 번 실행합니다. 이는 단일 패스 규칙에 대해 문서화된 제한적 예외입니다.

5 단계 — 토큰 처리(스타일, 레이아웃, 페인트). HtmlParser::processTokens()은 토큰 목록을 한 번 순회합니다. 각 요소마다 캐스케이드를 해석하고(Layer 1 적용기가 HtmlStyleState를 기록), 형상을 계산한 뒤(Layer 3 레이아웃), PDF 연산자를 방출합니다(Layer 4 페인트). 스타일 상속은 푸시 앤 팝 방식의 HtmlStyleState 스택을 사용합니다. 커서(x, y, 여백, 스트림 오프셋)는 HtmlBlockCursor 스냅숏을 통해 핸들러 간에 전달됩니다.

6 단계 — 결과 반환. parse()는 방출된 콘텐츠 스트림, 종료 커서 위치, 사용된 글꼴 키를 담은 불변 HtmlRenderResult를 반환합니다. 호출자(writeHtml())는 커서를 페이지 좌표계로 되돌립니다.

레이어 계약 페이지에서는 5 단계 내부에서 실행되는 4 계층 분리를 다룹니다. 스트리밍 제약 페이지에서는 트리를 유지하지 않는 특성과 그 상한을 다룹니다.

기호위치단계
Document::writeHtml(string $html): staticsrc/Core/Concerns/HasTextOutput.php공개 진입점
HtmlParser::parse(string $html): HtmlRenderResultsrc/Html/HtmlParser.php모든 단계 조율
HtmlTokenizer::cleanHtml() / tokenize()src/Html/HtmlTokenizer.php3 단계
HtmlChildScanner::scan()src/Html/HtmlChildScanner.php3 단계 인덱스 맵
CssResolver::resolveHasSelectors()src/Html/CssResolver.php4 단계(게이트됨)
HtmlRenderResult (stream, endX, endY, usedFontKeys)src/Html/HtmlRenderResult.php6 단계

examples/08-html-basic.php에서 가져온 예제입니다.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('HTML Basic');
$doc->addPage();
$doc->writeHtml('<h1 style="color:#1E3A8A;">HTML Rendering</h1><p>One pass.</p>');
$doc->save(__DIR__ . '/output/08-html-basic.pdf');

내장 <style> 블록으로 스타일이 적용된 보고서를 렌더링합니다. 파이프라인은 토큰을 하나라도 처리하기 전에 스타일 블록을 추출해 적용합니다.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
use NextPDF\Exception\HtmlParsingException;
function renderInvoice(string $bodyHtml, string $out): void
{
$doc = Document::createStandalone();
$doc->setTitle('Invoice');
$doc->addPage();
$html = '<style>@page { margin: 20mm; } '
. 'h1 { color: #1E3A8A; } '
. 'table { width: 100%; }</style>'
. $bodyHtml;
try {
$doc->writeHtml($html);
} catch (HtmlParsingException $e) {
// Sanitize/cap failures surface here. Do not retry.
throw $e;
}
$doc->save($out);
}
  • @page는 토큰보다 먼저 읽힙니다. 콘텐츠 뒤에 배치된 @page 규칙도 여전히 적용됩니다. 스타일 추출이 토큰화보다 앞서기 때문입니다. 페이지 형상은 5 단계 전에 고정됩니다.
  • <pre> 공백은 보존됩니다. cleanHtml()<pre> 콘텐츠를 보호하며, 나머지 공백은 축약됩니다.
  • :has()는 게이트됩니다. css.has 실험적 기능이 활성화되어 있지 않으면 4 단계는 실행되지 않으며 :has() 선택자는 일치하지 않습니다.
  • 하나의 스트림 버퍼. 파이프라인은 단일 문자열 버퍼에 기록합니다. 이미 기록된 콘텐츠는 결코 이동되지 않습니다. 재레이아웃은 없습니다.
  • 상한은 패스 중에 적용됩니다. 요소 및 중첩 상한은 5 단계 도중에 예외를 발생시키며, 그 전에는 발생시키지 않습니다. 문서는 처리 중에 실패할 수 있습니다.

파이프라인 순회는 O(토큰 수)입니다. 테이블 열 크기 조정은 테이블별로 제한된 행 스캔을 추가합니다(5 단계, TableParser). :has() 사전 스캔은 활성화된 경우 제한된 토큰 목록 패스 한 번을 추가합니다(4 단계). 스타일 스택의 메모리는 O(요소 수)가 아니라 O(중첩 깊이)입니다. 스트리밍 제약을 참조하세요. HTML 렌더 파이프라인 성능 벤치마크는 5% 게이트로 회귀를 방지합니다(병합된 작업, PR #564). 페이지별 performance_budget(wall_ms: 1500, peak_mb: 64)이 운영 상한입니다.

1 단계는 첫 보안 경계입니다. 10 MB 입력 상한, 제어 문자 제거, 줄 끝 정규화가 토큰화 전에 실행됩니다. 그런 다음 DefaultHtmlSecurityPolicy가 5 단계 동안 허용되는 태그, 속성, CSS 속성, URL 스킴을 게이트합니다. HTML 모듈 보안 모델을 참조하세요.

줄 끝 정규화는 HTML 표준의 줄 끝 처리를 따릅니다(CRLF와 단독 CR은 LF가 됩니다). 속성별 CSS 적합성은 CSS 지원 매트릭스에 문서화되어 있으며, 캐스케이드 동작은 css-resolver에 문서화되어 있습니다. 이 페이지에서는 속성별 지원을 다시 설명하지 않습니다.

엔터프라이즈 기능. Premium은 이와 동일한 파이프라인에서 CSS 적용 범위를 넓힙니다. 6 단계 순서는 에디션 간에 변경되지 않습니다. CSS 지원 매트릭스를 참조하세요.