Műszaki cikk

PDF űrlapmező-navigáció Delphi-ben (PDFium komponens)

Nyomj Tabot egy olyan PDF-űrlapon, amelyet a kódod épített, és a kurzor két mezővel arrébb landol, mint kellene, vagy teljesen kihagyja a második oszlopot, vagy a harmadik mező után visszaugrik a tetejére a negyedik helyett. Az, aki egy számlát tölt ki a nézegetődben, azt várja, hogy a billentyűzet ugyanúgy járja végig az űrlapot, ahogy minden webes űrlapot, amit valaha használt. Amikor ez nem történik meg, az egérhez nyúl, megkeresi a következő mezőt, és csendben úgy dönt, hogy az eszközöd befejezetlen. A kiszámítható mezőbejárás a különbség egy olyan adatbeviteli nézegető között, amelyet az emberek elviselnek, és egy olyan között, amelyben megbíznak, és ez szinte teljes egészében azon múlik, hogy a megfelelő fókusz-API-t használod-e szimulált kattintásokkal hamisított billentyűzetbevitel helyett

Az alábbi példák a PDFium Component-et használják, egy PDFium-alapú VCL/LCL komponenst Delphihez, C++Builderhez és Lazarushoz. A navigáció az egyik abból a három dologból, amit egy űrlapnézegetőnek helyesen kell csinálnia; a másik kettő, az űrlap helyes megnyitása és a kitöltött értékek olyan mentése, hogy tényleg megjelenjenek, az, ahol a legtöbb meglepetés rejtőzik, így mindhármat lefedi az alábbi rész

Egy űrlap megnyitása: FormFill, FormType és az XFA-kérdés

A mezőkhöz való hozzáférés megköveteli, hogy az űrlapkitöltő alrendszer, amelyet a FormFill tulajdonság vezérel, be legyen kapcsolva a dokumentum megnyitása előtt. Amint aktív, a FormType megmondja, milyen fajta űrlappal állsz szemben, és a válasz megváltoztatja azt a funkciókészletet, amit megígérhetsz:

Diagram a FormFill beállításról és FormType felismerési ágakról egy Delphi PDFium Component megjelenítőben: ftNone, ftAcroForm és ftXfaFull kezelés szétválasztása
A FormType elágazik, amint a FormFill engedélyezve van, és minden ág más funkciókészletet ígér
Pdf.FileName := FormPath;
Pdf.FormFill := True;   // az Active előtt kapcsold be; szükséges bármely mezőhöz való hozzáféréshez
Pdf.Active := True;

case Pdf.FormType of
  ftNone:
    DisableFormPanel('This document has no interactive form');
  ftAcroForm:
    BuildFieldList;     // teljes mezőnavigáció és szerkesztés elérhető
  ftXfaFull:
    ShowXfaNotice;      // az XFA a saját XML-sablonjából renderel;
                        // a mezőszerkesztést korlátozottként kezeld
end;

Két gyakorlati megjegyzés következik ebből a kapcsolóból. Az AcroForm a szabványos ISO 32000 űrlapmodell, és ez az, amire az itteni összes API célzottan épül. Az XFA-dokumentumok a saját XML űrlaparchitektúrájukat ágyazzák be, így egy gyors AcroForm-demó után egy ügyfélnek teljes XFA-szerkesztést ígérni olyan vállalás, amit megbánsz majd. A második megjegyzés a mellékhatásokról szól: a FormFill True-ra állítása a dokumentum JavaScriptjét is inicializálja. Egy adatbeviteli nézegetőben ez pontosan helyes, mert a számítási szkriptek tartják naprakészen a futó összeget, ahogy valaki gépel. Egy ismeretlen eredetű fájlokhoz szánt előnézeti ablakban ez pontosan helytelen. A biztonságos PDF-előnézet cikk lefedi ennek a kompromisszumnak a FormFill := False oldalát

Tab-billentyűs bejárás, amely oda landol, ahová a felhasználók várják

Vissza a legelején említett billentyűzet-problémához. A csábítás az, hogy a Tabot úgy hamisítod meg, hogy egy egérkattintást szintetizálsz a következő widget téglalapjára, ami abban a pillanatban elromlik, amint egy mező lekerül a képernyőről, vagy két widget átfedi egymást. A fókusz-API ehelyett közvetlenül mozgatja az űrlap saját fókuszát, geometriai találgatás nélkül. Öt hívás fedi le: a FocusFormField index szerint, a FocusNextFormField és a FocusPreviousFormField a léptetéshez, a FocusedFormFieldIndex annak kiolvasásához, hol tartasz, és a ClearFormFieldFocus a fókusz teljes elejtéséhez

Diagram a Tab billentyűs fókuszbejárásról Delphi PDFium Component megjelenítőben: a FocusNextFormField egy oldal tabulátorsorrendjén belül körbeér, öt fókuszAPI fedi a billentyűzetes navigációt
A bejárás egy oldal tabulátorsorrendjén belül köröz, így a következő oldalra lépés a néző dolga marad
procedure TFormViewer.HandleTabKey(Shift: TShiftState);
begin
  if ssShift in Shift then
    PdfView.FocusPreviousFormField
  else
    PdfView.FocusNextFormField;
  UpdateFieldStatus;  // pl.: „4. mező a 17-ből: InvoiceDate"
end;

Az egyetlen viselkedési elem, amiben az emberek elakadnak, a körbefordulás. A bejárás az aktuális oldal tab-sorrendjén keresztül működik, és azon belül hurkol: lépj túl az utolsó mezőn, és visszakerülsz az elsőhöz. Mindkét léptető függvény visszaadja az új mezőindexet, vagy -1-et, ha az oldalon egyáltalán nincsenek mezők. Ez a hurkolás oldalankénti, nem dokumentumonkénti, ami azt jelenti, hogy a következő oldalra való átlépés a te feladatod, nem a könyvtáré. Hasonlítsd össze a visszaadott indexet azzal, amivel kezdted, vedd észre, amikor körbefordult, és magad léptesd tovább a PageNumber-t, ha az űrlapot egyetlen folyamatos sorozatként kell olvasni. Hagyd ki ezt az ellenőrzést, és egy kétoldalas űrlap csendben az első oldalon csapdába ejti a kurzort, ami a törött-Tab panasz saját változata

A bejárás akkor válik hasznossá, amikor a felhasználói felület többi része reagál rá. Az OnFormFieldEnter esemény akkor tüzel, amikor a fókusz megérkezik, a nézegetőn pedig az OnFormFieldFocusChange jelenti az új mezőindexet, így egy oldalsó panel lépést tud tartani azzal, amit a billentyűzet éppen kiválasztott. Amikor a fordított leképezésre van szükséged, egy képernyőpozícióból egy mezőbe, a FormFieldAt indexelt tulajdonság végzi a hit-tesztelést az eszköztipp-előnézetekhez és a kattints-a-szerkesztéshez panelekhez. Mindebben van egy csendes akadálymentesítési haszon: mivel a fókusz a dokumentum saját mezősorrendjét követi, az az útvonal, amit a Tab billentyűhöz bekötsz, ugyanaz, amit egy képernyőolvasó bejelent, minden extra munka nélkül

Mezőnevek megjelenítése nyers indexszámok helyett még egy tulajdonságot igényel. A FormFieldInfo[] indexenként egy TPdfFormFieldInfo rekordot ad vissza, amely hordozza a mezőnevet, a típust, a betűméretet, a bejelölt állapotot, az exportértéket és a csoporttagságot, ami az, amit egy navigációs listának meg kell jelenítenie („4. mező a 17-ből: InvoiceDate", nem pedig „4"). A rádiócsoportok olyan eset, amely megérdemel egy dedikált tesztfájlt. Több widget is megoszthat egyetlen mezőnevet, így egy widgetekből naivan összeállított lista ugyanazt a csoportot többször mutatja, és mindenkit megzavar, aki olvassa

Miért jönnek ki üresen a kitöltött értékek, és melyik hívás javítja ezt

A másik panasz, amely megtölti a support-sorokat, riasztóbb, mint egy rosszul viselkedő Tab billentyű: egy űrlapot programmal töltenek ki, az ügyfél megnyitja Acrobatban, és minden mező üresnek tűnik. Kattints egy mezőbe, és az értéke egyből megjelenik. Az adat végig a fájlban volt. Ami hiányzik, az az adat képe, és az okot érdemes egyszer megérteni, mert egy egész hibacsaládot megmagyaráz

Egy AcroForm szövegmező az értékét a mezőszótár /V bejegyzésében tárolja (ISO 32000-1 §12.7.3.3). Amit egy nézegető ténylegesen kifest, az valami külön dolog: a widget megjelenési adatfolyama a /AP alatt (§12.5.5), a tartalom egy kis, előre renderelt darabja. Ha a /V-t írod, és a /AP-t érintetlenül hagyod, a kettő elsodródik egymástól. Az érték ott van; a renderelt változata elavult vagy hiányzik. Az Acrobat épp úgy alakul, hogy újraépíti egy mező megjelenését, amikor az fókuszt kap, ami a teljes magyarázat arra, hogy az értékek csak kattintásra jelennek meg. A régi NeedAppearances jelző, amely arra kérte a nézegetőket, hogy generálják újra a megjelenéseket helyetted, soha nem működött egységesen, és elavulttá vált a PDF 2.0-ban, a nyomtatókiszolgálók és a bélyegkép-generátorok pedig teljesen figyelmen kívül hagyják. Ők a /AP-t festik ki, és semmi mást, így ha a /AP üres, üres dobozt nyomtatnak

Egy érték hozzárendelése a FormField[i]-n keresztül csak a /V-t írja. Ezért egy űrlap kitöltése egy háromlépéses sorozat, és a lépés, amit a csapatok kihagynak, a középső:

Diagram a /V érték szemben a /AP megjelenési elcsúszással AcroForm mezőkben, és a GenerateFormAppearances köré épített háromlépéses Delphi kitöltési sorrend
Az értékhozzárendelés csak a /V-t írja, és a középső lépés az, amely újrafesti azt, amit a nyomtatószerverek valójában renderelnek
procedure TFormViewer.FillAndSave(const Values: array of WString;
  const OutputPath: string);
var
  i: Integer;
begin
  for i := 0 to Pdf.FormFieldCount - 1 do
    Pdf.FormField[i] := Values[i];   // csak a /V-t írja

  // Építsd újra a /AP megjelenési adatfolyamokat; enélkül az űrlap
  // üresnek látszik Acrobatban, amíg minden mezőre rá nem kattintanak
  Pdf.GenerateFormAppearances;

  Pdf.SaveAs(OutputPath);
end;

A GenerateFormAppearances a teljes javítás. Minden widget megjelenési adatfolyamát újraépíti az aktuális értékekből, betűtípusokból és igazításból (quadding), így egy nézegető, amely soha nem futtat fókuszeseményt, egy nyomtatókiszolgáló vagy egy bélyegkép-generátor, mindenképp kifesti a kitöltött állapotot. Hívd meg egyszer a hozzárendelések kötege után, ne mezőnként egyszer. A megjelenés-generálás valódi elrendezési munkát végez, és a mezőnkénti hívások ezt feleslegesen megsokszorozzák egy nagy űrlapon

A megjelenések újragenerálása az a pillanat is, amikor a betűtípusok és az igazítás érvényesülnek, ami egy másodrendű meglepetés forrása. Az új adatfolyam minden értéket a widget téglalapján belül helyez el, a mező betűtípusát, méretét és igazítását használva. Egy érték, amely kényelmesen elfér a tesztűrlapodban, levágódhat vagy összezsugorodhat egy ügyfélpéldányban, ahol ugyanaz a mező keskenyebb. Az automatikusan méretezett mezők (nulla betűméret) az illeszkedéshez zsugorítják a szöveget; a fix méretű mezők egyszerűen levágják. Mindkettő jogszerű, és az egyetlen becsületes módja annak, hogy megtudd, egy adott űrlap melyiket teszi, az, hogy az újragenerált kimenetet nézed meg, nem azt a karakterláncot, amit írtál. Amikor valaki egy doboz szélén levágott szöveget jelent, ez szinte mindig ez az ok

Az ellenőrzést a munka befejezésének részeként kezeld, ne utólagos gondolatként. Nyisd meg a mentett fájlt Acrobatban, és győződj meg róla, hogy az értékek láthatók, mielőtt bármelyik mezőhöz hozzányúlnál. Aztán nyomtasd ki PDF-be vagy képbe egy másik nézegetőből, egyből, amely teljesen figyelmen kívül hagyja az űrlaplogikát, és győződj meg róla, hogy az értékek ezt az utat is túlélik. Együtt ez a két ellenőrzés elkapja a /V-kontra-/AP elsodródás minden változatát

Mezőkonfigurációk, amelyek átmennek a demón, és a gyakorlatban elbuknak

A tiszta demóűrlapok elrejtenek egy sor szélsőséges esetet, amit az ügyfélfájlok nem. Négy közülük teszi ki a legtöbb „nálam működött" jelentést

  • Jelölőnégyzet exportértékek. A „be" állapot nem mindig Yes. Egy űrlap szabadon meghatározhatja a saját exportértékét, és a rossz karakterlánc beírása vizuálisan bejelöletlenül hagyja a négyzetet, miközben a kódod meg van győződve róla, hogy beállította. Olvasd ki az exportértéket a FormFieldInfo[]-ból, ahelyett hogy feltételeznél egyet
  • Közös nevű rádiócsoportok. Egy mező, több widget. Az érték, amit hozzárendelsz, dönti el, melyik widget olvasható kiválasztottként, így az a felhasználói felület kód, amely feltételezi, hogy egy név egy téglalapra képződik le, végül a rossz gombra rajzolja a fókuszgyűrűt
  • Számított mezők. A dokumentum JavaScriptje által fenntartott összegek mezőeseményekre válaszolva frissülnek. Egy olyan programozott kitöltésnek, amely megkerüli ezeket az eseményeket, vagy ki kell váltania az újraszámolást, vagy közvetlenül felül kell írnia a számított mezőket. Egy olyan űrlap, ahol a tételsorok és az összeg nem egyeznek, rosszabb, mint bármelyik javítás
  • Rejtett kötelező mezők. A feltételes űrlapok olyan mezőket rejtenek el, amelyek még mindig kötelezőként vannak megjelölve. Előre döntsd el, hogy az érvényesítésed a láthatóságot tiszteletben tartja-e, vagy a nyers kötelező jelzőt, majd írd le ezt a döntést valahol, ahol a support megtalálja

Egy megkülönböztetést érdemes lerendezni, mielőtt megharap: a megjelenések generálása nem belesimítás. A GenerateFormAppearances mindenhol láthatóvá teszi az értékeket, miközben a mezőket szerkeszthetőn hagyja. A belesimítás beégeti a megjelenést a statikus oldaltartalomba, és véglegesen megszünteti az interaktivitást, ami helyes egy archív példánynál, és helytelen egy olyan űrlapnál, amit a következő embernek még ki kell töltenie. Ha a FormType ftXfaFull-t jelent ftAcroForm helyett, az itteni szerkesztési felület egyike sem alkalmazható tisztán amúgy sem, mivel a dokumentum a saját XML-sablonjából renderel; ismerd fel ezt az esetet, és mondd el a felhasználónak, ahelyett hogy hagynád, hogy magától találja meg a korlátot

Az itt bemutatott űrlapkitöltő alrendszer, fókuszbejárás és megjelenés-generálás a Delphihez, C++Builderhez és Lazarus/FPC-hez készült PDFium Component része. Ha a nézegetőd az űrlapadatok mellett átnézői jelöléseket is kezel, a jegyzet-átnézési cikk lefedi ezt a szomszédos modellt