HotPDF Delphi Component tretira /FT, /Ff, /V i /DV na učitanom AcroForm polju kao naslijedive atribute, razriješene obilaskom lanca /Parent. Od v2.754.3 i v2.754.4, imenovano dijete čiji tip dolazi od roditelja ostaje pojedinačno adresabilno, RemoveFormField ostavlja brata-sestru na miru, a ResetLoadedFormField kopira naslijeđenu zadanu vrijednost s njezinim izvornim PDF tipom objekta. Prije toga, iznenađujuće mnogo običnih obrazaca čitalo se pogrešno
Obrazac koji sve ovo izbacuje na vidjelo nije egzotičan. Authoring alat sagradi group čvor group koji jednom nosi /FT /Ch, flagove polja i popis opcija, i objesi pod njega dva imenovana djeteta a i b, svako spojeni field-plus-widget rječnik s ničim osim /T, /Parent, /Rect i vlastitim /V. To je posve legalan način dijeljenja atributa, i točno je to slučaj koji je Limits sekcija članka o postavljanju vrijednosti polja obrasca u učitanom PDF-u u Delphiju označila kao neriješeno: usklađivanje gumba gledalo je samo lokalni /FT. Ovaj članak nastavlja tamo gdje je on stao, pokrivajući kako se field tree klasificira, kako se čitaju naslijeđene vrijednosti i što reset pojedinačnog polja smije zapisati
Koje AcroForm unose polje može naslijediti od roditelja?
ISO 32000-1 §12.7.3.1, Tablica 220, označava /FT, /Ff, /V i /DV kao naslijedive, a Tablica 229 u §12.7.4.3 isto čini za /MaxLen tekstualnog polja, pa će svaki čitač koji gleda samo lokalni rječnik javiti krivi tip, krive flagove i praznu vrijednost za posve valjano dijete. HotPDF sva ova čitanja provlači kroz jedan interni resolver, HPDFLoadedInheritedFieldObject, koji u rječniku provjerava ključ, razriješi indirektnu referencu ako je nađe, a inače slijedi /Parent najviše 128 razina, jer pokvarene datoteke mogu sagraditi /Parent cikluse koji s /Kids nemaju ništa. Javni getteri sjede na njemu: GetFormFieldType, GetFormFieldValue, GetLoadedFormFieldFlags, IsFormFieldRequired, IsFormFieldNoExport, GetLoadedFormFieldMaxLength, GetLoadedFormFieldDefaultValue i option helperi GetLoadedFormFieldOptionCount i GetLoadedFormFieldOptions, koji pokupi i /Opt polje spremljeno na roditelju. Jedno pravilo u resolveru lako je pokvariti: obilazak staje na prvom rječniku koji sadrži ključ, i kad je vrijednost tamo prazan string. Lokalni /V () namjerna je zamjena koja prikriva roditelja, a ne praznina koju treba popuniti s viših razina treea
var
Pdf: THotPDF;
Field: THPDFLoadedFormField;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('survey.pdf') <= 0 then Exit;
// 'group' nosi /FT /Ch, /Ff 131078 i /Opt; dijete
// 'group.b' nosi samo /T, /Parent, /Rect i vlastito /V
Field := Pdf.GetFormField('group.b');
try
if Pdf.GetFormFieldType(Field.Index) = lfftChoice then
begin
// 131078 = Combo (bit 18) + NoExport (bit 3) + Required (bit 2)
Writeln(Pdf.GetLoadedFormFieldFlags(Field.Index));
Writeln(Pdf.IsFormFieldRequired(Field.Index)); // TRUE
Writeln(Pdf.GetLoadedFormFieldOptionCount(Field.Index));
Writeln(Pdf.GetFormFieldValue(Field.Index)); // lokalni /V
end;
finally
Field.Free;
end;
finally
Pdf.Free;
end;
end;
Zašto je lokalni /FT krivi test za terminalno polje?
Zato što roditelj može dovoditi tip a i dalje posjedovati imenovana dijete-polja, pa prisutnost /FT ne govori ništa o tome gdje se field tree završava. Stari obilazak proglašavao je čvor terminalnim čim ima vlastiti /FT ili nema /Kids. U gore opisanom obrascu group ima i /FT /Ch i /Kids, pa je registriran kao jedno polje imena group s dva widgeta, a potpuna imena group.a i group.b jednostavno su nestala. GetFormFieldCount vratio je 1, upit po imenu djeteta pao, a SetFormFieldValue mogao je pisati samo u zajedničkog roditelja. Zamjenski test, HPDFLoadedFieldHasChildFields, gleda djecu umjesto roditelja: kid je dijete-polje ako ima vlastito /T, ima vlastite /Kids ili uopće nije rječnik /Subtype /Widget. Tek kad nijedan kid ne kvalificira, čvor je terminalan, a njegova se djeca tretiraju kao njegove widget anotacije
Dva rubna slučaja koja su oblikovala to pravilo oba dolaze iz spojenih rječnika, koje §12.7.3.1 dopušta kad polje ima jedan widget. Imenovani spojeni rječnik nosi /Subtype /Widget i i dalje je dijete-polje, pa sam subtype ne može poslati u anonimni widget popis roditelja; /T pobjeđuje. Dogodi se i obratno: neki producenti ponavljaju roditeljev /FT na svakom anonimnom widgetu, pa /FT ne može služiti kao dokaz da widget počinje novo polje. Klasifikaciju dijele cache odnosa, FormFieldExists i RemoveFormField, i svaki od tih obilazaka sada bilježi rječnike koje je već posjetio i staje nakon 128 razina. Regresijska datoteka čija se group dva puta nabraja samu sebe, /Kids [5 0 R 5 0 R 6 0 R 7 0 R], i dalje javlja točno dva polja umjesto da se vrti u krug ili broji isti čvor dvaput
Kako RemoveFormField izbjegava brisanje sestrinskih polja?
RemoveFormField sada briše samo dijete koje imenujete, jer se otkrivanje i brisanje napokon slažu oko toga što je terminalno polje. To se slaganje važnije je nego što izgleda. Overload po imenu razriješi indeks kroz cache odnosa, pa u drugom obilasku nad /AcroForm /Fields broji terminalna polja. Kad se cache jednom popravio da vidi group.a i group.b, nepopravljen bi obilazak brisanja i dalje tretirao group kao jedno terminalno polje, i indeks 0 bi uklonio roditelja zajedno sa svakim bratom-sestrom i svim njihovim widgetima. Obilazak brisanja sada koristi isti test HPDFLoadedFieldHasChildFields i isti skup posjećenih, skuplja widget anotacije samo uklonjenog djeteta, skine ih s /Annots svake stranice, a roditelja uklanja samo kad se njegovo /Kids polje isprazni. Regresija provjerava sva tri mjesta gdje bi se greška pokazala: roditeljeve /Kids, stranične /Annots te vrijednost i izgled preživjelog brata-sestre, i nakon potpunog prepisivanja i nakon inkrementalnog updatea
// Ukloni jedno imenovano dijete; njegov brat-sestra i zajednički roditelj prežive
Pdf.RemoveFormField('group.a');
Assert(Pdf.GetFormFieldCount = 1);
Assert(Pdf.FormFieldExists('group.b'));
// Tip, flagovi i opcije i dalje se razrješavaju kroz roditelja
Assert(Pdf.GetFormFieldType('group.b') = lfftChoice);
Pdf.SaveLoadedDocument('survey-trimmed.pdf');
Što ResetLoadedFormField zapiše kad je zadana vrijednost naslijeđena?
ResetLoadedFormField zapisuje lokalni /V koji je svježa kopija naslijeđenog /DV istog PDF tipa objekta, i validira cijelu zadanu vrijednost prije nego dira polje. Tip objekta važan je jer skalarski getteri sve izravnavaju u tekst. Zadana vrijednost checkboxa ime je poput /Yes, zadana vrijednost multi-select list boxa polje je stringova, a tekstualna zadana vrijednost može biti heksadecimalni UTF-16 string; kopiranje bilo kojeg kroz GetLoadedFormFieldDefaultValue pretvorilo bi ime u string, polje u prazan string, a hex string u njegove doslovne znamenke. Reset se zato grana po naslijeđenom tipu: text i choice polja dobivaju novi string objekt koji čuva flag IsHexadecimal, choice polja s poljnom zadanom vrijednošću dobivaju novo polje novih stringova, a nepushbutton gumbi novi name objekt. Kopiranje, umjesto upućivanja na roditeljeve objekte, namjerno je: /V koji bi dijelio roditeljevo /DV polje ili njegov broj objekta promijenio bi zadanu vrijednost sljedeći put kad itko uredi vrijednost. Zadana vrijednost krivog tipa, ili choice polje koje sadrži išta osim stringova, baca iznimku i ostavlja /V i /I točno kakvi su bili. Pushbuttoni, koji nemaju vrijednost (Tablica 226, bit 17), i signature polja vraćaju se na stariji put samo sa stringovima
Kad nigdje uz lanac ne postoji /DV, metoda zadržava svoj ugovor čišćenja zapisujući lokalni prazan string, ili /Off za checkbox ili radio polje. Brisanje lokalnog /V izgledalo bi urednije i bilo bi pogrešno: roditelj može držati trenutačnu vrijednost, i uklanjanje djetetove zamjene tiho bi tu vrijednost vratilo. Zato i reset pojedinačnog polja nije ResetForm akcija iz §12.7.5.3, koju preglednik izvodi nad skupom polja kad korisnik klikne gumb, kako je opisano u članku o izradi AcroForm polja i akcija pomoću HotPDF-a. ResetLoadedFormField je operacija uređivanja nad jednim učitanim poljem, sa svojim pravilom za slučaj bez zadane vrijednosti, i bilježi polje kroz NoteLoadedFormFieldDirty da inkrementalni preračun vidi promjenu
var
Field: THPDFLoadedFormField;
begin
Field := Pdf.GetFormField('group.a');
try
// Roditelj drži /DV [(b) (r)] na MultiSelect list boxu: group.a dobiva
// vlastito /V [(b) (r)] i svježi /I [0 2]; roditelj je netaknut
Pdf.ResetLoadedFormField(Field.Index);
// Skalarski getteri ne mogu prikazati poljnu zadanu vrijednost
Writeln(Pdf.GetLoadedFormFieldDefaultValue(Field.Index)); // prazno
finally
Field.Free;
end;
Pdf.SaveLoadedDocument('survey-reset.pdf');
end;
Da /V, /I i /AS ostanu usklađeni
Reset je ispravan samo ako indeks selekcije i stanje izgleda prate vrijednost, pa ResetLoadedFormField završava s ista dva usklađivača kao SetFormFieldValue. HPDFReconcileChoiceSelection sada prima poljnu vrijednost: briše lokalni /I bez mutiranja, svaku vrijednost poklopi s export polovicom svakog /Opt unosa, i zapiše jedno novo sortirano /I, pa reset na [(b) (r)] uz opcije b, g, r daje /I [0 2]. ReconcileLoadedButtonAppearanceStates sada traži naslijeđeni tip, pa dijete-checkbox čiji se /FT /Btn nalazi na roditelju napokon dobiva postavljen /AS. Na strani pisanja, SetFormFieldValue i SetLoadedFormFieldDefaultValue spremaju name objekt za naslijeđeni nepushbutton gumb čak i kad dijete nema lokalni unos s kojeg bi kopiralo tip. A kad EnsureLoadedFieldAppearanceStream ponovno gradi izglede gumba, zapisuje /AS /Off osim ako vrijednost odgovara uključenom stanju, i svakom state streamu daje ispravne /Type /XObject, /Subtype /Form i /BBox; prije v2.754.4, regeneracija izgleda nakon reseta mogla je ponovno staviti kvačicu prije nego se datoteka spremi
Ograničenja vrijedna poznavanja prije nego na tome gradite
Skalarski getteri ostaju skalarski. GetFormFieldValue i GetLoadedFormFieldDefaultValue vraćaju prazan string za poljnu vrijednost, brojeve i booleane pretvaraju u tekst kao 42 odnosno true, a hex-kodirani string javljaju u njegovu heksadecimalnom zapisu. /Parent ciklus završava obilazak bez iznimke, pa polje čiji se tip izgubi u ciklusu javlja lfftUnknown i flagove 0 umjesto da padne. SetFormFieldValue i ResetLoadedFormField uvijek pišu dijete koje adresirate i nikad ne promiču vrijednost zajedničkom roditelju, što je pravo za nezavisnu djecu, ali znači da radio grupe treba adresirati kroz polje koje posjeduje selekciju. I svaki poziv sam za sebe potvrđuje jedno polje; ništa ovdje ne čini seriju reseta transakcijom
Razrješavanje naslijeđenih atributa, ujedinjena klasifikacija field treea i tipizirani reset opisani ovdje dio su loaded-form API-ja u HotPDF Delphi Component za Delphi i C++Builder, uz izradu polja pokrivenu u članku o dodavanju AcroForm polja u učitani PDF u Delphiju