長い HTML を複数ページに分割する
自動改ページを使い、長いコンテンツを複数ページに流し込みます。アウトラインを追加して、読者がセクション間を移動できるようにします。このレシピは examples/12-bookmarks-and-toc.php に基づいています。
インストール
「インストール」という見出しのセクションcomposer require nextpdf/core:^3このバージョン制約は nextpdf/core パッケージに適用されます。このサンプルは PHP 8.4 で動作します。
概念の概要
「概念の概要」という見出しのセクションsetAutoPageBreak(true, $margin) は、コンテンツが下マージンのしきい値を超えてあふれるたびに新しいページを開始するよう、エンジンに指示します。エンジンは、multiCell() または writeHtml() で書き込まれた長いテキストを、その境界で分割します。CSS Fragmentation モジュール(css_break_3)はサポートマトリックスで Verified と評価されており、HTML パイプラインの改ページ動作を支えています。
bookmark($title, $level) は、現在位置を指すアウトライン項目を追加します。PDF のアウトライン項目は宛先(destination)を関連付けるため、ユーザーはページに直接ジャンプできます(ISO 32000-2)。エンジンはその宛先を、項目の Dest エントリとして書き込みます(ISO 32000-2)。level 引数は、リーダーのサイドバーで項目を階層的な目次にネストします。
パイプラインはシングルパスを維持します(ADR-001)。ページ分割は、保持されたレイアウトツリーによってではなく、ストリームの出力時に決定されます。
API サーフェス
「API サーフェス」という見出しのセクションsetAutoPageBreak(bool $enabled, float $margin = 20): static—NextPDF\Core\Concerns\HasPages。bookmark(string $title, int $level = 0, float $y = -1): static—NextPDF\Core\Concerns\HasNavigation。multiCell(...)/writeHtml(string $html): static—NextPDF\Core\Concerns\HasTextOutput。
完全な PHPDoc テーブルはソースから生成されます。
コードサンプル — クイックスタート
「コードサンプル — クイックスタート」という見出しのセクション<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->setAutoPageBreak(true, margin: 25);$doc->addPage();$doc->bookmark('Section 1', level: 0);$doc->setFont('helvetica', '', 11);
for ($i = 1; $i <= 80; $i++) { $doc->multiCell(0, 7, "Paragraph {$i} of a long flowing document.");}
$doc->save(__DIR__ . '/out.pdf');コードサンプル — 本番
「コードサンプル — 本番」という見出しのセクションこのサンプルは自己完結しており、ハーネスで実行できます。ネストされたアウトラインと自動改ページを備えた複数章のドキュメントを構築し、examples/12-bookmarks-and-toc.php の内容を反映しています。
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->setTitle('Bookmarks and Navigation');$doc->setPrintHeader(false);$doc->setPrintFooter(false);$doc->setAutoPageBreak(true, margin: 25);
$chapters = [ 'Chapter 1: Introduction' => ['What is NextPDF?', 'Key Features'], 'Chapter 2: Getting Started' => ['Installation', 'Your First PDF'], 'Chapter 3: Advanced Topics' => ['Worker-safe Architecture', 'Streaming Output'],];
$body = 'NextPDF is a modern PDF 2.0 library for PHP. This paragraph is ' . 'repeated so the content overflows the page and the engine inserts ' . 'an automatic page break at the bottom-margin threshold.';
foreach ($chapters as $chapter => $sections) { $doc->addPage(); $doc->bookmark($chapter, level: 0); $doc->setFont('helvetica', 'B', 18); $doc->cell(0, 12, $chapter, newLine: true); $doc->ln(3);
foreach ($sections as $section) { $doc->bookmark($section, level: 1); $doc->setFont('helvetica', 'B', 14); $doc->cell(0, 10, $section, newLine: true); $doc->setFont('helvetica', '', 11); for ($i = 0; $i < 12; $i++) { $doc->multiCell(0, 7, $body); } $doc->ln(4); }}
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');$doc->save($out !== false ? $out : __DIR__ . '/paginate-long-html.pdf');
echo "Wrote paginate-long-html.pdf\n";想定される STDOUT:
Wrote paginate-long-html.pdfエッジケースと注意点
「エッジケースと注意点」という見出しのセクション- 無効化したままにする。 自動改ページが無効になっていると、エンジンは下マージンを超えたコンテンツを流し込まず、切り取ります。長いコンテンツを出力する前に再度有効化してください。
- 分割できないコンテンツ。 使用可能なページの高さを超える単一のブロックは
UnsplittableContentExceptionを発生させることがあります。非常に高いテーブル行や大きな画像が原因の 1 つです。元のコンテンツを分割してください。 - コンテンツの前にブックマークを設定する。 宛先にしたい位置で
bookmark()を呼び出してください。目的のページで、次に書き込む見出しの直前に配置してください。 - ヘッダーとフッターは領域を確保する。 印刷ヘッダーやフッターにより使用可能なコンテンツの高さが減り、改ページのしきい値はその分を考慮します。このサンプルのように両方を無効化すると、本文の全高を使用できます。
- アウトラインのネスト。
levelはネストの深さです。level: 1の子は、level: 0の親に続く必要があります。そうでない場合、リーダーはアウトラインツリーをフラットにします。
パフォーマンス
「パフォーマンス」という見出しのセクションエンジンは、単一の出力パス内でページ分割を決定します。コストはコンテンツの長さに対して線形で、O(n) です。バジェットは wall_ms: 2000, peak_mb: 96 です。ウォール時間は、複数ページの xref とアウトラインの組み立てのため、単一ページのレシピよりわずかに長くなります。ストリーミングモデルによりメモリ使用量は一定の範囲に保たれ、アウトラインは小さなフラットリストとして扱われます。
CSS サポートマトリックスの抜粋(Verified の行のみ)
「CSS サポートマトリックスの抜粋(Verified の行のみ)」という見出しのセクション以下では、真偽が監査された CSS サポートマトリックス から Verified の行のみを再掲します。
| W3C モジュール | レベル | ステータス | 証跡 |
|---|---|---|---|
CSS Fragmentation(css_break_3) | 3 | Verified | src/Html/Fragmentation/, tests/Unit/Html/PagedMedia/ |
CSS Table(css_tables_3) | 3 | Verified | src/Html/Table/ + ゴールデン PDF |
CSS Cascading and Inheritance(css_cascade_3) | 3 | Verified | src/Html/Cascade/ |
@page の名前付きページセレクターは CSS Paged Media の一部です。これに依存する前に、そのモジュールの現在の評価をマトリックスで確認してください。
シングルパスストリーミングの制約(ADR-001)
「シングルパスストリーミングの制約(ADR-001)」という見出しのセクションエンジンは、ストリームの進行に合わせて改ページを出力します。再フローのために保持されるツリーは存在しないため、改ページの決定は一度行われると確定します。一部のコンテンツでは、相互参照などのように、レイアウト後に最終的なページ番号が必要になります。そのようなコンテンツには制約があるため、その制限を念頭に置いて作成してください。
レイヤー契約(ADR-010)
「レイヤー契約(ADR-010)」という見出しのセクションページ分割は、パーサーではなく改ページコントローラーの責務です。パーサーは生のページ遷移演算子を出力せず、コントローラー契約を通じて改ページを要求します。
大きなドキュメントのメモリバジェット
「大きなドキュメントのメモリバジェット」という見出しのセクションストリーミングモデルでは、すべてのページを一度に保持せず、現在のページバッファとフラットなアウトラインリストだけを保持します。エンジンは完成したページをフラッシュするため、非常に長いドキュメントでも ADR-020 の上限内に収まります。テーブルと flex コンテナは、引き続き 1 コンテキストあたり 5,000 ノードの制限に従います。
セキュリティに関する注意
「セキュリティに関する注意」という見出しのセクション悪意のあるドキュメントであっても、メモリを無制限に消費させることはできません。要素とネストの上限(ADR-001)、およびコンテキストごとのノードバジェット(ADR-020)が処理量を制限します。ユーザー提供の長いコンテンツについては、長さと構造を検証してください。エンジンは、攻撃者が制御するアウトラインのタイトルをテキストとしてレンダリングし、決して解釈しません。
| 記述 | 仕様 | 箇条 | リファレンス ID |
|---|---|---|---|
| 各アウトライン項目は宛先を関連付けることができるため、ユーザーはその項目に直接ジャンプできます。 | ISO 32000-2 | iso32000_2_sec12#x1.x5.p4 | |
| アウトライン項目の Dest エントリは、その項目がアクティブ化されたときに表示される宛先を指定します。 | ISO 32000-2 | iso32000_2_sec12#x1.x11.p30 |
このレシピは、NextPDF が長いコンテンツを流し込み、アウトラインを構築する方法を示します。CSS Fragmentation はサポートマトリックスで Verified と評価されています。
商用コンテキスト
「商用コンテキスト」という見出しのセクション該当なし。