Technical Article

PDFiumを使用した自動化されたPDFプリフライトとリスク監査

エンタープライズの印刷、アーカイブ、およびドキュメントコンプライアンスのワークフローにおいて、PDFファイルは単に「レンダリング」できるだけではいけません。監査する必要があります。PDFには、レガシーなラスターイメージプロセッサ(RIP)をクラッシュさせるフラット化されていない透明グループ、セキュリティリスクをもたらす埋め込み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仕様に関する深い知識が必要です。ラッパーを使用すると、これが大幅に簡素化されます。高レベルなDelphiラッパーを使用すると、複雑なCのポインタ反復をクリーンなオブジェクト指向のコードに変えることができます。

プリフライトフェーズを自動化することで、不良ファイルが本番パイプラインを詰まらせるのを防ぎ、時間とレンダリングリソースの両方を節約できます。

注: 高度なPDF解析およびプリフライト機能は、PDFium Componentで完全にサポートされています。