跳到內容
getnextpdf.com

Pro 版本

Diff — 深入參考

本頁是 NextPDF Pro diff 模組 NextPDF\Pro\Diff 的合約層級參考。此模組會比對兩份 PDF 文件,並回報文字、影像與中繼資料的變更。PdfDiffer 會產生頁面對齊的 Myers 行級 diff。StructuredDiffer 額外提供段落歸併、影像比對與中繼資料比對。DiffFormatter 會把結構化結果序列化為 JSON 或一段 HTML 片段。本頁陳述公開 API、可觀測的行為合約、資源界限,以及失敗模式。以任務為導向的設定與範例則位於 Diff 能力頁面

此能力隨 NextPDF Pronextpdf/pro)一同發行,並以 Pro 層級的授權封套啟用。未持有該授權的部署不會載入此能力的類別。比較各版本並取得授權

沒有任何執行階段能力旗標控管此模組。只要安裝並授權 nextpdf/pro,diff 類別即可使用。

符號參數預設行為回傳拋出或失敗於備註
PdfDiffer::compare()string $sourcePdf, string $targetPdf逐頁擷取文字,接著以來源的第 i 頁對比目標的第 iDiffResult當緩衝區缺少 %PDF 標頭,或選用讀取器剖析失敗時拋出 InvalidArgumentException;觸及資源界限時拋出 OverflowException靜態進入點
PdfDiffer::compareTexts()array $sourcePages, array $targetPages(各為 list<string>對已擷取的頁面文字進行 diff,略過擷取步驟DiffResult觸及資源界限時拋出 OverflowException靜態;當文字已備妥時使用
PdfDiffer::extractText()string $contentStream從單一原始內容串流剖析文字呈現運算子string—(容錯;無法剖析的輸入會產生空字串)靜態
StructuredDiffer::__construct()?ImageDiffer $imageDiffer = null, ?MetadataDiffer $metadataDiffer = nullnull 引數會建構出預設的比對器供測試用的建構子注入
StructuredDiffer::compare()string $sourcePdf, string $targetPdf執行文字、段落、影像與中繼資料比對,接著建立摘要StructuredDiffResult從文字路徑傳遞 InvalidArgumentExceptionOverflowException統籌整個模組的協調者
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>—(無法解碼的結構會以 fail-closed 方式略過)識別依據為頁面桶加上物件編號
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對兩份行清單執行 Myers 行級 difflist<DiffRegion>當合併後的行數超過 $maxLines,或編輯距離超過記憶體界限上限時拋出 OverflowException靜態;所有文字路徑的區域產生器
TextExtractor::fromContentStream()string $contentStream將串流權杖化並執行文字狀態機list<TextBlock>靜態
TextExtractor::fromOperations()array $operationslist<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文字 diff、段落、影像、中繼資料變更、摘要彙總結果;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、字型名稱、字型大小、行索引單一已擷取的文字段,附帶約略位置唯讀
DiffTypeenum:AddedRemovedModifiedUnchanged以字串為底的文字變更分類見行為合約中的 Modified 註記
ImageDiffTypeenum:AddedRemovedModifiedUnchanged以字串為底的影像變更分類
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 頁。當頁數不同時,多出來的頁面會把缺少的一側視為空白文字。在每一對頁面內,文字會以換行切分,並逐頁執行 Myers 行級 diff。引擎會產出 AddedRemovedUnchanged 區域。有變更的行會以一個 Removed 加上一個 Added 區域呈現;出貨的引擎絕不會產出 Modified 文字區域。由於 DiffResult 建構子是公開的,Modified 情形與 DiffResult::$modified 分桶是為呼叫端自行建構的結果而設。totalChanges() 會計入新增、移除與修改的區域;未變更的區域則排除在外。

擷取有兩條路徑:

  • 存在選用的 Artisan 讀取器。 當安裝了選用的 NextPDF\Parser\PdfReader 類別時,頁面內容串流會透過它讀取,以取得頁面精確的文字。trailer 的頁數會驅動整個迴圈。讀取失敗的頁面會貢獻空白文字,而非中止整個比對。
  • 回退路徑。 一個有界的位元組層級掃描器會以 strpos 定位 stream/endstream 配對,以固定的 50 MB 輸出上限對 FlateDecode 資料進行解壓縮,並在串流字典透過 /DecodeParms 要求時(依 ISO 32000-2:2020 §7.4.4.4)反向過濾 PNG 預測器。格式不正確或不受支援的預測器會讓解碼後的位元組維持不變。回退路徑會把所有還原出的文字串接到單一頁面桶中,因此僅在讀取器路徑上,頁面層級的對齊才是頁面精確的。

兩條路徑都會剖析 §9.4 的文字呈現運算子 TjTJ'。狀態機會追蹤 BT/ETTm(僅原點)、Td/TDT*Tf

StructuredDiffer::compare() 會執行文字 diff,將同一頁上連續同類型的區域歸併成段落(含未變更的連續段),接著執行影像與中繼資料比對,並組裝出一份 DiffSummary。摘要中的段落計數僅涵蓋新增、移除與修改的段落。

影像比對會以結構方式列舉 PDF 物件。串流本體的範圍依 §7.3.8.2 由其 /Length 項目決定,因此僅在外觀上類似物件語法的二進位位元組絕不會被登記為幻影物件。壓縮物件串流(/Type /ObjStm)會依 §7.5.7 解碼,使其中巢狀的影像 XObject 得以顯現。每個偵測到的影像都會以非密碼學的 xxh128 函式做內容雜湊;識別依據為頁面桶與物件編號這一組配對。在串流順序中找不到所屬頁面的影像,會歸屬到第 0 頁。

中繼資料比對會盡可能透過 trailer 解析出真正的 /Info 字典,使得內容串流中的誘餌欄位權杖不會被誤認為文件中繼資料。欄位值會以 PDF 字串解碼:literal 形式依 §7.3.4.2,hexadecimal 形式依 §7.3.4.3。若沒有可解析的 trailer,搜尋會退回整份輸入。日期會以解碼後的字串比對,而非剖析後的時間戳記。

DiffFormatter::toJson() 會回傳美化排版的 JSON,並以 JSON_THROW_ON_ERROR 編碼,因此編碼失敗會拋出 JsonException,而非回傳 falsetoHtml() 會回傳一段 <div class="nextpdf-diff"> 片段;段落文字與中繼資料值會經過 HTML 實體轉義。並沒有視覺化的並排式紅線 PDF 輸出。對於相同的輸入,區域與格式化輸出都是決定性的。

  • 頁面對齊是依位置進行的。單一頁面的插入或刪除,會讓其後所有頁面的對齊都跟著位移,並使下游的變更計數膨脹。
  • 在回退擷取路徑上,所有文字都會落在頁索引 0。將讀取器擷取的文件與回退路徑的預期結果做 diff,會產生不同的頁面歸屬。
  • 未以 %PDF 開頭的來源或目標緩衝區,會在任何比對之前以 InvalidArgumentException 失敗。
  • 單一頁面配對中合併行數超過 10,000 時,會以 OverflowException 失敗(行數界限)。
  • 當兩頁文字共有的行太少、以致 Myers 編輯距離超過記憶體界限上限時,會以 OverflowException 失敗。正當的修訂通常共有多數行而不受影響;蓄意設計的低共通性輸入才會觸發此界限。
  • 回退串流解壓縮後的輸出大於 50 MB 時,會以 OverflowException 失敗(解壓縮炸彈界限)。掃描器使用 strpos,而非無界的正規表示式,因此蓄意設計的輸入無法觸發災難性回溯。
  • " 文字呈現運算子在 3.1.0 中會被權杖化,但不會產生任何文字區塊;僅透過 " 呈現的文字不會參與 diff。
  • 掃描的純影像 PDF 只會產生極少或沒有文字 diff。不會執行任何 OCR。
  • 影像變更偵測是結構性的,而非感知性的。它不會對頁面做點陣化,而以相同像素重新編碼的影像,只要其位元組不同就會被回報為已修改。
  • 在不同修訂間頁面桶或物件編號改變的影像,會被回報為一組移除加新增的配對,而非修改。
  • 以 FlateDecode 以外的過濾器壓縮的物件串流,會以 fail-closed 方式略過;其成員影像不會被比對。
  • 此模組不執行任何密碼學運算,因此沒有任何 FIPS 模式專屬行為。影像雜湊僅供變更偵測之用,不具備任何完整性或證據上的分量。
主張標準條款
為擷取而剖析 TjTJ 文字呈現運算子ISO 32000-2:2020§9.4
回退串流資料起始於 stream 關鍵字之後的 CRLF 或 LF 之後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 預測器的反向處理遵循 /DecodeParmsPredictor 參數ISO 32000-2:2020§7.4.4.4
中繼資料值會解碼 literal 與 hexadecimal 字串形式ISO 32000-2:2020§7.3.4.2, §7.3.4.3
視覺化的並排式紅線 PDF 輸出不支援(僅 JSON/HTML)

所有條款皆為改寫;NextPDF 不重製規範性文字。這些是能力聲明,而非認證;NextPDF 未持有任何認證,也不授予任何認證。文字還原會從文字呈現運算子重建行文字。它並未執行完整的 §9.4 文字狀態機,因此 diff 是內容層級,而非幾何層級。

  • 在 Pro 套件內的可用性:PdfDifferDiffEngineTextExtractor 及其值物件自 1.8.0 起提供;StructuredDifferDiffFormatterImageDifferMetadataDiffer 及其值物件自 2.2.0 起提供。全部在 nextpdf/pro 3.1.0 中為現行版本。
  • 當頁面文字已備妥時,優先使用 PdfDiffer::compareTexts();它會完全略過擷取步驟及其失敗模式。
  • 選用的 Artisan 讀取器可提升擷取準確度與頁面歸屬。它會在執行階段偵測,且絕非必要。
  • 對不受信任的輸入做 diff 時,請攔截 OverflowException;這些界限是刻意的 fail-closed 拒絕,而非暫時性的錯誤。
  • DiffFormatter::toHtml() 會輸出類別名稱(diff-addeddiff-removeddiff-modifieddiff-unchanged),但不含樣式表;請自行提供 CSS。
  • 在測試中以樁(stub)比對器建構 StructuredDiffer,以將文字路徑與影像及中繼資料掃描隔離。

本頁僅記載外部可觀測的行為與受支援的公開 API 介面。內部命名空間路徑、輔助類別、機制表、runbook 檔名與工單前綴皆不在範圍內。