PDFium Component, PDFium pagrindu sukurtame VCL/LCL komponente Delphi, C++Builder ir Lazarus aplinkoms, formos lauko indeksas nėra anotacijos indeksas. Puslapis neša Link, Text ir Ink anotacijas šalia savo valdiklių, todėl lauko išvardijimas turi filtruoti pagal FPDFAnnot_GetSubtype ir atskleisti nuo nulio pradedamą loginį indeksą, susietą atgal su realia anotacijos pozicija tik natyviame iškvietime
Klaida, atskleidžianti tai, nesuklystama, kai ją pamatai. Testuotojas paspaudžia Tab užpildytoje sąskaitos formoje, ir žymeklis dingsta, nes fokusas nuėjo į hipernuorodą poraštėje. Arba dar blogiau, nieko nenutinka: jūsų kodas užregistruoja lauką 3 kaip fokusuotą, UI panelė atsinaujina, o FORM_SetFocusedAnnot tyliai grąžino false visą laiką. Abu simptomai kyla iš tos pačios dizaino klaidos, ir vienas iš jų turi antrą šaknies priežastį, slepiančią po apačia
Dvi indeksų erdvės, kurias jums duoda PDFium
PDFium atskleidžia dvi numeravimo schemas per tą patį puslapį, ir jos sutampa tik dokumentuose, kurie atsitiktinai neturi nieko, išskyrus formos valdiklius. Pirmoji yra anotacijos indeksas: pozicija puslapio /Annots masyve, kurį skaičiuoja FPDFPage_GetAnnotCount ir kurį ima FPDFPage_GetAnnot (ISO 32000-1 §12.5.2). Antra yra loginis lauko indeksas, kurį programos lygio API turėtų siūlyti, einantis nuo nulio per interaktyvius laukus, kuriuos vartotojas iš tikrųjų gali pasiekti. ISO 32000-1 §12.5.6.19 apibrėžia valdiklio anotacijas kaip interaktyvių formos laukų vizualinį atvaizdavimą, o §12.7 apibrėžia pačią formą. Viskas kita puslapyje yra kitas subtipas su kitokia semantika: Link anotacija turi paskirties tašką, Ink anotacija turi štrichų sąrašą, Text anotacija yra lipnus lapelis. Nė viena iš jų nepriklauso lauko skaičiui, ir nė viena negali priimti formos fokuso. Tačiau /Annots masyve jos sėdi persipynusios su valdikliais tokia tvarka, kokia parašė gaminanti programa, kas dažnai nėra tvarka, kurią siūlo bet kas kitas apie dokumentą
Kodėl Tab nusileidžia ant hipernuorodos vietoj kito lauko?
Todėl, kad lauko skaičius iš tikrųjų buvo anotacijos skaičius. Originali realizacija grąžindavo FPDFPage_GetAnnotCount tiesiogiai iš FormFieldCount, kol lauko informacijos prieigos mechanizmas, tabuliavimo tvarkos pagalbininkas ir fokuso pagalbininkas visi traktavo tą patį sveikąjį skaičių kaip valdiklio poziciją. Švariame AcroForm puslapyje su šešiais valdikliais ir nieko daugiau, šeši lygu šeši, ir kiekvienas testas praeina. Pridėkite hipernuorodą poraštėje ir recenzento komentarą paraštėje, ir skaičius praneša aštuonis laukus, indeksai 6 ir 7 išsprendžiami į ne-formos objektus, o Tab eina tiesiai į juos
Taisymas išvardijimo gale yra skaičiuoti subtipus, ne anotacijas. Atidarykite kiekvieną anotaciją, paklauskite jos subtipo, laikykite valdiklius ir uždarykite rankeną finally bloke, nes FPDFPage_GetAnnot grąžina nuosavą rankeną, kuri turi grįžti per 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;
Atkreipkite dėmesį, ko tai sąmoningai nedaro. Tai nieko neklausia formos-pildymo aplinkos, ir jam nereikia formos rankenos, nes subtipas gyvena anotacijos žodyne ir skaitomas vien iš puslapio. Tai svarbu tvarkai: skaičius prieinamas prieš jums nusprendžiant, ar dokumentas apskritai nusipelno formos-pildymo aplinkos, ką apima straipsnis apie AcroForm JavaScript ir šeimininko įvykius kaip saugumo sprendimą, ne patogumą
Loginio indekso susiejimas atgal natyvioje riboje
Taisyklė, neleidžianti dviem erdvėms nutekėti viena į kitą, yra paprasta: loginis indeksas yra vienintelis skaičius, kertantis jūsų viešą API, ir jis konvertuojamas į anotacijos indeksą paskutinėje funkcijoje prieš natyvų iškvietimą. Vienas susiejimo pagalbininkas, naudojamas lauko informacijos, fokuso, vėliavėlių nustatytojų ir tabuliavimo tvarkos vienodai, yra tai, kas daro tą taisyklę įgyvendinamą
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;
Dvi šio pagalbininko savybės vertos aiškiai pasakyti. Tai tiesinis skenavimas, todėl naivus ciklas per kiekvieną lauką kainuoja kvadratinį anotacijos atidarymų skaičių puslapyje su šimtais valdiklių; jei išvardijate visą puslapį, vaikščiokite per anotacijas vieną kartą ir rinkite valdiklio rankenas eidami, o ne kvieskite susiejiklį kiekvienam laukui. Ir jis grąžina -1, o ne kelia klaidą, kas leidžia iškvietėjui nuspręsti, ar pasenęs indeksas yra programavimo klaida, verta išimties, ar lenktynės, vertos ignoravimo, pavyzdžiui, po redagavimo, pašalinusio anotaciją, kurią talpykloje esantis UI sąrašas vis dar nurodo
Kodėl FORM_SetFocusedAnnot nepavyksta nematomame puslapyje?
Todėl, kad PDFium atsisako fokusuoti valdiklį, kurio puslapio vaizdas niekada nebuvo pažymėtas galiojančiu. FORM_SetFocusedAnnot išsprendžia anotaciją į puslapio vaizdą formos-pildymo aplinkos viduje, ir jei tas puslapio vaizdas neegzistuoja, jis grąžina false be jokios diagnostikos. Indekso susiejimo pataisymas savaime todėl ištaiso Tab nusileidimą ant hipernuorodos, tačiau palieka antrą simptomą nepaliestą: jūsų loginio fokuso įrašas sako lauką 3, natyvus fokusuotas valdiklis vis dar niekas, o kiekvienas prieigos mechanizmas, pastatytas ant natyvaus fokuso, fokusuoto teksto, fokusuotos reikšmės, pasirinkimo pasirinkimo būsenos, toliau grąžina tuščią. Puslapio vaizdas sukuriamas FORM_OnAfterLoadPage ir sunaikinamas FORM_OnBeforeClosePage. Peržiūros programoje, sukurtoje aplink vizualinį valdiklį, tie iškvietimai vyksta kaip puslapio rodymo dalis, todėl klaida taip dažnai atrodo kaip tik-nematomo-režimo klaida: tas pats kodas, kuris veikia GUI demo, nepavyksta paketiniame įrankyje. Gyvavimo ciklas priklauso dokumento objektui, ne peržiūros programai, todėl PDFium Component dabar iškviečia abu iškvietimus, kada tik puslapis įkeliamas ar iškeliamas, esant formos rankenai. C signatūra ima puslapį pirma, o formos rankeną antra, kas lengva apversti rašant susiejimą ranka
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;
Patikra, įrodanti pataisymą, yra ta, kuri palygina abi puses. Iškvieskite FocusFormField su loginiu indeksu, tada perskaitykite reikšmę per prieigos mechanizmą, einantį per natyvų fokusuotą valdiklį, o ne per jūsų pačių įrašą, pvz., FocusedFormFieldValue ar FocusedFormOptionSelected. Jei loginis indeksas apvažiuoja atgal, bet natyvus prieigos mechanizmas grįžta tuščias, trūksta puslapio vaizdo, ne susiejimo
Ko loginis lauko indeksas nežada
Nuo nulio pradedamas lauko indeksas yra patogumas, ne semantinė tapatybė, ir iš to seka keturios ribos. Jis yra vienam puslapiui, ne vienam dokumentui, todėl indeksas 0 2 puslapyje yra kitas valdiklis nei indeksas 0 1 puslapyje, ir jų lyginimas beprasmis. Jis yra pozicinis, todėl anotacijos įterpimas ar ištrynimas anuliuoja kiekvieną talpykloje esantį indeksą virš pakeitimo; traktuokite saugotą indeksą kaip galiojantį tik tol, kol puslapis lieka įkeltas ir neredaguotas
Trečia riba yra ta, kuri nustebina žmones, peržiūrinčius lauko sąrašą. Indeksas išvardija valdiklius, ne laukus. Radijo grupė yra vienas laukas su keliais valdikliais vaikais, todėl trijų mygtukų grupė prisideda trimis nuosekliais indeksais, visi pranešantys tą patį Name. TPdfFormFieldInfo įrašas neša GroupCount ir GroupIndex būtent šiam atvejui, o sąrašo UI, ignoruojantis juos, rodo tą patį lauką tris kartus. Ketvirta riba liečia perėjimo tvarką: čia atskleista tabuliavimo tvarka yra valdiklio išvardijimo tvarka, seka /Annots masyvą, ne puslapio /Tabs įrašą (ISO 32000-1 §7.7.3.3) ir ne AcroForm lauko medį. Daugumai gamintojų jie sutampa; formai, išdėstytai dviem stulpeliais generatoriaus, kuris pirmiausia išleido dešinįjį stulpelį, jie ne, o klaviatūros kelias, aprašytas formos lauko navigacijos straipsnyje, jausis neteisingas, net jei kiekvienas indeksas teisingas. Kai kliento failas elgiasi keistai, iškraukite abi indeksų erdves greta prieš teoretizuodami: to paties puslapio anotacijos vaizdas ir lauko vaizdas, atspausdinti kartu, dažniausiai padaro priežastį akivaizdžią vienu žvilgsniu
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;
Anotacijos skaičius, žymiai viršijantis lauko skaičių, reiškia, kad puslapis maišo subtipus, kas normalu peržiūrėtuose dokumentuose ir yra būtent situacija, kuriai susiejimas egzistuoja; anotacijų peržiūros eigos straipsnis žiūri į tą patį puslapį iš žymėjimo pusės. Vienodi skaičiai kiekviename testo faile, kita vertus, reiškia, kad jūsų fixture'ai negali aptikti šios klaidų klasės apskritai, o sąžiningas atsakymas yra pridėti formos fixture'ą, nešantį nuorodą ir lipnų lapelį
Čia aprašytos lauko išvardijimo, fokuso ir anotacijos API pristatomos su PDFium Component Delphi, C++Builder ir Lazarus aplinkoms, kurio produkto puslapyje pateikiama pilna formos lauko nuoroda, įskaitant lauko informacijos įrašą ir fokuso prieigos mechanizmus