Skip to content

Make a flat form fillable: create its fields from Iris's HTML - #16

Merged
bbertucc merged 2 commits into
mainfrom
flat-form
Oct 6, 2026
Merged

bbertucc merged 2 commits into
mainfrom
flat-form

Conversation

@bbertucc

@bbertucc bbertucc commented Oct 6, 2026

Copy link
Copy Markdown
Member

Closes #13.

A PDF with no form fields (a flat or scanned form) now gets them from Iris's HTML. Each <input>, <select> and <textarea> is placed on the blank next to its label. The blank is found in the rendered page: an underline, a box, a table cell (split at faint or dotted row lines), or a check box or radio circle. A field with no blank found is left out rather than put in the wrong place, and is warned field_not_placed. Fields made are listed in form.created, and --values fills them by name.

  • Scans use Tesseract's word boxes, and line tracing allows for skew.
  • Scans whose words are only placed approximately (no Tesseract) get no fields. Each control is warned instead.
  • Fix: table header ids repeated across a page's tables, so a later table's cells pointed at the first table's headers.

Not PDF/UA: typed text uses base-14 Helvetica, which is not embedded. An output with created fields warns font_not_embedded and does not claim PDF/UA-1, the same as source AcroForms. I tried an embedded font in /DR, but mupdf's own appearance streams still use unembedded Helvetica, and viewers handle Type0 fonts in /DA poorly.

Tests: new fixtures form-lines (drawn), form-scan and form-scan-skewed (rendered at 300 dpi; the last rotated 1.5°). They check names, types and positions, filling, /TU names, field_not_placed, and the approximate case. A real two-page application (kept local) gets 73 of 79 fields with none misplaced. Of the 6 missed, 3 have labels the OCR misread and 3 have HTML labels that are not printed on the page.

🤖 Generated with Claude Code

Each HTML control is placed on the blank beside its label, found in the
rendered page: an underline, a box, a table cell (split at faint row lines),
or a check box or radio circle. Scans, skewed ones too, use Tesseract's words.
A control with no blank found is warned field_not_placed. The report lists
the fields made under form.created, and --values fills them.

Also gives table header cells ids unique across a page's tables.

Closes #13

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking

Re-tagging this tool's own output drops a created check box from the structure tree. src/form/create.ts:96:

obj.put("AP", { N: { Yes: this.stream(cross(w, h), w, h), Off: this.stream("", w, h) } });
...
f.value = undefined; // its on-state is Yes, whatever the HTML's value

The on-state is forced to Yes, but on the next run (the output now has fields, so maker is off) emitField matches widgets with onStates(w.widget).includes(f.value) using the HTML's value. Reproduced: form-lines with <input type="checkbox" name="news" value="yes">, tag, then tag the output again with the same pages JSON → warnings field_on_other_page: news and field_not_in_html: news; the widget has no Form element. An untagged widget annotation fails PDF/UA and hides the control's label from a screen reader. value="yes"/"on" is ordinary HTML, and re-tagging is supported (retagged). Fix: use f.value || "Yes" as the on-state (and keep f.value), and add a retag test. The same mismatch hits names changed by unique() (a second name="date" becomes date-2, a dotted name gets -): on retag both HTML controls look for date and the second is field_not_in_pdf.

Non-blocking notes

  • src/form/create.ts:136 const clean = base.replace(/\./g, "-") and the -2 suffixes mean a field is not always named by its HTML name, as README.md ("A field is named by its name") says. --values {"a.b": …} for <input name="a.b"> is refused no_acroform_field; only form.created shows the real name. Say so in the README.
  • src/form/create.ts:59 radio groups are keyed per page() call, so a group whose buttons sit on two pages becomes two fields (card, card-2). This is reachable only for a group that spans a page break.

Accessibility impact: flat forms become fillable, with each field tagged and named, but a created check box whose HTML has a value loses its Form tag when the output is tagged again.

… fields by their changed names

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Earlier blocking finding (a made check box's on-state forced to Yes, and names changed by unique(), dropped from the structure tree on retag): fixed in 0ad6a2e. src/form/create.ts:90 now uses f.value || "Yes" as the on-state, src/tag.ts:354-360 tries fieldNames() on retag, and test/forms.test.ts:189 checks this with 9 Form elements and state yes.

Non-blocking notes

  • README naming (periods, -2): fixed.
  • src/form/create.ts:59 a radio group whose buttons sit on two pages still becomes two fields (card, card-2). Unchanged since e26cdd3. Only a group that spans a page break reaches it.
  • src/form/create.ts:90 (f.value || "Yes").replace(/^Off$/, "off"): an HTML value="Off" becomes on-state off, but on retag emitField still matches onStates(...).includes("Off"), so that check box loses its tag again. This is rare; matching on the same mapped value would close it.
  • src/tag.ts:356 the fieldNames() fallback also runs for source AcroForms (when maker is off). An HTML control named date can bind to an existing PDF field date-2 once date is used. This is usually right, but it is not limited to fields this tool made.

Accessibility impact: a flat form's created fields, check boxes with HTML values and renamed fields included, keep their Form tags and names when the output is tagged again.

@bbertucc
bbertucc merged commit 4533bdf into main Oct 6, 2026
2 checks passed
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.

Make a flat (scanned) form fillable: create fields from Iris's HTML

1 participant