HotPDF Delphi Component käsittelee ladatun AcroForm-kentän /FT-, /Ff-, /V- ja /DV-merkinnät perittävinä attribuutteina, jotka ratkaistaan kävelemällä /Parent-ketjua. Versioista v2.754.3 ja v2.754.4 alkaen nimetty lapsi, jonka tyyppi tulee sen vanhemmalta, pysyy yksilöllisesti osoitettavana, RemoveFormField jättää sen sisarukset rauhaan, ja ResetLoadedFormField kopioi perityn oletuksen sen alkuperäisellä PDF-objektityypillä. Sitä ennen yllättävän moni tavallinen lomake luettiin väärin
Lomake, joka paljastaa kaiken tämän, ei ole eksoottinen. Kirjoitustyökalu rakentaa ryhmäsolmun group, joka kantaa kerran /FT /Ch -merkinnän, kenttäliput ja valintaluettelon, ja ripustaa sen alle kaksi nimettyä lasta a ja b, kumpikin yhdistetty kenttä- ja widget-sanakirja, jossa ei ole muuta kuin /T, /Parent, /Rect ja oma /V. Se on täysin laillinen tapa jakaa attribuutteja, ja se on täsmälleen se tapaus, jonka artikkelin lomakekenttien arvojen asettamisesta ladattuun PDF:ään Delphillä Rajat-osa merkitsi käsittelemättömäksi: painikkeiden sovitus katsoi vain paikallista /FT-merkintää. Tämä artikkeli poimii sen, mihin edellinen jäi, ja kattaa, miten kenttäpuu luokitellaan, miten perittyjä arvoja luetaan ja mitä yhden kentän nollaus saa kirjoittaa
Mitkä AcroForm-merkinnät kenttä voi periä vanhemmaltaan?
ISO 32000-1 §12.7.3.1, taulukko 220, merkitsee /FT-, /Ff-, /V- ja /DV-merkinnät perittäviksi, ja taulukko 229 kohdassa §12.7.4.3 tekee saman tekstikentän /MaxLenille, joten mikä tahansa lukija, joka katsoo vain paikallista sanakirjaa, raportoi täysin kelvolliselle lapselle väärän tyypin, väärät liput ja tyhjän arvon. HotPDF ohjaa kaikki nämä lukee yhden sisäisen ratkaisijan, HPDFLoadedInheritedFieldObjectin, läpi, joka tarkistaa sanakirjasta avaimen, ratkaisee epäsuoran viittauksen, jos sellaisen löytää, ja muussa tapauksessa seuraa /Parentia enintään 128 tasoa, koska vialliset tiedostot voivat rakentaa /Parent-silmukoita, joilla ei ole mitään tekemistä /Kidsin kanssa. Julkiset getterit istuvat sen päällä: GetFormFieldType, GetFormFieldValue, GetLoadedFormFieldFlags, IsFormFieldRequired, IsFormFieldNoExport, GetLoadedFormFieldMaxLength, GetLoadedFormFieldDefaultValue sekä optioapurit GetLoadedFormFieldOptionCount ja GetLoadedFormFieldOptions, jotka poimivat myös vanhemmalle tallennetun /Opt-taulukon. Yksi sääntö ratkaisijassa on helppo saada väärin: kävely pysähtyy ensimmäiseen avaimen sisältävään sanakirjaan, vaikka arvo siellä olisi tyhjä merkkijono. Paikallinen /V () on tahallinen ohitus, joka peittää vanhemman, ei aukko, joka pitäisi täyttää ylempää puusta
var
Pdf: THotPDF;
Field: THPDFLoadedFormField;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('survey.pdf') <= 0 then Exit;
// 'group' kantaa /FT /Ch -merkinnän, /Ff 131078:n ja /Opt-taulukon; lapsi
// 'group.b' kantaa vain /T:n, /Parentin, /Rectin ja oman /V-merkintänsä
Field := Pdf.GetFormField('group.b');
try
if Pdf.GetFormFieldType(Field.Index) = lfftChoice then
begin
// 131078 = Combo (bitti 18) + NoExport (bitti 3) + Required (bitti 2)
Writeln(Pdf.GetLoadedFormFieldFlags(Field.Index));
Writeln(Pdf.IsFormFieldRequired(Field.Index)); // TRUE
Writeln(Pdf.GetLoadedFormFieldOptionCount(Field.Index));
Writeln(Pdf.GetFormFieldValue(Field.Index)); // paikallinen /V
end;
finally
Field.Free;
end;
finally
Pdf.Free;
end;
end;
Miksi paikallinen /FT on väärä testi päätekentälle?
Koska vanhempi voi toimittaa tyypin ja silti omistaa nimettyjä lapsikenttiä, /FT-merkinnän läsnäolo ei kerro mitään siitä, missä kenttäpuu päättyy. Vanha läpikäynti julisti solmun pääteksi aina, kun sillä oli oma /FT tai ei /Kidsia. Yllä olevassa lomakkeessa groupilla on sekä /FT /Ch että /Kids, joten se rekisteröitiin yhdeksi group-nimiseksi kentäksi kahdella widgetillä, ja täysin kvalifioidut nimet group.a ja group.b katosivat yksinkertaisesti. GetFormFieldCount palautti 1:n, haku lapsen nimellä epäonnistui, ja SetFormFieldValue pystyi kirjoittamaan vain jaettua vanhempaa. Korvaava testi, HPDFLoadedFieldHasChildFields, katsoo lapsia vanhemman sijaan: lapsi on lapsikenttä, jos sillä on oma /T, oma /Kids, tai jos se ei ole ylipäätään /Subtype /Widget-sanakirja. Vain kun mikään lapsi ei täytä ehtoja, solmu on pääte, ja sen lapset käsitellään sen widget-merkintähuomautuksina
Kaksi reunatapausta, jotka muovasivat kyseisen säännön, tulevat molemmat yhdistetyistä sanakirjoista, joita §12.7.3.1 sallii, kun kentällä on yksi widget. Nimetty yhdistetty sanakirja kantaa /Subtype /Widgetia ja on silti lapsikenttä, joten alatyyppi yksin ei voi lähettää sitä vanhemman anonyymien widgetien luetteloon; /T voittaa. Myös käänteinen tapahtuu: jotkin tuottajat toistavat vanhemman /FTin jokaisessa anonyymissä widgetissä, joten /FTia ei voi käyttääkään todisteena siitä, että widget aloittaa uuden kentän. Luokittelu on jaettu suhdevälimuistin, FormFieldExistsin ja RemoveFormFieldin kesken, ja jokainen kyseinen kävely kirjaa nyt käymänsä sanakirjat ja pysähtyy 128 tason jälkeen. Regressiotiedosto, jonka ryhmä luettelee itsensä kahdesti, /Kids [5 0 R 5 0 R 6 0 R 7 0 R], raportoi yhä täsmälleen kaksi kenttää sen sijaan että rekursioisi ikuisesti tai laskisi saman solmun kahdesti
Miten RemoveFormField välttää sisarkenttien poistamisen?
RemoveFormField poistaa nyt vain nimeämäsi lapsen, koska löytäminen ja poistaminen ovat vihdoin samaa mieltä siitä, mitä päätekenttä on. Se yhteisymmärrys on tärkeämpi kuin näyttää. Nimen perusteella toimiva ylikuormitus ratkaisee indeksin suhdevälimuistin kautta ja laskee sitten päätekentät toisella kävelyllä /AcroForm /Fieldsin yli. Kun välimuisti oli korjattu näkemään group.an ja group.bn, korjaamaton poistokävely olisi silti käsitellyt groupin yhtenä päätekenttänä, ja indeksi 0 olisi poistanut vanhemman yhdessä jokaisen sisaruksen ja kaikkien niiden widgetien kanssa. Poistokävely käyttää nyt samaa HPDFLoadedFieldHasChildFields-testiä ja samaa käyty-joukkoa, kerää vain poistetun lapsen widget-merkintähuomautukset, irrottaa ne jokaisen sivun /Annotsista ja poistaa vanhemman vain, kun sen /Kids-taulukko päätyy tyhjäksi. Regressio tarkistaa kaikki kolme paikkaa, joissa virhe näkyisi: vanhemman /Kidsin, sivun /Annotsin ja selvinneen sisaruksen arvon ja esityksen, sekä täyden uudelleenkirjoituksen että inkrementaalisen päivityksen jälkeen
// Poistetaan yksi nimetty lapsi; sen sisarus ja jaettu vanhempi selviävät
Pdf.RemoveFormField('group.a');
Assert(Pdf.GetFormFieldCount = 1);
Assert(Pdf.FormFieldExists('group.b'));
// Tyyppi, liput ja optiot ratkaistaan yhä vanhemman kautta
Assert(Pdf.GetFormFieldType('group.b') = lfftChoice);
Pdf.SaveLoadedDocument('survey-trimmed.pdf');
Mitä ResetLoadedFormField kirjoittaa, kun oletus on peritty?
ResetLoadedFormField kirjoittaa paikallisen /Vin, joka on tuore kopio peritystä /DVistä samalla PDF-objektityypillä, ja se validoi koko oletuksen ennen kuin koskee kenttää. Objektityyppi on merkitsevä, koska skalaarigetterit litistävät kaiken tekstiksi. Valintaruudun oletus on nimi kuten /Yes, monivalintaluetteloruudun oletus on merkkijonojen taulukko, ja tekstin oletus voi olla heksadesimaalinen UTF-16-merkkijono; kumpaakin tahansa kopioimalla GetLoadedFormFieldDefaultValuein kautta nimi muuttuisi merkkijonoksi, taulukko tyhjäksi merkkijonoksi ja heksamerkkijono sen kirjaimellisiksi numeroiksi. Nollaus haarautuu siis perityn tyypin mukaan: teksti- ja valintakentät saavat uuden merkkijono-objektin, joka pitää IsHexadecimal-lipun, taulukko-oletuksiset valintakentät saavat uuden taulukon uusia merkkijonoja, ja muut kuin pushbutton-painikkeet saavat uuden nimiobjektin. Kopioiminen, ei osoittaminen vanhemman objekteihin, on tahallista: /V, joka jakoi vanhemman /DV-taulukon tai sen objektinumeron, muuttaisi oletusta seuraavan kerran, kun kuka tahansa muokkasi arvoa. Väärän tyypin oletus tai valintataulukko, joka sisältää mitään muuta kuin merkkijonoja, nostaa poikkeuksen ja jättää /Vin ja /Iin täsmälleen ennalleen. Pushbuttonit, joilla ei ole arvoa (taulukko 226, bitti 17), ja allekirjoituskentät palaavat vanhempaan vain-merkkijonopolkuun
Kun /DVia ei ole missään ketjua ylöspäin, metodi pitää tyhjentämislupauksensa kirjoittamalla paikallisen tyhjän merkkijonon tai /Offin valintaruutu- tai radiokentälle. Paikallisen /Vin poistaminen näyttäisi siistimmältä ja olisi väärin: vanhempi voi kantaa nykyistä arvoa, ja lapsen ohituksen poistaminen palauttaisi tuon arvon hiljaa. Tämä on myös syy, miksi yhden kentän nollaus ei ole §12.7.5.3:n ResetForm-toiminto, jonka katselin ajaa kenttäjoukon yli, kun käyttäjä napsauttaa painiketta, niin kuin kerrotaan artikkelissa AcroForm-kenttien ja -toimintojen rakentamisesta HotPDF:llä. ResetLoadedFormField on muokkausoperaatio yhdellä ladatulla kentällä, omalla säännöllään olettoman tapauksen varalta, ja se kirjaa kentän NoteLoadedFormFieldDirtyilla, jotta inkrementaalinen uudelleenlaskenta näkee muutoksen
var
Field: THPDFLoadedFormField;
begin
Field := Pdf.GetFormField('group.a');
try
// Vanhempi kantaa /DV [(b) (r)] -merkintää MultiSelect-luetteloruudussa: group.a
// saa oman /V [(b) (r)] -merkintänsä ja tuoreen /I [0 2]:n; vanhempi pysyy ehjänä
Pdf.ResetLoadedFormField(Field.Index);
// Skalaarigetterit eivät voi esittää taulukko-oletusta
Writeln(Pdf.GetLoadedFormFieldDefaultValue(Field.Index)); // tyhjä
finally
Field.Free;
end;
Pdf.SaveLoadedDocument('survey-reset.pdf');
end;
/V:n, /I:n ja /AS:n pitäminen yhdenmukaisina
Nollaus on oikein vain, jos valintaindeksi ja esitystila seuraavat arvoa, joten ResetLoadedFormField päättää samoihin kahteen sovittajaan kuin SetFormFieldValue. HPDFReconcileChoiceSelection hyväksyy nyt taulukkoarvon: se poistaa paikallisen /I-merkinnän mutatoimatta sitä, täsmää jokaisen arvon jokaisen /Opt-merkinnän vientipuoliskoon ja kirjoittaa yhden uuden lajitellun /I-taulukon, joten nollaus arvoon [(b) (r)] vaihtoehtoja b, g, r vasten tuottaa /I [0 2] -taulukon. ReconcileLoadedButtonAppearanceStates kysyy nyt perittyä tyyppiä, joten lapsen valintaruutu, jonka /FT /Btn asuu vanhemmalla, saa vihdoin /AS-arvonsa asetettua. Kirjoituspuolella SetFormFieldValue ja SetLoadedFormFieldDefaultValue tallentavat nimiobjektin peritylle ei-pushbutton-painikkeelle silloinkin, kun lapsella ei ole paikallista merkintää tyypin kopioimiseksi. Ja kun EnsureLoadedFieldAppearanceStream rakentaa painikkeiden esitykset uudelleen, se kirjoittaa /AS /Off-arvon, ellei arvo täsmää päällä-tilaan, ja antaa jokaiselle tilastreamille oikean /Type /XObject-, /Subtype /Form- ja /BBox-merkinnän; ennen v2.754.4:ää esityksen uudelleenluonti nollauksen jälkeen saattoi rastittaa ruudun uudelleen ennen tiedoston tallennusta
Rajat, jotka kannattaa tietää ennen kuin rakennat tämän päälle
Skalaarigetterit pysyvät skalaareina. GetFormFieldValue ja GetLoadedFormFieldDefaultValue palauttavat tyhjän merkkijonon taulukkoarvolle, merkkijonoistuttavat luvut ja totuusarvot muotoon 42 tai true ja raportoivat heksakoodatun merkkijonon sen heksadesimaalisena kirjoitusasuna. /Parent-silmukka päättää kävelyn ilman poikkeusta, joten kenttä, jonka tyyppi katoaa silmukkaan, raportoi lfftUnknownin ja lipuiksi 0 sen sijaan että epäonnistuisi. SetFormFieldValue ja ResetLoadedFormField kirjoittavat aina lapsen, jonka osoitat, eivätkä koskaan ylennä arvoa jaettuun vanhempaan, mikä on oikein itsenäisille lapsille mutta tarkoittaa, että radioryhmät kannattaa osoittaa sen kentän kautta, joka omistaa valinnan. Ja jokainen kutsu käsittelee yhden kentän itsenäisesti; mikään tässä ei tee nollauserästä transaktiota
Tässä kuvattu perittyjen attribuuttien ratkaisu, yhtenäistetty kenttäpuun luokittelu ja tyypitetty nollaus kuuluvat ladatun lomakkeen API:in HotPDF Delphi Componentissa Delphille ja C++Builderille, yhdessä artikkelissa AcroForm-kenttien lisäämisestä ladattuun PDF:ään Delphissä käsitellyn kenttien luomisen kanssa