Artículo técnico

Exportar e importar XFDF en Delphi con PDFium

El PDFium Component para Delphi y Lazarus intercambia datos de formulario y anotaciones a través de XFDF, el formato de intercambio XML definido por ISO 19444-1. TPdf.ExportXFDF serializa todos los valores de campo de formulario y todas las anotaciones admitidas del documento cargado hacia un archivo XFDF o un TStream; TPdf.ImportXFDF vuelve a leer un documento XFDF, recrea las anotaciones en sus páginas y aplica los valores de campo. Ese único ciclo de ida y vuelta cubre dos flujos de trabajo que toda aplicación de documentos necesita tarde o temprano: un revisor marca un PDF en Acrobat y le envía los comentarios para que los fusione, o una regla de cumplimiento exige que los datos de formulario vivan en un archivo aparte, comparable y auditable, en lugar de quedar incrustados en el propio PDF

Diagrama del ciclo de ida y vuelta XFDF en Delphi: TPdf.ExportXFDF escribe campos de formulario y 18 subtipos de anotación en una carga XML pequeña que TPdf.ImportXFDF fusiona de vuelta en cualquier copia del PDF
ExportXFDF convierte todo el documento en una carga XML ISO 19444-1 pequeña, e ImportXFDF aplica los mismos campos y anotaciones sobre cualquier copia del PDF

¿PDFium admite la importación y exportación de XFDF?

La biblioteca PDFium en sí no lo hace. La API C nativa no tiene ninguna función XFDF ni FDF — nada en sus encabezados públicos lee ni escribe ninguno de los dos formatos, así que ningún grado de envoltura lo consigue. Por eso el PDFium Component implementa todo el motor XFDF en Pascal, en una unidad dedicada que construye y analiza el XML directamente sobre el modelo de datos de anotaciones y campos de formulario que TPdf ya tiene. El escritor emite el XML a mano y el lector es un analizador de máquina de estados escrito a mano, así que la función no agrega ninguna dependencia de Delphi XMLDoc ni de las unidades DOM de FPC y se comporta de forma idéntica en Delphi y en Lazarus

Esa decisión de diseño importa a la hora de evaluar alternativas. Si ha estado llamando funciones FPDF_* en crudo desde Pascal, XFDF es un muro: la capacidad simplemente no está en la DLL. El componente cruza ese muro tratando XFDF como un problema de serialización puro — reunir los valores de campo y los registros de anotación que el envoltorio ya sabe leer, escribirlos como XML estándar e invertir el proceso al importar

¿Qué contiene un archivo XFDF?

ISO 19444-1 define XFDF como la representación XML de FDF, y su carga se divide en dos bloques de nivel superior. El elemento <fields> lleva los nombres jerárquicos de los campos de formulario y sus valores — todo lo que un usuario escribió, eligió o marcó en un AcroForm. El elemento <annots> lleva las anotaciones: TPdf.ExportXFDF emite 18 subtipos — text, highlight, underline, strikeout, squiggly, line, circle, square, caret, polygon, polyline, stamp, ink, freetext, fileattachment, sound, link y redact. Las anotaciones widget están deliberadamente ausentes de <annots> porque sus datos viajan en <fields>, y los subtipos internos del visor, como Popup, no forman parte del vocabulario XFDF

Anatomía de un archivo XFDF del PDFium Component que muestra el bloque jerárquico fields con los valores de AcroForm junto al bloque annots que lista los 18 subtipos de anotación admitidos
El bloque fields lleva los valores jerárquicos de AcroForm, mientras que el bloque annots transporta 18 subtipos de marcado y excluye los widgets y los tipos internos del visor

Para que el ciclo de ida y vuelta sea fiel, el registro TPdfAnnotation se amplió con los metadatos que XFDF espera: Name (el identificador único NM), Subject, ModificationDate, CreationDate, Icon, Opacity, los extremos de línea, los vértices de polígonos y polilíneas, y las trayectorias de trazos a mano alzada. Cada campo nuevo va acompañado de un centinela booleano Has*, así que el código que construía anotaciones con versiones anteriores sigue compilando y sigue produciendo los mismos diccionarios — un campo sin asignar nunca se escribe. Si ya crea marcado por programa, el mismo registro que usa para las anotaciones de marcado de texto con quad points ahora lleva todo lo que XFDF necesita

¿Cómo se exportan los datos de formulario y las anotaciones a XFDF?

Una sola llamada abarca el documento entero. Cargue el PDF con el llenado de formularios habilitado, llame a ExportXFDF y el componente recorre cada página, reúne los valores de campo y las anotaciones, y escribe el XML. El valor de retorno es la cantidad de bytes UTF-8 escritos, lo que sirve como comprobación barata en los registros

var
  Pdf: TPdf;
  Bytes: Integer;
begin
  Pdf := TPdf.Create(Self);
  Pdf.FormFill := True;            // necesario para que los valores de campo estén vivos
  Pdf.FileName := 'expense-report.pdf';
  Pdf.Active := True;
  Bytes := Pdf.ExportXFDF('expense-report.xfdf');
  ShowMessage(Format('%d bytes of XFDF written', [Bytes]));
end;

Ambas direcciones tienen una sobrecarga con TStream, así que nada obliga a dejar un archivo temporal en disco. Exportar a un TMemoryStream es la forma natural cuando el XFDF va camino de una respuesta HTTP, de una columna blob de base de datos o de una entrada de archivo comprimido firmada

var
  Buffer: TMemoryStream;
begin
  Buffer := TMemoryStream.Create;
  try
    Pdf.ExportXFDF(Buffer);        // el mismo XML, sin archivo de por medio
    Buffer.Position := 0;
    // entregue el stream a una respuesta web, columna blob o entrada zip
  finally
    Buffer.Free;
  end;
end;

¿Cómo fusiona ImportXFDF los comentarios de vuelta en el documento?

El lado de la importación es donde se cierra el flujo del revisor. Un colega anota el contrato en Acrobat, exporta los comentarios como XFDF y le envía unos pocos kilobytes de XML en lugar de una segunda copia del PDF. TPdf.ImportXFDF analiza ese archivo, crea cada anotación en la página que nombra su atributo page y escribe los valores de campo en las anotaciones widget correspondientes. La función devuelve el conteo combinado de campos más anotaciones aplicados, así que la interfaz puede confirmar exactamente cuánto llegó

var
  Applied: Integer;
begin
  Applied := Pdf.ImportXFDF('review-comments.xfdf');
  StatusBar.SimpleText :=
    Format('%d fields and annotations merged', [Applied]);
end;

El analizador es un escáner de etiquetas escrito a mano y pensado para la tolerancia más que para el rigor: los elementos desconocidos se omiten, las instrucciones de procesamiento, las declaraciones DOCTYPE y los comentarios se ignoran, los nombres de indicador no reconocidos se descartan, y los valores de atributo se desescapan con el conjunto completo de entidades XML y referencias numéricas de caracteres, incluidos los pares suplentes UTF-16. El XFDF producido por Acrobat, por un visor web o por otra herramienta se fusiona sin ceremonias. Una vez que las anotaciones están en el documento, el ciclo de revisión continúa con la maquinaria de respuestas y estados descrita en construir un flujo de revisión de anotaciones con el PDFium Component, y los valores de campo importados participan en la lógica normal de orden de tabulación que cubre la navegación entre campos de formulario

¿Por qué importan los separadores decimales en XFDF?

Cada anotación de XFDF se posiciona mediante cadenas de coordenadas — rect="70.5,540,200,560" y similares — e ISO 19444-1 exige el punto como separador decimal. El formato predeterminado de números de coma flotante de Delphi sigue la configuración regional de Windows, así que en un sistema alemán o francés un FloatToStr ingenuo convierte 70.5 en 70,5, lo que corrompe una lista de coordenadas separadas por comas y la vuelve basura que otros procesadores rechazan o interpretan mal. El PDFium Component normaliza cada número que escribe: el separador decimal regional se reescribe como punto y los ceros finales se recortan, de modo que el archivo exportado es idéntico byte a byte lo mismo si se produjo en una máquina en-US que en una de-DE. Si alguna vez depuró una herramienta PDF que funcionaba en la oficina y fallaba en la sede de un cliente europeo, ya conoce esta clase exacta de error — vale la pena saber que el componente lo resuelve por usted

¿Cuáles son los límites del ciclo de ida y vuelta?

Vale la pena enunciar dos fronteras con claridad. Primero, XFDF transporta datos de anotación, no apariencias renderizadas — los flujos de apariencia no forman parte del formato, así que el visor receptor regenera el aspecto de cada anotación a partir de sus propiedades. Un resaltado o un cuadrado se renderizarán correctamente en todas partes; un sello con una apariencia personalizada recaerá en lo que el visor de destino dibuje para ese nombre de sello. Segundo, la API subyacente de PDFium tiene setters para cadenas pero ninguno para los números de anotación ni la geometría de trayectorias, así que Opacity, los extremos de línea, los vértices y los trazos a mano alzada son de solo exportación: el componente los preserva fielmente al escribir XFDF, pero no puede volver a escribirlos en un PDF al importar. Los valores de campo, los contenidos, los colores, los rects, los quad points, las fechas y los metadatos de identidad completan el ciclo en ambas direcciones

Tres niveles de fidelidad del ciclo XFDF en Delphi: propiedades que viajan en ambos sentidos, geometría de solo exportación como vértices y trazos a mano alzada, y flujos de apariencia que nunca salen del archivo
Las propiedades centrales completan el ciclo en ambas direcciones, la geometría de tipo trayectoria queda como solo exportación, y los flujos de apariencia los regenera el visor que reciba el archivo

Para empezar con las manos en el código, el ejemplo XfdfLab se incluye en los conjuntos de demos de Delphi, C++Builder y Lazarus, y conecta ambas llamadas a botones sobre cualquier PDF que abra. La compatibilidad con XFDF viene incluida en la versión actual del PDFium Component, junto con las APIs de anotaciones y formularios sobre las que se construye