Articol tehnic

Crearea câmpurilor și acțiunilor AcroForm cu HotPDF în Delphi

O acțiune AcroForm este un dicționar atașat unui widget care îi spune vizualizatorului ce să facă atunci când se întâmplă ceva cu acel widget. Faceți clic pe un buton, iar vizualizatorul citește dicționarul său de acțiune: o acțiune URI deschide o adresă web, o acțiune JavaScript rulează un script, o acțiune SubmitForm trimite valorile câmpurilor colectate către un endpoint, o acțiune ResetForm le readuce la valorile implicite. Acțiunea este date, nu comportament încorporat în fișier. ISO 32000-1 §12.6 definește forma dicționarului; vizualizatorul furnizează motorul care îl interpretează. Această separare contează, deoarece o acțiune scrisă perfect în PDF tot nu face nimic dacă cititorul de la celălalt capăt nu are un motor pentru ea, iar o mare parte din necazurile legate de AcroForm își au originea în acest decalaj, nu într-un câmp malformat

HotPDF scrie aceste dicționare direct din Delphi și C++Builder, alături de widget-urile câmpurilor de care aparțin. Pentru fiecare formular interactiv intră în joc două structuri: widget-ul pe care utilizatorul îl vede pe pagină și mecanismul de câmp plus acțiune de dedesubt, care poartă datele și cablajul logic. Sunt editate independent și oricare dintre ele poate fi greșită în timp ce cealaltă pare corectă. Secțiunile de mai jos parcurg denumirea câmpurilor, acțiunile butoanelor propriu-zise, JavaScript-ul la nivel de câmp și clasa de defecte care supraviețuiește unei verificări vizuale pentru că trăiește în întregime în a doua structură

Stratul de widget-uri AcroForm în HotPDF, mapat la valorile subiacente ale câmpurilor, dicționarul de acțiune submit și o nepotrivire de valoare de export a consimțământului
Utilizatorii dau click pe stratul de widget, în timp ce valorile călătoresc prin stratul de câmp și acțiuni de dedesubt, unde nepotrivirile rămân invizibile

Numele câmpurilor sunt chei de rutare, nu titluri

Fiecare câmp AcroForm poartă un nume complet calificat. ISO 32000-1 §12.7.3 face din acest nume, nu din titlul vizibil, cheia sub care circulă valoarea câmpului atunci când formularul este exportat sau trimis. Dezvoltatorii veniți din proiectarea VCL tind să trateze numele unui control ca pe un identificator de cod privat, dar aici nu este așa ceva. Este formatul de transmisie pe fir

Primul lucru care decurge de aici este că două câmpuri cu același nume complet calificat nu sunt două câmpuri. PDF le tratează ca pe două adnotări widget ale unui singur câmp, care partajează o singură valoare, astfel încât tastarea în unul îl actualizează instantaneu pe celălalt. Exact asta vă doriți atunci când numele unui client trebuie să se repete pe fiecare pagină a unui contract. Este un bug atunci când o buclă de generare reutilizează accidental 'Field1' pe trei pagini. Nicio inspecție vizuală nu prinde al doilea caz. Fiecare pagină tot desenează propria casetă, iar legătura iese la suprafață abia când cineva începe să tasteze

Numele cu puncte, precum applicant.email, construiesc o ierarhie. Nodul părinte applicant grupează copiii săi, ceea ce permite unei resetări sau trimiteri să vizeze doar o parte a formularului. Denumirea câmpurilor în acest fel de la bun început nu costă nimic și se amortizează prima dată când sistemul receptor cere doar blocul applicant

Butoanele radio au propria lor regulă. Butoanele care trebuie să comute împreună trebuie să partajeze un nume de grup. În HotPDF, apelurile AddRadioButton care transmit același nume de grup atașează widget-urile lor unui singur câmp părinte, iar valoarea de export a fiecărui buton ('basic' sau 'full') identifică opțiunea aleasă. Dați fiecărui buton un nume distinct și obțineți un rând de comutatoare independente on/off în loc de un grup reciproc exclusiv, care arată identic dar se comportă greșit

Crearea setului de câmpuri pagină cu pagină

HotPDF plasează câmpurile prin metodele THPDFPage, astfel încât fiecare câmp aparține obiectului pagină care l-a creat. Capcana de succesiune de care trebuie să țineți cont este AddPage. Aceasta repoziționează CurrentPage către noua pagină chiar în momentul în care revine din apel, astfel încât orice apel de câmp ulterior ajunge pe noua pagină, chiar dacă acel câmp aparținea logic paginii tocmai părăsite. Terminați fiecare pagină, conținutul desenat și câmpurile împreună, înainte de a apela AddPage

procedure BuildClaimForm(Pdf: THotPDF);
begin
  // Pagina 1: blocul solicitantului
  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 indică acum spre pagina 2
  Pdf.CurrentPage.AddListBox('riders', 'None',
    ['None', 'Flood', 'Earthquake'], Rect(50, 500, 200, 600));
end;

Coordonatele folosesc convenția PDF, cu originea în colțul din stânga jos al paginii. Aceasta este aceeași origine pe care TextOut o folosește pentru textul desenat, astfel încât Rect(50, 100, 200, 120) se așază lângă partea de jos a unei pagini Letter, nu lângă cea de sus. VCL plasează Y sus și îl crește în jos, astfel încât un tabel de layout portat direct iese oglindit pe verticală, fiecare câmp fiind răsturnat la capătul greșit al paginii. Faceți conversia o singură dată, într-un helper partajat, în loc să o faceți la fiecare loc de apel, iar o singură corecție repară tot formularul

Conectarea butoanelor la acțiuni URI, JavaScript și submit

Un buton push este inert până când i se atașează o acțiune. HotPDF expune tipurile de acțiuni din ISO 32000-1 §12.6.4 prin enumerarea THPDFButtonAction (baURI, baJavaScript, baSubmitURL, baResetForm, baHide, baShow, baNamed) și oferă două metode care creează butonul și îi leagă acțiunea într-un singur apel

Tipuri de acțiuni ale butoanelor push HotPDF în Delphi: link-uri baURI, script-uri baJavaScript și trimitere SubmitForm cu fanioane de format explicite
Un singur apel de legare atașează oricare dintre cele trei dicționare de acțiuni, iar doar varianta submit poartă un contract de indicatori cu endpoint-ul receptor
// Deschide o pagină de ajutor în browserul sistemului
Pdf.CurrentPage.AddPushButtonWithAction('btnHelp', 'Help',
  'https://www.example.com/claims-help', Rect(320, 700, 420, 730), baURI);

// Rulează JavaScript pe partea vizualizatorului
Pdf.CurrentPage.AddPushButtonWithAction('btnRecalc', 'Recalculate',
  'app.alert("Totals updated.");', Rect(320, 660, 420, 690), baJavaScript);

// Trimite ca XFDF și păstrează câmpurile goale în payload
Pdf.CurrentPage.AddPushButtonWithSubmitAction('btnSubmit', 'Submit claim',
  'https://api.example.com/claims', Rect(320, 620, 420, 650),
  [sffXFDF, sffIncludeNoValueFields]);

Flagurile de submit merită mai multă atenție decât primesc de obicei. AddPushButtonWithSubmitAction primește o mulțime de tip THPDFSubmitFormFlags, iar o mulțime goală produce un simplu post url-encoded, care este formatul pe care multe endpoint-uri de test îl acceptă, dar pe care multe endpoint-uri de producție îl resping. Adăugarea sffXFDF comută payload-ul la XFDF. sffGetMethod schimbă verbul HTTP. sffIncludeNoValueFields păstrează câmpurile goale în payload în loc să le elimine silențios, ceea ce contează din momentul în care consumatorul face diferența între "absent" și "gol". Setul de flaguri face parte din contractul de interfață cu endpoint-ul receptor, așa că stabiliți-l împreună cu echipa care parsează trimiterea, nu după primul lot respins

JavaScript la nivel de câmp: keystroke, format, validate

Clicurile pe butoane nu sunt singurul loc unde trăiesc acțiunile. HotPDF atașează JavaScript și evenimentelor per-câmp pe care vizualizatoarele capabile de scripting le declanșează în timp ce un utilizator introduce date. Există trei declanșatoare, iar acestea se declanșează în puncte diferite ale ciclului de viață al introducerii de date. O acțiune keystroke rulează pe măsură ce sosește fiecare caracter și din nou la commit. O acțiune format rescrie valoarea afișată după ce o modificare a fost confirmată, strict din motive de prezentare. O acțiune validate are ultimul cuvânt, acceptând sau refuzând valoarea confirmată înainte ca aceasta să devină valoarea câmpului

Ciclul de viață al evenimentelor JavaScript la nivel de câmp HotPDF, de la tastare la validare la format, cu avertissem de validare pe partea de server dedesubt
Scripturile keystroke și validate pot respinge input-ul, în timp ce format doar retușează afișajul, iar niciun script nu supraviețuiește unui cititor fără motor JavaScript
// Respinge valorile confirmate care nu sunt adrese de e-mail plauzibile
Pdf.AttachFieldKeyStrokeAction('applicant.email',
  'if (event.willCommit && !/^[\w.-]+@[\w.-]+\.\w+$/.test(event.value)) event.rc = false;');

// Afișează numerele de telefon din SUA ca (NNN) NNN-NNNN
Pdf.AttachFieldFormatAction('applicant.phone',
  'event.value = event.value.replace(/(\d{3})(\d{3})(\d{4})/, "($1) $2-$3");');

// Refuză solicitanții sub 18 ani la momentul commit-ului
Pdf.AttachFieldValidateAction('applicant.age',
  'if (parseInt(event.value) < 18) event.rc = false;');

Setarea event.rc = false într-un script keystroke sau validate îi spune vizualizatorului să respingă intrarea. Problema este că nimic din toate acestea nu rulează decât dacă vizualizatorul include un motor JavaScript. Acrobat și câteva produse desktop au unul. Majoritatea cititoarelor mobile, a motoarelor de randare integrate în browser și a fluxurilor de tipărire nu au, iar acestea aruncă scripturile fără nicio plângere. Așadar, scripturile de câmp îmbunătățesc calitatea datelor pentru subsetul de utilizatori al căror cititor le rulează, și asta este tot ce fac. Nu sunt o graniță de securitate. Fiecare valoare trimisă tot trebuie validată pe server odată ajunsă acolo, pentru că nu puteți presupune că clientul a verificat ceva

Defecte care trec de o verificare vizuală

Cele mai greu de prins defecte AcroForm sunt cele care trăiesc în structura de date, nu în randare, pentru că deschiderea fișierului și privirea lui nu vă spune nimic. Patru apar suficient de des încât să merite menționate, iar fiecare are un test mecanic care îl găsește înainte de lansare

  • Derapajul valorii de export. O casetă de bifat creată ca AddCheckBox('consent', 'Yes', ...) trimite Yes. Un consumator care se potrivește pe Y respinge fiecare trimitere, deși pagina arată perfect. Completați formularul, exportați-l ca XFDF din Acrobat și comparați valorile cu schema pe care consumatorul o așteaptă efectiv
  • Oglindirea accidentală a valorilor. Două câmpuri care partajează un nume complet calificat se contopesc într-unul singur. Simptomul apare la momentul introducerii datelor și niciodată la momentul generării, așa că testul corect este să tastați în formular, nu să îl randați și să evaluați rezultatul din priviri
  • Valori combo în afara listei de opțiuni. Atunci când valoarea curentă transmisă către AddComboBox nu este una dintre opțiunile listate, vizualizatoarele nu cad de acord dacă să o afișeze, să o golească sau să o semnaleze. Păstrați valoarea implicită în interiorul listei și dezacordul dispare
  • Câmpuri încă editabile după ce fluxul de lucru s-a încheiat. HotPDF nu are un apel de aplatizare a aspectului (appearance-flattening) pentru câmpurile AcroForm. Modul acceptat de a îngheța un formular completat este să creați câmpurile cu flagul ffReadOnly, care păstrează valoarea vizibilă prin propriul flux de aspect al câmpului, refuzând totodată editările. Câmpul rămâne un obiect de formular activ, ceea ce este exact ce așteaptă să găsească instrumentele din aval de asamblare și semnare

Un comportament de pe partea vizualizatorului merită o notă de regresie, chiar dacă nicio modificare de cod nu îl rezolvă. Implementările Acrobat la nivel enterprise pot dezactiva JavaScript-ul sau pot restricționa țintele de submit prin politică, astfel încât o acțiune care a funcționat pe parcursul fiecărei versiuni de dezvoltare poate rămâne inertă pe un desktop de client blocat prin politici. Planificați un fallback vizibil pentru cazul în care butonul nu face nimic, chiar dacă acel fallback este doar o instrucțiune tipărită care îi spune utilizatorului ce să facă în schimb

Unde munca de formular se conectează cu restul documentului

Un câmp de semnătură este el însuși un tip de câmp AcroForm. Un formular care va fi certificat sau contrasemnat ulterior este mai bine să aibă acel câmp rezervat încă din timpul generării, decât să fie adăugat ulterior prin patch, iar motivele la nivel de octeți se găsesc în articolul complementar despre semnăturile digitale și semnarea PAdES cu HotPDF. Datele de intrare care sosesc ca pachete XFA în loc de AcroForm nativ reprezintă o situație diferită: aplatizarea XFA în câmpuri AcroForm este un flux de lucru propriu, cu propriul său model de pierdere de date, pentru că cele două tehnologii de formulare nu pot coexista într-un singur fișier

Metodele de câmp, acțiune și declanșator prezentate aici fac parte din API-ul standard HotPDF Delphi Component pentru Delphi și C++Builder; pagina de produs face legătura către referința completă, inclusiv suprascrierile de flag de câmp și enumerarea completă a flagurilor de submit