Technical Article

Preflight Automatizzato dei PDF e Verifica dei Rischi con PDFium

Nei flussi di lavoro aziendali di stampa, archiviazione e conformità dei documenti, un file PDF non può essere semplicemente "renderizzato". Deve essere verificato. Un PDF potrebbe contenere gruppi di trasparenze non convertite che mandano in crash i vecchi Raster Image Processor (RIP), JavaScript incorporato che rappresenta un rischio per la sicurezza, o immagini a bassa risoluzione che appariranno scadenti se stampate a 300 DPI.

Questo processo di ispezione di un PDF prima che entri in un flusso di lavoro di produzione è noto come Preflight. In questo articolo, esploreremo come creare uno strumento automatizzato di preflight e audit dei rischi in Delphi sfruttando le capacità di parsing a basso livello di PDFium.

Controlli di Preflight Chiave

Un tipico audit dei rischi controlla i seguenti elementi all'interno di un PDF:

  • Elementi Interattivi: azioni JavaScript, azioni di Avvio (esecuzione di programmi esterni) e AcroForm.
  • Metriche delle Risorse: presenza di immagini a bassa risoluzione o font incorporati mancanti.
  • Stato di Sicurezza: crittografia del documento, requisiti della password e permessi di accesso (ad es. stampa disabilitata).
  • Standard del Documento: convalida dei marcatori di conformità PDF/A o PDF/X nei metadati del documento.

Estrarre Metadati e Annotazioni PDF con PDFium

PDFium fornisce un'API C robusta che può essere utilizzata da Delphi. Per eseguire un audit di preflight, non ci limitiamo a renderizzare la pagina; esaminiamo l'albero degli oggetti PDF. Vediamo come scorrere le pagine del documento e ispezionare le annotazioni per trovare azioni JavaScript potenzialmente rischiose.

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;

Controllo della Risoluzione delle Immagini

Un altro passaggio critico del preflight per i flussi di stampa è garantire che nessuna immagine scenda al di sotto di una soglia DPI specifica. PDFium consente di estrarre gli oggetti immagine direttamente dal flusso della pagina. Dividendo le dimensioni in pixel dell'immagine estratta per le dimensioni fisiche (in punti) che occupa sulla pagina PDF, è possibile calcolare i DPI effettivi.

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;

Integrazione con PDFium Component

Costruire da zero un motore di preflight robusto utilizzando l'API C nativa di PDFium richiede una notevole quantità di codice boilerplate e una profonda conoscenza delle specifiche PDF. L'uso di un wrapper semplifica immensamente tutto questo. Con un wrapper Delphi di alto livello, puoi trasformare le complesse iterazioni dei puntatori C in codice pulito orientato agli oggetti.

Automatizzando la fase di preflight, si evita che i file non validi intasino la pipeline di produzione, risparmiando tempo e risorse di rendering.

Nota: le funzionalità avanzate di parsing e preflight dei PDF sono pienamente supportate dal Componente VCL PDFium Component.