Tehnični članak

Nastavljanje vrednosti polj AcroForm v naloženem PDF

HotPDF Delphi Component izpolni obstoječe polje AcroForm v naloženem PDF s klicem THotPDF.SetFormFieldValue, naslovljeno bodisi z indeksom polja z osnovo nič bodisi s polnim kvalificiranim imenom polja. Zapis novega vnosa /V je lažji del; klic naredi zanesljiv na resničnih obrazcih to, da ista metoda usklajuje še tri kose stanja, ki so nevidni, dokler ne gredo narobe: dekodirano identiteto polja, da je ne-ASCII ime sploh mogoče najti, stanje pojavitve /AS na gradnikih potrditvenih polj in gumbov ter niz izbirnih indeksov /I na izbirnih poljih. Vidni tok pojavitve je ločen, izrecen korak skozi EnsureLoadedFieldAppearanceStream

Scenarij je vsakdanji: stranka vam pošlje svoj obrazec, davčno napoved, zavarovalniški zahtevek, naročilnico, ki jo je nekdo sestavil v Acrobatu pred leti, vaša aplikacija Delphi pa ga mora napolniti iz baze podatkov in vrniti datoteko, ki se povsod pravilno odpre. O tem, kako je bil obrazec izdelan, ne odločate. Imena polj so lahko kodirana v UTF-16, izvozne vrednosti potrditvenih polj so lahko 2 in ne Yes, kombinirani seznami pa uporabljajo pare možnosti [izvoz prikaz]. Vsaka od teh podrobnosti ima pravilo v ISO 32000-1 in vsako pravilo je nekaj, kar SetFormFieldValue zdaj reši namesto vas. Ta članek govori o tem, kaj počne, zakaj in kje se ustavi. Za sorodno težavo ustvarjanja polj, ki jih še ni, glejte dodajanje polj AcroForm v naložen PDF v Delphiju

Zakaj SetFormFieldValue ne najde polja z ne-ASCII imenom?

Pred različico v2.752.1 je bil odgovor kodiranje: polje je v datoteki živelo pod šestnajstiškim imenom UTF-16BE, predpomnilnik imen pa je shranil šestnajstiški zapis in ne besedila. ISO 32000-1 §12.7.3.1 določa delno ime polja /T kot besedilni niz, §7.9.2.2 pa pravi, da je besedilni niz lahko UTF-16BE z vodilno oznako vrstnega reda bajtov FE FF. Orodja za izdelavo take nize redno serializirajo kot šestnajstiške nize po §7.3.4.3, zato polje z imenom Straße prispe kot <FEFF005300740072006100DF0065>. Znotraj HotPDF THPDFStringObject.Value hrani surovo šestnajstiško besedilo vsakič, ko je nastavljen IsHexadecimal, kar je natanko to, kar hočete za pot brez izgub skozi izvirni slovar in natanko to, česar nočete kot iskalni ključ. HPDFLoadedFormTextName ti dve zadevi loči. Ko se gradi predpomnilnik razmerij, gre vsaka vrednost /T skozenj: če je objekt niza šestnajstiški, HPDFHexToBytes obnovi zaporedje bajtov; če se bajti začnejo s FE FF in imajo sodo dolžino, se koristna obremenitev dekodira kot UTF-16BE in znova kodira kot UTF-8; rezultat se nato s piko združi z imenom nadrejenega in tvori polno kvalificirano ime, ki ga opisuje §12.7.3.1, tako da je otrok z imenom City pod nadrejenim z imenom Address vpisan kot Address.City. Ključ predpomnilnika se normalizira na male črke, zato uspe tudi SetFormFieldValue('address.city', ...); to je udobje onkraj standarda, saj specifikacija imena obravnava kot občutljiva na velikost črk. Ključno je, da se spremeni le ključ predpomnilnika. Objekt /T v slovarju polja obdrži svoje šestnajstiško kodiranje, zato shranjevanje dokumenta ne prepiše identitete polja, ki ste ga le izpolnili

Kako HotPDF razreši ne-ASCII imena AcroForm: HPDFHexToBytes obnovi obremenitev UTF-16BE za šestnajstiškim nizom /T, oznaka vrstnega reda bajtov FE FF se dekodira in znova kodira kot UTF-8, kvalificirano ime pa se združi z nadrejenim, tako da Applicant.FullName in polje z imenom Straße oba pristaneta v iskalnem predpomnilniku
Spremeni se le ključ predpomnilnika: slovar polja obdrži svoje šestnajstiško kodiranje, iskanja se normalizirajo na male črke kot udobje onkraj standarda, shranjevanje dokumenta pa nikoli ne prepiše identitete polja, ki ste ga le izpolnili
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('claim-form.pdf') <= 0 then Exit;

    // Kvalificirana imena se dekodirajo iz nizov /T v UTF-16BE in
    // združijo s pikami, zato se gnezdena in ne-ASCII imena razrešijo
    Pdf.SetFormFieldValue('Applicant.FullName', 'Maria Schneider');
    Pdf.SetFormFieldValue('Applicant.Straße', 'Hauptstraße 12');

    // Vrednosti, ki niso Latin-1, potujejo kot šestnajstiški UTF-16BE
    // s predpono FEFF in se zapišejo kot šestnajstiški niz PDF
    Pdf.SetFormFieldValue('Applicant.City', 'FEFF004D00FC006E006300680065006E');

    Pdf.SaveLoadedDocument('claim-form-filled.pdf');
  finally
    Pdf.Free;
  end;
end;

Kaj SetFormFieldValue pravzaprav zapiše?

Oba klica preobtežitve opravita istih pet korakov: poiščeta slovar polja, zapišeta /V skozi HPDFSetDictFormValue, uskladita izbirne indekse, slovar označita kot umazan, uskladita stanja pojavitve gumbov in nazadnje zabeležita indeks polja skozi NoteLoadedFormFieldDirty. Ta zadnji korak je pomemben, če obrazec nosi računske skripte, ker je množica umazanih tisto, kar preobtežitev RecalculateLoadedFormFieldsIncremental brez parametrov porabi, da znova požene le tiste izračune, ki prehodno berejo spremenjeno polje. HPDFSetDictFormValue je sam previden glede vrste objekta, ki ga zamenja. Če je obstoječi /V objekt imena, kar potrditvena in radijska polja uporabljajo za svojo izvozno vrednost, se nova vrednost zapiše kot ime in nikoli kot niz, ker so imena PDF po zasnovi samo ASCII. Sicer zapiše objekt niza in pregleda vrednost, ki ste jo predali: niz, ki se začne s FEFF, ima sodo dolžino in je sestavljen samo iz šestnajstiških števk, se obravnava kot žična oblika UTF-16BE iz §7.9.2.2 in shrani z nastavljenim IsHexadecimal, zato se serializira kot <FEFF...> in ne kot dobesedni (FEFF...). Na tem mehanizmu temelji vrstica City zgoraj; vsak drug niz se shrani kot dobesedni niz z bajti, ki ste jih dali, zato za navadno latinično besedilo preprosto predate navadno besedilo

Zakaj potrditveno polje obdrži staro kljukico po spremembi vrednosti?

Ker pri polju gumba sama vrednost ne odloči, kaj se izriše. ISO 32000-1 §12.7.4.2.3 določa, da gradnik potrditvenega polja nosi stanje pojavitve /AS, ki poimenuje, kateri tok v /AP /N je trenutno prikazan, pregledovalniki pa rišejo iz /AS in ne iz /V. Če /V spremenite v Yes, /AS pa pustite pri Off, je datoteka notranje protislovna in sploščitev bo z veseljem vgradila zastarelo neodkljukano pojavitev v stran, medtem ko podatki obrazca pravijo odkljukano. ReconcileLoadedButtonAppearanceStates obstaja zato, da to vrzel zapre: za polje, katerega /FT je Btn, obišče slovar polja samega in vsak vnos v njegovem nizu /Kids, prebere ime vklopljenega stanja iz /AP /N in prepiše /AS v to ime, kadar se ujema z vrednostjo polja, ali v Off, kadar se ne

Zakaj potrditveno polje HotPDF obdrži staro kljukico, ko se spremeni le /V: pregledovalniki rišejo iz stanja pojavitve /AS v /AP /N, zato ReconcileLoadedButtonAppearanceStates obišče polje in vsakega otroka, prebere ime vklopljenega stanja kot prvi ključ, ki ni Off, in prepiše /AS ob ujemanju ali drugače v Off
Radijske skupine vsakega otroka primerjajo z vrednostjo nadrejenega, ki jo InheritedButtonValue obnovi s hojo po verigi /Parent, zato nastavitev skupine na eno izvozno vrednost vklopi natanko ta gradnik in izklopi vsakega brata

Popravek v v2.752.3 sta oblikovali dve podrobnosti iz resničnih obrazcev. Prvič, slovar običajne pojavitve sme vsebovati samo vklopljeno stanje; §12.7.4.2.3 izklopljeno pojavitev poimenuje Off, a orodja za izdelavo njen tok pogosto izpustijo in pustijo, da pregledovalnik ne izriše nič. Starejša koda je obupala, ko je slovar vseboval manj kot dva vnosa, zato so ta enostanjska potrditvena polja tiho obdržala staro kljukico. Preverjanje je zdaj preprosto to, da slovar ni prazen, ime vklopljenega stanja pa se vzame kot prvi ključ, ki ni Off. Drugič, ime vklopljenega stanja je tisto, kar je izbral avtor. Resnični obrazci uporabljajo 2, Yes, On ali lokalizirano besedo, zato primerjava poteka proti dejanskemu ključu, brez občutljivosti na velikost črk, in nikoli proti vnaprej zapisanemu Yes. Radijski gumbi dodajo še eno gubo, opisano v §12.7.4.2.4: izbira živi v /V na nadrejenem polju, medtem ko posamezni otroci posedujejo gradnike in običajno nimajo svojega /V. Gnezdeni pomožnik InheritedButtonValue zato hodi navzgor po verigi /Parent, do 64 ravni, dokler ne najde neprazne vrednosti, tako da se vsak otrok primerja z vrednostjo skupine, ki ji pripada. Nastavitev nadrejenega na izvozno vrednost enega otroka vklopi natanko tega otroka in izklopi vsakega brata

// Potrditveno polje: izvozna vrednost se mora ujemati s ključem
// vklopljenega stanja v /AP /N (pogosto 'Yes', a resnični obrazci uporabijo '2', 'On' ali karkoli drugega)
Pdf.SetFormFieldValue('Consent', 'Yes');

// Radijska skupina: /V se zapiše na nadrejenem; vsak otrok-gradnik
// dobi /AS nastavljen na svoje izvozno ime ali na Off
Pdf.SetFormFieldValue('PaymentMethod', 'Card');

// Čiščenje potrditvenega polja: vrednost brez ujemanja da /AS Off
Pdf.SetFormFieldValue('Newsletter', 'Off');

Izbirna polja: kako /I ostane v koraku z /V

Pri kombiniranem ali seznamskem polju /V ni edino mesto, kjer je izbira zabeležena. Tabela 231 v §12.7.4.4 določa /I kot niz indeksov z osnovo nič v /Opt, ki označuje izbrane elemente, in pregledovalnik, ki najde /I na možnosti 0, medtem ko /V navaja možnost 3, lahko poudari napačno vrstico. Od različice v2.754.1 HPDFReconcileChoiceSelection teče znotraj vsakega klica SetFormFieldValue in kadar je podedovani /FT enak Ch, znova zgradi /I iz nove vrednosti. Vrstni red operacij je nameren. Lokalni vnos /I se izbriše prvi, ne da bi se dotaknili njegove vsebine: če je bil stari niz posredni objekt, ki si ga deli drugo polje, bi spreminjanje na mestu pokvarilo izbiro onega drugega polja, zato rutina referenco opusti in namesto nje ustvari svež neposredni niz. Nato /Opt razreši skozi verigo /Parent, saj so možnosti izbire lahko podedovane, in pregleda vnose. Gola možnost niza se primerja neposredno; par [izvoz prikaz] se primerja po svojem izvoznem elementu, par z manj kot dvema elementoma pa se preskoči. Obe strani gresta skozi HPDFLoadedFormTextName, zato se šestnajstiška možnost UTF-16 ujema s šestnajstiško vrednostjo UTF-16, ne da bi ju morali zapisati enako. Ob prvem ujemanju se zapiše /I z enim elementom in pregledovanje se ustavi; skalarna vrednost vedno zamenja vsakršno prejšnjo večkratno izbiro, ne glede na zastavico MultiSelect

Kako HotPDF ohrani izbirno polje skladno: HPDFReconcileChoiceSelection izbriše lokalni niz /I, preden se ga dotakne, razreši /Opt skozi verigo /Parent, primerja izvozno polovico vsake možnosti skozi HPDFLoadedFormTextName, ob prvem ujemanju zapiše /I z enim elementom in ne zapiše nič, kadar vrednost urejljivega kombiniranega polja nima indeksa
Gola možnost niza se primerja neposredno, par izvoz-prikaz pa po svojem izvoznem elementu, vrednost zunaj /Opt pa pravilno ne pusti nobenega indeksa — zastarel /I, ki kaže na napačno vrstico, bi bil slabši od nobenega

Ko se nič ne ujema, se /I sploh ne zapiše. To je pravi izid za urejljivo kombinirano polje, kjer §12.7.4.4 uporabniku dovoljuje, da vtipka vrednost zunaj seznama možnosti; taka vrednost nima indeksa in zastarel indeks bi bil slabši od nobenega. To je tudi tisto, kar dobite, če paru možnosti predate prikazno oznako namesto izvozne vrednosti, zato, ko kombinirano polje noče pokazati vaše izbire, preverite, katero polovico para ste predali

// /Opt je [[US United States] [CA Canada] [MX Mexico]]:
// ujemanje po izvozni vrednosti, /I postane [1]
Pdf.SetFormFieldValue('Country', 'CA');

// Urejljivo kombinirano polje z vrednostjo zunaj /Opt: /V se zapiše,
// /I se odstrani in noben indeks se ne izmisli
Pdf.SetFormFieldValue('Title', 'Principal Engineer');

Vrednost in pojavitev sta dve ločeni operaciji

SetFormFieldValue se toka pojavitve besedilnega ali izbirnega polja nikoli ne dotakne. Po klicu /V nosi novo besedilo, medtem ko /AP /N še vedno izriše staro, in katero od teh dveh pregledovalnik pokaže, je odvisno od tega, ali slovar AcroForm nosi /NeedAppearances true po §12.7.3.3 in ali pregledovalnik to upošteva. Če hočete, da datoteka novo vrednost upodobi v vsakem bralniku, tudi v orodjih za sploščitev in generatorjih sličic, ki zastavico prezrejo, pokličite EnsureLoadedFieldAppearanceStream z indeksom polja. Ta zgradi Form XObject iz podedovanega niza /DA, poravnave /Q, postavitve glavnika /MaxLen in vrednosti, poimenovano pisavo razreši skozi vire /DR slovarja AcroForm, da pisava Type0 obdrži svojo izpeljano pisavo in se ne poslabša v Helvetica, ter vrne True, kadar je vsaj en gradnik prejel tok. Preobtežitev SetFormFieldValue po imenu vam ne vrne indeksa, zato ga pridobite skozi GetFormField, ki vrne THPDFLoadedFormField, ki je vaš in ga morate sprostiti. Regresijski niz za spremembo v2.752.1 je glede te razdelitve izrecen: nastavi vrednost, pokliče EnsureLoadedFieldAppearanceStream, nato upodobi stran in preveri, da so se pike znotraj pravokotnika gradnika spremenile, pike zunaj njega pa ne. Preverjanje, da se je /V spremenil, ne dokaže ničesar o tem, kaj bo uporabnik videl

var
  Field: THPDFLoadedFormField;
begin
  Pdf.SetFormFieldValue('Applicant.FullName', 'Maria Schneider');
  Field := Pdf.GetFormField('Applicant.FullName');
  try
    // Novo vrednost vriši v /AP, da jo pokažejo tudi
    // pregledovalniki, ki /NeedAppearances prezrejo
    if not Pdf.EnsureLoadedFieldAppearanceStream(Field.Index) then
      raise Exception.Create('No widget rectangle to paint into');
  finally
    Field.Free;
  end;
  Pdf.SaveLoadedDocument('claim-form-filled.pdf');
end;

Meje, ki jih velja poznati, preden na tem gradite

ReconcileLoadedButtonAppearanceStates preizkusi lokalni /FT slovarja, ki ste ga naslovili, zato deluje na nadrejenem radijskem polju ali na potrditvenem polju, ki nosi svoj /FT; gradnik-otrok, naslovljen samostojno, z /FT le na svojem nadrejenem, po tej poti ni usklajen. HPDFReconcileChoiceSelection obravnava eno samo skalarno vrednost in zapiše največ en indeks; seznamski polji z več izbranimi vnosi sta zunaj tega, kar SetFormFieldValue modelira. Nobena od obeh rutin ne preveri vrednosti, ki jo predate, proti /Opt ali proti ključem vklopljenega stanja, zato tipkarska napaka da Off potrditveno polje ali kombinirano polje brez indeksa in ne izjeme. In GetFormFieldValue vrne shranjeno besedilo /V takšno, kot sedi v slovarju, kar pri šestnajstiško kodirani vrednosti pomeni šestnajstiški zapis in ne dekodiranega besedila

Ko so vrednosti notri in pojavitve izrisane, se naravna naslednja koraka nahajata na obeh straneh te operacije. Izmenjava podatkov polj s tujimi sistemi v večjem obsegu, namesto po en klic SetFormFieldValue naenkrat, je tisto, kar pokriva uvoz in izvoz XFDF v Delphiju. In ko je izpolnjen obrazec dokončen in ne sme biti več urejljiv, sploščitev polj AcroForm in XFA v Delphiju v statično vsebino strani vgradi natanko tista stanja /AS in tokove pojavitve, ki so opisani tukaj, zato pred sploščitvijo nista usklajena po izbiri, ampak po nuji

API za urejanje naloženih obrazcev iz tega članka, vključno s SetFormFieldValue, EnsureLoadedFieldAppearanceStream in grafom inkrementalnega preračunavanja, se dobavlja v okviru HotPDF Delphi Component za Delphi in C++Builder