HTML レンダリングパイプライン
writeHtml() は単一の前方パスで処理します。トークン化、@page とスタイルの resolve(解決)、レイアウト、PDF オペレーターの描画を順に実行します。要素ツリーはステージ間で保持されません。
インストール
「インストール」という見出しのセクションcomposer require nextpdf/core:^3概念の概要
「概念の概要」という見出しのセクションHTML レンダリングパイプラインは、単一の前方パスで HTML+CSS を PDF コンテンツストリームのオペレーターに変換します。保持されるドキュメントツリーは構築しません。以下のステージシーケンスは、HtmlParser::parse() が main 上に実装されている内容を反映しています。
ステージ 1 — サニタイズと正規化。 HtmlParser::parse() は 10 MB を超える入力を拒否し、制御文字を除去し、改行を正規化します。ソース HTML の改行正規化に従い、CRLF と単独の CR はどちらも LF になります。その後、すべてのインスタンスフィールドをリセットするため、前回の呼び出しから状態が引き継がれることはありません。
ステージ 2 — @page とスタイルブロックの抽出。 パーサーはまず <style> ブロックを抽出し、続いて検出した @page ルールを適用してページジオメトリを再構成します。ページサイズはそれ以降のすべてのレイアウト判断に影響するため、この処理はトークンが処理される前に行われます。
ステージ 3 — トークン化。 HtmlTokenizer::cleanHtml() は <pre> のコンテンツを保持しつつ空白を正規化します。続いて tokenize() がフラットな list<HtmlToken> を生成します。これはノードグラフではなく、トークンリストです。空白のみのテキストトークンは直ちに破棄されます。HtmlChildScanner::scan() はフラットなリストに対してインデックスマップ(子の数、タグの数、空かどうか)を構築するため、構造セレクターはツリーを必要としません。
ステージ 4 — オプションの :has() プリスキャン。 css.has 実験的機能が有効な場合、CssResolver::resolveHasSelectors() がトークンリストに対して境界付きのプリスキャンを 1 回実行し、関係セレクターを解決します。これは単一パスルールにおける、文書化された境界付きの例外です。
ステージ 5 — トークンの処理(スタイル、レイアウト、描画)。 HtmlParser::processTokens() はトークンリストを 1 回走査します。各要素についてカスケードを解決し(レイヤー 1 のアプリケーターが HtmlStyleState に書き込み)、ジオメトリを計算し(レイヤー 3 レイアウト)、PDF オペレーターを出力します(レイヤー 4 描画)。スタイルの継承には、プッシュとポップを行う HtmlStyleState スタックを使用します。カーソル(x、y、マージン、ストリームオフセット)は、HtmlBlockCursor のスナップショットを通じてハンドラー間で受け渡されます。
ステージ 6 — 結果の返却。 parse() は、出力されたコンテンツストリーム、終端カーソル位置、および使用されたフォントキーを保持する不変の HtmlRenderResult を返します。呼び出し元(writeHtml())は、カーソルをページ座標フレームに戻します。
この レイヤーコントラクト のページでは、ステージ 5 の内部で実行される 4 レイヤー分離について説明しています。ストリーミング制約 のページでは、ツリーを保持しない特性とその上限について説明しています。
API サーフェス
「API サーフェス」という見出しのセクション| シンボル | 場所 | ステージ |
|---|---|---|
Document::writeHtml(string $html): static | src/Core/Concerns/HasTextOutput.php | 公開エントリ |
HtmlParser::parse(string $html): HtmlRenderResult | src/Html/HtmlParser.php | すべてのステージを統括 |
HtmlTokenizer::cleanHtml() / tokenize() | src/Html/HtmlTokenizer.php | ステージ 3 |
HtmlChildScanner::scan() | src/Html/HtmlChildScanner.php | ステージ 3 のインデックスマップ |
CssResolver::resolveHasSelectors() | src/Html/CssResolver.php | ステージ 4(ゲート制御) |
HtmlRenderResult (stream, endX, endY, usedFontKeys) | src/Html/HtmlRenderResult.php | ステージ 6 |
コードサンプル — クイックスタート
「コードサンプル — クイックスタート」という見出しのセクションこのコードは 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() プリスキャンは、有効な場合に境界付きのトークンリストパスを 1 回追加します(ステージ 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 に文書化されています。このページでは、プロパティごとのサポート状況を再掲しません。
商用コンテキスト
「商用コンテキスト」という見出しのセクションEnterprise の機能。 Premium は、この同じパイプライン上で CSS のカバレッジを拡大します。6 ステージのシーケンスはエディション間で変わりません。CSS サポートマトリックス を参照してください。