Pro édition
Diff — Référence approfondie
Cette page est la référence de niveau contractuel du module de diff NextPDF Pro, NextPDF\Pro\Diff. Le module compare deux documents PDF et signale les changements de texte, d’image et de métadonnées. PdfDiffer produit un diff de lignes Myers aligné sur les pages. StructuredDiffer ajoute le regroupement en paragraphes, la comparaison d’images et la comparaison de métadonnées. DiffFormatter sérialise le résultat structuré en JSON ou en fragment HTML. Cette page énonce l’API publique, le contrat de comportement observable, les limites de ressources et les modes d’échec. La configuration orientée tâches et les exemples se trouvent sur la page de la fonctionnalité Diff.
Disponibilité et licence
Section intitulée « Disponibilité et licence »Cette fonctionnalité est fournie dans NextPDF Pro (nextpdf/pro) et s’active avec une enveloppe de licence de niveau Pro. Un déploiement dépourvu de ce droit ne charge pas les classes de la fonctionnalité. Compare les éditions et obtiens une licence.
Aucun indicateur de fonctionnalité à l’exécution ne conditionne ce module. Les classes de diff sont utilisables dès lors que nextpdf/pro est installé et sous licence.
Surface d’API publique
Section intitulée « Surface d’API publique »| Symbole | Paramètres | Comportement par défaut | Retourne | Lève ou échoue avec | Notes |
|---|---|---|---|---|---|
PdfDiffer::compare() | string $sourcePdf, string $targetPdf | Extrait le texte page par page, puis compare la page i de la source à la page i de la cible | DiffResult | InvalidArgumentException lorsqu’un tampon n’a pas l’en-tête %PDF ou que le lecteur optionnel échoue à l’analyse ; OverflowException sur une limite de ressources | Point d’entrée statique |
PdfDiffer::compareTexts() | array $sourcePages, array $targetPages (list<string> chacun) | Compare des textes de pages pré-extraits, en contournant l’extraction | DiffResult | OverflowException sur une limite de ressources | Statique ; à utiliser lorsque le texte est déjà disponible |
PdfDiffer::extractText() | string $contentStream | Analyse les opérateurs d’affichage de texte d’un flux de contenu brut | string | — (tolérant aux fautes ; une entrée non analysable renvoie une chaîne vide) | Statique |
StructuredDiffer::__construct() | ?ImageDiffer $imageDiffer = null, ?MetadataDiffer $metadataDiffer = null | Des arguments null construisent les différenciateurs par défaut | — | — | Injection par constructeur pour les tests |
StructuredDiffer::compare() | string $sourcePdf, string $targetPdf | Exécute la comparaison de texte, de paragraphes, d’images et de métadonnées, puis construit un résumé | StructuredDiffResult | Propage InvalidArgumentException et OverflowException depuis le chemin de texte | Orchestrateur de l’ensemble du module |
DiffFormatter::toJson() | StructuredDiffResult $result | Document JSON formaté | string | JsonException en cas d’échec de l’encodage | — |
DiffFormatter::toHtml() | StructuredDiffResult $result | Fragment HTML avec des sections de résumé, de paragraphes et de métadonnées ; les valeurs de texte sont échappées en entités | string | — | Fragment uniquement, pas un document complet |
DiffFormatter::toArray() | StructuredDiffResult $result | Tableau de sérialisation sous-jacent à toJson() | array<string, mixed> | — | Clés snake_case stables |
ImageDiffer::diff() | string $sourcePdf, string $targetPdf | Hache les XObjects d’image et signale les images ajoutées, supprimées et modifiées | list<ImageDiff> | — (les structures non décodables sont ignorées en mode fail-closed) | L’identité est le compartiment de page plus le numéro d’objet |
MetadataDiffer::diff() | string $sourcePdf, string $targetPdf | Compare huit champs /Info (Title, Author, Subject, Keywords, Creator, Producer, CreationDate, ModDate) | list<MetadataChange> | — (ne lève jamais d’exception sur une entrée non conforme) | Valeurs comparées en tant que chaînes décodées |
DiffEngine::diff() | array $sourceLines, array $targetLines, int $pageIndex = 0, int $maxLines = 10000 | Diff de lignes Myers sur deux listes de lignes | list<DiffRegion> | OverflowException lorsque les lignes combinées dépassent $maxLines ou que la distance d’édition dépasse le plafond lié à la mémoire | Statique ; le producteur de régions pour tous les chemins de texte |
TextExtractor::fromContentStream() | string $contentStream | Tokenise le flux et exécute la machine à états de texte | list<TextBlock> | — | Statique |
TextExtractor::fromOperations() | array $operations (list<ContentStreamOp>) | Exécute la machine à états de texte sur des opérations pré-analysées | list<TextBlock> | — | Statique |
ContentStreamParser::parse() | le constructeur prend string $data | Tokenise les opérateurs et opérandes ; ignore les dictionnaires et commentaires ; tolérant aux fautes | list<ContentStreamOp> | — | Les octets non reconnus sont ignorés, jamais fatals |
ContentStreamOp | string $operator, list<mixed> $operands | Objet-valeur d’opération en lecture seule ; isTextOp() classe les opérateurs liés au texte | — | — | — |
DiffResult | list<DiffRegion> $regions, int $sourcePagesCount, int $targetPagesCount | Répartit les régions dans $added, $removed, $modified ; expose isIdentical(), hasDifferences(), totalChanges() | — | — | En lecture seule ; les régions Unchanged restent uniquement dans $regions |
StructuredDiffResult | diff de texte, paragraphes, images, changements de métadonnées, résumé | Résultat agrégé ; hasDifferences(), isIdentical() délèguent au résumé | — | — | En lecture seule |
DiffSummary | décomptes par catégorie plus décomptes de pages | hasDifferences() et totalChanges() sur les décomptes de texte, d’image et de métadonnées | — | — | En lecture seule |
DiffRegion | DiffType $type, string $text, int $pageIndex, int $lineIndex, ?string $counterpartText = null | Un changement au niveau de la ligne | — | — | $counterpartText reste null sur le moteur livré |
ParagraphDiff | type, texte, index de page, ligne de début/fin, régions | Régions consécutives de même type sur une page ; lineCount() | — | — | En lecture seule |
ImageDiff | type, index de page, hachage source, hachage cible, id d’objet | Une entrée de changement d’image | — | — | Les hachages sont des chaînes vides du côté absent |
MetadataChange | string $field, ?string $sourceValue, ?string $targetValue | Un changement de champ ; isAdded(), isRemoved(), isModified() | — | — | null signifie que le champ est absent |
TextBlock | texte, x, y, nom de police, taille de police, index de ligne | Une séquence de texte extraite avec une position approximative | — | — | En lecture seule |
DiffType | enum : Added, Removed, Modified, Unchanged | Classification de changement adossée à une chaîne pour le texte | — | — | Voir la note Modified dans le contrat de comportement |
ImageDiffType | enum : Added, Removed, Modified, Unchanged | Classification de changement adossée à une chaîne pour les images | — | — | — |
Signatures des points d’entrée
Section intitulée « Signatures des points d’entrée »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,): arrayContrat de comportement
Section intitulée « Contrat de comportement »Alignement des pages et diff de lignes
Section intitulée « Alignement des pages et diff de lignes »PdfDiffer::compare() extrait le texte page par page, puis compare la page i de la source à la page i de la cible. Lorsque les nombres de pages diffèrent, le côté manquant est traité comme du texte vide pour les pages excédentaires. Au sein de chaque paire de pages, le texte est découpé aux sauts de ligne et un diff de lignes Myers s’exécute par page. Le moteur émet des régions Added, Removed et Unchanged. Une ligne modifiée apparaît comme une région Removed plus une région Added ; le moteur livré n’émet jamais de régions de texte Modified. Le cas Modified et le compartiment DiffResult::$modified servent les résultats construits par l’appelant, puisque le constructeur DiffResult est public. totalChanges() compte les régions ajoutées, supprimées et modifiées ; les régions inchangées sont exclues.
Chemins d’extraction
Section intitulée « Chemins d’extraction »L’extraction comporte deux chemins :
- Lecteur Artisan optionnel présent. Lorsque la classe optionnelle
NextPDF\Parser\PdfReaderest installée, les flux de contenu de page sont lus à travers elle pour un texte exact à la page. Le nombre de pages du trailer pilote la boucle. Une page qui échoue à être lue contribue avec du texte vide au lieu d’interrompre la comparaison. - Repli. Un scanner borné au niveau des octets localise les paires
stream/endstreamparstrpos, décompresse les données FlateDecode avec un plafond de sortie strict de 50 Mo, et applique le filtre inverse d’un prédicteur PNG lorsque le dictionnaire de flux en demande un via/DecodeParmsconformément à ISO 32000-2:2020 §7.4.4.4. Un prédicteur malformé ou non pris en charge laisse les octets décodés inchangés. Le repli concatène tout le texte récupéré dans un unique compartiment de page, de sorte que l’alignement au niveau des pages n’est exact à la page que sur le chemin du lecteur.
Les deux chemins analysent les opérateurs d’affichage de texte §9.4 Tj, TJ et '. La machine à états suit BT/ET, Tm (origine uniquement), Td/TD, T* et Tf.
Comparaison structurée
Section intitulée « Comparaison structurée »StructuredDiffer::compare() exécute le diff de texte, regroupe en paragraphes les régions consécutives de même type sur la même page (séquences inchangées incluses), puis exécute la comparaison d’images et de métadonnées et assemble un DiffSummary. Les décomptes de paragraphes du résumé ne couvrent que les paragraphes ajoutés, supprimés et modifiés.
La comparaison d’images énumère les objets PDF de façon structurelle. L’étendue du corps d’un flux est régie par son entrée /Length conformément à §7.3.8.2, de sorte que des octets binaires qui ne font que ressembler à la syntaxe d’objet ne sont jamais enregistrés comme des objets fantômes. Les flux d’objets compressés (/Type /ObjStm) sont décodés conformément à §7.5.7 afin que les XObjects d’image imbriqués à l’intérieur soient visibles. Chaque image détectée est hachée par son contenu avec la fonction non cryptographique xxh128 ; l’identité est la paire compartiment de page et numéro d’objet. Les images sans page propriétaire dans l’ordre du flux sont attribuées à la page 0.
La comparaison de métadonnées résout le véritable dictionnaire /Info via le trailer lorsque c’est possible, de sorte qu’un jeton de champ leurre à l’intérieur d’un flux de contenu n’est pas confondu avec les métadonnées du document. Les valeurs de champ sont décodées en tant que chaînes PDF : la forme littérale conformément à §7.3.4.2 et la forme hexadécimale conformément à §7.3.4.3. Sans trailer résoluble, la recherche se rabat sur l’entrée entière. Les dates sont comparées en tant que chaînes décodées, non en tant qu’horodatages analysés.
Sortie de rapport
Section intitulée « Sortie de rapport »DiffFormatter::toJson() renvoie du JSON formaté et encode avec JSON_THROW_ON_ERROR, de sorte qu’un échec d’encodage lève JsonException au lieu de renvoyer false. toHtml() renvoie un fragment <div class="nextpdf-diff"> ; le texte des paragraphes et les valeurs de métadonnées passent par l’échappement des entités HTML. Il n’y a pas de sortie PDF de correction visuelle côte à côte. Pour des entrées identiques, les régions et la sortie formatée sont déterministes.
Cas limites et modes d’échec
Section intitulée « Cas limites et modes d’échec »- L’alignement des pages est positionnel. Une seule page insérée ou supprimée décale l’alignement de toutes les pages suivantes et gonfle les décomptes de changements en aval.
- Sur le chemin d’extraction de repli, tout le texte atterrit à l’index de page 0. Comparer un document extrait par le lecteur avec des attentes issues du chemin de repli produit une attribution de page différente.
- Un tampon source ou cible ne commençant pas par
%PDFéchoue avecInvalidArgumentExceptionavant toute comparaison. - Plus de 10 000 lignes combinées dans une paire de pages échoue avec
OverflowException(limite de nombre de lignes). - Deux textes de page partageant trop peu de lignes échouent avec
OverflowExceptiondès que la distance d’édition Myers dépasse le plafond lié à la mémoire. Les révisions légitimes partagent la plupart des lignes et restent non affectées ; les entrées adverses à faible communauté déclenchent la limite. - Une sortie de flux de repli décompressée supérieure à 50 Mo échoue avec
OverflowException(limite anti-bombe de décompression). Le scanner utilisestrpos, non une regex non bornée, de sorte qu’une entrée fabriquée ne peut pas déclencher de retour arrière catastrophique. - L’opérateur d’affichage de texte
"est tokenisé mais ne produit aucun bloc de texte dans 3.1.0 ; le texte affiché uniquement via"ne participe pas au diff. - Les PDF numérisés, uniquement composés d’images, produisent peu ou pas de diff de texte. Aucun OCR ne s’exécute.
- La détection de changement d’image est structurelle, non perceptuelle. Elle ne rastérise pas les pages, et une image ré-encodée avec des pixels identiques est signalée comme modifiée lorsque ses octets diffèrent.
- Une image dont le compartiment de page ou le numéro d’objet change entre les révisions est signalée comme une paire supprimée-plus-ajoutée, non comme modifiée.
- Les flux d’objets compressés avec des filtres autres que FlateDecode sont ignorés en mode fail-closed ; leurs images membres ne sont pas comparées.
- Aucune opération cryptographique n’a lieu dans ce module, il n’existe donc aucun comportement spécifique au mode FIPS. Le hachage d’image sert uniquement à la détection de changement et ne porte aucun poids d’intégrité ou probatoire.
Conformité
Section intitulée « Conformité »| Assertion | Norme | Clause |
|---|---|---|
Les opérateurs d’affichage de texte Tj et TJ sont analysés pour l’extraction | ISO 32000-2:2020 | §9.4 |
Les données de flux de repli commencent après le CRLF ou LF qui suit le mot-clé stream | ISO 32000-2:2020 | §7.3.8.1 |
Les étendues de flux du balayage d’images sont régies par l’entrée /Length du dictionnaire | ISO 32000-2:2020 | §7.3.8.2 |
Les membres du flux d’objets sont localisés via la table de paires /N et le décalage /First | ISO 32000-2:2020 | §7.5.7 |
L’inversion du prédicteur PNG suit le paramètre Predictor de /DecodeParms | ISO 32000-2:2020 | §7.4.4.4 |
| Les valeurs de métadonnées décodent les formes de chaîne littérale et hexadécimale | ISO 32000-2:2020 | §7.3.4.2, §7.3.4.3 |
| Sortie PDF de correction visuelle côte à côte | — | Non pris en charge (JSON/HTML uniquement) |
Toutes les clauses sont paraphrasées ; NextPDF ne reproduit pas le texte normatif. Ce sont des déclarations de capacité, non des certifications ; NextPDF ne détient aucune certification et n’en accorde aucune. La récupération de texte reconstruit le texte des lignes à partir des opérateurs d’affichage de texte. Elle n’exécute pas la machine à états de texte complète §9.4, de sorte que le diff est au niveau du contenu, non au niveau de la géométrie.
Notes de développement
Section intitulée « Notes de développement »- Disponibilité au sein du package Pro :
PdfDiffer,DiffEngine,TextExtractoret leurs objets-valeurs depuis 1.8.0 ;StructuredDiffer,DiffFormatter,ImageDiffer,MetadataDifferet les leurs depuis 2.2.0. Tous sont à jour dansnextpdf/pro3.1.0. - Préfère
PdfDiffer::compareTexts()lorsque le texte de page est déjà disponible ; il contourne entièrement l’extraction et ses modes d’échec. - Le lecteur Artisan optionnel améliore la précision d’extraction et l’attribution de page. Il est détecté à l’exécution et n’est jamais requis.
- Intercepte
OverflowExceptionlors du diff d’entrées non fiables ; les limites sont des rejets fail-closed délibérés, non des erreurs transitoires. DiffFormatter::toHtml()émet des noms de classe (diff-added,diff-removed,diff-modified,diff-unchanged) mais pas de feuille de style ; fournis ta propre CSS.- Construis
StructuredDifferavec des différenciateurs factices dans les tests pour isoler le chemin de texte du balayage d’images et de métadonnées.
Périmètre de publication
Section intitulée « Périmètre de publication »Cette page documente uniquement le comportement observable de l’extérieur et la surface d’API publique prise en charge. Les chemins de namespace internes, les classes utilitaires, les tables de mécanismes, les noms de fichiers de runbook et les préfixes de tickets sont hors périmètre.
Voir aussi
Section intitulée « Voir aussi »- Diff (fonctionnalité) — installation, démarrage rapide et exemples de production.
- Converter — Référence approfondie
- Filter — Référence approfondie