HotPDF Delphi Component užpildo esamą AcroForm lauką įkeltame PDF per THotPDF.SetFormFieldValue, adresuodamas jį arba nuliu pagrįstu lauko indeksu, arba pilnai kvalifikuotu lauko vardu. Naujo /V įrašo įrašymas yra lengvoji dalis; tai, kas daro šį kvietimą patikimą tikrose formose, yra tai, kad tas pats metodas dar palaiko nuoseklias tris būsenos dalis, kurios yra nematomos, kol nesugenda: nusidekoduotą lauko tapatybę, kad ne ASCII vardą apskritai būtų galima rasti, /AS išvaizdos būseną žymimųjų laukelių ir radijo valdikliuose bei /I pasirinkimo indeksų masyvą pasirinkčių laukuose. Matomas išvaizdos srautas yra atskiras, aiškus žingsnis per EnsureLoadedFieldAppearanceStream
Scenarijus yra kasdienis: klientas atsiunčia jums savo formą – mokesčių deklaraciją, draudimo pretenziją, pirkimo užsakymą, kurį kas nors prieš metus sudėliojo Acrobat – ir jūsų Delphi programa turi ją užpildyti iš duomenų bazės ir grąžinti failą, kuris teisingai atsidaro visur. Jūs nekontroliuojate, kaip forma buvo sukurta. Lauko vardai gali būti UTF-16 koduoti, žymimųjų laukelių eksporto reikšmės gali būti 2, o ne Yes, o išskleidžiamieji sąrašai gali naudoti [export display] parinkčių poras. Kiekviena iš tų smulkmenų turi taisyklę ISO 32000-1, ir kiekvieną taisyklę SetFormFieldValue dabar apdoroja už jus. Šis straipsnis apie tai, ką jis daro, kodėl ir kur sustoja. Gretimą problemą – laukų, kurių dar nėra, kūrimą – rasite straipsnyje AcroForm laukų pridėjimas į įkeltą PDF Delphi aplinkoje
Kodėl SetFormFieldValue neranda lauko su ne ASCII vardu?
Iki v2.752.1 atsakymas buvo kodavimas: laukas faile gyveno po šešioliktainiu UTF-16BE vardu, o vardų podėlis saugojo šešioliktainę rašybą, o ne tekstą. ISO 32000-1 §12.7.3.1 apibrėžia dalinį lauko vardą /T kaip teksto eilutę, o §7.9.2.2 sako, kad teksto eilutė gali būti UTF-16BE su pradiniu FE FF baitų tvarkos žymeniu. Kūrimo įrankiai tokius vardus paprastai serializuoja kaip šešioliktaines eilutes pagal §7.3.4.3, tad laukas, vadinamas Straße, ateina kaip <FEFF005300740072006100DF0065>. HotPDF viduje THPDFStringObject.Value laiko neapdorotą šešioliktainį tekstą, kai tik IsHexadecimal nustatytas, ir tai yra būtent tai, ko norite originalaus žodyno be nuostolių kelionei, ir būtent tai, ko nenorite kaip paieškos rakto. HPDFLoadedFormTextName atskiria šiuos du dalykus. Kai ryšių podėlis pastatomas, kiekviena /T reikšmė pereina per jį: jei eilutės objektas šešioliktainis, HPDFHexToBytes atkuria baitų seką; jei baitai prasideda FE FF ir yra lyginio ilgio, turinys nusidekoduoja kaip UTF-16BE ir iš naujo užkoduojamas kaip UTF-8; rezultatas tada sujungiamas su savo tėvo vardu tašku, kad sudarytų pilnai kvalifikuotą vardą, kurį aprašo §12.7.3.1, tad vaikas, vadinamas City po tėvu, vadinamu Address, užregistruojamas kaip Address.City. Podėlio raktas normalizuojamas į mažąsias raides, ir tai leidžia sėkmingai suveikti ir SetFormFieldValue('address.city', ...); tai patogumas, viršijantis standartą, nes specifikacija vardus traktuoja kaip didžiųjų ir mažųjų raidžių jautrius. Svarbiausia, keičiasi tik podėlio raktas. /T objektas lauko žodyne išlaiko savo šešioliktainį kodavimą, tad dokumento išsaugojimas neperrašo tapatybės lauko, kurį tik užpildėte
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('claim-form.pdf') <= 0 then Exit;
// Kvalifikuoti vardai nusidekoduoja iš UTF-16BE /T eilučių ir
// jungiami taškais, tad lizdiniai ir ne ASCII vardai išsisprendžia
Pdf.SetFormFieldValue('Applicant.FullName', 'Maria Schneider');
Pdf.SetFormFieldValue('Applicant.Straße', 'Hauptstraße 12');
// Reikšmės, kurios nėra Latin-1, keliauja kaip FEFF priešdėlio UTF-16BE hex
// ir įrašomos kaip PDF šešioliktainė eilutė
Pdf.SetFormFieldValue('Applicant.City', 'FEFF004D00FC006E006300680065006E');
Pdf.SaveLoadedDocument('claim-form-filled.pdf');
finally
Pdf.Free;
end;
end;
Ką SetFormFieldValue iš tikrųjų įrašo?
Abi perkrovos atlieka tuos pačius penkis žingsnius: suranda lauko žodyną, įrašo /V per HPDFSetDictFormValue, suderina pasirinkimo indeksus, pažymi žodyną nešvariu, suderina mygtukų išvaizdos būsenas ir galiausiai užregistruoja lauko indeksą per NoteLoadedFormFieldDirty. Tas paskutinis žingsnis svarbus, jei forma nešasi skaičiavimo skriptus, nes nešvarių aibė yra tai, ką vartoja be parametrų esanti RecalculateLoadedFormFieldsIncremental perkrova, kad iš naujo paleistų tik tuos skaičiavimus, kurie tranzytyviai skaito pakeistą lauką. Pats HPDFSetDictFormValue atsargiai elgiasi su objekto tipu, kurį keičia. Jei esamas /V yra vardo objektas, o būtent tokį žymimieji laukeliai ir radijo laukai naudoja savo eksporto reikšmei, nauja reikšmė įrašoma kaip vardas, niekada kaip eilutė, nes PDF vardai iš konstrukto yra tik ASCII. Priešingu atveju jis įrašo eilutės objektą ir apžiūri jūsų perduotą reikšmę: eilutė, kuri prasideda FEFF, yra lyginio ilgio ir susideda vien iš šešioliktainių skaitmenų, traktuojama kaip UTF-16BE laidinė forma iš §7.9.2.2 ir įrašoma su nustatytu IsHexadecimal, tad serializuojasi kaip <FEFF...>, o ne kaip literalas (FEFF...). Būtent šiuo mechanizmu remiasi aukščiau esanti City eilutė; bet kuri kita eilutė įrašoma kaip literalas su tais baitais, kuriuos davėte, tad paprastam lotyniškam tekstui perduodate paprastą tekstą
Kodėl žymimasis laukelis po reikšmės pakeitimo išlaiko seną varnelę?
Nes mygtuko laukui vien reikšmė nenusprendžia, kas nubraižoma. ISO 32000-1 §12.7.4.2.3 nustato, kad žymimojo laukelio valdiklis nešasi /AS išvaizdos būseną, įvardijančią, kuris /AP /N srautas šiuo metu rodomas, o peržiūros programos piešia iš /AS, o ne iš /V. Jei pakeisite /V į Yes, bet paliksite /AS ties Off, failas yra viduje prieštaringas, ir suliejimas mielai įkeps pasenusią nepažymėtą išvaizdą į puslapį, kol formos duomenys sako „pažymėta“. ReconcileLoadedButtonAppearanceStates egzistuoja tam, kad užpildytų šią spragą: laukui, kurio /FT yra Btn, jis aplanko patį lauko žodyną ir kiekvieną jo /Kids masyvo įrašą, perskaito įjungtos būsenos vardą iš /AP /N ir perrašo /AS į tą vardą, kai jis sutampa su lauko reikšme, arba į Off, kai nesutampa
v2.752.3 pataisą suformavo dvi smulkmenos iš tikrų formų. Pirma, normalios išvaizdos žodynui leidžiama talpinti tik įjungtą būseną; §12.7.4.2.3 išjungtą išvaizdą vadina Off, bet kūrimo įrankiai dažnai praleidžia jos srautą ir leidžia peržiūros programai nieko nenubraižyti. Ankstesnis kodas pasiduodavo, kai žodyne būdavo mažiau nei du įrašai, tad tie vienos būsenos žymimieji laukeliai tyliai išlaikydavo seną varnelę. Dabar patikrinimas tėra toks, kad žodynas nėra tuščias, o įjungtos būsenos vardas imamas kaip pirmas raktas, kuris nėra Off. Antra, įjungtos būsenos vardas yra toks, kokį pasirinko autorius. Tikros formos naudoja 2, Yes, On arba lokalizuotą žodį, tad palyginama su tikruoju raktu, nekreipiant dėmesio į raidžių dydį, o niekada su užkietintu Yes. Radijo mygtukai prideda dar vieną raukšlę, aprašytą §12.7.4.2.4: pasirinkimas gyvena /V tėviniame lauke, o atskiri vaikai valdo valdiklius ir paprastai neturi savo /V. Todėl lizdinis InheritedButtonValue pagalbininkas kyla /Parent grandine iki 64 lygių, kol randa ne tuščią reikšmę, tad kiekvienas vaikas lyginamas su grupės, kuriai priklauso, reikšme. Nustačius tėvą į vieno vaiko eksporto reikšmę, įjungiamas būtent tas vaikas ir išjungiami visi broliai
// Žymimasis laukelis: eksporto reikšmė turi sutapti su įjungtos
// būsenos raktu /AP /N (dažnai 'Yes', bet tikros formos naudoja '2', 'On' ar bet ką)
Pdf.SetFormFieldValue('Consent', 'Yes');
// Radijo grupė: /V įrašoma tėvui; kiekvienas vaiko valdiklis gauna
// /AS savo eksporto vardą arba Off
Pdf.SetFormFieldValue('PaymentMethod', 'Card');
// Žymimojo laukelio nuėmimas: bet kuri reikšmė, nesutampanti su jokia įjungta būsena, duoda /AS Off
Pdf.SetFormFieldValue('Newsletter', 'Off');
Pasirinkčių laukai: /I derinimas su /V
Išskleidžiamajam ar slenkamajam sąrašui /V nėra vienintelė vieta, kur įrašomas pasirinkimas. §12.7.4.4 231 lentelė apibrėžia /I kaip nuliu pagrįstų indeksų į /Opt masyvą, kuris įvardija pasirinktus elementus, o peržiūros programa, kuri randa /I rodantį į 0 parinktį, kol /V įvardija 3 parinktį, gali paryškinti neteisingą eilutę. Nuo v2.754.1 HPDFReconcileChoiceSelection paleidžiamas kiekvieno SetFormFieldValue kvietimo viduje ir, kai paveldėtas /FT yra Ch, perstato /I iš naujos reikšmės. Operacijų tvarka yra sąmoninga. Vietinis /I įrašas pirmiausia ištrinamas, neliečiant jo turinio: jei senasis masyvas buvo netiesioginis objektas, bendras su kitu lauku, jo keitimas vietoje sugadintų kito lauko pasirinkimą, tad rutina numeta nuorodą ir vietoje jos sukuria šviežią tiesioginį masyvą. Tada ji išsprendžia /Opt per /Parent grandinę, nes pasirinkčių parinktys gali būti paveldimos, ir skenuoja įrašus. Nuogas eilutės elementas lyginamas tiesiogiai; [export display] pora lyginama pagal savo eksporto elementą, o pora su mažiau nei dviem elementais praleidžiama. Abi pusės eina per HPDFLoadedFormTextName, tad šešioliktainis UTF-16 elementas sutampa su šešioliktaine UTF-16 reikšme, nereikalaujant jų ištarti identiškai. Ties pirmu sutapimu įrašomas vieno elemento /I ir skenavimas sustoja; skaliarinė reikšmė visada pakeičia bet kurį ankstesnį kelių pasirinkimų rinkinį, nepriklausomai nuo MultiSelect vėliavėlės
Kai niekas nesutampa, /I neįrašomas visai. Tai teisingas rezultatas redaguojamam išskleidžiamajam sąrašui, kur §12.7.4.4 leidžia naudotojui įrašyti reikšmę už parinkčių sąrašo ribų; tokia reikšmė neturi indekso, o pasenęs indeksas būtų blogiau už jokio. Tai taip pat tai, ką gausite, jei porinei parinkčių lentelei perdavėte rodomą etiketę, o ne eksporto reikšmę, tad kai išskleidžiamasis sąrašas atsisako parodyti jūsų pasirinkimą, patikrinkite, kurią poros pusę pateikėte
// /Opt yra [[US United States] [CA Canada] [MX Mexico]]:
// lyginama pagal eksporto reikšmę, ir /I tampa [1]
Pdf.SetFormFieldValue('Country', 'CA');
// Redaguojamas sąrašas su reikšme už /Opt ribų: /V įrašoma,
// /I pašalinamas, ir joks indeksas neišgalvojamas
Pdf.SetFormFieldValue('Title', 'Principal Engineer');
Reikšmė ir išvaizda yra dvi atskiros operacijos
SetFormFieldValue niekada neliečia teksto ar pasirinkčių lauko išvaizdos srauto. Po kvietimo /V laiko naują tekstą, kol /AP /N vis dar piešia senąjį, ir kurią iš tų dviejų parodys peržiūros programa, priklauso nuo to, ar AcroForm žodynas nešasi /NeedAppearances true pagal §12.7.3.3, ir ar peržiūros programa jį gerbia. Jei jums reikia, kad failas atvaizduotų naują reikšmę kiekviename skaitytuve, įskaitant suliejiklius ir miniatiūrų generatorius, kurie šios vėliavėlės nepaiso, iškvieskite EnsureLoadedFieldAppearanceStream su lauko indeksu. Jis pastato Form XObject iš paveldėtos /DA eilutės, /Q lygiavimo, /MaxLen šukų išdėstymo ir reikšmės, išsprendžia įvardytą šriftą per AcroForm /DR resursus, kad Type0 šriftas išlaikytų savo palikuonio šriftą, o ne nusmuktų iki Helvetica, ir grąžina True, kai bent vienas valdiklis gavo srautą. SetFormFieldValue pagal vardą perkrova negrąžina jokio indekso, tad jo gausite per GetFormField, kuris grąžina THPDFLoadedFormField, kuris priklauso jums ir kurį turite atlaisvinti. v2.752.1 pakeitimo regresijos rinkinys apie šį padalijimą kalba aiškiai: jis nustato reikšmę, iškviečia EnsureLoadedFieldAppearanceStream, tada atvaizduoja puslapį ir patikrina, kad pikseliai valdiklio stačiakampio viduje pakito, o pikseliai už jo ribų – ne. Patikrinimas, kad /V pasikeitė, neįrodo nieko apie tai, ką pamatys naudotojas
var
Field: THPDFLoadedFormField;
begin
Pdf.SetFormFieldValue('Applicant.FullName', 'Maria Schneider');
Field := Pdf.GetFormField('Applicant.FullName');
try
// Nubraižoma nauja reikšmė į /AP, kad peržiūros programos,
// nepaisančios /NeedAppearances, ją vis tiek parodytų
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;
Ribos, kurias verta žinoti prieš ant to statant
ReconcileLoadedButtonAppearanceStates tikrina vietinį to žodyno, kurį adresavote, /FT, tad jis veikia radijo tėvą arba žymimąjį laukelį, kuris nešasi savo paties /FT; vaiko valdiklis, adresuotas atskirai, kai /FT yra tik jo tėve, tuo keliu nesuderinamas. HPDFReconcileChoiceSelection apdoroja vieną skaliarinę reikšmę ir įrašo daugiausiai vieną indeksą; kelių pasirinkimų slenkantieji sąrašai su keliais pasirinktais elementais yra už to, ką SetFormFieldValue modeliuoja. Nė viena rutina netikrina jūsų perduotos reikšmės prieš /Opt ar prieš įjungtos būsenos raktus, tad rašybos klaida duoda Off žymimąjį laukelį arba sąrašą be indekso, o ne išimtį. O GetFormFieldValue grąžina įrašytą /V tekstą tokį, koks jis sėdi žodyne, ir šešioliktainiškai užkoduotai reikšmei tai reiškia šešioliktainę rašybą, o ne nusidekoduotą tekstą
Kai reikšmės jau įrašytos ir išvaizdos nubraižytos, du natūralūs tolesni žingsniai stovi abiejose šios operacijos pusėse. Masinis laukų duomenų mainymasis su išorinėmis sistemomis, o ne po vieną SetFormFieldValue kvietimą, yra tai, ką uždengia XFDF importas ir eksportas Delphi. O kai užpildyta forma jau galutinė ir nebeturi būti redaguojama, AcroForm ir XFA laukų suliejimas Delphi įkepa būtent čia aprašytas /AS būsenas ir išvaizdos srautus į statinį puslapių turinį, ir štai kodėl jų suderinimas prieš suliejimą nėra pasirenkamas
Šiame straipsnyje aprašytas įkeltos formos redagavimo API, įskaitant SetFormFieldValue, EnsureLoadedFieldAppearanceStream ir inkrementinio perskaičiavimo grafą, platinamas kaip HotPDF Delphi Component, skirto Delphi ir C++Builder, dalis