XFA, la XML Forms Architecture, está obsoleta. ISO 32000-1 la recoge en el §12.7 con la nota de que se elimina en PDF 2.0, y los visores modernos van retirando sus motores XFA uno tras otro. Nada de eso ha vaciado los archivos. Formularios de admisión de la administración pública, solicitudes de seguros y extractos bancarios se crearon como XFA durante casi dos décadas, y esos ficheros siguen llegando hoy a bandejas de entrada y a canalizaciones documentales. Cuando el visor que antes los renderizaba deja de hacerlo, el formulario se convierte en una página en blanco con un aviso de «ábralo en otro lector». La solución duradera es aplanar el XFA en contenido PDF estático que cualquier lector pueda pintar
Lo difícil de ese aplanado no son los campos. Los cuadros de texto y las casillas de verificación se corresponden con widgets AcroForm sin demasiado problema. Lo difícil es el texto enriquecido que XFA almacena dentro de un elemento draw, en un bloque <exData contentType="text/html">. Ese bloque es un subconjunto de HTML con estilos en línea y, a menudo, anclas. Llevarlo a la página significa reproducir tanto el texto con estilo como los hipervínculos activos, y los hipervínculos son el punto en el que la mayoría de implementaciones se rinden en silencio
Qué aspecto tiene realmente el texto enriquecido XFA
El cuerpo de un exData es una pequeña porción de XHTML. Un párrafo es un <p>; una secuencia de caracteres con estilo es un <span> con su propio CSS en línea para el peso, la inclinación, el color y el tamaño; y un hipervínculo es un <a href="..."> que envuelve su texto visible. Una sola línea puede contener varios span seguidos, cada uno con un estilo distinto, y uno de ellos puede ser un ancla. El estilo no es un adorno que se pueda descartar. Una cláusula representada en negrita y en rojo porque es una advertencia legal tiene que seguir en negrita y en rojo tras el aplanado, o el documento aplanado tergiversa el original
Por eso el motor de aplanado no puede tratar el bloque como una única cadena. Tiene que recorrer la estructura en línea, resolver el estilo efectivo de cada fragmento superponiendo el CSS en línea del span a la fuente base del elemento draw, y disponer los fragmentos uno tras otro a lo largo de la línea. HotPDF modela cada uno de estos fragmentos ya maquetados como un registro interno TXFARichRun. El registro lleva el texto del fragmento, su estilo resuelto, su caja medida y, en el caso de un ancla, el Href al que apunta
Disponer los fragmentos de izquierda a derecha
El posicionamiento es donde el texto enriquecido deja de ser un problema de análisis y se convierte en un problema de composición tipográfica. Los fragmentos comparten una línea, así que cada uno empieza donde terminó el anterior. No hay ningún marcado que registre esas posiciones; hay que medirlas. La rutina interna LayoutRichText del motor mide cada fragmento con las mismas métricas de fuente que después lo pintarán, y luego fija el desplazamiento horizontal del fragmento en la suma acumulada de las anchuras de todos los fragmentos anteriores. El primero empieza en el origen de la caja draw, el segundo en la anchura del primero, el tercero en la anchura combinada de los dos primeros, y así sucesivamente a lo largo de la línea
Por eso importa tanto que la fuente de medición coincida. La pasada de maquetación mide avances; una pasada de renderizado distinta dibuja los glifos. Si esas dos pasadas discrepan sobre la fuente, las cajas que calculó la maquetación no quedarán debajo de los glifos que pinta el renderizador. HotPDF las mantiene sincronizadas asignando el estilo resuelto de cada fragmento a una especificación de fuente, mediante el auxiliar interno RunStyleToFontSpec, que coincide con los valores predeterminados del propio renderizador: Arial a 10 puntos. El avance medido y el texto dibujado concuerdan entonces, y la caja calculada de un fragmento cubre de verdad los caracteres que ve el lector
// Forma conceptual de un fragmento ya maquetado. El motor construye internamente
// un array de estos; usted nunca los crea, pero los campos explican cómo la zona
// activa de un enlace se deriva de la geometría medida y no del texto.
type
TRichRunInfo = record
Dx, Dy : Double; // esquina superior izquierda, relativa al origen de la caja draw
W, H : Double; // caja medida del fragmento (anchura de la pasada de maquetación)
Text : AnsiString; // los caracteres visibles del fragmento
Href : AnsiString; // destino URI para un fragmento <a>, '' en caso contrario
end;
De un fragmento ancla a una anotación Link de PDF
Un hipervínculo en un PDF terminado no forma parte del contenido de la página. Es un objeto aparte, una anotación Link, descrita en ISO 32000-1 §12.5.6.5. La anotación tiene un /Rect que define el rectángulo en el que se puede hacer clic en la página y una acción que se dispara al hacer clic en ese rectángulo. Para un enlace externo la acción es una acción URI: /S /URI con la dirección de destino como su cadena /URI. El texto visible que queda debajo es contenido de página ordinario; la anotación es la zona activa invisible superpuesta
La ruta de aplanado sigue exactamente este modelo. Cuando un fragmento lleva un Href, HotPDF dibuja primero el texto con estilo y después construye una anotación Link sobre la caja del fragmento. El punto de entrada público para esa anotación es el método de página AddURILink, que crea el objeto /Type /Annot /Subtype /Link con una acción /URI y devuelve el diccionario de la anotación. Su rectángulo es la caja medida del fragmento, trasladada de las coordenadas locales del elemento draw a coordenadas de página. El resultado es un enlace que cae con precisión sobre el texto del ancla y en ningún otro sitio
// La misma API pública que usa la ruta de aplanado para cada fragmento ancla. Produce
// una anotación Link de ISO 32000-1 12.5.6.5: /Subtype /Link con una acción /URI
// sobre el rectángulo indicado. La descripción opcional rellena /Contents para que
// un lector de pantalla pueda anunciar el destino.
var
LinkRect: TRect;
Annot: THPDFDictionaryObject;
begin
LinkRect := Rect(72, 690, 268, 706); // zona activa del fragmento en el espacio de página
Annot := Pdf.CurrentPage.AddURILink(LinkRect,
'https://www.example.gov/appeal', 'File an appeal online');
end;
Por qué la zona activa tiene que salir de las anchuras medidas
Resulta tentador imaginar que el enlace se localiza buscando en la página su texto visible y dibujando el rectángulo alrededor de lo que se encuentre. Eso no funciona, y la razón es fundamental para entender cómo se almacena el texto aplanado. Los fragmentos con estilo se pintan con fuentes de subconjunto incrustadas. Una fuente de subconjunto renumera los glifos que conserva, así que el flujo de contenido de la página contiene códigos CID hexadecimales, no los códigos de carácter originales. Los bytes de la página no son las letras que lee una persona, y no se pueden buscar como texto. Una búsqueda del texto del ancla no encuentra nada, porque ese texto no existe como texto literal en ningún punto del flujo
El único anclaje fiable para el rectángulo es la geometría que la pasada de maquetación ya produjo. El desplazamiento y la anchura medida de cada fragmento se calcularon al fluir la línea, antes de renumerar ningún glifo, y describen dónde aparecerá físicamente el texto. HotPDF toma por tanto el rectángulo del enlace directamente de la caja maquetada del fragmento y no de ninguna búsqueda de texto. Como la medición usó la fuente de renderizado, la caja es correcta con independencia del subconjunto. La geometría sobrevive a la codificación; el texto no. Ese es todo el argumento a favor del posicionamiento por anchuras medidas, y es la razón por la que un aplanador que intenta reconstruir los enlaces buscando texto produce zonas activas que se desplazan o desaparecen
Cómo lanzar el aplanado desde su código
Para un PDF que ya contiene un paquete XFA, el punto de entrada es FlattenLoadedXFA. Cargue el documento, llame al método y guarde el resultado. El parámetro Editable decide qué ocurre con los campos del formulario: pase True para conservarlos como widgets AcroForm rellenables, o False para marcar todos los widgets como de solo lectura y que la salida sea un registro congelado. Los bloques draw de texto enriquecido, con sus fragmentos con estilo y sus anotaciones de enlace, se generan en ambos casos. La función devuelve el número de widgets que ha emitido
var
Pdf: THotPDF;
Emitted, i: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('xfa_appeal_form.pdf');
// True mantiene los campos rellenables; False los congela como solo lectura.
Emitted := Pdf.FlattenLoadedXFA(True);
// Todo lo que el motor no pudo mapear se informa, no se lanza como excepción.
for i := 0 to Pdf.XFAFlattenWarnings.Count - 1 do
Writeln('XFA warning: ', Pdf.XFAFlattenWarnings[i]);
Pdf.SaveLoadedDocument('appeal_form_flat.pdf');
Writeln('Widgets emitted: ', Emitted);
finally
Pdf.Free;
end;
end;
Lea siempre XFAFlattenWarnings después de la llamada. La lista se vacía al inicio de cada aplanado y acumula una línea por cada elemento que el motor se negó a renderizar: un tipo de campo no admitido, una imagen draw que no se pudo decodificar, un bloque exData sin span utilizables. Ninguno de ellos lanza una excepción, así que una lista de avisos vacía es su prueba de que todo se ha mapeado, y una lista no vacía le indica exactamente qué originales revisar. Cuando dispone del XFA en bruto como bytes XDP en lugar de como PDF cargado, el método hermano ApplyXFAAsAcroForm toma esos bytes directamente y comparte la misma ruta de código y el mismo comportamiento de avisos. El método complementario AddXFAPacket hace el camino inverso, incrustando un paquete XFA en un documento que está construyendo
Confirmar el resultado en un lector
Abra el fichero aplanado en Acrobat, o en cualquier visor actual, y compruebe dos cosas. Primero, que el texto enriquecido se ha renderizado con su estilo intacto: los fragmentos en negrita están en negrita, los fragmentos coloreados conservan su color y los span están en el orden correcto sobre la línea en lugar de solaparse o salirse de la caja. Segundo, que los hipervínculos están activos. Pase el cursor sobre un ancla y la barra de estado debería mostrar la dirección de destino; haga clic y la acción URI debería abrirla. Use el inspector de anotaciones del visor para confirmar que cada una es una anotación /Link auténtica cuyo /Rect se ciñe al texto del ancla, situada sobre un contenido que ahora son glifos pintados normales y no XFA renderizado por el formulario. Esa combinación, texto estático con estilo más anotaciones Link reales sobre los rectángulos correctos, es lo que permite al documento aplanado sobrevivir a los motores XFA que ya no necesita
El aplanado de los propios campos, los cuadros de texto, casillas de verificación y listas de selección que rodean este texto enriquecido, se trata en nuestra guía sobre aplanar formularios XFA en widgets AcroForm. Para la historia más amplia de construir y colocar anotaciones Link a mano, más allá de las que genera la ruta de aplanado, consulte trabajar con anotaciones PDF en HotPDF. Ambas se apoyan en el mismo modelo de anotaciones y formularios que se distribuye con el componente HotPDF para Delphi para Delphi y C++Builder