M17 — HTML forms
State: to do — Depends on: M12, M13, M14, M16 — Fields through M16's generation-side builder; tagged through M13's structure writer, which precedes it; no script produced, by ADR 37; nothing loaded, by ADR 38
Goal
Render a template written as an HTML form into a PDF whose controls are real, fillable, accessible fields — named, typed, valued, and drawn exactly as the page shows them — and whose signature blocks are empty signature fields placed where the layout put them.
Fillable contracts, onboarding packs, direct-debit mandates, client questionnaires: a business writes them once
as templates and sends them to be completed. M12 prints form controls statically, as a browser prints a form,
so today a caller who wants fields must position them afterwards by coordinates, and move every one of them
each time the template changes. The HTML engines that do this — WeasyPrint's pdf_forms, Prince's
--pdf-forms, iText pdfHTML's AcroForm option — show the two failures to avoid: fields whose look in the viewer
differs from the page around them, because the viewer regenerates what the engine did not draw; and fields a
screen reader cannot name, because nothing tied the widget to its label. Here the appearance of each field is
what the static print would have shown, painted by the same painter, and every field is tagged with its
accessible name.
Scope
In:
- opt-in, twice:
PdfRenderOptions.FormFields(Static, the default and M12's behavior, orInteractive), and the CSS property-adc-pdf-fieldon an element (auto,static,interactive,signature); - the mapping of
<input>— text-like types,password,checkbox,radio,submit,reset,button—,<textarea>,<select>and<button>to M16's text, check box, radio group, combo box, list box and push button fields, through M16'sPdfFormBuilderon M08'sPdfDocumentBuilder.Form; - names from
name, their hierarchy, their collisions, several<form>elements and theformattribute; values, defaults and flags fromvalue,checked,selected,required,readonly,disabled,maxlengthandspellcheck; - appearances painted by M12's painter from the computed style, and the
/DA,/Q,/MKand/BSentries a viewer needs to redraw a field once someone types in it; ResetFormandSubmitFormactions for reset and submit buttons, opt-in; never a script;- signature placeholders from any element the stylesheet marks, with
/Lockfrom attributes and seed values from the options; - controls in running elements, margin boxes and fixed boxes, repeated on every page;
- tagging with M13: a
Formelement per widget,/TUfrom the accessible name,/Tabs /S; and theFormelement with itsPrintFieldattributes for controls printed statically, which M13 left here; PDF/UA-1 conflicts under M09's policy; - conformance under M14's PDF/A-3 targets and M20's PDF/A-2 ones when they exist: appearances, embedded
fonts, no
NeedAppearances, forbidden actions refused; - batch generation (M12.6) with fields: unique names per record in one volume;
- the command-line tool's
html2pdf --forms.
Out, explicitly:
- generating JavaScript, of any kind — never (ADR 37): HTML's constraint validation (
pattern,min,max,step, whattype="email"checks), event handlers, and Acrobat's format actions — which are JavaScript actions, so not written even fortype="number"ortype="date", though M16 recognizes them when it reads a form; each reported once per template, never enforced; <input>of typefile,color,range,imageandhidden,<datalist>,<output>,<meter>,<progress>,contenteditable— printed statically as M12 prints them, and reported;- filling, flattening, exchanging and batch-filling the forms we generate — M16's operations, which apply to M17's output unchanged;
- signing the placeholders — M26;
- fields in PDF 2.0's structure namespace and under PDF/UA-2 — M28;
- rich-text fields: a
<textarea>holds text, and its markup is not a field's/RV; - XFA — never generated;
- a form described in a markup of its own (XForms, a JSON schema) — not planned: HTML is the template language.
Design
Where it lives
In AdCodicem.Pdf.Html, a layer between layout and painting, which consumes the fragment tree and hands
M16's builder what it needs:
Forms/ FormControlMapper an element and its computed style -> a field specification, or none
FieldNaming names, hierarchy, collisions, form prefixes, record prefixes
FieldPainter a control's fragment -> its appearance XObjects, through Paint/
FormStructure the Form elements, OBJR and PrintField attributes, through M13's sink
The core gains nothing. M16's PdfFormBuilder on PdfDocumentBuilder.Form writes widgets with their page and the
field tree at Finish, and takes an appearance the caller supplies for every field type — per state for buttons —
rather than generating one (M16 slice 10); M13's PdfStructureBuilder.Reference(element, annotation) places each
widget in the structure; M11's annotation writer writes the widget dictionaries as it writes links.
Turning it on
PdfRenderOptions.FormFields—Statickeeps M12's output, tagged as below;Interactivemakes fields of every mapped control.-adc-pdf-field(not inherited; initialauto):autofollows the option;staticprints the control even underInteractive;interactivemakes a field of it even underStatic— the per-element opt-in;signaturemakes any element a signature placeholder (below).-adc-pdf-field-scope: document | pagefor controls that repeat on every page (below);-adc-pdf-field-font-size: auto | <length>, whoseautowrites size 0 in/DAso that a viewer fits the text a user types.- The three properties are in the declared CSS level of M12.1, with the
-adc-pdf-tagfamily M13 added, and reported when misspelled as any unknown property is.
The mapping
| HTML | Field | Notes |
|---|---|---|
input text, search, email, tel, url | Text | maxlength → /MaxLen; spellcheck="false" → DoNotSpellCheck; size sets the width only |
input number, date, time, datetime-local, month, week | Text | The value as the attribute writes it; no format action; html-form.type-as-text once per type |
input password | Text, Password | Its value is never written — M16's rule —, reported when the template had one |
textarea | Text, Multiline | Its text content as the value; rows, cols and wrap are layout only |
input checkbox | Check box | value → the on-state name, Yes when absent (Acrobat's convention, not HTML's on); checked → /V and /AS |
input radio | One radio group per name | Each control a kid widget whose on-state is its value, /Opt carrying the exact text when the value is not a plain name; NoToggleToOff, since an HTML radio cannot be unchecked; RadiosInUnison when two values repeat |
select, neither multiple nor a size above 1 | Combo box | Each option's value and text as an /Opt pair; selected → /V; optgroup labels dropped from the list and reported |
select multiple, or a size above 1 | List box | MultiSelect with multiple; /I from the selected options; /TI 0 |
button or input of type reset | Push button, ResetForm over its form's fields | With PdfRenderOptions.FormActions; printed statically otherwise |
button or input of type submit, in a form with an action | Push button, SubmitForm to that URL resolved against the base, HTML format, GET when method says so, its form's fields | With FormActions; the URL is written, never fetched; a javascript: URL gives no action, reported |
button or input of type button | Push button without action | Only under -adc-pdf-field: interactive: an inert button is otherwise a picture |
Any element under -adc-pdf-field: signature | Signature field | Below |
input file, color, range, image, hidden; datalist, output, meter, progress | None | Printed as M12 prints them; html-form.control-unsupported once per kind |
- Flags:
required→Required;readonly→ReadOnly;disabled, or inside a disabledfieldset→ReadOnlyandNoExport, since HTML does not submit a disabled control. - Default values:
/DVis the initial value, so thatResetFormrestores what the template said. - Check boxes sharing a name with different values — HTML's way of submitting several choices — become one non-terminal field holding one check box per value, named by the value, rather than check boxes that would toggle together as a PDF viewer makes same-named boxes do.
Names
- From
name, split at periods:client.nomisnomunder a non-terminalclient— useful and documented, since a PDF partial name may not contain a period. With noname, theid; with neither, a generatedfield-nin document order (html-form.name-generated). - Collisions, by M06's definition — equal names, or one the other plus a period-separated tail:
radio buttons sharing a name are one group; text-like controls of one type and the same attributes become one
field with a widget each, sharing a value (
html-form.name-shared, information); anything else renames the later control with the smallest free suffix_2,_3(html-form.name-collision, warning). A value used as a name loses the characters PDF forbids in a partial name, replaced by_, reported. - Several
<form>elements, controls associated by containment or theformattribute: names prefixed by the form'snameoridonly when names collide across forms (html-form.form-prefixed). - Batch generation: one file per record needs nothing; one volume for the run nests each record's fields
under its key or
record-n, as M06'sNestpolicy does, so that a thousand subscription forms keep a thousand sets of values.
Geometry
- The widget's rectangle is the control's border box on its page, in PDF space; padding and borders are drawn inside the appearance. With ADR 39's two passes, the rectangle is the final pass's.
- A control is atomic: never fragmented across pages or columns. One taller than the page area is clipped at
the page's edge as an image is, its widget covering what shows (
html-form.control-clipped). - Transforms: a rotation by a multiple of 90 degrees becomes
/MK /Rwith the appearance drawn upright, as viewers expect; a uniform scale scales the rectangle; any other transform — a skew, another angle — prints the control statically (html-form.transform-unsupported). A control clipped by an ancestor'soverfloworclip-pathprints statically too: a widget cannot be clipped. - Repeated controls — in a running element, in a margin box's
element(), underposition: fixed— appear on every page.-adc-pdf-field-scope: document, the initial value, makes one field with a widget per page, so that initials typed once show everywhere;pagemakes one field per page, namedname.pNfrom the physical page number. A signature placeholder is always per page: one signature signs once.
Appearances
- Painted by M12's painter from the control's fragment: exactly what the static print shows — background,
borders with their radii and shadows, padding, the value's text shaped by HarfBuzz in the computed font, the
user-agent stylesheet's check mark and radio dot in
accent-color, an author's own drawing underappearance: none— into one form XObject per state:/Nfor text and choice fields;/Nwith the on-state and/Offfor check boxes and radio buttons, both painted, the checked and the unchecked look; no/D. The page itself does not paint the control a second time. - For a viewer that redraws after a user types:
/DAnames the computed font's/DRentry, its size — or 0 underauto— and its color;/Qcomes fromtext-alignresolved againstdirection;/MK /BGfrombackground-color;/MK /BCand/BSfrom the top border's color, width and style —solid,dashedanddottedas dashed,inset,outsetas beveled, a bottom border alone as underline. What those entries cannot say — gradients, radii, shadows, four border colors, a web font's features — a viewer loses when it redraws; reported once per template (html-form.style-not-regenerable, information). - Text is set by the engine, so a value in Arabic or Hebrew, which M16's core generator draws in logical
order and reports, is shaped and ordered here.
text-transformchanges the appearance but not the value: a viewer that redraws shows the value as it is, reported (html-form.text-transform-appearance-only). - Choice fields: a combo box shows its selected option with the user-agent's arrow; a list box its visible
rows, the selected ones as the user-agent stylesheet's
option:checkedpaints them. - Fonts: the appearance uses the subsets M12 embeds for the page.
/DRmust name a face a viewer can type with:PdfRenderOptions.FormFieldFontsisSubset(the default: the page's subset — a character outside it shows the viewer's fallback),Full(the face embedded whole in/DR, once per face, its cost measured and documented), orStandard(a standard 14 font named in/DR, refused under a PDF/A target). Slice 2 checks with veraPDF that a subset in/DRbreaks no PDF/A claim, and records the verdict instatus.md.
Signature placeholders
- Any element under
-adc-pdf-field: signature— adivframing "Signature du client, précédée de la mention « Lu et approuvé »" — becomes an unsigned signature field whose widget is its border box, and whose appearance is the element's own painting, taken off the page so it is not drawn twice. The box shows in every viewer before signing; M26, or any signing service, signs the field by its name. - Name from
data-adc-field-name, then theid, then a generated one;/TUfrom the accessible name. /Lockfromdata-adc-lock:all,include: a b, orexclude: a b, naming fields as the PDF names them after mapping; a name that matches no field is reported, not guessed.- Seed values (
/SV) fromPdfRenderOptions.SignatureFields, a map from field name to M16's signature-field options: they are policy — digest methods, reasons, a certification level — not presentation. - An element with no box makes no field (
html-form.signature-without-box); a box of zero size makes an invisible one.
Tagging
With M13's structure writer, tagging on by default in the HTML engine:
- Interactive: each widget in a
Formstructure element holding itsOBJR, placed at the control's position in document order;/TUon the field from the accessible name, computed as HTML-AAM computes it, in this order —aria-labelledby,aria-label, the associated<label>byforor containment,title,placeholder; a radio group's/TUfrom itsfieldset'slegend, or its first radio's label; the label's text stays in the page's structure where M13 put it./Tabs /Son every page with a widget. - Repeated controls live in running content, which is an artifact, while a widget must be structure: each
page's widget gets a
Formelement after the page's last element (html-form.running-field-tagged, information), as M11 places annotations it cannot place better. - Static: a control printed statically becomes a
Formelement with aPrintFieldattribute object —/Roletvfor text-like controls and selects,cb,rb,pb;/checkedonoroff;/Descthe accessible name — holding the painted control: the non-interactive form of ISO 32000-1 §14.8.5.6, which M13 left to this milestone. - Under the PDF/UA-1 target: a field without an accessible name is a conflict
(
html-form.accessible-name-missing) under M09'sPdfConformancePolicy, collected with M13's so that a template is fixed in one pass, each named by its source position.
Conformance and output
- Under M14's PDF/A-3 targets, and M20's PDF/A-2 ones: every widget has its
/AP /N, noNeedAppearancesis written, every font is embedded, and each action is checked against the part's forbidden actions as veraPDF's rules state them — a conflict under the same policy (html-form.action-refused). - In PDF 2.0 output (ADR 40) fields work as in 1.7; M16's version rows apply.
- Determinism: fields in document order, names derived, appearances by content, dates none; two renders give the same bytes, alone or in a batch.
- Memory: widgets leave with their page; the field tree — a few hundred bytes a field — is written at
Finish. A report of a thousand pages with a field on each holds memory proportional to its fields, measured.
Diagnostics and the tool
In the HTML engine's diagnostics, disjoint from validation rule identifiers (ADR 36), each documented:
html-form.control-unsupported, type-as-text, constraint-not-enforced, name-generated, name-shared
(information); name-collision, form-prefixed, password-value-dropped, optgroup-flattened,
control-clipped, transform-unsupported, text-transform-appearance-only, signature-without-box,
action-refused (warning); style-not-regenerable, running-field-tagged (information);
accessible-name-missing (warning, a conflict under the PDF/UA-1 target).
html2pdf gains --forms (Interactive), --form-actions, --form-fonts subset|full|standard, and its JSON
report lists the fields made, with each control's source line and column.
Slices
Each slice ends on a green commit, with its acceptance rows passing, the diagnostics it introduces documented, and the referee harness it adds running in CI.
- Text fields end to end. Delivers the option and
-adc-pdf-field, theForms/layer, text-like inputs,passwordandtextarea, names and their collisions, values and flags, the widget at the border box, the appearance painted by M12's painter and handed to M16's builder as the caller's, and the derived/DA,/Q,/MKand/BS. Proven by unit tests — the mapping row by row, every naming case, the appearance XObject's content equal to what the static paint writes for the same box — and by qpdf's--json, pikepdf, pdf.js'sgetFieldObjects()and pdftk'sdump_data_fields_utf8listing names, types, values and flags as the template declares them; the subscription template's fields named and typed asdocuments/form/reportlab-subscription-form.pdf's; MuPDF and pdf.js rendering the interactive output as M12's static one, within M12's threshold. Leaves buttons and choices. - Check boxes, radio groups, combo and list boxes; fonts for typing. Delivers the button and choice rows,
both states painted,
/Opt,NoToggleToOff, multi-select,FormFieldFonts, and the veraPDF check of a subset in/DR. Proven by unit tests (non-ASCII radio values, repeated values, check boxes sharing a name,optgroup) and by pdf.js'sgetFieldObjects()export values, pdftk'sfill_formswitching states that pdf.js and MuPDF then draw as the checked static paint, and veraPDF's PDF/A-3b profile on the onboarding pack. Leaves actions and repetition. - Buttons, actions, repeated controls, awkward geometry. Delivers reset and submit buttons under
FormActions, running, margin-box and fixed controls with both scopes, rotations, clipping, controls taller than the page, several forms, record prefixes in batch generation. Proven by unit tests (each geometry case, each scope, ajavascript:action) and by qpdf's--jsonand pikepdf showing each action, widget and page, pdf.js resolving the submit URL without fetching it, and a one-volume batch whose names pikepdf finds unique. Leaves signatures. - Signature placeholders. Delivers
-adc-pdf-field: signature, the appearance taken off the page, names,/Lock, seed values from the options, per-page placeholders. Proven by unit tests and by pyHanko, in a container, signing each placeholder of the contract template: the signature is intact, the lock applied, the widget's rectangle the box M12 laid out, and the page showing the block once — MuPDF's rendering before and after signing identical outside the box. Leaves tagging. - Tagging, PDF/UA-1 and PDF/A. Delivers the
Formelements,OBJR,/TUby the accessible-name order,PrintFieldfor static controls, repeated controls' elements,/Tabs /S, the conflicts, the PDF/A checks. Proven by unit tests (every accessible-name source, fieldsets, a control with none) and by veraPDF's PDF/UA-1 profile on every template, interactive and static, and its PDF/A-3a profile with the dual claim of M14; pikepdf finding each widget'sFormelement shaped asvendor/pdf-association/indesign15-pdfua1-form.pdf's are. Leaves round trips and budgets. - Round trips, determinism, budgets, the tool. Delivers
html2pdf --forms,HtmlFormBenchmarkswithMemoryDiagnoser, the batch and memory rows. Proven by pdftk filling our output from an XFDF and M16 flattening it, pdftotext finding the values and veraPDF's PDF/UA-1 profile still passing; determinism across renders and batches;CorpusToolTests.
Tests required
Unit — tests/AdCodicem.Pdf.Tests:
- Mapping: every row of the table, every attribute that changes a field, every unsupported control reported once
per kind;
-adc-pdf-fieldin each value against each option. - Names: periods, missing names, every collision case, several forms and the
formattribute, record prefixes, values that make illegal names. - Geometry: border boxes in PDF space under each page size and orientation, rotations by 90, 180 and 270, a
skew, a scale, clipping by
overflowandclip-path, a control taller than the page, both scopes of repeated controls, the final pass's rectangle under ADR 39. - Appearances: each state's XObject equal, by content bytes, to the static paint of the same box;
/DA,/Q,/MKand/BSderived from every border style, alignment and direction; the three font policies. - Tagging: the accessible-name order, radio groups and fieldsets,
PrintFieldfor each role, the placement of repeated controls' elements, each conflict. - Hostile templates: a hundred thousand controls, a
selectof a million options, a name of a megabyte, a thousand nested fieldsets — each within M12's render limits, bounded in time and memory, or reported under theirlimit.*code. - Determinism: every template rendered twice, under the invariant culture and
fr-FR, identical bytes.
Integration — tests/AdCodicem.Pdf.IntegrationTests, every referee in a container
(ADR 27):
- qpdf —
--check,--json(acroform); pikepdf — fields, widgets, actions, the structure tree; pdf.js —getFieldObjects()and rendering with forms enabled; PyMuPDF — widgets as a second listing; pdftk-java —dump_data_fields_utf8andfill_form; MuPDF — rendering against M12's static references; pyHanko — signing each placeholder; veraPDF — PDF/UA-1, PDF/A-3a and 3b. - WeasyPrint with
pdf_forms, informative: the same templates' fields — names, types, rectangles — compared with ours, each difference recorded rather than failed, since WeasyPrint is not the reference.
Acceptance conditions
"The templates" are the three sources this milestone adds — not in the corpus (below): the subscription form, the onboarding pack and the fillable contract.
| Documents | Behavior | Verified by |
|---|---|---|
The subscription template, and documents/form/reportlab-subscription-form.pdf, which carries the same form | Rendered Interactive: qpdf's --json, pikepdf, pdf.js and pdftk list nom, prenom, societe, courriel as text fields and newsletter as a check box — the names and types they list on ReportLab's form —, with the template's values and flags | HtmlFormRefereeTests.A_form_template_renders_to_fields_referees_list |
| The onboarding pack | Every mapped control a field of the table's type, with its name, value, flags, options and actions as the template declares them, in every referee's listing; every unsupported control printed statically and reported exactly once per kind; no JavaScript anywhere in the file, as pikepdf's walk of every action finds | HtmlFormRefereeTests.Every_control_maps_to_its_field |
The three templates, Interactive and Static | MuPDF and pdf.js render the interactive output as M12 renders the static one, page by page, within M12's threshold; after pdftk sets new values, pdf.js and MuPDF draw them in the template's font, size and alignment | HtmlFormRefereeTests.Fields_look_like_the_page_they_were_printed_on |
The contract template, from tests/corpus/sources/contract-fr.html with two signature blocks and initials in its footer | Two unsigned signature fields at the blocks' boxes, within 0.01 point of M12's layout; pyHanko signs each in turn, finds each signature intact and its lock applied; the initials one field with a widget on every page | PyHankoHtmlFormTests.Signature_blocks_become_fields_that_sign |
| The three templates under the PDF/UA-1 target, interactive and static | veraPDF's PDF/UA-1 profile reports no failure; each widget in a Form element with its OBJR, each field with its /TU, each static control in a Form element with its PrintField attributes — the shape pikepdf finds in vendor/pdf-association/indesign15-pdfua1-form.pdf, whose claim veraPDF upholds | VeraPdfHtmlFormTests.Form_templates_pass_pdf_ua_1 |
| The onboarding pack with a control stripped of its label | Under Refuse, PdfConformanceException naming the control's source position and the clause; under RemoveClaim, the file without pdfuaid and the loss reported | CorpusHtmlFormTests.A_field_without_a_name_is_a_pdf_ua_conflict |
| The onboarding pack under M14's PDF/A-3a and PDF/UA-1 dual target | veraPDF upholds both; a reset button under FormActions is refused as a conflict where the part forbids the action, and its field printed statically under RemoveClaim | VeraPdfHtmlFormTests.Forms_keep_the_pdf_a_3_claim |
The three templates rendered Interactive, then filled by pdftk from a recorded XFDF and flattened by M16 | pdftotext finds every value in the page content; veraPDF's PDF/UA-1 profile still passes; pikepdf finds no field | PdftkHtmlFormTests.Generated_forms_fill_and_flatten |
| The subscription template as a batch of a thousand records, one volume | Each record's fields under its own name, a thousand sets pikepdf finds distinct; memory flat as records grow and throughput recorded in status.md; one file per record gives, for any record, the bytes the volume's record would have alone | CorpusHtmlFormTests.A_thousand_forms_hold_their_budget, HtmlFormBenchmarks |
| A report of a thousand pages with an initials control in its running footer — M12's long report, not in the corpus, M12's need | One field, a thousand widgets, each tagged after its page's content; memory proportional to the fields, measured | CorpusHtmlFormTests.Repeated_controls_hold_their_budget |
| Every render above | Two renders give identical bytes | CorpusHtmlFormTests.Html_forms_are_deterministic |
| The templates through the tool | html2pdf --forms produces the API's bytes | CorpusToolTests.Html2pdf_forms_matches_the_api |
Corpus
What the corpus holds
- Sources:
sources/contract-fr.html, eight articles underh2, which the contract template extends;sources/invoice-fr.htmlandreport-fr.html, without a single form control. - The same form from another producer:
documents/form/reportlab-subscription-form.pdf— four text fields and a check box with tooltips,/Helvin/DR, whose names and types the subscription template must reproduce. - A PDF/UA-1 form:
vendor/pdf-association/indesign15-pdfua1-form.pdf— text, combo, check box and radio fields tagged underFormelements withOBJR, field tooltips, a merged field and widget —, whose claim veraPDF upholds: the shape our tagging is compared with. - Other producers' conventions: M16's AcroForms — Acrobat's, LiveCycle's, LibreOffice's, InDesign's,
OmniForm's — for
/DA,/MKand/BSas viewers expect them. - M12's reference renderings (
documents/*/adcodicem-*-fr.pdf), which M12 commits — none with a control.
What it lacks
| Need | Why | Priority | Likely source |
|---|---|---|---|
Three HTML form templates: sources/subscription-form-fr.html (the ReportLab form's fields and labels); sources/onboarding-pack-fr.html (every mapped control; every unsupported one; fieldset and legend; labels by for and by containment, aria-label, title, placeholder; required, readonly, disabled, maxlength; a password with a value; radios with non-ASCII values; a multi-select; optgroup; reset and submit buttons; a rotated control; a textarea taller than a page; a control without a label for the conflict row); the contract template, sources/contract-fr.html with two signature blocks and initials in a running footer | Every acceptance row is written against them; no source in the corpus has a single form control | 1 | Generated here, our own sources, MIT, recorded in build_corpus.py |
| Their static renderings by our engine, as approved references, and their interactive renderings committed | The visual rows compare the two; M12's references cover no control | 1 | Generated here, by M12 and then this milestone, recorded |
The same templates through WeasyPrint's pdf_forms | A second HTML engine's fields, to compare with ours and for M16 to read | 2 | Generated here: WeasyPrint from pypi, already M12's referee |
| A PDF/UA-1 form with a list box, a push button and a signature field, from an authoring tool, whose claim veraPDF upholds | The tagging shape of those three types is checked on our output only; the InDesign form has none of them | 2 | A contribution (W08); a public source if one is found with an attribution-only license |
| Chromium's print of the same templates | How a browser prints the same controls statically, beside M12's rendering | 3 | Generated here with Chromium, already in the container |
A template with right-to-left values and dir="rtl" controls | Alignment, /Q and shaped values in Arabic and Hebrew are checked on unit fixtures only | 3 | Generated here, with M13's accessibility reference's Arabic passage |
Traps
- HTML names allow periods; PDF partial names do not. Splitting them is a choice, documented, and
abesidea.bis a collision M06 already defines. - Same-named check boxes toggle together in a PDF viewer, while HTML submits each. Group them under a parent and name each by its value.
- HTML's default check-box value is
on, Acrobat'sYes; radio values are arbitrary text, on-states are names./Optkeeps the text. - Disabled controls are not submitted, read-only ones are:
NoExportfor the first. - A widget cannot be clipped, skewed or split. Print the control statically rather than draw a field that lies about its extent.
/DAcannot say what CSS says. The first redraw in a viewer loses radii, gradients and shadows; say so once rather than pretend.- The appearance must not be painted twice: once on the page and once as the widget, the two drift apart the moment someone types.
- A subset cannot type new characters. Whatever
/DRnames is what a user's keystrokes are drawn in. - A password field's value is never written, even when the template carries one.
text-transformis presentation: the value is what the attribute says, and a redraw shows it so.- Running content is an artifact, and a widget is structure. A repeated control needs a
Formelement on every page it appears on. - A static control is still a form to a screen reader:
PrintFieldsays what it was. - A submit URL is a destination for someone's data. It is opt-in, never fetched by us, and never a
javascript:URL. - Batch volumes share one field namespace: a thousand records named
nomare one field unless nested. - The second layout pass moves boxes (ADR 39): a widget's rectangle is taken from the final pass, or the field sits beside the control it belongs to.
Documentation
docs/website/docs/guides/html-forms.md— turning fields on, naming, values and flags, appearances and fonts, buttons and actions, signature placeholders, repeated controls, batches, what a viewer's redraw keeps.docs/website/docs/reference/html-form-mapping.md— the mapping table, every attribute, every unsupported control, the diagnostics.docs/website/docs/reference/css-support.md—-adc-pdf-field,-adc-pdf-field-scope,-adc-pdf-field-font-size.docs/website/docs/guides/accessible-documents.md— accessible names of fields,FormandPrintField, the PDF/UA-1 conflicts of forms.docs/website/docs/guides/forms.md(M16's) — filling and flattening the forms the engine generates.docs/website/docs/reference/diagnostics.mdanddocs/website/docs/reference/tool/— thehtml-form.*codes andhtml2pdf --forms.docs/website/docs/introduction.mdanddocs/features/features.json— thehtml-formsfeature delivered.docs/architecture.md— theForms/layer ofAdCodicem.Pdf.Html, between layout and painting.
Exit criteria
- Every mapped control becomes its field, with its name, value, flags, options and appearance; every unsupported one is printed and reported; no script is ever written.
- Signature placeholders are placed from the layout, with their locks and seed values, and sign in pyHanko.
- Repeated controls, rotations, clipping and batches behave as designed.
- Fields are tagged with their accessible names, static controls with
PrintField; the templates pass PDF/UA-1, and PDF/A-3a with the dual claim. - The priority-1 gaps above are filled; each remaining gap is recorded in
docs/corpus-contributions.md. - The acceptance conditions above pass on the corpus, in CI, with no document skipped.
- Unit tests cover each behavior, its degenerate cases and its hostile ones.
- Integration tests confirm fields, rendering, fills, flattening, signatures and conformance through qpdf, pikepdf, pdf.js, PyMuPDF, pdftk-java, MuPDF, pyHanko and veraPDF, each in a container.
-
HtmlFormBenchmarksmeasures rendering with fields and batches withMemoryDiagnoser;status.mdrecords the budgets. - The documentation site publishes the guide, the mapping reference and the CSS properties.
- Every page of Documentation is written in its Diátaxis section, one mode per page (ADR 47).