Pro edition
Diff
At a glance
Section titled “At a glance”NextPDF\Pro\Diff compares two PDF documents and reports what changed. The
quick path produces a page-aligned text diff; the structured path adds image
and metadata change detection and formats the result as JSON or HTML.
Availability & licensing
Section titled “Availability & licensing”This capability ships in NextPDF Pro (nextpdf/pro) and activates with a
Pro-tier license envelope. A deployment without that entitlement does not load the capability’s classes. Compare editions and
get a license.
No runtime capability flag gates the Diff classes; they are present whenever the Pro package is installed.
Install
Section titled “Install”composer require nextpdf/pro:^3Conceptual overview
Section titled “Conceptual overview”PdfDiffer::compare() extracts text per page from each document, splits it
into lines, and runs a Myers line diff per page pair, producing added,
removed, and modified regions. Text extraction parses the text-showing
operators of ISO 32000-2:2020 §9.4 (Tj, TJ, ').
StructuredDiffer builds on that: it groups text regions into paragraph-
level changes, compares embedded images, compares metadata, and produces a
StructuredDiffResult with an aggregate summary. DiffFormatter serializes
that result to a JSON string or an HTML report fragment.
When the optional Artisan PDF reader is installed, text extraction uses it for page-accurate content; otherwise a bounded byte-level fallback scans content streams directly.
Why it works this way
Section titled “Why it works this way”The differ compares extracted text and structure, not rendered pixels. A
structural comparison is deterministic, cheap, and maps to the editorial
changes a reviewer cares about. A pixel diff would instead flag antialiasing
and font-hinting noise as content. Because a PDF stores glyphs and positioning,
not ready-to-read characters, every comparison first reconstructs text from the
content stream. That extraction step is why the Artisan reader sharpens
accuracy, why the bounded FlateDecode fallback trades coverage for safety, and
why scanned pages barely diff. Page alignment stays index-based for
predictability, so an inserted page reads as a clear downstream shift.
Design background: Why the text in a PDF is not really text.
Behavior contract
Section titled “Behavior contract”- Input. Raw PDF bytes for source and target. A buffer not starting with
%PDFraisesInvalidArgumentException. - Output (quick path).
DiffResultwithadded,removed,modifiedregion lists plusisIdentical(),hasDifferences(),totalChanges(). - Output (structured path).
StructuredDiffResultwith paragraph diffs, image diffs, metadata changes, and aDiffSummary. - Report output.
DiffFormatteremits a JSON string or an HTML fragment. It does not produce a visual side-by-side redline PDF. - Resource bounds. Decompressed content-stream size is capped to guard against decompression bombs; the byte-level scanner avoids catastrophic regex backtracking on crafted input.
- Determinism. For identical inputs the diff regions and the formatted output are stable.
Public API surface
Section titled “Public API surface”| Type | Kind | Key members |
|---|---|---|
NextPDF\Pro\Diff\PdfDiffer | final class | static compare(string $sourcePdf, string $targetPdf): DiffResult, static compareTexts(array $sourcePages, array $targetPages): DiffResult, static extractText(string $contentStream): string |
NextPDF\Pro\Diff\StructuredDiffer | final class | __construct(?ImageDiffer $imageDiffer = null, ?MetadataDiffer $metadataDiffer = null), compare(string $sourcePdf, string $targetPdf): StructuredDiffResult |
NextPDF\Pro\Diff\DiffFormatter | final class | toJson(StructuredDiffResult $result): string, toHtml(StructuredDiffResult $result): string |
NextPDF\Pro\Diff\DiffResult | final readonly class | array $added, array $removed, array $modified, isIdentical(): bool, hasDifferences(): bool, totalChanges(): int |
NextPDF\Pro\Diff\StructuredDiffResult | final readonly class | text diff, paragraphs, images, metadata changes, summary |
NextPDF\Pro\Diff\DiffType | enum | Added, Removed, Modified, Unchanged |
Code sample — Quick start
Section titled “Code sample — Quick start”<?php
declare(strict_types=1);
use NextPDF\Pro\Diff\PdfDiffer;
$diff = PdfDiffer::compare( file_get_contents('v1.pdf'), file_get_contents('v2.pdf'),);
if ($diff->hasDifferences()) { echo $diff->totalChanges(), " text changes detected\n";}Code sample — Production
Section titled “Code sample — Production”<?php
declare(strict_types=1);
use NextPDF\Pro\Diff\DiffFormatter;use NextPDF\Pro\Diff\StructuredDiffer;
function reviewReport(string $oldPdf, string $newPdf): string{ $result = (new StructuredDiffer())->compare($oldPdf, $newPdf);
// JSON for machine consumption; toHtml() for a review UI fragment. return (new DiffFormatter())->toJson($result);}Edge cases & gotchas
Section titled “Edge cases & gotchas”- The diff is page-aligned by index. Inserting a page early shifts all later pages and reports large downstream changes — this is expected for an index-aligned comparison.
- Image comparison detects added, removed, and modified embedded images; it is not a perceptual visual-diff and does not pixel-render pages.
- Scanned image-only PDFs produce little or no text diff because no OCR is performed.
- Without the optional Artisan reader, extraction uses the bounded fallback; heavily compressed documents may yield reduced text coverage.
Performance
Section titled “Performance”Text extraction is linear in document bytes; the Myers diff is
near-linear for similar documents and quadratic in the worst case per page
pair. The decompression cap bounds memory. See performance_budget.
Security notes
Section titled “Security notes”The byte-level fallback uses strpos-based scanning instead of unbounded
regex to avoid catastrophic backtracking on crafted PDFs, and bounds
decompression output. Diffing does not execute embedded scripts. See the
Core security model.
Conformance
Section titled “Conformance”| Claim | Spec clause | Status |
|---|---|---|
Tj text operator parsed for extraction | ISO 32000-2:2020 §9.4 | Verified (unit suite) |
TJ array text operator parsed for extraction | ISO 32000-2:2020 §9.4 | Verified (unit suite) |
| Visual side-by-side redline PDF output | — | Not supported (JSON/HTML only) |
Core fallback / alternative
Section titled “Core fallback / alternative”No Core equivalent exists for document comparison. The optional Artisan reader improves extraction accuracy when installed but is not required.
Enterprise boundary note
Section titled “Enterprise boundary note”This is a content-change detector. It is not a forensic difference analyzer and does not produce evidentiary or tamper-attribution reports; those concerns are out of scope for this module.
Publication boundary
Section titled “Publication boundary”This page documents externally observable behavior and the supported public API surface only. Internal namespace paths, helper classes, mechanism tables, runbook filenames, and ticket prefixes are out of scope.