Technical Article

การทำ Preflight แบบอัตโนมัติและการตรวจสอบความเสี่ยง PDF ด้วย PDFium

ในขั้นตอนการทำงานของการพิมพ์ระดับองค์กร การจัดเก็บ และความสอดคล้องของเอกสาร ไฟล์ PDF ไม่สามารถเพียงแค่ถูก "เรนเดอร์" แต่มันต้องได้รับการตรวจสอบ ไฟล์ PDF อาจมีกลุ่มความโปร่งใสที่ไม่ได้แบนราบซึ่งทำให้ตัวประมวลผลภาพแรสเตอร์ (RIPs) แบบดั้งเดิมขัดข้อง มี JavaScript แบบฝังที่ก่อให้เกิดความเสี่ยงด้านความปลอดภัย หรือมีภาพความละเอียดต่ำที่จะดูแย่มากเมื่อพิมพ์ที่ 300 DPI

กระบวนการตรวจสอบ PDF ก่อนที่จะเข้าสู่ขั้นตอนการทำงานของการผลิตนี้เรียกว่า Preflighting ในบทความนี้ เราจะมาสำรวจวิธีการสร้างเครื่องมือพรีไฟลต์อัตโนมัติและการตรวจสอบความเสี่ยงใน Delphi โดยเจาะเข้าไปในความสามารถในการแยกวิเคราะห์ระดับต่ำของ PDFium

การตรวจสอบ Preflight ที่สำคัญ

โดยทั่วไป การตรวจสอบความเสี่ยงมาตรฐานจะตรวจสอบองค์ประกอบต่อไปนี้ภายในไฟล์ PDF:

  • องค์ประกอบแบบโต้ตอบ: คำสั่ง JavaScript, คำสั่งเรียกใช้ (การเรียกใช้โปรแกรมภายนอก) และ AcroForms
  • เมตริกทรัพยากร: การมีอยู่ของรูปภาพความละเอียดต่ำ หรือฟอนต์ที่ฝังไว้หายไป
  • สถานะความปลอดภัย: การเข้ารหัสเอกสาร, ข้อกำหนดรหัสผ่าน และสิทธิ์การเข้าถึง (เช่น ปิดใช้งานการพิมพ์)
  • มาตรฐานเอกสาร: การตรวจสอบเครื่องหมายความสอดคล้องของ PDF/A หรือ PDF/X ในข้อมูลเมตาของเอกสาร

การสกัดข้อมูลเมตาและคำอธิบายประกอบ PDF ด้วย PDFium

PDFium จัดเตรียม C API ที่แข็งแกร่งซึ่ง Delphi สามารถใช้งานได้ ในการดำเนินการตรวจสอบพรีไฟลต์ เราไม่ได้เพียงแค่เรนเดอร์หน้า แต่เราจะเดินตามต้นไม้ออบเจกต์ของ 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

การสร้างเอนจินพรีไฟลต์ที่แข็งแกร่งตั้งแต่เริ่มต้นโดยใช้ raw PDFium C API ต้องใช้โค้ดต้นแบบ (boilerplate code) จำนวนมากและความรู้เชิงลึกเกี่ยวกับข้อกำหนดของ PDF การใช้ Wrapper ช่วยให้เรื่องนี้ง่ายขึ้นอย่างมาก ด้วย Wrapper Delphi ระดับสูง คุณสามารถเปลี่ยนการวนซ้ำตัวชี้ C ที่ซับซ้อนให้กลายเป็นโค้ดเชิงวัตถุที่สะอาดตาได้

การทำให้ขั้นตอนพรีไฟลต์เป็นแบบอัตโนมัติ จะช่วยป้องกันไม่ให้ไฟล์เสียมาทำให้ไปป์ไลน์การผลิตของคุณติดขัด ช่วยประหยัดทั้งเวลาและทรัพยากรการเรนเดอร์

หมายเหตุ: ความสามารถในการแยกวิเคราะห์ PDF และพรีไฟลต์ขั้นสูงได้รับการสนับสนุนอย่างเต็มรูปแบบโดย PDFium Component