PDFium Delphi Component Docs

Annotation and form semantic comparison

ComparePdfSemantics compares annotation and AcroForm widget properties independently of page rendering; form values can change even when appearance pixels stay identical

Pass TPdfDiffPagePairs from page alignment or an explicit mapping; nil selects positional pairs, and explicit mappings must cover both documents exactly once, using -1 for a missing side

Annotation identity uses its /NM name, and form-widget identity uses the fully qualified field name; identity is scoped to the mapped page and item kind

Missing names and repeated names, including multiple widgets of one field on the same page, are reported as psckAmbiguous with an explicit reason; annotation array order does not determine identity

The supported profile compares strings, flags, colors, opacity, borders, markup, line, vertex and ink geometry, URI actions, field values, choice labels and selected options, and checkbox or radio state

CompareAppearanceStreams optionally compares annotation appearance stream content; it is disabled by default

CompareAnnotations and CompareForms select the respective item kinds; GeometryTolerance defaults to 0.001 PDF user-space points

Added, removed and changed items include before/after properties and bounds; BuildSemanticDiffOverlays returns separate before/after rectangles with page and change indexes

All indexes are zero-based and bounds remain in PDF user space, in points; apply the relevant page rotation, crop and viewport transform before painting into a bitmap

Pages are loaded with independent native handles, preserving the caller's current page and form focus and avoiding form actions

The profile excludes non-widget fields, arbitrary custom dictionaries, embedded files, script actions and non-URI destinations; XFA comparison is explicitly rejected

Default limits are 10,000 page pairs, 4,096 scanned items, 4,096 changes, 1 MiB per string, 16 MiB of total strings and 16,777,216 comparisons; exhausting a budget rejects incomplete output

Complete distinguishes a successful complete comparison from a rejected comparison; its changes may include explicitly ambiguous identities, so inspect AmbiguousCount before treating the results as a definitive item match

Actual Delphi/FPC Win32/Win64 acceptance covers 92 assertions per target, including saved/reopened documents, identical-pixel value changes, Unicode identity, page movement, ambiguity and budgets

Options := TPdfSemanticDiffOptions.Default;
if not ComparePdfSemantics(BeforePdf, AfterPdf, PagePairs, Options, Report) then
  raise EPdfError.Create(Report.ErrorMessage);
Overlays := BuildSemanticDiffOverlays(Report);

Public declarations

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;