Žymimieji langeliai ir radijo mygtukai suplokštinami kaip nepažymėti, nes išvaizdos būsena /AS niekada nebuvo sinchronizuota su lauko reikšme /V. PDFium Component, PDFium pagrindu sukurtas VCL ir LCL komponentas Delphi, C++Builder ir Lazarus aplinkoms, dabar tą reikšmę skaito su FPDFAnnot_GetFormFieldValue, kuris išsprendžia tėvinį lauko žodyną, o ne valdiklio anotaciją
Klaidos pranešimas, atvedęs čia, yra toks, kuriuo iš pradžių nepasitikite. Klientas suplokština pasirašytą sutikimo formą, atidaro rezultatą, ir kiekvienas žymimasis langelis tuščias. Atidarykite šaltinio failą Acrobat, ir langeliai matomai pažymėti. Perskaitykite šaltinio failą atgal per tą patį komponentą, ir lauko reikšmės teisingos. Tik suplokštinta išvestis jas praranda, ir tik žymimiesiems langeliams bei radijo mygtukams: teksto laukai tame pačiame puslapyje pasirodo teisingai
Kodėl žymimieji langeliai nepažymėti po suplokštinimo?
Todėl, kad suplokštinimas niekada nežiūri į /V. FPDFPage_Flatten įkepa valdiklio išvaizdos srautą į puslapio turinį, o išvaizdą, kurią jis pasirenka, yra ta, kurią pavadina /AS. Jei /AS vis dar sako /Off, kol lauko reikšmė sako, kad langelis pažymėtas, suplokštinimas ištikimai įkepa išjungtą išvaizdą. Reikšmė niekada nebuvo prarasta; ji tiesiog niekada nebuvo konsultuojama
ISO 32000-1 §12.5.5 apibrėžia išvaizdos žodyną /AP su trimis galimais įrašais, /N, /R ir /D. Žymimajam langeliui ar radijo mygtukui /N įrašas nėra srautas, o požodynas, kurio raktai yra išvaizdos būsenų vardai, o §12.5.2 padaro /AS privalomu selektoriumi, kai /N yra požodynas. Taigi žymimasis langelis neša dvi iš anksto sukurtas išvaizdas ir vieną rodyklę. Suklyskite su rodykle, ir atvaizdavimas neteisingas taip, kad jokia teisinga /V reikšmė to nepataisys. Tai taip pat kodėl nesėkmės režimas skiriasi nuo teksto laukų, kurie iš viso neturi iš anksto sukurtos išvaizdos, kurią pasirinkti: teksto lauko /N yra vienas srautas, kurį reikia perkurti nuo nulio, pakeitus reikšmę, todėl GenerateFormAppearances tvarko šiuos du atvejus per visiškai atskirus kodo kelius, ir tik mygtukų kelias buvo sugadintas
Kur iš tikrųjų gyvena žymimojo langelio reikšmė?
Lauko žodyne, ne valdiklyje. ISO 32000-1 §12.7.5.2 aprašo žymimuosius langelius ir radijo mygtukus kaip mygtukų laukus, kurių /V yra vardo objektas, įvardijantis dabartinę išvaizdos būseną, o §12.7.3.1 patalpina /V tarp įrašų, bendrų visiems lauko žodynams. Valdiklio anotacija, apibrėžta §12.5.6.19, prideda /AS ir /AP. Niekas specifikacijoje neįpareigoja valdiklio nešti /V
// Wrong: reads the widget annotation dictionary directly
buflen := FPDFAnnot_GetStringValue(Annot, 'V', nil, 0);
// For most real forms buflen comes back as 2 (an empty UTF-16 string),
// so /AS is never written and the box flattens as Off
{ What the two objects look like when the field has several widgets:
12 0 obj % field dictionary (the parent)
<< /FT /Btn /T (Consent) /V /On
/Kids [ 13 0 R 14 0 R ] >>
endobj
13 0 obj % widget annotation (a kid)
<< /Type /Annot /Subtype /Widget /Parent 12 0 R
/AS /Off
/AP << /N << /On 20 0 R /Off 21 0 R >> >> >>
endobj }
FPDFAnnot_GetStringValue nėra defektuota. Jos sutartis yra lygiai tokia, kokia sako jos vardas: gauti eilutės įrašą iš anotacijos žodyno, kurį jai perdavėte. Klausimas apie /V objekte 13 grąžina nieko, nes objektas 13 iš tiesų neturi /V. Defektas buvo iškvietėjyje, kuris darė prielaidą apie plokščią objektų modelį, kurio ISO 32000-1 niekada nežadėjo
Kada laukas ir valdiklis dalijasi vienu žodynu?
Kada tik laukas turi lygiai vieną valdiklį. §12.5.6.19 leidžia sujungti lauko žodyną ir jo vienintelę valdiklio anotaciją į vieną objektą, ir dauguma kūrimo įrankių renkasi šį trumpąjį kelią. Sujungtame objekte /FT, /T, /V, /AS ir /AP visi sėdi šalia vienas kito, todėl valdiklio lygio /V skaitymas pavyksta, ir visa klaida lieka nematoma
Kai tik laukas turi du ar daugiau valdiklių, sujungimas tampa neįmanomas, ir §12.7.3.1 reikalauja, kad valdikliai taptų atskiro lauko žodyno /Kids. Kiekviena radijo grupė yra šios formos pagal konstrukciją. Taip pat ir sutikimo žymimieji langeliai, pakartoti antraštėje ir poraštėje, ir bet koks laukas, kurį kūrimo įrankis nukopijavo į antrą puslapį. Tai visas paaiškinimas, kodėl defektas išgyveno regresinį rinkinį: testų korpusas buvo pilnas vieno-valdiklio formų, o kliento failai ne. Jei patys vaikštote valdikliais, o ne pasikliaujate komponentu, ta pati asimetrija pasirodo išvardijimo tvarkoje, ir pastabos apie PDF formos lauko navigaciją su PDFium Component apima, kaip puslapio lygio anotacijos vaikščiojimas siejasi su dokumento lygio lauko medžiu
Reikšmės skaitymas taip, kaip numato PDFium
FPDFAnnot_GetFormFieldValue yra teisingas API, ir jis buvo susietas komponente jau kurį laiką, jo nenaudojant žymimojo langelio kelyje. Jis ima formos rankeną kartu su anotacija, kas yra ženklas, kuris svarbus: turint prieinamą formos-pildymo aplinką, PDFium išsprendžia anotaciją į jos formos valdiklį ir skaito reikšmę iš lauko objekto, todėl grąžina teisingą atsakymą tiek sujungtiems, tiek atskirtiems išdėstymams
FPDF_FORMFIELD_CHECKBOX, FPDF_FORMFIELD_RADIOBUTTON:
begin
// /AP is prebuilt per state; only /AS has to be synchronised with /V.
// FPDFAnnot_GetFormFieldValue resolves the parent field dictionary,
// which is where ISO 32000-1 12.7.5.2 keeps the value.
buflen := FPDFAnnot_GetFormFieldValue(FFormHandle, Annot, nil, 0);
if buflen >= 4 then
begin
SetLength(OrigVal, buflen div 2 - 1);
FPDFAnnot_GetFormFieldValue(FFormHandle, Annot, PWideChar(OrigVal), buflen);
FPDFAnnot_SetStringValue(Annot, 'AS', Pointer(OrigVal));
end;
end;
Dvi to fragmento smulkmenos lengva suklysti. Grąžintas ilgis yra baitų skaičius UTF-16 tekstui, įskaitant terminatorių, todėl simbolių skaičius yra buflen div 2 - 1, o reikšmė 2 reiškia tuščią eilutę. Apsauga buflen >= 4 todėl reiškia bent vieną tikrą simbolį, kas ir neleidžia lauko be jokio /V turėti savo /AS perrašytą tuščiu vardu
Dėl ko iš tikrųjų susitaria /AS ir /AP /N?
Jie susitaria dėl vardo, o vardą pasirenka tas, kas sukūrė failą. §12.7.5.2 reikalauja, kad išjungta būsena vadintųsi /Off, ir palieka įjungtą būseną visiškai gamintojui. /Yes yra konvencija, ne taisyklė. Acrobat rašo /Yes, tačiau daugybė generatorių rašo /On, /1, /Choice1 ar lokalizuotą žodį, o radijo grupė paprastai suteikia kiekvienam vaikui atskirą įjungtos-būsenos vardą, kad grupė galėtų išreikšti, kuris mygtukas pasirinktas. Būtent dėl to /V kopijavimas pažodžiui į /AS yra teisinga operacija, ne triukas: pažymėtam valdikliui PDFium praneša įjungtos-būsenos vardą, kurį pats failas apibrėžia, o nepažymėtam praneša Off, todėl reikšmė, kurią rašote į /AS, garantuotai yra raktas, egzistuojantis tame valdiklio /AP /N požodyne. /Yes kietas įkodavimas veiktų su Acrobat išvestimi ir tyliai sugestų visur kitur
Operacijų tvarka ir kur vis dar reikia atsargumo
Seka fiksuota ir negailestinga: įjunkite formos pildymą, priskirkite reikšmes, atkurkite išvaizdas, suplokštinkite, tada išsaugokite. Praleiskite atkūrimo žingsnį, ir FPDFPage_Flatten randa tuščius ar pasenusius išvaizdos srautus ir juos įkepa be jokio skundo, kas yra tylus duomenų praradimas, o ne klaidos grąžinimas
Pdf.FileName := FormPath;
Pdf.FormFill := True; // required: FormHandle must exist
Pdf.Active := True;
Pdf.FormField[0] := 'On'; // writes /V only
Pdf.GenerateFormAppearances; // syncs /AS for buttons, rebuilds /AP for text
if Pdf.FlattenAllPages(FLAT_PRINT) then
Pdf.SaveAs('consent-flat.pdf');
Dvi sąžiningos ribos lieka. Pirma, sinchronizavimas rašo lauko reikšmę į kiekvieno to lauko valdiklio /AS, kas teisinga žymimiesiems langeliams ir apytiksliai radijo grupėms, kurių kiekvienas vaikas apibrėžia savo įjungtos-būsenos vardą; vaikas, kurio /AP /N neturi įrašo, atitinkančio parašytą /AS, neturi jokios išvaizdos, kurią pasirinkti pagal §12.5.5, todėl nepasirinktas mygtukas gali suplokštėti į nieką vietoj tuščio apskritimo. Radijo grupės audito atlikimas su FPDFAnnot_GetFormControlIndex prieš suplokštinant yra vertas kelių eilučių. Antra, nė vienas iš to netaikomas XFA, kur reikšmė gyvena XML duomenų pakete, o ne AcroForm žodynuose, atskyrimas, aprašytas pastabose apie XFA lauko redagavimus, kurie neišsaugomi. Bendra pamoka verta išlaikyti ir po šio pataisymo: kada tik API ima formos rankeną kartu su anotacija, jis sako jums, kad išspręs lauko hierarchiją už jus, ir kada tik jis ima tik anotaciją, jis skaitys lygiai tą objektą, kurį perdavėte. Tas skirtumas taip pat valdo duomenų mainus, nes XFDF formos duomenų eksportavimas ir importavimas veikia pilnai kvalifikuotais lauko vardais, niekada valdiklio pozicijomis
Formos suplokštinimas yra viena iš tų funkcijų, kuri atrodo kaip vienas API iškvietimas ir pasirodo esanti sutartis tarp trijų žodynų. Jei norėtumėte dirbti su komponentu, jau užkoduojančiu tą sutartį, PDFium Component Delphi ir C++Builder pristato čia aprašytą išvaizdos atkūrimą, suplokštinimą ir formos lauko prieigą kaip įprastas savybes ir metodus