适用于 Delphi 和 Lazarus 的 PDFium 组件通过 XFDF(由 ISO 19444-1 定义的 XML 交换格式)交换表单数据和批注; TPdf.ExportXFDF 将加载的文档中的每个表单字段值和每个受支持的批注序列化为 XFDF 文件或 TStream; TPdf.ImportXFDF 读回 XFDF 文档,在其所在的页面上重建批注并应用字段值;这一往返处理涵盖了每个文档应用程序最终都需要两个工作流:评审人员在 Acrobat 中标记 PDF 并将意见邮寄给您进行合并,或者合规规则规定表单数据必须活在独立的、可对比的、可审计的文件中,而不是烘焙到 PDF 本身中
PDFium 支持 XFDF 导入和导出吗?
PDFium 库本身并不支持;原生的 C API 根本没有 XFDF 或 FDF 函数 —— 其公共头文件中没有任何内容可以读取或写入这两种格式,因此无论进行多少封装都无法带您到达那里;因此,PDFium 组件在 Pascal 的专用单元中实现了整个 XFDF 引擎,该单元直接在现有的 TPdf 批注和表单字段数据模型之上构建和解析 XML;写入器手动发出 XML,读取器则是手动编写的状态机解析器,因此该功能不增加对 Delphi XMLDoc 或 FPC DOM 单元的依赖,并且在 Delphi 和 Lazarus 上的行为完全相同
XFDF 文件包含什么?
ISO 19444-1 将 XFDF 定义为 FDF 的 XML 表示,其负载分为两个顶级块; <fields> 元素携带分层的表单字段名称及其值 —— 用户在 AcroForm 中输入、挑选或勾选的一切内容; <annots> 元素携带批注: TPdf.ExportXFDF 发出 18 种子类型 —— 文本(text)、高亮(highlight)、下划线(underline)、删除线(strikeout)、波浪线(squiggly)、线段(line)、圆形(circle)、方形(square)、插入符(caret)、多边形(polygon)、折线(polyline)、印章(stamp)、墨迹(ink)、自由文本(freetext)、文件附件(fileattachment)、声音(sound)、链接(link)和红线密改(redact);Widget 批注被故意排除在 <annots> 之外,因为它们的数据在 <fields> 中传输,并且诸如 Popup 这样的查看器内部子类型并不属于 XFDF 词汇表的一部分
为了使往返处理忠实可靠, TPdfAnnotation 记录已扩展了 XFDF 所期望的元数据: Name (NM 唯一标识符)、 Subject、 ModificationDate、 CreationDate、 Icon、 Opacity、线条端点、多边形和折线顶点以及墨迹手势路径;每个新字段都与一个 Has* 布尔哨兵配对,因此针对早期版本构建批注的代码可以继续编译并继续生成相同的字典 —— 未设置 the 字段永远不会被写入;如果您已经以编程方式创建标记,现在您用于带有四边形顶点的文本标记批注的同一记录,就携带了 XFDF 所需的一切
如何将表单数据和批注导出到 XFDF?
一次调用就可以完成整个文档;加载启用表单填充的 PDF,调用 ExportXFDF,组件就会遍历每个页面,收集字段值和批注,并写入 XML;返回值是写入的 UTF-8 字节数,这可以在日志中进行廉价的健全性检查:
var
Pdf: TPdf;
Bytes: Integer;
begin
Pdf := TPdf.Create(Self);
Pdf.FormFill := True; // needed so field values are live
Pdf.FileName := 'expense-report.pdf';
Pdf.Active := True;
Bytes := Pdf.ExportXFDF('expense-report.xfdf');
ShowMessage(Format('%d bytes of XFDF written', [Bytes]));
end;
两个方向都有一个 TStream 重载,因此没有什么会强迫磁盘上产生临时文件;当 XFDF 朝向 HTTP 响应、数据库 Blob 或签名归档条目时,导出到 TMemoryStream 是自然的选择:
var
Buffer: TMemoryStream;
begin
Buffer := TMemoryStream.Create;
try
Pdf.ExportXFDF(Buffer); // same XML, no file involved
Buffer.Position := 0;
// hand the stream to a web response, blob column, or zip entry
finally
Buffer.Free;
end;
end;
ImportXFDF 如何将意见合并回文档?
导入侧是评审人员工作流闭合的地方;同事在 Acrobat 中对合同进行批注,将意见导出为 XFDF,并向您发送几 KB 的 XML,而不是 PDF 的第二份副本; TPdf.ImportXFDF 解析该文件,在 page 属性命名的页面上创建每个批注,并将字段值写入匹配的 Widget 批注;该函数返回所应用的字段加批注的合并计数,以便 UI 可以确切确认到达了多少内容:
var
Applied: Integer;
begin
Applied := Pdf.ImportXFDF('review-comments.xfdf');
StatusBar.SimpleText :=
Format('%d fields and annotations merged', [Applied]);
end;
该解析器是手动编写的标签扫描器,它是为了宽容度而不是严格度而构建的:未知的元素会被跳过,处理指令、DOCTYPE 声明和注释会被忽略,无法识别的标志名称会被丢弃,并且属性值通过全套 XML 实体和数字字符引用(包括 UTF-16 代理对)进行反转义;由 Acrobat、Web 查看器或其他工具生成的 XFDF 都可以无障碍地进行合并;批注进入文档后,评审周期将继续使用在使用 PDFium 组件构建批注评审工作流中描述的回复和状态机制,并且导入的字段值参与了在表单字段导航中涵盖的正常 Tab 键顺序逻辑
为什么小数点分隔符对 XFDF 很重要?
XFDF 中的每个批注都是由坐标字符串定位的 —— rect="70.5,540,200,560" 等 —— 并且 ISO 19444-1 要求使用句点作为小数点分隔符;Delphi 的默认浮点格式遵循 Windows 区域设置,因此在德语或法语系统上,幼稚的 FloatToStr 会将 70.5 转为 70,5,这会将逗号分隔的坐标列表损坏为其他处理器拒绝或误读的垃圾;PDFium 组件对其写入的每个数字进行规范化:将区域设置小数点分隔符重写为句点,并修剪末尾的零,因此无论是在 en-US 还是 de-DE 机器上生成的导出文件,在字节上都是相同的;如果您曾经调试过在办公室工作正常但在欧洲客户现场失败的 PDF 工具,您就已经遇到过这类的 bug 了 —— 很高兴得知组件为您处理了该问题
往返处理的限制是什么?
有两个边界值得坦诚说明;首先,XFDF 传输的是批注数据,而不是渲染的外观 —— 外观流(Appearance streams)并不是该格式的一部分,因此接收查看器会从其属性重新生成每个批注的外观;高亮(Highlight)或方形(Square)在各处都会正确渲染,带有自定义外观的印章则会退回到目标查看器为该印章名称所绘制的样式;其次,底层的 PDFium API 具有用于字符串的 Setter,但没有用于批注编号或路径几何形状的 Setter,因此 Opacity、线条端点、顶点和墨迹手势是仅用于导出的:组件在编写 XFDF 时会忠实保留它们,但在导入时无法将其写回 PDF;字段值、内容、颜色、矩形、四边形顶点、日期和身份元数据都可以在两个方向上双向往返
为了动手开始, XfdfLab 示例随 Delphi、C++Builder 和 Lazarus 演示集一起发售,并将这两次调用连接到了您打开的任意 PDF 上方的按钮;XFDF 支持已包含在当前版本的 PDFium Component 中,同时也提供了它所构建的批注和表单 API