Un team sinistri distribuisce un modulo di consenso generato in Delphi che in Acrobat sembra impeccabile: ogni checkbox viene renderizzata, ogni didascalia è allineata, il layout coincide con l'originale cartaceo. Tre settimane dopo, il servizio di intake comincia a respingere un terzo delle sottomissioni. Il backend si aspetta che la checkbox del consenso invii il valore Y; il modulo esporta Yes, perché chi lo ha costruito ha dato per scontato che didascalia e valore di esportazione fossero la stessa cosa. Nella pagina renderizzata nulla suggerisce la differenza. Questa è la proprietà decisiva dello sviluppo AcroForm: il widget visibile e il contratto dati sottostante sono strutture separate, e solo una delle due viene verificata quando qualcuno apre il file e lo guarda.
HotPDF è un componente VCL nativo che scrive i dizionari dei campi AcroForm direttamente da codice Delphi e C++Builder, quindi entrambe le strutture sono sotto controllo esplicito del programma — ed entrambe possono essere sbagliate in modo indipendente. Di seguito vediamo creazione dei campi, azioni dei pulsanti e JavaScript a livello di campo, con attenzione ai punti in cui l'anteprima della pagina e i dati del modulo divergono in silenzio.
I nomi dei campi sono chiavi di routing, non didascalie
Ogni campo AcroForm ha un nome completamente qualificato, e ISO 32000-1 §12.7.3 definisce quel nome — mai la didascalia visibile — come la chiave con cui il valore del campo viaggia in sottomissioni FDF, XFDF e HTTP. Ne derivano due conseguenze che sorprendono spesso gli sviluppatori abituati al design di form VCL, dove il nome di un controllo è poco più di un identificatore nel codice.
Primo: due campi con lo stesso nome completamente qualificato non sono due campi. Il modello PDF li tratta come due annotazioni widget di un unico campo, che condividono un unico valore: scrivi in uno e l'altro si aggiorna immediatamente. Ripetere il nome del cliente in ogni pagina di un contratto è un uso legittimo di questo comportamento. Ottenerlo per errore, perché un ciclo di generazione ha riutilizzato 'Field1' su tre pagine, è un bug che nessuna ispezione visiva intercetta: ogni pagina mostra comunque il proprio widget, e il mirroring appare solo quando un utente inizia a digitare.
Secondo: i nomi puntati come applicant.email costruiscono una gerarchia: il nodo padre applicant raggruppa i figli per operazioni di reset e invio che riguardano solo una parte del modulo. Nominare i campi in modo gerarchico fin dall'inizio non costa nulla e ripaga appena il sistema ricevente chiede "solo il blocco applicant".
I pulsanti radio aggiungono una terza regola: i pulsanti che devono alternarsi come un unico gruppo devono condividere un nome di gruppo. In HotPDF, le chiamate AddRadioButton che usano lo stesso nome di gruppo collegano i rispettivi widget a un unico campo padre, e il valore di esportazione di ogni pulsante — 'basic', 'full' — identifica l'opzione selezionata. Assegnare invece un nome univoco a ogni pulsante produce interruttori on/off indipendenti, non un gruppo a scelta esclusiva.
Creare il set di campi pagina per pagina
HotPDF inserisce i campi tramite metodi di THPDFPage, quindi ogni campo appartiene all'oggetto pagina che lo ha creato. Qui domina una trappola di sequenza: AddPage ripunta subito CurrentPage alla nuova pagina, perciò ogni chiamata campo emessa dopo finisce sulla nuova pagina anche se logicamente appartiene alla precedente. Completa ogni pagina — contenuto disegnato e campi insieme — prima di chiamare AddPage.
procedure BuildClaimForm(Pdf: THotPDF);
begin
// Page 1: applicant block
Pdf.CurrentPage.AddTextField('applicant.name', '', Rect(50, 700, 300, 722));
Pdf.CurrentPage.AddTextField('applicant.email', '', Rect(50, 660, 300, 682));
Pdf.CurrentPage.AddCheckBox('consent', 'Y', Rect(50, 620, 70, 640), False);
Pdf.CurrentPage.AddRadioButton('coverage', 'basic', Rect(50, 580, 70, 600), True);
Pdf.CurrentPage.AddRadioButton('coverage', 'full', Rect(90, 580, 110, 600), False);
Pdf.CurrentPage.AddComboBox('plan', 'Standard',
['Basic', 'Standard', 'Premium'], Rect(50, 540, 200, 565));
Pdf.AddPage; // CurrentPage now points at page 2
Pdf.CurrentPage.AddListBox('riders', 'None',
['None', 'Flood', 'Earthquake'], Rect(50, 500, 200, 600));
end;
Le coordinate seguono la convenzione PDF: l'origine è l'angolo inferiore sinistro della pagina, la stessa convenzione che TextOut usa per il testo disegnato. Rect(50, 100, 200, 120) quindi si trova vicino al fondo di una pagina Letter, non in alto. I team che importano tabelle di layout da VCL, dove Y cresce verso il basso partendo dall'alto, producono regolarmente una prima bozza con tutti i campi specchiati verticalmente. Converti le coordinate in un helper condiviso, non in ogni call site, così la correzione arriva dappertutto in una sola volta.
Collegare pulsanti ad azioni URI, JavaScript e submit
Un pulsante push non fa nulla finché non gli viene associata un'azione. HotPDF espone i tipi di azione di ISO 32000-1 §12.6.4 tramite l'enumerazione THPDFButtonAction — baURI, baJavaScript, baSubmitURL, baResetForm, baHide, baShow, baNamed — più due metodi che creano il pulsante e legano l'azione in un unico passaggio.
// Open a help page in the system browser
Pdf.CurrentPage.AddPushButtonWithAction('btnHelp', 'Help',
'https://www.example.com/claims-help', Rect(320, 700, 420, 730), baURI);
// Run viewer-side JavaScript
Pdf.CurrentPage.AddPushButtonWithAction('btnRecalc', 'Recalculate',
'app.alert("Totals updated.");', Rect(320, 660, 420, 690), baJavaScript);
// Submit as XFDF and keep empty fields in the payload
Pdf.CurrentPage.AddPushButtonWithSubmitAction('btnSubmit', 'Submit claim',
'https://api.example.com/claims', Rect(320, 620, 420, 650),
[sffXFDF, sffIncludeNoValueFields]);
I flag di invio meritano più attenzione progettuale di quanta ne ricevano di solito. AddPushButtonWithSubmitAction accetta un set THPDFSubmitFormFlags, e un set vuoto produce un normale post url-encoded: il formato che molti endpoint di esempio accettano e molti endpoint di produzione no. Aggiungere sffXFDF passa il payload a XFDF; sffGetMethod cambia il verbo HTTP; sffIncludeNoValueFields fa comparire nel payload anche i campi vuoti invece di eliminarli in silenzio, cosa importante quando il consumer distingue "assente" da "vuoto". Il set di flag è di fatto parte del contratto di interfaccia con l'endpoint ricevente, quindi sceglilo insieme al team che interpreta la sottomissione, non dopo il primo lotto respinto.
JavaScript a livello di campo: keystroke, format, validate
Oltre ai click sui pulsanti, HotPDF collega JavaScript agli eventi per-campo che i viewer con supporto script generano durante l'immissione dei dati. I tre trigger corrispondono a momenti diversi del ciclo di input: le azioni keystroke girano mentre arrivano i caratteri, le azioni format riscrivono il valore visualizzato dopo il commit di una modifica, e le azioni validate accettano o rifiutano il valore confermato.
// Reject committed values that are not plausible email addresses
Pdf.AttachFieldKeyStrokeAction('applicant.email',
'if (event.willCommit && !/^[\w.-]+@[\w.-]+\.\w+$/.test(event.value)) event.rc = false;');
// Display US phone numbers as (NNN) NNN-NNNN
Pdf.AttachFieldFormatAction('applicant.phone',
'event.value = event.value.replace(/(\d{3})(\d{3})(\d{4})/, "($1) $2-$3");');
// Refuse applicants under 18 at commit time
Pdf.AttachFieldValidateAction('applicant.age',
'if (parseInt(event.value) < 18) event.rc = false;');
Impostare event.rc = false dentro uno script keystroke o validate fa rifiutare l'input al viewer. Il limite da tenere presente: questo codice viene eseguito solo nei viewer che includono un motore JavaScript. Acrobat e pochi prodotti desktop lo eseguono; la maggior parte dei reader mobili, dei renderer incorporati nei browser e delle pipeline di stampa lo ignora completamente. Gli script di campo migliorano la qualità dei dati per gli utenti che li ricevono: non sono un confine di sicurezza, e ogni valore inviato deve comunque essere validato dal server quando arriva.
Difetti che superano la revisione visiva
Alcuni difetti dei moduli sono strutturalmente invisibili a un controllo "apri e guarda". Questi quattro spiegano la maggior parte delle escalation AcroForm che arrivano al supporto, e ciascuno può essere intercettato meccanicamente prima del rilascio:
- Deriva del valore di esportazione. Una checkbox creata come
AddCheckBox('consent', 'Yes', ...)inviaYes; un consumer che fa match suYrespinge ogni sottomissione mentre la pagina appare perfetta. Esporta da Acrobat il modulo compilato come XFDF e confronta i valori con lo schema del consumer. - Mirroring accidentale dei valori. Nomi completamente qualificati duplicati fondono i campi in uno solo. Il sintomo compare durante l'immissione dati, mai durante la generazione, quindi testa digitando nel modulo, non solo renderizzandolo.
- Valori combo fuori dall'elenco delle opzioni. Se il valore corrente passato a
AddComboBoxnon è tra le opzioni, i viewer non concordano se mostrarlo, svuotarlo o segnalarlo. Mantieni il default dentro l'elenco. - Campi ancora modificabili dopo la chiusura del workflow. HotPDF non ha una chiamata di appearance-flattening per i campi AcroForm; il modo supportato per congelare un modulo completato è creare i campi con il flag
ffReadOnly, che mantiene il valore renderizzato tramite lo stream di appearance del campo bloccando le modifiche. Un campo read-only resta un oggetto form vivo, che gli strumenti downstream di assemblaggio e firma gestiscono in modo prevedibile.
Un comportamento lato viewer merita una nota di regressione anche se nessuna modifica al codice lo risolve: le distribuzioni enterprise di Acrobat possono disabilitare JavaScript o limitare i target di submit per policy, quindi un'azione che funzionava in sviluppo può essere inerte sul desktop del cliente. Prevedi un fallback visibile per l'invio — almeno un'istruzione stampata — nel caso in cui il pulsante non faccia nulla.
Dove il lavoro sui form incontra il resto del documento
Un campo firma è a sua volta un tipo di campo AcroForm, quindi un modulo che verrà certificato o controfirmato più tardi dovrebbe riservare quel campo durante la generazione invece di applicarlo a posteriori; i dettagli byte-level sono trattati nell'articolo collegato su firme digitali e firma PAdES con HotPDF. Se gli input arrivano come pacchetti XFA invece che come AcroForm nativi, appiattire XFA in campi AcroForm è un workflow separato con un proprio modello di perdita, perché le due tecnologie di modulo sono mutuamente esclusive dentro un singolo file.
I metodi per campi, azioni e trigger mostrati qui fanno parte dell'API standard HotPDF Component per Delphi e C++Builder; la pagina prodotto collega il riferimento completo, inclusi gli overload dei flag di campo e l'enumerazione completa dei flag di submit.