跳到內容
getnextpdf.com

從可填寫表單到凍結記錄:AcroForm 填寫與扁平化

Spec: ISO 32000-2, §12.7

一份 PDF 表單有兩段生命。它一開始是可填寫的:一組讓人輸入、勾選或從中挑選的有型別欄位。接著,當約定達成時,它便成為一份凍結記錄:那些值被印進頁面本身,讓每一個檢視器、在每一台裝置上,都看到與所約定一模一樣的內容。NextPDF 建構前者並產生後者,且帶有一項刻意的保證——它不會在中途悄悄把那些值丟棄。

「我所填寫的」與「你所看到的」之間的落差,正是表單出錯之處。

一個可填寫欄位,在技術上是一個繪製在頁面之上的小型互動小工具。不同的檢視器可能以不同方式繪製它。有些會兌現所儲存的值,有些會用一個你並未持有的字型重新產生外觀,有些則讓讀者再次編輯它。對於一份你希望協作者繼續編輯的草稿,那正是其用意。但對於一份已約定內容的已簽署副本,那卻是一項負債:記錄不該取決於是哪個應用程式開啟它,也不該在事後仍可編輯。

扁平化弭平了這道落差。它取每個欄位的目前值,並把它印進頁面,成為普通、不可變的圖形——與一個標題或一個標誌同一種內容。在那之後,便沒有欄位可編輯,也沒有外觀需重新產生。文件處處顯示一樣的、同一件東西。

  • AcroForm 是文件的互動式表單:一棵宣告於目錄中的有型別欄位樹(Spec: ISO 32000-2, §12.7)。
  • 每個欄位都透過一個小工具註解而變得可見——那是你點按或輸入其中的頁面上矩形(Spec: ISO 32000-2, §12.5)。
  • NextPDF 為每一種受支援的非簽章表單控制項提供有型別的建構器——文字、核取方塊、單選按鈕、清單方塊與下拉方塊(選擇),以及按鈕——外加一個把它們寫成正規 PDF 物件的欄位管理員。
  • 扁平化把每個欄位的值繪製進頁面內容串流,並移除此時已多餘的互動式表單,留下一份凍結記錄。
  • 如果你要求把一份沒有頁面的文件扁平化,NextPDF 不會悄悄摧毀你的欄位值。它會保留表單、對你提出警告,並讓你加上一頁後正確地扁平化。

心智模型是兩層。欄位是資料:一個名稱、一個型別、一個值,以及一組旗標。小工具是圖像:特定頁面上一個讓檢視器與欄位互動的矩形(Spec: ISO 32000-2, §12.5)。一個欄位甚至能透過數個小工具浮現——那正是單選群組的運作方式,數個頁面上的選項被接到一個底層的值上。

NextPDF 為每一種欄位型別提供其自己的有型別建構器,因此你永遠不必親手組裝一個原始字典。型別替你帶上 PDF 正確的細節。核取方塊、單選按鈕與按鈕在規格中全都共用同一個底層表單型別,並靠其旗標來區分;引擎會依你所選的型別設定那些旗標,而不要求你記得哪個位元代表「單選」。清單方塊與下拉方塊都是選擇欄位;同樣地,建構器會挑出正確的編碼。你以文字陳述欄位型別一次,位元組便隨之而來。

扁平化是後半部。扁平化器依小工具註解所屬的頁面把它們分組,並以每個小工具的矩形決定其在該頁的放置位置,接著把每個值繪製為一小串內容串流運算子——設定顏色、設定文字位置、繪出字符——附加到該頁既有的內容上(Spec: ISO 32000-2, §8.4)。值不再是一個活的欄位,而成為被印上去的墨跡。由於表單不再攜帶任何互動式欄位,引擎接著便移除 AcroForm 項目:再也沒有什麼可供互動了。

  1. Declare typed fieldsAdd text, checkbox, radio, choice, and button fields through their typed builders; the engine sets the spec-correct PDF type and flags.
  2. Place the widgetsEach field is drawn as a widget annotation — a rectangle on a chosen page that a viewer can type into, tick, or pick from.
  3. Collect inputShip it fillable: a reader supplies values, or your code sets them, leaving a filled but still-editable document.
  4. Flatten the valuesRender each field's value into the page content stream as graphics; the painted value is now immutable.
  5. Drop the interactive formWith every value baked in, remove the AcroForm so nothing remains editable — a frozen record of what was agreed.
From a fillable form to a frozen record: declare typed fields, draw their on-page widgets, fill in values, then flatten those values into immutable page graphics and drop the now-empty interactive form.

一份小巧、有代表性的表單:建構幾個有型別的欄位,再把它扁平化為一份凍結記錄。

<?php
declare(strict_types=1);
use NextPDF\Core\Document;
$document = Document::createStandalone();
$document->addPage();
// Typed builders, called straight on the document. You pick the field
// type by choosing its builder method — textField, checkBox, comboBox —
// and you pass the value to freeze at creation time. The engine writes
// the spec-correct PDF type and flags for you.
$document->textField('full_name', x: 40, y: 700, w: 220, h: 18, default: 'Ada Lovelace');
$document->checkBox('agree_terms', x: 40, y: 660, size: 14, checked: true);
$document->comboBox(
'plan',
x: 40,
y: 620,
w: 160,
h: 18,
items: ['Starter', 'Team', 'Enterprise'],
selected: 'Team',
);
// Flatten: the values become immutable page graphics and the
// interactive AcroForm is dropped. The result is a frozen record.
$document->flattenForms();
$bytes = $document->getPdfData();

flattenForms() 之前,這是一份可填寫的表單。在它之後,那些同樣的值被印進了頁面,再也沒有欄位可供更改。你藉由挑選其建構器方法——textFieldcheckBoxcomboBox——來選擇欄位型別,因此一個錯誤的型別無法被編碼成一個鬆散的字串:打錯字會是一個對不存在方法的呼叫,在任何欄位被寫入之前就被攔下,而不是一個悄悄錯誤的欄位。那正是引擎其餘部分所採取的同一套「拒絕猜測」立場;參見拒絕猜測的 API

陷阱在於相信填寫一個欄位就會凍結它。並不會。一個已填寫的欄位仍攜帶一個有能力的檢視器可編輯的活值,以及一個某些檢視器會重新產生的外觀。「我設定了值」與「文件現在是一份固定記錄」是兩個不同的狀態。唯有扁平化能從前者跨越到後者,因為唯有扁平化能把值轉成不再表現得像欄位的頁面圖形。

與之鏡像相反的錯誤,是把一份你仍需蒐集輸入的草稿扁平化。一旦扁平化,欄位便消失了——那正是其用意——所以請把你打算定稿的那份副本扁平化,而非你仍在傳閱的那一份。

NextPDF 的表單支援是完整核心:為常見互動式欄位控制項提供的有型別建構器、一個表單扁平化器,以及一個把欄位寫成正規 PDF 物件的欄位管理員。本頁描述的就是那個核心介面。

AcroForm fields and flattening — edition availability
EditionAvailability
Core

Typed builders for text, checkbox, radio, choice (list box and combo box), and push-button fields; widget placement; a field manager; and a form flattener that bakes values into page graphics. Available in every edition.

ProNot in this edition
EnterpriseNot in this edition

扁平化在設計上是單向的。它移除互動式表單,好讓記錄固定下來;它不是一個「暫時鎖定」的切換,而且沒有一個能從被印上的圖形重新推導出可編輯欄位的「反扁平化」。如果你需要一份人們能繼續編輯的副本,就保留未扁平化的表單,並把一份複本扁平化。

扁平化也不是簽章。它讓文件在普通檢視器的意義上變得不可編輯,但它並不以密碼學方式證明是誰產生了它、或它自此未曾變更。當記錄必須可被證明就是當初所約定的那一份時,請先扁平化、再簽署;參見簽章在 PDF 中如何安置

最後,一份已加標籤、可存取的表單,與一份已扁平化的表單是各自獨立的關注點。如果可填寫的版本必須能與輔助科技搭配使用,那麼欄位在仍處於互動狀態時就需要可存取的名稱與結構;參見是什麼讓 PDF 可存取

  • AcroForm — 一份 PDF 的互動式表單:那棵宣告於文件目錄中、讓檔案得以填寫的有型別欄位樹(Spec: ISO 32000-2, §12.7)。
  • 欄位 — 表單控制項的資料面:一個名稱、一個型別、一個值,以及旗標。與它在頁面上的外觀無關。
  • 小工具註解 — 頁面上那個可見、可點按的矩形,檢視器透過它與欄位互動(Spec: ISO 32000-2, §12.5)。一個欄位可以有數個。
  • 選擇欄位 — 一個提供一組選項的欄位:清單方塊把它們攤開顯示,下拉方塊則以下拉形式顯示。兩者是同一個 PDF 欄位型別。
  • 扁平化 — 把每個欄位的目前值繪製進頁面、成為不可變的圖形,並移除互動式表單,產生一份凍結記錄。
  • 凍結記錄 — 一份已扁平化的文件:它在每個檢視器中都顯示一件固定的東西,且再無欄位可編輯。