Pro エディション
Diff — 詳細リファレンス
このページは、NextPDF Pro の差分モジュール NextPDF\Pro\Diff の契約レベルのリファレンスです。このモジュールは 2 つの PDF ドキュメントを比較し、テキスト・画像・メタデータの変更を報告します。PdfDiffer はページアライメント済みの Myers 行差分を生成します。StructuredDiffer は段落のグループ化、画像比較、メタデータ比較を追加します。DiffFormatter は構造化結果を JSON または HTML フラグメントへシリアライズします。このページでは、公開 API、観測可能な動作契約、リソース境界、失敗モードを規定します。タスク指向のセットアップとサンプルは Diff 機能ページ にあります。
提供状況とライセンス
「提供状況とライセンス」という見出しのセクションこの機能は NextPDF Pro(nextpdf/pro)に同梱され、Pro ティアのライセンスエンベロープで有効化されます。その利用資格が無いデプロイでは、この機能のクラスはロードされません。エディションを比較してライセンスを取得。
このモジュールをゲートするランタイムのケーパビリティフラグはありません。差分クラスは、nextpdf/pro がインストールされライセンスされている限りいつでも利用できます。
公開 API サーフェス
「公開 API サーフェス」という見出しのセクション| シンボル | パラメーター | デフォルト動作 | 戻り値 | 送出・失敗条件 | 備考 |
|---|---|---|---|---|---|
PdfDiffer::compare() | string $sourcePdf, string $targetPdf | ページごとのテキストを抽出し、ソースのページ i をターゲットのページ i と比較 | DiffResult | バッファに %PDF ヘッダーが無い場合、またはオプションのリーダーが解析に失敗した場合は InvalidArgumentException。リソース境界に達した場合は OverflowException | 静的なエントリーポイント |
PdfDiffer::compareTexts() | array $sourcePages, array $targetPages(いずれも list<string>) | 抽出を経由せず、事前抽出済みのページテキストを比較 | DiffResult | リソース境界に達した場合は OverflowException | 静的。テキストが既に利用可能な場合に使用 |
PdfDiffer::extractText() | string $contentStream | 1 つの生コンテンツストリームからテキスト表示演算子を解析 | string | —(フォールトトレラント。解析不能な入力は空文字列を返す) | 静的 |
StructuredDiffer::__construct() | ?ImageDiffer $imageDiffer = null, ?MetadataDiffer $metadataDiffer = null | null の引数はデフォルトの差分器を構築 | — | — | テスト向けのコンストラクター注入 |
StructuredDiffer::compare() | string $sourcePdf, string $targetPdf | テキスト・段落・画像・メタデータの比較を実行し、サマリーを構築 | StructuredDiffResult | テキスト経路からの InvalidArgumentException と OverflowException を伝播 | モジュール全体のオーケストレーター |
DiffFormatter::toJson() | StructuredDiffResult $result | 整形された JSON ドキュメント | string | エンコードに失敗した場合は JsonException | — |
DiffFormatter::toHtml() | StructuredDiffResult $result | サマリー・段落・メタデータの各セクションを含む HTML フラグメント。テキスト値はエンティティエスケープ済み | string | — | 完全なドキュメントではなくフラグメントのみ |
DiffFormatter::toArray() | StructuredDiffResult $result | toJson() を支えるシリアライズ配列 | array<string, mixed> | — | 安定した snake_case キー |
ImageDiffer::diff() | string $sourcePdf, string $targetPdf | 画像 XObject をハッシュ化し、追加・削除・変更された画像を報告 | list<ImageDiff> | —(デコード不能な構造はフェイルクローズドでスキップ) | 同一性はページバケットとオブジェクト番号の組 |
MetadataDiffer::diff() | string $sourcePdf, string $targetPdf | 8 つの /Info フィールド(Title、Author、Subject、Keywords、Creator、Producer、CreationDate、ModDate)を比較 | list<MetadataChange> | —(非準拠の入力でも例外を送出しない) | 値はデコード済み文字列として比較 |
DiffEngine::diff() | array $sourceLines, array $targetLines, int $pageIndex = 0, int $maxLines = 10000 | 2 つの行リストに対する Myers 行差分 | list<DiffRegion> | 合算した行数が $maxLines を超える場合、または編集距離がメモリ境界の上限を超える場合は OverflowException | 静的。すべてのテキスト経路の領域生成器 |
TextExtractor::fromContentStream() | string $contentStream | ストリームをトークン化し、テキストステートマシンを実行 | list<TextBlock> | — | 静的 |
TextExtractor::fromOperations() | array $operations(list<ContentStreamOp>) | 事前解析済みの操作に対してテキストステートマシンを実行 | list<TextBlock> | — | 静的 |
ContentStreamParser::parse() | コンストラクターが string $data を受け取る | 演算子とオペランドをトークン化。辞書とコメントはスキップ。フォールトトレラント | list<ContentStreamOp> | — | 認識されないバイトはスキップされ、致命的にならない |
ContentStreamOp | string $operator, list<mixed> $operands | 読み取り専用の操作値オブジェクト。isTextOp() がテキスト関連演算子を分類 | — | — | — |
DiffResult | list<DiffRegion> $regions, int $sourcePagesCount, int $targetPagesCount | 領域を $added、$removed、$modified に振り分け、isIdentical()、hasDifferences()、totalChanges() を公開 | — | — | 読み取り専用。Unchanged の領域は $regions にのみ残る |
StructuredDiffResult | テキスト差分、段落、画像、メタデータ変更、サマリー | 集約結果。hasDifferences()、isIdentical() はサマリーへ委譲 | — | — | 読み取り専用 |
DiffSummary | カテゴリー別カウントとページ数 | テキスト・画像・メタデータのカウントに対する hasDifferences() と totalChanges() | — | — | 読み取り専用 |
DiffRegion | DiffType $type, string $text, int $pageIndex, int $lineIndex, ?string $counterpartText = null | 1 件の行レベルの変更 | — | — | $counterpartText は出荷版エンジンでは null のまま |
ParagraphDiff | 種別、テキスト、ページインデックス、開始/終了行、領域 | 1 ページ上の連続する同一種別の領域。lineCount() | — | — | 読み取り専用 |
ImageDiff | 種別、ページインデックス、ソースハッシュ、ターゲットハッシュ、オブジェクト ID | 1 件の画像変更エントリー | — | — | ハッシュは存在しない側では空文字列 |
MetadataChange | string $field, ?string $sourceValue, ?string $targetValue | 1 件のフィールド変更。isAdded()、isRemoved()、isModified() | — | — | null はフィールドが存在しないことを意味する |
TextBlock | テキスト、x、y、フォント名、フォントサイズ、行インデックス | 概略位置を伴う 1 件の抽出テキストラン | — | — | 読み取り専用 |
DiffType | enum: Added, Removed, Modified, Unchanged | テキスト向けの文字列ベースの変更分類 | — | — | 動作契約の Modified に関する注記を参照 |
ImageDiffType | enum: Added, Removed, Modified, Unchanged | 画像向けの文字列ベースの変更分類 | — | — | — |
エントリーポイントのシグネチャ
「エントリーポイントのシグネチャ」という見出しのセクションpublic static function compare(string $sourcePdf, string $targetPdf): DiffResult
public static function compareTexts(array $sourcePages, array $targetPages): DiffResult
public static function extractText(string $contentStream): stringpublic function __construct( ?ImageDiffer $imageDiffer = null, ?MetadataDiffer $metadataDiffer = null,)
public function compare(string $sourcePdf, string $targetPdf): StructuredDiffResultpublic function toJson(StructuredDiffResult $result): string
public function toHtml(StructuredDiffResult $result): string
public function toArray(StructuredDiffResult $result): arraypublic static function diff( array $sourceLines, array $targetLines, int $pageIndex = 0, int $maxLines = self::MAX_DIFF_LINES,): arrayページアライメントと行差分
「ページアライメントと行差分」という見出しのセクションPdfDiffer::compare() はページごとのテキストを抽出し、ソースのページ i をターゲットのページ i と比較します。ページ数が異なる場合、不足している側は超過分のページについて空のテキストとして扱われます。各ページペアの内部では、テキストは改行で分割され、ページごとに Myers 行差分が実行されます。エンジンは Added、Removed、Unchanged の領域を出力します。変更された行は Removed と Added の領域の組として現れます。出荷版エンジンは Modified のテキスト領域を出力しません。Modified のケースと DiffResult::$modified バケットは、DiffResult のコンストラクターが公開されているため、呼び出し側が構築した結果のために用意されています。totalChanges() は追加・削除・変更された領域を数え、変更のない領域は除外します。
抽出には 2 つの経路があります。
- オプションの Artisan リーダーが存在する場合。 オプションの
NextPDF\Parser\PdfReaderクラスがインストールされている場合、ページのコンテンツストリームはページ正確なテキストを得るためにそれを通して読み取られます。トレーラーのページ数がループを駆動します。読み取りに失敗したページは、比較を中止する代わりに空のテキストを提供します。 - フォールバック。 境界付きのバイトレベルスキャナーが
strposでstream/endstreamのペアを特定し、50 MB の厳格な出力上限で FlateDecode データを展開し、ストリーム辞書が/DecodeParmsを通じて要求する場合は ISO 32000-2:2020 §7.4.4.4 に従って PNG プレディクターを逆フィルタリングします。不正または未対応のプレディクターの場合、デコード済みバイトは変更されません。フォールバックは復元したすべてのテキストを単一のページバケットへ連結するため、ページレベルのアライメントはリーダー経路でのみページ正確です。
どちらの経路も、§9.4 のテキスト表示演算子 Tj、TJ、' を解析します。ステートマシンは BT/ET、Tm(原点のみ)、Td/TD、T*、Tf を追跡します。
構造化比較
「構造化比較」という見出しのセクションStructuredDiffer::compare() はテキスト差分を実行し、同一ページ上で連続する同一種別の領域を段落へグループ化し(変更のないランも含む)、続いて画像とメタデータの比較を実行して DiffSummary を組み立てます。サマリーの段落数は、追加・削除・変更された段落のみを対象とします。
画像比較は PDF オブジェクトを構造的に列挙します。ストリーム本体の範囲は §7.3.8.2 に従って /Length エントリーによって決まるため、単にオブジェクト構文に似ているだけのバイナリバイトが幻のオブジェクトとして登録されることはありません。圧縮オブジェクトストリーム(/Type /ObjStm)は §7.5.7 に従ってデコードされるため、その内部にネストされた画像 XObject も可視化されます。検出された各画像は、非暗号の xxh128 関数でコンテンツハッシュ化されます。同一性はページバケットとオブジェクト番号の組です。ストリーム順で所有ページを持たない画像はページ 0 に帰属されます。
メタデータ比較は、可能な場合はトレーラーを通じて実際の /Info 辞書を解決するため、コンテンツストリーム内のおとりのフィールドトークンがドキュメントメタデータと誤認されることはありません。フィールド値は PDF 文字列としてデコードされます。すなわち §7.3.4.2 に従うリテラル形式と、§7.3.4.3 に従う 16 進形式です。解決可能なトレーラーが無い場合、探索は入力全体へフォールバックします。日付は、解析されたタイムスタンプではなくデコード済み文字列として比較されます。
レポート出力
「レポート出力」という見出しのセクションDiffFormatter::toJson() は整形された JSON を返し、JSON_THROW_ON_ERROR でエンコードするため、エンコード失敗時には false を返す代わりに JsonException を送出します。toHtml() は <div class="nextpdf-diff"> フラグメントを返します。段落テキストとメタデータ値は HTML エンティティエスケープを通ります。視覚的なサイドバイサイドのレッドライン PDF 出力はありません。同一の入力に対しては、領域と整形済み出力は決定的です。
エッジケースと失敗モード
「エッジケースと失敗モード」という見出しのセクション- ページアライメントは位置ベースです。ページが 1 つ挿入または削除されると、後続するすべてのページのアライメントがずれ、下流の変更カウントが水増しされます。
- フォールバックの抽出経路では、すべてのテキストがページインデックス 0 に配置されます。リーダーで抽出したドキュメントを、フォールバック経路による期待値と比較すると、ページ帰属が異なります。
%PDFで始まらないソースまたはターゲットのバッファは、比較の前にInvalidArgumentExceptionで失敗します。- 1 つのページペアで合算した行数が 10,000 を超えると、
OverflowException(行数境界)で失敗します。 - 共有する行が少なすぎる 2 つのページテキストは、Myers 編集距離がメモリ境界の上限を超えると
OverflowExceptionで失敗します。正当なリビジョンはほとんどの行を共有するため影響を受けません。共通性の低い敵対的入力はこの境界に抵触します。 - 50 MB を超えるフォールバックストリームの展開出力は、
OverflowException(解凍爆弾境界)で失敗します。スキャナーは無制限な正規表現ではなくstrposを使用するため、細工された入力が壊滅的なバックトラッキングを引き起こすことはできません。 "テキスト表示演算子は 3.1.0 ではトークン化されますがテキストブロックを生成しません。"のみで表示されるテキストは差分に参加しません。- スキャンされた画像のみの PDF は、テキスト差分をほとんど、または全く生成しません。OCR は実行されません。
- 画像変更検出は知覚的ではなく構造的です。ページをラスタライズせず、同一ピクセルで再エンコードされた画像も、そのバイトが異なれば変更として報告されます。
- リビジョン間でページバケットまたはオブジェクト番号が変わる画像は、変更としてではなく、削除と追加の組として報告されます。
- FlateDecode 以外のフィルターで圧縮されたオブジェクトストリームは、フェイルクローズドでスキップされます。そのメンバー画像は比較されません。
- このモジュールでは暗号操作は一切行われないため、FIPS モード固有の挙動はありません。画像ハッシュは変更検出のみを目的とし、完全性や証拠としての重みを持ちません。
| 主張 | 標準 | 条項 |
|---|---|---|
抽出のために Tj と TJ のテキスト表示演算子を解析 | ISO 32000-2:2020 | §9.4 |
フォールバックのストリームデータは stream キーワードに続く CRLF または LF の後から開始 | ISO 32000-2:2020 | §7.3.8.1 |
画像スキャンのストリーム範囲は辞書の /Length エントリーによって決まる | ISO 32000-2:2020 | §7.3.8.2 |
オブジェクトストリームのメンバーは /N ペアテーブルと /First オフセットを通じて特定 | ISO 32000-2:2020 | §7.5.7 |
PNG プレディクターの逆変換は /DecodeParms の Predictor パラメーターに従う | ISO 32000-2:2020 | §7.4.4.4 |
| メタデータ値はリテラルおよび 16 進の文字列形式をデコード | ISO 32000-2:2020 | §7.3.4.2, §7.3.4.3 |
| 視覚的なサイドバイサイドのレッドライン PDF 出力 | — | 未対応(JSON/HTML のみ) |
すべての条項は言い換えたものであり、NextPDF は規範テキストを複製しません。これらは能力に関する記述であり、認証ではありません。NextPDF はいかなる認証も保有せず、付与もしません。テキスト復元はテキスト表示演算子から行テキストを再構成します。§9.4 の完全なテキストステートマシンは実行しないため、差分はジオメトリレベルではなくコンテンツレベルです。
開発ノート
「開発ノート」という見出しのセクション- Pro パッケージ内での提供状況:
PdfDiffer、DiffEngine、TextExtractorおよびそれらの値オブジェクトは 1.8.0 から。StructuredDiffer、DiffFormatter、ImageDiffer、MetadataDifferおよびそれらの値オブジェクトは 2.2.0 から。すべてnextpdf/pro3.1.0 で最新です。 - ページテキストが既に利用可能な場合は
PdfDiffer::compareTexts()を優先してください。抽出とその失敗モードを完全にスキップします。 - オプションの Artisan リーダーは抽出精度とページ帰属を向上させます。ランタイムで検出され、必須になることはありません。
- 信頼できない入力を差分する際は
OverflowExceptionを捕捉してください。これらの境界は意図的なフェイルクローズドの拒否であり、一時的なエラーではありません。 DiffFormatter::toHtml()はクラス名(diff-added、diff-removed、diff-modified、diff-unchanged)を出力しますが、スタイルシートは出力しません。独自の CSS を用意してください。- テストでは
StructuredDifferをスタブの差分器で構築し、テキスト経路を画像・メタデータのスキャンから分離してください。
このページは、外部から観測可能な動作とサポート対象の公開 API サーフェスのみを文書化します。内部の名前空間パス、ヘルパークラス、メカニズム表、Runbook のファイル名、チケットのプレフィックスは対象外です。
- Diff(機能) — インストール、クイックスタート、本番向けサンプル。
- Converter — 詳細リファレンス
- Filter — 詳細リファレンス