PDFium Componentissa, PDFium-pohjaisessa VCL/LCL-komponentissa Delphille, C++Builderille ja Lazarukselle, lomakekentän indeksi ei ole huomautusindeksi. Sivu kantaa Link-, Text- ja Ink-huomautuksia widgettiensä vierellä, joten kenttien luettelointi täytyy suodattaa FPDFAnnot_GetSubtype:lla ja paljastaa nollapohjainen looginen indeksi, kartoitettuna takaisin todelliseen huomautuspaikkaan vasta natiivikutsussa
Bugi, joka paljastaa tämän, on erehtymätön, kun sen on kerran nähnyt. Testaaja painaa Tab-näppäintä täytetyssä laskulomakkeessa, ja kohdistin katoaa, koska fokus meni alatunnisteen hyperlinkkiin. Tai pahempaa, mitään ei tapahdu ollenkaan: koodisi tallentaa kentän 3 fokusoituna, käyttöliittymäpaneeli päivittyy, ja FORM_SetFocusedAnnot palautti hiljaa false:n koko ajan. Molemmat oireet tulevat samasta suunnitteluvirheestä, ja yhdellä niistä on toinen juurisyy piilossa alla
Kaksi indeksiavaruutta, jotka PDFium antaa sinulle
PDFium paljastaa kaksi numerointijärjestelmää saman sivun yli, ja ne täsmäävät vain dokumenteilla, jotka sattuvat sisältämään vain lomakewidgettejä. Ensimmäinen on huomautusindeksi: paikka sivun /Annots-taulukossa, jonka FPDFPage_GetAnnotCount laskee ja jonka FPDFPage_GetAnnot ottaa (ISO 32000-1 §12.5.2). Toinen on looginen kenttäindeksi, jonka sovellustason API:n pitäisi tarjota, kulkien nollasta interaktiivisten kenttien yli, jotka käyttäjä oikeasti pystyy tavoittamaan. ISO 32000-1 §12.5.6.19 määrittelee widget-huomautukset interaktiivisten lomakekenttien visuaalisena esityksenä, ja §12.7 määrittelee itse lomakkeen. Kaikki muu sivulla on eri alityyppi eri semantiikalla: Link-huomautuksella on kohde, Ink-huomautuksella on vetoluettelo, Text-huomautus on tarralappu. Mikään niistä ei kuulu kenttälaskuun, eikä mikään niistä pysty ottamaan vastaan lomakefokusta. Silti /Annots-taulukossa ne istuvat sekoittuneina widgettien kanssa missä tahansa järjestyksessä, jonka tuottava sovellus kirjoitti ne, mikä on usein eri järjestys kuin mitä mikään muu dokumentissa ehdottaisi
Miksi Tab laskeutuu hyperlinkkiin seuraavan kentän sijaan?
Koska kenttälaskenta oli oikeasti huomautuslaskenta. Alkuperäinen toteutus palautti FPDFPage_GetAnnotCount:in suoraan FormFieldCount:ista, samalla kun kenttätietojen käyttäjä, sarkainjärjestyksen apuri ja fokusapuri kaikki käsittelivät tuota samaa kokonaislukua widget-paikkana. Puhtaalla AcroForm-sivulla kuudella widgetillä eikä muulla, kuusi on yhtä kuin kuusi ja jokainen testi läpäisee. Lisää hyperlinkki alatunnisteeseen ja tarkastajan kommentti marginaaliin, ja laskenta raportoi kahdeksan kenttää, indeksit 6 ja 7 ratkeavat ei-lomakeobjekteiksi, ja Tab kävelee suoraan niihin
Korjaus luettelointipäässä on laskea alityyppejä eikä huomautuksia. Avaa jokainen huomautus, kysy sen alityyppiä, pidä widgetit, ja sulje kahva finally-lohkossa, koska FPDFPage_GetAnnot palauttaa omistetun kahvan, jonka täytyy mennä takaisin FPDFPage_CloseAnnot:in kautta
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;
Huomaa, mitä tämä tarkoituksella ei tee. Se ei kysy lomaketäyttöympäristöltä mitään, eikä se tarvitse lomakekahvaa, koska alityyppi elää huomautussanakirjassa ja on luettavissa pelkästä sivusta. Se on tärkeää järjestyksen kannalta: laskenta on saatavilla ennen kuin olet edes päättänyt, ansaitseeko dokumentti ylipäätään lomaketäyttöympäristön, mikä käsitellään artikkelissa AcroForm JavaScriptista ja isäntätapahtumista turvallisuuspäätöksenä eikä mukavuuspäätöksenä
Loogisen indeksin kartoittaminen takaisin natiivirajalla
Sääntö, joka estää kahta avaruutta vuotamasta toisiinsa, on yksinkertainen: looginen indeksi on ainoa luku, joka ylittää julkisen API:si, ja se muunnetaan huomautusindeksiksi viimeisessä funktiossa ennen natiivikutsua. Yksi kartoitusapuri, jota kenttätiedot, fokus, lippujen asetusfunktiot ja sarkainjärjestys kaikki käyttävät, on se, mikä tekee tuosta säännöstä täytäntöönpanokelpoisen
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;
Kaksi tämän apurin ominaisuutta kannattaa todeta suoraan. Se on lineaarinen skannaus, joten naiivi silmukka jokaisen kentän yli maksaa neliöllisen määrän huomautusavauksia sivulla, jolla on satoja widgettejä; jos luettelet koko sivua, kulje huomautukset läpi kerran ja kerää widget-kahvat matkan varrella sen sijaan, että kutsuisit kartoittajaa jokaista kenttää kohti. Ja se palauttaa -1:n nostamisen sijaan, mikä antaa kutsujalle mahdollisuuden päättää, onko vanhentunut indeksi ohjelmointivirhe, joka ansaitsee poikkeuksen, vai kilpajuoksu, joka ansaitsee ohituksen, esimerkiksi muokkauksen jälkeen, joka poisti huomautuksen, johon välimuistiin tallennettu käyttöliittymälista yhä viittaa
Miksi FORM_SetFocusedAnnot epäonnistuu ilman käyttöliittymää ajettavalla sivulla?
Koska PDFium kieltäytyy fokusoimasta widgettiä, jonka sivunäkymää ei koskaan merkitty päteväksi. FORM_SetFocusedAnnot ratkaisee huomautuksen sivunäkymäksi lomaketäyttöympäristön sisällä, ja jos tuota sivunäkymää ei ole olemassa, se palauttaa false:n ilman minkäänlaista diagnostiikkaa. Pelkän indeksikartoituksen korjaaminen siis korjaa Tab:n laskeutumisen hyperlinkkiin, mutta jättää toisen oireen koskemattomaksi: loogisen fokuksesi tietue sanoo kenttä 3, natiivi fokusoitu widget on yhä tyhjä, ja jokainen natiivin fokuksen päälle rakennettu käyttäjä, fokusoitu teksti, fokusoitu arvo, valinnan tilan tila, jatkaa tyhjän palauttamista. Sivunäkymän luo FORM_OnAfterLoadPage ja tuhoaa FORM_OnBeforeClosePage. Visuaalisen kontrollin ympärille rakennetussa katseluohjelmassa nuo kutsut tapahtuvat osana sivun näyttämistä, minkä vuoksi epäonnistuminen niin usein näyttää vain-ilman-käyttöliittymää-bugilta: sama koodi, joka toimii GUI-demossa, epäonnistuu eräajotyökalussa. Elinkaari kuuluu dokumenttiobjektille, ei katseluohjelmalle, joten PDFium Component antaa nyt molemmat kutsut aina, kun sivu ladataan tai puretaan lomakekahvan ollessa läsnä. C-signatuuri ottaa sivun ensin ja lomakekahvan toisena, mikä on helppo kääntää ympäri, kun kirjoitat sidontaa käsin
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;
Tarkistus, joka todistaa korjauksen, on se, joka vertaa kahta puolta. Kutsu FocusFormField:ia loogisella indeksillä, lue sitten arvo käyttäjän kautta, joka kulkee natiivin fokusoidun widgetin kautta oman tietueesi sijaan, kuten FocusedFormFieldValue tai FocusedFormOptionSelected. Jos looginen indeksi kiertää edestakaisin mutta natiivi käyttäjä tulee takaisin tyhjänä, sivunäkymä puuttuu, ei kartoitus
Mitä looginen kenttäindeksi ei lupaa
Nollapohjainen kenttäindeksi on mukavuus, ei semanttinen identiteetti, ja neljä rajaa seuraa siitä. Se on sivukohtainen, ei dokumenttikohtainen, joten indeksi 0 sivulla 2 on eri widget kuin indeksi 0 sivulla 1, ja niiden vertaaminen on merkityksetöntä. Se on paikkaan sidottu, joten huomautuksen lisääminen tai poistaminen mitätöi jokaisen välimuistiin tallennetun indeksin muutoksen yläpuolella; kohtele tallennettua indeksiä pätevänä vain niin kauan kuin sivu pysyy ladattuna ja muokkaamattomana
Kolmas raja on se, joka yllättää kenttälistaa tarkastelevat ihmiset. Indeksi luetteloi widgettejä, ei kenttiä. Radioryhmä on yksi kenttä useilla widget-lapsilla, joten kolmen painikkeen ryhmä tuottaa kolme peräkkäistä indeksiä, jotka kaikki raportoivat saman Name:n. TPdfFormFieldInfo-tietue kantaa GroupCount:in ja GroupIndex:in täsmälleen tätä tapausta varten, ja käyttöliittymälista, joka ohittaa ne, näyttää saman kentän kolmesti. Neljäs raja koskee läpikäyntijärjestystä: täällä paljastettu sarkainjärjestys on widget-luettelointijärjestys, joka seuraa /Annots-taulukkoa, ei sivun /Tabs-merkintää (ISO 32000-1 §7.7.3.3) eikä AcroForm-kenttäpuuta. Useimmille tuottajille nämä täsmäävät; lomakkeelle, joka on aseteltu kahteen sarakkeeseen generaattorilla, joka tuotti oikean sarakkeen ensin, ne eivät täsmää, ja näppäimistöpolku, joka kuvataan artikkelissa lomakekentän navigoinnista, tuntuu väärältä, vaikka jokainen indeksi on oikein. Kun asiakastiedosto käyttäytyy oudosti, dumppaa molemmat indeksiavaruudet rinnakkain ennen teorioimista: saman sivun huomautusnäkymä ja kenttänäkymä, tulostettuina yhdessä, tekevät yleensä syyn ilmeiseksi yhdellä silmäyksellä
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;
Huomautuslaskenta, joka on paljon kenttälaskentaa korkeampi, tarkoittaa, että sivu sekoittaa alityyppejä, mikä on normaalia tarkastetuissa dokumenteissa ja on täsmälleen se tilanne, jota varten kartoitus on olemassa; artikkeli huomautusten tarkastustyönkulusta katsoo samaa sivua merkintäpuolelta. Yhtäläiset laskennat jokaisessa testitiedostossa taas tarkoittavat, että kalusteesi eivät pysty havaitsemaan tätä bugiluokkaa lainkaan, ja rehellinen vastaus on lisätä lomakekaluste, joka kantaa linkin ja tarralapun
Tässä kuvatut kenttäluettelointi-, fokus- ja huomautus-API:t toimitetaan PDFium Componentin mukana Delphille, C++Builderille ja Lazarukselle, jonka tuotesivu sisältää täydellisen lomakekenttäviitteen, mukaan lukien kenttätietotietueen ja fokuskäyttäjät