Technical Article

Automatiserad PDF-preflight och riskgranskning med PDFium

Inom företagsutskrifter, arkivering och arbetsflöden för dokumentefterlevnad kan en PDF-fil inte bara "renderas". Den måste granskas. En PDF kan innehålla oplattade (unflattened) transparensgrupper som kraschar äldre rasterbildprocessorer (RIP), inbäddat JavaScript som utgör en säkerhetsrisk, eller lågupplösta bilder som kommer att se hemska ut när de skrivs ut i 300 DPI.

Denna process att inspektera en PDF innan den går in i ett produktionsarbetsflöde kallas Preflighting. I den här artikeln kommer vi att utforska hur man bygger ett automatiserat verktyg för preflight och riskgranskning i Delphi genom att utnyttja PDFiums lågnivåkapacitet för tolkning.

Viktiga Preflight-kontroller

En standardriskgranskning letar vanligtvis efter följande element inuti en PDF:

  • Interaktiva element: JavaScript-händelser, Launch-händelser (körning av externa program) och AcroForms.
  • Resursmått: Förekomst av lågupplösta bilder eller saknade inbäddade teckensnitt.
  • Säkerhetsstatus: Dokumentkryptering, lösenordskrav och åtkomstbehörigheter (t.ex. att utskrift är inaktiverat).
  • Dokumentstandarder: Validering av PDF/A- eller PDF/X-efterlevnadsmarkörer i dokumentets metadata.

Extrahera PDF-metadata och kommentarer med PDFium

PDFium tillhandahåller ett robust C API som Delphi kan använda. För att utföra en preflight-granskning renderar vi inte bara sidan; vi går igenom PDF-objektträdet. Låt oss titta på hur man itererar genom dokumentsidorna och inspekterar kommentarer (annotations) för att hitta potentiellt riskfyllda JavaScript-händelser.

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;

Kontrollera bildupplösning

Ett annat kritiskt preflight-steg för utskriftsarbetsflöden är att säkerställa att ingen bild hamnar under ett specifikt DPI-gränsvärde. PDFium låter dig extrahera bildobjekt direkt från sidströmmen. Genom att dividera pixeldimensionerna för den extraherade bilden med de fysiska dimensioner (i punkter) som den upptar på PDF-sidan, kan du beräkna det effektiva DPI-värdet.

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;

Integrera med PDFium Component

Att bygga en robust preflight-motor från grunden med hjälp av det råa PDFium C API:et kräver betydande mängder kod och djup kunskap om PDF-specifikationen. Att använda en wrapper förenklar detta enormt. Med en Delphi-wrapper på hög nivå kan du omvandla komplexa C-pekariterationer till ren, objektorienterad kod.

Genom att automatisera preflight-fasen förhindrar du att dåliga filer stoppar upp din produktionspipeline, vilket sparar både tid och renderingsresurser.

Obs: Avancerade funktioner för PDF-tolkning och preflight stöds fullt ut av PDFium Component.