コンテンツにスキップ

HTML を PDF ページにレンダリングする

このレシピでは、1 つのメソッド writeHtml() を使って、HTML と CSS のフラグメントを PDF ページのコンテンツに変換します。マークアップを渡すと、書式設定済みのページとしてレンダリングされます。このコードの完全な実行可能版は examples/08-html-basic.php です。以下の手順に従うか、サンプルをそのままコピーしてください。

NextPDF は HTML を 1 回のパスで読み取り、その結果を直接ページにストリーミングします。これはシングルパスのストリーミングパイプラインです。このレシピを使うためにそのモデルを理解する必要はありませんが、後述するいくつかのルールに関わるため、覚えておくとよいでしょう。

Terminal window
composer require nextpdf/core:^3

このコマンドは nextpdf/core パッケージをインストールします。このページのサンプルは PHP 8.4 で実行され、サポート対象のランタイムは >=8.4 <9.0 です。

writeHtml() は HTML 文字列を受け取り、現在のカーソル位置を起点に現在のページへ描画します。内部で何が起きているのかを、順を追って説明します。まず、エンジンは HTML を 1 回スキャンし、トークンのリストに分解します(HtmlTokenizer)。次に、そのリストを左から右へたどります(HtmlParser)。各要素について、対応する PDF の描画命令、すなわちコンテンツストリームの演算子をバッファに書き込みます。エンジンは、呼び出し間で要素ツリーをメモリ上に構築したり保持したりしません。これはシングルパスのストリーミングモデルという意図的な設計であり、ADR-001 に記録されています。

サポートされている各ブロック要素はレイアウトボックスになり、各テキストランはテキスト表示演算子になります。インラインの style 属性と <style> ブロックのスタイルは、CSS カスケード、すなわち複数のスタイルが適用される場合にどのスタイルが優先されるかを決定する標準ルールによって resolve(解決)されます。テキストの折り返し、配置、間隔は、ソーステキストが書式設定され、行で折り返されたテキストになる過程を定める CSS Text モデル(W3C CSS Text Level 3)に従います。

フォントを選択しない場合、本文テキストにはデフォルトの書体が使用されます。そのデフォルトは標準 Type 1 フォント、すなわち ISO 32000-2 で定められた 14 種類の標準フォントのうちの 1 つです。デフォルトが変わるのは、独自のフォントを登録して選択した場合、または適合性プロファイルにより NextPDF が代替フォントを埋め込む必要がある場合のみです。

最初に 1 つ前提を確認しておきます。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 を反映しています。出力パスをハードコードする代わりに、ハーネスが提供する場所へ書き込むため、再現性ハーネスはスクリプトを 2 回実行して結果を比較できます。

<?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() や 2 回目の writeHtml() は、ページの先頭からではなく、その位置から続行します。
  • ページがまだない場合。 ページが存在しない場合、writeHtml() はレンダリング前にページを 1 つ追加します。特定のページサイズを先に設定する必要がある場合は、addPage() を明示的に呼び出してください。
  • 要素数とネストの上限。 ストリーミングエンジンは 50,000 要素および 100 レベルのネスト上限を適用します(ADR-001)。これらを超えるドキュメントは、暗黙的に切り捨てられず、拒否されます。
  • サポート対象外のマークアップ。 サポートされているサブセットの範囲外の要素やプロパティは、無視されるかフォールバックの対象になり、例外は発生しません。あるプロパティに依存する前に、CSS サポートマトリックスでカバー範囲を確認してください。
  • 外部リソース。 リモートの画像やスタイルシートは外部リソースポリシーによって管理されます。デフォルトのポリシーでは、任意のリモート URL を取得しません。

トークン化とレンダリングは入力に対して 1 回のパスで行われるため、コストはトークン数に比例して、すなわち O(n) で増加します。このレシピのデフォルトのバジェットは wall_ms: 1500, peak_mb: 96 です。エンジンは出力をストリーミングし、DOM をメモリに保持しないため、ピークメモリはドキュメント全体のサイズではなく、コンテンツストリームバッファとアクティブなスタイルスタックのサイズに応じます。

ここで Verified と評価されているのは、真正性が監査された CSS サポートマトリックスにある行のみです。「Verified」とは、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-aligntext-indentcolor などのプロパティは、マトリックスで「Claimed」と評価されています(実装済みだが専用のモジュールフィクスチャはなし)。そのため、ここでは意図的に Verified として記載していません。

HTML エンジンは DOM を保持しません。状態はスカラーのカーソルと push/pop 方式のスタイルスタックであり、空白のみのテキストノードはトークン化時に破棄されます。その結果の 1 つとして、後続の要素が先行する要素を再スタイルすることはできず、ツリー全体のコンテキストを必要とするセレクター(たとえば複雑な :has() のケース)は ADR-006 に従って制約されます。ドキュメント順序のみに依存するレイアウトを計画してください。

解析、レイアウト、ペイントはそれぞれ別個のレイヤーです。パーサーは生のペイント演算子を出力せず、レイアウトのディスパッチは CSS を解析しません。これらの境界を越えることは、ADR-010 が禁じる結合の負債です。レシピの作成者にとって、これはパブリックなエントリーポイントが writeHtml() であることを意味します。パーサーの内部に直接触れないでください。

ADR-020 により、コンテナースコープの整形コンテキスト(flex、table)は一時的なサブツリーを構築する場合があり、コンテキストあたり 5,000 ノード、深さ 20 レベルに制限され、ライブなコンテキスト全体で 50 MB のアクティブメモリ上限とネスト 10 レベルが設けられています。これらのコンテキストの外では、ストリーミングモデルはツリーを保持しません。メモリを予測可能に保つため、個々のテーブルや flex コンテナーをノード上限の範囲内に収めてください。

HTML 入力は信頼できないものとして扱ってください。NextPDF はスクリプトを実行せず、デフォルトの外部リソースポリシーは任意のリモート URL を取得しないため、エンジン自体は保守的です。それでも、ユーザー入力から組み立てた HTML は、レンダリングする前に検証またはサニタイズしてください。要素数とネストの上限もユーザーを保護します。これらは、悪意のあるドキュメントや不正なドキュメントが要求しうる処理量を制限します。

記述仕様条項リファレンス 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 サポートマトリックスに記載されています。

該当なし。