V PDFium Component, komponenti VCL/LCL, zgrajeni na PDFium, za Delphi, C++Builder in Lazarus, indeks polja obrazca ni indeks opombe. Stran nosi opombe Link, Text in Ink zraven svojih widgetov, tako da mora naštevanje polj filtrirati po FPDFAnnot_GetSubtype in izpostaviti logičen indeks, oštevilčen od nič, preslikan nazaj na resničen položaj opombe le pri naravnem klicu
Hrošč, ki to razkrije, je nezamenljiv, ko ga enkrat vidite. Testiralec pritisne Tab v izpolnjenem obrazcu računa in kazalka izgine, ker je fokus šel na hiperpovezavo v nogi. Ali še huje, nič se ne zgodi: vaša koda zabeleži polje 3 kot fokusirano, plošča uporabniškega vmesnika se posodobi, FORM_SetFocusedAnnot pa je ves čas tiho vračal false. Oba simptoma izhajata iz iste napake zasnove, en od njiju pa ima drugi vzrok, skrit spodaj
Dva prostora indeksov, ki vam ju da PDFium
PDFium izpostavi dve shemi oštevilčenja nad isto stranjo, ujemata pa se le na dokumentih, ki slučajno ne vsebujejo ničesar razen widgetov obrazca. Prvi je indeks opombe: položaj v polju strani /Annots, kar šteje FPDFPage_GetAnnotCount in kar sprejme FPDFPage_GetAnnot (ISO 32000-1 §12.5.2). Drugi je logičen indeks polja, ki naj bi ga ponudil API na ravni aplikacije, in teče od nič čez interaktivna polja, ki jih lahko uporabnik dejansko doseže. ISO 32000-1 §12.5.6.19 opombe widgetov definira kot vizualno predstavitev interaktivnih polj obrazca, §12.7 pa definira sam obrazec. Vse drugo na strani je drugačen podtip z drugačno semantiko: opomba Link ima cilj, opomba Ink ima seznam potez, opomba Text je lepljiv listek. Nobena od njih ne sodi v štetje polj, nobena od njih pa ne more sprejeti fokusa obrazca. Kljub temu v polju /Annots sedijo prepleteni z widgeti v vrstnem redu, v katerem jih je zapisala izdelujoča aplikacija, kar pogosto ni vrstni red, ki bi ga nakazovalo karkoli drugega o dokumentu
Zakaj Tab pristane na hiperpovezavi namesto na naslednjem polju?
Ker je štetje polj v resnici bilo štetje opomb. Izvirna implementacija je vrnila FPDFPage_GetAnnotCount neposredno iz FormFieldCount, medtem ko so dostopnik informacij polja, pomočnik vrstnega reda tabulacije in pomočnik fokusa vsi to isto celo število obravnavali kot položaj widgeta. Na čisti strani AcroForm s šestimi widgeti in ničemer drugim šest je enako šest in vsak test prestane. Dodajte hiperpovezavo v nogi in komentar pregledovalca na robu, štetje pa poroča osem polj, indeksa 6 in 7 se razrešita v objekte, ki niso obrazci, Tab pa gre naravnost vanju
Popravek na strani naštevanja je šteti podtipe namesto opomb. Odprite vsako opombo, vprašajte za njen podtip, obdržite widgete in zaprite ročnik v bloku finally, ker FPDFPage_GetAnnot vrne lasten ročnik, ki se mora vrniti skozi FPDFPage_CloseAnnot
function WidgetCountForPage(Page: FPDF_PAGE): Integer;
var
Count, I: Integer;
Annot: FPDF_ANNOTATION;
begin
Result := 0;
if Page = nil then
Exit;
Count := FPDFPage_GetAnnotCount(Page); // every annotation, not just fields
for I := 0 to Count - 1 do
begin
Annot := FPDFPage_GetAnnot(Page, I);
if Annot = nil then
Continue;
try
if FPDFAnnot_GetSubtype(Annot) = FPDF_ANNOT_WIDGET then
Inc(Result);
finally
FPDFPage_CloseAnnot(Annot);
end;
end;
end;
Opazite, česa to namerno ne stori. Ničesar ne vpraša okolja izpolnjevanja obrazca in ne potrebuje ročnika obrazca, ker podtip živi v slovarju opombe in je berljiv že iz same strani. To je pomembno za vrstni red: štetje je na voljo, preden ste sploh odločili, ali si dokument zasluži okolje izpolnjevanja obrazca, kar članek o AcroForm JavaScript in gostiteljskih dogodkih obravnava kot varnostno odločitev, ne pa udobnostno
Preslikava logičnega indeksa nazaj na naravni meji
Pravilo, ki prostora drži narazen, je preprosto: logični indeks je edino število, ki prečka vaš javni API, v indeks opombe pa se pretvori v zadnji funkciji pred naravnim klicem. En pomočnik preslikave, ki ga uporabljajo informacije polja, fokus, nastavljalci zastavic in vrstni red tabulacije, je tisto, kar to pravilo naredi uveljivo
function AnnotationIndexForField(Page: FPDF_PAGE;
FieldIndex: Integer): Integer;
var
Count, I, Current: Integer;
Annot: FPDF_ANNOTATION;
begin
Result := -1;
if (Page = nil) or (FieldIndex < 0) then
Exit;
Count := FPDFPage_GetAnnotCount(Page);
Current := 0;
for I := 0 to Count - 1 do
begin
Annot := FPDFPage_GetAnnot(Page, I);
if Annot = nil then
Continue;
try
if FPDFAnnot_GetSubtype(Annot) = FPDF_ANNOT_WIDGET then
begin
if Current = FieldIndex then
Exit(I); // real /Annots position: native calls only
Inc(Current);
end;
finally
FPDFPage_CloseAnnot(Annot);
end;
end;
end;
Vredno je jasno navesti dve lastnosti tega pomočnika. Je linearno skeniranje, tako da naivna zanka čez vsako polje na strani s stotinami widgetov stane kvadratno število odpiranj opomb; če naštevate celo stran, prehodite opombe enkrat in med potjo zberite ročnike widgetov namesto klicanja preslikovalnika za vsako polje. In vrne -1 namesto da bi vrgel izjemo, kar klicatelju prepusti odločitev, ali je zastarel indeks programerska napaka, vredna izjeme, ali tekma, vredna ignoriranja, na primer po urejanju, ki je odstranilo opombo, na katero se predpomnjen seznam uporabniškega vmesnika še vedno sklicuje
Zakaj FORM_SetFocusedAnnot odpove na strani brez glave?
Ker PDFium zavrne fokusiranje widgeta, čigar pogled strani nikoli ni bil označen kot veljaven. FORM_SetFocusedAnnot opombo razreši na pogled strani znotraj okolja izpolnjevanja obrazca, če pa ta pogled strani ne obstaja, vrne false brez kakršne koli diagnostike. Sam popravek preslikave indeksa torej popravi Tab, ki pristane na hiperpovezavi, vendar pusti drugi simptom nedotaknjen: vaš logičen zapis fokusa pravi polje 3, naravno fokusiran widget pa je še vedno nič, vsak dostopnik, zgrajen na naravnem fokusu, fokusiranem besedilu, fokusirani vrednosti, stanju izbire izbire, pa še naprej vrača prazno. Pogled strani ustvari FORM_OnAfterLoadPage in uniči FORM_OnBeforeClosePage. V pregledovalniku, zgrajenem okoli vizualnega kontrolnika, se ta klica zgodita kot del prikazovanja strani, zato je odpoved tako pogosto videti kot hrošč, ki obstaja le brez glave: ista koda, ki deluje v demonstraciji GUI, odpove v paketnem orodju. Življenjski cikel pripada objektu dokumenta, ne pregledovalniku, tako da PDFium Component zdaj izda oba klica, kadarkoli je stran naložena ali razložena, medtem ko je prisoten ročnik obrazca. Podpis C sprejme najprej stran, nato ročnik obrazca, kar je enostavno obrniti pri ročnem pisanju vezave
procedure ReportFirstField(const FileName: string);
var
Pdf: TPdf;
Idx: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FormFill := True; // form-fill environment, before Active
Pdf.FileName := FileName;
Pdf.Active := True;
Pdf.PageNumber := 1; // page load also runs FORM_OnAfterLoadPage
Idx := Pdf.FocusNextFormField; // logical index, 0-based over widgets
if Idx < 0 then
Exit; // page holds no widget annotations
Writeln(string(Pdf.FormFieldInfo[Idx].Name), ' = ',
string(Pdf.FocusedFormFieldValue)); // reads the native focused widget
finally
Pdf.Free; // page unload runs FORM_OnBeforeClosePage
end;
end;
Preverjanje, ki dokaže popravek, je tisto, ki primerja obe strani. Pokličite FocusFormField z logičnim indeksom, nato pa preberite vrednost prek dostopnika, ki gre skozi naravno fokusiran widget namesto skozi vaš lasten zapis, kot je FocusedFormFieldValue ali FocusedFormOptionSelected. Če se logični indeks vrne pravilno, naravni dostopnik pa vrne prazno, pogled strani manjka, ne pa preslikava
Kaj logičen indeks polja ne obljublja
Indeks polja, oštevilčen od nič, je udobje, ne pa semantična identiteta, iz tega pa sledijo štiri meje. Je na stran, ne pa na dokument, tako da je indeks 0 na strani 2 drugačen widget od indeksa 0 na strani 1, njuna primerjava pa je nesmiselna. Je pozicijski, tako da vstavljanje ali brisanje opombe izniči vsak predpomnjen indeks nad spremembo; shranjen indeks obravnavajte kot veljaven le, dokler stran ostane naložena in neurejena
Tretja meja je tista, ki preseneti ljudi, ki pregledujejo seznam polj. Indeks naštevanja so widgeti, ne polja. Skupina izbirnih gumbov je eno polje z več widget-potomci, tako da tri-gumbna skupina prispeva tri zaporedne indekse, ki vsi poročajo isto Name. Zapis TPdfFormFieldInfo nosi GroupCount in GroupIndex natanko za ta primer, uporabniški vmesnik seznama, ki ju prezre, pa isto polje pokaže trikrat. Četrta meja zadeva vrstni red prehoda: vrstni red tabulacije, izpostavljen tukaj, je vrstni red naštevanja widgetov, ki sledi polju /Annots, ne pa vnosu strani /Tabs (ISO 32000-1 §7.7.3.3) in ne drevesu polj AcroForm. Za večino proizvajalcev se ta ujemata; za obrazec, postavljen v dva stolpca z generatorjem, ki je najprej izdal desni stolpec, se ne, pot s tipkovnico, opisana v članku o navigaciji polj obrazca, pa se bo počutila napačno, čeprav je vsak indeks pravilen. Kadar se datoteka stranke nenavadno obnaša, preden teoretizirate, izpišite oba prostora indeksov drug ob drugem: pogled opomb in pogled polj iste strani, natisnjena skupaj, ponavadi vzrok razjasnita v enem pogledu
procedure DumpIndexSpaces(Pdf: TPdf);
var
I: Integer;
Info: TPdfFormFieldInfo;
begin
for I := 0 to Pdf.AnnotationCount - 1 do
Writeln('annot ', I, ': subtype ', Ord(Pdf.Annotation[I].Subtype));
for I := 0 to Pdf.FormFieldCount - 1 do
begin
Info := Pdf.FormFieldInfo[I];
Writeln('field ', I, ': ', string(Info.Name),
' widget ', Info.GroupIndex, ' of ', Info.GroupCount);
end;
end;
Štetje opomb, veliko nad štetjem polj, pomeni, da stran meša podtipe, kar je normalno v pregledanih dokumentih in je natanko situacija, za katero preslikava obstaja; članek o poteku pregleda opomb isto stran obravnava s strani označb. Enaki štetji na vsaki testni datoteki pa po drugi strani pomenita, da vaše pritrdilne naprave tega razreda hrošča sploh ne morejo zaznati, pošten odziv pa je dodati pritrdilno napravo obrazca, ki nosi povezavo in lepljiv listek
Naštevanje polj, fokus in API-ji opomb, opisani tukaj, so izdani z PDFium Component za Delphi, C++Builder in Lazarus, katerega stran izdelka nosi celoten referenčni opis polj obrazca, vključno z zapisom informacij polja in dostopniki fokusa