Sim — o PDFium suporta formulários XFA, com uma divisão importante. O PDFium Component pode detetar um formulário XFA e extrair os seus pacotes template, datasets e config como XML em bruto utilizando a DLL padrão, com dependência de runtime zero. Executar um formulário XFA dinâmico — estruturando a sua descrição XML em páginas ativas e interativas — é um problema separado que necessita adicionalmente da compilação com suporte para V8. A comunidade continua a perguntar se o PDFium "suporta XFA" como se fosse uma capacidade única de sim ou não, e a resposta sincera é que ler dados XFA e renderizar um formulário XFA são duas coisas muito diferentes, com duas pegadas de dependência (dependency footprints) muito distintas
Este artigo aborda ambas as metades com o PDFium Component, a biblioteca VCL nativa que envolve o PDFium para Delphi e C++Builder. Primeiro, o caminho de deteção e extração estática — FormType, a propriedade XFA e os leitores GetXfaPacket* —, depois o caminho de execução (runtime) dinâmico com a sua limitação do V8 e, finalmente, as verificações de capacidade (capability probes) que lhe permitem falhar de forma controlada quando uma DLL implementada não consegue executar o motor
O PDFium suporta formulários XFA?
A extração estática de XFA está sempre disponível: as exportações de baixo nível FPDF_GetXFAPacket* que dão suporte a GetXfaPacketCount, GetXfaPacketName e GetXfaPacketContent estão compiladas em todas as pdfium.dll razoavelmente modernas, mesmo numa compilação com XFA desativado. É por isso que extrair o XML em bruto de template, datasets e config de um formulário não acarreta qualquer dependência de execução (runtime) — trata-se de uma leitura puramente estrutural, sem motor envolvido. O XFA dinâmico é a outra capacidade: executar efetivamente o formulário, avaliar os seus cálculos e permitir que o motor de layout do XFA o pagine. Esse caminho só existe numa compilação do PDFium criada com suporte a XFA sobre o motor de JavaScript V8, e é a parte sobre a qual a maioria das discussões sobre "se o PDFium suporta XFA" realmente debate
As normas estruturam a divisão de forma clara. O XFA está definido fora da gramática principal do PDF no Adobe XFA 3.3 (2012), e a norma PDF 1.7 §8.6.7 descreve como um formulário XFA é transportado dentro de um PDF: os pacotes do formulário residem sob a entrada /XFA do dicionário AcroForm, e um documento que deve ser estruturado por um processador XFA antes de ter qualquer significado define /NeedsRendering true no catálogo. Esses dois marcadores são exatamente o que o PDFium Component procura, e exatamente aquilo em que pode confiar ao analisar um ficheiro que nunca viu antes
Como o XFA se diferencia de um AcroForm?
Um AcroForm é o formulário PDF clássico: anotações de controlos (widgets) — campos de texto, caixas de verificação, grupos de botões de rádio — ancoradas em coordenadas fixas em páginas normais de PDF. A página que visualiza é a página presente no ficheiro, e os campos situam-se sobre ela. O XFA adota a abordagem oposta. O formulário interativo é descrito inteiramente em XML e, num documento XFA completo (dinâmico), as páginas PDF funcionam essencialmente como um marcador de posição (placeholder) — o conteúdo real é gerado no momento da abertura por um motor XFA que lê o template XML e o desenha, expandindo ou encolhendo para se ajustar aos dados. É isso que a instrução /NeedsRendering true anuncia: sem um processador XFA, as páginas estáticas podem estar em branco ou apresentar apenas um aviso de "abra num visualizador compatível"
A própria carga útil (payload) do XFA é um conjunto de pacotes nomeados, e três deles contêm tudo o que a maioria das integrações necessita. O pacote template é a definição do formulário — campos, layout, cálculos e scripts. O pacote datasets contém os dados reais da instância, os valores dos campos preenchidos como uma árvore XML. O pacote config transporta instruções de processamento para o motor XFA. Existem outros — localeSet, connectionSet, o invólucro (wrapper) xdp —, mas para auditar um formulário ou extrair os seus dados, o template juntamente com os datasets costumam constituir a tarefa completa. Como esses dados são apenas XML num stream, a sua leitura nunca requer a execução de código, o que é a razão fundamental pela qual a extração estática não tem dependências
Detetar um formulário XFA no Delphi
A deteção começa como qualquer tarefa do PDFium Component: defina a FileName, altere para Active := True e verifique se o ficheiro foi realmente aberto. Active := True nunca gera exceções — um ficheiro em falta, palavra-passe incorreta ou DLL ausente apenas mantém a propriedade Active como False —, pelo que a verificação de segurança é obrigatória. Assim que o documento estiver aberto, a FormType indica qual o modelo de formulário em utilização. Dois dos valores de TPdfFormType correspondem a XFA: o ftXfaFull é um formulário XFA dinâmico completo, do tipo que requer renderização, enquanto o ftXfaForeground é o XFAF, o subconjunto de primeiro plano (foreground) onde o conteúdo XFA is desenhado sobre páginas AcroForm normais para que as páginas estáticas continuem a ter significado. O booleano XFA é um atalho para "este é um documento XFA de qualquer um dos tipos". Se também trabalhar com os campos ao nível de controlos (widgets) num documento XFAF ou AcroForm, o funcionamento é abordado no guia de navegação de campos de formulário no PDFium
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'application-form.pdf';
Pdf.Active := True;
if not Pdf.Active then
Exit; // danificado, encriptado ou DLL em falta
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;
Extrair pacotes XFA como XML em bruto
A extração é a metade de alto valor e sem dependências. Proteja a operação com XfaStaticReadable — que confirma se as exportações do leitor de pacotes foram resolvidas na DLL carregada — e percorra os pacotes por índice. O GetXfaPacketCount devolve quantos pacotes o formulário contém, o GetXfaPacketName atribui o nome a cada um e o GetXfaPacketContent devolve os bytes em bruto, que nos pacotes conhecidos são XML em UTF-8. Quando já sabe qual o pacote que deseja, os auxiliares nomeados GetXfaTemplate, GetXfaDatasets e GetXfaConfig resolvem o nome e devolvem um TBytes diretamente. Para uma tarefa de migração de dados, o pacote relevante é o GetXfaDatasets: fornece os valores dos campos submetidos como um documento XML que pode analisar e mapear para o seu próprio esquema, o mesmo tipo de trabalho posterior que faria após extrair texto de um PDF
procedure ExtractXfaData(Pdf: TPdf; const Dir: string);
var
I, Count: Integer;
PacketName: string;
Content, Datasets: TBytes;
begin
if not Pdf.XfaStaticReadable then
Exit; // exportações do leitor de pacotes não estão presentes
Count := Pdf.GetXfaPacketCount;
for I := 0 to Count - 1 do
begin
PacketName := Pdf.GetXfaPacketName(I);
Content := Pdf.GetXfaPacketContent(I); // XML em bruto em UTF-8
TFile.WriteAllBytes(
TPath.Combine(Dir, PacketName + '.xml'), Content);
end;
// Ou avance diretamente para os valores dos campos preenchidos por nome:
Datasets := Pdf.GetXfaDatasets;
if Length(Datasets) > 0 then
ParseAndMap(Datasets);
end;
Por que a renderização dinâmica de XFA necessita do motor V8?
Conclusão primeiro: porque um formulário XFA dinâmico é um pequeno programa, e não um desenho estático. O pacote template transporta cálculos, validações e scripts de eventos em FormCalc e JavaScript, e o motor de layout XFA tem de os executar para decidir qual a aparência das páginas. O PDFium implementa esse motor apenas numa compilação criada com suporte a XFA sobre o V8, o motor de JavaScript da Google — a mesma razão pela qual a DLL com suporte a V8 (pdfium32v8.dll ou pdfium64v8.dll) é visivelmente maior do que a compilação padrão. Sem o V8, não há forma de executar os scripts, pelo que não existem páginas renderizadas para mostrar, apenas o XML em bruto que já pode ler estaticamente
A seleção dessa compilação tem uma restrição rígida que não pode contornar: trata-se de uma decisão única por processo. Um único processo não pode carregar a pdfium.dll padrão e a compilação compatível com V8 ao mesmo tempo — ambas exportam os mesmos símbolos e o V8 mantém o estado global do isolate —, pelo que o PDFium Component deve escolher a versão com V8 antes de a biblioteca ser carregada pela primeira vez. Para tornar isso automático, a LoadDocument pré-examina o ficheiro em busca dos marcadores /XFA e /NeedsRendering true (a mesma verificação exposta como a função independente PdfFileLooksXfa) e define EnableV8Engine := True antes do carregamento quando os encontra. Assim que uma pdfium.dll padrão já se encontra residente na memória, a alteração deixa de poder ter efeito, e é esse caso que o caminho de falha de runtime reporta
begin
// Verifica a presença de XFA antes de o PDFium ser carregado pela primeira vez, para que a
// compilação compatível com V8 possa ser selecionada. A PdfFileLooksXfa procura por
// /XFA e /NeedsRendering true sem analisar todo o ficheiro.
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) // o motor está ativo: layout dinâmico
else
ExtractXfaData(Pdf, 'C:\out'); // reverte para os pacotes estáticos
end;
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
Tratar a ausência de runtime de XFA de forma controlada
Três verificações indicam-lhe exatamente o nível de capacidade XFA disponível, e um código correto deve avaliá-las em vez de assumir conclusões. A XfaFeaturesAvailable é uma verificação global de que as exportações auxiliares do XFA/V8 estão presentes na DLL carregada. A XfaStaticReadable confirma, por documento, que os leitores de pacotes foram resolvidos — a proteção para a extração. A XfaRuntimeAvailable é a verificação mais robusta: é verdadeira apenas quando este documento específico ativou de facto o motor XFA, significando que é um ficheiro XFA, a compilação do V8 foi carregada e a FPDF_LoadXFA foi bem-sucedida. Um documento pode reportar XFA = True e simultaneamente XfaRuntimeAvailable = False sempre que a DLL implementada carecer de suporte a XFA ou a compilação padrão já tiver sido carregada
Quando um documento XFA é aberto mas o motor não consegue executar, o PDFium Component não arrisca. Associa o formulário ao modo estático seguro em vez de solicitar um runtime dinâmico que não pode disponibilizar, e gera o evento OnXfaRuntimeMissing uma vez para que o anfitrião possa reagir — normalmente instruindo o utilizador a reiniciar com a compilação compatível com V8, oferecendo ao mesmo tempo o acesso aos dados legíveis estaticamente. Associar este evento (handler) faz a diferença entre uma alternativa segura e um formulário que não mostra nada silenciosamente
procedure TForm1.PdfXfaRuntimeMissing(Sender: TObject);
begin
// Disparado uma vez quando um documento XFA abre mas o motor não consegue executar:
// uma pdfium.dll padrão já foi carregada, ou a compilação carece de XFA/V8.
ShowMessage('Este é um formulário XFA dinâmico. Reinicie com a ' +
'compilação compatível com V8 para o renderizar; os seus dados de pacotes continuam legíveis.');
end;
Seja realista em relação aos limites também no seu próprio produto. Se nunca distribuir a DLL compatível com V8, os formulários XFA dinâmicos nunca serão renderizados para os seus utilizadores — mas continuará a conseguir detetá-los, extrair todos os pacotes e obter os dados submetidos, o que é suficiente para o vasto grupo de tarefas focadas na obtenção de dados de formulários governamentais e bancários legados, e não na sua apresentação interativa. Reserve a compilação mais pesada do V8 para os casos que necessitam genuinamente de um formulário ativo e preenchível, e deixe que as verificações de capacidade guiem cada documento pelo caminho correto. Se o seu pipeline de extração também processar ficheiros incorporados, a mesma filosofia de apenas leitura se aplica aos anexos de PDF no Delphi
As APIs de FormType, XFA, GetXfaPacketCount, GetXfaPacketName, GetXfaPacketContent, GetXfaTemplate, GetXfaDatasets, GetXfaConfig e as verificações de capacidade apresentadas aqui fazem parte do PDFium Component para Delphi e C++Builder