コンテンツにスキップ

長い HTML を複数ページに分割する

自動改ページを使い、長いコンテンツを複数ページに流し込みます。アウトラインを追加して、読者がセクション間を移動できるようにします。このレシピは examples/12-bookmarks-and-toc.php に基づいています。

Terminal window
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)。ページ分割は、保持されたレイアウトツリーによってではなく、ストリームの出力時に決定されます。

  • setAutoPageBreak(bool $enabled, float $margin = 20): staticNextPDF\Core\Concerns\HasPages
  • bookmark(string $title, int $level = 0, float $y = -1): staticNextPDF\Core\Concerns\HasNavigation
  • multiCell(...) / writeHtml(string $html): staticNextPDF\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 の行のみを再掲します。

W3C モジュールレベルステータス証跡
CSS Fragmentation(css_break_33Verifiedsrc/Html/Fragmentation/, tests/Unit/Html/PagedMedia/
CSS Table(css_tables_33Verifiedsrc/Html/Table/ + ゴールデン PDF
CSS Cascading and Inheritance(css_cascade_33Verifiedsrc/Html/Cascade/

@page の名前付きページセレクターは CSS Paged Media の一部です。これに依存する前に、そのモジュールの現在の評価をマトリックスで確認してください。

エンジンは、ストリームの進行に合わせて改ページを出力します。再フローのために保持されるツリーは存在しないため、改ページの決定は一度行われると確定します。一部のコンテンツでは、相互参照などのように、レイアウト後に最終的なページ番号が必要になります。そのようなコンテンツには制約があるため、その制限を念頭に置いて作成してください。

ページ分割は、パーサーではなく改ページコントローラーの責務です。パーサーは生のページ遷移演算子を出力せず、コントローラー契約を通じて改ページを要求します。

ストリーミングモデルでは、すべてのページを一度に保持せず、現在のページバッファとフラットなアウトラインリストだけを保持します。エンジンは完成したページをフラッシュするため、非常に長いドキュメントでも ADR-020 の上限内に収まります。テーブルと flex コンテナは、引き続き 1 コンテキストあたり 5,000 ノードの制限に従います。

悪意のあるドキュメントであっても、メモリを無制限に消費させることはできません。要素とネストの上限(ADR-001)、およびコンテキストごとのノードバジェット(ADR-020)が処理量を制限します。ユーザー提供の長いコンテンツについては、長さと構造を検証してください。エンジンは、攻撃者が制御するアウトラインのタイトルをテキストとしてレンダリングし、決して解釈しません。

記述仕様箇条リファレンス ID
各アウトライン項目は宛先を関連付けることができるため、ユーザーはその項目に直接ジャンプできます。ISO 32000-2iso32000_2_sec12#x1.x5.p4
アウトライン項目の Dest エントリは、その項目がアクティブ化されたときに表示される宛先を指定します。ISO 32000-2iso32000_2_sec12#x1.x11.p30

このレシピは、NextPDF が長いコンテンツを流し込み、アウトラインを構築する方法を示します。CSS Fragmentation はサポートマトリックスで Verified と評価されています。

該当なし。