Technical Article

Обработка PDF с гибридными ссылками из офисных приложений в Delphi

При разработке корпоративных решений для работы с документами вы неизбежно столкнетесь с PDF-файлами, созданными огромным множеством различных инструментов: от высококлассного программного обеспечения Adobe до нестабильных библиотек с открытым исходным кодом или драйверов виртуальных принтеров. Одной из самых известных структурных проблем, с которыми вы столкнетесь, является PDF с «гибридными ссылками» (hybrid-reference PDF).

В этой статье мы объясним, что такое гибридные ссылки, почему некоторые офисные приложения генерируют их, и как программно парсить и восстанавливать эти структуры с помощью Delphi и надежных библиотек PDF.

Эволюция таблицы перекрестных ссылок PDF

Чтобы быстро находить объекты (шрифты, изображения, страницы) без синтаксического анализа всего файла, PDF использует таблицу перекрестных ссылок (XRef). В ранних спецификациях PDF (PDF 1.4 и старше) это была обычная текстовая таблица ASCII в конце файла.

Начиная с PDF 1.5, компания Adobe представила потоки перекрестных ссылок (XRefStm), которые сжимали данные перекрестных ссылок в двоичный поток, что значительно уменьшало размер файлов. Однако для обратной совместимости с более старыми программами для чтения PDF некоторые генераторы начали создавать PDF с гибридными ссылками. Такие файлы содержат как таблицу XRef в старом формате ASCII, так и поток XRef в новом стиле.

Проблема с гибридными ссылками

Гибридные файлы теоретически допустимы, но многие генераторы PDF (особенно устаревшие плагины «Сохранить в PDF» в старых офисных пакетах) записывают их некорректно. Распространенной ошибкой является запись неверного смещения в байтах для указателя `startxref` или создание разрозненных потоков объектов, где таблица XRef указывает на неверный номер поколения.

Если ваше приложение Delphi попытается прочитать плохо сформированный гибридный PDF с помощью строгого парсера, этот парсер завершит работу с исключением «Corrupt XRef table» (Поврежденная таблица XRef) или «Invalid Object Number» (Недопустимый номер объекта).

Обработка гибридных структур с помощью PDFium

Механизм PDFium (изначально разработанный Foxit и переведенный Google в открытый исходный код) очень терпим к искаженным PDF-файлам. Когда он обнаруживает поврежденную таблицу XRef, он автоматически сканирует файл в обратном направлении от конца файла (EOF), чтобы найти альтернативный XRefStm.

В Delphi при работе с PDFium вам не нужно вручную парсить словари трейлера. Тем не менее, вам следует проверять наличие структурных предупреждений, чтобы предупредить пользователя или занести проблему в журнал.

uses
  System.SysUtils, pdfium_lib;

procedure LoadAndCheckHybridPDF(const FileName: string);
var
  Doc: FPDF_DOCUMENT;
  LastError: ULONG;
begin
  FPDF_InitLibrary();
  try
    Doc := FPDF_LoadDocument(PAnsiChar(AnsiString(FileName)), nil);
    if Doc = nil then
    begin
      LastError := FPDF_GetLastError();
      case LastError of
        FPDF_ERR_FILE: Writeln('File not found or could not be opened.');
        FPDF_ERR_FORMAT: Writeln('File not in PDF format or corrupted.');
        FPDF_ERR_PASSWORD: Writeln('Password required or incorrect password.');
        FPDF_ERR_SECURITY: Writeln('Unsupported security scheme.');
        FPDF_ERR_XFDF: Writeln('Invalid XRef or Hybrid Reference structure.');
      else
        Writeln('Unknown error occurred loading PDF.');
      end;
      Exit;
    end;
    
    Writeln('PDF loaded successfully despite hybrid or structural anomalies.');
    
    // Proceed with processing...
    
    FPDF_CloseDocument(Doc);
  finally
    FPDF_DestroyLibrary();
  end;
end;

Исправление и перестроение PDF

Если ваш рабочий процесс требует передачи PDF в более строгую нисходящую систему (например, в старый аппаратный RIP), вам необходимо «свести» (flatten) гибридную структуру. Самый надежный способ исправить сломанный гибридный PDF в Delphi, это загрузить его в толерантный движок и выполнить операцию «Сохранить как». Это заставит парсер перестроить чистую, единую таблицу XRef из дерева объектов в памяти.

// Conceptual example using a high-level wrapper
procedure RebuildPdfStructure(const InputFile, OutputFile: string);
var
  Doc: TlxPDFDocument;
begin
  Doc := TlxPDFDocument.Create;
  try
    // Tolerant engine ignores the broken hybrid XRef and walks the objects
    Doc.LoadFromFile(InputFile);
    
    // Saving rewrites the file with a clean PDF 1.7 XRef stream
    Doc.SaveToFile(OutputFile);
    Writeln('PDF structure successfully rebuilt.');
  finally
    Doc.Free;
  end;
end;

Понимание и предвидение повреждений гибридных ссылок гарантирует, что ваши конвейеры обработки документов останутся устойчивыми, даже при работе с устаревшими файлами десятилетней давности.

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