Τεχνικό Άρθρο

Αυτοματοποιημένος Προέλεγχος PDF και Έλεγχος Κινδύνων με το PDFium

Ένα PDF που φτάνει σε ένα όριο παραγωγής — μια ουρά εκτύπωσης, ένα αρχείο, μια πύλη μεταφόρτωσης (upload) πελατών — θα πρέπει να ελέγχεται (audited) πριν οτιδήποτε το αποδώσει (renders). Το αρχείο μπορεί να φέρει μια ενέργεια Εκκίνησης (Launch) ρυθμισμένη να ξεκινήσει ένα εξωτερικό πρόγραμμα, εικόνες πολύ τραχιές (coarse) για να επιβιώσουν κατά την εκτύπωση, ένα λεξικό κρυπτογράφησης που απαγορεύει ακριβώς την ίδια την εργασία εκτύπωσης για την οποία υποβλήθηκε, ή μια ετικέτα PDF/A στην οποία δεν ανταποκρίνεται. Η επιθεώρηση ενός εγγράφου έναντι κανόνων όπως αυτοί προτού εισέλθει σε μια ροή εργασίας (workflow) ονομάζεται προέλεγχος (preflighting), και το C API του PDFium δίνει στο Delphi ό,τι χρειάζεται για την απευθείας υλοποίηση αυτών των ελέγχων, χωρίς την απόδοση (rendering) ούτε μίας σελίδας

Αυτό το άρθρο κατασκευάζει τους ίδιους τους ελέγχους: τέσσερις κλάσεις ελέγχου (audit classes), καθεμία εκ των οποίων αποτελεί μια μικρή ρουτίνα που προσαρτά ευρήματα σε μια κοινή λίστα αποτελεσμάτων. Διαδραστικά στοιχεία, μετρικές πόρων, κατάσταση ασφαλείας, και δείκτες προτύπων, όλα αποκτούν λειτουργικό κώδικα, συμπεριλαμβανομένης της αριθμητικής (arithmetic). Αν αυτό που χρειάζεστε είναι ο μηχανισμός γύρω από τους ελέγχους — βρόχοι (loops) φακέλων για μαζική (batch) επεξεργασία, αρχεία αναφορών σε JSON και HTML, απομόνωση ανά αρχείο — το PDFium Component παρέχει μια έτοιμη μηχανή προελέγχου, και το άρθρο για το batch preflight CLI καλύπτει αυτή την υποδομή. Τα δύο μέρη μοιράζονται εσκεμμένα ένα κοινό λεξιλόγιο κωδικών εξόδου (exit-code), έτσι ώστε ένας ελεγκτής (auditor) γραμμένος εδώ να ταιριάζει κατευθείαν κάτω από αυτόν τον οδηγό παρτίδων (batch driver)

Η εγγραφή εύρεσης (finding record) και το συμβόλαιο κωδικού εξόδου (exit-code)

Κάθε έλεγχος γράφει σε έναν επίπεδο τύπο εγγραφής (flat record type), επειδή η εναλλακτική λύση, δηλαδή κάθε έλεγχος να εκτυπώνει το δικό του κείμενο, δεν μπορεί να μετρηθεί, να φιλτραριστεί ή να οριστεί με κατώφλι (thresholded) εκ των υστέρων. Τέσσερα πεδία είναι αρκετά

uses
  System.SysUtils, System.Math, System.IOUtils,
  System.Generics.Collections, pdfium_lib;

type
  TFindingSeverity = (fsInfo, fsWarning, fsError);

  TPreflightFinding = record
    Severity: TFindingSeverity;
    Code: string;       // stable machine key, e.g. 'ACT-LAUNCH'
    Page: Integer;      // 1-based; 0 means document level
    Message: string;    // for humans; free to reword between releases
  end;

  TFindings = TList<TPreflightFinding>;

procedure Add(Findings: TFindings; Severity: TFindingSeverity;
  const Code: string; Page: Integer; const Msg: string);
var
  F: TPreflightFinding;
begin
  F.Severity := Severity;
  F.Code := Code;
  F.Page := Page;
  F.Message := Msg;
  Findings.Add(F);
end;

Τα εργαλεία στα επόμενα στάδια (downstream tooling) βασίζονται στο Code, ποτέ στο κείμενο Message, το οποίο είναι ελεύθερο να αλλάξει. Ο κωδικός εξόδου της διεργασίας ακολουθεί το ίδιο συμβόλαιο τριών τιμών με το άρθρο των batch: 0 σημαίνει ότι το αρχείο δεν παρήγαγε κανένα εύρημα, 1 σημαίνει ότι υπάρχουν ευρήματα, και 2 σημαίνει ότι ο ίδιος ο έλεγχος (audit) δεν μπόρεσε να εκτελεστεί επειδή το αρχείο απέτυχε να αναλυθεί (failed to parse) ή απαιτεί κωδικό πρόσβασης. Η διατήρηση του κωδικού 2 ως ξεχωριστού έχει σημασία. Ένας φάκελος με κατεστραμμένες σαρώσεις οφείλεται σε σπασμένο σαρωτή νωρίτερα στη διαδικασία (upstream), όχι σε ξαφνική κατάρρευση συμμόρφωσης (compliance collapse), και η συνένωση των δύο οδηγεί κάποιον να κυνηγάει το λάθος πρόβλημα

Διαδραστικά στοιχεία: scripts, στόχοι εκκίνησης (launch targets), εξωτερικοί σύνδεσμοι

Το PDFium ταξινομεί κάθε ενέργεια που βρίσκει με έναν ακέραιο τύπο, και οι σταθερές (constants) από το fpdf_doc.h αξίζει να καθοριστούν με ακρίβεια, επειδή αντιγραμμένες λάθος τιμές καθιστούν έναν σαρωτή (scanner) αθόρυβα τυφλό. Η πραγματική απαρίθμηση (enumeration) είναι PDFACTION_UNSUPPORTED = 0, PDFACTION_GOTO = 1, PDFACTION_REMOTEGOTO = 2, PDFACTION_URI = 3, PDFACTION_LAUNCH = 4, και PDFACTION_EMBEDDEDGOTO = 5. Προσέξτε τι απουσιάζει: δεν υπάρχει μέλος για τη JavaScript. Τα σενάρια επιπέδου εγγράφου (Document-level scripts) δεν είναι ενέργειες συνδέσμων και δεν εμφανίζονται ποτέ μέσω της FPDFAction_GetType· απαριθμούνται από μια ξεχωριστή οικογένεια κλήσεων. Ένας ελεγκτής που δοκιμάζει (tests) τύπους ενεργειών έναντι μιας φανταστικής σταθεράς JavaScript, μεταγλωττίζεται (compiles), τρέχει και δεν βρίσκει τίποτα, ποτέ

const
  PDFACTION_GOTO         = 1;   // in-document jump: harmless
  PDFACTION_REMOTEGOTO   = 2;   // jump into another local file
  PDFACTION_URI          = 3;   // opens an external URL
  PDFACTION_LAUNCH       = 4;   // starts an external program
  PDFACTION_EMBEDDEDGOTO = 5;   // jump into an embedded file

function ActionTarget(Doc: FPDF_DOCUMENT; Action: FPDF_ACTION;
  AType: ULONG): string;
var
  Buf: array[0..2047] of AnsiChar;
begin
  FillChar(Buf, SizeOf(Buf), 0);
  if AType = PDFACTION_URI then
    FPDFAction_GetURIPath(Doc, Action, @Buf, SizeOf(Buf))
  else
    FPDFAction_GetFilePath(Action, @Buf, SizeOf(Buf));
  Result := string(UTF8String(PAnsiChar(@Buf)));
end;

procedure AuditPageActions(Doc: FPDF_DOCUMENT; Page: FPDF_PAGE;
  PageNo: Integer; Findings: TFindings);
var
  StartPos: Integer;
  Link: FPDF_LINK;
  Action: FPDF_ACTION;
  AType: ULONG;
begin
  StartPos := 0;
  while FPDFLink_Enumerate(Page, @StartPos, @Link) <> 0 do
  begin
    Action := FPDFLink_GetAction(Link);
    if Action = nil then
      Continue;                 // destination-only link, nothing to flag
    AType := FPDFAction_GetType(Action);
    case AType of
      PDFACTION_LAUNCH:
        Add(Findings, fsError, 'ACT-LAUNCH', PageNo,
          'Launch action targets "' + ActionTarget(Doc, Action, AType) + '"');
      PDFACTION_URI:
        Add(Findings, fsWarning, 'ACT-URI', PageNo,
          'link opens ' + ActionTarget(Doc, Action, AType));
      PDFACTION_REMOTEGOTO, PDFACTION_EMBEDDEDGOTO:
        Add(Findings, fsWarning, 'ACT-XFILE', PageNo,
          'cross-file destination "' + ActionTarget(Doc, Action, AType) + '"');
    end;                        // PDFACTION_GOTO stays silent by design
  end;
end;

procedure AuditDocumentBehaviors(Doc: FPDF_DOCUMENT; Findings: TFindings);
var
  N: Integer;
begin
  N := FPDFDoc_GetJavaScriptActionCount(Doc);
  if N > 0 then
    Add(Findings, fsError, 'JS-DOC', 0,
      Format('%d document-level JavaScript action(s) run on open', [N]));
  N := FPDFDoc_GetAttachmentCount(Doc);
  if N > 0 then
    Add(Findings, fsWarning, 'ATT-EMB', 0,
      Format('%d embedded file attachment(s)', [N]));
end;

Ο διαχωρισμός της σοβαρότητας (severity) κωδικοποιεί πολιτική (policy). Μια ενέργεια Launch (Εκκίνηση) είναι ένα σφάλμα (error) επειδή η έναρξη ενός αυθαίρετου προγράμματος είναι το πιο επικίνδυνο πράγμα που μπορεί να κάνει ένα κλικ σε ένα PDF, και κανένα τιμολόγιο δεν το χρειάζεται. Τα εξωτερικά URI είναι προειδοποιήσεις (warnings): συνηθισμένα σε νόμιμα έγγραφα, αλλά ένας ελεγκτής (reviewer) θα πρέπει να βλέπει τον στόχο χωρίς να κάνει κλικ, αφού το ορατό κείμενο του συνδέσμου και ο πραγματικός προορισμός δεν χρειάζεται να συμφωνούν. Τα άλματα (jumps) GoTo εντός του εγγράφου είναι δομή (structure), όχι συμπεριφορά, και μένουν εντελώς εκτός αναφοράς — ένας προέλεγχος (preflight) που φωνάζει "λύκος" σε κάθε καταχώρηση του πίνακα περιεχομένων εκπαιδεύει τους ανθρώπους να τον αγνοούν. Για την ανάγνωση του περιεχομένου των σεναρίων (script bodies) πίσω από τον αριθμό JavaScript, και για επίπεδα υπογραφών MDP και ανίχνευση XFA, το άρθρο για τον έλεγχο κινδύνων ασφαλείας διατρέχει την ίδια επιφάνεια (surface) μέσω του object wrapper του στοιχείου

Μετρικές πόρων: αποτελεσματικό DPI εικόνας

Μια εικόνα μέσα σε ένα PDF δεν έχει δικό της DPI. Έχει pixel, και η σελίδα τοποθετεί αυτά τα pixel σε ένα ορθογώνιο μετρημένο σε στιγμές (points), όπου 72 στιγμές αποτελούν μια ίντσα. Η ανάλυση (resolution) υπάρχει μόνο ως ο λόγος των δύο, γι' αυτό και η ίδια φωτογραφία 600 επί 400 είναι εξαιρετικά ευκρινής ως μικρογραφία (thumbnail) και ένα θολό χάος (blurry mess) ως κεντρική εικόνα (hero) σε όλη τη σελίδα. Επομένως, ο έλεγχος (audit) χρειάζεται και τους δύο αριθμούς για κάθε εικόνα: τις διαστάσεις των pixel-πηγή από τα μεταδεδομένα της εικόνας, και το τοποθετημένο ορθογώνιο από τα όρια (bounds) του αντικειμένου

procedure AuditPageImages(Page: FPDF_PAGE; PageNo: Integer;
  Findings: TFindings);
var
  I, ObjCount: Integer;
  Obj: FPDF_PAGEOBJECT;
  Meta: FPDF_IMAGEOBJ_METADATA;
  L, B, R, T: Single;
  WidthPt, HeightPt, DpiX, DpiY, EffDpi: Double;
begin
  ObjCount := FPDFPage_CountObjects(Page);
  for I := 0 to ObjCount - 1 do
  begin
    Obj := FPDFPage_GetObject(Page, I);
    if FPDFPageObj_GetType(Obj) <> FPDF_PAGEOBJ_IMAGE then
      Continue;
    if FPDFImageObj_GetImageMetadata(Obj, Page, @Meta) = 0 then
      Continue;
    if FPDFPageObj_GetBounds(Obj, @L, @B, @R, @T) = 0 then
      Continue;

    WidthPt  := R - L;              // placed size on the page, in points
    HeightPt := T - B;
    if (WidthPt <= 0) or (HeightPt <= 0) or
       (Meta.Width = 0) or (Meta.Height = 0) then
      Continue;

    // 72 points = 1 inch, so placed inches = points / 72, and
    // effective DPI = source pixels / placed inches.
    DpiX := Meta.Width  / (WidthPt  / 72.0);
    DpiY := Meta.Height / (HeightPt / 72.0);
    EffDpi := Min(DpiX, DpiY);      // the worse axis decides print quality

    if EffDpi < 150.0 then
      Add(Findings, fsWarning, 'IMG-LOWRES', PageNo,
        Format('image %dx%d px placed at %.1fx%.1f pt = %.0f DPI effective',
          [Meta.Width, Meta.Height, WidthPt, HeightPt, EffDpi]))
    else if EffDpi > 600.0 then
      Add(Findings, fsInfo, 'IMG-BLOAT', PageNo,
        Format('image is %.0f DPI at placed size; resampling would ' +
          'shrink the file with no visible loss', [EffDpi]));
  end;
end;

Τα κατώφλια (thresholds) αποτελούν πολιτική (policy), όχι φυσική: το 150 DPI είναι ένα πάτωμα κάτω από το οποίο η εκτύπωση γραφείου αρχίζει να εμφανίζει ορατά pixel (pixelates), το 300 είναι ο συνηθισμένος εμπορικός στόχος, και οτιδήποτε πάνω από 600 δεν αγοράζει καμία ορατή ποιότητα, ενώ διογκώνει το μέγεθος του αρχείου, γι' αυτό και αναφέρεται ως ενημερωτική διόγκωση (informational bloat) και όχι ως ελάττωμα. Μια ειλικρινής προειδοποίηση: το FPDFPageObj_GetBounds επιστρέφει το πλαίσιο που είναι ευθυγραμμισμένο με τους άξονες (axis-aligned box), επομένως, για μια εικόνα τοποθετημένη με περιστροφή (rotation), η υπολογισμένη τιμή υποεκτιμά (underestimates) την πραγματική πυκνότητα. Η δομή (struct) FPDF_IMAGEOBJ_METADATA φέρει επίσης τα πεδία horizontal_dpi και vertical_dpi τα οποία το PDFium παράγει από την πλήρη μήτρα μετασχηματισμού (transform matrix), και η σύγκριση των δύο αποτελεσμάτων είναι ένας φθηνός τρόπος για να εντοπίσετε περιστραμμένες (rotated) τοποθετήσεις. Η ίδια αριθμητική από στιγμές σε pixel (points-to-pixels) οδηγεί την απόδοση (rendering) στην αντίθετη κατεύθυνση, η οποία καλύπτεται στο άρθρο για την εξαγωγή JPEG

Κατάσταση ασφαλείας: κρυπτογράφηση και bits δικαιωμάτων

Η κρυπτογράφηση (encryption) του PDF ορίζει δύο κωδικούς πρόσβασης (passwords) με διαφορετικές εργασίες. Ο κωδικός πρόσβασης χρήστη ελέγχει την αποκρυπτογράφηση (decryption): χωρίς αυτόν, το αρχείο δεν θα ανοίξει καθόλου, και η FPDF_LoadDocument επιστρέφει nil με την FPDF_GetLastError να αναφέρει FPDF_ERR_PASSWORD. Ο κωδικός πρόσβασης κατόχου (owner password) ελέγχει τα δικαιώματα (permissions): ένα αρχείο που προστατεύεται μόνο με κωδικό πρόσβασης κατόχου ανοίγει χωρίς διαπιστευτήρια, αλλά φέρει bits περιορισμού τα οποία ένας συμμορφούμενος αναγνώστης (conforming reader) πρέπει να τιμήσει. Επομένως, η ίδια η απόπειρα φόρτωσης είναι ο πρώτος ανιχνευτής ασφαλείας, και η διάκριση αυτή αποφασίζει τον κωδικό εξόδου — ένα αρχείο με κωδικό πρόσβασης χρήστη δεν μπορεί να ελεγχθεί (unauditable, κωδικός 2), ενώ ένα αρχείο με κωδικό πρόσβασης κατόχου ελέγχεται κανονικά (audits normally) και απλώς συσσωρεύει ευρήματα

const
  FPDF_ERR_PASSWORD = 4;

function AuditSecurity(const FileName: string;
  Findings: TFindings): FPDF_DOCUMENT;
var
  Perms: ULONG;
  Revision: Integer;
begin
  Result := FPDF_LoadDocument(PAnsiChar(AnsiString(FileName)), nil);
  if Result = nil then
  begin
    if FPDF_GetLastError() = FPDF_ERR_PASSWORD then
      Add(Findings, fsError, 'SEC-USERPW', 0,
        'user (open) password required; audit cannot proceed')
    else
      Add(Findings, fsError, 'DOC-BROKEN', 0, 'file failed to parse');
    Exit;
  end;

  Revision := FPDF_GetSecurityHandlerRevision(Result);
  if Revision >= 0 then       // -1 means the file is not encrypted
  begin
    // Opened with an empty password yet encrypted: owner-password-only.
    // Anyone may read it, but the permission bits restrict what a
    // conforming reader lets them do. Unencrypted files report all
    // bits set, which is why the revision gate comes first.
    Perms := FPDF_GetDocPermissions(Result);
    Add(Findings, fsInfo, 'SEC-ENC', 0,
      Format('encrypted, security handler revision %d', [Revision]));
    if (Perms and 4) = 0 then      // bit 3: print
      Add(Findings, fsWarning, 'SEC-NOPRINT', 0,
        'printing is not permitted');
    if (Perms and 16) = 0 then     // bit 5: copy / extract content
      Add(Findings, fsInfo, 'SEC-NOCOPY', 0,
        'content extraction is not permitted');
    if (Perms and 2048) = 0 then   // bit 12: high-resolution print
      Add(Findings, fsWarning, 'SEC-LOWPRINT', 0,
        'only low-resolution printing is permitted');
  end;
end;

Οι μάσκες (masks) προέρχονται από τον Πίνακα 22 του ISO 32000-1, ο οποίος αριθμεί τα bits ξεκινώντας από το 1: το bit 3 της τιμής /P είναι η μάσκα 4, το bit 5 είναι το 16, το bit 12 είναι το 2048. Το εάν έχει σημασία ένα δεδομένο εύρημα είναι μια απόφαση δρομολόγησης (routing decision). Ένα γραφείο εκτυπώσεων (print bureau) θα έπρεπε να αναπηδήσει (bounce) ένα αρχείο SEC-NOPRINT κατά την παραλαβή (intake), όπου αυτός που το υποβάλλει λαμβάνει ένα ξεκάθαρο μήνυμα, και όχι στο RIP (Raster Image Processor) τρεις ώρες πριν από μια προθεσμία. Ένα αρχείο θα πρέπει να αντιμετωπίζει το SEC-ENC καθεαυτό ως αποκλεισμό (blocker), δεδομένου ότι η κρυπτογράφηση και η μακροπρόθεσμη διατήρηση (long-term preservation) δεν συνδυάζονται — ένα σημείο που ο έλεγχος προτύπων (standards check) πρόκειται να κάνει επίσημα

Δείκτες προτύπων: ανάγνωση ενός ισχυρισμού PDF/A

Ένα αρχείο δηλώνει συμμόρφωση PDF/A (PDF/A conformance) στο πακέτο μεταδεδομένων XMP, μέσω της ιδιότητας (property) pdfaid:part (1 έως 4) και της pdfaid:conformance (το γράμμα επιπέδου, όπως b για οπτική πιστότητα ή a για πλήρη δομική προσθήκη ετικετών). Το C API του PDFium δεν προσφέρει προσβάτη (accessor) για το XMP· η κλήση FPDF_GetMetaText διαβάζει μόνο το λεξικό πληροφοριών (Info dictionary), όπου δεν κατοικεί αυτή η αναγνώριση. Η διέξοδος ασφαλείας (escape hatch) είναι ένας κανόνας στο ίδιο το πρότυπο: το ISO 19005 απαιτεί η ροή (stream) των μεταδεδομένων XMP να αποθηκεύεται ασυμπίεστη, ακριβώς για να μπορούν τα εργαλεία να τη βρουν χωρίς έναν πλήρη αναλυτή (parser) PDF. Η σάρωση των ωμών (raw) bytes αποτελεί επομένως έναν νόμιμο ανιχνευτή ισχυρισμών — και ένα αρχείο του οποίου ο ισχυρισμός (claim) κρύβεται μέσα σε μια συμπιεσμένη (compressed) ροή έχει ήδη παραβιάσει το πρότυπο που ισχυρίζεται

function PdfAClaim(const FileName: string): string;
var
  Bytes: TBytes;
  S: RawByteString;
  P, Limit: Integer;
begin
  Result := '';                     // empty = no PDF/A claim present
  Bytes := TFile.ReadAllBytes(FileName);
  if Length(Bytes) = 0 then
    Exit;
  SetString(S, PAnsiChar(@Bytes[0]), Length(Bytes));
  P := Pos('pdfaid:part', S);       // XMP identification schema
  if P = 0 then
    Exit;
  // Handles both <pdfaid:part>2</pdfaid:part> and pdfaid:part="2":
  // take the first digit after the property name.
  Limit := Min(P + 32, Length(S));
  Inc(P, Length('pdfaid:part'));
  while (P <= Limit) and not (S[P] in ['1'..'4']) do
    Inc(P);
  if P <= Limit then
    Result := 'PDF/A-' + Char(S[P]);
end;

Το εύρημα που αυτό παράγει είναι εσκεμμένα ενημερωτικό (informational), επειδή ένας ισχυρισμός (claim) είναι μια δήλωση, όχι μια ιδιότητα του αρχείου. Η καταχώρηση XMP είναι μια γραμμή XML που κάθε παραγωγός (producer) μπορεί να γράψει, συμπεριλαμβανομένου ενός χαλασμένου· η συμμόρφωση (conformance) σημαίνει ότι το αρχείο ικανοποιεί στην πραγματικότητα εκατοντάδες κανόνες σχετικά με ενσωματωμένες γραμματοσειρές, χρώματα ανεξάρτητα από τη συσκευή, και απαγορευμένα χαρακτηριστικά. Η ανίχνευση του ισχυρισμού σας λέει απλώς ποια αρχεία να δρομολογήσετε (route) για πραγματική επικύρωση (validation), και τίποτα περισσότερο. Η ενσωματωμένη μηχανή προελέγχου του στοιχείου εκτελεί αυτή την επικύρωση σε προφίλ PDF/A, PDF/UA, και PDF/X, και το άρθρο για το batch CLI δείχνει πώς να την ενσωματώσετε σε μια γραμμή επεξεργασίας (pipeline) με αναφορές που μπορεί να ανοίξει αργότερα ένας ελεγκτής (auditor)

Μια εκτέλεση σε ένα προβληματικό αρχείο

Ο οδηγός (driver) συνδέει τους ελέγχους μαζί: την ασφάλεια (security) πρώτα, διότι αυτή αποφασίζει αν ο έλεγχος (audit) θα τρέξει καθόλου, έπειτα τις συμπεριφορές (behaviors) επιπέδου εγγράφου και τον ισχυρισμό προτύπων (standards claim), στη συνέχεια έναν βρόχο (loop) σελίδων για ενέργειες και εικόνες

function AuditFile(const FileName: string; Findings: TFindings): Integer;
var
  Doc: FPDF_DOCUMENT;
  Page: FPDF_PAGE;
  I: Integer;
  Claim: string;
begin
  Doc := AuditSecurity(FileName, Findings);
  if Doc = nil then
    Exit(2);                        // audit failure, not a verdict
  try
    AuditDocumentBehaviors(Doc, Findings);
    Claim := PdfAClaim(FileName);
    if Claim <> '' then
      Add(Findings, fsInfo, 'STD-PDFA', 0,
        Claim + ' conformance claimed (declaration only, not validated)');
    for I := 0 to FPDF_GetPageCount(Doc) - 1 do
    begin
      Page := FPDF_LoadPage(Doc, I);
      if Page = nil then
      begin
        Add(Findings, fsError, 'PAGE-BROKEN', I + 1, 'page failed to parse');
        Continue;
      end;
      try
        AuditPageActions(Doc, Page, I + 1, Findings);
        AuditPageImages(Page, I + 1, Findings);
      finally
        FPDF_ClosePage(Page);
      end;
    end;
  finally
    FPDF_CloseDocument(Doc);
  end;
  if Findings.Count > 0 then
    Result := 1
  else
    Result := 0;
end;

Σε ένα φυλλάδιο που επέστρεψε από ένα εξωτερικό πρακτορείο (outside agency), η έξοδος (output) φαίνεται κάπως έτσι

> preflight_audit brochure_final.pdf
brochure_final.pdf: 5 finding(s)
  [ERROR]   ACT-LAUNCH   page 3   Launch action targets "..\tools\setup.exe"
  [ERROR]   JS-DOC       doc      2 document-level JavaScript action(s) run on open
  [WARNING] IMG-LOWRES   page 7   image 412x287 px placed at 396.0x275.8 pt = 75 DPI effective
  [WARNING] SEC-NOPRINT  doc      printing is not permitted
  [INFO]    STD-PDFA     doc      PDF/A-2 conformance claimed (declaration only, not validated)
exit code 1

Κάθε γραμμή είναι αξιοποιήσιμη (actionable) από μόνη της, αλλά ο συνδυασμός είναι η πραγματική ετυμηγορία (verdict). Αυτό το αρχείο ισχυρίζεται (claims) PDF/A-2, ενώ φέρει ένα λεξικό κρυπτογράφησης (encryption dictionary) και ζωντανή JavaScript, και το PDF/A απαγορεύει ρητά και τα δύο — άρα ο ισχυρισμός αποδεικνύεται ψευδής (provably false) προτού καν τρέξει κάποιος βαθύς επικυρωτής (validator). Αυτό είναι το είδος της αντίφασης που αναδεικνύει μια επίπεδη (flat) λίστα ευρημάτων και την οποία κρύβει ένα απλό boolean επιτυχίας/αποτυχίας (pass/fail)

Τι δεν μπορεί να σας πει αυτός ο έλεγχος (audit)

Η ειλικρίνεια σχετικά με την εμβέλεια (scope) είναι αυτό που διατηρεί ένα εργαλείο προελέγχου (preflight) αξιόπιστο. Όλα τα παραπάνω διαβάζουν όσα το αρχείο δηλώνει για τον εαυτό του: το PDFium αναλύει (parses) τη δομή, και αυτός ο έλεγχος (audit) την καταγράφει (inventories). Δεν εκτελεί επικύρωση (validation) PDF/A — δεν κάνει ελέγχους κάλυψης γλυφικών (glyph-coverage checks) σε σχέση με ενσωματωμένες γραμματοσειρές (embedded fonts), δεν κάνει ανάλυση του χρωματικού χώρου (color space analysis) σε σχέση με τις προθέσεις εξόδου (output intents), ούτε εφαρμόζει τους κανόνες επιπέδου ρήτρας (clause-level rules) που διαχωρίζουν έναν ισχυρισμό από τη συμμόρφωση (conformance)· γι' αυτό χρειάζεστε έναν ειδικό επικυρωτή (validator) όπως η μηχανή προελέγχου του στοιχείου ή το veraPDF. Τα bits δικαιωμάτων (Permission bits) είναι δηλώσεις που τιμούν οι συμμορφούμενοι αναγνώστες (conforming readers), όχι κρυπτογραφικοί τοίχοι, επομένως το SEC-NOPRINT περιγράφει την πρόθεση (intent) και όχι την επιβολή (enforcement). Η σάρωση ενεργειών (action scan) καλύπτει σημειώσεις συνδέσμων (link annotations) και σενάρια επιπέδου εγγράφου· τα σενάρια που είναι θαμμένα στα λεξικά συμβάντων (event dictionaries) των πεδίων φόρμας χρειάζονται επιπλέον τα APIs των φορμών. Και ένας έλεγχος υπογραφής (signature check), αν επεκτείνετε τον έλεγχο (audit) με έναν τέτοιο, αναφέρει δηλωμένη πρόθεση, όχι επαληθευμένη (verified) κρυπτογραφία — η επικύρωση της αλυσίδας πιστοποιητικών (certificate chain validation) είναι μια ξεχωριστή εργασία. Ένας έλεγχος προελέγχου (preflight audit) είναι η συνέντευξη παραλαβής (intake interview), όχι η δίκη: η δουλειά του είναι να κάνει την απόφαση δρομολόγησης (routing decision) ενημερωμένη, γρήγορη και επαναλήψιμη

Σημείωση: Τα APIs αντικειμένων εγγράφου, σελίδας, σημείωσης, και εικόνας που χρησιμοποιούνται σε όλο αυτόν τον έλεγχο, μαζί με ένα υψηλού επιπέδου Delphi wrapper και μια πλήρη μηχανή προελέγχου επικύρωσης προτύπων, αποστέλλονται με το PDFium Component