Taip — PDFium palaiko XFA formas, turėdama vieną svarbų pasidalijimą. PDFium komponentas gali aptikti XFA formą bei išgauti jos template, datasets bei config paketus kaip neapdorotą XML naudodamas standartinę DLL, be jokių vykdymo laiko priklausomybių. Vykdyti dinaminę XFA formą — išdėstant jos XML aprašymą į gyvus, interaktyvius puslapius — yra atskira problema, kuriai papildomai reikalinga V8 palaikanti bibliotekos versija. Bendruomenė nuolat klausia, ar PDFium „palaiko XFA“, tarsi tai būtų viena „taip arba ne“ galimybė, ir sąžiningas atsakymas yra tas, kad XFA duomenų nuskaitymas bei XFA formos atvaizdavimas yra du labai skirtingi dalykai su labai skirtingais priklausomybių pėdsakais
Šiame straipsnyje apžvelgiamos abi dalys naudojant PDFium komponentą — vietinį VCL susiejimą aplink PDFium, skirtą Delphi bei C++Builder. Pirmiausia aptikimo bei statinio išgavimo kelias — FormType, XFA savybė bei GetXfaPacket* skaitytuvai —, tada dinaminio vykdymo laiko kelias su jo V8 apribojimu, ir galiausiai galimybių patikros, kurios leidžia grakščiai susitvarkyti su nesėkme, kai įdiegta DLL negali paleisti variklio
Ar PDFium palaiko XFA formas?
Vertinkite tai kaip dvi galimybes, o ne vieną. Statinis XFA išgavimas yra prieinamas visada: žemo lygio FPDF_GetXFAPacket* eksportai, palaikantys GetXfaPacketCount, GetXfaPacketName bei GetXfaPacketContent funkcijas, yra sukompiliuoti kiekvienoje šiuolaikinėje pdfium.dll versijoje, net ir toje, kurioje XFA yra išjungta. Būtent todėl neapdorotų template, datasets bei config XML duomenų ištraukimas iš formos neturi jokių vykdymo laiko priklausomybių — tai grynas struktūrinis nuskaitymas, nedalyvaujant jokiam varikliui. Dinaminė XFA yra kita galimybė: tikras formos vykdymas, jos skaičiavimų įvertinimas bei leidimas XFA išdėstymo varikliui ją sunumeruoti. Šis kelias egzistuoja tik PDFium versijoje, sukompiliuotoje su XFA palaikymu virš V8 JavaScript variklio, ir tai yra būtent ta dalis, dėl kurios dažniausiai ginčijamasi forumuose
Standartai šį pasidalijimą apibrėžia aiškiai. XFA apibrėžta už pagrindinės PDF gramatikos ribų Adobe XFA 3.3 (2012) specifikacijoje, o PDF 1.7 §8.6.7 aprašo, kaip XFA forma pernešama PDF viduje: formos paketai randasi po AcroForm žodyno /XFA įrašu, o dokumentas, kurį prieš naudojimą turi išdėstyti XFA procesorius, kataloge nustato /NeedsRendering true. Šie du žymekliai yra būtent tai, ko ieško PDFium komponentas, bei tai, kuo galite pasikliauti vertindami failą
Kuo XFA skiriasi nuo AcroForm?
AcroForm yra klasikinė PDF forma: valdymo elementų anotacijos (valdikliai) — tekstiniai laukai, žymimieji langeliai, radijo grupės —, prikabintos fiksuotose koordinatėse įprastuose PDF puslapiuose. Puslapis, kurį matote, yra faile esantis puslapis, o laukai randasi ant jo. XFA naudoja priešingą metodą. Interaktyvi forma yra visiškai aprašyta XML, o pilname (dinaminiame) XFA dokumente PDF puslapiai iš tikrųjų yra tik vietos rezervuaras — tikrasis turinys generuojamas atidarymo metu XFA variklio, kuris nuskaito XML šabloną bei jį išdėsto, padidėdamas arba sumažėdamas, kad tilptų duomenys. Štai ką praneša /NeedsRendering true: be XFA procesoriaus statiniai puslapiai gali būti tušti arba turėti tik užrašą „atidarykite suderinamoje skaityklėje“
Pats XFA duomenų paketas yra pavadintų paketų rinkinys, ir trys iš jų perneša viską, ko reikia daugumai integracijų. template paketas yra formos apibrėžimas — laukai, išdėstymas, skaičiavimai bei scenarijai. datasets paketas saugo tikruosius duomenis, užpildytas laukų reikšmes kaip XML medį. config paketas perneša apdorojimo instrukcijas, skirtas XFA varikliui. Yra ir kitų — localeSet, connectionSet, xdp apvalkalas —, tačiau formos auditui ar duomenų perkėlimui template bei datasets paprastai yra visas darbas. Kadangi šie duomenys yra tik XML sraute, jų nuskaitymui niekada nereikia nieko vykdyti; tai ir yra pagrindinė priežastis, kodėl statinis išgavimas neturi jokių priklausomybių
XFA formos aptikimas Delphi aplinkoje
Aptikimas prasideda taip, kaip ir kiekviena PDFium komponento užduotis: nustatykite FileName, perjunkite Active := True bei patikrinkite, ar jis tikrai atsidarė. Active := True niekada nesukelia klaidų — trūkstamas failas, neteisingas slaptažodis ar nesanti DLL tiesiog palieka Active reikšmę False —, todėl ši patikra yra privaloma. Atidarius dokumentą, FormType praneša, kurį formos modelį jis naudoja. Dvi iš TPdfFormType reikšmių yra XFA: ftXfaFull yra pilna, dinaminė XFA forma, kuriai reikalingas atvaizdavimas, o ftXfaForeground is XFAF — priekinio plano poaibis, kur XFA turinys nupiešiamas ant įprastų AcroForm puslapių, kad statiniai puslapiai išlaikytų prasmę. Loginė reikšmė XFA yra trumpinys, reiškiantis „tai yra bet kurio tipo XFA dokumentas“. Jei taip pat dirbate su valdiklių lygio laukais XFAF ar AcroForm dokumentuose, mechanika aprašyta straipsnyje PDFium formos laukų naršymo vadovas
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;
XFA paketų išgavimas kaip neapdoroto XML
Išgavimas yra vertingoji pusė, neturinti jokių priklausomybių. Apsaugokite patikrinimu XfaStaticReadable — jis patvirtina, kad paketo skaitytuvo eksportai yra išspręsti įkeltoje DLL —, tada pereikite paketus pagal indeksą. GetXfaPacketCount grąžina paketų skaičių formoje, GetXfaPacketName suteikia kiekvienam pavadinimą, o GetXfaPacketContent grąžina neapdorotus baitus, kurie žinomiems paketams yra UTF-8 XML. Kai jau žinote, kurio paketo norite, pavadintos pagalbinės funkcijos GetXfaTemplate, GetXfaDatasets bei GetXfaConfig suranda pavadinimą už jus bei grąžina TBytes tiesiogiai. Duomenų migravimo darbui svarbiausias yra GetXfaDatasets: jis pateikia pateiktas laukų reikšmes kaip XML dokumentą, kurį galite analizuoti bei susieti su savo schema; tai panašus darbas, kokį darytumėte po teksto išgavimo iš 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;
Kodėl dinaminiam XFA atvaizdavimui reikalingas V8 variklis?
Išvada pirmiausia: todėl, kad dinaminė XFA forma yra maža programa, o ne statinis piešinys. Šablono pakete yra FormCalc bei JavaScript skaičiavimai, patvirtinimai bei įvykių scenarijai, o XFA išdėstymo variklis turi juos įvykdyti, kad nuspręstų, kaip puslapiai išvis atrodo. PDFium įdiegia šį variklį tik toje versijoje, kuri sukompiliuota su XFA palaikymu virš V8 — „Google“ JavaScript variklio; tai yra ta pati priežastis, kokybės sumetimais, kodėl V8 palaikanti DLL (pdfium32v8.dll arba pdfium64v8.dll) yra žymiai didesnė už standartinę versiją. Be V8 nėra kam vykdyti scenarijų, todėl nėra rodomų atvaizduotų puslapių, tik neapdorotas XML, kurį jau galite nuskaityti statiškai
Šios versijos pasirinkimas turi griežtą apribojimą, kurio negalite apeiti: tai yra vienkartinis sprendimas vienam procesui. Vienas procesas negali įkelti tiek standartinės pdfium.dll, tiek V8 palaikančios versijos — jos eksportuoja tuos pačius simbolius, o V8 išlaiko globalią izoliato būseną —, todėl PDFium komponentas turi pasirinkti V8 versiją prieš įkeliant biblioteką pačiu pirmasis kartas. Kad tai būtų atliekama automatiškai, LoadDocument iš anksto patikrina failą, ieškodama /XFA bei /NeedsRendering true žymeklių (tas pats tikrinimas pateikiamas kaip savarankiška PdfFileLooksXfa funkcija) bei prieš įkėlimą nustato EnableV8Engine := True, jei juos randa. Kai įprasta pdfium.dll jau yra atmintyje, jungiklis nebegali suveikti, ir apie šį atvejį praneša vykdymo laiko trūkumo kelias
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;
Grakštus trūkstamos XFA vykdymo aplinkos apdorojimas
Trys patikros praneša, kokį XFA palaikymą turite, ir teisingas kodas jas tikrina, užuot spėliojęs. XfaFeaturesAvailable yra globalus patikrinimas, ar įkeltoje DLL yra XFA/V8 pagalbiniai eksportai. XfaStaticReadable patvirtina konkrečiam dokumentui, kad paketų skaitytuvai yra išspręsti — tai apsauga išgavimui. XfaRuntimeAvailable yra stiprioji patikra: ji yra True tik tada, kai šis konkretus dokumentas iš tikrųjų aktyvavo XFA variklį, t. y. jis yra XFA, įkelta V8 versija bei FPDF_LoadXFA pavyko. Dokumentas gali pranešti XFA = True, tačiau XfaRuntimeAvailable = False, kai įdiegtoje DLL trūksta XFA arba standartinė versija jau buvo įkelta
Kai XFA dokumentas atsidaro, tačiau variklis negali veikti, PDFium komponentas nerizikuoja. Jis pririša formą prie saugaus statinio režimo, užuot reikalavęs dinaminio vykdymo laiko, kurio negali suteikti, bei vieną kartą suaktyvina OnXfaRuntimeMissing įvykį, kad programa galėtų sureaguoti — paprastai pranešdama vartotojui apie būtinybę perkrauti programą su V8 versija, tuo pat metu leisdama pasiekti statiškai skaitomus duomenis. Šio įvykio apdorojimas užkerta kelią situacijai, kai forma byla tyliai nieko nerodo
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;
Būkite sąžingi dėl šių ribų ir savo pačių kuriamoje programoje. Jei niekada neplatinsite V8 palaikančios DLL, dinaminės XFA formos jūsų vartotojams niekada nebus atvaizduojamos — tačiau vis tiek galite jas aptikti, išgauti kiekvieną paketą bei ištraukti pateiktus duomenis; to pakanka didelei užduočių daliai, kai reikia tiesiog ištraukti duomenis iš senų vyriausybės bei bankų formų, užuot jas rodžius interaktyviai. Pasilikite sunkesnę V8 versiją tais atvejais, kai tikrai reikia gyvos, pildomos formos, ir leiskite galimybių patikroms nukreipti kiekvieną dokumentą jam tinkamu keliu. Jei jūsų išgavimo konvejeris taip pat apdoroja įterptuosius failus, tas pats skaitomas principas galioja ir straipsnyje PDF priedai Delphi aplinkoje
Čia parodyti FormType, XFA, GetXfaPacketCount, GetXfaPacketName, GetXfaPacketContent, GetXfaTemplate, GetXfaDatasets, GetXfaConfig bei galimybių patikrų API platinami kaip PDFium komponento, skirto Delphi ir C++Builder, dalis