注释不是页面内容。当您调用 TextOut 或绘制矩形时,这些标记将成为页面内容流的一部分,并被固化到渲染器绘制的字节中。注释是一个独立的字典,通过其 /Annots 数组挂靠在页面上,拥有自己的矩形框、自己的外观和自己的生命周期。阅读器可以打开它、移动它、隐藏它或将其剥离,而不会触及底层页面的任何一个字形。这种分离正是注释存在的全部原因,这也是最初让人们感到惊讶的两件事的根源:注释最终落在哪里,以及特定的查看器获取它后它看起来是什么样子
HotPDF 通过页面对象上的一系列 AddXxxAnnotation 调用来暴露 ISO 32000 注释子类型。它们都具有相同的结构:一个在 PDF 用户空间中将注释固定在页面上的矩形,一些负载(文本、图章名称、一对点)以及一种颜色。把矩形设置正确,大部分工作就完成了。剩下的就是了解哪些子类型自带外观,而哪些需要依赖查看器来绘制它们

矩形框才是注释本身,而不是文本
每一个注释调用都接收一个 TRect 参数,并且那个矩形框的含义与你传递给 TextOut 的坐标大不相同。对于文本备注来说,它是一个可点击的热点,也就是注释图标所在的小区域,点击该区域即可弹出评论。对于正方形或自由文本框来说,它是标记的可见范围。对于图章来说,它是图章艺术按比例缩放后的框。这些数字是 PDF 用户空间点,从页面的左下角开始测量,Y 轴向上增加,这与 HotPDF 其他部分使用的惯例相同
文本备注是最轻量级的子类型。您只需提供正文文本、用于显示图标的矩形区域、指示其是否默认打开的标志、图标名称以及颜色即可
Pdf.CurrentPage.AddTextAnnotation(
'Reviewer: confirm the totals on this line before sign-off.',
Rect(120, 700, 140, 720), // icon hotspot, ~20pt square
False, // closed until the reader clicks it
taComment, // bubble icon
clBlue);
这里的矩形故意做得很小,边长约为 20 磅,因为在有人点击之前,文本备注仅仅是一个图标。把矩形做大并不会得到一个大大的备注;你只会得到一个超大的点击目标,而且图标被固定在了一个角落上。Open 标志控制着文档加载时是否显示弹出窗口。如果将少数几个备注设置为 True,它们就会相互堆叠并覆盖在内容之上,因此请把这个设置留给你真正想让读者立刻看到的那个备注吧
图标名称源自 THPDFTextAnnotationType,它映射了标准的备忘图标:taComment、taKey、taNote、taHelp、taParagraph、taNewParagraph 和 taInsert。图标是该类型唯一改变的东西。它不会改变行为,而且值得了解的是,并非每个查看器都会绘制全部七种图标;在各种新旧阅读器中较安全的是 taComment、taNote 和 taHelp
自由文本写在页面上,但仍然是注释
自由文本注释看起来就像内容一样,因为该文本无需点击即可见,就像标题一样落在其矩形框内。但它仍然是一个注释,具有其意味着的所有可分离性,而这恰好就是用于审批印章或日后需要被移除的草稿标签所真正需要的。该签名将图标和打开标志替换成了对齐值
Pdf.CurrentPage.AddFreeTextAnnotation(
'DRAFT - not for distribution',
Rect(200, 210, 400, 235), // the box the text is laid into
ftCenter, // ftLeftJust / ftCenter / ftRightJust
clRed);
与文本备注相比,这里的矩形更重要,因为文本会在其中换行和对齐。如果框太短,文本就会在底部边缘被截断;如果框太窄,文本就会在你意想不到的地方换行。对齐方式来自 THPDFFreeTextAnnotationJust 且仅有三个值。由于自由文本是一种标记注释,在编辑器中打开该文件的阅读器可以将其作为一个整体进行选择、移动或删除,这正是决定您是使用自由文本还是直接使用 TextOut 绘制文字的区别所在。如果标签必须是永久性的,就绘制它。如果它是编辑性的并且意在以后删除,就把它变成注释
用于指示事物的几何和线条标记
正方形、圆形和线条是您用来指向某个区域而不是用文字来描述它的标记。AddCircleSquareAnnotation 通过 csCircle 或 csSquare 的 THPDFCSAnnotationType 覆盖了这两种盒装形状,由矩形给出该形状的边界
// A box drawn around a figure that needs attention
Pdf.CurrentPage.AddCircleSquareAnnotation(
'Check this region against the source data',
Rect(50, 300, 120, 360),
csSquare,
clGreen);
// A line, given two points rather than a rectangle
var
StartPt, EndPt: THPDFCurrPoint;
begin
StartPt.X := 130; StartPt.Y := 360;
EndPt.X := 250; EndPt.Y := 320;
Pdf.CurrentPage.AddLineAnnotation(
'Points from the note to the figure',
StartPt, EndPt,
clBlue);
end;
请注意,线条注释打破了矩形模式:它接受两个 THPDFCurrPoint 记录,一个起点和一个终点,因为线条是由其端点而不是边界框定义的。颜色用于设置描边。如果您需要箭头,HotPDF 提供了接受线端样式的 AddLineAnnotation 重载,但是简单的三参数形式会绘制一条裸线,这通常也是标注所需要的
文本标记子类型作用于您已经布置好的区域。AddHighlightAnnotation 接受一个矩形、可选内容以及一种默认为黄色的颜色,并像荧光笔一样为该区域着色。它旨在覆盖于真实文本之上,因此矩形应该与你绘制的文字边界相匹配,这意味着你通常要从你传递给 TextOut 的相同坐标来计算它,而不是靠猜测
印章依赖于查看器进行渲染
图章注释是最有可能在不同的阅读器中看起来不同的注释,其原因值得我们去理解。AddStampAnnotation 通过 THPDFStampAnnotationType 命名一个标准图章,其值包括 satApproved、satConfidential、satFinal、satDraft 和 satForComment
Pdf.CurrentPage.AddStampAnnotation(
'Approved for release on review',
Rect(50, 400, 200, 440),
satApproved,
clGreen);
印章名称只是一个请求。PDF 定义了一组标准印章名称,但没有定义它们背后的图样,因此每个查看器都会自带其自身的“批准”(APPROVED)或“机密”(CONFIDENTIAL)渲染效果,而有些查看器对于它们不识别的名称则根本不进行任何渲染。矩形框控制图样缩放进的范围,而颜色只是一个提示,查看器可能会也可能不会遵循。如果一个印章在任何地方都必须看起来完全一样,那么可靠的途径根本不是使用标准印章:用 TextOut 和绘制调用自己画出这个标记,或者将它放置为由你来控制外观的自由文本注释。当你想要获得在查看器中大家熟悉的外观且能够容忍其变化时,再去选择标准印章
文件附件遵循相同的“矩形加有效负载”模式。AddFileAttachmentAnnotation 需要描述、要嵌入文件的路径、回形针图标所在的矩形,以及一种颜色。文件包含在 PDF 中,而图标则是阅读者用来提取文件的把手
注释与 AcroForm 字段的区别
耗费时间最多的误解就是把注释当作表单字段对待。两者都是通过 /Annots 附加到页面上的,而且表单字段实际上是一个特殊的注释子类型(小部件),这就是为什么它们看起来相关。它们是不可以互换的。表单字段保存一个值,有一个名称,参与 Tab 键顺序,并且可以被提交、重置或编写脚本;您可以使用 AddTextField、AddCheckBox 和 AddPushButton 调用来创建这些字段,而不是本页上的那些注释调用。标记注释包含的是评论或形状,没有可提交的值,当你需要收集输入时,它就是错误的工具
实际的检验方法很简单。如果打算让用户输入、选择或点击并让文档记住它,您需要的是一个 AcroForm 字段。如果您正在留下笔记、标记区域,或是盖上一个伴随文件但并非数据的状态图章,您需要的是一个注释。将它们混淆会产生看起来正确但行为错误的文档:一个没人能填写的“字段”,或者在表单重置时消失的评论。带有字段类型、验证和提交动作的交互方面是独立的主题,在 AcroForm 字段和操作演练中有所介绍
组合一个页面
这些元素的组合方式与 HotPDF 其他部分的处理方式相同。设置文档属性,调用 BeginDoc,通过文本和图形调用绘制您需要的任何页面内容,在上面添加注释,并以 EndDoc 结束。注释附加到 CurrentPage 上,因此在 AddPage 之后它们会落在新页面上,如果您在分页之后添加了原本计划放在第一页上的备注,它将悄然出现在第二页
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'annotated.pdf';
Pdf.Compression := cmFlateDecode;
Pdf.FontEmbedding := True;
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 740, 0, 'Quarterly figures, draft for review');
Pdf.CurrentPage.AddTextAnnotation(
'Confirm the totals before sign-off.',
Rect(50, 720, 70, 740), False, taComment, clBlue);
Pdf.CurrentPage.AddFreeTextAnnotation(
'DRAFT', Rect(450, 720, 540, 745), ftCenter, clRed);
Pdf.CurrentPage.AddStampAnnotation(
'For comment', Rect(50, 660, 180, 695), satForComment, clGreen);
Pdf.EndDoc;
finally
Pdf.Free;
end;
当输出看起来不正确时,值得培养的一个最后反射动作是:在断定代码坏了之前,用多个查看器打开该文件。图章和那些较罕见的笔记图标通常是罪魁祸首,因为注释对阅读器来说只是一种请求而不是绘制的像素,所以 Acrobat 与轻量级查看器之间的差异通常只是遵循规范设计的正常现象,并非你代码调用中的 Bug
这里显示的注释调用是适用于 Delphi 和 C++Builder 的 HotPDF 组件的一部分