技術文章

使用 PDFium 進行自動化 PDF 預檢與風險稽核

在企業列印、歸檔與檔案合規性工作流程中,PDF 檔案不能只是「被渲染」。它必須經過稽核。PDF 可能包含會使傳統光柵影像處理器 (RIP) 當機的未平面化透明度群組 (unflattened transparency groups)、構成安全風險的內嵌 JavaScript,或是以 300 DPI 列印時看起來會很糟糕的低解析度圖片

這種在 PDF 進入生產工作流程之前檢查它的過程被稱為預檢 (Preflighting)。在本文中,我們將探討如何利用 PDFium 的低階解析功能,在 Delphi 中建置自動化的預檢與風險稽核工具

關鍵預檢檢查

標準的風險稽核通常會檢查 PDF 內的下列元素:

  • 互動式元素:JavaScript 動作、Launch 動作 (執行外部程式) 以及 AcroForms
  • 資源指標:是否存在低解析度圖片或遺失內嵌字型
  • 安全狀態:檔案加密、密碼需求與存取權限 (例如:停用列印)
  • 檔案標準:驗證檔案詮釋資料中的 PDF/A 或 PDF/X 一致性標記

使用 PDFium 擷取 PDF 詮釋資料與註解

PDFium 提供了可供 Delphi 使用的強大 C API。為了執行預檢稽核,我們不僅渲染頁面;我們還會遍歷 PDF 物件樹。讓我們看看如何迭代檔案頁面並檢查註解,以尋找潛在風險的 JavaScript 動作

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;

檢查圖片解析度

列印工作流程中另一個關鍵的預檢步驟是確保沒有圖片低於特定的 DPI 閾值。PDFium 允許您直接從頁面串流中擷取圖片物件。藉由將擷取圖片的像素尺寸除以它在 PDF 頁面上佔據的實體尺寸 (以點數計),您可以計算出有效的 DPI

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;

與 PDFium Component 整合

使用原始的 PDFium C API 從頭開始建立一個穩健的預檢引擎需要大量的樣板程式碼,以及對 PDF 規範的深入了解。使用包裝器 (wrapper) 可以極大地簡化這項工作。藉助高階 Delphi 包裝器,您可以將複雜的 C 指標迭代轉換為簡潔的物件導向程式碼

藉由將預檢階段自動化,您可以防止不良檔案堵塞您的生產管線,進而節省時間與渲染資源

備註:PDFium Component VCL 元件 完全支援進階 PDF 解析與預檢功能