Da — PDFium podržava XFA obrasce, ali uz jednu važnu podelu. PDFium komponenta može detektovati XFA obrazac i ekstrahovati njegove pakete template, datasets i config kao sirovi XML koristeći standardni DLL, sa nula zavisnosti u vremenu izvršavanja (runtime). Pokretanje dinamičkog XFA obrasca — odnosno raspoređivanje njegovog XML opisa u aktivne, interaktivne stranice — je poseban problem koji dodatno zahteva verziju sa omogućenim V8 motorom. Zajednica često pita da li PDFium "radi XFA" kao da je to jedna jedinstvena mogućnost, a iskren odgovor je da su čitanje XFA podataka i renderovanje XFA obrasca dve veoma različite stvari sa veoma različitim zavisnostima
Ovaj članak prolazi kroz obe polovine pomoću PDFium komponente, izvornog VCL omotača (wrapper) oko PDFium-a za Delphi i C++Builder. Najpre ćemo proći kroz putanju detekcije i statičke ekstrakcije — FormType, svojstvo XFA i čitače GetXfaPacket* — a zatim kroz dinamičku putanju izvršavanja sa njenim V8 ograničenjem, i na kraju kroz provere mogućnosti koje vam omogućavaju da elegantno odreagujete kada distribuirani DLL ne može da pokrene motor
Da li PDFium podržava XFA obrasce?
Posmatrajte to kao dve mogućnosti, a ne kao jednu. Statička XFA ekstrakcija je uvek dostupna: niskonivoelske izvozne funkcije FPDF_GetXFAPacket* koje stoje iza GetXfaPacketCount, GetXfaPacketName i GetXfaPacketContent su kompajlirane u svaku iole modernu datoteku pdfium.dll, čak i u verziju sa isključenom XFA podrškom. Zato izvlačenje sirovog XML-a za template, datasets i config iz obrasca ne nosi nikakvu zavisnost u vremenu izvršavanja — to je čisto strukturno čitanje, bez učešća motora. Dinamički XFA je druga mogućnost: stvarno pokretanje obrasca, procena njegovih proračuna i prepuštanje XFA motoru za raspored da ga prelomi na stranice. Ta putanja postoji samo u PDFium verziji kompajliranoj sa XFA podrškom povrh V8 JavaScript motora, i to je deo oko kog se zapravo vodi većina rasprava na temu "da li PDFium podržava XFA"
Kako se XFA razlikuje od AcroForm-a?
AcroForm je klasičan PDF obrazac: anotacije vidžeta — tekstualna polja, polja za potvrdu, radio dugmad — usidrena na fiksnim koordinatama na običnim PDF stranicama. Stranica koju vidite je stranica u datoteci, a polja leže povrh nje. XFA zauzima suprotan pristup. Interaktivni obrazac je u potpunosti opisan u XML-u, a u potpunom (dinamičkom) XFA dokumentu PDF stranice su zapravo samo čuvari mesta — stvarni sadržaj se generiše u vreme otvaranja od strane XFA motora koji čita XML šablon i raspoređuje ga, šireći se ili skupljajući kako bi odgovarao podacima. To je ono što najavljuje /NeedsRendering true: bez XFA procesora, statičke stranice mogu biti prazne ili sadržati samo obaveštenje "molimo otvorite u kompatibilnom pregledaču"
Sam XFA sadržaj je skup imenovanih paketa, a tri od njih nose sve što je većini integracija potrebno. Paket template je definicija obrasca — polja, raspored, proračuni i skripte. Paket datasets drži stvarne podatke instance, odnosno popunjene vrednosti polja u vidu XML stabla. Paket config nosi uputstva za obradu za XFA motor. Postoje i drugi — localeSet, connectionSet, xdp omotač — ali za reviziju obrasca ili migraciju podataka iz njega, template plus datasets je obično ceo posao. Pošto su ti podaci samo XML koji leži u toku (stream), njihovo čitanje nikada ne zahteva izvršavanje bilo čega, što je osnovni razlog zašto je statička ekstrakcija bez zavisnosti
Detektovanje XFA obrasca u Delphi-ju
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'application-form.pdf';
Pdf.Active := True;
if not Pdf.Active then
Exit; // damaged, encrypted, or DLL missing
case Pdf.FormType of
ftXfaFull:
Log('Full (dynamic) XFA form');
ftXfaForeground:
Log('XFAF: XFA layered over static AcroForm pages');
ftAcroForm:
Log('Classic AcroForm');
else
Log('No interactive form');
end;
if Pdf.XFA then
Log('Document carries an XFA packet payload');
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
Ekstrakcija XFA paketa kao sirovog XML-a
Ekstrakcija je vrednija polovina koja ne zahteva zavisnosti. Zaštitite kod proverom XfaStaticReadable — ona potvrđuje da su izvozne funkcije čitača paketa razrešene u učitanom DLL-u — a zatim prolazite kroz pakete po indeksu. Metod GetXfaPacketCount vraća broj paketa koje obrazac nosi, GetXfaPacketName daje naziv svakom od njih, a GetXfaPacketContent vraća sirove bajtove, koji su za dobro poznate pakete UTF-8 XML. Kada već znate koji paket želite, imenovani pomoćnici GetXfaTemplate, GetXfaDatasets i GetXfaConfig razrešavaju naziv umesto vas i direktno vraćaju TBytes. Za posao migracije podataka, onaj koji je zaista važan je GetXfaDatasets: on vam daje popunjene vrednosti polja kao XML dokument koji možete parsirati i mapirati u sopstvenu šemu, što je ista vrsta posla koji biste radili nakon ekstrakcije teksta iz PDF-a
procedure ExtractXfaData(Pdf: TPdf; const Dir: string);
var
I, Count: Integer;
PacketName: string;
Content, Datasets: TBytes;
begin
if not Pdf.XfaStaticReadable then
Exit; // packet reader exports not present
Count := Pdf.GetXfaPacketCount;
for I := 0 to Count - 1 do
begin
PacketName := Pdf.GetXfaPacketName(I);
Content := Pdf.GetXfaPacketContent(I); // raw UTF-8 XML
TFile.WriteAllBytes(
TPath.Combine(Dir, PacketName + '.xml'), Content);
end;
// Or jump straight to the filled-in field values by name:
Datasets := Pdf.GetXfaDatasets;
if Length(Datasets) > 0 then
ParseAndMap(Datasets);
end;
Zašto je za dinamičko renderovanje XFA potreban V8 motor?
Najpre zaključak: zato što je dinamički XFA obrazac mali program, a ne statički crtež. Paket template nosi FormCalc i JavaScript proračune, validacije i skripte događaja, a XFA motor za raspored mora da ih izvrši da bi uopšte odredio kako stranice izgledaju. PDFium implementira taj motor samo u verziji koja je kompajlirana sa XFA podrškom povrh V8, Google-ovog JavaScript motora — što je isti razlog zašto je DLL sa omogućenim V8 (pdfium32v8.dll ili pdfium64v8.dll) znatno veći od standardne verzije. Bez V8 motora nema ko da pokrene skripte, pa nema ni renderovanih stranica za prikaz, već samo sirovog XML-a koji već možete čitati statički
Izbor te verzije ima strogo ograničenje koje ne možete zaobići: to je odluka koja se donosi jednom po procesu. Jedan proces ne može da učita i standardni pdfium.dll i verziju sa omogućenim V8 — oni izvoze iste simbole, a V8 zadržava globalno stanje izolata — tako da PDFium komponenta mora da izabere V8 verziju pre nego što se biblioteka učita po prvi put. Da bi to bilo automatski, metod LoadDocument unapred skenira datoteku tražeći markere /XFA i /NeedsRendering true (ista provera koja je izložena kao samostalna funkcija PdfFileLooksXfa) i postavlja EnableV8Engine := True pre učitavanja kada ih pronađe. Kada je običan pdfium.dll već učitan u memoriju, ova promena više ne može da stupi na snagu, i to je slučaj koji prijavljuje putanja za nedostatak izvršnog okruženja
begin
// Peek for XFA before PDFium is loaded for the first time, so the
// V8-enabled build can still be selected. PdfFileLooksXfa scans for
// /XFA and /NeedsRendering true without parsing the whole file.
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) // engine is live: dynamic layout
else
ExtractXfaData(Pdf, 'C:\out'); // fall back to static packets
end;
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
Elegantno rukovanje nedostatkom XFA izvršnog okruženja
Tri provere vam govore tačno koliki nivo XFA podrške imate, i korektan kod ih proverava umesto da pretpostavlja. XfaFeaturesAvailable je globalna provera koja potvrđuje da su izvozne funkcije XFA/V8 pomoćnika prisutne u učitanom DLL-u. XfaStaticReadable potvrđuje, po dokumentu, da su čitači paketa razrešeni — što služi kao zaštita za ekstrakciju. XfaRuntimeAvailable je najjača provera: tačna je samo kada je ovaj konkretni dokument zaista aktivirao XFA motor, što znači da se radi o XFA dokumentu, da je učitana V8 verzija i da je funkcija FPDF_LoadXFA uspela. Dokument može prijaviti XFA = True, a da ipak bude XfaRuntimeAvailable = False kad god distribuirani DLL nema XFA podršku ili je standardna verzija već učitana
Kada se XFA dokument otvori ali motor ne može da se pokrene, PDFium komponenta ne rizikuje. Ona zaključava obrazac u bezbedan statički režim umesto da zahteva dinamičko okruženje koje ne može da obezbedi, i jednom podiže događaj OnXfaRuntimeMissing kako bi domaćin mogao da reaguje — obično tako što će reći korisniku da ponovo pokrene aplikaciju sa omogućenom V8 verzijom, dok u međuvremenu i dalje nudi statički čitljive podatke. Povezivanje tog rukovaoca čini razliku između čistog prelaska na rezervni režim i obrasca koji nemo ne prikazuje ništa
procedure TForm1.PdfXfaRuntimeMissing(Sender: TObject);
begin
// Fires once when an XFA document opens but the engine cannot run:
// a plain pdfium.dll was already loaded, or the build lacks 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;
Budite iskreni o ovim granicama i u sopstvenom proizvodu. Ako nikada ne isporučujete DLL sa omogućenim V8 motorom, dinamički XFA obrasci se nikada neće renderovati za vaše korisnike — ali ih i dalje možete detektovati, ekstrahovati svaki paket i izvući popunjene podatke, što je dovoljno za veliku klasu poslova koji se zapravo svode na izvlačenje podataka iz starih državnih i bankarskih obrazaca, a ne na njihovo interaktivno prikazivanje. Rezervišite težu V8 verziju za slučajeve koji zaista zahtevaju aktivan obrazac koji se može popunjavati, i prepustite proverama mogućnosti da usmere svaki dokument na putanju kojom zaista može da ide. Ako vaš proces ekstrakcije takođe rukuje ugrađenim datotekama, ista filozofija samo za čitanje važi i za PDF priloge u Delphi-ju
API-ji za FormType, XFA, GetXfaPacketCount, GetXfaPacketName, GetXfaPacketContent, GetXfaTemplate, GetXfaDatasets, GetXfaConfig i provere mogućnosti prikazani ovde su deo komponente PDFium Component za Delphi i C++Builder