Pro Edition
Diff — Tiefenreferenz
Auf einen Blick
Abschnitt betitelt „Auf einen Blick“Diese Seite ist die Referenz auf Vertragsebene für das NextPDF-Pro-Diff-Modul, NextPDF\Pro\Diff. Das Modul vergleicht zwei PDF-Dokumente und meldet Änderungen an Text, Bildern und Metadaten. PdfDiffer erzeugt ein seitenausgerichtetes Myers-Zeilendiff. StructuredDiffer ergänzt Absatzgruppierung, Bildvergleich und Metadatenvergleich. DiffFormatter serialisiert das strukturierte Ergebnis nach JSON oder in ein HTML-Fragment. Diese Seite legt die öffentliche API, den beobachtbaren Verhaltensvertrag, die Ressourcengrenzen und die Fehlermodi dar. Aufgabenorientierte Einrichtung und Beispiele finden Sie auf der Diff-Fähigkeitsseite.
Verfügbarkeit & Lizenzierung
Abschnitt betitelt „Verfügbarkeit & Lizenzierung“Diese Fähigkeit wird mit NextPDF Pro (nextpdf/pro) ausgeliefert und aktiviert sich mit einer Lizenzhülle der Pro-Stufe. Eine Bereitstellung ohne diese Berechtigung lädt die Klassen der Fähigkeit nicht. Editionen vergleichen und eine Lizenz erwerben.
Kein Laufzeit-Fähigkeitsflag sperrt dieses Modul. Die Diff-Klassen sind nutzbar, sobald nextpdf/pro installiert und lizenziert ist.
Öffentliche API-Oberfläche
Abschnitt betitelt „Öffentliche API-Oberfläche“| Symbol | Parameter | Standardverhalten | Rückgabe | Wirft oder scheitert mit | Hinweise |
|---|---|---|---|---|---|
PdfDiffer::compare() | string $sourcePdf, string $targetPdf | Extrahiert Text je Seite und vergleicht dann Seite i der Quelle mit Seite i des Ziels | DiffResult | InvalidArgumentException, wenn einem Puffer der %PDF-Header fehlt oder der optionale Reader nicht parsen kann; OverflowException bei einer Ressourcengrenze | Statischer Einstiegspunkt |
PdfDiffer::compareTexts() | array $sourcePages, array $targetPages (jeweils list<string>) | Vergleicht vorab extrahierte Seitentexte und umgeht die Extraktion | DiffResult | OverflowException bei einer Ressourcengrenze | Statisch; verwenden, wenn der Text bereits vorliegt |
PdfDiffer::extractText() | string $contentStream | Parst textausgebende Operatoren aus einem rohen Content-Stream | string | — (fehlertolerant; nicht parsbare Eingabe ergibt eine leere Zeichenkette) | Statisch |
StructuredDiffer::__construct() | ?ImageDiffer $imageDiffer = null, ?MetadataDiffer $metadataDiffer = null | null-Argumente konstruieren die Standard-Differ | — | — | Konstruktorinjektion zum Testen |
StructuredDiffer::compare() | string $sourcePdf, string $targetPdf | Führt Text-, Absatz-, Bild- und Metadatenvergleich aus und erstellt dann eine Zusammenfassung | StructuredDiffResult | Propagiert InvalidArgumentException und OverflowException aus dem Textpfad | Orchestrator über das gesamte Modul |
DiffFormatter::toJson() | StructuredDiffResult $result | Formatiertes JSON-Dokument | string | JsonException, wenn die Kodierung scheitert | — |
DiffFormatter::toHtml() | StructuredDiffResult $result | HTML-Fragment mit Abschnitten für Zusammenfassung, Absatz und Metadaten; Textwerte werden entitätsmaskiert | string | — | Nur Fragment, kein vollständiges Dokument |
DiffFormatter::toArray() | StructuredDiffResult $result | Serialisierungsarray, das toJson() zugrunde liegt | array<string, mixed> | — | Stabile snake_case-Schlüssel |
ImageDiffer::diff() | string $sourcePdf, string $targetPdf | Hasht Bild-XObjects und meldet hinzugefügte, entfernte und geänderte Bilder | list<ImageDiff> | — (nicht dekodierbare Strukturen werden fail-closed übersprungen) | Identität ist Seiten-Bucket plus Objektnummer |
MetadataDiffer::diff() | string $sourcePdf, string $targetPdf | Vergleicht acht /Info-Felder (Title, Author, Subject, Keywords, Creator, Producer, CreationDate, ModDate) | list<MetadataChange> | — (wirft bei nicht konformer Eingabe niemals) | Werte werden als dekodierte Zeichenketten verglichen |
DiffEngine::diff() | array $sourceLines, array $targetLines, int $pageIndex = 0, int $maxLines = 10000 | Myers-Zeilendiff über zwei Zeilenlisten | list<DiffRegion> | OverflowException, wenn die kombinierten Zeilen $maxLines überschreiten oder die Editierdistanz die speichergebundene Obergrenze überschreitet | Statisch; der Regionenerzeuger für alle Textpfade |
TextExtractor::fromContentStream() | string $contentStream | Tokenisiert den Stream und führt die Textzustandsmaschine aus | list<TextBlock> | — | Statisch |
TextExtractor::fromOperations() | array $operations (list<ContentStreamOp>) | Führt die Textzustandsmaschine über vorab geparste Operationen aus | list<TextBlock> | — | Statisch |
ContentStreamParser::parse() | Konstruktor nimmt string $data | Tokenisiert Operatoren und Operanden; überspringt Dictionaries und Kommentare; fehlertolerant | list<ContentStreamOp> | — | Nicht erkannte Bytes werden übersprungen, niemals fatal |
ContentStreamOp | string $operator, list<mixed> $operands | Schreibgeschütztes Operationswertobjekt; isTextOp() klassifiziert textbezogene Operatoren | — | — | — |
DiffResult | list<DiffRegion> $regions, int $sourcePagesCount, int $targetPagesCount | Ordnet Regionen in $added, $removed, $modified ein; stellt isIdentical(), hasDifferences(), totalChanges() bereit | — | — | Schreibgeschützt; Unchanged-Regionen verbleiben nur in $regions |
StructuredDiffResult | Textdiff, Absätze, Bilder, Metadatenänderungen, Zusammenfassung | Aggregiertes Ergebnis; hasDifferences(), isIdentical() delegieren an die Zusammenfassung | — | — | Schreibgeschützt |
DiffSummary | Zählungen je Kategorie plus Seitenzählungen | hasDifferences() und totalChanges() über Text-, Bild- und Metadatenzählungen | — | — | Schreibgeschützt |
DiffRegion | DiffType $type, string $text, int $pageIndex, int $lineIndex, ?string $counterpartText = null | Eine Änderung auf Zeilenebene | — | — | $counterpartText bleibt in der ausgelieferten Engine null |
ParagraphDiff | Typ, Text, Seitenindex, Start-/Endzeile, Regionen | Aufeinanderfolgende gleichtypige Regionen auf einer Seite; lineCount() | — | — | Schreibgeschützt |
ImageDiff | Typ, Seitenindex, Quell-Hash, Ziel-Hash, Objekt-ID | Ein Eintrag einer Bildänderung | — | — | Hashes sind auf der fehlenden Seite leere Zeichenketten |
MetadataChange | string $field, ?string $sourceValue, ?string $targetValue | Eine Feldänderung; isAdded(), isRemoved(), isModified() | — | — | null bedeutet, dass das Feld fehlt |
TextBlock | Text, x, y, Schriftname, Schriftgröße, Zeilenindex | Ein extrahierter Textlauf mit ungefährer Position | — | — | Schreibgeschützt |
DiffType | Enum: Added, Removed, Modified, Unchanged | Zeichenkettengestützte Änderungsklassifikation für Text | — | — | Siehe den Hinweis zu Modified im Verhaltensvertrag |
ImageDiffType | Enum: Added, Removed, Modified, Unchanged | Zeichenkettengestützte Änderungsklassifikation für Bilder | — | — | — |
Einstiegspunkt-Signaturen
Abschnitt betitelt „Einstiegspunkt-Signaturen“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,): arrayVerhaltensvertrag
Abschnitt betitelt „Verhaltensvertrag“Seitenausrichtung und Zeilendiff
Abschnitt betitelt „Seitenausrichtung und Zeilendiff“PdfDiffer::compare() extrahiert Text je Seite und vergleicht dann Seite i der Quelle mit Seite i des Ziels. Weichen die Seitenzahlen ab, wird die fehlende Seite für die überzähligen Seiten als leerer Text behandelt. Innerhalb jedes Seitenpaars wird Text an Zeilenumbrüchen getrennt und je Seite ein Myers-Zeilendiff ausgeführt. Die Engine gibt Added-, Removed- und Unchanged-Regionen aus. Eine geänderte Zeile erscheint als eine Removed- plus eine Added-Region; die ausgelieferte Engine gibt niemals Modified-Textregionen aus. Der Modified-Fall und der Bucket DiffResult::$modified dienen aufruferseitig konstruierten Ergebnissen, da der DiffResult-Konstruktor öffentlich ist. totalChanges() zählt hinzugefügte, entfernte und geänderte Regionen; unveränderte Regionen werden ausgeschlossen.
Extraktionspfade
Abschnitt betitelt „Extraktionspfade“Die Extraktion hat zwei Pfade:
- Optionaler Artisan-Reader vorhanden. Wenn die optionale Klasse
NextPDF\Parser\PdfReaderinstalliert ist, werden Seiten-Content-Streams über sie gelesen, um seitengenauen Text zu erhalten. Die Seitenzahl aus dem Trailer steuert die Schleife. Eine Seite, die nicht gelesen werden kann, trägt leeren Text bei, statt den Vergleich abzubrechen. - Fallback. Ein begrenzter Scanner auf Byte-Ebene lokalisiert
stream/endstream-Paare perstrpos, dekomprimiert FlateDecode-Daten mit einer harten Ausgabeobergrenze von 50 MB und kehrt einen PNG-Prädiktor um, wenn das Stream-Dictionary über/DecodeParmsgemäß ISO 32000-2:2020 §7.4.4.4 einen anfordert. Ein fehlerhafter oder nicht unterstützter Prädiktor lässt die dekodierten Bytes unverändert. Der Fallback verkettet den gesamten wiederhergestellten Text in einen einzigen Seiten-Bucket, sodass die Ausrichtung auf Seitenebene nur auf dem Reader-Pfad seitengenau ist.
Beide Pfade parsen die textausgebenden §9.4-Operatoren Tj, TJ und '. Die Zustandsmaschine verfolgt BT/ET, Tm (nur Ursprung), Td/TD, T* und Tf.
Strukturierter Vergleich
Abschnitt betitelt „Strukturierter Vergleich“StructuredDiffer::compare() führt das Textdiff aus, gruppiert aufeinanderfolgende gleichtypige Regionen auf derselben Seite zu Absätzen (unveränderte Läufe eingeschlossen), führt dann den Bild- und Metadatenvergleich aus und setzt eine DiffSummary zusammen. Die Absatzzählungen der Zusammenfassung erfassen nur hinzugefügte, entfernte und geänderte Absätze.
Der Bildvergleich zählt PDF-Objekte strukturell auf. Die Ausdehnung eines Stream-Körpers wird durch seinen /Length-Eintrag gemäß §7.3.8.2 bestimmt, sodass Binärbytes, die lediglich Objektsyntax ähneln, niemals als Phantomobjekte erfasst werden. Komprimierte Objekt-Streams (/Type /ObjStm) werden gemäß §7.5.7 dekodiert, damit darin verschachtelte Bild-XObjects sichtbar sind. Jedes erkannte Bild wird mit der nicht kryptografischen Funktion xxh128 inhaltsgehasht; die Identität ist das Paar aus Seiten-Bucket und Objektnummer. Bilder ohne zugehörige Seite in Stream-Reihenfolge werden Seite 0 zugeordnet.
Der Metadatenvergleich löst das reale /Info-Dictionary nach Möglichkeit über den Trailer auf, sodass ein Köderfeld-Token innerhalb eines Content-Streams nicht mit Dokumentmetadaten verwechselt wird. Feldwerte werden als PDF-Zeichenketten dekodiert: die literale Form gemäß §7.3.4.2 und die hexadezimale Form gemäß §7.3.4.3. Ohne einen auflösbaren Trailer greift die Suche auf die gesamte Eingabe zurück. Daten werden als dekodierte Zeichenketten verglichen, nicht als geparste Zeitstempel.
Berichtsausgabe
Abschnitt betitelt „Berichtsausgabe“DiffFormatter::toJson() gibt formatiertes JSON zurück und kodiert mit JSON_THROW_ON_ERROR, sodass ein Kodierungsfehler eine JsonException auslöst, statt false zurückzugeben. toHtml() gibt ein <div class="nextpdf-diff">-Fragment zurück; Absatztext und Metadatenwerte durchlaufen die HTML-Entitätsmaskierung. Es gibt keine visuelle Nebeneinander-Redline-PDF-Ausgabe. Für identische Eingaben sind Regionen und formatierte Ausgabe deterministisch.
Randfälle & Fehlermodi
Abschnitt betitelt „Randfälle & Fehlermodi“- Die Seitenausrichtung ist positionsbasiert. Eine einzelne eingefügte oder gelöschte Seite verschiebt die Ausrichtung für alle nachfolgenden Seiten und bläht die nachgelagerten Änderungszählungen auf.
- Auf dem Fallback-Extraktionspfad landet der gesamte Text auf Seitenindex 0. Der Vergleich eines reader-extrahierten Dokuments mit Erwartungen aus dem Fallback-Pfad ergibt eine abweichende Seitenzuordnung.
- Ein Quell- oder Zielpuffer, der nicht mit
%PDFbeginnt, scheitert vor jedem Vergleich mitInvalidArgumentException. - Mehr als 10.000 kombinierte Zeilen in einem Seitenpaar scheitern mit
OverflowException(Zeilenanzahlgrenze). - Zwei Seitentexte, die zu wenige Zeilen teilen, scheitern mit
OverflowException, sobald die Myers-Editierdistanz die speichergebundene Obergrenze überschreitet. Legitime Revisionen teilen die meisten Zeilen und bleiben unberührt; adversarielle Eingaben mit geringer Gemeinsamkeit lösen die Grenze aus. - Eine dekomprimierte Fallback-Stream-Ausgabe größer als 50 MB scheitert mit
OverflowException(Dekompressionsbombengrenze). Der Scanner verwendetstrpos, keine unbegrenzten regulären Ausdrücke, sodass präparierte Eingaben kein katastrophales Backtracking auslösen können. - Der textausgebende Operator
"wird tokenisiert, erzeugt in 3.1.0 aber keinen Textblock; Text, der nur über"gezeigt wird, nimmt am Diff nicht teil. - Gescannte, reine Bild-PDFs erzeugen wenig oder kein Textdiff. Es läuft keine OCR.
- Die Erkennung von Bildänderungen ist strukturell, nicht perzeptuell. Sie rastert keine Seiten und meldet ein mit identischen Pixeln neu kodiertes Bild als geändert, wenn sich seine Bytes unterscheiden.
- Ein Bild, dessen Seiten-Bucket oder Objektnummer sich zwischen Revisionen ändert, wird als Paar aus entfernt und hinzugefügt gemeldet, nicht als geändert.
- Objekt-Streams, die mit anderen Filtern als FlateDecode komprimiert sind, werden fail-closed übersprungen; ihre Mitgliedsbilder werden nicht verglichen.
- In diesem Modul findet keine kryptografische Operation statt, daher existiert kein FIPS-modus-spezifisches Verhalten. Der Bild-Hash dient ausschließlich der Änderungserkennung und trägt kein Integritäts- oder Beweisgewicht.
Konformität
Abschnitt betitelt „Konformität“| Aussage | Standard | Klausel |
|---|---|---|
Die textausgebenden Operatoren Tj und TJ werden für die Extraktion geparst | ISO 32000-2:2020 | §9.4 |
Fallback-Stream-Daten beginnen nach dem CRLF oder LF, das dem stream-Schlüsselwort folgt | ISO 32000-2:2020 | §7.3.8.1 |
Die Stream-Ausdehnungen beim Bildscan werden durch den Dictionary-Eintrag /Length bestimmt | ISO 32000-2:2020 | §7.3.8.2 |
Objekt-Stream-Mitglieder werden über die /N-Paartabelle und den /First-Offset lokalisiert | ISO 32000-2:2020 | §7.5.7 |
Die PNG-Prädiktorumkehr folgt dem /DecodeParms-Parameter Predictor | ISO 32000-2:2020 | §7.4.4.4 |
| Metadatenwerte dekodieren die literale und die hexadezimale Zeichenkettenform | ISO 32000-2:2020 | §7.3.4.2, §7.3.4.3 |
| Visuelle Nebeneinander-Redline-PDF-Ausgabe | — | Nicht unterstützt (nur JSON/HTML) |
Alle Klauseln sind paraphrasiert; NextPDF gibt normativen Text nicht wieder. Dies sind Fähigkeitsaussagen, keine Zertifizierungen; NextPDF hält keine Zertifizierung und erteilt keine. Die Textwiederherstellung rekonstruiert Zeilentext aus textausgebenden Operatoren. Sie führt die vollständige §9.4-Textzustandsmaschine nicht aus, sodass das Diff auf Inhaltsebene liegt, nicht auf Geometrieebene.
Entwicklungshinweise
Abschnitt betitelt „Entwicklungshinweise“- Verfügbarkeit innerhalb des Pro-Pakets:
PdfDiffer,DiffEngine,TextExtractorund ihre Wertobjekte seit 1.8.0;StructuredDiffer,DiffFormatter,ImageDiffer,MetadataDifferund ihre seit 2.2.0. Alle sind innextpdf/pro3.1.0 aktuell. - Bevorzugen Sie
PdfDiffer::compareTexts(), wenn Seitentext bereits vorliegt; es überspringt die Extraktion und ihre Fehlermodi vollständig. - Der optionale Artisan-Reader verbessert die Extraktionsgenauigkeit und die Seitenzuordnung. Er wird zur Laufzeit erkannt und ist niemals erforderlich.
- Fangen Sie
OverflowExceptionbeim Diffen nicht vertrauenswürdiger Eingaben; die Grenzen sind bewusste Fail-closed-Zurückweisungen, keine transienten Fehler. DiffFormatter::toHtml()gibt Klassennamen (diff-added,diff-removed,diff-modified,diff-unchanged) aus, aber kein Stylesheet; liefern Sie Ihr eigenes CSS.- Konstruieren Sie
StructuredDifferin Tests mit Stub-Differn, um den Textpfad vom Bild- und Metadatenscannen zu isolieren.
Veröffentlichungsgrenze
Abschnitt betitelt „Veröffentlichungsgrenze“Diese Seite dokumentiert ausschließlich extern beobachtbares Verhalten und die unterstützte öffentliche API-Oberfläche. Interne Namensraumpfade, Hilfsklassen, Mechanismustabellen, Runbook-Dateinamen und Ticket-Präfixe liegen außerhalb des Umfangs.
Siehe auch
Abschnitt betitelt „Siehe auch“- Diff (Fähigkeit) — Installation, Schnellstart und Produktionsbeispiele.
- Converter — Tiefenreferenz
- Filter — Tiefenreferenz