Kyllä — PDFium tukee XFA-lomakkeita, mutta siinä on yksi tärkeä jako. PDFium-komponentti pystyy havaitsemaan XFA-lomakkeen ja purkamaan sen template-, datasets- ja config-paketit raakana XML:nä käyttämällä tavallista DLL:ää ilman ajoympäristöriippuvuutta. *Dynaamisen* XFA-lomakkeen ajaminen eli sen XML-kuvauksen asettelu eläviksi, interaktiivisiksi sivuiksi on erillinen ongelma, joka vaatii lisäksi V8-tuella varustetun koontiversion. Yhteisö kysyy jatkuvasti, "tukeeko PDFium XFA-muotoa", ikään kuin se olisi yksi kyllä-tai-ei-ominaisuus. Rehellinen vastaus on, että XFA-tietojen lukeminen ja XFA-lomakkeen renderöinti ovat kaksi hyvin erilaista asiaa, joilla on kaksi hyvin erilaista riippuvuusjalanjälkeä
Tämä artikkeli käy läpi molemmat puolet PDFium-komponentilla, joka on PDFiumin natiivi VCL-kääre Delphille ja C++Builderille. Ensin tarkastellaan havaitsemista ja staattista vientipolkua — FormType-tyyppiä, XFA-ominaisuutta ja GetXfaPacket*-lukijoita —, sitten dynaamista ajoympäristöpolkua sen V8-rajoituksella, ja lopuksi suorituskykytestejä (capability probes), joiden avulla voit epäonnistua tyylikkäästi, kun käyttöönotettu DLL ei pysty ajamaan moottoria
Tukeeko PDFium XFA-lomakkeita?
Käsitä se kahtena eri suorituskykynä, ei yhtenä. Staattinen XFA-tietojen purkaminen on aina käytettävissä: matalan tason FPDF_GetXFAPacket*-vientifunktiot, jotka tukevat ominaisuuksia GetXfaPacketCount, GetXfaPacketName ja GetXfaPacketContent, on käännetty jokaiseen kohtuullisen uuteen pdfium.dll-tiedostoon, jopa sellaiseen koontiversioon, jossa XFA on kytketty pois päältä. Siksi raa'an template-, datasets- ja config-XML:n vetäminen ulos lomakkeesta ei aiheuta lainkaan ajoympäristöriippuvuutta — se on puhdasta rakenteellista lukemista ilman moottoria. Dynaaminen XFA on se toinen suorituskyky: lomakkeen ajaminen, sen laskelmien arviointi ja sen antaminen XFA-asettelumoottorin sivutettavaksi. Tämä polku on olemassa vain PDFium-koontiversiossa, joka on käännetty XFA-tuella V8 JavaScript -moottorin päälle, ja se on se osa, josta useimmat "tukeeko PDFium XFA:ta" -keskustelut todella kiistelevät
Standardit kehystävät jaon siististi. XFA on määritelty ydin-PDF-kieliopin ulkopuolella standardissa Adobe XFA 3.3 (2012), ja PDF 1.7 §8.6.7 kuvaa, miten XFA-lomaketta kuljetetaan PDF-tiedostossa: lomakepaketit elävät AcroForm-sanakirjan /XFA-merkinnän alla, ja asiakirja, joka on aseteltava XFA-prosessorilla ennen kuin sillä on mitään merkitystä, asettaa luetteloon arvon /NeedsRendering true. Nämä kaksi merkkiä ovat juuri ne, joita PDFium-komponentti etsii, ja juuri ne, joihin voit luottaa, kun analysoit tiedostoa, jota et ole koskaan nähnyt
Miten XFA eroaa AcroFormista?
AcroForm on klassinen PDF-lomake: widget-huomautukset — tekstikentät, valintaruudut, radioryhmät — on ankkuroitu kiinteisiin koordinaatteihin tavallisille PDF-sivuille. Näkemäsi sivu on tiedostossa oleva sivu, ja kentät istuvat sen päällä. XFA valitsee päinvastaisen lähestymistavan. Interaktiivinen lomake on kuvattu kokonaan XML-muodossa, ja dynaamisessa XFA-asiakirjassa PDF-sivut ovat käytännössä paikkamerkkejä — todellinen sisältö luodaan avaamishetkellä XFA-moottorilla, joka lukee XML-mallin ja asettelee sen, kasvaen tai kutistuen tietojen mukaan. Tämän ilmoittaa /NeedsRendering true: ilman XFA-prosessoria staattiset sivut voivat olla tyhjiä tai sisältää vain "avaa yhteensopivassa katseluohjelmassa" -ilmoituksen
Itse XFA-hyötykuorma on joukko nimettyjä paketteja, ja kolme niistä kantaa kaiken, mitä useimmat integraatiot tarvitsevat. template-paketti on lomakkeen määrittely — kentät, asettelu, laskelmat ja skriptit. datasets-paketti pitää sisällään varsinaisen esiintymän tiedot, täytetyt kenttäarvot XML-puuna. config-paketti kuljettaa käsittelyohjeita XFA-moottorille. Muita on olemassa — localeSet, connectionSet, xdp-kääre —, mutta lomakkeen auditointiin tai tietojen migraatioon template plus datasets on yleensä koko tehtävä. Koska nämä tiedot ovat vain XML:ää virrassa, niiden lukeminen ei koskaan vaadi minkään suorittamista, mikä on perussyy siihen, miksi staattinen purkaminen on riippuvuuksista vapaata
XFA-lomakkeen havaitseminen Delphissä
Havaitseminen alkaa samalla tavalla kuin jokainen PDFium-komponentin tehtävä: aseta FileName, käännä Active := True ja tarkista, että se todella aukesi. Active := True ei koskaan nosta poikkeusta — vaurioitunut, salattu tai DLL puuttuu jättää Active-ominaisuuden arvoon False —, joten suojalause on pakollinen. Kun asiakirja on auki, FormType kertoo, mitä lomakemallia se käyttää. Kaksi TPdfFormType-arvoista on XFA:ta: ftXfaFull on täysimääräinen, dynaaminen XFA-lomake, sellainen joka tarvitsee renderöintiä, kun taas ftXfaForeground on XFAF, etualamäärite (foreground subset), jossa XFA-sisältö piirretään muuten normaalien AcroForm-sivujen päälle, jolloin staattiset sivut säilyvät mielekkäinä. XFA-boolean on oikotie ilmaisemaan "tämä asiakirja sisältää kumpaa tahansa tyyppiä olevan XFA-paketin". Jos työskentelet myös widget-tason kenttien kanssa XFAF- tai AcroForm-asiakirjassa, mekaniikka käsitellään oppaassa PDFium-lomakekenttien navigointi
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'application-form.pdf';
Pdf.Active := True;
if not Pdf.Active then
Exit; // vaurioitunut, salattu tai DLL puuttuu
case Pdf.FormType of
ftXfaFull:
Log('Täysi (dynaaminen) XFA-lomake');
ftXfaForeground:
Log('XFAF: XFA kerrostettuna staattisten AcroForm-sivujen päälle');
ftAcroForm:
Log('Klassinen AcroForm');
else
Log('Ei interaktiivista lomaketta');
end;
if Pdf.XFA then
Log('Asiakirja kuljettaa XFA-pakettikuormaa');
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
XFA-pakettien purkaminen raakana XML:nä
Purkaminen on se arvokas, nollariippuvuuden puoli. Suojaa XfaStaticReadable-ominaisuudella — se varmistaa, että pakettilukijan vientifunktiot selvitettiin ladatussa DLL-tiedostossa —, ja käy sitten paketit läpi indeksin mukaan. GetXfaPacketCount palauttaa lomakkeen sisältämien pakettien määrän, GetXfaPacketName antaa kullekin nimen ja GetXfaPacketContent palauttaa raa'at tavut, jotka tunnetuille paketeille ovat UTF-8 XML-koodia. Kun tiedät jo, minkä paketin haluat, nimetyt apurit GetXfaTemplate, GetXfaDatasets ja GetXfaConfig selvittävät nimen puolestasi ja palauttavat TBytes-tavut suoraan. Tiedonsiirtotehtävässä se tärkein on GetXfaDatasets: se antaa sinulle täytetyt kenttäarvot XML-asiakirjana, jonka voit jäsentää ja mäpätä omaan skeemaasi. Tämä on samanlaista jatkotyötä, jota tekisit tekstin PDF-dokumentista purkamisen jälkeen
procedure ExtractXfaData(Pdf: TPdf; const Dir: string);
var
I, Count: Integer;
PacketName: string;
Content, Datasets: TBytes;
begin
if not Pdf.XfaStaticReadable then
Exit; // pakettilukijan vientifunktioita ei ole
Count := Pdf.GetXfaPacketCount;
for I := 0 to Count - 1 do
begin
PacketName := Pdf.GetXfaPacketName(I);
Content := Pdf.GetXfaPacketContent(I); // raaka UTF-8 XML
TFile.WriteAllBytes(
TPath.Combine(Dir, PacketName + '.xml'), Content);
end;
// Tai hyppää suoraan täytettyihin kenttäarvoihin nimen mukaan:
Datasets := Pdf.GetXfaDatasets;
if Length(Datasets) > 0 then
ParseAndMap(Datasets);
end;
Miksi dynaaminen XFA-renderöinti vaatii V8-moottoria?
Johtopäätös ensin: koska dynaaminen XFA-lomake on pieni ohjelma, ei staattinen piirros. Mallipaketti (template packet) kuljettaa FormCalc- ja JavaScript-laskelmia, validointeja ja tapahtumaskriptejä, ja XFA-asettelumoottorin on suoritettava ne päättääkseen, miltä sivut edes näyttävät. PDFium toteuttaa kyseisen moottorin vain koontiversiossa, joka on käännetty XFA-tuella Googlen V8 JavaScript -moottorin päälle. Tämä on sama syy, miksi V8-tuella varustettu DLL (pdfium32v8.dll tai pdfium64v8.dll) on huomattavasti suurempi kuin standardikoontiversio. Ilman V8-moottoria skriptejä ei voi ajaa, joten renderöitäviä sivuja ei voida näyttää — ainoastaan raaka XML-koodi, jonka voit jo lukea staattisesti
Kyseisen koontiversion valinnalla on tiukka rajoitus, jota et voi kiertää: se on prosessikohtainen, kertaluonteinen päätös. Yksittäinen prosessi ei voi ladata sekä standardia pdfium.dll-tiedostoa että V8-tuella varustettua koontiversiota — ne vievät samat symbolit ja V8 säilyttää globaalin isolaattitilan (global isolate state) —, joten PDFium-komponentin on valittava V8-koontiversio ennen kuin kirjasto ladataan ensimmäisen kerran. Tehdäkseen tästä automaattista, LoadDocument esiskannaa tiedoston /XFA- ja /NeedsRendering true -merkkien varalta (sama tarkistus on käytettävissä erillisenä PdfFileLooksXfa-funktiona) ja asettaa EnableV8Engine := True ennen latausta ne löytäessään. Kun tavallinen pdfium.dll on jo ladattu muistiin, kytkin ei voi enää astua voimaan, ja ajoympäristön puuttumispolku raportoi tästä tapauksesta
begin
// Kurkista XFA-paketti ennen kuin PDFium ladataan ensimmäistä kertaa, jotta
// V8-tuella varustettu koontiversio voidaan edelleen valita. PdfFileLooksXfa etsii
// merkkejä /XFA ja /NeedsRendering true jäsentämättä koko tiedostoa.
if PdfFileLooksXfa('dynamic-form.pdf') then
EnableV8Engine := True;
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'dynamic-form.pdf';
Pdf.Active := True;
if Pdf.Active and Pdf.XFA then
begin
if Pdf.XfaRuntimeAvailable then
RenderXfaPages(Pdf) // moottori on käynnissä: dynaaminen asettelu
else
ExtractXfaData(Pdf, 'C:\out'); // turvaudu staattisiin paketteihin
end;
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
Puuttuvan XFA-ajoympäristön tyylikäs käsittely
Kolme suorituskykytestiä kertoo tarkalleen, kuinka paljon XFA-kykyä sinulla on. XfaFeaturesAvailable on globaali tarkistus siitä, että XFA/V8-apurin vientifunktiot ovat olemassa ladatussa DLL-tiedostossa. XfaStaticReadable varmistaa asiakirjakohtaisesti, että pakettilukijat selvitettiin — tämä on suojalause purkamista varten. XfaRuntimeAvailable on se vahvin: se on tosi vain silloin, kun tämä nimenomainen asiakirja todella aktivoi XFA-moottorin, mikä tarkoittaa, että se on XFA, V8-koontiversio ladattiin ja FPDF_LoadXFA onnistui. Asiakirja voi raportoida tilan XFA = True, mutta XfaRuntimeAvailable = False aina, kun käyttöönotetusta DLL-tiedostosta puuttuu XFA tai standardikoontiversio oli jo ladattu
Kun XFA-asiakirja avautuu, mutta moottori ei voi toimia, PDFium-komponentti ei ota riskejä. Se lukitsee lomakkeen turvalliseen staattiseen tilaan sen sijaan, että se pyytäisi dynaamista ajoympäristöä, jota se ei voi tukea, ja nostaa poikkeuksen OnXfaRuntimeMissing kerran, jotta isäntäsovellus voi reagoida — tyypillisesti kertomalla käyttäjälle, että sovellus on käynnistettävä uudelleen V8-tuella varustetun koontiversion kanssa, tarjoten silti sillä välin staattisesti luettavissa olevat tiedot. Tämän käsittelijän liittäminen on ero tyylikkään suojakeinon ja sellaisen lomakkeen välillä, joka ei näytä mitään
procedure TForm1.PdfXfaRuntimeMissing(Sender: TObject);
begin
// Laukeaa kerran, kun XFA-asiakirja avautuu mutta moottori ei voi toimia:
// tavallinen pdfium.dll oli jo ladattu, tai koontiversiosta puuttuu XFA/V8.
ShowMessage('This is a dynamic XFA form. Restart with the ' +
'V8-enabled build to render it; its packet data is still readable.');
end;
Ole rehellinen rajasta myös omassa tuotteessasi. Jos et koskaan toimita V8-tuella varustettua DLL-tiedostoa, dynaamiset XFA-lomakkeet eivät koskaan renderöidy käyttäjillesi — mutta voit silti havaita ne, purkaa jokaisen paketin ja vetää täytetyt tiedot ulos. Tämä riittää siihen suureen tehtäväluokkaan, jossa on todellisuudessa kyse tietojen saamisesta ulos vanhoista viranomais- ja pankkilomakkeista niiden interaktiivisen esittämisen sijaan. Varaa raskaampi V8-koontiversio tapauksiin, jotka todella tarvitsevat live-lomakkeen, ja anna suorituskykytestien reitittää kukin asiakirja polulle, jonka se voi todella ottaa. Jos purkuputkesi käsittelee myös upotettuja tiedostoja, sama vain luku -filosofia pätee artikkeliin PDF-liitetiedostot Delphissä
Tässä esitetyt FormType-, XFA-, GetXfaPacketCount-, GetXfaPacketName-, GetXfaPacketContent-, GetXfaTemplate-, GetXfaDatasets-, GetXfaConfig- ja suorituskykytestien API-rajapinnat ovat osa Delphille ja C++Builderille tarkoitetun PDFium-komponentin mukana