Technical Article

在 Delphi 中处理 Office 应用程序生成的混合引用 PDF

在开发企业级文档解决方案时,您不可避免地会遇到由各种工具生成的 PDF 文件,从高端的 Adobe 软件到存在缺陷的开源库,再到虚拟打印机驱动程序。您将面临的最臭名昭著的结构问题之一就是“混合引用(hybrid-reference)”PDF。

在本文中,我们将解释什么是混合引用,为什么某些办公应用程序会生成它们,以及如何使用 Delphi 和稳健的 PDF 库以编程方式解析和修复这些结构。

PDF 交叉引用表的演变

为了在不解析整个文件的情况下快速定位对象(字体、图像、页面),PDF 使用交叉引用表(XRef)。在早期的 PDF 规范(PDF 1.4 及更早版本)中,这是位于文件末尾的字面 ASCII 文本表。

从 PDF 1.5 开始,Adobe 引入了交叉引用流(XRefStm),它将交叉引用数据压缩为二进制流,从而显著减小了文件大小。然而,为了向后兼容较旧的 PDF 阅读器,一些生成器开始生成混合引用 PDF。这些文件同时包含旧式 ASCII XRef 表和新式 XRef 流。

混合引用的问题

混合文件在理论上是有效的,但许多 PDF 生成器(尤其是旧版 Office 套件中传统的“保存为 PDF”插件)会错误地写入它们。一个常见的错误是为 `startxref` 指针写入了错误的字节偏移量,或者创建了脱节的对象流,其中 XRef 表指向了错误的生成号。

如果您的 Delphi 应用程序尝试使用严格的解析器读取格式不良的混合 PDF,解析器将失败并抛出“损坏的 XRef 表”或“无效的对象编号”异常。

使用 PDFium 处理混合回退

PDFium 引擎(最初由 Foxit 开发并由 Google 开源)对格式不良的 PDF 具有极高的容错能力。当它检测到损坏的 XRef 表时,它会自动从文件末尾(EOF)向后扫描以定位备用的 XRefStm。

在 Delphi 中,当使用 PDFium 时,您无需手动解析 trailer 字典。但是,您应该检查结构性警告,以便您可以提醒用户或记录问题。

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),您需要“展平”混合结构。在 Delphi 中修复损坏的混合 PDF 最可靠的方法是将其加载到具有容错能力的引擎中,并执行“另存为”操作。这迫使解析器从内存中的对象树重建干净、统一的 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 完全支持混合引用解析和自动结构修复。