Áno — PDFium podporuje formuláre XFA, avšak s jedným dôležitým rozdelením. Komponent PDFium Component dokáže detegovať XFA formulár a extrahovať jeho pakety template (šablóna), datasets (dáta) a config (konfigurácia) ako čisté XML pomocou štandardnej DLL, a to s nulovými závislosťami v čase behu. Spustenie dynamického formulára XFA — rozloženie jeho XML popisu do živých, interaktívnych stránok — je samostatným problémom, ktorý navyše vyžaduje build s povoleným engine V8. Komunita sa neustále pýta, či PDFium „podporuje XFA“, akoby to bola jedna funkcia typu áno/nie, a úprimná odpoveď znie, že čítanie XFA dát a vykresľovanie XFA formulára sú dve veľmi odlišné veci s veľmi odlišnou sadou závislostí
Tento článok prechádza oboma polovicami s komponentom PDFium Component, čo je natívny VCL obal (wrapper) okolo PDFium pre Delphi a C++Builder. Najprv si ukážeme cestu detekcie a statickej extrakcie — FormType, vlastnosť XFA a čítačky GetXfaPacket* —, potom dynamickú behová cestu s jej obmedzením na V8, a nakoniec testy schopností (capability probes), ktoré vám umožnia elegantne zlyhať, keď nasadená DLL nedokáže spustiť tento engine
Podporuje PDFium formuláre XFA?
Chápte to ako dve schopnosti, nie jednu. Statická extrakcia XFA je k dispozícii vždy: nízkoúrovňové exportované funkcie FPDF_GetXFAPacket*, ktoré stoja za GetXfaPacketCount, GetXfaPacketName a GetXfaPacketContent, sú zakompilované v každej rozumne modernej knižnici pdfium.dll, dokonca aj v builde s vypnutou podporou XFA. To je dôvod, prečo vytiahnutie surového XML template, datasets a config z formulára nenesie vôbec žiadne závislosti pri behu — ide o čisto štrukturálne čítanie bez zapojenia enginu. Dynamické XFA je druhou schopnosťou: skutočné spustenie formulára, vyhodnocovanie jeho výpočtov a prenechanie stránkovania rozvrhovému enginu XFA. Táto cesta existuje len v builde PDFium kompilovanom s podporou XFA nad JavaScript engineom V8 a je to presne tá časť, o ktorej sa väčšina vlákien s otázkou „podporuje PDFium XFA“ skutočne sporí
Ako sa XFA líši od AcroFormu?
AcroForm je klasický PDF formulár: ovládacie prvky (widgets) — textové polia, začiarkavacie políčka, prepínače —, ktoré sú ukotvené na pevných súradniciach na bežných stránkach PDF. Stránka, ktorú vidíte, je stránkou v súbore a polia sedia na nej. XFA volí opačný prístup. Interaktívny formulár je kompletne popísaný v XML a v plnom (dynamickom) dokumente XFA sú PDF stránky v podstate len zástupným symbolom — skutočný obsah sa generuje pri otvorení XFA engineom, ktorý prečíta šablónu XML a rozloží ju, pričom sa zväčšuje alebo zmenšuje podľa dát. To je to, čo oznamuje príznak /NeedsRendering true: bez procesora XFA môžu byť statické stránky prázdne alebo obsahovať iba upozornenie „otvorte v kompatibilnom prehliadači“
Samotný obsah XFA je sada pomenovaných paketov a tri z nich obsahujú všetko, čo väčšina integrácií potrebuje. Paket template (šablóna) je definícia formulára — polia, rozloženie, výpočty a skripty. Paket datasets (dáta) obsahuje skutočné inštančné dáta, vyplnené hodnoty polí ako XML strom. Paket config (konfigurácia) nesie pokyny na spracovanie pre XFA engine. Existujú aj ďalšie — localeSet, connectionSet, obal xdp —, ale pre audit formulára alebo prenos dát z neho je šablóna plus dáta zvyčajne celou prácou. Keďže tieto dáta sú len XML prúd (stream), ich čítanie nikdy nevyžaduje spúšťanie ničoho, čo je základným dôvodom, prečo je statická extrakcia bez závislostí
Detekcia XFA formulára v Delphi
Detekcia začína tak, ako každá úloha s komponentom PDFium Component: nastavíte FileName, prepnete Active := True a skontrolujete, či sa skutočne otvoril. Active := True never raises — a missing file, wrong password, or absent DLL just leaves Active at False — so the guard is mandatory. Po otvorení dokumentu vám FormType povie, ktorý model formulára sa používa. Dve z hodnôt TPdfFormType sú XFA: ftXfaFull je plný, dynamický formulár XFA, ktorý vyžaduje vykreslenie, zatiaľ čo ftXfaForeground is XFAF, the foreground subset where XFA content is drawn over otherwise-normal AcroForm pages so the static pages remain meaningful. Boolean hodnota XFA je skratkou pre „toto je dokument XFA jedného z oboch typov“. Ak pracujete aj s poľami na úrovni widgetov v dokumente XFAF alebo AcroForm, mechanika je opísaná v sprievodcovi navigáciou po formulárových poliach v PDFium
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;
Extrahovanie XFA paketov ako surového XML
Extrakcia je tá hodnotnejšia polovica bez závislostí. Zabezpečte ju pomocou XfaStaticReadable — to potvrdí, že exporty čítačky paketov boli v načítanej DLL vyriešené —, potom prechádzajte pakety podľa indexu. GetXfaPacketCount vracia počet paketov, ktoré formulár nesie, GetXfaPacketName vráti názov každého z nich a GetXfaPacketContent vráti surové bajty, čo sú v prípade známych paketov UTF-8 XML. Ak už viete, ktorý paket chcete, pomenovaní pomocníci GetXfaTemplate, GetXfaDatasets a GetXfaConfig vyriešia názov za vás a vrátia priamo TBytes. Pri migrácii dát je dôležitý najmä GetXfaDatasets: poskytne vám vyplnené hodnoty polí ako XML dokument, ktorý môžete parsovať a namapovať do vlastnej schémy, čo je rovnaký typ následnej práce, akú by ste robili po extrahovaní textu z PDF
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;
Prečo dynamické vykresľovanie XFA vyžaduje engine V8?
Záver najprv: pretože dynamický formulár XFA je malý program a nie statický nákres. Paket šablóny (template) nesie výpočty, validácie a skripty udalostí vo FormCalc a JavaScripte a rozvrhový engine XFA ich musí vykonať, aby vôbec rozhodol, ako budú stránky vyzerať. PDFium implementuje tento engine len v builde skompilovanom s podporou XFA nad V8, JavaScript engineom od Google — to je dôvod, prečo je DLL s podporou V8 (pdfium32v8.dll or pdfium64v8.dll) výrazne väčšia ako štandardný build. Bez V8 nemá skripty čo spustiť, takže neexistujú žiadne vykreslené stránky, ktoré by sa dali zobraziť, iba surové XML, ktoré si už môžete prečítať staticky
Výber tohto buildu má prísne obmedzenie, ktoré sa nedá obísť: je to jednorazové rozhodnutie pre celý proces. Jeden proces nemôže načítať štandardnú pdfium.dll aj build s podporou V8 — exportujú rovnaké symboly a V8 udržiava globálny stav izolátu —, takže PDFium Component musí vybrať build V8 predtým, než sa knižnica načíta úplne prvýkrát. Aby to bolo automatické, LoadDocument vopred naskenuje súbor a hľadá značky /XFA a /NeedsRendering true (rovnaká kontrola, akú poskytuje samostatná funkcia PdfFileLooksXfa) a pri ich nájdení nastaví pred načítaním EnableV8Engine := True. Keď je už načítaná obyčajná pdfium.dll, prepnutie už nemôže nastať a tento prípad hlási chyba chýbajúceho behového prostredia
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;
Elegantné spracovanie chýbajúceho runtime XFA
Tri testy vám presne povedia, aké schopnosti XFA máte, a poctivý kód ich kontroluje namiesto predpokladov. XfaFeaturesAvailable je globálna kontrola, či sú pomocné exporty XFA/V8 prítomné v načítanej DLL. XfaStaticReadable potvrdzuje pre konkrétny dokument, že čítačky paketov boli úspešne naviazané — čo je poistka pre extrakciu. XfaRuntimeAvailable je ten najdôležitejší: je true iba vtedy, keď tento konkrétny dokument skutočne aktivoval engine XFA, čo znamená, že ide o XFA, načítal sa build V8 a volanie FPDF_LoadXFA uspelo. Dokument môže hlásiť XFA = True, ale XfaRuntimeAvailable = False vždy, keď nasadená DLL nepodporuje XFA alebo bola predtým načítaná štandardná DLL
Keď sa dokument XFA otvorí, ale engine nemôže bežať, komponent PDFium neriskuje. Ukotví formulár do bezpečného statického režimu namiesto vyžadovania dynamického behu, ktorý nemôže splniť, a raz vyvolá udalosť OnXfaRuntimeMissing, aby hostiteľská aplikácia mohla reagovať — zvyčajne informovaním používateľa, aby reštartoval aplikáciu s povoleným buildom V8, pričom medzitým stále ponúka staticky čitateľné dáta. Prepojenie tejto obsluhy je rozdielom medzi čistým náhradným riešením a formulárom, ktorý potichu nič nezobrazí
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;
Buďte úprimní ohľadom týchto hraníc aj vo vašom vlastnom produkte. Ak nikdy nedodáte DLL s povoleným V8, dynamické formuláre XFA sa vašim používateľom nikdy nevykreslia — stále ich však môžete detegovať, extrahovať každý paket a vytiahnuť odoslané dáta, čo stačí pre veľkú skupinu úloh, ktoré sú naozaj o získavaní dát zo starších vládnych a bankových formulárov a nie o ich interaktívnom prezentovaní. Vyhraďte si zložitejší build V8 pre prípady, ktoré skutočne potrebujú živý, vyplniteľný formulár, a nechajte testy schopností nasmerovať každý dokument na cestu, ktorú môže reálne podniknúť. Ak váš proces extrakcie spracováva aj vložené súbory, rovnaká filozofia len na čítanie platí aj pre prílohy PDF v Delphi
Rozhrania API pre FormType, XFA, GetXfaPacketCount, GetXfaPacketName, GetXfaPacketContent, GetXfaTemplate, GetXfaDatasets, GetXfaConfig a testy schopností (capability probes) zobrazené v tomto článku sú súčasťou komponentu PDFium Component pre Delphi a C++Builder