Aller au contenu
getnextpdf.com

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.

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.

SymboleParamètresComportement par défautRetourneLève ou échoue avecNotes
PdfDiffer::compare()string $sourcePdf, string $targetPdfExtrait le texte page par page, puis compare la page i de la source à la page i de la cibleDiffResultInvalidArgumentException lorsqu’un tampon n’a pas l’en-tête %PDF ou que le lecteur optionnel échoue à l’analyse ; OverflowException sur une limite de ressourcesPoint d’entrée statique
PdfDiffer::compareTexts()array $sourcePages, array $targetPages (list<string> chacun)Compare des textes de pages pré-extraits, en contournant l’extractionDiffResultOverflowException sur une limite de ressourcesStatique ; à utiliser lorsque le texte est déjà disponible
PdfDiffer::extractText()string $contentStreamAnalyse les opérateurs d’affichage de texte d’un flux de contenu brutstring— (tolérant aux fautes ; une entrée non analysable renvoie une chaîne vide)Statique
StructuredDiffer::__construct()?ImageDiffer $imageDiffer = null, ?MetadataDiffer $metadataDiffer = nullDes arguments null construisent les différenciateurs par défautInjection par constructeur pour les tests
StructuredDiffer::compare()string $sourcePdf, string $targetPdfExécute la comparaison de texte, de paragraphes, d’images et de métadonnées, puis construit un résuméStructuredDiffResultPropage InvalidArgumentException et OverflowException depuis le chemin de texteOrchestrateur de l’ensemble du module
DiffFormatter::toJson()StructuredDiffResult $resultDocument JSON formatéstringJsonException en cas d’échec de l’encodage
DiffFormatter::toHtml()StructuredDiffResult $resultFragment HTML avec des sections de résumé, de paragraphes et de métadonnées ; les valeurs de texte sont échappées en entitésstringFragment uniquement, pas un document complet
DiffFormatter::toArray()StructuredDiffResult $resultTableau de sérialisation sous-jacent à toJson()array<string, mixed>Clés snake_case stables
ImageDiffer::diff()string $sourcePdf, string $targetPdfHache les XObjects d’image et signale les images ajoutées, supprimées et modifiéeslist<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 $targetPdfCompare 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 = 10000Diff de lignes Myers sur deux listes de ligneslist<DiffRegion>OverflowException lorsque les lignes combinées dépassent $maxLines ou que la distance d’édition dépasse le plafond lié à la mémoireStatique ; le producteur de régions pour tous les chemins de texte
TextExtractor::fromContentStream()string $contentStreamTokenise le flux et exécute la machine à états de textelist<TextBlock>Statique
TextExtractor::fromOperations()array $operations (list<ContentStreamOp>)Exécute la machine à états de texte sur des opérations pré-analyséeslist<TextBlock>Statique
ContentStreamParser::parse()le constructeur prend string $dataTokenise les opérateurs et opérandes ; ignore les dictionnaires et commentaires ; tolérant aux fauteslist<ContentStreamOp>Les octets non reconnus sont ignorés, jamais fatals
ContentStreamOpstring $operator, list<mixed> $operandsObjet-valeur d’opération en lecture seule ; isTextOp() classe les opérateurs liés au texte
DiffResultlist<DiffRegion> $regions, int $sourcePagesCount, int $targetPagesCountRépartit les régions dans $added, $removed, $modified ; expose isIdentical(), hasDifferences(), totalChanges()En lecture seule ; les régions Unchanged restent uniquement dans $regions
StructuredDiffResultdiff 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
DiffSummarydécomptes par catégorie plus décomptes de pageshasDifferences() et totalChanges() sur les décomptes de texte, d’image et de métadonnéesEn lecture seule
DiffRegionDiffType $type, string $text, int $pageIndex, int $lineIndex, ?string $counterpartText = nullUn changement au niveau de la ligne$counterpartText reste null sur le moteur livré
ParagraphDifftype, texte, index de page, ligne de début/fin, régionsRégions consécutives de même type sur une page ; lineCount()En lecture seule
ImageDifftype, index de page, hachage source, hachage cible, id d’objetUne entrée de changement d’imageLes hachages sont des chaînes vides du côté absent
MetadataChangestring $field, ?string $sourceValue, ?string $targetValueUn changement de champ ; isAdded(), isRemoved(), isModified()null signifie que le champ est absent
TextBlocktexte, x, y, nom de police, taille de police, index de ligneUne séquence de texte extraite avec une position approximativeEn lecture seule
DiffTypeenum : Added, Removed, Modified, UnchangedClassification de changement adossée à une chaîne pour le texteVoir la note Modified dans le contrat de comportement
ImageDiffTypeenum : Added, Removed, Modified, UnchangedClassification de changement adossée à une chaîne pour les images
public static function compare(string $sourcePdf, string $targetPdf): DiffResult
public static function compareTexts(array $sourcePages, array $targetPages): DiffResult
public static function extractText(string $contentStream): string
public function __construct(
?ImageDiffer $imageDiffer = null,
?MetadataDiffer $metadataDiffer = null,
)
public function compare(string $sourcePdf, string $targetPdf): StructuredDiffResult
public function toJson(StructuredDiffResult $result): string
public function toHtml(StructuredDiffResult $result): string
public function toArray(StructuredDiffResult $result): array
public static function diff(
array $sourceLines,
array $targetLines,
int $pageIndex = 0,
int $maxLines = self::MAX_DIFF_LINES,
): array

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.

L’extraction comporte deux chemins :

  • Lecteur Artisan optionnel présent. Lorsque la classe optionnelle NextPDF\Parser\PdfReader est 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/endstream par strpos, 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 /DecodeParms conformé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.

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.

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.

  • 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 avec InvalidArgumentException avant 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 OverflowException dè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 utilise strpos, 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.
AssertionNormeClause
Les opérateurs d’affichage de texte Tj et TJ sont analysés pour l’extractionISO 32000-2:2020§9.4
Les données de flux de repli commencent après le CRLF ou LF qui suit le mot-clé streamISO 32000-2:2020§7.3.8.1
Les étendues de flux du balayage d’images sont régies par l’entrée /Length du dictionnaireISO 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 /FirstISO 32000-2:2020§7.5.7
L’inversion du prédicteur PNG suit le paramètre Predictor de /DecodeParmsISO 32000-2:2020§7.4.4.4
Les valeurs de métadonnées décodent les formes de chaîne littérale et hexadécimaleISO 32000-2:2020§7.3.4.2, §7.3.4.3
Sortie PDF de correction visuelle côte à côteNon 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.

  • Disponibilité au sein du package Pro : PdfDiffer, DiffEngine, TextExtractor et leurs objets-valeurs depuis 1.8.0 ; StructuredDiffer, DiffFormatter, ImageDiffer, MetadataDiffer et les leurs depuis 2.2.0. Tous sont à jour dans nextpdf/pro 3.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 OverflowException lors 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 StructuredDiffer avec des différenciateurs factices dans les tests pour isoler le chemin de texte du balayage d’images et de métadonnées.

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.