Technical Article

Preflight e Auditoria de Risco de PDF Automatizados com PDFium

Em fluxos de trabalho de impressão empresarial, arquivo e conformidade de documentos, um ficheiro PDF não pode simplesmente ser "renderizado". Deve ser auditado. Um PDF pode conter grupos de transparência não achatados que bloqueiam processadores de imagem raster (RIPs) legados, JavaScript incorporado que representa um risco de segurança, ou imagens de baixa resolução que ficarão péssimas quando impressas a 300 DPI.

Este processo de inspecionar um PDF antes que entre num fluxo de produção é conhecido como Preflighting (ou Verificação Prévia). Neste artigo, exploraremos como criar uma ferramenta automatizada de preflight e auditoria de risco em Delphi, aproveitando as capacidades de análise de baixo nível do PDFium.

Principais Verificações de Preflight

Uma auditoria de risco padrão verifica tipicamente os seguintes elementos dentro de um PDF:

  • Elementos Interativos: Ações de JavaScript, ações de Lançamento (executar programas externos) e AcroForms.
  • Métricas de Recursos: Presença de imagens de baixa resolução ou tipos de letra incorporados em falta.
  • Estado de Segurança: Encriptação do documento, requisitos de palavra-passe e permissões de acesso (por exemplo, impressão desativada).
  • Normas de Documentos: Validação de marcadores de conformidade PDF/A ou PDF/X nos metadados do documento.

Extrair Metadados de PDF e Anotações com PDFium

O PDFium fornece uma robusta API C que o Delphi pode consumir. Para realizar uma auditoria de preflight, não renderizamos apenas a página; percorremos a árvore de objetos do PDF. Vejamos como iterar pelas páginas do documento e inspecionar anotações para encontrar ações JavaScript potencialmente arriscadas.

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;

Verificar a Resolução da Imagem

Outro passo crítico de preflight para fluxos de impressão é garantir que nenhuma imagem fique abaixo de um limite de DPI específico. O PDFium permite extrair objetos de imagem diretamente do fluxo da página. Ao dividir as dimensões em píxeis da imagem extraída pelas dimensões físicas (em pontos) que ocupa na página do PDF, pode calcular o DPI efetivo.

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;

Integrar com PDFium Component

Criar um motor de preflight robusto do zero usando a API C bruta do PDFium exige código de rotina (boilerplate) significativo e um conhecimento profundo da especificação PDF. O uso de um invólucro (wrapper) simplifica isto imensamente. Com um invólucro Delphi de alto nível, pode transformar iterações complexas de ponteiros C em código limpo orientado a objetos.

Ao automatizar a fase de preflight, evita que ficheiros corrompidos bloqueiem o seu pipeline de produção, poupando tempo e recursos de renderização.

Nota: As capacidades avançadas de análise e preflight de PDF são totalmente suportadas pelo Componente VCL PDFium Component.