Potrditvena polja in izbirni gumbi se sploščijo kot nepotrjeni, ker stanje videza /AS nikoli ni bilo usklajeno z vrednostjo polja /V. PDFium Component, komponenta VCL in LCL, zgrajena na PDFium, za Delphi, C++Builder in Lazarus, zdaj to vrednost bere z FPDFAnnot_GetFormFieldValue, ki razreši nadrejeni slovar polja namesto opombe widgeta
Poročilo o hrošču, ki je pripeljalo sem, je vrsta, ki ji na začetku ne zaupate. Stranka splošči podpisan obrazec soglasja, odpre rezultat, vsako potrditveno polje pa je prazno. Odprite izvorno datoteko v Acrobatu in polja so vidno kljukana. Preberite izvorno datoteko nazaj skozi isto komponento in vrednosti polj so pravilne. Le sploščen izhod jih izgubi, in to le za potrditvena polja in izbirne gumbe: besedilna polja na isti strani izpadejo v redu
Zakaj so potrditvena polja po sploščenju nepotrjena?
Ker sploščenje nikoli ne pogleda v /V. FPDFPage_Flatten zapeče tok videza widgeta v vsebino strani, izbrani videz pa je tisti, poimenovan z /AS. Če /AS še vedno pravi /Off, medtem ko vrednost polja pravi, da je polje vklopljeno, sploščenje zvesto zapeče izklopljen videz. Vrednost nikoli ni bila izgubljena; nikoli ni bila posvetovana
ISO 32000-1 §12.5.5 definira slovar videza /AP s tremi mogočimi vnosi, /N, /R in /D. Za potrditveno polje ali izbirni gumb vnos /N ni tok, temveč podslovar, katerega ključi so imena stanj videza, §12.5.2 pa /AS naredi obvezen selektor, kadar je /N podslovar. Potrditveno polje torej nosi dva vnaprej zgrajena videza in en kazalec. Napačno postavite kazalec in upodabljanje je napačno na način, ki ga nobena količina pravilnega /V ne bo popravila. To je tudi razlog, zakaj se način odpovedi razlikuje od besedilnih polj, ki sploh nimajo vnaprej zgrajenega videza za izbiro: /N besedilnega polja je en sam tok, ki ga je treba po spremembi vrednosti regenerirati iz nič, tako da GenerateFormAppearances oba primera obravnava skozi popolnoma ločeni poti kode, pokvarjena pa je bila le pot gumbov
Kje dejansko živi vrednost potrditvenega polja?
Na slovarju polja, ne na widgetu. ISO 32000-1 §12.7.5.2 opisuje potrditvena polja in izbirne gumbe kot polja gumbov, čigar /V je objekt imena, ki poimenuje trenutno stanje videza, §12.7.3.1 pa /V postavi med vnose, skupne vsem slovarjem polj. Opomba widgeta, definirana v §12.5.6.19, prispeva /AS in /AP. Nič v specifikaciji widgeta ne zavezuje, da nosi /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 ni pomanjkljiv. Njegova pogodba je natanko to, kar pove njegovo ime: pridobi vnos niza iz slovarja opombe, ki ste mu ga izročili. Vprašanje po /V na objektu 13 ne vrne ničesar, ker objekt 13 resnično nima /V. Napaka je bila pri klicatelju, ki je predpostavljal raven objektni model, ki ga ISO 32000-1 nikoli ni obljubil
Kdaj si polje in widget delita en slovar?
Kadarkoli ima polje natanko en widget. §12.5.6.19 dovoli, da se slovar polja in njegova ena opomba widgeta zlijeta v en objekt, večina orodij za avtorstvo pa to bližnjico izkoristi. V zlitem objektu /FT, /T, /V, /AS in /AP vsi sedijo drug ob drugem, tako da branje /V na ravni widgeta uspe, cel hrošč pa ostane neviden
V trenutku, ko polje poseduje dva ali več widgetov, zlitje ni mogoče, §12.7.3.1 pa zahteva, da widgeti postanejo /Kids ločenega slovarja polja. Vsaka skupina izbirnih gumbov je po konstrukciji v tej obliki. Enako so potrditvena polja soglasja, ponovljena v glavi in nogi, in vsako polje, ki ga je orodje za avtorstvo skopiralo na drugo stran. To je celotna razlaga, zakaj je napaka preživela regresijski nabor: testni korpus je bil poln obrazcev z enim widgetom, datoteke strank pa niso bile. Če widgete prehajate sami namesto da bi se zanašali na komponento, se ista asimetrija pokaže v vrstnem redu naštevanja, opombe o navigaciji polj obrazca PDF s PDFium Component pa pokrivajo, kako je prehod opomb na ravni strani povezan z drevesom polj na ravni dokumenta
Branje vrednosti na način, kot ga PDFium namerava
FPDFAnnot_GetFormFieldValue je pravilen API, v komponenti pa je bil vezan že nekaj časa, ne da bi ga pot potrditvenega polja uporabljala. Sprejme ročnik obrazca poleg opombe, kar je signal, ki je pomemben: z na voljo okoljem izpolnjevanja obrazca PDFium razreši opombo na njen kontrolnik obrazca in prebere vrednost iz objekta polja, tako da vrne pravilen odgovor tako za zlite kot razdeljene postavitve
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;
Dve podrobnosti v tem odlomku je lahko narediti napačno. Vrnjena dolžina je bajtno štetje za besedilo UTF-16, vključno s terminatorjem, tako da je število znakov buflen div 2 - 1, vrednost 2 pa pomeni prazen niz. Varovalo buflen >= 4 torej pomeni vsaj en pravi znak, kar je tisto, kar polju brez /V sploh prepreči, da bi mu bil /AS prepisan s praznim imenom
V čem se /AS in /AP /N resnično strinjata
Strinjata se v imenu, ime pa izbere kdorkoli je izdelal datoteko. §12.7.5.2 zahteva, da se izklopljeno stanje imenuje /Off, vklopljeno stanje pa v celoti prepusti proizvajalcu. /Yes je konvencija, ne pravilo. Acrobat piše /Yes, vendar mnogo generatorjev piše /On, /1, /Choice1 ali lokalizirano besedo, skupina izbirnih gumbov pa vsakemu potomcu ponavadi da razločno ime vklopljenega stanja, tako da lahko skupina izrazi, kateri gumb je izbran. To je natanko razlog, zakaj je kopiranje /V dobesedno v /AS pravilna operacija, ne pa trik: za potrjen kontrolnik PDFium poroča ime vklopljenega stanja, ki ga sama datoteka definira, za nepotrjenega pa poroča Off, tako da je vrednost, ki jo zapišete v /AS, zagotovljeno ključ, ki obstaja v tistem podslovarju widgeta /AP /N. Trdo kodiranje /Yes bi delovalo na izhodu Acrobata in tiho odpovedalo povsod drugje
Vrstni red operacij in kje je še vedno potrebna previdnost
Zaporedje je fiksno in neizprosno: omogočite izpolnjevanje obrazca, dodelite vrednosti, regenerirajte videze, sploščite, nato shranite. Preskočite korak regeneracije in FPDFPage_Flatten najde prazne ali zastarele tokove videza ter jih zapeče brez pritožbe, kar je tiha izguba podatkov namesto vrnjene napake
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');
Ostajata dve pošteni meji. Prvič, uskladitev zapiše vrednost polja v /AS vsakega widgeta tega polja, kar je pravilno za potrditvena polja, a približno za skupine izbirnih gumbov, katerih potomci vsak definirajo svoje lastno ime vklopljenega stanja; potomec, čigar /AP /N nima vnosa, ki se ujema z zapisanim /AS, nima videza za izbiro pod §12.5.5, tako da se neizbran gumb lahko splošči v nič namesto v prazen krog. Revizija skupine izbirnih gumbov z FPDFAnnot_GetFormControlIndex pred sploščenjem je vredna nekaj vrstic. Drugič, nič od tega ne velja za XFA, kjer vrednost živi v paketu podatkov XML namesto v slovarjih AcroForm, ločitev pa je obravnavana v opombah o urejanjih polj XFA, ki niso obstojna. Splošna lekcija je vredna ohraniti tudi onkraj tega popravka: kadarkoli API poleg opombe sprejme tudi ročnik obrazca, vam pove, da bo razrešil hierarhijo polj namesto vas, kadarkoli pa sprejme le opombo, bo prebral natanko objekt, ki ste mu ga podali. Ta razlika upravlja tudi izmenjavo podatkov, ker izvoz in uvoz podatkov obrazca XFDF deluje v popolnoma kvalificiranih imenih polj, nikoli v položajih widgetov
Sploščenje obrazcev je ena od tistih funkcij, ki je videti kot en sam klic API-ja, izkaže pa se za pogodbo med tremi slovarji. Če bi raje delali proti komponenti, ki to pogodbo že kodira, PDFium Component za Delphi in C++Builder izda tukaj opisano regeneracijo videza, sploščenje in dostop do polj obrazca kot navadne lastnosti in metode