Pro редакция
Diff — глубокий справочник
Краткий обзор
Заголовок раздела «Краткий обзор»Эта страница — справочник уровня контракта для модуля сравнения NextPDF Pro, NextPDF\Pro\Diff. Модуль сравнивает два документа PDF и сообщает об изменениях текста, изображений и метаданных. PdfDiffer создаёт построчное сравнение Майерса с выравниванием по страницам. StructuredDiffer добавляет группировку абзацев, сравнение изображений и сравнение метаданных. DiffFormatter сериализует структурированный результат в JSON или во фрагмент HTML. Эта страница описывает публичный API, контракт наблюдаемого поведения, ограничения ресурсов и режимы отказа. Настройка и примеры, ориентированные на задачи, находятся на странице возможности Diff.
Доступность и лицензирование
Заголовок раздела «Доступность и лицензирование»Эта возможность поставляется в NextPDF Pro (nextpdf/pro) и активируется лицензионным конвертом уровня Pro. Развёртывание без этого права не загружает классы возможности. Сравнить редакции и получить лицензию.
Никакой флаг возможностей времени выполнения не закрывает этот модуль. Классы сравнения пригодны к использованию всегда, когда nextpdf/pro установлен и лицензирован.
Публичная поверхность API
Заголовок раздела «Публичная поверхность API»| Символ | Параметры | Поведение по умолчанию | Возвращает | Бросает или завершается ошибкой | Примечания |
|---|---|---|---|---|---|
PdfDiffer::compare() | string $sourcePdf, string $targetPdf | Извлекает текст по страницам, затем сравнивает страницу i источника со страницей i цели | DiffResult | InvalidArgumentException, когда в буфере нет заголовка %PDF или опциональный читатель не может выполнить разбор; OverflowException при достижении границы ресурсов | Статическая точка входа |
PdfDiffer::compareTexts() | array $sourcePages, array $targetPages (каждый list<string>) | Сравнивает предварительно извлечённые тексты страниц, минуя извлечение | DiffResult | OverflowException при достижении границы ресурсов | Статический; используйте, когда текст уже доступен |
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 $result | JSON-документ с форматированием | string | JsonException, когда кодирование не удаётся | — |
DiffFormatter::toHtml() | StructuredDiffResult $result | HTML-фрагмент с разделами сводки, абзацев и метаданных; текстовые значения экранируются как сущности | 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> | — | Нераспознанные байты пропускаются, никогда не фатальны |
ContentStreamOp | string $operator, list<mixed> $operands | Неизменяемый объект-значение операции; isTextOp() классифицирует связанные с текстом операторы | — | — | — |
DiffResult | list<DiffRegion> $regions, int $sourcePagesCount, int $targetPagesCount | Распределяет области по $added, $removed, $modified; предоставляет isIdentical(), hasDifferences(), totalChanges() | — | — | Неизменяемый; области Unchanged остаются только в $regions |
StructuredDiffResult | текстовое сравнение, абзацы, изображения, изменения метаданных, сводка | Агрегированный результат; hasDifferences(), isIdentical() делегируют сводке | — | — | Неизменяемый |
DiffSummary | счётчики по категориям плюс счётчики страниц | hasDifferences() и totalChanges() по счётчикам текста, изображений и метаданных | — | — | Неизменяемый |
DiffRegion | DiffType $type, string $text, int $pageIndex, int $lineIndex, ?string $counterpartText = null | Одно изменение на уровне строки | — | — | $counterpartText остаётся null в поставляемом движке |
ParagraphDiff | тип, текст, индекс страницы, начальная/конечная строка, области | Последовательные области одного типа на одной странице; lineCount() | — | — | Неизменяемый |
ImageDiff | тип, индекс страницы, хеш источника, хеш цели, id объекта | Одна запись об изменении изображения | — | — | Хеши — пустые строки на отсутствующей стороне |
MetadataChange | string $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): 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,): 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, следующего за ключевым словом stream | ISO 32000-2:2020 | §7.3.8.1 |
Протяжённость потоков при сканировании изображений определяется записью словаря /Length | ISO 32000-2:2020 | §7.3.8.2 |
Члены потока объектов находятся через таблицу пар /N и смещение /First | ISO 32000-2:2020 | §7.5.7 |
Обращение PNG-предиктора следует параметру Predictor в /DecodeParms | ISO 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/pro3.1.0. - Предпочитайте
PdfDiffer::compareTexts(), когда текст страниц уже доступен; он полностью пропускает извлечение и его режимы отказа. - Опциональный читатель Artisan повышает точность извлечения и приписывания страниц. Он обнаруживается во время выполнения и никогда не требуется.
- Перехватывайте
OverflowExceptionпри сравнении недоверенного ввода; границы — это преднамеренные отказы в закрытую сторону, а не временные ошибки. DiffFormatter::toHtml()выдаёт имена классов (diff-added,diff-removed,diff-modified,diff-unchanged), но не таблицу стилей; предоставьте собственный CSS.- Создавайте
StructuredDifferс дифферами-заглушками в тестах, чтобы изолировать текстовый путь от сканирования изображений и метаданных.
Граница публикации
Заголовок раздела «Граница публикации»Эта страница документирует только внешне наблюдаемое поведение и поддерживаемую публичную поверхность API. Внутренние пути пространств имён, вспомогательные классы, таблицы механизмов, имена файлов runbook и префиксы тикетов вне области рассмотрения.
См. также
Заголовок раздела «См. также»- Diff (возможность) — установка, быстрый старт и рабочие примеры.
- Converter — глубокий справочник
- Filter — глубокий справочник