Actualizarea precizează cum diferă acțiunile named, Hide, ImportData și JavaScript și când un formular trebuie păstrat activ sau aplatizat

Articol tehnic

Formulare PDF interactive în Delphi: Acțiuni și JavaScript

Un câmp de formular PDF în sine este doar o casetă care conține o valoare. Ceea ce face ca un formular să se comporte ca o mică aplicație este acțiunea atașată acestuia: un clic care ascunde o secțiune, preia valori salvate dintr-un fișier, sare la ultima pagină sau rulează un script care calculează totalul unei coloane. Nimic din toate acestea nu trăiește în câmp. Trăiește într-un dicționar de acțiuni, iar ISO 32000-1 organizează întreaga familie în §12.6. Acest articol prezintă acțiunile pe care un program Delphi le folosește cel mai des și arată cum PDF Library for Delphi conectează fiecare acțiune la un câmp sau la un link

Diagramă PDF Library for Delphi a unei adnotări PDF de link sau widget care referențiază un singur dicționar de acțiune, ce se ramifică în verbele de acțiune named, hide, import-data și JavaScript, cu cardul import-data marcat ca exclus din PDF/A
Un singur dicționar de acțiuni în spatele unui link sau widget alimentează patru verbe distincte — mișcarea viewport-ului, vizibilitatea, importul de pe disc și scriptarea

Modelul mental care merită reținut este că un câmp și o acțiune sunt obiecte separate, unite printr-o referință. O adnotare de tip widget sau o adnotare de tip link poartă o acțiune în intrarea sa /A. Acțiunea numește câmpul pe care operează prin titlu, nu prin index, astfel încât titlul pe care îl dați unui câmp este identificatorul pe care fiecare acțiune ulterioară îl folosește pentru a-l găsi. Odată ce această separare este clară, API-ul nu mai arată ca o colecție aleatorie de apeluri, ci ca un singur model aplicat la patru tipuri de verbe

Acțiuni denumite: navigare fără număr de pagină

Cele mai simple acțiuni nu poartă deloc parametri. ISO 32000-1 §12.6.4.11, Tabelul 194, definește acțiunile denumite: vizualizatorul interpretează un nume simbolic la rulare în loc să urmeze o destinație stocată. Patru nume sunt acceptate universal și sunt exact cele pe care un cititor le așteaptă de la o bară de instrumente: NextPage, PrevPage, FirstPage și LastPage. Deoarece destinația este relativă la orice pagină pe care o afișează vizualizatorul în acel moment, un buton Next creat în acest mod funcționează pe fiecare pagină fără a fi nevoie să calculați o țintă

În PDF Library for Delphi, o acțiune denumită este atașată unui dreptunghi hotspot pe pagina curentă. Al patrulea și al cincilea argument de tip întreg selectează verbul și aspectul

// NamedActionType: 0 = NextPage, 1 = PrevPage, 2 = FirstPage, 3 = LastPage
// bitul 0 din Options (valoarea 1) desenează o margine în jurul hotspot-ului
Pdf.AddLinkToNamedAction(500, 560, 60, 18, 0, 1);   // Next
Pdf.AddLinkToNamedAction(40, 560, 60, 18, 1, 1);    // Previous
Pdf.AddLinkToNamedAction(110, 560, 60, 18, 3, 1);   // săriți la ultima pagină

Nu există nicio destinație de menținut în sincronizare, ceea ce este și ideea. O acțiune denumită supraviețuiește inserării și ștergerii paginilor pentru că nu numește niciodată o pagină în primul rând. Comparați acest lucru cu un link go-to explicit, care stochează un index de pagină țintă pe care trebuie să îl renumerotați în momentul în care documentul crește

Acțiunea Hide și problema legată de tablouri

Acțiunea Hide, ISO 32000-1 §12.6.4.10, Tabelul 196, comută vizibilitatea unuia sau mai multor câmpuri. Es cel mai simplu mod de a construi un comportament de afișare și ascundere fără scripting și este ceea ce doriți pentru un link Show details sau pentru două panouri reciproc exclusive în care afișarea unuia îl ascunde pe celălalt. Acțiunea poartă o țintă în intrarea sa /T și o valoare booleană /H care decide direcția: ascunde când este adevărat, afișează când este fals

Subtilitatea constă în întregime în modul în care este codificată acea țintă și este genul de detaliu care produce un formular care funcționează pe mașina dumneavoastră și eșuează pe cea a clientului. Când acțiunea numește un singur câmp, /T este scris ca un singur șir de text. Când numește mai multe, /T este scris ca un tablou de șiruri de text. Vizualizatoarele mai vechi nu tratează un tablou cu un singur element în același mod în care tratează un șir simplu, așa că codificarea trebuie să se ramifice în funcție de număr: un singur nume trebuie emis ca un șir, nu ca un tablou cu lungimea unu, pentru ca cea mai largă gamă de cititoare să îl respecte. PDF Library for Delphi ia această decizie pentru dumneavoastră. Transmiteți numele câmpurilor separate prin virgule, punct și virgulă sau întreruperi de rând, iar scriitorul emite un singur șir pentru un singur nume și un tablou pentru două sau mai multe

Diagramă PDF Library for Delphi a regulilor de codare ale acțiunii PDF Hide, unde exact un nume de câmp devine un șir de text /T, iar două sau mai multe nume devin o matrice de șiruri, plus note despre direcția fanionului de ascundere, siguranța PDF/A și titlurile de câmp complet calificate
Writer-ul se ramifică după numărul de câmpuri, astfel încât un nume singular se expediază ca string simplu pe care cititorii mai vechi îl respectă, în timp ce două sau mai multe devin un array comutat în bloc
// HideFlag diferit de zero ascunde câmpurile enumerate (/H true); zero le afișează
// Un nume -> /T este un șir text. Două sau mai multe -> /T este un tablou de șiruri
Pdf.AddLinkToHideField(40, 700, 90, 18, 'ShippingAddress', 1, 1);
Pdf.AddLinkToHideField(140, 700, 90, 18,
  'ShippingName,ShippingAddress,ShippingZip', 1, 1);

Deoarece acțiunea nu face referire la nicio resursă externă, rămâne compatibilă cu PDF/A. Numele pe care le transmiteți sunt titluri de câmpuri complet calificate, motiv pentru care un câmp copil din interiorul unui grup trebuie adresat prin calea sa completă cu puncte, mai degrabă decât prin numele său simplu de frunză

ImportData: precompletarea din FDF

Acolo unde acțiunea Hide rearanjează ceea ce este deja pe pagină, acțiunea import-data aduce valori din afara acesteia. ISO 32000-1 §12.6.4.8, Tabelul 198, o definește ca o acțiune care populează AcroForm dintr-un fișier Forms Data Format de pe disc. Aceasta este acțiunea din spatele unui control Reload sample data sau Reset to defaults, unde un fișier FDF este livrat alături de PDF și conține valorile canonice ale câmpurilor. Apelul le oglindește pe celelalte, preluând dreptunghiul hotspot, calea către FDF și o mască de biți pentru aspect: Pdf.AddLinkToImportData(40, 660, 120, 18, 'defaults.fdf', 1). Fișierul nu trebuie să existe atunci când este construit PDF-ul, dar trebuie să fie prezent atunci când utilizatorul face clic, iar orice backslash-uri din cale sunt rescrise automat în forma de slash canonică pentru PDF

O constrângere merită menționată clar deoarece reprezintă o surpriză frecventă. O acțiune import-data indică un fișier extern, deci nu este permisă în PDF/A. Când documentul este în modul PDF/A, apelul returnează zero și nu adaugă nimic, în loc să producă un fișier care eșuează la validare. Dacă fluxul dumneavoastră vizează un rezultat de arhivare, precompletarea trebuie să aibă loc la momentul generării prin scrierea directă a valorilor câmpurilor, nu prin amânarea lor la un clic

JavaScript: pachete globale și scripturi per acțiune

Pentru o logică ce depășește afișarea, ascunderea și importul, familia de acțiuni apelează la JavaScript la nivel de document. Există două locuri distincte în care poate trăi un script, iar diferența contează. Un pachet JavaScript la nivel de document este stocat o singură dată pentru întregul fișier și rulează când documentul se deschide, ceea ce îl face locul potrivit pentru definițiile de funcții și starea partajată. Un script per acțiune este atașat unui singur link sau câmp și rulează numai atunci când acel obiect este activat, ceea ce îl face locul potrivit pentru singura linie care apelează o funcție pe care pachetul a definit-o deja

PDF Library for Delphi le expune pe ambele. AddGlobalJavaScript stochează un pachet numit la nivel de document; reutilizarea unui nume înlocuiește tot ce a fost stocat sub el. AddLinkToJavaScript atașează un script unui hotspot, astfel încât un clic să îl execute

Diagramă PDF Library for Delphi a modelului PDF JavaScript pe două niveluri, în care un pachet global la nivel de document definește recalcTotal la deschiderea documentului, iar fiecare link poartă un script de o linie, per acțiune, care îl apelează la click
Definește recalcTotal o dată în pachetul la nivel de document și lasă fiecare hotspot clickabil să îl atteagă cu un apel de o linie per acțiune
// Pachet la nivel de document: definiți o funcție reutilizabilă o singură dată
Pdf.AddGlobalJavaScript('Totals',
  'function recalcTotal() {' +
  '  var net = this.getField("Net").value;' +
  '  var tax = this.getField("Tax").value;' +
  '  this.getField("Gross").value = Number(net) + Number(tax);' +
  '}');

// Script per acțiune pe un link: apelați doar funcția partajată
Pdf.AddLinkToJavaScript(40, 620, 100, 18, 'recalcTotal();', 1);

Păstrarea funcției în pachetul global și a apelului în link nu este o preferință de stil. Evită duplicarea aceluiași corp pe fiecare control care are nevoie de el și înseamnă că un vizualizator cu scripting-ul dezactivat pur și simplu nu face nimic la clic, în loc să se blokeze la un blob inline malformat. De asemenea, menține intrările per acțiune mici, ceea ce păstrează fișierul lizibil atunci când îl inspectați mai târziu

Câmpuri, câmpuri copil și înghețarea rezultatului

Acțiunile au nevoie de câmpuri pe care să acționeze, așa că este util să vedem cum ia naștere un câmp. NewFormField creează un câmp pe pagina curentă și returnează indexul acestuia; tipul întreg selectează tipul de câmp, unde 1 este Text, 2 este Pushbutton, 3 este Checkbox, 4 este Radiobutton, 5 este Choice, 6 este Signature și 7 este un câmp Parent care deține copii, dar nu desenează nimic în sine. Titlul pe care îl transmiteți nu poate conține punct, deoarece punctul este separatorul în numele complet calificate pe care acțiunile le folosesc pentru a adresa copiii

Grupurile radio și formularele ierarhice sunt construite oferind câmpului părinte copii. NewChildFormField adaugă un copil sub un părinte numit, iar pentru cazurile radio și de selecție, AddFormFieldSub adaugă opțiunile individuale și returnează un index temporar pe care îl folosiți pentru a le poziționa pe fiecare. Când faza interactivă s-a încheiat și doriți să înghețați un câmp astfel încât aspectul său curent să devină conținut permanent al paginii, FlattenFormField desenează câmpul pe pagină și îl elimină din formular. După o aplatizare, indicii câmpurilor ulterioare se deplasează în jos cu unu, ceea ce este singurul lucru de reținut dacă aplatizați mai multe câmpuri într-o buclă

var
  Pdf: TPDFlib;
  FldShip: Integer;
begin
  Pdf := TPDFlib.Create;
  try
    Pdf.SetOrigin(1);          // top-left origin
    Pdf.SetPageSize('A4');
    Pdf.NewPage;

    // Un câmp text pe care acțiunea Hide îl va viza după titlu
    FldShip := Pdf.NewFormField('ShippingAddress', 1);
    Pdf.SetFormFieldBounds(FldShip, 40, 120, 240, 20);
    Pdf.SetFormFieldValue(FldShip, '');

    // Conectați un link Hide și un link de navigare la această pagină
    Pdf.DrawText(40, 110, 'Toggle shipping block:');
    Pdf.AddLinkToHideField(220, 100, 70, 16, 'ShippingAddress', 1, 1);
    Pdf.AddLinkToNamedAction(500, 800, 60, 18, 3, 1);  // Last page

    // Un script la nivel de document disponibil pentru fiecare eveniment din fișier
    Pdf.AddGlobalJavaScript('OnOpen',
      'app.alert("Form ready", 3);');

    // Înghețați câmpul dacă ieșirea nu mai trebuie să fie editabilă
    // Pdf.FlattenFormField(FldShip);

    if Pdf.SaveToFile('form_actions.pdf') <> 1 then
      raise Exception.Create('Save failed');
  finally
    Pdf.Free;
  end;
end;

Apelul flatten este comentat intenționat. Lăsați-l deoparte și documentul va fi livrat ca un formular activ ale cărui acțiuni se declanșează în cititor. Activați-l și câmpul este redat sub formă de marcaje statice, ceea ce doriți atunci când formularul a fost completat iar rezultatul ar trebui să circule ca o înregistrare fixă. Același câmp, același cod, două documente foarte diferite în funcție de faptul dacă îl înghețați sau nu

Alegerea verbului potrivit

Cele patru acțiuni se împart clar în funcție de ceea ce ating. O acțiune denumită mută fereastra de vizualizare și nu are nevoie de niciun câmp. O acțiune Hide modifică vizibilitatea și are nevoie de titlurile câmpurilor, codificarea de tip șir versus tablou fiind gestionată automat pentru dumneavoastră. O acțiune import-data accesează un fișier de pe disc și, prin urmare, este interzisă în PDF/A. O acțiune JavaScript rulează o logică arbitrară și este cel mai bine să fie împărțită între un pachet global de funcții și apeluri mici per acțiune. Optați pentru cea mai simplă acțiune care își face treaba: o acțiune Hide este mai portabilă decât un script care setează un flag ascuns, iar o acțiune denumită este mai durabilă decât o destinație de pagină stocată, deoarece nu există un număr de întreținut

De aici, două subiecte adiacente completează imaginea. Dacă formularul face parte dintr-un document accesibil, arborele de structură pe care îl parcurg cititoarele de ecran este acoperit în articolul nostru despre PDF marcat și structura de accesibilitate. Când formularul completat trebuie blocat și semnat, fluxul de lucru este descris în prezentarea detaliată a bancului de lucru pentru conformitate și semnare. Toate trei se bazează pe același motor, care este livrat ca PDF library for Delphi împreună cu API-urile de creare, formulare și semnături acoperite în alte părți ale acestui blog