Technical Article

Автоматизированная предпечатная проверка PDF и аудит рисков с помощью PDFium

В корпоративных рабочих процессах печати, архивирования и обеспечения соответствия документов требованиям PDF-файл нельзя просто «отрендерить». Он должен пройти аудит. PDF-файл может содержать несведенные группы прозрачности, которые приводят к сбою устаревших растровых процессоров изображений (RIP), встроенный JavaScript, представляющий угрозу безопасности, или изображения с низким разрешением, которые будут ужасно выглядеть при печати с разрешением 300 DPI.

Этот процесс проверки PDF перед его попаданием в производственный рабочий процесс известен как предпечатная проверка (Preflighting). В этой статье мы рассмотрим, как создать автоматизированный инструмент предпечатной проверки и аудита рисков в Delphi, используя возможности низкоуровневого парсинга PDFium.

Ключевые предпечатные проверки

Стандартный аудит рисков обычно проверяет наличие следующих элементов в PDF:

  • Интерактивные элементы: действия JavaScript, действия запуска Launch (выполнение внешних программ) и формы AcroForms.
  • Метрики ресурсов: наличие изображений с низким разрешением или отсутствующие встроенные шрифты.
  • Статус безопасности: шифрование документа, требования к паролю и разрешения доступа (например, печать отключена).
  • Стандарты документов: проверка маркеров соответствия PDF/A или PDF/X в метаданных документа.

Извлечение метаданных и аннотаций PDF с помощью PDFium

PDFium предоставляет надежный API C, который может использовать 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

Создание надежного механизма предпечатной проверки с нуля с использованием исходного C API PDFium требует значительного количества шаблонного кода и глубоких знаний спецификации PDF. Использование обертки (wrapper) невероятно упрощает эту задачу. С помощью высокоуровневой обертки Delphi вы можете превратить сложные итерации указателей C в чистый объектно-ориентированный код.

Автоматизируя этап предпечатной проверки, вы предотвращаете засорение вашего производственного конвейера плохими файлами, экономя время и ресурсы рендеринга.

Примечание. Расширенные возможности парсинга и предпечатной проверки PDF полностью поддерживаются в компоненте PDFium Component VCL.