Tehnični članak

Podedovane vrednosti in ponastavitve v AcroForm v Delphiju

HotPDF Delphi Component obravnava /FT, /Ff, /V in /DV na naloženem polju AcroForm kot podedovane lastnosti, ki se razrešijo s sprehodom po verigi /Parent. Od v2.754.3 in v2.754.4 poimenovani otrok, katerega tip pride od starša, ostane posamično naslovljiv, RemoveFormField pusti njegove sorojence pri miru, ResetLoadedFormField pa prekopira podedovano privzeto vrednost z njenim izvirnim tipom objekta PDF. Pred tem jih je presenetljivo veliko navadnih obrazcev bilo prebranih narobe

Obrazec, ki vse to obne, ni nič eksotičnega. Orodje za avtorstvo zgradi vozlišče skupine group, ki enkrat nosi /FT /Ch, zastavice polja in seznam možnosti, pod njega pa obesi dva poimenovana otroka a in b, vsakega kot zlit slovar polja in gradnika, ki nima ničesar razen /T, /Parent, /Rect in svojega /V. To je povsem zakonit način delitve lastnosti in točno ta primer je odsek Omejitve v nastavljanju vrednosti polj obrazca v naloženem PDF z Delphijem označil kot neobdelan: usklajevanje gumbov je gledalo le na krajevni /FT. Ta članek nadaljuje tam, kjer se je ta ustavil, in pokriva, kako se drevo polj klasificira, kako se berejo podedovane vrednosti in čemu je ponastavitev enega polja dovoljeno pisati

Kateri vnosi AcroForm se lahko podedujejo od starša polja?

ISO 32000-1 §12.7.3.1, tabela 220, označi /FT, /Ff, /V in /DV kot podedljive, tabela 229 v §12.7.4.3 pa enako za /MaxLen besedilnega polja, zato vsak bralnik, ki pogleda samo krajevni slovar, poroča napačen tip, napačne zastavice in prazno vrednost za povsem veljavnega otroka. HotPDF vse te dognane spusti skozi enega notranjega razreševalca, HPDFLoadedInheritedFieldObject, ki v slovarju preveri ključ, razreši posredno referenco, če jo najde, sicer pa sledi /Parent do največ 128 ravni, ker lahko deformirane datoteke zgradijo cikle /Parent, ki nimajo nič skupnega s /Kids. Javne pridobivalne funkcije sedijo na njem: GetFormFieldType, GetFormFieldValue, GetLoadedFormFieldFlags, IsFormFieldRequired, IsFormFieldNoExport, GetLoadedFormFieldMaxLength, GetLoadedFormFieldDefaultValue ter pomožnika možnosti GetLoadedFormFieldOptionCount in GetLoadedFormFieldOptions, ki zaobjemita tudi polje /Opt, shranjeno na staršu. Ena pravilo v razreševalcu se zlahka povozi: sprehod se ustavi pri prvem slovarju, ki vsebuje ključ, tudi če je vrednost tam prazen niz. Krajevni /V () je namerna preglasitev, ki prikrije starša, in ne vrzel za zapolniti od višje po drevesu

Diagram podedovanih lastnosti AcroForm v HotPDF: vozlišče skupine enkrat nosi /FT, /Ff in /Opt, medtem ko poimenovana otroka group.a in group.b držita le /T, /Parent, /Rect in krajevni /V, prikazano pa je HPDFLoadedInheritedFieldObject, ki sprehodi /Parent do 128 ravni, kjer zmaga prvi slovar s ključem, prazen krajevni vrednost pa prikrije starša
HotPDF razreši /FT, /Ff, /V, /DV in /Opt skozi enega razreševalca, ki sprehodi po starših, tako da poimenovani otrok ostane naslovljiv, krajevna prazna vrednost pa namerno preglaši vse, kar nosi skupina nad njim
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 in /Opt; otrok
    // 'group.b' nosi le /T, /Parent, /Rect in svoj /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));       // krajevni /V
      end;
    finally
      Field.Free;
    end;
  finally
    Pdf.Free;
  end;
end;

Zakaj je krajevni /FT napačen preizkus za končno polje?

Ker lahko starš dovaja tip in hkrati še vedno lasti poimenovana podrejena polja, navzočnost /FT ne pove nič o tem, kje se drevo polj konča. Stari prehod je vozlišče razglasil za končno, kadarkoli je imelo svoj /FT ali ni imelo /Kids. V zgornjem obrazcu ima group oboje, /FT /Ch in /Kids, zato je bil registriran kot eno polje z imenom group z dvema gradnikoma, polno kvalificirani imeni group.a in group.b pa sta preprosto izginili. GetFormFieldCount je vrnil 1, iskanje po imenu otroka je odpovedalo, SetFormFieldValue pa je mogel pisati le na skupnega starša. Nadomestni preizkus, HPDFLoadedFieldHasChildFields, pogleda otroke namesto starša: otrok je podrejeno polje, če ima svoj /T, ima svoje /Kids ali sploh ni slovar /Subtype /Widget. Šele ko noben otrok ne ustreza, je vozlišče končno, njegovi otroci pa se obravnavajo kot njegove gradniške pripombe

Dva mejna primera, ki sta oblikovala to pravilo, oba prihajata iz zlitih slovarjev, ki jih §12.7.3.1 dovoljuje, ko ima polje en sam gradnik. Poimenovani zlit slovar nosi /Subtype /Widget in je kljub temu podrejeno polje, zato podtip sam ne more poslati na staršev seznam anonimnih gradnikov; zmaga /T. Obratno se prav tako zgodi: nekateri proizvajalci ponovijo staršev /FT na vsakem anonimnem gradniku, zato /FT ne more služiti kot dokaz, da gradnik začne novo polje. Klasifikacijo si delijo predpomnilnik razmerij, FormFieldExists in RemoveFormField, vsak od teh sprehodov pa si zdaj zabeleži slovarje, ki jih je že obiskal, in se ustavi čez 128 ravni. Regresijska datoteka, katere skupina našteje sama sebe dvakrat, /Kids [5 0 R 5 0 R 6 0 R 7 0 R], še vedno poroča točno dve polji, namesto da bi se ponavljala v nedogled ali štela isto vozlišče dvakrat

Kako RemoveFormField prepreči brisanje sorojenih polj?

RemoveFormField zdaj briše le otroka, ki ga poimenujete, ker se odkrivanje in brisanje končno strinjata o tem, kaj je končno polje. To strinjanje je pomembnejše, kot izgleda. Pretirjava po imenu razreši kazalo skozi predpomnilnik razmerij in nato prešteje končna polja v drugem sprehodu po /AcroForm /Fields. Ko je bil predpomnilnik popravljen, da vidi group.a in group.b, bi nepopravljen briselj sprehod še vedno obravnaval group kot eno končno polje, kazalo 0 pa bi odstranilo starša skupaj z vsakim sorojencem in vsemi njihovimi gradniki. Briselj sprehod zdaj uporablja isti preizkus HPDFLoadedFieldHasChildFields in isto obiskano množico, pobere gradniške pripombe le odstranjenega otroka, jih odstrani iz /Annots vsake strani, starša pa odstrani šele, ko se njegovo polje /Kids izprazni. Regresija preveri vsa tri mesta, kjer bi se napaka pokazala: staršev /Kids, strani /Annots ter vrednost in videz preživelega sorojenca, tako po polnem prepisu kot po priraščajoči posodobitvi

Diagram preživetja sorojencev pri HotPDF RemoveFormField: briselj sprehod ponovno uporabi HPDFLoadedFieldHasChildFields in obiskano množico odkrivanja, odstrani le poimenovanega otroka group.a iz AcroForm /Fields in strani /Annots ter obdrži skupnega starša, dokler njegovo polje /Kids še nosi preživelega group.b
Odkrivanje in brisanje se končno strinjata o tem, kaj je končno polje, zato odstranitev enega poimenovanega otroka pusti vrednost in videz njegovega sorojenca nedotaknjena po polnem prepisu ali priraščajoči posodobitvi
// Odstranitev enega poimenovanega otroka; sorojenec in skupni starš preživita
Pdf.RemoveFormField('group.a');

Assert(Pdf.GetFormFieldCount = 1);
Assert(Pdf.FormFieldExists('group.b'));
// Tip, zastavice in možnosti se še vedno razrešijo skozi starša
Assert(Pdf.GetFormFieldType('group.b') = lfftChoice);
Pdf.SaveLoadedDocument('survey-trimmed.pdf');

Kaj zapiše ResetLoadedFormField, ko je privzeta vrednost podedovana?

ResetLoadedFormField zapiše krajevni /V, ki je sveža kopija podedovanega /DV z istim tipom objekta PDF, in preveri celotno privzeto vrednost, preden se dotakne polja. Tip objekta je pomemben, ker skalarne pridobivalne funkcije vse splaščajo v besedilo. Privzeta vrednost potrditvenega polja je ime, kot je /Yes, privzeta vrednost večizbirnega seznama je polje nizov, besedilna privzeta vrednost pa je lahko šestnajstiški niz UTF-16; kopiranje katerega koli od njih skozi GetLoadedFormFieldDefaultValue bi spremenilo ime v niz, polje v prazen niz in šestnajstiški niz v njegove dobesedne števke. Ponastavitev se zato razveji po podedovanem tipu: besedilna in izbirna polja dobijo nov objekt niza, ki ohrani zastavico IsHexadecimal, izbirna polja s privzeto vrednostjo polja dobijo novo polje novih nizov, gumbi, ki niso potisni, pa nov objekt imena. Kopiranje, namesto da bi kazalo na starševe objekte, je namerno: /V, ki bi si delil starševsko polje /DV ali njegovo številko objekta, bi ob naslednjem urejanju vrednosti spremenil privzeto vrednost. Privzeta vrednost napačnega tipa ali izbirno polje, ki vsebuje kaj drugega kot nize, sproži izjemo in pusti /V ter /I točno takšna, kot sta bila. Potisni gumbi, ki nimajo vrednosti (tabela 226, bit 17), in polja podpisa padejo nazaj na starejšo pot samo z nizi

Diagram tipizirane ponastavitve HotPDF: ResetLoadedFormField se razveji po podedovanem tipu objekta /DV, zapiše svež objekt imena za potrditveno polje, novo polje novih nizov za večizbirno izbiro, niz, ki ohrani IsHexadecimal, za šestnajstiško besedilo, prazen niz ali /Off, ko /DV ne obstaja, in sproži izjemo, brez da bi se dotaknil /V ali /I, pri neujemanju tipa
Kopiranje namesto kazanja na starševe objekte prepreči, da bi poznejše urejanje vrednosti tiho spremenilo privzeto vrednost, potisni gumbi in polja podpisa pa padejo nazaj na starejšo pot samo z nizi

Kjer /DV ni nikjer po verigi, metoda obdrži svojo pogodbo čiščenja z zapisom krajevnega praznega niza ali /Off za potrditveno ali radijsko polje. Brisanje krajevnega /V bi izgledalo urejeneje in bilo narobe: starš lahko nosi trenutno vrednost, odstranitev otrokove preglasitve pa bi to vrednost tiho vrnila. Prav zato ponastavitev enega polja ni dejanje ResetForm iz §12.7.5.3, ki ga pregledovalnik požene čez množico polj, ko uporabnik klikne gumb, kot je opisano v gradnji polj in dejanj AcroForm s HotPDF. ResetLoadedFormField je operacija urejanja na enem naloženem polju, s svojim pravilom za primer brez privzete vrednosti, in polje zabeleži prek NoteLoadedFormFieldDirty, tako da priraščajoči izračun vidi spremembo

var
  Field: THPDFLoadedFormField;
begin
  Field := Pdf.GetFormField('group.a');
  try
    // Starš nosi /DV [(b) (r)] na večizbirnem seznamu: group.a dobi
    // svoj /V [(b) (r)] in svež /I [0 2]; starš je nedotaknjen
    Pdf.ResetLoadedFormField(Field.Index);
    // Skalarne pridobivalne funkcije ne morejo prikazati privzete vrednosti polja
    Writeln(Pdf.GetLoadedFormFieldDefaultValue(Field.Index)); // prazno
  finally
    Field.Free;
  end;
  Pdf.SaveLoadedDocument('survey-reset.pdf');
end;

Vzdrževanje strinjanja med /V, /I in /AS

Ponastavitev je pravilna le, če kazalo izbire in stanje videza sledita vrednosti, zato se ResetLoadedFormField zaključi z istima dvema usklajevalcema kot SetFormFieldValue. HPDFReconcileChoiceSelection zdaj sprejme vrednost polja: izbriše krajevni /I, brez da bi ga spreminjal, vsako vrednost primerja z izvozno polovico vsakega vnosa /Opt in zapiše eno novo urejeno /I, tako da ponastavitev na [(b) (r)] proti možnostim b, g, r da /I [0 2]. ReconcileLoadedButtonAppearanceStates zdaj povpraša po podedovanem tipu, zato otroško potrditveno polje, katerega /FT /Btn živi na staršu, končno dobi nastavljen svoj /AS. Na strani pisanja SetFormFieldValue in SetLoadedFormFieldDefaultValue shranita objekt imena za podedovani gumb, ki ni potisni, tudi kadar otrok nima krajevnega vnosa, iz katerega bi kopiral tip. Ko EnsureLoadedFieldAppearanceStream znova zgradi videze gumbov, zapiše /AS /Off, razen če se vrednost ujema z vklopnim stanjem, vsakemu toku stanja pa da pravi /Type /XObject, /Subtype /Form in /BBox; pred v2.754.4 je lahko ponovno ustvarjanje videza po ponastavitvi spet odkljukalo polje, preden je bila datoteka shranjena

Omejitve, vredne vedenja, preden zgradite na tem

Skalarne pridobivalne funkcije ostanejo skalarne. GetFormFieldValue in GetLoadedFormFieldDefaultValue vrnejo prazen niz za vrednost polja, številke in logične vrednosti znakovno predstavita kot 42 ali true, šestnajstiško kodiran niz pa poročata v njegovi šestnajstiški črkovanji. Cikel /Parent konča sprehod brez izjeme, zato polje, katerega tip se izgubi v ciklu, poroča lfftUnknown in zastavice 0, namesto da bi odpovedalo. SetFormFieldValue in ResetLoadedFormField vselej zapišeta otroka, ki ga naslovita, in nikoli ne povzdigneta vrednosti na skupnega starša, kar je prav za neodvisne otroke, pomeni pa, da naj bodo radijske skupine naslovljene skozi polje, ki lasti izbiro. Vsak klic pa zagosti eno polje zase; nič tu ne naredi serije ponastavitev transakcijske

Razreševanje podedovanih lastnosti, poenotena klasifikacija drevesa polj in tipizirana ponastavitev, opisani tukaj, so del API naloženih obrazcev v HotPDF Delphi Component za Delphi in C++Builder, skupaj z ustvarjanjem polj, pokritim v dodajanju polj AcroForm naloženemu PDF v Delphiju