Hai un modello di fattura di terze parti, oppure un contratto archiviato che qualcuno ha generato anni fa in un software che ormai nessuno riesce più a trovare, e il requisito è renderlo interattivo: inserire una casella firma nell'angolo, aggiungere un paio di campi di testo, trasformare una semplice checklist in vere caselle di controllo. Il punto critico è che non stai creando questo PDF da zero. Esiste già, ha già pagine, content stream e font che non controlli, e devi innestare widget AcroForm su quel grafo di oggetti senza ricostruirlo. È un problema diverso dal creare un modulo su un documento nuovo, e la parte che manda fuori strada le persone resta invisibile finché non apri il risultato in un visualizzatore e i campi che hai appena scritto non compaiono da nessuna parte sulla pagina.
HotPDF è un componente PDF VCL nativo per Delphi e C++Builder e, dalla v2.247.0, espone una famiglia dedicata di metodi proprio per questo: creare tutti e sei i tipi di campo standard direttamente su un documento caricato con LoadFromFile. Questo articolo illustra cosa fanno quei metodi, il dizionario ISO 32000-1 che costruiscono e l'unico flag senza il quale l'intero processo produce silenziosamente un file che sembra vuoto.
Perché la creazione dei campi su un documento caricato segue un percorso separato
Quando crei un PDF da zero, HotPDF possiede l'intero modello a oggetti. Ogni pagina è un wrapper scrivibile THPDFPage e aggiungere un campo di testo tramite AddTextField collega il nuovo widget all'oggetto annotazione della pagina, all'oggetto pagina e alla raccolta dei campi del modulo, poi genera uno stream di aspetto dalle risorse font del documento. Lo stream di aspetto è la superficie visibile del widget, cioè il riquadro, il bordo e l'eventuale testo predefinito, disegnati come operatori grafici PDF che il visualizzatore renderizza alla lettera.
Un documento caricato non ti offre nessuno di questi supporti. Le pagine arrivano come dizionari grezzi; non esiste un wrapper THPDFPage scrivibile su cui agganciare un widget e, soprattutto, non c'è alcuna pipeline di risorse font pronta a disegnare gli appearance stream. Il percorso di caricamento quindi prende un'altra strada. Scrive i dizionari dei campi direttamente sul grafo di oggetti parsato e indirizza le pagine tramite indice a base zero invece che tramite oggetto pagina. I tipi di campo e i bit dei flag coincidono esattamente con il percorso da zero, quindi un campo Text resta un campo Text in entrambi i casi; ciò che cambia è la meccanica sottostante e, cosa cruciale, il modo in cui viene disegnata la superficie del widget.
Il flag /NeedAppearances qui non è facoltativo
Questo è l'unico fatto che decide se il tuo lavoro si vede. Poiché il percorso dei documenti caricati non genera stream di aspetto, un widget appena aggiunto arriva al visualizzatore senza una voce /AP: un campo senza superficie descritta. Molti visualizzatori, chiamati a renderizzare un widget che non ha appearance e non ha istruzioni per costruirne uno, non disegnano nulla. Il campo è nel file, è strutturalmente valido, è indirizzabile da uno strumento di compilazione moduli, ed è completamente invisibile a una persona.
La via d'uscita è definita in ISO 32000-1 §12.7.3: il dizionario AcroForm contiene un booleano /NeedAppearances, e quando è true un lettore conforme è tenuto a costruire da sé gli stream di aspetto mancanti partendo dalla stringa /DA (default appearance) e dal valore di ciascun campo. HotPDF lo imposta per te. La prima volta che aggiungi un campo a un documento caricato, EnsureLoadedAcroForm viene eseguito: se il catalogo non ha /AcroForm, ne crea uno; se non esiste l'array /Fields, lo crea; e forza /NeedAppearances true. Non lo chiami direttamente, ma sapere che esiste chiarisce il comportamento. Chiarisce anche una nota di distribuzione che vale la pena dire apertamente: alcuni visualizzatori minimalisti o non conformi ignorano /NeedAppearances e continuano a non mostrare nulla. Per i lettori principali il flag fa il suo dovere, ma se il tuo pubblico usa un renderer incorporato insolito, testalo lì prima di fare promesse.
Aggiunta dei sei tipi di campo
Ogni metodo segue la stessa forma. Passi l'indice di pagina a base zero, i quattro angoli del rettangolo del widget nelle coordinate dello spazio utente PDF, il nome del campo e tutti gli argomenti aggiuntivi richiesti dal tipo. Il rettangolo è X1, Y1, X2, Y2 con l'origine PDF in basso a sinistra della pagina, quindi i valori Y più alti stanno più in alto; questa è la convenzione di coordinate del formato file, non quella dello schermo con origine in alto a sinistra, e sbagliare questo dettaglio è il secondo errore più comune dopo aver dimenticato il flag. Ogni chiamata restituisce l'indice a base zero del nuovo campo, oppure -1 se l'indice di pagina è fuori intervallo o non si riesce a risolvere l'oggetto pagina.
var
Pdf: THotPDF;
Idx: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('contract.pdf') <= 0 then Exit;
// Text field: name, initial value, max length (0 = unlimited)
Idx := Pdf.AddLoadedTextField(0, 72, 680, 320, 700, 'FullName', '', 0);
// CheckBox: export value, initial checked state
Pdf.AddLoadedCheckBox(0, 72, 640, 90, 658, 'AgreeTerms', 'Yes', False);
// Signature field: just a name and a rectangle
Pdf.AddLoadedSignatureField(0, 360, 72, 540, 132, 'ApproverSig');
if Idx >= 0 then
Pdf.SaveLoadedDocument('contract-interactive.pdf');
finally
Pdf.Free;
end;
end;
Il terzo e il quarto argomento stringa del campo di testo sono il nome del campo e il suo valore iniziale /V; l'intero è /MaxLen, scritto solo quando è maggiore di zero. HotPDF assegna a ogni campo modificabile una stringa di aspetto predefinito pari a /Helv 12 Tf 0 0 0 rg, che è ciò che un visualizzatore /NeedAppearances-compatibile legge per decidere il font e il colore con cui visualizza il valore. La casella di controllo accetta un export value, la stringa che il modulo invia quando la casella è selezionata, più un booleano per lo stato iniziale; internamente scrive le corrispondenti voci di nome /V, /AS, e /DV voci di nome in modo che lo stato on/off sia coerente nel momento in cui il file si apre. Un export value vuoto predefinisce Yes, il nome convenzionale "on" della checkbox.
Campi di scelta e bit /Ff
ComboBox e ListBox sono entrambi campi di scelta, tipo di campo /Ch in ISO 32000-1 §12.7.4. La differenza tra un menu a discesa e un elenco scorrevole è un singolo bit nell'intero dei flag del campo /Ff: il bit 18, il flag Combo, valore $40000. HotPDF imposta quel bit per AddLoadedComboBox e lo lascia disattivato per AddLoadedListBox; altrimenti i due sono identici, ed entrambi prendono le proprie scelte da un array aperto di stringhe scritto nella voce /Opt.
// Dropdown (Combo flag set internally) with an initial selection
Pdf.AddLoadedComboBox(0, 72, 600, 300, 620, 'Country', 'Canada',
['United States', 'Canada', 'Mexico']);
// Scrolling list, no initial value
Pdf.AddLoadedListBox(0, 72, 520, 300, 590, 'Priority', '',
['Low', 'Normal', 'High']);
// Push button with a caption drawn through /MK
Pdf.AddLoadedPushButton(0, 360, 600, 480, 626, 'SubmitBtn', 'Submit');
Due note sull'elenco di opzioni. HotPDF scrive ogni /Opt voce come una semplice stringa, in cui il valore di export e l'etichetta visualizzata coincidono. ISO 32000-1 §12.7.4.4 consente anche la forma a due elementi [export display] quando hai bisogno che il valore inviato differisca da quello che l'utente legge; i metodi di creazione su documento caricato usano la forma più semplice a stringa singola, quindi se ti servono valori di export e visualizzazione diversi dovresti impostarli tu nel dizionario risultante. E il valore che passi come selezione corrente del campo dovrebbe essere una delle opzioni fornite, poiché il visualizzatore lo confronta con l'elenco.
Il pulsante è l'altro caso guidato dai flag: tipo di campo /Btn con il bit 17, il flag PushButton, valore $10000. Quel bit è ciò che separa un pulsante cliccabile da una checkbox, che è anch'essa un campo /Btn ma senza di esso. La caption che passi viene scritta nel dizionario delle caratteristiche di aspetto /MK come caption normale /CA. Vale la pena essere chiari sui limiti: il pulsante viene creato con la sua etichetta e il suo rettangolo, ma il metodo di creazione su documento caricato non associa alcuna azione, quindi da solo è un pulsante che ha l'aspetto giusto ma non fa nulla quando viene cliccato. Collegare azioni di submit, reset o JavaScript è una questione separata; per la parte di authoring da zero, il flusso campo-più-azione è trattato in creazione di campi e azioni AcroForm in Delphi, che è il punto di confronto corretto per ciò che il percorso caricato lascia deliberatamente fuori.
Il dizionario condiviso da ogni campo
Alla base di tutti e sei i metodi c'è un builder condiviso che costruisce l'annotazione widget e la registra in due punti. Scrive /Type /Annot e /Subtype /Widget, l'/Rect array dai quattro valori di coordinata, i flag dell'annotazione /F 4 che imposta il bit Print così che il campo appaia sia su carta sia a schermo, il nome del campo /T, il tipo di campo /FT, i flag /Ff, e un /P riferimento inverso all'oggetto pagina. Poi aggiunge il nuovo campo all'array dell'AcroForm /Fields e all'array della pagina /Annots, risolvendo le referenze indirette lungo il percorso così da estendere gli array reali invece di lasciare il widget orfano.
Questa doppia registrazione conta perché un widget che vive in solo una delle due liste è rotto in modo sottile. Un campo presente in /Fields ma assente dall'array della pagina /Annots è noto al modulo ma non viene mai disegnato; il contrario viene disegnato ma resta sconosciuto alla logica del modulo. HotPDF mantiene entrambe le cose sincronizzate a ogni aggiunta, ed è il tipo di contabilità che altrimenti dovresti fare perfettamente a mano contro la specifica.
Alcuni limiti onesti
Stabilisci aspettative chiare prima di costruirci sopra un flusso di lavoro. Il comportamento di appiattimento e rigenerazione dipende dal fatto che il visualizzatore rispetti /NeedAppearances e copre Acrobat, i motori PDF dei browser moderni e i comuni lettori desktop, ma non è una garanzia assoluta su ogni renderer in circolazione. Se devi produrre un file i cui campi si renderizzino in modo identico ovunque, anche nei visualizzatori che ignorano il flag, sei nel territorio degli appearance stream e il percorso di authoring da zero che disegna /AP per te è la scelta migliore. Anche il campo firma viene creato come widget firma vuoto, pronto per essere firmato; posizionare il campo non equivale ad applicare una firma crittografica.
Per modificare ciò che esiste già invece di aggiungere altro, l'operazione correlata è l'appiattimento del modulo, in cui incorpori i campi interattivi nel contenuto statico della pagina così che i valori diventino permanenti e non modificabili; quel percorso di andata e ritorno, incluso il modo in cui vengono gestiti i moduli con XFA, è discusso in appiattimento dei campi XFA e AcroForm in Delphi. Aggiungere campi e appiattire campi sono le due estremità dello stesso ciclo di vita: questo articolo mostra come portare l'interattività in un documento che ne era privo, mentre l'appiattimento è il modo per toglierla di nuovo una volta che il modulo ha assolto il suo compito.
L'API dei moduli su documento caricato mostrata qui è inclusa nel HotPDF Component standard per Delphi e C++Builder, insieme al riferimento completo per i flag dei campi, la gestione dell'aspetto e il resto del modello AcroForm.