Form: AcroForm interactive fields and flattening
At a glance
Section titled “At a glance”The Form module creates interactive forms for Portable Document Format (PDF) documents. It builds text, checkbox, radio, choice (list/combo), button, and signature fields, organizes them into a parent/child hierarchy, writes them as PDF objects with appearance streams, and can flatten the form into static page content.
Install
Section titled “Install”composer require nextpdf/core:^3Conceptual overview
Section titled “Conceptual overview”An interactive PDF form is the AcroForm: a document-level field hierarchy whose terminal fields are paired with widget annotations on pages. ISO 32000-2 §12.7 defines that form, its field dictionaries, the AcroForm root, and the field hierarchy. This module encodes those structures for the engine.
FormFieldManager is the primary surface. It exposes textField(),
checkBox(), radioButton(), button(), and signatureField() builders,
tracks fields, and serializes them with writeFields(). It emits one Form
XObject appearance stream for each widget (/Subtype /Form, /BBox), as §8.10
requires. FormField is the field value object. FormFieldDictionaryBuilder
converts a field to its PDF dictionary and applies type-specific entries and
flag bits for text, checkbox, choice, and button fields. The manager and
FormField are @since 1.0.0. The dictionary builder is @since 1.1.0.
FormFieldHierarchy models the parent/child field tree with addChild(),
getRootFieldNames(), getChildren(), getParent(), and a
walkDepthFirst() visitor. A fully-qualified field name is the dotted path
through this hierarchy, matching the nested-field model in §12.7.
FormFlattener renders the form into static page content. flatten() returns
a FlattenResult, so you can freeze a filled form for archival. FieldMDP and
FieldMdpAction model the field-lock transform. A signed document can lock a
named set of fields against further modification. FieldMdpAction enumerates
the lock scope, and requiresFieldList() indicates when an explicit field list
is mandatory. FieldMDP is @since 2.0.0.
API surface
Section titled “API surface”| Class | Key members | Role |
|---|---|---|
FormFieldManager | textField(), checkBox(), radioButton(), button(), signatureField(), addChildField(), getHierarchy(), writeFields() | Field builder + serializer (@since 1.0.0) |
FormField | field value object | One AcroForm field (@since 1.0.0) |
FormFieldDictionaryBuilder | buildFieldDictionary(), applyTextFieldOptions(), applyCheckBoxOptions(), applyChoiceFieldOptions(), applyButtonOptions() | Field-to-dictionary builder (@since 1.1.0) |
FormFieldHierarchy | addChild(), getRootFieldNames(), getChildren(), getParent(), walkDepthFirst() | Parent/child field tree (@since 2.0.0) |
FormFlattener | flatten(array $fields, array $pages): FlattenResult | Flattens the form to static content (@since 1.0.0) |
FieldMDP | toTransformParams() | Field-lock (FieldMDP) transform (@since 2.0.0) |
FieldMdpAction (enum) | requiresFieldList() | Field-lock scope (@since 2.0.0) |
Run composer docs:generate-api-php -- --module=Form to generate the full PHPDoc table.
Code sample — Quick start
Section titled “Code sample — Quick start”Source: examples/30-form-fields.php.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Form\FormFieldManager;
$form = new FormFieldManager();
$form->textField(name: 'applicant_name', x: 50, y: 700, w: 200, h: 18);$form->checkBox(name: 'agree_terms', x: 50, y: 660, size: 12);$form->radioButton(name: 'plan', x: 50, y: 620, size: 12 /* + option set */);
// The Writer invokes $form->writeFields(...) during document serialization.Code sample — Production
Section titled “Code sample — Production”Build a signed-document field set, then lock the signed fields with FieldMDP.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Form\FieldMDP;use NextPDF\Form\FieldMdpAction;use NextPDF\Form\FormFieldManager;
$form = new FormFieldManager();$form->textField(name: 'contract_value', x: 50, y: 700, w: 160, h: 18);$form->signatureField(name: 'approver_sig', x: 50, y: 600, w: 200, h: 60);
// Lock only the named fields after signing.$lock = new FieldMDP( action: FieldMdpAction::Include, fields: ['contract_value'],);
$transformParams = $lock->toTransformParams();// $transformParams is attached to the signature's transform method by the signing layer.Edge cases & gotchas
Section titled “Edge cases & gotchas”- A field name is a fully-qualified dotted path through
FormFieldHierarchy. In a viewer, two terminal fields with the same fully-qualified name are the same logical field. You are responsible for uniqueness. FieldMdpAction::requiresFieldList()tells you when a field list is mandatory. AnInclude/Excludeaction without the required list creates a malformed lock; check the flag.FormFlattener::flatten()deliberately removes interactivity. The result is static content. Keep the interactive source if you need it later.- Appearance streams are emitted per widget. A field with no appearance can render inconsistently across viewers; let the manager generate the appearance instead of omitting it.
signatureField()creates only the field placeholder. The Security/signing layer produces the actual signature; this module does not.
Performance
Section titled “Performance”Field creation and serialization are O(n) in the field count, plus one Form
XObject appearance stream per widget. Flattening cost scales with rendered
content, not with the field count. The default reference workload stays within
the 1500 ms wall / 64 MB peak budget. The reproducibility profile is
structural: object numbers and trailer /ID vary between runs. Two documents
with the same form are structurally equal but not byte-identical.
Security notes
Section titled “Security notes”Form field values are user input. The module escapes string values for PDF
serialization with PdfStringEscaper, so a field value cannot break out of its
PDF string and inject structure. When a form is encrypted, the document’s
AES-256 encryption covers field content. FieldMDP is a security control: it
locks named fields against post-signature modification. A field-lock that does
not match the signed field set undermines that control; set the lock scope
deliberately. Treat any value extracted from a filled form as untrusted input.
See the engine security model in /modules/core/security/.
Conformance
Section titled “Conformance”The form structures this module emits follow the interactive-form model in ISO
32000-2 §12.7: the AcroForm root, field dictionaries, and the document field
hierarchy. Per-widget appearances are emitted as Form XObjects per §8.10 and
documented inline in src/Form/. tests/Unit/Form/ exercises these
implementation facts. They are not a statement of end-to-end PDF 2.0
conformance. Full-document conformance is validated by the oracle and golden
suites in /modules/core/conformance/.
See also
Section titled “See also”- Navigation module — widget annotations that pair with fields.
- Security module — signing and FieldMDP trust model.
- Accessibility module — tagging form fields for PDF/UA.
- Conformance overview