Technical Article

Geautomatiseerde PDF preflight en risicocontrole met PDFium

In zakelijke print-, archiverings- en documentcomplianceworkflows kan een PDF-bestand niet zomaar worden "gerenderd". Het moet worden gecontroleerd (ge-audit). Een PDF kan niet-afgevlakte transparantiegroepen bevatten die oudere raster image processors (RIP's) doen crashen, ingesloten JavaScript dat een veiligheidsrisico vormt, of afbeeldingen met een lage resolutie die er vreselijk uitzien wanneer ze op 300 DPI worden afgedrukt.

Dit proces van het inspecteren van een PDF voordat deze in een productieworkflow terechtkomt, staat bekend als Preflighting. In dit artikel onderzoeken we hoe u een geautomatiseerde preflight- en risicocontroletool in Delphi kunt bouwen door gebruik te maken van de low-level parsingmogelijkheden van PDFium.

Belangrijkste preflight-controles

Een standaard risicocontrole (audit) controleert doorgaans op de volgende elementen binnen een PDF:

  • Interactieve elementen: JavaScript-acties, Start-acties (uitvoeren van externe programma's) en AcroForms.
  • Middelmetrieken: Aanwezigheid van afbeeldingen met een lage resolutie of ontbrekende ingesloten lettertypen.
  • Beveiligingsstatus: Documentversleuteling, wachtwoordvereisten en toegangsrechten (bijv. afdrukken uitgeschakeld).
  • Documentstandaarden: Validatie van PDF/A- of PDF/X-conformiteitsmarkeringen in de documentmetadata.

PDF-metadata en annotaties extraheren met PDFium

PDFium biedt een robuuste C API die Delphi kan gebruiken. Om een preflight-audit uit te voeren, renderen we niet alleen de pagina; we doorlopen de objectboom van de PDF. Laten we eens kijken hoe we door de documentpagina's kunnen itereren en annotaties kunnen inspecteren om potentieel risicovolle JavaScript-acties te vinden.

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;

Afbeeldingsresolutie controleren

Een andere kritieke preflight-stap voor printworkflows is ervoor zorgen dat geen enkele afbeelding onder een specifieke DPI-drempel valt. Met PDFium kunt u afbeeldingsobjecten rechtstreeks uit de paginastream extraheren. Door de pixelafmetingen van de geëxtraheerde afbeelding te delen door de fysieke afmetingen (in punten) die deze op de PDF-pagina inneemt, kunt u de effectieve DPI berekenen.

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;

Integratie met PDFium Component

Het bouwen van een robuuste preflight-engine vanaf nul met behulp van de ruwe PDFium C API vereist aanzienlijke boilerplate-code en diepgaande kennis van de PDF-specificatie. Het gebruik van een wrapper vereenvoudigt dit enorm. Met een high-level Delphi wrapper kunt u complexe C-pointer-iteraties omzetten in schone objectgeoriënteerde code.

Door de preflight-fase te automatiseren, voorkomt u dat slechte bestanden uw productiepijplijn verstoppen, wat zowel tijd als renderingbronnen bespaart.

Opmerking: Geavanceerde PDF-parsing en preflight-mogelijkheden worden volledig ondersteund door de PDFium Component.