Technical Article

Automatisierter PDF-Preflight und Risiko-Audit mit PDFium

In Workflows für Unternehmensdruck, Archivierung und Dokumenten-Compliance kann eine PDF-Datei nicht einfach „gerendert“ werden. Sie muss geprüft werden. Ein PDF kann nicht reduzierte Transparenzgruppen enthalten, die ältere Raster Image Processors (RIPs) zum Absturz bringen, eingebettetes JavaScript, das ein Sicherheitsrisiko darstellt, oder niedrig auflösende Bilder, die bei einem Druck mit 300 DPI schrecklich aussehen.

Dieser Prozess der Inspektion eines PDFs, bevor es in einen Produktions-Workflow gelangt, wird als Preflighting bezeichnet. In diesem Artikel werden wir untersuchen, wie man ein automatisiertes Preflight- und Risiko-Auditing-Tool in Delphi entwickelt, indem man die Low-Level-Parsing-Funktionen von PDFium nutzt.

Wichtige Preflight-Prüfungen

Ein standardmäßiges Risiko-Audit prüft typischerweise auf die folgenden Elemente innerhalb eines PDFs:

  • Interaktive Elemente: JavaScript-Aktionen, Launch-Aktionen (Ausführen externer Programme) und AcroForms.
  • Ressourcenmetriken: Vorhandensein von niedrig auflösenden Bildern oder fehlenden eingebetteten Schriftarten.
  • Sicherheitsstatus: Dokumentenverschlüsselung, Kennwortanforderungen und Zugriffsberechtigungen (z. B. Drucken deaktiviert).
  • Dokumentenstandards: Validierung von PDF/A- oder PDF/X-Konformitätsmarkierungen in den Dokumentenmetadaten.

Extrahieren von PDF-Metadaten und Anmerkungen mit PDFium

PDFium bietet eine robuste C-API, die von Delphi konsumiert werden kann. Um ein Preflight-Audit durchzuführen, rendern wir nicht nur die Seite; wir durchlaufen den PDF-Objektbaum. Sehen wir uns an, wie man durch die Dokumentseiten iteriert und Anmerkungen überprüft, um potenziell riskante JavaScript-Aktionen zu finden.

uses
  System.SysUtils, pdfium_lib;

procedure AuditPdfSecurity(const FileName: string);
var
  Doc: FPDF_DOCUMENT;
  PageCount, i, j: Integer;
  Page: FPDF_PAGE;
  AnnotCount: Integer;
  Annot: FPDF_ANNOTATION;
  AnnotSubType: FPDF_ANNOTATION_SUBTYPE;
  Action: FPDF_ACTION;
  ActionType: ULONG;
begin
  FPDF_InitLibrary();
  try
    // Load the document without a password
    Doc := FPDF_LoadDocument(PAnsiChar(AnsiString(FileName)), nil);
    if Doc = nil then
      raise Exception.Create('Failed to load document or password required.');
      
    try
      PageCount := FPDF_GetPageCount(Doc);
      Writeln(Format('Auditing %d pages...', [PageCount]));
      
      for i := 0 to PageCount - 1 do
      begin
        Page := FPDF_LoadPage(Doc, i);
        if Page <> nil then
        begin
          AnnotCount := FPDFPage_GetAnnotCount(Page);
          for j := 0 to AnnotCount - 1 do
          begin
            Annot := FPDFPage_GetAnnot(Page, j);
            if Annot <> nil then
            begin
              AnnotSubType := FPDFAnnot_GetSubtype(Annot);
              
              // Check for Link Annotations that might have malicious actions
              if AnnotSubType = FPDF_ANNOT_LINK then
              begin
                Action := FPDFAnnot_GetLinkAction(Annot);
                if Action <> nil then
                begin
                  ActionType := FPDFAction_GetType(Action);
                  // 2 represents PDFACTION_URI, 3 represents PDFACTION_SOUND, 4 represents PDFACTION_MOVIE
                  // We specifically look out for PDFACTION_LAUNCH or PDFACTION_JAVA_SCRIPT
                  if (ActionType = PDFACTION_JAVA_SCRIPT) or (ActionType = PDFACTION_LAUNCH) then
                  begin
                    Writeln(Format('WARNING: Malicious action detected on page %d', [i + 1]));
                  end;
                end;
              end;
              FPDFPage_CloseAnnot(Annot);
            end;
          end;
          FPDF_ClosePage(Page);
        end;
      end;
    finally
      FPDF_CloseDocument(Doc);
    end;
  finally
    FPDF_DestroyLibrary();
  end;
end;

Überprüfen der Bildauflösung

Ein weiterer kritischer Preflight-Schritt für Druck-Workflows ist die Sicherstellung, dass kein Bild einen bestimmten DPI-Schwellenwert unterschreitet. PDFium ermöglicht es Ihnen, Bildobjekte direkt aus dem Seitenstrom zu extrahieren. Indem Sie die Pixelabmessungen des extrahierten Bildes durch die physischen Abmessungen (in Punkt) teilen, die es auf der PDF-Seite einnimmt, können Sie die effektive DPI berechnen.

procedure AuditPageImages(Page: FPDF_PAGE);
var
  ObjCount, i: Integer;
  PageObj: FPDF_PAGEOBJECT;
  ImgWidth, ImgHeight: Integer;
begin
  ObjCount := FPDFPage_CountObjects(Page);
  for i := 0 to ObjCount - 1 do
  begin
    PageObj := FPDFPage_GetObject(Page, i);
    if FPDFPageObj_GetType(PageObj) = FPDF_PAGEOBJ_IMAGE then
    begin
      FPDFImageObj_GetBitmap(PageObj); // Returns an FPDF_BITMAP you can inspect
      // Retrieve metadata using FPDFImageObj_GetImageMetadata
      // Calculate effective DPI based on quad coordinates
    end;
  end;
end;

Integration mit PDFium Component

Der Aufbau einer robusten Preflight-Engine von Grund auf mit der rohen PDFium-C-API erfordert erheblichen Boilerplate-Code und tiefes Wissen über die PDF-Spezifikation. Die Verwendung eines Wrappers vereinfacht dies immens. Mit einem High-Level-Delphi-Wrapper können Sie komplexe C-Zeiger-Iterationen in sauberen objektorientierten Code umwandeln.

Durch die Automatisierung der Preflight-Phase verhindern Sie, dass fehlerhafte Dateien Ihre Produktionspipeline verstopfen, und sparen so sowohl Zeit als auch Rendering-Ressourcen.

Hinweis: Erweiterte PDF-Parsing- und Preflight-Funktionen werden von der PDFium Component VCL-Komponente vollständig unterstützt.