跳转到内容

创建并预先填入 PDF 表单

AcroForm 是 PDF 中的交互式表单。本 recipe(示例)会创建一份表单,并为各字段预先填入初始值。核心的 HasFormFields trait 是用于表单编写(authoring)的 API:你会在同一次调用中创建每个字段并提供其值。文本字段使用 default 参数,选项字段使用 selected,复选框则使用 checked。打开 PDF 时,各字段已经填好值,并且在符合规范的阅读器中仍可编辑。任何打开文件的人仍然可以更改这些值。本 recipe 取自 examples/30-form-fields.php

适用范围界限。 核心在构建文档时会创建并填入表单字段, 而不是事后另行处理。它不会读取已存在于第三方 PDF 中的表单, 也不会把一份值映射表合并进去。在这里,“填入”指的是带着值编写表单, 而不是加载并填写一份外部 PDF。对外部表单进行往返处理(round-trip)属于 Premium 与服务器端的能力,而非核心的公开 API。

Terminal window
composer require nextpdf/core:^3

AcroForm 字段会将当前值存放在字段字典的 V 条目中(ISO 32000-2 §12.7)。它也可以在 DV 中存放一个可选的默认值;执行重置表单操作时,字段会回到这个值。NextPDF 会使用你传给每个字段构造函数的值来设置 V。文本渲染使用默认外观字符串(DA)。

这个配置文件之所以标记为 structural,是因为文档带有一个 trailer /ID 数组。后处理阶段会先规范化这个易变的标识符,再进行比较。

NextPDF\Core\Concerns\HasFormFields(混入 Document):

  • textField(string $name, float $x, float $y, float $w, float $h, string $default = '', array $options = []): static
  • checkBox(string $name, float $x, float $y, float $size, bool $checked = false): static
  • radioButton(string $name, float $x, float $y, float $size, string $value, string $group): static
  • comboBox(string $name, float $x, float $y, float $w, float $h, array $items, string $selected = ''): static
  • listBox(string $name, float $x, float $y, float $w, float $h, array $items, string $selected = ''): static
  • button(string $name, float $x, float $y, float $w, float $h, string $caption, string $action = ''): static

其中 defaultcheckedselected 参数用于传入预先填入的值。

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Pre-filled Form');
$doc->addPage();
// Text field pre-filled with a value (sets the field dictionary /V entry).
$doc->textField(name: 'full_name', x: 20, y: 30, w: 90, h: 8, default: 'Ada Lovelace');
// Choice field pre-selected.
$doc->comboBox(
name: 'country',
x: 20, y: 45, w: 90, h: 8,
items: ['United Kingdom', 'Taiwan', 'Japan'],
selected: 'Taiwan',
);
// Checkbox pre-checked.
$doc->checkBox(name: 'newsletter', x: 20, y: 60, size: 5, checked: true);
$doc->save(__DIR__ . '/prefilled-form.pdf');
echo "Wrote prefilled-form.pdf\n";

以下完整示例对应 examples/30-form-fields.php,是一份包含多个区块的注册表单。它会将输出写入 NEXTPDF_COOKBOOK_OUTPUT 指定的位置,供测试框架使用。

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Customer Registration — Pre-filled');
$doc->addPage();
$doc->setFont('helvetica', 'B', 20);
$doc->cell(0, 14, 'Customer Registration Form', newLine: true);
$doc->ln(4);
$leftMargin = 15.0;
$fieldX = 70.0;
$fieldW = 120.0;
$fieldH = 8.0;
$rowSpacing = 12.0;
// --- Personal information, pre-filled ---
$prefill = [
'full_name' => 'Ada Lovelace',
'email' => '[email protected]',
'phone' => '+44 20 7946 0000',
'company' => 'Analytical Engines Ltd',
];
$y = 40.0;
$doc->setFont('helvetica', '', 10);
foreach ($prefill as $name => $value) {
$doc->setXY($leftMargin, $y);
$doc->cell(50, $fieldH, ucwords(str_replace('_', ' ', $name)) . ':');
$doc->textField(
name: $name,
x: $fieldX,
y: $y,
w: $fieldW,
h: $fieldH,
default: $value,
options: ['maxLen' => 80],
);
$y += $rowSpacing;
}
// --- Choice field, pre-selected ---
$y += 6;
$doc->setXY($leftMargin, $y);
$doc->cell(50, $fieldH, 'Country:');
$doc->comboBox(
name: 'country',
x: $fieldX,
y: $y,
w: $fieldW,
h: $fieldH,
items: ['United States', 'United Kingdom', 'Germany', 'Japan', 'Taiwan'],
selected: 'United Kingdom',
);
// --- Checkboxes, pre-set ---
$y += $rowSpacing + 6;
$doc->setXY($leftMargin, $y);
$doc->cell(0, 7, 'Subscribe to newsletter');
$doc->checkBox(name: 'newsletter', x: $leftMargin + 70, y: $y, size: 5, checked: true);
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');
$doc->save($out !== false ? $out : __DIR__ . '/registration-prefilled.pdf');
echo "Wrote pre-filled registration form\n";

预期输出:

Wrote pre-filled registration form

当你打开 PDF 时,每个字段都已经填好值,并且仍可编辑。

  • 字段名称必须唯一。 两个共用同一名称的字段在符合规范的阅读器中会变成一个共享值的逻辑字段。对联动字段来说,这正是你要的结果;但在其他情况下会是意外。
  • 单选按钮组语义。 radioButton() 把每个选项绑定到一个 group。被选中的选项,是其 value 与组值相符的那一个。每个组同一时间只会有一个选项处于打开状态。
  • maxLen 只是一个提示。 maxLen 选项会在符合规范的阅读器中限制输入长度。它并不会限制你预先填入的存储值。
  • API 中的坐标以左上角为基准。 这个 trait 会替你转换到 PDF 的左下角原点,所以请按示例所示传入左上角坐标。
  • 不支持填写外部 PDF。 核心没有任何方法能加载既有的第三方表单并应用值映射表。请参见上方的适用范围界限。

表单编写的性能会随字段数量线性增长。每个字段都会新增一个 widget 注释与一份外观。数百个字段仍远在 1500 ms / 64 MB 的预算之内。

预先填入的值会原封不动地写入字段字典。在把任何来自不受信任来源的值放进你要分发的文档之前,必须先对其进行转义或验证。预先填入的表单并未受到保护:任何能打开该 PDF 的人都可以读取并更改其中的值。当表单内容属于敏感信息时,请把本 recipe 与 使用权限加密搭配使用,并留意该处关于阅读器需配合的提醒。

陈述规范条款参考 ID
字段的当前值存储在字段字典的 V 条目中。ISO 32000-2§12.7
默认值存储在 DV 条目中,并会在表单重置时还原。ISO 32000-2§12.7
字段文本格式化使用默认外观字符串 DAISO 32000-2§12.7

NextPDF 会输出所引用条款描述的 AcroForm 结构。它并未声称完全符合 ISO 32000-2。