PDF 中的 Javascript






4.98/5 (33投票s)
本文介绍如何使用 Javascript 代码创建交互式 PDF 文档。
目录
引言
很快,Javascript 就能渲染 PDF 文档。与此同时,功能上与之相反——在 PDF 文档中执行 Javascript——早已可用。本文将介绍这一功能。
任何软件都不仅包含积极使用的一套功能,也包含一部分很少使用的功能。有时后者占的比重会很大。例如,您可以想想 Microsoft Word 或您最喜欢的 IDE 的功能。它们很可能包含足够多的您从未用过的功能。
便携式文档格式(Portable Document Format)也包含许多很少使用的功能。我们都习惯了 PDF 文档中的文本和图像,但这仅仅是该格式所提供功能的一个子集。PDF 包含一套用于创建文档的功能,这些文档可以响应阅读者的操作而改变其内容。其中一项功能就是在 PDF 文档中使用 Javascript 的能力。
PDF 中的 Javascript 最常用于以下任务:
- 响应某些事件来更改文档内容。例如,在打印前隐藏文档的一部分,或在文档打开时预填充某些表单字段。
- 限制阅读者的操作。例如,验证输入的表单字段值。
- 在文档中植入恶意代码。
为 PDF 查看器开发了一个Javascript API,以便能够解释 Javascript 代码。该 API 主要为 Adobe Acrobat 系列产品开发,但其他查看器通常也支持该 API 的一部分。
让我们来看一些示例。
Hello World
首先,来看一个传统的“Hello World”示例。请注意,我将使用 C# 和 Docotic.Pdf 库来进行示例。您可以在文章末尾下载所有示例的源代码。
using BitMiracle.Docotic.Pdf;
namespace JavascriptInPdf
{
public static class Demo
{
public static void Main(string[] args)
{
PdfDocument pdf = new PdfDocument();
pdf.OnOpenDocument = pdf.CreateJavaScriptAction("app.alert(\"Hello CodeProject!\", 3);");
pdf.Save("Hello world.pdf");
}
}
}
如果使用 Adobe Reader 打开该示例创建的 PDF,您应该会看到类似下面截图的内容。

那么,这个示例做了什么?以下是关键的一行:
pdf.OnOpenDocument = pdf.CreateJavaScriptAction("app.alert(\"Hello CodeProject!\", 3);");
PDF 支持操作(actions)。这是一种在事件发生时执行的操作。例如,点击 PDF 文档大纲中的链接可能会触发一个操作,使查看器在文档中打开一个页面。

定义了不同类型的操作。Javascript 操作是其中一种类型。可以使用 `PdfDocument.CreateJavaScriptAction` 方法创建这种类型的操作。该方法接受 Javascript 代码。然后可以将该操作附加到 `OnOpenDocument` 事件。显然,当文档在查看器中打开时,就会发生此事件。
以下是 Javascript 代码:
app.alert("Hello CodeProject!", 3);
`app` 静态类是 Javascript API 的一部分。此类提供与 PDF 查看器通信的方法。特别是,此类包含带有多个重载的 `alert` 方法。alert 方法可用于显示带有消息的模态窗口。上面的代码使用了带有可选第二个参数的重载。此参数指定要显示的图标(“3”是状态图标的代码)。
更复杂的场景
让我们尝试更实用的东西。
有很多 PDF 文档包含可填写的表单。带有表单的文档可以是存款协议、签证申请表、调查问卷等。这些文档可以在查看器中方便地填写并打印或保存以备将来使用。此类文档的创建者可以利用 Javascript 进一步提升用户体验。
可填写的表单通常包含日期字段。这些字段可能看起来像这样:

当然,文档的创建者可以只在文档中添加日期字段然后停止。但是,通过将当前日期放入日期字段来帮助稍后填写文档的人会很好。在大多数情况下,这正是人们所期望的。
日期字段的默认值
我不会详细介绍如何创建日期字段。我们只关注 Javascript 部分。因此,我们需要将当前日期放入一个日期字段(实际上有三个字段,分别代表日、月和年)。这是脚本:
function setDay(date) {
var dayField = this.getField("day");
if (dayField.value.length == 0) {
dayField.value = util.printd("dd", date);
}
}
function setMonth(date) {
var monthField = this.getField("month");
if (monthField.value.length == 0) {
monthField.value = util.printd("date(en){MMMM}", date, true);
}
}
function setYear(date) {
var yearField = this.getField("year");
if (yearField.value.length == 0) {
yearField.value = util.printd("yyyy", date);
}
}
function setCurrentDate() {
var now = new Date();
setDay(now);
setMonth(now);
setYear(now);
}
setCurrentDate();
正如您所见,代码比“Hello World”示例要多得多。使用字符串来存储所有这些代码可能不太方便,因为需要转义引号。并且由于缺乏格式,以后编辑此类代码可能会很痛苦。让我们将包含代码的文本文件放入应用程序资源,并使用 `CreateJavaScriptAction` 方法的另一个重载。
pdf.OnOpenDocument = pdf.CreateJavaScriptAction(Resources.SetCurrentDate);
打开时,带有脚本的文档应如下所示:

仔细看看下面这段代码:
monthField.value = util.printd("date(en){MMMM}", date, true);
请注意,我正在使用 `util.printd` 方法的重载来获取月份的本地化名称。这在 Adobe Reader 中按预期工作,但不幸的是,其他 PDF 查看器可能不支持 Javascript API 的所有内置方法。如果您打算支持 Adobe Reader 以外的任何内容,则应考虑这一点。另一种方法是实现与 `util.printd` 相同功能的自定义代码。
另请注意,代码仅在字段为空时才填充。没有这个额外的检查,可能会发生以下情况:一个人填写了文档并保存了它,但下次打开文档时,所有日期字段都会被当前日期重置。
数据验证
假设您想确保日和年是数字。这很简单。让我们使用以下代码来实现:
function validateNumeric(event) {
var validCharacters = "0123456789";
for (var i = 0; i < event.change.length; i++) {
if (validCharacters.indexOf(event.change.charAt(i)) == -1) {
app.beep(0);
event.rc = false;
break;
}
}
}
validateNumeric(event);
每当在字段中输入内容时,PDF 字段都会触发 `OnKeyPress` 事件。您可能希望创建一个带有验证代码的操作,并将其附加到字段的 `OnKeyPress` 事件。
PdfJavaScriptAction validateNumericAction = m_document.CreateJavaScriptAction(Resources.ValidateNumeric);
dayTextBox.OnKeyPress = validateNumericAction;
yearTextBox.OnKeyPress = validateNumericAction;
在此之后,填写表单的人将无法在日和年字段中输入除数字以外的任何内容。从剪贴板粘贴的任何内容也将被验证。
数据同步
很多时候,同一数据应该在表单的不同部分多次输入。在这种情况下,一些 Javascript 代码可以帮助消除这种重复工作的需要。
假设有一个像这样的文档:

最好能同步这两个字段。
使用以下 Javascript 方法可以实现:
function synchronizeFields(sourceFieldName, destinationFieldName) {
var source = this.getField(sourceFieldName);
var destination = this.getField(destinationFieldName);
if (source != null && destination != null) {
destination.value = source.value;
}
}
Javascript 方法可以这样使用:
PdfDocument pdf = new PdfDocument(“Names.pdf”);
pdf.SharedScripts.Add(
pdf.CreateJavaScriptAction(Resources.SynchronizeFields)
);
pdf.GetControl("name0").OnLostFocus = pdf.CreateJavaScriptAction("synchronizeFields(\"name0\", \"name1\");");
pdf.GetControl("name1").OnLostFocus = pdf.CreateJavaScriptAction("synchronizeFields(\"name1\", \"name0\");");
pdf.Save("NamesModified.pdf");
这将确保两个字段中的数据始终相同。每当任何字段失去焦点时,数据就会同步。请注意,`synchronizeFields` 方法被放入共享脚本集合(`PdfDocument.SharedScripts`)中。这样做是为了从多个操作中使用相同的代码。
摘要
正如您所见,Javascript 不仅可以在 Web 开发中使用。通过一些额外的努力,可以创建一个带有 Javascript 代码的 PDF 表单。这样的表单可以像一个 UI 设计周到的应用程序一样取悦填写者。
请勿误解我的意思,PDF 文件最重要的部分是其内容。Javascript 只是一种不错的补充。而且存在一些限制:
- 为 PDF 文件编写 Javascript 代码不像为网页编写那样方便。
- Adobe Reader 以外的 PDF 查看器对 Javascript API 的支持不完整。
- 查看器中可以禁用脚本的执行。
此外,PDF 文件中的 Javascript 存在安全威胁。不时地,会在查看器中发现(并修复)各种漏洞。了解了所有这些,如果一个打开的 PDF 文件提供您下一盘象棋来分散您对后台正在执行的恶意行为的注意力,也不要感到惊讶。 " />