Перейти к содержимому
getnextpdf.com

Pro редакция

Diff — глубокий справочник

Эта страница — справочник уровня контракта для модуля сравнения NextPDF Pro, NextPDF\Pro\Diff. Модуль сравнивает два документа PDF и сообщает об изменениях текста, изображений и метаданных. PdfDiffer создаёт построчное сравнение Майерса с выравниванием по страницам. StructuredDiffer добавляет группировку абзацев, сравнение изображений и сравнение метаданных. DiffFormatter сериализует структурированный результат в JSON или во фрагмент HTML. Эта страница описывает публичный API, контракт наблюдаемого поведения, ограничения ресурсов и режимы отказа. Настройка и примеры, ориентированные на задачи, находятся на странице возможности Diff.

Эта возможность поставляется в NextPDF Pro (nextpdf/pro) и активируется лицензионным конвертом уровня Pro. Развёртывание без этого права не загружает классы возможности. Сравнить редакции и получить лицензию.

Никакой флаг возможностей времени выполнения не закрывает этот модуль. Классы сравнения пригодны к использованию всегда, когда nextpdf/pro установлен и лицензирован.

СимволПараметрыПоведение по умолчаниюВозвращаетБросает или завершается ошибкойПримечания
PdfDiffer::compare()string $sourcePdf, string $targetPdfИзвлекает текст по страницам, затем сравнивает страницу i источника со страницей i целиDiffResultInvalidArgumentException, когда в буфере нет заголовка %PDF или опциональный читатель не может выполнить разбор; OverflowException при достижении границы ресурсовСтатическая точка входа
PdfDiffer::compareTexts()array $sourcePages, array $targetPages (каждый list<string>)Сравнивает предварительно извлечённые тексты страниц, минуя извлечениеDiffResultOverflowException при достижении границы ресурсовСтатический; используйте, когда текст уже доступен
PdfDiffer::extractText()string $contentStreamРазбирает операторы показа текста из одного сырого потока содержимогоstring— (отказоустойчиво; неразбираемый ввод даёт пустую строку)Статический
StructuredDiffer::__construct()?ImageDiffer $imageDiffer = null, ?MetadataDiffer $metadataDiffer = nullАргументы null создают дифферы по умолчаниюВнедрение через конструктор для тестирования
StructuredDiffer::compare()string $sourcePdf, string $targetPdfВыполняет сравнение текста, абзацев, изображений и метаданных, затем строит сводкуStructuredDiffResultПробрасывает InvalidArgumentException и OverflowException из текстового путиОркестратор всего модуля
DiffFormatter::toJson()StructuredDiffResult $resultJSON-документ с форматированиемstringJsonException, когда кодирование не удаётся
DiffFormatter::toHtml()StructuredDiffResult $resultHTML-фрагмент с разделами сводки, абзацев и метаданных; текстовые значения экранируются как сущностиstringТолько фрагмент, не полный документ
DiffFormatter::toArray()StructuredDiffResult $resultМассив сериализации, лежащий в основе toJson()array<string, mixed>Стабильные ключи в snake_case
ImageDiffer::diff()string $sourcePdf, string $targetPdfХеширует изображения-XObject и сообщает о добавленных, удалённых и изменённых изображенияхlist<ImageDiff>— (недекодируемые структуры пропускаются с отказом в закрытую сторону)Идентичность — это корзина страницы плюс номер объекта
MetadataDiffer::diff()string $sourcePdf, string $targetPdfСравнивает восемь полей /Info (Title, Author, Subject, Keywords, Creator, Producer, CreationDate, ModDate)list<MetadataChange>— (никогда не бросает исключение на несоответствующем вводе)Значения сравниваются как декодированные строки
DiffEngine::diff()array $sourceLines, array $targetLines, int $pageIndex = 0, int $maxLines = 10000Построчное сравнение Майерса над двумя списками строкlist<DiffRegion>OverflowException, когда суммарное число строк превышает $maxLines или расстояние редактирования превышает предел по памятиСтатический; производитель областей для всех текстовых путей
TextExtractor::fromContentStream()string $contentStreamТокенизирует поток и запускает конечный автомат состояния текстаlist<TextBlock>Статический
TextExtractor::fromOperations()array $operations (list<ContentStreamOp>)Запускает конечный автомат состояния текста над предварительно разобранными операциямиlist<TextBlock>Статический
ContentStreamParser::parse()конструктор принимает string $dataТокенизирует операторы и операнды; пропускает словари и комментарии; отказоустойчивlist<ContentStreamOp>Нераспознанные байты пропускаются, никогда не фатальны
ContentStreamOpstring $operator, list<mixed> $operandsНеизменяемый объект-значение операции; isTextOp() классифицирует связанные с текстом операторы
DiffResultlist<DiffRegion> $regions, int $sourcePagesCount, int $targetPagesCountРаспределяет области по $added, $removed, $modified; предоставляет isIdentical(), hasDifferences(), totalChanges()Неизменяемый; области Unchanged остаются только в $regions
StructuredDiffResultтекстовое сравнение, абзацы, изображения, изменения метаданных, сводкаАгрегированный результат; hasDifferences(), isIdentical() делегируют сводкеНеизменяемый
DiffSummaryсчётчики по категориям плюс счётчики страницhasDifferences() и totalChanges() по счётчикам текста, изображений и метаданныхНеизменяемый
DiffRegionDiffType $type, string $text, int $pageIndex, int $lineIndex, ?string $counterpartText = nullОдно изменение на уровне строки$counterpartText остаётся null в поставляемом движке
ParagraphDiffтип, текст, индекс страницы, начальная/конечная строка, областиПоследовательные области одного типа на одной странице; lineCount()Неизменяемый
ImageDiffтип, индекс страницы, хеш источника, хеш цели, id объектаОдна запись об изменении изображенияХеши — пустые строки на отсутствующей стороне
MetadataChangestring $field, ?string $sourceValue, ?string $targetValueОдно изменение поля; isAdded(), isRemoved(), isModified()null означает, что поле отсутствует
TextBlockтекст, x, y, имя шрифта, размер шрифта, индекс строкиОдин извлечённый текстовый фрагмент с приблизительной позициейНеизменяемый
DiffTypeперечисление: Added, Removed, Modified, UnchangedКлассификация изменений текста на основе строкСм. примечание о Modified в контракте поведения
ImageDiffTypeперечисление: Added, Removed, Modified, UnchangedКлассификация изменений изображений на основе строк
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() извлекает текст по страницам, затем сравнивает страницу i источника со страницей i цели. Когда число страниц различается, отсутствующая сторона рассматривается как пустой текст для избыточных страниц. Внутри каждой пары страниц текст разбивается по переводам строки, и для каждой страницы выполняется построчное сравнение Майерса. Движок выдаёт области Added, Removed и Unchanged. Изменённая строка проявляется как область Removed плюс область Added; поставляемый движок никогда не выдаёт текстовые области Modified. Случай Modified и корзина DiffResult::$modified служат результатам, сконструированным вызывающей стороной, поскольку конструктор DiffResult публичный. totalChanges() считает добавленные, удалённые и изменённые области; неизменённые области исключаются.

У извлечения два пути:

  • Присутствует опциональный читатель Artisan. Когда установлен опциональный класс NextPDF\Parser\PdfReader, потоки содержимого страниц читаются через него для постранично точного текста. Число страниц из трейлера управляет циклом. Страница, которую не удалось прочитать, вносит пустой текст вместо прерывания сравнения.
  • Резервный вариант. Ограниченный побайтовый сканер находит пары stream/endstream через strpos, распаковывает данные FlateDecode с жёстким ограничением вывода в 50 МБ и обращает PNG-предиктор, когда словарь потока запрашивает его через /DecodeParms по ISO 32000-2:2020 §7.4.4.4. Некорректный или неподдерживаемый предиктор оставляет декодированные байты без изменений. Резервный вариант объединяет весь восстановленный текст в одну корзину страницы, поэтому выравнивание на уровне страниц постранично точно только на пути читателя.

Оба пути разбирают операторы показа текста §9.4 Tj, TJ и '. Конечный автомат отслеживает BT/ET, Tm (только начало координат), Td/TD, T* и Tf.

StructuredDiffer::compare() выполняет текстовое сравнение, группирует последовательные области одного типа на одной странице в абзацы (включая неизменённые серии), затем запускает сравнение изображений и метаданных и собирает DiffSummary. Счётчики абзацев в сводке охватывают только добавленные, удалённые и изменённые абзацы.

Сравнение изображений перечисляет объекты PDF структурно. Протяжённость тела потока определяется его записью /Length по §7.3.8.2, поэтому двоичные байты, лишь напоминающие синтаксис объекта, никогда не регистрируются как фантомные объекты. Сжатые потоки объектов (/Type /ObjStm) декодируются по §7.5.7, чтобы вложенные в них изображения-XObject были видимы. Каждое обнаруженное изображение хешируется по содержимому некриптографической функцией xxh128; идентичность — это пара из корзины страницы и номера объекта. Изображения без владеющей страницы в порядке потока приписываются странице 0.

Сравнение метаданных по возможности разрешает настоящий словарь /Info через трейлер, поэтому обманный токен поля внутри потока содержимого не принимается за метаданные документа. Значения полей декодируются как строки PDF: буквальная форма по §7.3.4.2 и шестнадцатеричная форма по §7.3.4.3. Без разрешимого трейлера поиск возвращается ко всему вводу. Даты сравниваются как декодированные строки, а не как разобранные метки времени.

DiffFormatter::toJson() возвращает JSON с форматированием и кодирует с JSON_THROW_ON_ERROR, поэтому ошибка кодирования вызывает JsonException вместо возврата false. toHtml() возвращает фрагмент <div class="nextpdf-diff">; текст абзацев и значения метаданных проходят через HTML-экранирование сущностей. Визуального параллельного PDF-вывода с правками нет. Для идентичных входных данных области и форматированный вывод детерминированы.

  • Выравнивание страниц позиционное. Одна вставленная или удалённая страница смещает выравнивание для всех последующих страниц и завышает нижестоящие счётчики изменений.
  • На резервном пути извлечения весь текст попадает на страницу с индексом 0. Сравнение документа, извлечённого читателем, с ожиданиями от резервного пути даёт другое приписывание страниц.
  • Буфер источника или цели, не начинающийся с %PDF, завершается ошибкой InvalidArgumentException до какого-либо сравнения.
  • Более 10,000 суммарных строк в одной паре страниц завершается ошибкой OverflowException (граница по числу строк).
  • Два текста страниц, разделяющих слишком мало строк, завершаются ошибкой OverflowException, как только расстояние редактирования Майерса превышает предел по памяти. Легитимные редакции разделяют большинство строк и остаются незатронутыми; враждебные входные данные с малой общностью срабатывают на границе.
  • Распакованный вывод потока в резервном варианте больше 50 МБ завершается ошибкой OverflowException (граница декомпрессионной бомбы). Сканер использует strpos, а не неограниченное регулярное выражение, поэтому специально созданный ввод не может вызвать катастрофический возврат.
  • Оператор показа текста " токенизируется, но не создаёт текстовый блок в 3.1.0; текст, показанный только через ", не участвует в сравнении.
  • Отсканированные PDF, содержащие только изображения, дают мало текстовых различий или не дают их вовсе. OCR не выполняется.
  • Обнаружение изменений изображений структурное, а не перцептивное. Оно не растеризует страницы, и изображение, перекодированное с идентичными пикселями, сообщается как изменённое, когда его байты различаются.
  • Изображение, у которого корзина страницы или номер объекта меняется между редакциями, сообщается как пара «удалено плюс добавлено», а не как изменённое.
  • Потоки объектов, сжатые фильтрами, отличными от FlateDecode, пропускаются с отказом в закрытую сторону; их изображения-члены не сравниваются.
  • В этом модуле не выполняется никакой криптографической операции, поэтому нет поведения, специфичного для режима FIPS. Хеш изображения предназначен только для обнаружения изменений и не несёт веса целостности или доказательности.
УтверждениеСтандартПункт
Операторы показа текста Tj и TJ разбираются для извлеченияISO 32000-2:2020§9.4
Данные потока в резервном варианте начинаются после CRLF или LF, следующего за ключевым словом streamISO 32000-2:2020§7.3.8.1
Протяжённость потоков при сканировании изображений определяется записью словаря /LengthISO 32000-2:2020§7.3.8.2
Члены потока объектов находятся через таблицу пар /N и смещение /FirstISO 32000-2:2020§7.5.7
Обращение PNG-предиктора следует параметру Predictor в /DecodeParmsISO 32000-2:2020§7.4.4.4
Значения метаданных декодируют буквальную и шестнадцатеричную формы строкISO 32000-2:2020§7.3.4.2, §7.3.4.3
Визуальный параллельный PDF-вывод с правкамиНе поддерживается (только JSON/HTML)

Все пункты пересказаны; NextPDF не воспроизводит нормативный текст. Это утверждения о возможностях, а не сертификации; NextPDF не имеет сертификации и не предоставляет её. Восстановление текста реконструирует текст строк из операторов показа текста. Оно не запускает полный конечный автомат состояния текста §9.4, поэтому сравнение выполняется на уровне содержимого, а не геометрии.

  • Доступность в пакете Pro: PdfDiffer, DiffEngine, TextExtractor и их объекты-значения с 1.8.0; StructuredDiffer, DiffFormatter, ImageDiffer, MetadataDiffer и их — с 2.2.0. Все актуальны в nextpdf/pro 3.1.0.
  • Предпочитайте PdfDiffer::compareTexts(), когда текст страниц уже доступен; он полностью пропускает извлечение и его режимы отказа.
  • Опциональный читатель Artisan повышает точность извлечения и приписывания страниц. Он обнаруживается во время выполнения и никогда не требуется.
  • Перехватывайте OverflowException при сравнении недоверенного ввода; границы — это преднамеренные отказы в закрытую сторону, а не временные ошибки.
  • DiffFormatter::toHtml() выдаёт имена классов (diff-added, diff-removed, diff-modified, diff-unchanged), но не таблицу стилей; предоставьте собственный CSS.
  • Создавайте StructuredDiffer с дифферами-заглушками в тестах, чтобы изолировать текстовый путь от сканирования изображений и метаданных.

Эта страница документирует только внешне наблюдаемое поведение и поддерживаемую публичную поверхность API. Внутренние пути пространств имён, вспомогательные классы, таблицы механизмов, имена файлов runbook и префиксы тикетов вне области рассмотрения.