Sì — PDFium supporta i moduli XFA, con un'importante distinzione. Il componente PDFium può rilevare un modulo XFA ed estrarre i suoi pacchetti template, datasets e config come XML grezzo utilizzando la DLL standard, con zero dipendenze a runtime. L'esecuzione di un modulo XFA dinamico — ovvero la disposizione della sua descrizione XML in pagine attive e interattive — è un problema separato che richiede inoltre la build abilitata per V8. La community continua a chiedere se PDFium "supporta XFA" come se si trattasse di una funzionalità con risposta sì/no, e la risposta onesta è che la lettura dei dati XFA e il rendering di un modulo XFA sono due cose molto diverse con footprint di dipendenza completamente differenti
Questo articolo esamina entrambe le parti con PDFium Component, the wrapper VCL nativo basato su PDFium per Delphi e C++Builder. In primo luogo, il percorso di rilevamento ed estrazione statica — FormType, la proprietà XFA e i lettori GetXfaPacket* —, poi il percorso a runtime dinamico con il relativo vincolo V8 e infine i controlli delle funzionalità che consentono di gestire gli errori in modo controllato quando una DLL distribuita non può eseguire il motore
PDFium supporta i moduli XFA?
Considerala come due funzionalità separate, non come una sola. L'estrazione statica XFA è sempre disponibile: le esportazioni di basso livello FPDF_GetXFAPacket* che supportano GetXfaPacketCount, GetXfaPacketName e GetXfaPacketContent sono compilate in ogni versione di pdfium.dll ragionevolmente recente, anche nelle build con XFA disattivato. Questo è il motivo per cui l'estrazione dell'XML grezzo dei pacchetti template, datasets e config da un modulo non comporta alcuna dipendenza a runtime — si tratta di una lettura puramente strutturale, senza l'intervento del motore. L'XFA dinamico è l'altra funzionalità: l'esecuzione vera e propria del modulo, la valutazione dei suoi calcoli e la sua impaginazione da parte del motore di layout XFA. Questo percorso esiste solo in una build di PDFium compilata con il supporto XFA basato sul motore JavaScript V8, ed è proprio la parte su cui si concentrano la maggior parte delle discussioni relative al supporto XFA di PDFium
Gli classifica definiscono chiaramente la suddivisione. L'XFA è definito al di fuori della grammatica PDF principale in Adobe XFA 3.3 (2012), e la specifica PDF 1.7 §8.6.7 descrive come un modulo XFA viene trasportato all'interno di un PDF: i pacchetti del modulo risiedono sotto la voce /XFA del dizionario AcroForm, e un documento che deve essere impaginato da un processore XFA prima di assumere significato imposta /NeedsRendering true nel catalogo. Questi due indicatori sono esattamente ciò che il componente PDFium cerca, e su cui puoi fare affidamento quando analizzi un file sconosciuto
In che modo l'XFA differisce da un AcroForm?
Un AcroForm è il classico modulo PDF: annotazioni widget — campi di testo, caselle di controllo, pulsanti di scelta — ancorati a coordinate fisse su normali pagine PDF. La pagina visualizzata è la pagina presente nel file e i campi si trovano su di essa. L'XFA adotta l'approccio opposto. Il modulo interattivo è descritto interamente in XML e, in un documento XFA completo (dinamico), le pagine PDF sono di fatto un segnaposto: il contenuto reale viene generato al momento dell'apertura da un motore XFA che legge il modello XML e lo distribuisce, espandendosi o riducendosi in base ai dati. Questo è ciò che comunica /NeedsRendering true: senza un processore XFA, le pagine statiche potrebbero essere vuote o mostrare solo un avviso che invita ad aprire il file in un visualizzatore compatibile
Il contenuto XFA stesso è un insieme di pacchetti con nome, e tre di essi contengono tutto ciò di cui ha bisogno la maggior parte delle integrazioni. Il pacchetto template è la definizione del modulo — campi, layout, calcoli e script. Il pacchetto datasets contiene i dati effettivi dell'istanza, ovvero i valori dei campi compilati sotto forma di albero XML. Il pacchetto config contiene le istruzioni di elaborazione per il motore XFA. Ne esistono altri — localeSet, connectionSet, il wrapper xdp —, ma per analizzare un modulo o migrare i dati da esso, la combinazione di template e datasets rappresenta solitamente l'intero lavoro. Poiché questi dati sono semplicemente XML inserito in un flusso, la loro lettura non richiede l'esecuzione di codice, che è il motivo principale per cui l'estrazione statica è priva di dipendenze
Rilevare un modulo XFA in Delphi
Il rilevamento inizia come ogni attività di PDFium Component: si imposta FileName, si abilita Active := True e si verifica che il file si sia effettivamente aperto. L'abilitazione di Active := True non genera mai eccezioni — un file danneggiato, crittografato o DLL mancante lasciano semplicemente Active impostato su False —, quindi questo controllo è obbligatorio. Una volta aperto il documento, FormType indica quale modello di modulo utilizza. Due dei valori TPdfFormType fanno riferimento all'XFA: ftXfaFull indica un modulo XFA completo e dinamico, del tipo che richiede il rendering, mentre ftXfaForeground indica l'XFAF, il sottoinsieme in primo piano in cui il contenuto XFA viene disegnato su pagine AcroForm altrimenti normali, consentendo alle pagine statiche di rimanere leggibili. Il valore booleano XFA è una scorciatoia per "questo è un documento XFA di uno dei due tipi". Se lavori anche con i campi a livello di widget su un documento XFAF o AcroForm, i meccanismi sono trattati nella guida alla navigazione nei campi modulo di PDFium
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'application-form.pdf';
Pdf.Active := True;
if not Pdf.Active then
Exit; // file danneggiato, crittografato o DLL mancante
case Pdf.FormType of
ftXfaFull:
Log('Modulo XFA completo (dinamico)');
ftXfaForeground:
Log('XFAF: XFA sovrapposto a pagine AcroForm statiche');
ftAcroForm:
Log('AcroForm classico');
else
Log('Nessun modulo interattivo');
end;
if Pdf.XFA then
Log('Il documento contiene pacchetti di dati XFA');
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
Estrarre i pacchetti XFA come XML grezzo
L'estrazione rappresenta la parte più importante e a zero dipendenze. Esegui il controllo con XfaStaticReadable — che conferma che le esportazioni del lettore di pacchetti sono state risolte nella DLL caricata —, quindi scorri i pacchetti per indice. GetXfaPacketCount restituisce quanti pacchetti contiene il modulo, GetXfaPacketName assegna a ciascuno il relativo nome e GetXfaPacketContent restituisce i byte grezzi, che per i pacchetti noti corrispondono a XML in formato UTF-8. Quando conosci già il pacchetto desiderato, gli helper dedicati GetXfaTemplate, GetXfaDatasets e GetXfaConfig risolvono il nome per te restituendo direttamente l'oggetto TBytes. Per un'attività di migrazione dei dati, quello fondamentale è GetXfaDatasets: ti fornisce i valori dei campi inseriti come documento XML che puoi analizzare e mappare sul tuo schema, lo stesso tipo di lavoro successivo che faresti dopo aver estratto il testo da un PDF
procedure ExtractXfaData(Pdf: TPdf; const Dir: string);
var
I, Count: Integer;
PacketName: string;
Content, Datasets: TBytes;
begin
if not Pdf.XfaStaticReadable then
Exit; // esportazioni del lettore di pacchetti non presenti
Count := Pdf.GetXfaPacketCount;
for I := 0 to Count - 1 do
begin
PacketName := Pdf.GetXfaPacketName(I);
Content := Pdf.GetXfaPacketContent(I); // XML UTF-8 grezzo
TFile.WriteAllBytes(
TPath.Combine(Dir, PacketName + '.xml'), Content);
end;
// O accedi direttamente ai valori dei campi compilati tramite nome:
Datasets := Pdf.GetXfaDatasets;
if Length(Datasets) > 0 then
ParseAndMap(Datasets);
end;
Perché il rendering dinamico XFA richiede il motore V8?
La conclusione prima di tutto: perché un modulo XFA dinamico è un piccolo programma, non un disegno statico. Il pacchetto template contiene calcoli FormCalc e JavaScript, convalide e script di evento, e il motore di layout XFA deve eseguirli per determinare l'aspetto effettivo delle pagine. PDFium implementa tale motore solo in una build compilata con il supporto XFA basato su V8, il motore JavaScript di Google — lo stesso motivo per cui la DLL abilitata per V8 (pdfium32v8.dll o pdfium64v8.dll) è notevolmente più grande rispetto alla build standard. Senza V8, non vi è alcun componente in grado di eseguire gli script, pertanto non vi saranno pagine renderizzate da mostrare, ma solo l'XML grezzo che puoi comunque leggere in modo statico
La scelta di quella build comporta un vincolo rigido che non può essere aggirato: si tratta di una decisione da prendere una sola volta per processo. Un singolo processo non può caricare contemporaneamente sia la pdfium.dll standard sia la build abilitata per V8 — in quanto esportano gli stessi simboli e V8 mantiene lo stato globale dell'isolamento —, quindi il componente PDFium deve scegliere la build V8 prima che la libreria venga caricata per la prima volta. Per rendere automatica questa operazione, LoadDocument esegue una scansione preliminare del file alla ricerca degli indicatori /XFA e /NeedsRendering true (lo stesso controllo esposto come funzione standalone PdfFileLooksXfa) e imposta EnableV8Engine := True prima del caricamento se li trova. Se una normale versione di pdfium.dll è già residente, l'interruttore non può più avere effetto, e questo scenario è ciò che viene segnalato dal percorso relativo alla mancanza del runtime
begin
// Controlla la presenza di XFA prima che PDFium venga caricato per la prima volta, in modo
// da poter comunque selezionare la build abilitata per V8. PdfFileLooksXfa scansiona
// /XFA e /NeedsRendering true senza analizzare l'intero 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) // il motore è attivo: layout dinamico
else
ExtractXfaData(Pdf, 'C:\out'); // ripiega sui pacchetti statici
end;
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
Gestire la mancanza del runtime XFA in modo controllato
Tre verifiche indicano esattamente il livello di funzionalità XFA disponibile, e un codice affidabile le controlla anziché fare supposizioni. XfaFeaturesAvailable è un controllo globale sulla presenza delle esportazioni degli helper XFA/V8 nella DLL caricata. XfaStaticReadable conferma, per documento, che i lettori dei pacchetti sono stati risolti — la protezione per l'estrazione. XfaRuntimeAvailable è la verifica più importante: è vera solo quando questo specifico documento ha effettivamente attivato il motore XFA, il che significa che il documento è XFA, la build V8 è stata caricata e FPDF_LoadXFA ha avuto successo. Un documento può segnalare XFA = True ma XfaRuntimeAvailable = False qualora la DLL distribuita sia priva del supporto XFA o la build standard sia già stata caricata
Quando si apre un documento XFA ma il motore non può essere eseguito, PDFium Component non corre rischi. Blocca il modulo sulla modalità statica sicura anziché richiedere un runtime dinamico che non può onorare, e solleva l'evento OnXfaRuntimeMissing una volta in modo che l'host possa reagire — solitamente informando l'utente di riavviare con la build abilitata per V8, offrendo comunque nel frattempo i dati leggibili staticamente. Configurare questo gestore fa la differenza tra un ripiego pulito e un modulo che non mostra nulla silenziosamente
procedure TForm1.PdfXfaRuntimeMissing(Sender: TObject);
begin
// Si attiva una volta quando si apre un documento XFA ma il motore non può essere eseguito:
// una normale versione di pdfium.dll era già caricata, o la build è priva di 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;
Sii onesto riguardo a questo confine anche all'interno del tuo prodotto. Se non distribuisci mai la DLL abilitata per V8, i moduli XFA dinamici non verranno mai renderizzati per i tuoi utenti — ma potrai comunque rilevarli, estrarre ogni pacchetto e ricavare i dati inviati, operazione sufficiente per gran parte delle attività incentrate sul recupero di dati da vecchi moduli governativi e bancari anziché sulla loro presentazione interattiva. Riserva la build V8 più pesante ai casi che richiedono effettivamente un modulo compilabile attivo, e lascia che le verifiche delle funzionalità instradino ciascun documento sul percorso che può effettivamente compiere. Se la tua pipeline di estrazione gestisce anche i file incorporati, la stessa filosofia di sola lettura si applica agli allegati PDF in Delphi
Le API FormType, XFA, GetXfaPacketCount, GetXfaPacketName, GetXfaPacketContent, GetXfaTemplate, GetXfaDatasets, GetXfaConfig e le verifiche delle funzionalità mostrate qui fanno parte di PDFium Component per Delphi e C++Builder