Technical Article

使用 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 解析與預檢功能。