HotPDF Delphi Component puni postojeće AcroForm polje u učitanom PDF-u kroz THotPDF.SetFormFieldValue, adresirano bilo nulto baziranim indeksom polja bilo potpuno kvalificiranim imenom polja. Upisivanje novog unosa /V laki je dio; ono što taj poziv čini pouzdanim na stvarnim obrascima jest da ista metoda održava konzistentnima i tri stanja koja su nevidljiva dok ne pođu po zlu: dekodirani identitet polja, da bi se ne-ASCII ime uopće moglo naći, stanje izgleda /AS na widgetima potvrdnih okvira i radio gumba, i array indeksa odabira /I na poljima izbora. Vidljivi appearance stream zaseban je, izričit korak kroz EnsureLoadedFieldAppearanceStream
Scenarij je onaj svakodnevni: kupac vam pošalje svoj obrazac, poreznu prijavu, zahtjev za osiguranje, narudžbenicu koju je netko sastavio u Acrobatu prije godinama, a vaša Delphi aplikacija mora ga popuniti iz baze i vratiti datoteku koja se ispravno otvara svugdje. Nemate nikakvu kontrolu nad tim kako je obrazac autoriran. Imena polja mogu biti UTF-16 kodirana, izvozne vrijednosti potvrdnih okvira mogu biti 2, a ne Yes, a combo boxovi mogu koristiti parove opcija [export display]. Svaka od tih pojedinosti ima svoje pravilo u ISO 32000-1, i svako pravilo SetFormFieldValue sada rješava umjesto vas. Ovaj članak govori o tome što radi, zašto i gdje staje. Za srodni problem stvaranja polja koja još ne postoje pogledajte dodavanje AcroForm polja u učitanom PDF-u u Delphiju
Zašto SetFormFieldValue ne uspije naći polje s ne-ASCII imenom?
Prije v2.752.1 odgovor je bio kodiranje: polje je u datoteci živjelo pod heksadecimalnim UTF-16BE imenom, a cache imena spremao je heksadecimalni zapis umjesto teksta. ISO 32000-1 §12.7.3.1 definira djelomično ime polja /T kao text string, a §7.9.2.2 kaže da text string može biti UTF-16BE s vodećim FE FF byte order markom. Alati za autoriranje rutinski serijaliziraju takva imena kao hex stringove prema §7.3.4.3, pa polje nazvano Straße stiže kao <FEFF005300740072006100DF0065>. Unutar HotPDF-a THPDFStringObject.Value drži sirovi heksadecimalni tekst kad god je IsHexadecimal postavljen, što je točno ono što želite za round trip izvornog rječnika bez gubitka i točno ono što ne želite kao ključ za pretragu. HPDFLoadedFormTextName razdvaja te dvije brige. Kad se gradi cache relacija, svaka vrijednost /T prolazi kroz njega: ako je string objekt heksadecimalan, HPDFHexToBytes vraća slijed bajtova; ako bajtovi počinju s FE FF i parne su duljine, payload se dekodira kao UTF-16BE i ponovno kodira kao UTF-8; rezultat se zatim spaja s imenom roditelja točkom u potpuno kvalificirano ime koje opisuje §12.7.3.1, pa se dijete nazvano City pod roditeljem nazvanim Address registrira kao Address.City. Ključ cachea normalizira se na mala slova, što čini da i SetFormFieldValue('address.city', ...) uspije; to je pogodnost iznad standarda, jer specifikacija imena tretira kao osjetljiva na velika i mala slova. Ključno je da se mijenja samo ključ cachea. Objekt /T u rječniku polja zadržava svoje heksadecimalno kodiranje, pa spremanje dokumenta ne prepisuje identitet polja koje ste samo ispunili
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('claim-form.pdf') <= 0 then Exit;
// Kvalificirana imena dekodiraju se iz UTF-16BE /T stringova i
// spajaju točkama, pa se ugniježđena i ne-ASCII imena razrješavaju
Pdf.SetFormFieldValue('Applicant.FullName', 'Maria Schneider');
Pdf.SetFormFieldValue('Applicant.Straße', 'Hauptstraße 12');
// Vrijednosti koje nisu Latin-1 putuju kao FEFF-prefiksirani UTF-16BE hex
// i zapisuju se kao PDF heksadecimalni string
Pdf.SetFormFieldValue('Applicant.City', 'FEFF004D00FC006E006300680065006E');
Pdf.SaveLoadedDocument('claim-form-filled.pdf');
finally
Pdf.Free;
end;
end;
Što SetFormFieldValue zapravo piše?
Oba overloada rade istih pet koraka: lociraju rječnik polja, upisuju /V kroz HPDFSetDictFormValue, usklađuju indekse odabira kod polja izbora, označavaju rječnik prljavim, usklađuju stanja izgleda gumba i konačno zabilježe indeks polja kroz NoteLoadedFormFieldDirty. Taj posljednji korak važan je ako obrazac nosi skripte za izračun, jer prljavi skup ono je što bezargumentni overload RecalculateLoadedFormFieldsIncremental troši da ponovno pokrene samo izračune koji tranzitivno čitaju promijenjeno polje. Sam HPDFSetDictFormValue pažljiv je oko tipa objekta koji zamjenjuje. Ako je postojeći /V name objekt, što potvrdni okviri i radio polja koriste za svoju izvoznu vrijednost, nova vrijednost zapisuje se kao name, nikad kao string, jer su PDF imena po konstrukciji samo ASCII. Inače zapisuje string objekt i pregledava vrijednost koju ste predali: string koji počinje s FEFF, parne je duljine i sastoji se isključivo od hex znamenki tretira se kao UTF-16BE wire oblik iz §7.9.2.2 i sprema s postavljenim IsHexadecimal, pa se serijalizira kao <FEFF...>, a ne kao literal (FEFF...). To je mehanizam na koji se oslanja gornji redak s City; svaki drugi string sprema se kao literal string s bajtovima koje ste dali, pa za običan latinični tekst predajete običan tekst
Zašto potvrdni okvir zadrži staru kvačicu nakon promjene vrijednosti?
Zato što kod polja gumba sama vrijednost ne određuje što se crta. ISO 32000-1 §12.7.4.2.3 propisuje da widget potvrdnog okvira nosi stanje izgleda /AS koje imenuje koji je stream u /AP /N trenutačno prikazan, a preglednici crtaju iz /AS, ne iz /V. Ako promijenite /V u Yes, ali ostavite /AS na Off, datoteka je interno proturječna, a flattening će rado upeći zastarjeli neoznačeni izgled u stranicu dok podaci obrasca govore da je označeno. ReconcileLoadedButtonAppearanceStates postoji da zatvori tu prazninu: za polje čiji je /FT Btn, obilazi sam rječnik polja i svaki unos u njegovu nizu /Kids, čita ime uključenog stanja iz /AP /N i prepisuje /AS u to ime kad odgovara vrijednosti polja, ili u Off kad ne odgovara
Dvije pojedinosti sa stvarnih obrazaca oblikovale su popravak v2.752.3. Prvo, normal appearance rječnik smije sadržavati samo uključeno stanje; §12.7.4.2.3 imenuje isključeni izgled Off, ali alati za autoriranje često izostavljaju njegov stream i puštaju da preglednik ne nacrta ništa. Raniji kod odustajao je kad je rječnik držao manje od dva unosa, pa su ti potvrdni okviri s jednim stanjem tiho zadržavali staru kvačicu. Provjera je sada jednostavno to da rječnik nije prazan, a ime uključenog stanja uzima se kao prvi ključ koji nije Off. Drugo, ime uključenog stanja je što god je autor odabrao. Stvarni obrasci koriste 2, Yes, On ili lokaliziranu riječ, pa se usporedba radi prema stvarnom ključu, neosjetljivo na velika i mala slova, nikad prema tvrdo kodiranom Yes. Radio gumbi dodaju još jedan nabor, opisan u §12.7.4.2.4: odabir živi u /V na roditeljskom polju, dok pojedina djeca posjeduju widgete i obično nemaju vlastiti /V. Ugniježđeni pomoćnik InheritedButtonValue zato hoda lancem /Parent prema gore, do 64 razine, dok ne nađe nepraznu vrijednost, pa se svako dijete uspoređuje s vrijednošću grupe kojoj pripada. Postavljanje roditelja na izvoznu vrijednost jednog djeteta uključuje točno to dijete, a isključuje svakog brata
// Potvrdni okvir: izvozna vrijednost mora odgovarati ključu uključenog stanja u /AP /N
// (često 'Yes', ali stvarni obrasci koriste '2', 'On' ili bilo što drugo)
Pdf.SetFormFieldValue('Consent', 'Yes');
// Radio grupa: /V se upisuje na roditelja; svaki dječji widget dobiva
// /AS postavljen na svoje izvozno ime ili na Off
Pdf.SetFormFieldValue('PaymentMethod', 'Card');
// Brisanje potvrdnog okvira: svaka vrijednost koja ne odgovara nijednom uključenom stanju daje /AS Off
Pdf.SetFormFieldValue('Newsletter', 'Off');
Polja izbora: držanje /I u koraku s /V
Za combo box ili list box /V nije jedino mjesto gdje se bilježi odabir. Tablica 231 u §12.7.4.4 definira /I kao niz nulto baziranih indeksa u /Opt koji identificira odabrane stavke, a preglednik koji nađe /I kako pokazuje na opciju 0 dok /V imenuje opciju 3 može istaknuti pogrešan redak. Od v2.754.1 HPDFReconcileChoiceSelection vrti se unutar svakog poziva SetFormFieldValue i, kad je naslijeđeni /FT jednak Ch, ponovno gradi /I iz nove vrijednosti. Redoslijed operacija je namjeran. Lokalni unos /I briše se prvi, bez diranja njegova sadržaja: ako je stari niz bio neizravni objekt dijeljen s drugim poljem, mijenjanje na mjestu pokvarilo bi odabir tog drugog polja, pa rutina odbacuje referencu i stvara svježi izravni niz. Zatim razrješava /Opt kroz lanac /Parent, jer se opcije izbora mogu naslijediti, i skenira unose. Gola string opcija uspoređuje se izravno; par [export display] uspoređuje se po svom izvoznom elementu, a par s manje od dva elementa preskače se. Obje strane prolaze kroz HPDFLoadedFormTextName, pa hex UTF-16 opcija odgovara hex UTF-16 vrijednosti bez da ih vi ispisujete identično. Na prvom podudaranju upisuje se /I s jednim elementom i skeniranje staje; skalarna vrijednost uvijek zamjenjuje svaki prethodni višestruki odabir, bez obzira na MultiSelect zastavicu
Kad se ništa ne podudara, /I se uopće ne upisuje. To je ispravan ishod za editabilni combo box, gdje §12.7.4.4 dopušta korisniku da upiše vrijednost izvan popisa opcija; takva vrijednost nema indeks, a zastarjeli indeks bio bi gori od nikakvog. To je i ono što dobivate ako paru opcija predate prikaznu oznaku umjesto izvozne vrijednosti, pa kad combo box odbije pokazati vaš odabir, provjerite koju ste polovicu para predali
// /Opt je [[US United States] [CA Canada] [MX Mexico]]:
// podudaranje po izvoznoj vrijednosti, i /I postaje [1]
Pdf.SetFormFieldValue('Country', 'CA');
// Editabilni combo s vrijednošću izvan /Opt: /V se upisuje,
// /I se uklanja, i nikakav indeks se ne izmišlja
Pdf.SetFormFieldValue('Title', 'Principal Engineer');
Vrijednost i izgled dvije su odvojene operacije
SetFormFieldValue nikad ne dira appearance stream tekstualnog polja ili polja izbora. Nakon poziva /V drži novi tekst, dok /AP /N još crta stari, a koje će od to dvoje preglednik pokazati ovisi o tome nosi li AcroForm rječnik /NeedAppearances true prema §12.7.3.3 i poštuje li ga preglednik. Ako trebate da datoteka renderira novu vrijednost u svakom čitaču, uključujući flattenere i generatore thumbnaila koji ignoriraju zastavicu, pozovite EnsureLoadedFieldAppearanceStream s indeksom polja. On gradi Form XObject iz naslijeđenog /DA stringa, /Q quaddinga, /MaxLen comb layouta i vrijednosti, razrješava imenovani font kroz AcroForm resurse /DR da Type0 font zadrži svoj descendant font umjesto da degradira u Helvetica, i vraća True kad je barem jedan widget dobio stream. Overload SetFormFieldValue po imenu ne vraća vam indeks, pa ga dohvatite kroz GetFormField, koji vraća THPDFLoadedFormField koji posjedujete i morate ga osloboditi. Regresijski suite za promjenu v2.752.1 izričit je oko te podjele: postavlja vrijednost, poziva EnsureLoadedFieldAppearanceStream, zatim renderira stranicu i provjerava da su se pikseli unutar pravokutnika widgeta promijenili, a pikseli izvan njega nisu. Provjera da se /V promijenio ne dokazuje ništa o tome što će korisnik vidjeti
var
Field: THPDFLoadedFormField;
begin
Pdf.SetFormFieldValue('Applicant.FullName', 'Maria Schneider');
Field := Pdf.GetFormField('Applicant.FullName');
try
// Upisi novu vrijednost u /AP da je preglednici koji ignoriraju
// /NeedAppearances ipak prikažu
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;
Ograničenja koja vrijedi znati prije nego na ovome gradite
ReconcileLoadedButtonAppearanceStates testira lokalni /FT rječnika koji ste adresirali, pa djeluje na roditelja radio grupe ili na potvrdni okvir koji nosi vlastiti /FT; dječji widget adresiran samostalno, s /FT samo na svom roditelju, ne usklađuje se tim putem. HPDFReconcileChoiceSelection rukuje jednom skalarnom vrijednošću i upisuje najviše jedan indeks; list boxovi s višestrukim odabirom i nekoliko odabranih unosa izvan su onoga što SetFormFieldValue modelira. Nijedna rutina ne validira vrijednost koju predajete prema /Opt ili prema ključevima uključenog stanja, pa tipfeler daje Off potvrdni okvir ili combo bez indeksa umjesto iznimke. A GetFormFieldValue vraća spremljeni tekst /V onakav kakav stoji u rječniku, što za heksadecimalno kodiranu vrijednost znači heksadecimalni zapis, a ne dekodirani tekst
Kad su vrijednosti unutra i izgledi iscrtani, dva prirodna sljedeća koraka stoje s obje strane ove operacije. Razmjena podataka polja s vanjskim sustavima u skupnom obliku, umjesto jednog poziva SetFormFieldValue po putu, ono je što pokriva uvoz i izvoz XFDF-a u Delphiju. A kad je ispunjeni obrazac konačan i više ne treba biti uređiv, flattening AcroForm i XFA polja u Delphiju upeče upravo ovdje opisana stanja /AS i appearance streamove u statični sadržaj stranice, zato njihovo usklađivanje prije flatteninga nije opcionalno
API za uređivanje učitanih obrazaca iz ovog članka, uključujući SetFormFieldValue, EnsureLoadedFieldAppearanceStream i graf inkrementalnog ponovnog izračuna, isporučuje se kao dio HotPDF Delphi Component za Delphi i C++Builder