ComparePdfSemantics 比較註解與 AcroForm Widget 屬性,不依賴頁面繪製;即使外觀像素完全相同,表單值也可能已變更
傳入頁面對齊產生的 TPdfDiffPagePairs 或明確對應;nil 使用依位置配對,明確對應則必須將兩份文件的每頁各涵蓋一次,缺少的一側使用 -1
註解識別使用 /NM 名稱,表單 Widget 識別使用完整欄位名稱;識別範圍限定於對應頁面及項目種類
缺少名稱或重複名稱,包括同頁上同一欄位的多個 Widget,會回報 psckAmbiguous 與明確原因;註解陣列順序不決定識別
支援模式比較字串、旗標、色彩、不透明度、邊框、標記、線段、頂點與墨跡幾何、URI 動作、欄位值、選項標籤與選定選項,以及核取方塊或選項按鈕狀態
CompareAppearanceStreams 可選擇比較註解外觀串流內容;預設停用
CompareAnnotations 與 CompareForms 分別選取對應項目種類;GeometryTolerance 預設為 0.001 PDF 使用者空間點
新增、移除與變更項目包含變更前後的屬性與邊界;BuildSemanticDiffOverlays 分別傳回變更前後矩形,並附上頁面與變更索引
所有索引從零起算,邊界維持在以點表示的 PDF 使用者空間;繪製到點陣圖之前,請套用相應頁面的旋轉、裁切與視埠變換
頁面使用獨立原生 Handle 載入,保留呼叫端目前頁面與表單焦點,避免觸發表單動作
此模式不涵蓋非 Widget 欄位、任意自訂字典、內嵌檔案、指令碼動作與非 URI 目的地;明確拒絕 XFA 比較
預設上限為 10,000 組頁面配對、4,096 個掃描項目、4,096 項變更、每個字串 1 MiB、字串總計 16 MiB 及 16,777,216 次比較;預算耗盡時拒絕不完整輸出
Complete 區分成功且完整的比較與遭拒絕的比較;變更可能包含明確的識別歧義,因此將結果視為確定的項目匹配之前,請先檢查 AmbiguousCount
實際 Delphi/FPC Win32/Win64 驗收每個目標涵蓋 92 項斷言,包括儲存/重新開啟文件、相同像素下的值變更、Unicode 識別、頁面移動、歧義及預算
Options := TPdfSemanticDiffOptions.Default; if not ComparePdfSemantics(BeforePdf, AfterPdf, PagePairs, Options, Report) then raise EPdfError.Create(Report.ErrorMessage); Overlays := BuildSemanticDiffOverlays(Report);
type
TPdfSemanticItemKind = (psikAnnotation, psikFormWidget);
TPdfSemanticChangeKind = (psckAdded, psckRemoved, psckChanged, psckAmbiguous);
TPdfSemanticSide = (pssBefore, pssAfter);
TPdfSemanticProperty = record
Name: string;
Value: WideString;
end;
TPdfSemanticProperties = array of TPdfSemanticProperty;
TPdfSemanticItem = record
Kind: TPdfSemanticItemKind;
Identity: WideString;
PageIndex, AnnotationIndex: Integer;
HasBounds: Boolean;
Bounds: TPdfRectangle;
Properties: TPdfSemanticProperties;
end;
TPdfSemanticPropertyChange = record
Name: string;
HasBefore, HasAfter: Boolean;
BeforeValue, AfterValue: WideString;
end;
TPdfSemanticPropertyChanges = array of TPdfSemanticPropertyChange;
TPdfSemanticChange = record
Kind: TPdfSemanticChangeKind;
ItemKind: TPdfSemanticItemKind;
Identity: WideString;
HasBefore, HasAfter: Boolean;
BeforeItem, AfterItem: TPdfSemanticItem;
Properties: TPdfSemanticPropertyChanges;
AmbiguityReason: string;
end;
TPdfSemanticChanges = array of TPdfSemanticChange;
TPdfSemanticOverlay = record
Side: TPdfSemanticSide;
PageIndex, ChangeIndex: Integer;
Kind: TPdfSemanticChangeKind;
Bounds: TPdfRectangle;
end;
TPdfSemanticOverlays = array of TPdfSemanticOverlay;
TPdfSemanticDiffOptions = record
CompareAnnotations, CompareForms, CompareAppearanceStreams: Boolean;
GeometryTolerance: Double;
MaxPagePairs, MaxItems, MaxChanges, MaxStringBytes: Integer;
MaxTotalStringBytes, MaxComparisons: Int64;
class function Default: TPdfSemanticDiffOptions; static;
end;
TPdfSemanticDiffReport = record
Complete: Boolean;
Changes: TPdfSemanticChanges;
AddedCount, RemovedCount, ChangedCount, AmbiguousCount: Integer;
ComparedItemCount: Integer;
ErrorMessage: string;
end;
// Indices are zero based. Nil PagePairs selects positional pairing. Explicit
// pairs must cover both documents exactly once; -1 denotes an absent side
// Identity is scoped to a mapped page and item kind. Missing names and repeated
// names (including repeated widgets of one field) are reported as ambiguous
// No rendering, focus changes, form actions, or positional identity guessing
// are performed. Pages are opened independently of the caller's current page
// The supported profile covers strings, flags, colors, opacity, borders,
// markup/line/vertex/ink geometry, URI actions, field values/options/checks,
// and optionally appearance streams. Arbitrary dictionaries, embedded files,
// script actions, non-URI destinations, and non-widget fields are not compared
function ComparePdfSemantics(BeforePdf, AfterPdf: TPdf;
const PagePairs: TPdfDiffPagePairs; const Options: TPdfSemanticDiffOptions;
out Report: TPdfSemanticDiffReport): Boolean;
// Bounds remain in PDF user space, in points. Transform with the destination
// page's rotation/crop/viewport rather than treating them as bitmap pixels
function BuildSemanticDiffOverlays(const Report: TPdfSemanticDiffReport):
TPdfSemanticOverlays;