Skip to content

feat: integrate native AcroForm widgets as typed fields - #4140

Draft
cau-git wants to merge 8 commits into
mainfrom
feat/acroform-native-fields
Draft

feat: integrate native AcroForm widgets as typed fields#4140
cau-git wants to merge 8 commits into
mainfrom
feat/acroform-native-fields

Conversation

@cau-git

@cau-git cau-git commented Sep 2, 2026

Copy link
Copy Markdown
Member

What

Extracts native interactive PDF widgets (AcroForm) from docling-parse parsed pages and materializes them as keyless, format-neutral fillable field values in the DoclingDocument.

This supersedes and replaces #4055. That PR extracted fields late from pypdfium2 and attached legacy FormItem + GraphData objects after document assembly; this change deletes that path (docling/utils/form_utils.py) entirely. #4055 should be closed once this lands.

How

A small PdfFormFieldModel stage runs after OCR and before layout postprocessing — the only point in the pipeline where both inputs exist:

  • raw layout FORM cluster bboxes have not yet been tightened, and
  • parsed-page widgets (SegmentedPdfPage.widgets) are still available.

The stage:

  1. normalizes each widget into a format-neutral FieldValuePrediction (text / orig / bbox) — checkable controls resolve from widget-local /AS first, then /V; pushbuttons are excluded;
  2. binds each widget to a raw FORM cluster by strong coverage (deterministic tie-break on coverage → smallest container → id);
  3. puts unmatched widgets into one explicit page-local fallback region;
  4. never mutates the layout prediction and never infers labels.

Page assembly replaces matched FORM container elements with FieldRegionElements (reusing the raw region bbox and the cluster's visible children) and emits fallback regions with collision-free synthetic ids. Reading order places each region by bbox and materializes it as FieldRegionItem → FieldItem → FieldValueItem(kind="fillable") in native widget order, with no FieldKeyItem.

Boundaries

  • PdfWidget and raw AcroForm state (/Ff, /AS, field names) stay on SegmentedPdfPage.widgets; nothing PDF-specific enters DoclingDocument, FieldValueItem, or add_field_value().
  • No PdfWidget survives past the form-field stage.
  • Document schema is unchanged (1.10).
  • extract_form_fields=False and documents without widgets are exact no-ops (layout prediction identical either way).

Dependencies

Pins the AcroForm branches of the companion PRs (draft until they release):

pyproject.toml uses git-branch sources for these; they must be repointed to published versions before merge.

Tests

tests/test_form_extraction.py:

  • docling-parse exposes the complete widget contract in native order;
  • field mapping / assembly / materialization preserve regions and native order across matched + fallback + overlapping-FORM cases, with no FIELD_KEY;
  • full pipeline produces keyless format-neutral fields, and disabling the option is a no-op with an identical layout prediction.

What this does NOT solve (yet)

Deliberately out of scope for this PR — the fields are extracted and typed, but not yet semantically labeled or optimally ordered:

  • Field name / key association. Widgets are materialized as keyless FieldValueItems. Raw AcroForm names (/T) and descriptions (/TU) are available on SegmentedPdfPage.widgets but are not associated with the value widgets as FieldKeyItems. No key is inferred from geometry, proximity, or /T//TU (an earlier reconciliation experiment that did this produced wrong keys and leaked machine names like topmostSubform[0].Page1[0].f1_08[0]). Authoritative key↔value association is future work.
  • Form reading order. Field values are emitted in native widget order within each region, and regions are placed by bbox. This is not a true form-aware reading order — values are not interleaved with their visible printed labels, and multi-column / tab-order layouts are not reconstructed. Improving intra-region reading order is future work.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

DCO Check Failed

Hi @cau-git, your pull request has failed the Developer Certificate of Origin (DCO) check.

This repository supports remediation commits, so you can fix this without rewriting history — but you must follow the required message format.


🛠 Quick Fix: Add a remediation commit

Run this command:

git commit --allow-empty -s -m "DCO Remediation Commit for Christoph Auer <cau@zurich.ibm.com>

I, Christoph Auer <cau@zurich.ibm.com>, hereby add my Signed-off-by to this commit: 06790b294d6bbcc692a257d2a0c6fad6afc6a916
I, Christoph Auer <cau@zurich.ibm.com>, hereby add my Signed-off-by to this commit: ac110b9a584a20d1a7a2f2da2bec3262536fa02c"
git push

🔧 Advanced: Sign off each commit directly

For the latest commit:

git commit --amend --signoff
git push --force-with-lease

For multiple commits:

git rebase --signoff origin/main
git push --force-with-lease

More info: DCO check report

@mergify

mergify Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🔴 1 of 2 protections blocking · waiting on 👀 reviews

Protection Waiting on
🔴 Require two reviewer for test updates 👀 reviews
🟢 Enforce conventional commit

🔴 Require two reviewer for test updates

Waiting for

  • #approved-reviews-by >= 2
This rule is failing.

When test data is updated, we require two reviewers

  • #approved-reviews-by >= 2

Show 1 satisfied protection

🟢 Enforce conventional commit

Make sure that we follow https://www.conventionalcommits.org/en/v1.0.0/

  • title ~= ^(fix|feat|docs|style|refactor|perf|test|build|ci|chore|revert)(?:\(.+\))?(!)?:

Extract native interactive PDF widgets from docling-parse parsed pages and
materialize them as keyless, format-neutral fillable field values.

A small PDF form-field stage runs after OCR and before layout postprocessing —
the only point where both the raw layout FORM clusters and the parsed-page
widgets are available. It normalizes each widget into a format-neutral field
prediction, binds it to a raw FORM cluster (with a page-local fallback region
for unmatched widgets), and never mutates the layout prediction or infers
labels. Page assembly turns matched FORM clusters into FieldRegionElements and
emits fallback regions; reading order materializes them as
FieldRegionItem/FieldItem/FieldValueItem in native widget order.

Raw AcroForm state (/Ff, /AS, field names) stays on SegmentedPdfPage.widgets;
nothing PDF-specific enters DoclingDocument or add_field_value(). The legacy
late-pypdfium2 FormItem+GraphData path (form_utils.py) is removed.

Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
@cau-git

cau-git commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

Before / After

Before
image

After
image

@codecov

codecov Bot commented Sep 2, 2026

Copy link
Copy Markdown

@wittjeff

wittjeff commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Review notes on #4140, read against Well-Tagged PDF 1.0 (WTPDF), the basis of PDF/UA-2. The overall shape is right and I am building on it; these are the things I would change or decide before it lands.

  1. Push buttons are not excluded. The description says they are, but _normalize_widget only uses the pushbutton flag to skip the checkbox branch, so a push button becomes an empty fillable value. Signature fields take the same path because /V is a dictionary and docling-parse renders it as empty text. WTPDF 8.10.3.2 says a button's meaning is its CA caption; until docling-parse exposes /MK /CA, skipping push buttons matches the description. (Done in feat(pdf): key form fields by their /TU description and skip artifact widgets #4144.)

  2. Zero-size widgets are artifacts (WTPDF 8.9.2.4.13) and should be skipped. Timestamp-signature workflows produce these. (Done in feat(pdf): key form fields by their /TU description and skip artifact widgets #4144.)

  3. /TU as key. WTPDF 8.10.2.1 lists TU as a context source and says T is not one, which matches your finding that /T leaks machine names. Using /TU for FieldKeyItem needs no inference. (feat(pdf): key form fields by their /TU description and skip artifact widgets #4144.)

  4. Checkbox representation diverges from the HTML backend. This PR emits a FieldValueItem with text checked; html_backend.py emits CHECKBOX_SELECTED / CHECKBOX_UNSELECTED items inside the FieldItem. Both are valid docling-core shapes, but the serializers and downstream consumers will see two different trees for the same form. Worth picking one before this lands.

  5. Multi-select list boxes lose their value. docling-parse feat: added excel backend #334 only accepts string or name /V, so an array becomes an empty string. WTPDF 8.10.3.4 also says the Opt display text, not the export value, carries the intent, so choice fields should use the display text once /Opt is exposed.

  6. Regions matched against raw FORM clusters that postprocessing later drops end up as fallback regions with the dead cluster's bbox, overlapping the surviving FORM_AREA group. Not wrong, but noisy; it may be worth matching against the surviving clusters instead, or dropping matched regions whose source cluster did not survive.

  7. Coverage. The matched-form path is exercised only through a mocked parsed page; the real pipeline test hits the fallback branch. A fixture whose layout actually yields a FORM cluster would close that.

Longer-term direction (structure-tree labels, PrintField for non-interactive forms, ordering by structure rather than /Tabs, role and state slots in docling-core) is in #4143.

Document experiment findings on native widget state vs. visual layout
extraction colliding for filled form fields, alongside form field
model and pipeline updates.

Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
Materialize a non-pushbutton /Btn widget as an empty-text FieldValueItem
that nests a CHECKBOX_SELECTED/UNSELECTED child, instead of encoding state
as a synthesized "checked"/"unchecked" value string. The value text stays
empty so the doclang serializer inlines the <checkbox> token into <value>.
Keeps /AS as the authoritative state and preserves the raw value in orig.

Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
…abel

Reconcile the native /Btn widget with the layout CHECKBOX_* cluster it
overlaps: match on widget-rect-in-cluster overlap, lift the cluster's
option label (mark glyph stripped) onto the nested checkbox child, and
promote the cluster out of the plain-text stream so it is no longer a
disjoint duplicate. /AS stays authoritative for the selection state,
overriding the glyph classifier. Unselected boxes with no overlapping
cluster keep the widget-only path.

Verified on f1040s1_filled: the checked option serializes as
<value class="fillable"><checkbox class="selected"/>4797</value> with the
duplicate cluster removed.

Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
…ts bbox as prov

Drop the mark-glyph sanitation; the checkbox cluster's text is now lifted as-is
onto the value, and the matched cluster bbox becomes the value's prov so the box
wraps the whole checkbox+label rather than the tiny widget rect.
…ield_item

Introduce an item layer between field region and value: FieldRegionPrediction
now holds FieldItemPrediction items, each a keyless single value (today's flat
shape, unchanged) or a keyed group. A widget with no smaller enclosing container
than a text paragraph is matched to that paragraph -- the smallest enclosing
cluster wins over the page-wide FORM -- and widgets sharing one paragraph
accumulate under a single key (the paragraph text/bbox), with the paragraph
promoted out of the plain-text body. Reading order materializes the key via
add_field_key.

IRS Schedule 1 line 7 ("check here [] and enter amount repaid: 1221.00") now
emits one field_item: key + checkbox-unselected value + fillable "1221.00",
instead of two disjoint value-only fields.
…ld_region

An inlined-widget paragraph was pulled out into its own field_region, detaching
it from the list (and enclosing form region) it belonged to. Keep the paragraph
cluster in the body and attach the keyed item onto its TextElement; reading order
now emits a field_item in the paragraph's own place -- inside its list when it is
a list item, else under the current parent -- with its text as the key. No
separate field_region is created for the inline case.

On IRS Sch.1, line 7's field_item now sits inside its list within the form's
existing field_region instead of a standalone region ahead of it.

Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
A widget of zero height or width is an artifact (Well-Tagged PDF 1.0,
8.9.2.4.13) and a push button holds no field value; both were being
emitted as empty fillable values. Skip them before normalization.

Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
wittjeff added a commit to wittjeff/docling that referenced this pull request Sep 3, 2026
Stacked on docling-project#4140. Well-Tagged PDF 1.0, the basis of PDF/UA-2, lists a
field's /TU entry among the sources of context for a widget (8.10.2.1)
and states that the field name (/T) does not contribute. A widget that
is not inlined in a paragraph (a FORM-matched or fallback field) now
gets a FieldKeyItem with its /TU text, without provenance since it is
dictionary metadata rather than page text. Widgets inlined in a
paragraph keep the paragraph as their key.

FieldItemPrediction records where the key came from (layout or
tooltip) so structure-tree labels can be added later without changing
the contract. Docs and option text updated: fields are no longer
described as keyless.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Jeff Witt <1848307+wittjeff@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants