Sí, PDFium admite formularios XFA, con una importante salvedad. PDFium Component puede detectar un formulario XFA y extraer sus paquetes de template, datasets y config como XML sin formato utilizando la DLL estándar, con cero dependencias en tiempo de ejecución. Ejecutar un formulario XFA dinámico (distribuir su descripción XML en páginas interactivas y activas) es un problema independiente que requiere además la compilación habilitada para V8. La comunidad se pregunta constantemente si PDFium "soporta XFA" como si fuera una sola capacidad de sí o no, y la respuesta honesta es que leer datos de XFA y renderizar un formulario XFA son dos cosas muy diferentes con perfiles de dependencia muy distintos
Este artículo recorre ambas partes con PDFium Component, el contenedor VCL nativo alrededor de PDFium para Delphi y C++Builder. Primero, la ruta de detección y extracción estática (FormType, la propiedad XFA y los lectores GetXfaPacket*); luego, la ruta de ejecución dinámica con su restricción de V8; y finalmente, las comprobaciones de capacidad que le permiten reaccionar de manera adecuada cuando una DLL desplegada no puede ejecutar el motor
¿Admite PDFium formularios XFA?
Interprételo como dos capacidades, no como una. La extracción estática de XFA siempre está disponible: las funciones de exportación de bajo nivel FPDF_GetXFAPacket* que respaldan a GetXfaPacketCount, GetXfaPacketName y GetXfaPacketContent se compilan en cualquier versión medianamente moderna de pdfium.dll, incluso en una compilación con XFA desactivado. Es por eso que extraer el XML sin formato de template, datasets y config de un formulario no conlleva ninguna dependencia en tiempo de ejecución: es una lectura puramente estructural sin intervención del motor. El XFA dinámico es la otra capacidad: ejecutar el formulario en sí, evaluar sus cálculos y permitir que el motor de diseño de XFA lo pagine. Esa ruta existe únicamente en una compilación de PDFium que incluya compatibilidad con XFA sobre el motor de JavaScript V8, y es el punto sobre el cual la mayoría de los debates acerca de si "PDFium admite XFA" realmente están discutiendo
Los estándares enmarcan esta división con claridad. XFA se define fuera de la gramática central de PDF en Adobe XFA 3.3 (2012), y PDF 1.7 §8.6.7 describe cómo se transporta un formulario XFA dentro de un PDF: los paquetes del formulario residen bajo la entrada /XFA del diccionario AcroForm, y un documento que deba ser estructurado por un procesador XFA antes de tener sentido establece /NeedsRendering true en el catálogo. Estos dos marcadores son exactamente lo que busca PDFium Component, y exactamente en lo que usted puede confiar al analizar un archivo desconocido
¿En qué se diferencia XFA de un AcroForm?
Un AcroForm es el formulario clásico de PDF: anotaciones de controles (widgets) — campos de texto, casillas de verificación, grupos de opciones de radio — ancladas en coordenadas fijas sobre páginas de PDF normales. La página que usted ve es la página del archivo, y los campos se sitúan sobre ella. XFA adopta el enfoque opuesto. El formulario interactivo se describe completamente en XML, y en un documento XFA completo (dinámico) las páginas PDF son básicamente un marcador de posición: el contenido real es generado al abrir el archivo por un motor XFA que lee la plantilla XML y la distribuye, expandiéndose o contrayéndose para adaptarse a los datos. Eso es lo que anuncia /NeedsRendering true: sin un procesador XFA, las páginas estáticas pueden estar en blanco o contener únicamente un aviso de "abra en un visor compatible"
La carga útil de XFA en sí es un conjunto de paquetes con nombre, y tres de ellos contienen todo lo que la mayoría de las integraciones necesitan. El paquete template es la definición del formulario: campos, diseño, cálculos y scripts. El paquete datasets contiene los datos de la instancia real (los valores de los campos completados como un árbol XML). El paquete config transporta instrucciones de procesamiento para el motor XFA. Existen otros (localeSet, connectionSet, el contenedor xdp), pero para auditar un formulario o migrar sus datos, template más datasets suele ser suficiente. Debido a que esta información es solo XML alojado en un flujo de datos, su lectura nunca requiere la ejecución de código, lo cual es la razón principal por la que la extracción estática no tiene dependencias
Detectar un formulario XFA en Delphi
La detección comienza de la misma manera que cualquier tarea de PDFium Component: establezca FileName, asigne Active := True y verifique que realmente se haya abierto. Active := True nunca genera una excepción (un archivo faltante, una contraseña incorrecta o la ausencia de la DLL simplemente dejan Active en False), por lo que la comprobación es obligatoria. Una vez abierto el documento, FormType le indica qué modelo de formulario utiliza. Dos de los valores de TPdfFormType corresponden a XFA: ftXfaFull es un formulario XFA dinámico y completo, el tipo que requiere renderizado, mientras que ftXfaForeground es XFAF, the foreground subset where XFA content is drawn over otherwise-normal AcroForm pages so the static pages remain meaningful. El booleano XFA es un acceso rápido para determinar si "este es un documento XFA de cualquiera de las dos variantes". Si también trabaja con campos a nivel de control (widget) en un documento XFAF o AcroForm, el funcionamiento se cubre en la guía de navegación de campos de formulario de 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;
Extraer paquetes XFA como XML sin formato
La extracción es la mitad de alto valor y sin dependencias. Protéjala con XfaStaticReadable (que confirma que las funciones de exportación del lector de paquetes se resolvieron en la DLL cargada) y luego recorra los paquetes por índice. GetXfaPacketCount devuelve cuántos paquetes contiene el formulario, GetXfaPacketName asigna a cada uno su nombre y GetXfaPacketContent devuelve los bytes sin formato, los cuales para los paquetes conocidos corresponden a XML codificado en UTF-8. Cuando ya sabe qué paquete desea, los asistentes con nombre GetXfaTemplate, GetXfaDatasets y GetXfaConfig resuelven el nombre por usted y devuelven TBytes directamente. Para una tarea de migración de datos, el que importa es GetXfaDatasets: le proporciona los valores de los campos enviados como un documento XML que usted puede analizar y mapear en su propio esquema, el mismo tipo de trabajo posterior que realizaría tras extraer texto de 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; // 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;
¿Por qué la renderización dinámica de XFA requiere el motor V8?
La conclusión primero: porque un formulario XFA dinámico es un pequeño programa, no un dibujo estático. El paquete template contiene cálculos, validaciones y scripts de eventos en FormCalc y JavaScript, y el motor de diseño de XFA debe ejecutarlos para decidir cómo se verán las páginas. PDFium implementa ese motor únicamente en una compilación estructurada con compatibilidad de XFA sobre V8, el motor de JavaScript de Google (la misma razón por la cual la DLL habilitada para V8, pdfium32v8.dll o pdfium64v8.dll, es significativamente más grande que la compilación estándar). Sin V8 no hay nada que ejecute los scripts, por lo que no habrá páginas renderizadas que mostrar, solo el XML sin formato que usted ya puede leer de forma estática
Seleccionar esa compilación conlleva una restricción estricta que no se puede evitar: es una decisión única por proceso. Un solo proceso no puede cargar a la vez la versión estándar de pdfium.dll y la compilación habilitada para V8 (exportan los mismos símbolos y V8 mantiene un estado de aislamiento global), por lo que PDFium Component debe elegir la compilación V8 antes de que la biblioteca se cargue por primera vez. Para que esto sea automático, LoadDocument escanea previamente el archivo en busca de los marcadores /XFA y /NeedsRendering true (la misma comprobación expuesta en la función independiente PdfFileLooksXfa) y establece EnableV8Engine := True antes de la carga cuando los encuentra. Una vez que una DLL de pdfium básica ya reside en memoria, el cambio ya no puede aplicarse, y ese es el caso que reporta la ruta de ejecución ausente
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;
Manejar la falta del entorno de ejecución de XFA de forma adecuada
Tres comprobaciones le indican exactamente qué nivel de capacidad de XFA posee, y un código correcto las verifica en lugar de asumirlas. XfaFeaturesAvailable es una comprobación global de que las funciones auxiliares de exportación de XFA/V8 están presentes en la DLL cargada. XfaStaticReadable confirma, por documento, que los lectores de paquetes se resolvieron (la protección para la extracción). XfaRuntimeAvailable es la determinante: es verdadera solo cuando este documento específico activó realmente el motor XFA, lo que significa que es XFA, que la compilación de V8 se cargó y que FPDF_LoadXFA se ejecutó con éxito. Un documento puede reportar XFA = True y a la vez XfaRuntimeAvailable = False cuando la DLL desplegada carece de compatibilidad con XFA o la compilación estándar ya se encontraba cargada
Cuando un documento XFA se abre pero el motor no puede ejecutarse, PDFium Component no asume riesgos. Ajusta el formulario al modo estático seguro en lugar de solicitar un entorno de ejecución dinámico que no puede cumplir, y activa el evento OnXfaRuntimeMissing una vez para que el programa principal reaccione (generalmente indicando al usuario que reinicie con la compilación habilitada para V8, mientras sigue ofreciendo la lectura estática de los datos). Conectar ese controlador marca la diferencia entre una alternativa adecuada y un formulario que no muestra nada en silencio
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;
Defina claramente los límites también en su propio producto. Si nunca distribuye la DLL habilitada para V8, los formularios XFA dinámicos nunca se renderizarán para sus usuarios; sin embargo, aún puede detectarlos, extraer cada paquete y obtener los datos enviados, lo cual es suficiente para una gran cantidad de tareas destinadas principalmente a extraer información de formularios heredados de administraciones públicas y entidades bancarias, en lugar de presentarlos interactivamente. Reserve la compilación V8 más pesada para los casos que realmente requieran un formulario activo y editable, y permita que las comprobaciones de capacidad dirijan cada documento a la ruta que realmente puede seguir. Si su flujo de extracción también maneja archivos adjuntos, la misma filosofía de solo lectura se aplica a los archivos adjuntos de PDF en Delphi
Las API de FormType, XFA, GetXfaPacketCount, GetXfaPacketName, GetXfaPacketContent, GetXfaTemplate, GetXfaDatasets, GetXfaConfig y de comprobación de capacidad mostradas aquí forman parte del PDFium Component para Delphi y C++Builder