HotPDF Delphi Component /FT, /Ff, /V ir /DV įkeltame AcroForm lauke traktuoja kaip paveldimus atributus, išsprendžiamus apeinant /Parent grandinę. Nuo v2.754.3 ir v2.754.4 pavadintas vaikas, kurio tipas ateina iš tėvo, lieka atskirai pasiekiamas, RemoveFormField palieka jo brolius ramybėje, o ResetLoadedFormField nukopijuoja paveldėtą numatytąją reikšmę su originaliu PDF objekto tipu. Iki tol stebėtinai daug paprastų formų buvo skaitomos neteisingai
Forma, atverianti visa tai, nėra egzotiška. Autorinis įrankis sukonstruoja grupės mazgą group, kuris vienąkart neša /FT /Ch, lauko vėliavėles ir parinkčių sąrašą, ir po juo pakabina du pavadintus vaikus a ir b, kiekvieną – sulietą lauko ir valdiklio žodyną, neturintį nieko, tik /T, /Parent, /Rect ir savą /V. Tai visiškai teisėtas būdas dalintis atributais, ir būtent tą atvejį formos laukų reikšmių nustatymas įkeltame PDF su Delphi straipsnio Limits skyrius pažymėjo kaip neapdorojamą: mygtukų suderinimas žiūrėjo tik į vietinį /FT. Šis straipsnis pasiima ten, kur tas sustojo, ir aprėpia, kaip klasifikuojamas laukų medis, kaip skaitomos paveldėtos reikšmės ir ką vieno lauko atstatymui leidžiama įrašyti
Kuriuos AcroForm įrašus laukas gali paveldėti iš tėvo?
ISO 32000-1 §12.7.3.1, 220 lentelė, pažymi /FT, /Ff, /V ir /DV kaip paveldimus, o 229 lentelė §12.7.4.3 tą patį daro tekstinio lauko /MaxLen atžvilgiu, tad bet kuris skaitytuvas, žiūrintis tik į vietinį žodyną, visiškai teisėtam vaikui praneš neteisingą tipą, neteisingas vėliavėles ir tuščią reikšmę. HotPDF visus šiuos skaitymus supila pro vieną vidinį išsprendėją HPDFLoadedInheritedFieldObject, kuris žodyne patikrina raktą, išsprendžia netiesioginę nuorodą, jei ją randa, ir kitu atveju seka /Parent ne daugiau kaip 128 lygius, nes netinkamai suformuoti failai gali sukurti /Parent ciklus, su /Kids neturinčius nieko bendra. Viešieji getteriai sėdi ant jo viršaus: GetFormFieldType, GetFormFieldValue, GetLoadedFormFieldFlags, IsFormFieldRequired, IsFormFieldNoExport, GetLoadedFormFieldMaxLength, GetLoadedFormFieldDefaultValue ir parinkčių pagalbinės GetLoadedFormFieldOptionCount bei GetLoadedFormFieldOptions, kurios taip pat pasiima tėve saugomą /Opt masyvą. Viena išsprendėjo taisyklė lengvai suklaidina: apeiga sustoja ties pirmuoju žodynu, turinčiu raktą, net jei ten esanti reikšmė yra tuščia eilutė. Vietinis /V () yra sąmoningas perrašymas, uždengiantis tėvą, o ne tuštuma, kurią reikia užpildyti iš aukštesnių medžio pakopų
var
Pdf: THotPDF;
Field: THPDFLoadedFormField;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('survey.pdf') <= 0 then Exit;
// „group“ neša /FT /Ch, /Ff 131078 ir /Opt; vaikas
// „group.b“ laiko tik /T, /Parent, /Rect ir savą /V
Field := Pdf.GetFormField('group.b');
try
if Pdf.GetFormFieldType(Field.Index) = lfftChoice then
begin
// 131078 = Combo (bitas 18) + NoExport (bitas 3) + Required (bitas 2)
Writeln(Pdf.GetLoadedFormFieldFlags(Field.Index));
Writeln(Pdf.IsFormFieldRequired(Field.Index)); // TRUE
Writeln(Pdf.GetLoadedFormFieldOptionCount(Field.Index));
Writeln(Pdf.GetFormFieldValue(Field.Index)); // vietinis /V
end;
finally
Field.Free;
end;
finally
Pdf.Free;
end;
end;
Kodėl vietinis /FT yra netinkamas terminalinio lauko testas?
Nes tėvas gali duoti tipą ir vis tiek turėti pavadintus vaiko laukus, tad /FT buvimas nesako nieko apie tai, kur baigiasi laukų medis. Senoji apeiga mazgą skelbdavo terminaliniu, kai tik jis turėdavo savą /FT arba neturėdavo /Kids. Aukščiau esančioje formoje group turi ir /FT /Ch, ir /Kids, tad jis buvo užregistruojamas kaip vienas laukas group su dviem valdikliais, o pilnieji vardai group.a ir group.b tiesiog dingdavo. GetFormFieldCount grąžindavo 1, paieška pagal vaiko vardą žlugdavo, o SetFormFieldValue galėdavo rašyti tik į bendrąjį tėvą. Pakaitinis testas HPDFLoadedFieldHasChildFields žiūri į vaikus, o ne į tėvą: vaikas yra vaiko laukas, jei turi savą /T, turi savą /Kids arba apskritai nėra /Subtype /Widget žodynas. Tik kai nė vienas vaikas neatitinka, mazgas terminalinis, o jo vaikai traktuojami kaip jo valdiklio anotacijos
Abu kraštiniai atvejai, suformavę tą taisyklę, ateina iš sulietų žodynų, kuriuos §12.7.3.1 leidžia, kai laukas turi vieną valdiklį. Pavadintas sulietas žodynas neša /Subtype /Widget ir vis tiek yra vaiko laukas, tad vien subtype negali jo nusiųsti į tėvo anonominių valdiklių sąrašą; laimi /T. Atvirkščiai irgi nutinka: kai kurie generatoriai tėvo /FT pakartoja ant kiekvieno anonominio valdiklio, tad /FT negali būti įrodymu, kad valdiklis pradeda naują lauką. Klasifikaciją dalijasi santykių podėlis, FormFieldExists ir RemoveFormField, ir kiekviena tų apeigų dabar užsirašo jau aplankytus žodynus ir sustoja praėjusi 128 lygius. Regresinis failas, kurio grupė save išvardija dukart, /Kids [5 0 R 5 0 R 6 0 R 7 0 R], vis tiek praneša lygiai du laukus, vietoj to, kad rekursuotų amžinai arba skaičiuotų tą patį mazgą dukart
Kaip RemoveFormField išvengia brolio laukų ištrynimo?
RemoveFormField dabar ištrina tik tą vaiką, kurį pavadinate, nes atradimas ir trynimas galiausiai sutaria, kas yra terminalinis laukas. Ta sutartis svarbiau, nei atrodo. Overloadas pagal vardą išsprendžia indeksą pro santykių podėlį ir tada antra apeiga per /AcroForm /Fields suskaičiuoja terminalinius laukus. Kai podėlis buvo pataisytas matyti group.a ir group.b, nepataisyta trynimo apeiga vis tiek būtų group laikiusi vienu terminaliniu lauku, ir indeksas 0 būtų pašalinęs tėvą kartu su kiekvienu broliu ir visais jų valdikliais. Trynimo apeiga dabar naudoja tą patį HPDFLoadedFieldHasChildFields testą ir tą patį aplankytų rinkinį, surenka tik pašalinto vaiko valdiklio anotacijas, jas išplėšia iš kiekvieno puslapio /Annots, o tėvą šalina tik tada, kai jo /Kids masyvas lieka tuščias. Regresija tikrina visas tris vietas, kur klaida išryškėtų: tėvo /Kids, puslapio /Annots ir išgyvenusio brolio reikšmę bei išvaizdą – ir po pilno perrašymo, ir po inkrementinio atnaujinimo
// Pašalinkite vieną pavadintą vaiką; jo brolis ir bendrasis tėvas išgyvena
Pdf.RemoveFormField('group.a');
Assert(Pdf.GetFormFieldCount = 1);
Assert(Pdf.FormFieldExists('group.b'));
// Tipas, vėliavėlės ir parinktys vis dar išsprendžiamos pro tėvą
Assert(Pdf.GetFormFieldType('group.b') = lfftChoice);
Pdf.SaveLoadedDocument('survey-trimmed.pdf');
Ką ResetLoadedFormField įrašo, kai numatytoji reikšmė paveldėta?
ResetLoadedFormField įrašo vietinę /V, kuri yra šviežia paveldėtos /DV kopija su tuo pačiu PDF objekto tipu, ir patvirtina visą numatytąją reikšmę, dar neliesdamas lauko. Objekto tipas svarbus, nes skaliariniai getteriai viską suploja į tekstą. Žymimojo langelio numatytoji reikšmė yra vardas, toks kaip /Yes, kelių pasirinkimų sąrašo dėžutės numatytoji reikšmė yra eilučių masyvas, o tekstinė numatytoji reikšmė gali būti šešioliktainė UTF-16 eilutė; bet kurios jų kopijavimas pro GetLoadedFormFieldDefaultValue pavestų vardą eilute, masyvą – tuščia eilute, o šešioliktainę eilutę – jos pačios skaitmenimis. Todėl atstatymas šakojasi pagal paveldėtą tipą: tekstiniai ir choice laukai gauna naują eilutės objektą, išlaikantį IsHexadecimal vėliavėlę, choice laukai su masyvine numatytąja reikšme gauna naują naujų eilučių masyvą, o nepushbutton mygtukai gauna naują vardo objektą. Kopijavimas, o ne rodyklė į tėvo objektus, sąmoningas: /V, dalijanti tėvo /DV masyvą arba jo objekto numerį, pakeistų numatytąją reikšmę kitąkart, kas nors redaguojant reikšmę. Netinkamo tipo numatytoji reikšmė arba choice masyvas, kuriame yra kas nors kitą nei eilutės, kelia išimtį ir palieka /V bei /I lygiai tokias, kokios buvo. Pushbutton, kurie neturi reikšmės (226 lentelė, 17 bitas), ir parašo laukai grįžta į senąjį kelią, skirtą tik eilutėms
Kai /DV niekur grandinėje nėra, metodas išlaiko savo išvalymo sutartį įrašydamas vietinę tuščią eilutę arba /Off žymimajam langeliui arba radijo laukui. Vietinės /V ištrynimas atrodytų tvarkingiau ir būtų neteisingas: tėvas gali laikyti dabartinę reikšmę, ir vaiko perrašymo pašalinimas tyliai sugrąžintų tą reikšmę. Todėl vieno lauko atstatymas nėra §12.7.5.3 ResetForm veiksmas, kurį peržiūros programa paleidžia per laukų rinkinį, vartotojui paspaudus mygtuką, kaip aprašyta AcroForm laukų ir veiksmų kūrime su HotPDF. ResetLoadedFormField yra redagavimo operacija ant vieno įkelto lauko, su savo taisykle be-numatytosios-reikšmės atvejui, ir jis užregistruoja lauką per NoteLoadedFormFieldDirty, kad inkrementinis perskaičiavimas pamatytų pokytį
var
Field: THPDFLoadedFormField;
begin
Field := Pdf.GetFormField('group.a');
try
// Tėvas laiko /DV [(b) (r)] ant MultiSelect sąrašo dėžutės: group.a gauna
// savą /V [(b) (r)] ir šviežią /I [0 2]; tėvas neliečiamas
Pdf.ResetLoadedFormField(Field.Index);
// Skaliariniai getteriai negali atvaizduoti masyvinės numatytosios
Writeln(Pdf.GetLoadedFormFieldDefaultValue(Field.Index)); // tuščia
finally
Field.Free;
end;
Pdf.SaveLoadedDocument('survey-reset.pdf');
end;
Kaip /V, /I ir /AS laikomi suderinti?
Atstatymas teisingas tik tada, kai pasirinkimo indeksas ir išvaizdos būsena seka reikšmę, todėl ResetLoadedFormField baigiasi tais pačiais dviem suderinintojais kaip SetFormFieldValue. HPDFReconcileChoiceSelection dabar priima masyvinę reikšmę: jis ištrina vietinę /I jos nekeisdamas, kiekvieną reikšmę sugretina su kiekvieno /Opt įrašo eksportine puse ir rašo vieną naują surūšiuotą /I, tad atstatymas į [(b) (r)] prieš parinktis b, g, r duoda /I [0 2]. ReconcileLoadedButtonAppearanceStates dabar prašo paveldėto tipo, tad vaiko žymimasis langelis, kurio /FT /Btn gyvena tėve, galiausiai gauna savą /AS. Rašymo pusėje SetFormFieldValue ir SetLoadedFormFieldDefaultValue saugo vardo objektą paveldėtam nepushbutton mygtukui net tada, kai vaikas neturi vietinio įrašo, iš kurio nukopijuoti tipą. O kai EnsureLoadedFieldAppearanceStream perstatinėja mygtukų išvaizdas, jis rašo /AS /Off, nebent reikšmė sutampa su įjungta būsena, ir kiekvienam būsenos srautui duoda tvarkingą /Type /XObject, /Subtype /Form ir /BBox; iki v2.754.4 išvaizdos atkūrimas po atstatymo galėjo vėl pažymėti langelį dar prieš failą įrašant
Ribos, vertos žinoti, prieš statant ant to
Skaliariniai getteriai lieka skaliariniai. GetFormFieldValue ir GetLoadedFormFieldDefaultValue masyvinei reikšmei grąžina tuščią eilutę, skaičius ir boolean suveda į 42 ar true, o šešioliktaine koduotą eilutę praneša jos šešioliktaine rašyba. /Parent ciklas baigia apeigą be išimties, tad laukas, kurio tipas pamestas cikle, praneša lfftUnknown ir nulines vėliavėles, o ne žlunga. SetFormFieldValue ir ResetLoadedFormField visada rašo tą vaiką, kurį adresuojate, ir niekada nepakelia reikšmės į bendrąjį tėvą – tai teisinga nepriklausomiems vaikams, bet reiškia, kad radio grupes reikia adresuoti pro lauką, valdantį pasirinkimą. Ir kiekvienas iškvietimas patvirtina po vieną lauką savarankiškai; niekas čia neatveria atstatymų paketo kaip transakcijos
Čia aprašytas paveldimų atributų išsprendimas, vieninga laukų medžio klasifikacija ir tipizuotas atstatymas yra įkeltų formų API dalis HotPDF Delphi Component Delphi ir C++Builder aplinkose, greta AcroForm laukų pridėjimo į įkeltą PDF Delphi aprašyto laukų kūrimo