技术文章

从 Delphi 把 Excel 工作簿导出为 PDF/A

HotXLS 可以在 Delphi 与 C++Builder 中把工作簿或单个工作表直接导出为 PDF/A,无需 Excel,也不需要 PDF 打印驱动。在 TXLSPDFExport 上设置 UsePDFA 并选择一个规范档次,导出的文件就会带有一个内嵌的 sRGB ICC 配置文件作为输出意图(output intent)、XMP 元数据中的 pdfaid 标识块,以及与该合规级别相匹配的 PDF 版本头

这项需求通常来自记录保存政策,而不是开发者本身。财务报表、化验结果、监管申报文件,以及任何公共机构必须保存数十年的内容,都必须以一种在五十年后仍能原样呈现的格式存储,而这正是 PDF/A(ISO 19005)存在的意义。电子表格是一次实时计算;而电子表格的归档副本,则必须是这次计算结果的一张固定图像

是什么让一份 PDF 成为归档级 PDF?

三项要求承担了大部分工作。文件必须自包含,因此它所用的每一种字体都要内嵌,不引用任何外部资源。它的颜色必须没有歧义,也就是说要有一个输出意图,指明该文件是为哪个色彩空间准备的。它还必须能自我标识,通过 XMP 元数据声明部件编号和合规级别

HotXLS 三项都提供了。sRGB 配置文件被内嵌为一个三分量的流对象,以 DeviceRGB 作为其备用色彩空间,并由一个 GTS_PDFA1 输出意图引用,该意图的条件说明和标识符都指向 sRGB IEC61966-2.1。XMP 数据包携带 pdfaid 的部件编号和合规级别条目。配置文件的字节被直接编译进库中,而不是运行时加载,因此没有部署步骤,也就不存在因文件缺失而把一次归档导出悄悄变成普通导出的风险

示意图:PDF/A 三项归档要求与 HotXLS 从 Delphi 写出的配套件——嵌入的 FontFile2 子集、GTS_PDFA1 sRGB 输出意图和 pdfaid XMP 标识
PDF/A 文件必须自包含、色彩无歧义且能自证身份,HotXLS 依据 UsePDFA 属性对写出配套装置
uses
  lxHandleX, lxPDF;

var
  Book: TXLSXWorkbook;
  Exporter: TXLSPDFExport;
begin
  Book := TXLSXWorkbook.Create;
  Exporter := TXLSPDFExport.Create;
  try
    if Book.Open('quarterly-results.xlsx') <> 1 then
      Exit;

    Exporter.UsePDFA := True;
    Exporter.PDFAProfile := xlsPdfA2b;      // ISO 19005-2 B 级
    Exporter.DocumentLanguage := 'en-GB';
    Exporter.TagMode := xlsPdfTagsAutomatic;

    if Exporter.SaveAsPDF(Book, 'quarterly-results-archive.pdf') <> 1 then
      raise Exception.Create('archival export failed');
  finally
    Exporter.Free;
    Book.Free;
  end;
end;

该选哪一个规范档次?

规范档次的枚举涵盖 PDF/A-1、PDF/A-2 和 PDF/A-3,每一部分又分为无障碍变体和基础变体。实际差异比这份清单看起来要窄得多

B 级(基础变体)保证的是视觉外观可复现。A 级在此基础上增加了结构和语义要求,主要是一棵带标记的结构树和明确定义的阅读顺序,从而让文档能被辅助技术访问。第 2 部分放宽了第 1 部分的一些限制,允许对象流和 JPEG 2000;第 3 部分则进一步允许任意嵌入文件,这正是电子发票格式用来在人类可读页面之外附带机器可读 XML 的方式

对于一份归档用的电子表格,PDF/A-2b 是务实的默认选择:它被各类档案机构广泛接受,不要求 A 级对一张表格施加的那种标记规范,并且允许使用能防止 200 页工作簿导出体积膨胀的紧凑结构。当归档还必须以附件形式携带源数据时,选第 3 部分,导出器通过 AddAttachment 支持这一点

大多数人栽跟头的字体规则

PDF/A 文件绘制的每一个字形都必须来自内嵌字体。这是一份原本正确的导出未能通过校验的最常见单一原因,而这种失败方式很隐蔽:文件在制作它的那台机器上看起来是对的,因为那台机器安装了对应字体

HotXLS 会为它所使用的字体内嵌 TrueType 子集,但当某个字体无法解析时,仍存在一条 base-14 回退路径,而 base-14 回退按定义是不内嵌的。对于归档输出,应配置一个明确的 Unicode 字体文件,使这条回退路径永远不会被触发。子集化和回退链的具体机制见 TrueType 字体子集化;一条实用的规则是:在一台没有安装你公司专用字体的机器上测试导出结果

示意图:HotXLS Delphi 导出中的 PDF/A 字体规则:已解析字体变成嵌入的 TrueType 子集,随处可通过验证;base-14 回退字体从不嵌入,在没有该字体的机器上失败
解析不到的字体回退到从不嵌入的 base-14 字体,因此请配置显式的 Unicode 字体文件,并在没有公司字体的机器上测试

导出器保证什么,不保证什么

这里精确表述比听起来自信更重要。导出会产出 PDF/A 的整套识别机制——输出意图、内嵌 ICC 配置文件、pdfaid 元数据、合适的版本头——并把文本纳入内嵌路径。但它不会对自己的输出运行一遍完整的校验器,因此不会强制执行规范中的每一条禁止规定,例如关于透明度、某些注释类型和可选内容的规则

对于电子表格导出而言,这些结构很少出现,这也是实际成功率很高的原因。但"很高"不等于"有保证",对于真正的归档流水线,正确的架构是先导出、再用独立工具校验,并把校验失败当作流水线错误处理,而不是警告。PDF 文档中一种类似的职责划分见 PDF/A 预检校验

版面也是归档保真度的一部分

一份在技术上合规、但视觉上不可读的归档电子表格,实际上没有完成它真正的任务。页面设置在归档导出中的分量,比在日常打印中要重得多,因为以后不会再有人来重新排版它

三项设置能物有所值。适应页宽可以避免一种典型失败:一张 14 列宽的表格变成七页、每页两列。重复表头行能让每一页单独看都可读。而明确的打印区域则能排除掉那些位于真实数据右侧的工作笔记。完整的打印和页面设置控制见 保护、页面设置与打印

// 归档就绪的页面设置,在导出之前应用
Sheet := Book.Sheets[0];
Sheet.PageLandscape := True;
Sheet.FitToWidth := 1;
Sheet.FitToHeight := 0;                  // 纵向按需要分成多少页都可以
Sheet.SetPrintArea('A1:N480');
Sheet.PrintTitleRows := '$1:$3';

// 元数据也是归档的一部分
Book.Title := 'Quarterly results 2026 Q2';
Book.Author := 'Finance';

第一批任务运行前的检查清单

确认文件能自我标识:不用解压任何东西,直接在文本编辑器里打开这份 PDF,查找 pdfaid 部件编号、合规级别条目,以及 GTS_PDFA1 输出意图。确认字体:检查文档中每一种字体是否都报告为已内嵌。确认外观:把导出的页面与一份打印副本对比,因为一个把文字悄悄缩小到四号字的"适应页面"设置,即便合规也毫无用处

然后对一份有代表性的样本运行一次独立校验,并保留报告。越来越多的档案机构不仅索要文件本身,还要求验证证据,而在导出时就生成它,远比多年后再去重建它便宜得多。关于日常的非归档导出路径及其渲染选项,见 工作表 PDF 导出

归档导出、页面设置及其背后的渲染引擎,都打包在同一个面向 Delphi 与 C++Builder 的库中;完整功能列表见 HotXLS Delphi 电子表格组件页面

示意图:HotXLS Delphi PDF/A 输出的先导出后验证架构:导出器写出标识配套件,独立验证器把文件送入归档或抛出管道错误
导出器构建标识装置,独立校验器证明合规;校验失败会终止批次,而不是记一条警告