Checkboxevi i radio gumbi spljošte se kao neoznačeni jer /AS nikad nije bio sinkroniziran s vrijednošću polja /V. PDFium Component, VCL i LCL komponenta temeljena na PDFium-u za Delphi, C++Builder i Lazarus, sada čita tu vrijednost pomoću FPDFAnnot_GetFormFieldValue, koja razrješava rječnik roditeljskog polja umjesto widget napomene
Prijava buga koja je dovela ovdje je vrsta koju u prvi mah ne vjerujete. Klijent spljošti potpisani obrazac suglasnosti, otvori rezultat, i svaki checkbox je prazan. Otvorite izvornu datoteku u Acrobatu i kućice su vidljivo označene. Pročitajte izvornu datoteku natrag kroz istu komponentu i vrijednosti polja su ispravne. Samo spljošteni izlaz ih gubi, i samo za checkboxeve i radio gumbe: tekstualna polja na istoj stranici ispadnu ispravno
Zašto su checkboxevi neoznačeni nakon spljoštenja?
Zato što spljoštenje nikad ne pogleda /V. FPDFPage_Flatten peče tok izgleda widgeta u sadržaj stranice, a izgled koji bira je onaj imenovan s /AS. Ako /AS i dalje kaže /Off dok vrijednost polja kaže da je kućica uključena, spljoštenje vjerno peče isključeni izgled. Vrijednost nikad nije bila izgubljena; nikad nije bila konzultirana
ISO 32000-1 §12.5.5 definira rječnik izgleda /AP s tri moguća unosa, /N, /R i /D. Za checkbox ili radio gumb unos /N nije tok, nego podrječnik čiji su ključevi imena stanja izgleda, a §12.5.2 čini /AS obaveznim selektorom kad je /N podrječnik. Dakle checkbox nosi dva unaprijed izgrađena izgleda i jedan pokazivač. Pogriješite pokazivač i renderiranje je pogrešno na način koji nikakva ispravna /V vrijednost neće popraviti. Ovo je i razlog zašto se način neuspjeha razlikuje od tekstualnih polja, koja nemaju unaprijed izgrađen izgled za odabrati uopće: tekstualno polje /N je jedan tok koji se mora regenerirati od nule nakon promjene vrijednosti, pa GenerateFormAppearances obrađuje ta dva slučaja kroz potpuno odvojene putove kôda, i samo je put gumba bio pokvaren
Gdje zapravo živi vrijednost checkboxa?
Na rječniku polja, ne na widgetu. ISO 32000-1 §12.7.5.2 opisuje checkboxeve i radio gumbe kao polja gumba čiji je /V objekt imena koji imenuje trenutno stanje izgleda, a §12.7.3.1 stavlja /V među unose zajedničke svim rječnicima polja. Widget napomena definirana u §12.5.6.19 pridonosi /AS i /AP. Ništa u specifikaciji ne obavezuje widget 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 nije neispravan. Njegov ugovor je točno ono što mu ime kaže: dohvati string unos iz rječnika napomene koji ste mu predali. Traženje /V na objektu 13 vraća ništa jer objekt 13 doista nema /V. Kvar je bio u pozivatelju, koji je pretpostavio ravan model objekta koji ISO 32000-1 nikad nije obećao
Kad polje i widget dijele jedan rječnik?
Kad god polje ima točno jedan widget. §12.5.6.19 dopušta da se rječnik polja i njegova jedina widget napomena spoje u jedan objekt, i većina alata za izradu koristi tu prečicu. U spojenom objektu /FT, /T, /V, /AS i /AP svi sjede jedan pored drugog, pa čitanje /V na razini widgeta uspijeva, i cijeli bug ostaje nevidljiv
U trenutku kad polje posjeduje dva ili više widgeta, spajanje je nemoguće, i §12.7.3.1 zahtijeva da widgeti postanu /Kids zasebnog rječnika polja. Svaka radio grupa je u ovom obliku po konstrukciji. Isto tako su i checkboxevi suglasnosti ponovljeni u zaglavlju i podnožju, i bilo koje polje koje je alat za izradu kopirao na drugu stranicu. To je cijelo objašnjenje zašto je kvar preživio regresijski paket: testni korpus bio je pun obrazaca s jednim widgetom, a klijentove datoteke nisu bile. Ako sami hodate widgetima umjesto da se oslanjate na komponentu, ista asimetrija pojavljuje se u redoslijedu nabrajanja, a bilješke o navigaciji poljima PDF obrazaca s PDFium Component pokrivaju kako se šetnja napomenama na razini stranice odnosi na stablo polja na razini dokumenta
Čitanje vrijednosti na način koji PDFium namjerava
FPDFAnnot_GetFormFieldValue je ispravan API, i bio je povezan u komponenti neko vrijeme prije nego što ga je put checkboxa počeo koristiti. Uzima ručku obrasca uz napomenu, što je signal koji je bitan: s dostupnim okruženjem popunjavanja obrazaca, PDFium razrješava napomenu u njezinu kontrolu obrasca i čita vrijednost s objekta polja, pa vraća ispravan odgovor i za spojene i za razdvojene rasporede
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;
Dvije pojedinosti u tom isječku lako je pogrešno shvatiti. Vraćena duljina je broj bajtova za UTF-16 tekst uključujući terminator, pa je broj znakova buflen div 2 - 1, a vrijednost 2 znači prazan niz. Zaštita buflen >= 4 stoga znači barem jedan pravi znak, što je ono što sprječava da polje bez ikakvog /V dobije prepisan svoj /AS praznim imenom
Oko čega se /AS i /AP /N zapravo slažu?
Slažu se oko imena, a ime bira tko god je proizveo datoteku. §12.7.5.2 zahtijeva da se isključeno stanje zove /Off, a uključeno stanje potpuno prepušta proizvođaču. /Yes je konvencija, ne pravilo. Acrobat piše /Yes, ali mnogi generatori pišu /On, /1, /Choice1, ili lokaliziranu riječ, a radio grupa obično daje svakom djetetu drukčije ime uključenog stanja tako da grupa može izraziti koji je gumb odabran. To je upravo razlog zašto je kopiranje /V doslovno u /AS ispravna operacija, a ne trik: za označenu kontrolu PDFium prijavljuje ime uključenog stanja koje sama datoteka definira, a za neoznačenu prijavljuje Off, pa je vrijednost koju upisujete u /AS zajamčeno ključ koji postoji u podrječniku /AP /N tog widgeta. Tvrdo kodiranje /Yes radilo bi na Acrobatovom izlazu, a tiho bi se pokvarilo svugdje drugdje
Redoslijed operacija, i gdje je i dalje potreban oprez
Redoslijed je fiksan i neopraštajući: omogući popunjavanje obrazaca, dodijeli vrijednosti, regeneriraj izglede, spljošti, zatim spremi. Preskočite korak regeneracije i FPDFPage_Flatten pronađe prazne ili zastarjele tokove izgleda i peče ih bez prigovora, što je tihi gubitak podataka, a ne povratna pogreška
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');
Dva iskrena ograničenja ostaju. Prvo, sinkronizacija upisuje vrijednost polja u /AS svakog widgeta tog polja, što je ispravno za checkboxeve, a približno za radio grupe čije djeca svako definira vlastito ime uključenog stanja; dijete čiji /AP /N nema unos koji se podudara s upisanim /AS nema izgled za odabrati prema §12.5.5, pa neoznačeni gumb može spljoštiti u ništa umjesto u prazan krug. Revizija radio grupe s FPDFAnnot_GetFormControlIndex prije spljoštenja vrijedi tih nekoliko redaka. Drugo, ništa od ovoga se ne primjenjuje na XFA, gdje vrijednost živi u XML paketu podataka umjesto u AcroForm rječnicima, razdvajanje pokriveno u bilješkama o izmjenama XFA polja koje se ne trajno pohranjuju. Opća pouka vrijedi zadržati izvan ovog popravka: kad god API uzima ručku obrasca uz napomenu, govori vam da će razriješiti hijerarhiju polja umjesto vas, a kad god uzima samo napomenu, čitat će točno objekt koji ste proslijedili. Ta razlika također upravlja razmjenom podataka, budući da izvoz i uvoz XFDF podataka obrazaca radi u potpuno kvalificiranim imenima polja, nikad u pozicijama widgeta
Spljoštenje obrazaca jedna je od onih značajki koja izgleda kao jedan API poziv, a ispadne ugovor između tri rječnika. Ako biste radije radili s komponentom koja već kodira taj ugovor, PDFium Component za Delphi i C++Builder isporučuje regeneraciju izgleda, spljoštenje i pristup poljima obrazaca opisan ovdje kao obična svojstva i metode