Teknisk artikkel

Bygge AcroForm-felt og handlinger med HotPDF i Delphi

En AcroForm-handling er en dictionary festet til en widget som forteller viseren hva den skal gjøre når noe skjer med denne widgeten. Klikk på en knapp, og viseren leser dens action dictionary: en URI-handling åpner en nettadresse, en JavaScript-handling kjører et skript, en SubmitForm-handling sender de innsamlede feltverdiene til et endepunkt, en ResetForm-handling tilbakestiller dem til standardverdiene. Handlingen er data, ikke atferd bakt inn i filen. ISO 32000-1 §12.6 definerer dictionary-formen; viseren leverer motoren som tolker den. Dette skillet betyr noe fordi en handling som er skrevet perfekt inn i PDF-en, likevel ikke gjør noe hvis leseren i den andre enden mangler en motor for den, og mye av AcroForm-elendigheten kan spores tilbake til dette gapet snarere enn til et feilformet felt

HotPDF skriver disse dictionary-strukturene direkte fra Delphi og C++Builder, sammen med feltwidgetene de henger på. To strukturer er i spill for hvert interaktive skjema: widgeten brukeren ser på siden, og feltet pluss handlingsmekanismen under som bærer dataene og koblingene. De redigeres uavhengig av hverandre, og hver av dem kan være feil mens den andre ser riktig ut. Avsnittene nedenfor går gjennom feltnavngiving, selve knappehandlingene, feltnivå-JavaScript og den typen defekt som overlever en visuell kontroll fordi den lever utelukkende i den andre strukturen

AcroForm widget-lag i HotPDF knyttet til de underliggende feltverdiene, submit-handlingsordboken og en samtykke-eksportverdi-uoverensstemmelse
Brukere klikker i widget-laget mens verdier reiser gjennom felt- og handlingslaget under, der uoverensstemmelser forblir usynlige

Feltnavn er rutingnøkler, ikke bildetekster

Hvert AcroForm-felt har et fullt kvalifisert navn. ISO 32000-1 §12.7.3 gjør dette navnet, ikke den synlige bildeteksten, til nøkkelen som feltets verdi følger når skjemaet eksporteres eller sendes inn. Utviklere som kommer fra VCL-design har en tendens til å behandle et kontrollnavn som en privat kodeidentifikator, og det er det ikke her. Det er overføringsformatet

Det første som følger av dette, er at to felt med samme fullt kvalifiserte navn ikke er to felt. PDF behandler dem som to widget-annotasjoner for ett felt, som deler én verdi, slik at det å skrive i det ene oppdaterer det andre umiddelbart. Det er akkurat det du vil ha når et kundenavn må gjentas på hver side i en kontrakt. Det er en feil når en genereringsløkke ved et uhell gjenbruker 'Field1' på tvers av tre sider. Ingen visuell inspeksjon fanger opp det andre tilfellet. Hver side tegner fortsatt sin egen boks, og koblingen kommer først til syne når noen begynner å skrive

Punktnavn som applicant.email bygger et hierarki. Foreldrenoden applicant grupperer sine barn, og det er dette som lar en tilbakestilling eller innsending rette seg mot bare en del av et skjema. Å navngi felt på denne måten fra start koster ingenting, og det betaler seg selv første gang det mottakende systemet bare ber om søkerblokken

Radioknapper har sin egen regel. Knapper som skal veksle sammen, må dele et gruppenavn. I HotPDF fester AddRadioButton-kall som sender inn samme gruppenavn, widgetene sine til ett felles overordnet felt, og hver knapps eksportverdi ('basic' eller 'full') identifiserer det valgte alternativet. Gir du hver knapp et unikt navn, får du en rekke uavhengige av/på-brytere i stedet for én gjensidig utelukkende gruppe, noe som ser identisk ut, men oppfører seg feil

Å opprette feltsettet side for side

HotPDF plasserer felt gjennom THPDFPage-metoder, så hvert felt tilhører sideobjektet som opprettet det. Rekkefølgefellen å se opp for, er AddPage. Den peker CurrentPage om til den nye siden i det øyeblikket den returnerer, så ethvert feltkall etter den havner på den nye siden, selv om feltet logisk sett hørte til siden du nettopp forlot. Fullfør hver side, tegnet innhold og felt sammen, før du kaller AddPage

procedure BuildClaimForm(Pdf: THotPDF);
begin
  // Side 1: søkerblokk
  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 peker nå på side 2
  Pdf.CurrentPage.AddListBox('riders', 'None',
    ['None', 'Flood', 'Earthquake'], Rect(50, 500, 200, 600));
end;

Koordinater følger PDF-konvensjonen, med origo i sidens nedre venstre hjørne. Dette er samme origo som TextOut bruker for tegnet tekst, så Rect(50, 100, 200, 120) havner nær bunnen av en Letter-side, ikke toppen. VCL legger Y øverst og lar den vokse nedover, så en layouttabell som porteres rett over, kommer ut vertikalt speilvendt, med hvert felt flippet til feil ende av siden. Gjør konverteringen én gang i en delt hjelpefunksjon i stedet for ved hvert kallsted, så retter én enkelt korrigering opp hele skjemaet

Å koble knapper til URI-, JavaScript- og innsendingshandlinger

En trykknapp er inaktiv til en handling er festet til den. HotPDF eksponerer handlingstypene fra ISO 32000-1 §12.6.4 gjennom enumereringen THPDFButtonAction (baURI, baJavaScript, baSubmitURL, baResetForm, baHide, baShow, baNamed), og tilbyr to metoder som oppretter knappen og binder handlingen dens i ett kall

HotPDF trykknapp-handlingstyper i Delphi: baURI-lenker, baJavaScript-skript og SubmitForm-innsending med eksplisitte formatflagg
Ett bindingskall knytter hvilken som helst av de tre handlingsordbøkene, og bare submit-varianten bærer en flaggkontrakt med mottakerendepunktet
// Åpne en hjelpeside i systemets nettleser
Pdf.CurrentPage.AddPushButtonWithAction('btnHelp', 'Help',
  'https://www.example.com/claims-help', Rect(320, 700, 420, 730), baURI);

// Kjør JavaScript på viser-siden
Pdf.CurrentPage.AddPushButtonWithAction('btnRecalc', 'Recalculate',
  'app.alert("Totals updated.");', Rect(320, 660, 420, 690), baJavaScript);

// Send inn som XFDF og behold tomme felt i nyttelasten
Pdf.CurrentPage.AddPushButtonWithSubmitAction('btnSubmit', 'Submit claim',
  'https://api.example.com/claims', Rect(320, 620, 420, 650),
  [sffXFDF, sffIncludeNoValueFields]);

Innsendingsflaggene fortjener mer omtanke enn de vanligvis får. AddPushButtonWithSubmitAction tar imot et THPDFSubmitFormFlags-sett, og et tomt sett gir en vanlig url-kodet post, som er formatet mange eksempelendepunkter godtar og mange produksjonsendepunkter avviser. Å legge til sffXFDF bytter nyttelasten til XFDF. sffGetMethod endrer HTTP-verbet. sffIncludeNoValueFields beholder tomme felt i nyttelasten i stedet for å droppe dem stille, noe som betyr noe i det øyeblikket mottakeren skiller «fraværende» fra «tomt». Flaggsettet er en del av grensesnittavtalen din med det mottakende endepunktet, så avklar det med teamet som parser innsendingen, ikke etter den første avviste batchen

Feltnivå-JavaScript: tastetrykk, formatering og validering

Knappeklikk er ikke det eneste stedet handlinger finnes. HotPDF fester også JavaScript til de feltvise hendelsene som skriptkapable visere utløser mens en bruker skriver inn data. Det finnes tre utløsere, og de utløses på ulike punkter i inndatalivssyklusen. En keystroke-handling kjører etter hvert som hvert tegn kommer inn, og igjen ved commit. En format-handling skriver om den viste verdien etter at en endring er committet, rent for presentasjonens skyld. En validate-handling får siste ord, og godtar eller avviser den committede verdien før den blir feltets verdi

Livssyklus for HotPDF feltnivå JavaScript-hendelser fra tastetrykk til validate til format, med advarselen om validering på serversiden nedenfor
Tastetrykks- og valideringsskript kan avvise inndata, mens format bare retusjerer visningen, og ingen skript overlever en leser uten JavaScript-motor
// Avvis committede verdier som ikke er plausible e-postadresser
Pdf.AttachFieldKeyStrokeAction('applicant.email',
  'if (event.willCommit && !/^[\w.-]+@[\w.-]+\.\w+$/.test(event.value)) event.rc = false;');

// Vis amerikanske telefonnumre som (NNN) NNN-NNNN
Pdf.AttachFieldFormatAction('applicant.phone',
  'event.value = event.value.replace(/(\d{3})(\d{3})(\d{4})/, "($1) $2-$3");');

// Avvis søkere under 18 år ved commit
Pdf.AttachFieldValidateAction('applicant.age',
  'if (parseInt(event.value) < 18) event.rc = false;');

Å sette event.rc = false inne i et keystroke- eller validate-skript forteller viseren at inndataene skal avvises. Fangsten er at ingenting av dette kjører med mindre viseren leveres med en JavaScript-motor. Acrobat og noen få skrivebordsprodukter har en. De fleste mobilleserne, nettleserinnebygde rendere og utskriftspipeliner har det ikke, og de dropper skriptene uten et ord. Så feltskript forbedrer datakvaliteten for den delen av brukerne hvis leser kjører dem, og det er alt de gjør. De er ingen sikkerhetsgrense. Hver innsendte verdi må fortsatt valideres på serveren når den kommer inn, fordi du ikke kan anta at klienten har sjekket noe som helst

Defekter som består visuell gjennomgang

De vanskeligste AcroForm-defektene å fange opp er de som lever i datastrukturen snarere enn i rendringen, fordi det å åpne filen og se på den ikke forteller deg noe. Fire dukker opp ofte nok til å være verdt å nevne, og hver av dem har en mekanisk test som finner den før utgivelse

  • Avvik i eksportverdi. En avkrysningsboks opprettet som AddCheckBox('consent', 'Yes', ...) sender inn Yes. En mottaker som matcher på Y, avviser hver innsending selv om siden ser perfekt ut. Fyll ut skjemaet, eksporter det som XFDF fra Acrobat, og sammenlign verdiene med den datastrukturen mottakeren faktisk forventer
  • Utilsiktet verdispeiling. To felt som deler samme fullt kvalifiserte navn, smelter sammen til ett. Symptomet dukker opp når data legges inn, aldri ved generering, så testen består i å skrive i skjemaet, ikke i å rendre det og vurdere resultatet visuelt
  • Komboverdier utenfor alternativlisten. Når gjeldende verdi som sendes til AddComboBox, ikke er ett av de oppførte alternativene, er visere uenige om hvorvidt den skal vises, tømmes eller flagges. Hold standardverdien innenfor listen, så forsvinner uenigheten
  • Felt som fortsatt er redigerbare etter at arbeidsflyten er lukket. HotPDF har ikke noe kall for å flate ut visningen for AcroForm-felt. Den støttede måten å fryse et fullført skjema på, er å opprette feltene med flagget ffReadOnly, som holder verdien synlig gjennom feltets eget appearance-stream samtidig som redigering nektes. Feltet forblir et levende skjemaobjekt, noe som er det nedstrøms sammenstillings- og signeringsverktøy forventer å finne

Én viser-side atferd er verdt en regresjonsmerknad selv om ingen kodeendring adresserer den. Bedriftsdistribusjoner av Acrobat kan deaktivere JavaScript eller begrense innsendingsmål via policy, så en handling som fungerte gjennom hver utviklingsversjon, kan ligge død på et låst kundeskrivebord. Planlegg en synlig reserveløsning for tilfellet der knappen ikke gjør noe, selv om den reserveløsningen bare er en trykt instruksjon som forteller brukeren hva de skal gjøre i stedet

Hvor skjemaarbeidet knytter seg til resten av dokumentet

Et signaturfelt er selv en AcroForm-felttype. Et skjema som senere skal sertifiseres eller motsignes, er bedre tjent med å reservere det feltet under genereringen enn å få det lappet inn i etterkant, og årsakene til dette på byte-nivå står i følgeartikkelen om digitale signaturer og PAdES-signering med HotPDF. Inndata som kommer som XFA-pakker i stedet for opprinnelig AcroForm, er en annen situasjon: å flate ut XFA til AcroForm-felt er sin egen arbeidsflyt med sin egen tapsmodell, fordi de to skjemateknologiene ikke kan sameksistere i én fil

Felt-, handlings- og utløsermetodene som vises her, er en del av standard-API-et til HotPDF Delphi Component for Delphi og C++Builder; produktsiden lenker til den fullstendige referansen, inkludert overloadene for feltflagg og den komplette enumereringen av innsendingsflagg