PDFium Delphi Component Docs

注解与表单语义比对

ComparePdfSemantics 独立于页面渲染比对注解和 AcroForm widget 属性;即使外观像素完全相同,表单值也可能变化

传入页面对齐得到的 TPdfDiffPagePairs 或显式映射;nil 使用位置配对,显式映射须恰好覆盖两个文档各一次,缺失的一侧使用 -1

注解以 /NM 名称标识,表单 widget 以字段全限定名标识;身份范围限定在映射页面和项类型内

缺失或重复名称会报告为 psckAmbiguous 并给出原因,同一字段在同页有多个 widget 也属于此类;注解数组顺序不决定身份

受支持配置比对字符串、标志、颜色、不透明度、边框、标记、线段/顶点/墨迹几何、URI 动作、字段值、选项标签及选中项,以及复选框或单选状态

CompareAppearanceStreams 可选地比对注解外观流内容,默认关闭

CompareAnnotations 和 CompareForms 分别选择对应项类型;GeometryTolerance 默认为 0.001 PDF 用户空间点

新增、移除和变更项包含前后属性及边界;BuildSemanticDiffOverlays 返回独立的前后矩形,附带页面和变更索引

所有索引从零开始,边界仍为以点计的 PDF 用户空间坐标;绘制到位图前需应用相应页面旋转、裁剪和视口变换

页面使用独立原生句柄加载,保留调用方当前页及表单焦点,并避免触发表单动作

该配置不涵盖非 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;