XFA(XML 表单架构)已被弃用。ISO 32000-1 在第 12.7 节中包含了它,并指出它已从 PDF 2.0 中删除,现代查看器正在逐个放弃其 XFA 引擎。然而这并没有清空归档文件。在过去的近二十年里,政府的受理表单、保险申请和银行对账单都是以 XFA 格式编写的,这些文件至今仍在投递到收件箱和文档处理流水线中。当以前用于渲染它们的查看器停止支持时,表单就会变成一个空白页面,并带有“请在其他阅读器中打开”的占位符。持久的解决方法是将 XFA 扁平化为任何阅读器都可以绘制的静态 PDF 内容。
扁平化中困难的部分不是字段。文本框和复选框可以足够干净地映射到 AcroForm 小部件上。困难的部分是 XFA 存储在绘制元素内部的富文本,即在 <exData contentType="text/html"> 块中。该块是一个带有内联样式且通常带有锚点的 HTML 子集。将其呈现在页面上意味着同时再现格式化文本和活动超链接,而超链接正是大多数实现默默放弃的地方。
XFA 富文本的实际外观
exData 体是 XHTML 的一小部分。段落是 <p>;带样式的字符跨度是 <span>,带有其自身的关于粗细、姿态、颜色和大小的内联 CSS;超链接是包装其可见文本的 <a href="...">。单行可以连续包含几个跨度,每个跨度具有不同的样式,其中一个可以是锚点。样式不是可以丢弃的装饰。因为法律警告而以粗体红色渲染的条款,在扁平化后必须保持粗体和红色,否则扁平化后的文档会歪曲原意。
因此,扁平化引擎不能将该块视为一个字符串。它必须遍历内联结构,通过在绘制元素的基准字体之上叠加跨度的内联 CSS 来解析每个段的有效样式,并在行中逐个排列这些段。HotPDF 将每个排列好的片段建模为一个内部 TXFARichRun 记录。该记录携带段的文本、其解析后的样式、其测量的框,以及对于锚点而言,它所指向的 Href。
自左向右排列段
定位是富文本不再仅仅是解析问题,而成为排版问题的地方。段共享一行,因此每个段从上一个段结束的地方开始。没有记录这些位置的标记,必须对它们进行测量。引擎内部的 LayoutRichText 例程使用随后将绘制它的相同字体度量来测量每个段,然后将段的水平偏移量设置为所有先前段宽度的运行总和。段 1 从绘制框原点开始,段 2 从段 1 的宽度开始,段 3 从前两个段的组合宽度开始,依此类推跨越整行。
这就是为什么测量字体对齐如此重要的原因。布局过程测量间距;独立的渲染过程绘制字形。如果这两个过程对字体不一致,布局计算的框将不会位于渲染器绘制的字形下方。HotPDF 通过内部的 RunStyleToFontSpec 辅助函数,将每个段的解析样式映射到字体规范,从而与渲染器自身默认的 10 磅 Arial 保持一致。测量的间距和绘制的文本随后达成一致,段的计算框真实地覆盖了读者看到的字符。
// Conceptual shape of one laid-out run. The engine builds an array of these
// internally; you never construct them yourself, but the fields explain how a
// link's hit box is derived from measured geometry rather than from text.
type
TRichRunInfo = record
Dx, Dy : Double; // top-left, relative to the draw-box origin
W, H : Double; // measured run box (width from the layout pass)
Text : AnsiString; // the run's visible characters
Href : AnsiString; // URI target for an <a> run, '' otherwise
end;
从锚点段到 PDF 链接批注
已完成 PDF 中的超链接不是页面内容的一部分。它是一个独立的对象,即链接批注(ISO 32000-1 第 12.5.6.5 节中描述)。该批注具有定义页面上可点击矩形的 /Rect,以及在点击矩形时触发的操作。对于外部链接,该操作是一个 URI 操作:以目标地址作为其 /URI 字符串的 /S /URI。下方的可见文本是普通的页面内容;批注是铺在其上的隐形热区。
扁平化路径完全遵循这一模型。当段携带 Href 时,HotPDF 首先绘制带样式的文本,然后在此段的框上构建链接批注。该批注的公开入口点是页面方法 AddURILink,它创建带有 /URI 操作的 /Type /Annot /Subtype /Link 对象并返回批注字典。它的矩形是段的测量框,从绘制元素的局部坐标翻译为页面坐标。结果是链接精确落在锚点文本上,而不是其他任何地方。
// The same public API the flatten path uses for each anchor run. It produces
// an ISO 32000-1 12.5.6.5 Link annotation: /Subtype /Link with a /URI action
// over the given rectangle. The optional description fills /Contents so a
// screen reader can announce the target.
var
LinkRect: TRect;
Annot: THPDFDictionaryObject;
begin
LinkRect := Rect(72, 690, 268, 706); // page-space hit box for the run
Annot := Pdf.CurrentPage.AddURILink(LinkRect,
'https://www.example.gov/appeal', 'File an appeal online');
end;
为什么点击框必须来自测量的宽度
人们很容易想象通过在页面中搜索可见文本并在找到的任何内容周围绘制矩形来定位链接。这行不通,原因与扁平化文本的存储方式有根本的关系。格式化的段是用嵌入的子集字体绘制的。子集字体对其保留的字形重新编号,因此页面内容流保存的是十六进制的 CID 代码,而不是原始的字符代码。页面上的字节不是人类阅读的字母,并且它们作为文本是不可搜索的。搜索锚点的标题什么也找不到,因为该标题作为字面文本不存在于流中的任何地方。
矩形唯一可靠的锚点是布局过程已经生成的几何结构。在流式传输行时,在对任何字形重新编号之前,计算了每个段的偏移量和测量宽度,它们描述了文本实际将出现的位置。因此,HotPDF 直接从段绘制的框获取链接矩形,而不是从任何文本查找获取。因为测量使用了渲染字体,所以无论子集化如何,框都是正确的。几何结构在编码中幸存下来,而文本没有。这就是测量宽度定位的全部依据,也是为什么试图通过文本搜索追溯添加链接的扁平化器产生的点击区域会出现偏移或消失的原因。
从您的代码驱动扁平化
对于已经包含 XFA 包的 PDF,入口点是 FlattenLoadedXFA。加载文档,调用该方法,然后保存结果。Editable 参数决定表单字段会发生什么:传入 True 以将它们保留为可填充 of AcroForm 小部件,或传入 False 以将每个小部件标记为只读,从而输出冻结的记录。无论哪种方式,都会生成带有格式化段 and 链接批注的富文本绘制块。该函数返回它输出的小部件数。
var
Pdf: THotPDF;
Emitted, i: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('xfa_appeal_form.pdf');
// True keeps fields fillable; False freezes them read-only.
Emitted := Pdf.FlattenLoadedXFA(True);
// Anything the engine could not map is reported, not raised.
for i := 0 to Pdf.XFAFlattenWarnings.Count - 1 do
Writeln('XFA warning: ', Pdf.XFAFlattenWarnings[i]);
Pdf.SaveLoadedDocument('appeal_form_flat.pdf');
Writeln('Widgets emitted: ', Emitted);
finally
Pdf.Free;
end;
end;
调用后一定要读取 XFAFlattenWarnings。列表在每次扁平化开始时被清空,并为引擎拒绝渲染的每个元素累积一行:不受支持的字段类型、无法解码的绘制图像、没有可用跨度的 exData 块。这些都不会引发异常,因此空警告列表是您一切都映射好的证据,而非空列表则准确告诉您要检查哪些原始元素。当您将原始 XFA 保存为 XDP 字节而不是加载的 PDF 时,同级方法 ApplyXFAAsAcroForm 直接接受这些字节,并共享相同的代码路径和警告行为。补充的 AddXFAPacket 方法走向相反的方向,将 XFA 包嵌入到您正在构建的文档中。
在阅读器中确认结果
在 Acrobat 或任何当前查看器中打开扁平化后的文件,并检查两件事。首先,富文本在渲染时其样式保持不变:粗体段是粗体,彩色段带有它们的颜色,并且跨度在行上按正确的顺序排列,而不是重叠或超出边框。其次,超链接是活动的。将鼠标悬停在锚点上,状态栏应显示目标地址;点击它,URI 操作应该打开它。使用查看器的批注检查器确认每个都是真实的 /Link 批注,其 /Rect 紧贴锚点文本,位于现在只是普通绘制的字形而不是表单渲染的 XFA 内容之上。这种将格式化的静态文本与正确矩形上的真实链接批注相结合的方式,使扁平化后的文档比它不再需要的 XFA 引擎更长久。
扁平化字段本身(围绕此富文本的文本框、复选框和选择列表)在我们关于将 XFA 表单扁平化为 AcroForm 小部件的演练中进行介绍。对于超越扁平化路径生成的链接,手动构建和放置链接批注的更广泛故事,请参见在 HotPDF 中使用 PDF 批注。两者都基于相同的批注和表单模型构建,该模型随适用于 Delphi 和 C++Builder 的 HotPDF 组件一起交付。