Ir al contenido
getnextpdf.com

Pro edición

Diff — Referencia detallada

Esta página es la referencia a nivel de contrato del módulo de diff de NextPDF Pro, NextPDF\Pro\Diff. El módulo compara dos documentos PDF e informa de los cambios de texto, imágenes y metadatos. PdfDiffer produce un diff de líneas de Myers alineado por páginas. StructuredDiffer añade agrupación de párrafos, comparación de imágenes y comparación de metadatos. DiffFormatter serializa el resultado estructurado a JSON o a un fragmento HTML. Esta página expone la API pública, el contrato de comportamiento observable, los límites de recursos y los modos de fallo. La configuración orientada a tareas y los ejemplos están en la página de la capacidad Diff.

Esta capacidad se distribuye en NextPDF Pro (nextpdf/pro) y se activa con un sobre de licencia de nivel Pro. Un despliegue sin ese derecho no carga las clases de la capacidad. Comparar ediciones y obtener una licencia.

Ningún indicador de capacidad en tiempo de ejecución restringe este módulo. Las clases de diff son utilizables siempre que nextpdf/pro esté instalado y licenciado.

SímboloParámetrosComportamiento por defectoDevuelveLanza o falla conNotas
PdfDiffer::compare()string $sourcePdf, string $targetPdfExtrae el texto por página y luego compara la página i del origen con la página i del destinoDiffResultInvalidArgumentException cuando un búfer carece de la cabecera %PDF o el lector opcional no puede analizarlo; OverflowException al alcanzar un límite de recursosPunto de entrada estático
PdfDiffer::compareTexts()array $sourcePages, array $targetPages (list<string> cada uno)Compara textos de página ya extraídos, omitiendo la extracciónDiffResultOverflowException al alcanzar un límite de recursosEstático; usar cuando el texto ya está disponible
PdfDiffer::extractText()string $contentStreamAnaliza los operadores de presentación de texto de un flujo de contenido en brutostring— (tolerante a fallos; una entrada no analizable produce una cadena vacía)Estático
StructuredDiffer::__construct()?ImageDiffer $imageDiffer = null, ?MetadataDiffer $metadataDiffer = nullLos argumentos null construyen los diferenciadores por defectoInyección por constructor para pruebas
StructuredDiffer::compare()string $sourcePdf, string $targetPdfEjecuta la comparación de texto, párrafos, imágenes y metadatos y luego construye un resumenStructuredDiffResultPropaga InvalidArgumentException y OverflowException desde la ruta de textoOrquestador de todo el módulo
DiffFormatter::toJson()StructuredDiffResult $resultDocumento JSON con formato legiblestringJsonException cuando la codificación falla
DiffFormatter::toHtml()StructuredDiffResult $resultFragmento HTML con secciones de resumen, párrafos y metadatos; los valores de texto se escapan como entidadesstringSolo fragmento, no un documento completo
DiffFormatter::toArray()StructuredDiffResult $resultArray de serialización que respalda a toJson()array<string, mixed>Claves snake_case estables
ImageDiffer::diff()string $sourcePdf, string $targetPdfCalcula el hash de los XObjects de imagen e informa de imágenes añadidas, eliminadas y modificadaslist<ImageDiff>— (las estructuras no decodificables se omiten fail-closed)La identidad es el cubo de página más el número de objeto
MetadataDiffer::diff()string $sourcePdf, string $targetPdfCompara ocho campos de /Info (Title, Author, Subject, Keywords, Creator, Producer, CreationDate, ModDate)list<MetadataChange>— (nunca lanza excepción ante entradas no conformes)Los valores se comparan como cadenas decodificadas
DiffEngine::diff()array $sourceLines, array $targetLines, int $pageIndex = 0, int $maxLines = 10000Diff de líneas de Myers sobre dos listas de líneaslist<DiffRegion>OverflowException cuando el total de líneas supera $maxLines o la distancia de edición supera el tope acotado por memoriaEstático; el productor de regiones para todas las rutas de texto
TextExtractor::fromContentStream()string $contentStreamTokeniza el flujo y ejecuta la máquina de estados de textolist<TextBlock>Estático
TextExtractor::fromOperations()array $operations (list<ContentStreamOp>)Ejecuta la máquina de estados de texto sobre operaciones ya analizadaslist<TextBlock>Estático
ContentStreamParser::parse()el constructor toma string $dataTokeniza operadores y operandos; omite diccionarios y comentarios; tolerante a falloslist<ContentStreamOp>Los bytes no reconocidos se omiten, nunca son fatales
ContentStreamOpstring $operator, list<mixed> $operandsObjeto de valor de operación de solo lectura; isTextOp() clasifica los operadores relacionados con texto
DiffResultlist<DiffRegion> $regions, int $sourcePagesCount, int $targetPagesCountAgrupa las regiones en $added, $removed, $modified; expone isIdentical(), hasDifferences(), totalChanges()De solo lectura; las regiones Unchanged permanecen solo en $regions
StructuredDiffResultdiff de texto, párrafos, imágenes, cambios de metadatos, resumenResultado agregado; hasDifferences(), isIdentical() delegan en el resumenDe solo lectura
DiffSummaryrecuentos por categoría más recuentos de páginashasDifferences() y totalChanges() sobre los recuentos de texto, imágenes y metadatosDe solo lectura
DiffRegionDiffType $type, string $text, int $pageIndex, int $lineIndex, ?string $counterpartText = nullUn cambio a nivel de línea$counterpartText permanece null en el motor distribuido
ParagraphDifftipo, texto, índice de página, línea inicial/final, regionesRegiones consecutivas del mismo tipo en una página; lineCount()De solo lectura
ImageDifftipo, índice de página, hash de origen, hash de destino, id de objetoUna entrada de cambio de imagenLos hashes son cadenas vacías en el lado ausente
MetadataChangestring $field, ?string $sourceValue, ?string $targetValueUn cambio de campo; isAdded(), isRemoved(), isModified()null significa que el campo está ausente
TextBlocktexto, x, y, nombre de fuente, tamaño de fuente, índice de líneaUn fragmento de texto extraído con posición aproximadaDe solo lectura
DiffTypeenum: Added, Removed, Modified, UnchangedClasificación de cambios respaldada por cadena para textoVer la nota sobre Modified en el contrato de comportamiento
ImageDiffTypeenum: Added, Removed, Modified, UnchangedClasificación de cambios respaldada por cadena para imágenes
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() extrae el texto por página y luego compara la página i del origen con la página i del destino. Cuando los recuentos de páginas difieren, el lado que falta se trata como texto vacío en las páginas sobrantes. Dentro de cada par de páginas, el texto se divide por saltos de línea y se ejecuta un diff de líneas de Myers por página. El motor emite regiones Added, Removed y Unchanged. Una línea modificada aparece como una región Removed más una Added; el motor distribuido nunca emite regiones de texto Modified. El caso Modified y el cubo DiffResult::$modified sirven a los resultados construidos por el llamador, ya que el constructor de DiffResult es público. totalChanges() cuenta las regiones añadidas, eliminadas y modificadas; las regiones sin cambios quedan excluidas.

La extracción tiene dos rutas:

  • Lector Artisan opcional presente. Cuando la clase opcional NextPDF\Parser\PdfReader está instalada, los flujos de contenido de las páginas se leen a través de ella para obtener texto exacto por página. El recuento de páginas del trailer dirige el bucle. Una página que no puede leerse aporta texto vacío en lugar de abortar la comparación.
  • Alternativa. Un escáner acotado a nivel de bytes localiza los pares stream/endstream mediante strpos, infla los datos FlateDecode con un tope estricto de salida de 50 MB y aplica el filtrado inverso de un predictor PNG cuando el diccionario del flujo lo solicita a través de /DecodeParms según ISO 32000-2:2020 §7.4.4.4. Un predictor malformado o no soportado deja los bytes decodificados sin cambios. La alternativa concatena todo el texto recuperado en un único cubo de página, por lo que la alineación a nivel de página solo es exacta por página en la ruta del lector.

Ambas rutas analizan los operadores de presentación de texto de la §9.4: Tj, TJ y '. La máquina de estados rastrea BT/ET, Tm (solo origen), Td/TD, T* y Tf.

StructuredDiffer::compare() ejecuta el diff de texto, agrupa las regiones consecutivas del mismo tipo en la misma página en párrafos (incluyendo las series sin cambios), luego ejecuta la comparación de imágenes y metadatos y ensambla un DiffSummary. Los recuentos de párrafos del resumen abarcan únicamente los párrafos añadidos, eliminados y modificados.

La comparación de imágenes enumera los objetos PDF de forma estructural. La extensión del cuerpo de un flujo se rige por su entrada /Length según §7.3.8.2, de modo que los bytes binarios que solo se asemejan a la sintaxis de objetos nunca se registran como objetos fantasma. Los flujos de objetos comprimidos (/Type /ObjStm) se decodifican según §7.5.7 para que los XObjects de imagen anidados en ellos sean visibles. Cada imagen detectada se somete a un hash de contenido con la función no criptográfica xxh128; la identidad es el par de cubo de página y número de objeto. Las imágenes sin una página propietaria en el orden del flujo se atribuyen a la página 0.

La comparación de metadatos resuelve el diccionario /Info real a través del trailer cuando es posible, de modo que un token de campo señuelo dentro de un flujo de contenido no se confunde con metadatos del documento. Los valores de los campos se decodifican como cadenas PDF: la forma literal según §7.3.4.2 y la forma hexadecimal según §7.3.4.3. Sin un trailer resoluble, la búsqueda recurre a toda la entrada. Las fechas se comparan como cadenas decodificadas, no como marcas de tiempo analizadas.

DiffFormatter::toJson() devuelve JSON con formato legible y codifica con JSON_THROW_ON_ERROR, de modo que un fallo de codificación lanza JsonException en lugar de devolver false. toHtml() devuelve un fragmento <div class="nextpdf-diff">; el texto de los párrafos y los valores de metadatos pasan por el escape de entidades HTML. No hay salida visual de PDF con marcado de revisión en paralelo. Para entradas idénticas, las regiones y la salida formateada son deterministas.

  • La alineación de páginas es posicional. Una sola página insertada o eliminada desplaza la alineación de todas las páginas posteriores e infla los recuentos de cambios subsiguientes.
  • En la ruta de extracción alternativa, todo el texto cae en el índice de página 0. Comparar un documento extraído por el lector con expectativas de la ruta alternativa produce una atribución de páginas distinta.
  • Un búfer de origen o destino que no comienza con %PDF falla con InvalidArgumentException antes de cualquier comparación.
  • Más de 10.000 líneas combinadas en un par de páginas falla con OverflowException (límite de recuento de líneas).
  • Dos textos de página que comparten muy pocas líneas fallan con OverflowException una vez que la distancia de edición de Myers supera el tope acotado por memoria. Las revisiones legítimas comparten la mayoría de las líneas y no se ven afectadas; las entradas adversarias de baja coincidencia disparan el límite.
  • Una salida de flujo alternativo descomprimida mayor de 50 MB falla con OverflowException (límite de bomba de descompresión). El escáner usa strpos, no una expresión regular sin límites, por lo que una entrada manipulada no puede provocar retroceso catastrófico.
  • El operador de presentación de texto " se tokeniza pero no produce ningún bloque de texto en 3.1.0; el texto mostrado solo a través de " no participa en el diff.
  • Los PDF escaneados, con solo imágenes, producen poco o ningún diff de texto. No se ejecuta OCR.
  • La detección de cambios de imagen es estructural, no perceptual. No rasteriza las páginas, y una imagen recodificada con píxeles idénticos se informa como modificada cuando sus bytes difieren.
  • Una imagen cuyo cubo de página o número de objeto cambia entre revisiones se informa como un par eliminado-más-añadido, no como modificada.
  • Los flujos de objetos comprimidos con filtros distintos de FlateDecode se omiten fail-closed; sus imágenes miembro no se comparan.
  • En este módulo no ocurre ninguna operación criptográfica, por lo que no existe ningún comportamiento específico del modo FIPS. El hash de imagen es solo para la detección de cambios y no tiene peso de integridad ni probatorio.
AfirmaciónEstándarCláusula
Los operadores de presentación de texto Tj y TJ se analizan para la extracciónISO 32000-2:2020§9.4
Los datos del flujo alternativo comienzan después del CRLF o LF que sigue a la palabra clave streamISO 32000-2:2020§7.3.8.1
Las extensiones de flujo del escaneo de imágenes se rigen por la entrada /Length del diccionarioISO 32000-2:2020§7.3.8.2
Los miembros del flujo de objetos se localizan mediante la tabla de pares /N y el desplazamiento /FirstISO 32000-2:2020§7.5.7
La reversión del predictor PNG sigue el parámetro Predictor de /DecodeParmsISO 32000-2:2020§7.4.4.4
Los valores de metadatos decodifican las formas de cadena literal y hexadecimalISO 32000-2:2020§7.3.4.2, §7.3.4.3
Salida visual de PDF con marcado de revisión en paraleloNo soportado (solo JSON/HTML)

Todas las cláusulas están parafraseadas; NextPDF no reproduce el texto normativo. Son declaraciones de capacidad, no certificaciones; NextPDF no posee ninguna certificación y no concede ninguna. La recuperación de texto reconstruye el texto de las líneas a partir de los operadores de presentación de texto. No ejecuta la máquina de estados de texto completa de la §9.4, por lo que el diff es a nivel de contenido, no a nivel de geometría.

  • Disponibilidad dentro del paquete Pro: PdfDiffer, DiffEngine, TextExtractor y sus objetos de valor desde 1.8.0; StructuredDiffer, DiffFormatter, ImageDiffer, MetadataDiffer y los suyos desde 2.2.0. Todos están vigentes en nextpdf/pro 3.1.0.
  • Preferir PdfDiffer::compareTexts() cuando el texto de página ya está disponible; omite la extracción y sus modos de fallo por completo.
  • El lector Artisan opcional mejora la precisión de la extracción y la atribución de páginas. Se detecta en tiempo de ejecución y nunca es obligatorio.
  • Capturar OverflowException al comparar entradas no confiables; los límites son rechazos fail-closed deliberados, no errores transitorios.
  • DiffFormatter::toHtml() emite nombres de clase (diff-added, diff-removed, diff-modified, diff-unchanged) pero ninguna hoja de estilos; proporcionar el CSS propio.
  • Construir StructuredDiffer con diferenciadores de prueba en los tests para aislar la ruta de texto del escaneo de imágenes y metadatos.

Esta página documenta únicamente el comportamiento observable externamente y la superficie de la API pública soportada. Las rutas de espacios de nombres internos, las clases auxiliares, las tablas de mecanismos, los nombres de archivo de runbooks y los prefijos de tickets están fuera del alcance.