コンテンツにスキップ

ドキュメント:DPart、split / merge、ベンダー拡張

Document モジュールは、ページコンテンツではなく PDF ドキュメント全体を操作対象にします。規制対象のワークフローでメタデータを付加するためのドキュメントパート階層を構築し、PDF をページ範囲ごとのセグメントに分割し、複数の PDF を 1 つに結合し、ドキュメントカタログへ開発者拡張を登録します。

Terminal window
composer require nextpdf/core:^3

このモジュールはページコンテンツより上位の層に位置します。Graphics と Content がオペレーターを出力するのに対し、Document はページツリー、ドキュメントカタログ、ドキュメントパートツリーといった構造レベルを扱います。

ドキュメントパートDPart)は、PDF における論理的な区画です。ISO 32000-2 は、各ノードがドキュメントパートメタデータ(DPM)を保持する DPart 階層を定義しています。製薬、法務、アーカイブなどの規制対象ワークフローでは、ファイル全体ではなくページのサブ範囲にメタデータを関連付けられます — §14.12。DPart は不変の readonly ノードです。リーフはページインデックスの連続範囲を参照し、中間ノードは子の DPart ノードをツリーにまとめます。DPartRoot は、Writer がシリアライズするツリーのルートです。リーフノードの /Start および /End エントリは、ページインデックス整数ではなく、ページオブジェクトへの間接参照です — §14.12。DPart::resolveWithPageObjects() は、Writer から提供されるページインデックス→オブジェクト番号マップに基づいてその解決を行い、/Start(および省略可能な /End)の参照形式を返します。マップを利用できないテストパスでのみ、整数形式へフォールバックします。

PdfMergerPdfSplitter は、ドキュメント合成のための API サーフェスです。PdfMerger は、複数の入力 PDF からページオブジェクトを収集し、衝突を避けるためにオブジェクトを再採番し、単一のページツリーとクロスリファレンステーブルを再構築します。生成されるページツリーは、Pages ノードを KidsCount でバランスさせて構成され、PDF がページツリーノードに対して定義する継承可能な属性モデルを備えています — §7.7.3。PdfSplitter は逆方向の処理として、ページ範囲を独立した SplitDocument オブジェクトとして抽出します。PageRange は、両者で利用される値オブジェクトです。1 始まりで境界を検証し、contains()count()toArray() に対応します。

VendorExtensionRegistryExtensionsDictionaryDeveloperExtensionEntry は、ドキュメントカタログ内の開発者拡張辞書をモデル化し、エンジンが基本仕様を超えるベンダー拡張レベルを宣言するための仕組みを提供します。レジストリは、同一ベンダープレフィックスの競合する再登録を VendorExtensionRegistryConflictException で拒否します。CollectionDictionaryCollectionSort は、PDF コレクション(ポータブルコレクション/ポートフォリオ)のカタログエントリをモデル化します。

クラス主なメソッド役割
DPartisLeaf(), hasMetadata(), resolveWithPageObjects(), write()不変のドキュメントパートノード(@since 1.12.0
DPartRootisEmpty(), write()Writer がシリアライズする DPart ツリーのルート(@since 1.12.0
PdfMergermerge(array $pdfFiles, int $maxFiles = 100, int $maxTotalBytes = 200_000_000), append()オブジェクト再採番を伴う複数 PDF の結合(@since 1.9.0
PdfSplittersplit(), splitEvery(), extractPages()ページ範囲の SplitDocument への分割(@since 1.9.0
PageRangecontains(int $page), count(), toArray()1 始まりのページ範囲値オブジェクト
MergeResult / SplitResultisValid(), count(), document(), totalOutputSize()合成結果オブジェクト
VendorExtensionRegistry拡張登録開発者拡張レジストリ(@since 2.2.0
ExtensionsDictionarywithEntry(), entries(), isEmpty(), toPdfDictionary()不変の拡張辞書ビルダー(@since 2.0.0
CollectionDictionarytoPdfDictionary()ポータブルコレクションのカタログエントリ(@since 2.0.0

完全な PHPDoc テーブルを確認するには、composer docs:generate-api-php -- --module=Document を実行してください。

PDF を 1 ページごとのドキュメントに分割し、その結果を確認します。

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Document\PageRange;
use NextPDF\Document\PdfSplitter;
$splitter = new PdfSplitter();
$result = $splitter->splitEvery(file_get_contents('/srv/in/report.pdf'), 1);
foreach (range(0, $result->count() - 1) as $index) {
$segment = $result->document($index);
file_put_contents("/srv/out/page-{$index}.pdf", $segment->pdfData);
}
$singlePage = $splitter->extractPages(
file_get_contents('/srv/in/report.pdf'),
new PageRange(2, 4),
);

明示的な入力予算の下で複数の PDF を結合し、出力を書き込む前に妥当性を確認します。

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Document\PdfMerger;
use NextPDF\Exception\PageLayoutException;
/** @var list<string> $sources Raw PDF byte strings to combine. */
$sources = array_map(
static fn (string $path): string => file_get_contents($path),
glob('/srv/batch/*.pdf') ?: [],
);
$merger = new PdfMerger();
try {
// Bound the merge: at most 50 files, 100 MB total.
$merged = $merger->merge($sources, maxFiles: 50, maxTotalBytes: 100_000_000);
} catch (PageLayoutException $e) {
throw new \RuntimeException('Merge rejected: empty or invalid input set.', previous: $e);
}
if (!$merged->isValid()) {
throw new \RuntimeException('Merged document failed structural validation.');
}
file_put_contents('/srv/out/combined.pdf', $merged->pdfData);
  • PdfMerger::merge()PdfSplitter::split() は、ResourceGuard を通じて入力の上限を強制します。上限を超えた個数またはサイズの入力は、暗黙的に切り詰めるのではなく例外を送出します。maxFiles / maxTotalBytes は、ワークロードに合わせて明示的に調整してください。
  • 空のファイルリストまたは空の範囲リストは PageLayoutException を送出します。これらは空の結果ではなく、構成エラーです。
  • PageRange は 1 始まりで、両端を含みます。リーフ DPartpages リストは 0 始まりのページインデックスです。この 2 つの抽象化はインデックスの基準を共有しません。両者をまたぐ際は明示的に変換してください。
  • DPartreadonly です。異なるツリーを構築するには、既存ノードを変更するのではなく、新しいノードを構築します。resolveWithPageObjects() は、ページオブジェクトマップが空の場合にのみ整数インデックスのフォールバック形式を返します。本番出力でそのパスに依存しないでください。
  • VendorExtensionRegistry は、重複するベンダープレフィックスに対して VendorExtensionRegistryConflictException を送出します。各プレフィックスは一度だけ登録してください。

分割と結合の計算量はページ数に対して線形であり、処理時間を左右するのはモジュール自身の管理処理ではなく、解析とオブジェクト再採番です。デフォルトの参照ワークロードは、1500 ms のウォール時間 / 64 MB のピークという予算内に収まります。大規模な結合は、主に入力の総バイト数によって制約されます。maxTotalBytes ガードは、ピークメモリを境界内に保つために存在します。再現性プロファイルは structural です。結合または分割された PDF は新しいトレーラーと /ID を持つため、2 回の実行は構造的には等しくなりますが、バイト単位では同一になりません。

PdfMerger::merge()PdfSplitter::split() は、信頼できない PDF バイト列を処理します。いずれも解析の前に入力を ResourceGuard::assertSize() / assertCount() に通し、これにより解凍またはオブジェクト数の増幅によるサービス拒否を境界内に抑えます。maxFilesmaxTotalBytesmaxBytes の各引数は、デフォルトに依存せず、デプロイ環境に合わせて厳しめに保ってください。すべての入力 PDF を敵対的なものとして扱ってください。ソースがユーザーから提供される場合は、制限付きワーカー内でバッチ合成を実行してください。信頼境界については、/modules/core/security/ のエンジン脅威モデルを参照してください。

このモジュールが構築する DPart ツリーは、ISO 32000-2 §14.12 のドキュメントパートモデルに従い、リーフの /Start および /End エントリは同じ条項に従ってページオブジェクトへの間接参照として出力されます。結合された出力は、§7.7.3 で定義されたページツリーノード構造を使用します。これらは src/Document/ が生成し、tests/Unit/Document/DPartTest, DPartRootTest, DPartPageRefTest, DocumentPdfMergerDeepTest, DocumentPageRangeParseDeepTest)が検証する実装上の事実です。これらはエンドツーエンドの PDF 2.0 適合性を表明するものではありません。ドキュメント全体の適合性は、/modules/core/conformance/ で説明されているオラクルおよびゴールデンスイートによって検証されます。