콘텐츠로 이동

HTML을 PDF 페이지로 렌더링하기

이 레시피는 단일 메서드인 writeHtml()으로 HTML 및 CSS 조각을 PDF 페이지 콘텐츠로 변환합니다. 마크업을 전달하면 서식이 적용된 페이지를 렌더링합니다. 이 코드의 전체 실행 가능 버전은 examples/08-html-basic.php입니다. 아래 단계를 따르거나 예제를 직접 복사하세요.

NextPDF는 HTML을 한 번의 패스로 읽고 그 결과를 페이지로 바로 스트리밍합니다. 이는 단일 패스 스트리밍 파이프라인입니다. 이 레시피를 사용하기 위해 해당 모델을 반드시 이해할 필요는 없지만, 이 페이지 아래쪽에 나오는 몇 가지 규칙의 배경이 되므로 염두에 두는 것이 좋습니다.

Terminal window
composer require nextpdf/core:^3

이 명령은 nextpdf/core 패키지를 설치합니다. 이 페이지의 예제는 PHP 8.4에서 실행되며, 지원되는 런타임은 >=8.4 <9.0입니다.

writeHtml()은 HTML 문자열을 받아 현재 커서 위치에서 시작하여 현재 페이지에 그려 넣습니다. 내부 동작은 다음과 같이 진행됩니다. 먼저 엔진은 HTML을 한 번 스캔하여 토큰 목록으로 분해합니다(HtmlTokenizer). 그런 다음 해당 목록을 왼쪽에서 오른쪽으로 순회합니다(HtmlParser). 각 요소에 대해 일치하는 PDF 그리기 명령(콘텐츠 스트림 연산자)을 버퍼에 기록합니다. 엔진은 호출 사이에 요소 트리를 메모리에 구성하거나 보관하지 않습니다. 이는 의도적으로 설계된 단일 패스 스트리밍 모델이며, ADR-001에 기록되어 있습니다.

지원되는 각 블록 요소는 레이아웃 상자가 되고, 각 텍스트 런은 텍스트 표시 연산자가 됩니다. 인라인 style 속성과 <style> 블록의 스타일은 CSS 캐스케이드, 즉 여러 규칙이 적용될 때 어떤 스타일이 우선하는지 정하는 표준 규칙을 통해 해결됩니다. 텍스트 줄바꿈, 정렬, 간격은 CSS Text 모델을 따르며, 이 모델은 원본 텍스트가 서식 적용 및 줄바꿈된 텍스트로 변환되는 방식을 규정합니다(W3C CSS Text Level 3).

글꼴을 지정하지 않으면 본문 텍스트는 기본 서체를 사용합니다. 이 기본값은 표준 Type 1 글꼴로, ISO 32000-2에 명명된 14개 표준 글꼴 중 하나입니다. 기본값은 직접 글꼴을 등록하고 선택할 때, 또는 적합성 프로필이 NextPDF에 대체 글꼴 임베딩을 요구할 때에만 변경됩니다.

한 가지 전제를 미리 분명히 하겠습니다. NextPDF는 HTML과 CSS 전체가 아니라 그 하위 집합을 지원합니다. 이 레시피는 지원되는 하위 집합을 다루며, 전체 HTML 또는 전체 CSS 지원을 주장하지 않습니다. 모듈별 정확한 검증 상태는 CSS 지원 매트릭스를 참조하세요.

메서드 시그니처는 writeHtml(string $html): static입니다. 이 메서드는 NextPDF\Contracts\PdfDocumentInterface 인터페이스에 선언되어 있으며 NextPDF\Core\Concerns\HasTextOutput에 구현되어 있습니다. 현재 페이지에 렌더링하고, 아직 페이지가 없으면 하나를 생성합니다. 이 메서드의 전체 PHPDoc 표는 소스 코드에서 생성됩니다.

<?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>HTML Rendering in NextPDF</h1><p>Rendered with <strong>writeHtml()</strong>.</p>');
$doc->save(__DIR__ . '/out.pdf');

다음은 완전한 자체 완결 예제이며, 테스트 하니스가 실제로 실행하는 예제입니다. 이는 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();
$html = <<<'HTML'
<h1 style="color: #1E3A8A;">HTML Rendering in NextPDF</h1>
<p>NextPDF renders <strong>HTML content</strong> directly into PDF pages.
This is the recommended approach for <em>mixed formatting</em>.</p>
<h2>Supported elements</h2>
<ul>
<li>Headings (h1-h6)</li>
<li>Paragraphs with <strong>bold</strong> and <em>italic</em></li>
<li>Ordered and unordered lists</li>
<li>Tables with borders and alignment</li>
<li>Inline styles (color, font-size, margin)</li>
</ul>
<h2>Ordered list</h2>
<ol>
<li>Create a Document instance</li>
<li>Add pages and content</li>
<li>Call save() or output()</li>
</ol>
HTML;
$doc->writeHtml($html);
// The harness sets NEXTPDF_COOKBOOK_OUTPUT and runs this script twice.
// Honour it: do not hard-code a path, do not echo the PDF to STDOUT.
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');
$doc->save($out !== false ? $out : __DIR__ . '/render-html-to-pdf.pdf');
echo "Wrote render-html-to-pdf.pdf\n";

예상되는 STDOUT:

Wrote render-html-to-pdf.pdf
  • 커서 인계. writeHtml()은 커서를 렌더링된 콘텐츠의 끝으로 이동합니다. 이후의 cell() 또는 두 번째 writeHtml()은 페이지 상단이 아니라 그 지점부터 이어집니다.
  • 아직 페이지가 없는 경우. 페이지가 존재하지 않으면 writeHtml()은 렌더링 전에 하나를 추가합니다. 특정 페이지 크기를 먼저 설정해야 하는 경우에는 addPage()를 명시적으로 호출하세요.
  • 요소 및 중첩 한도. 스트리밍 엔진은 50,000개 요소 및 100 단계 중첩 한도를 적용합니다(ADR-001). 이를 초과하는 문서는 자동으로 잘리지 않고 거부됩니다.
  • 지원되지 않는 마크업. 지원되는 하위 집합을 벗어난 요소와 속성은 무시되거나 폴백 처리되며, 예외는 발생하지 않습니다. 속성에 의존하기 전에 CSS 지원 매트릭스에서 지원 범위를 확인하세요.
  • 외부 리소스. 원격 이미지와 스타일시트는 외부 리소스 정책에 따라 관리되며, 기본 정책은 임의의 원격 URL을 가져오지 않습니다.

토큰화와 렌더링이 입력에 대한 단일 패스로 이루어지기 때문에 비용은 토큰 수에 비례해 선형적으로 증가합니다. 즉, O(n)입니다. 이 레시피의 기본 예산은 wall_ms: 1500, peak_mb: 96입니다. 엔진은 출력을 스트리밍하고 DOM을 메모리에 유지하지 않으므로, 최대 메모리는 전체 문서 크기가 아니라 콘텐츠 스트림 버퍼와 활성 스타일 스택의 크기에 따라 결정됩니다.

CSS 지원 매트릭스 발췌(검증됨 행만)

섹션 제목: “CSS 지원 매트릭스 발췌(검증됨 행만)”

여기에는 진실성이 감사된 CSS 지원 매트릭스에서 검증됨 등급을 받은 행만 표시합니다. “검증됨”은 src/Html/ 구현과, 구조 프로필에서 결정적으로 통과하는 실제 전용 픽스처 스위트를 의미합니다.

W3C 모듈레벨상태증거
CSS Flexible Box Layout (css_flexbox_1) 모듈1검증됨src/Html/Flex/, tests/Unit/Html/Flex/
CSS Grid Layout (css_grid_1) 모듈1검증됨src/Html/Grid/, WPT 코퍼스
CSS Cascading and Inheritance (css_cascade_3) 모듈3검증됨src/Html/Cascade/, tests/Unit/Html/Cascade/
CSS Table (css_tables_3) 모듈3검증됨src/Html/Table/, 테이블 픽스처 + 골든 PDF
CSS Fonts (css_fonts_4) 모듈4검증됨src/Html/FontFace/, tests/Unit/Html/FontFace/

예를 들어 text-align, text-indent, color와 같은 속성은 매트릭스에서 “주장됨”으로 등급이 매겨지므로(구현되었으나 전용 모듈 픽스처 없음), 여기에서는 의도적으로 “검증됨”으로 나열하지 않습니다.

단일 패스 스트리밍 제약(ADR-001)

섹션 제목: “단일 패스 스트리밍 제약(ADR-001)”

HTML 엔진은 DOM을 유지하지 않습니다. 상태는 스칼라 커서와 push/pop 스타일 스택으로 구성되며, 공백만 있는 텍스트 노드는 토큰화 시 폐기됩니다. 그 결과 중 하나로, 뒤에 오는 요소가 앞선 요소의 스타일을 다시 지정할 수 없으며, 전체 트리 컨텍스트가 필요한 선택자(예: 복잡한 :has() 경우)는 ADR-006에 따라 제약됩니다. 문서 순서에만 의존하는 레이아웃으로 설계하세요.

파싱, 레이아웃, 페인트는 별도의 레이어입니다. 파서는 원시 페인트 연산자를 내보내지 않으며, 레이아웃 디스패치는 CSS를 파싱하지 않습니다. 이러한 경계를 넘으면 ADR-010이 금지하는 결합 부채가 됩니다. 레시피 작성자 입장에서는 공개 진입점이 writeHtml()이라는 뜻입니다. 파서 내부에 직접 접근하지 마세요.

대용량 문서를 위한 메모리 예산

섹션 제목: “대용량 문서를 위한 메모리 예산”

ADR-020에 따라 컨테이너 범위의 서식 컨텍스트(flex, table)는 임시 하위 트리를 구성할 수 있으며, 이는 컨텍스트당 5,000개 노드와 20 단계 깊이로 제한되고, 활성 컨텍스트 전체에는 50MB 활성 메모리 상한과 10 단계 중첩이 적용됩니다. 이러한 컨텍스트 외부에서는 스트리밍 모델이 트리를 유지하지 않습니다. 예측 가능한 메모리를 위해 개별 테이블과 flex 컨테이너를 노드 한도 내로 유지하세요.

HTML 입력을 신뢰할 수 없는 입력으로 취급하세요. NextPDF는 스크립트를 실행하지 않으며 기본 외부 리소스 정책은 임의의 원격 URL을 가져오지 않으므로, 엔진 자체는 보수적입니다. 그렇더라도 사용자 입력으로 조합한 모든 HTML은 렌더링하기 전에 검증하거나 정제하세요. 요소 및 중첩 한도도 사용자를 보호합니다. 이는 악의적이거나 잘못된 형식의 문서가 요구할 수 있는 작업량을 제한합니다.

설명사양reference_id (참조 ID)
CSS Text는 원본 텍스트를 서식 적용 및 줄바꿈된 텍스트로 변환하는 것을 제어합니다.W3C CSS Text Level 3css_text_3#x1.x2.p4
기본 본문 서체는 표준 Type 1 글꼴로 결정됩니다.ISO 32000-2iso32000_2_sec9#x1.x29

이 레시피는 NextPDF가 지원되는 HTML 및 CSS 하위 집합을 렌더링하는 방법을 보여 줍니다. 전체 HTML 또는 전체 CSS 지원을 주장하지 않으며, 모듈별 검증 상태는 CSS 지원 매트릭스에서 확인할 수 있습니다.

해당 없음.