Articol tehnic

Adăugarea câmpurilor AcroForm într-un PDF încărcat în Delphi

Aveți un șablon de factură de la o terță parte sau un contract arhivat pe care cineva l-a generat cu ani în urmă într-un software pe care nu-l mai găsește nimeni, iar cerința este să-l faceți interactiv: să adăugați o casetă de semnătură într-un colț, câteva câmpuri text și să transformați o listă de verificare statică în bife reale. Capcana este că nu creați acest PDF de la zero. El există deja, are deja pagini și fluxuri de conținut și fonturi pe care nu le controlați, iar dumneavoastră trebuie să atașați widgeturi AcroForm la acel graf de obiecte fără să-l reconstruiți. Este o problemă diferită de crearea unui formular într-un document nou, iar partea care îi încurcă pe oameni nu se vede până când deschideți rezultatul într-un vizualizator și câmpurile pe care tocmai le-ați scris nu apar nicăieri pe pagină.

HotPDF este o componentă PDF VCL nativă pentru Delphi și C++Builder, iar începând cu v2.247.0 expune o familie dedicată de metode exact pentru asta: construirea direct pe un document încărcat cu LoadFromFile a tuturor celor șase tipuri standard de câmpuri. Articolul de față explică ce fac aceste metode, dicționarul ISO 32000-1 pe care îl construiesc și singurul flag fără de care toată operațiunea produce fără zgomot un fișier care pare gol.

De ce crearea câmpurilor pe un document încărcat are propriul său traseu de cod

Când construiți un PDF de la zero, HotPDF deține întregul model de obiecte. Fiecare pagină este un wrapper THPDFPage inscriptibil, iar adăugarea unui câmp text prin AddTextField conectează noul widget la obiectul de adnotare al paginii, la obiectul paginii și la colecția de câmpuri a formularului, apoi generează un flux de aparență din resursele de font ale documentului. Fluxul de aparență este suprafața vizibilă a widgetului, caseta, chenarul și orice text implicit, desenate ca operatori PDF pe care vizualizatorul îi redă literal.

Un document încărcat nu vă oferă niciunul dintre aceste elemente auxiliare. Paginile sosesc ca dicționare brute; nu există un wrapper THPDFPage inscriptibil pe care să agățați un widget și, mai important, nu există o conductă de resurse de font pregătită să deseneze fluxuri de aparență. Prin urmare, traseul pentru documente încărcate merge pe altă rută. Scrie dicționarele câmpurilor direct peste graful de obiecte parsate și se adresează paginilor printr-un index bazat pe zero, nu prin obiectul paginii. Tipurile de câmp și biții de flag coincid exact cu traseul de la zero, astfel încât un câmp Text rămâne un câmp Text în ambele cazuri; ceea ce se schimbă este infrastructura de dedesubt și, crucial, modul în care este desenată suprafața widgetului.

Flagul /NeedAppearances nu este opțional aici

Acesta este singurul fapt care decide dacă munca dvs. apare. Pentru că traseul pentru documente încărcate nu generează fluxuri de aparență, un widget adăugat proaspăt ajunge în vizualizator fără intrarea /AP: un câmp fără suprafață descrisă. Multe vizualizatoare, când li se cere să redea un widget care nu are aparență și nici instrucțiune să construiască una, nu afișează nimic. Câmpul există în fișier, este valid structural, poate fi adresat de un instrument de completare a formularelor și este complet invizibil pentru un om.

Calea de ieșire este definită în ISO 32000-1 §12.7.3: dicționarul AcroForm conține un boolean /NeedAppearances, iar când acesta este true un cititor conform trebuie să construiască singur fluxurile de aparență lipsă din șirul /DA al fiecărui câmp și din valoarea lui. HotPDF face asta pentru dumneavoastră. Prima dată când adăugați orice câmp la un document încărcat, se execută EnsureLoadedAcroForm: dacă catalogul nu are /AcroForm, creează unul; dacă nu există array-ul /Fields, îl creează; și forțează /NeedAppearances true. Nu îl apelați direct, dar știind că există explică comportamentul. Explică și o precizare de implementare care merită spusă clar: câteva vizualizatoare minimale sau neconforme ignoră /NeedAppearances și tot nu afișează nimic. Pentru cititorii obișnuiți, flagul își face treaba, dar dacă publicul dvs. folosește un renderer încorporat neobișnuit, testați acolo înainte să promiteți ceva.

Adăugarea celor șase tipuri de câmpuri

Fiecare metodă urmărește aceeași formă. Transmiteți indexul de pagină bazat pe zero, cele patru colțuri ale dreptunghiului widgetului în coordonatele spațiului utilizator PDF, numele câmpului și orice argumente suplimentare are nevoie tipul. Dreptunghiul este X1, Y1, X2, Y2, cu originea PDF în colțul stânga-jos al paginii, astfel încât valorile Y mai mari sunt mai sus; aceasta este convenția de coordonate a formatului de fișier, nu convenția ecranului cu originea sus-stânga, iar inversarea lor este a doua cea mai frecventă greșeală după uitarea flagului. Fiecare apel returnează indexul bazat pe zero al noului câmp, sau -1 dacă indexul de pagină este în afara intervalului ori obiectul paginii nu a putut fi rezolvat.

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;

Al treilea și al patrulea argument șir ale câmpului text sunt numele câmpului și valoarea inițială /V; întregul este /MaxLen, scris numai când este mai mare decât zero. HotPDF oferă fiecărui câmp editabil un șir de aparență implicită /Helv 12 Tf 0 0 0 rg, pe care un vizualizator care respectă /NeedAppearances îl citește pentru a decide fontul și culoarea în care desenează valoarea. Caseta de bifare primește o valoare de export, șirul pe care formularul îl trimite când caseta este bifată, plus un boolean pentru starea inițială; intern, scrie intrările de nume corespunzătoare /V, /AS și /DV, astfel încât starea on/off să fie coerentă chiar din momentul deschiderii fișierului. O valoare de export goală devine implicit Yes, numele convențional „on” al casetei de bifare.

Câmpurile de alegere și biții de flag /Ff

ComboBox și ListBox sunt ambele câmpuri de alegere, tipul de câmp /Ch în ISO 32000-1 §12.7.4. Diferența dintre o listă derulantă și o listă cu derulare este un singur bit în întregul de flag-uri al câmpului /Ff: bitul 18, flagul Combo, valoarea $40000. HotPDF setează acel bit pentru AddLoadedComboBox și îl lasă curat pentru AddLoadedListBox; în rest, cele două sunt identice, iar ambele își iau opțiunile ca un array deschis de șiruri scris în intrarea /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');

Două observații despre lista de opțiuni. HotPDF scrie fiecare intrare /Opt ca un simplu șir, unde valoarea de export și eticheta afișată sunt același text. ISO 32000-1 §12.7.4.4 permite și forma cu două elemente [export display] atunci când aveți nevoie ca valoarea transmisă să difere de ceea ce citește utilizatorul; metodele de creare pentru documente încărcate folosesc forma mai simplă, cu un singur șir, așa că dacă aveți nevoie de valori de export și afișare diferite trebuie să le setați singuri în dicționarul rezultat. Iar valoarea pe care o transmiteți ca selecție curentă a câmpului ar trebui să fie una dintre opțiunile furnizate, deoarece vizualizatorul o potrivește cu lista.

Butonul PushButton este celălalt caz condus de flag-uri: tip de câmp /Btn cu bitul 17, flagul PushButton, valoarea $10000. Acest bit este cel care separă un buton clicabil de o casetă de bifare, care este tot un câmp /Btn, dar fără el. Legenda pe care o transmiteți este scrisă în dicționarul de caracteristici de aspect /MK ca legenda normală /CA. Merită să fim sinceri aici cu privire la domeniu: butonul este creat cu eticheta și dreptunghiul lui, dar metoda de creare pentru documente încărcate nu atașează o acțiune, așa că, de unul singur, este un buton care arată corect și nu face nimic când este apăsat. Legarea acțiunilor de trimitere, resetare sau JavaScript este o preocupare separată; pentru partea de creare de la zero, fluxul câmp-plus-acțiune este acoperit în construirea câmpurilor și acțiunilor AcroForm în Delphi, care este punctul corect de comparație pentru ceea ce traseul pentru documente încărcate lasă în mod deliberat deoparte.

Dicționarul pe care îl partajează fiecare câmp

Sub toate cele șase metode există un singur constructor comun care creează adnotarea widget și o înregistrează în două locuri. El scrie /Type /Annot și /Subtype /Widget, array-ul /Rect din cele patru coordonate, flagurile de adnotare /F 4, care setează bitul Print astfel încât câmpul să apară atât pe hârtie, cât și pe ecran, numele câmpului /T, tipul câmpului /FT, flagurile /Ff și o referință inversă /P la obiectul paginii. Apoi adaugă noul câmp în array-ul /Fields al AcroForm și în array-ul /Annots al acelei pagini, rezolvând pe parcurs referințele indirecte astfel încât să extindă array-urile reale în loc să lase widgetul orfan.

Acea înregistrare dublă contează, pentru că un widget care trăiește doar într-una dintre cele două liste este defect într-un mod subtil. Un câmp prezent în /Fields dar absent din /Annots al paginii este cunoscut de formular, dar nu este niciodată desenat; inversul este desenat, dar necunoscut logicii formularului. HotPDF le menține pe ambele sincronizate la fiecare adăugare, ceea ce este genul de evidență pe care altfel ar trebui să o faceți exact corect manual, împotriva specificației.

Câteva limite oneste

Setați așteptările înainte să construiți un flux de lucru pe baza asta. Comportamentul de a aplatiza și regenera depinde de faptul că vizualizatorul respectă /NeedAppearances, lucru care acoperă Acrobat, motoarele PDF moderne din browser și cititoarele desktop obișnuite, dar nu este o garanție absolută pentru orice renderer din teren. Dacă trebuie să produceți un fișier ale cărui câmpuri se afișează identic peste tot, inclusiv în vizualizatoare care ignoră flagul, intrați în teritoriul fluxurilor de aparență și traseul de creare de la zero care desenează /AP pentru dumneavoastră este alegerea mai potrivită. Câmpul de semnătură, la fel, este creat ca un widget de semnătură gol, gata să fie semnat; plasarea câmpului nu este același lucru cu aplicarea unei semnături criptografice.

Pentru a modifica ceea ce există deja, în loc să adăugați la el, operațiunea aferentă este aplatizarea formularului, prin care încorporați câmpurile interactive înapoi în conținutul static al paginii astfel încât valorile să devină permanente și imposibil de editat; această transformare dus-întors, inclusiv modul în care sunt tratate formularele cu XFA, este discutată în aplatizarea câmpurilor XFA și AcroForm în Delphi. Adăugarea câmpurilor și aplatizarea câmpurilor sunt cele două capete ale aceluiași ciclu de viață: acest articol arată cum aduceți interactivitate într-un document care nu avea, iar aplatizarea este modul în care o scoateți din nou, odată ce formularul și-a îndeplinit scopul.

API-ul de formular pentru documente încărcate prezentat aici este livrat ca parte a componentei HotPDF standard pentru Delphi și C++Builder, alături de referința completă pentru flagurile câmpurilor, gestionarea aparenței și restul modelului AcroForm.