Artículo técnico

Texto árabe y RTL en PDF Delphi con HotPDF

Tomen la frase árabe يوضح ملف PDF, pásenla a TextOut y abran la salida: las letras corren en la dirección equivocada y cada una queda en su forma aislada, desconectada de sus vecinas. Un lector árabe ve algo parecido al inglés escrito al revés con un espacio después de cada letra. Nada falló, no hubo excepción ni advertencia, porque simplemente nunca se ejecutaron dos transformaciones de texto distintas. Entender cuáles son esas transformaciones y qué API las aplica es todo el juego en la salida PDF para escrituras complejas.

Este artículo recorre el texto de derecha a izquierda y de escrituras complejas con HotPDF, un componente PDF VCL nativo para Delphi y C++Builder, incluido el punto donde su soporte de moldeado termina realmente, algo igual de importante cuando deciden si cubre sus locales.

Dos transformaciones separan una cadena de una línea impresa

Unicode almacena el texto en orden lógico: el orden en que se escribe, se guarda y se lee en voz alta. Un renderizador dibuja en orden visual. Para escrituras de derecha a izquierda, ambos difieren, y para contenido mixto, una oración árabe que contiene el token latino "PDF" o un precio en dígitos, el Unicode Bidirectional Algorithm (UAX #9) define cómo se incrustan tramos de izquierda a derecha dentro de una línea de derecha a izquierda. Esa es la primera transformación: reordenamiento.

La segunda transformación es el moldeado contextual. Una letra árabe toma un glifo distinto según esté al inicio, en medio o al final de una palabra, o aislada; el codepoint nunca cambia, solo la forma renderizada. Una tubería de texto que asigna codepoints directamente a glifos predeterminados produce la salida desconectada descrita arriba. El hebreo no necesita unión, pero sí reordenamiento; el árabe necesita ambas cosas, por eso es el caso de prueba canónico.

El desarrollo de escritorio oculta esta maquinaria. Cuando una aplicación VCL dibuja árabe en pantalla, la pila de texto del sistema operativo lo reordena y moldea de forma invisible, por eso la misma cadena que se renderiza perfectamente en un TEdit sale mal en un PDF ingenuo. Un content stream PDF almacena glifos posicionados, no tramos de texto editables: quien escribe el stream es dueño del moldeado, y esa es la brecha que RtLTextOut existe para cerrar.

RtLTextOut: reordenar y unir en una sola llamada

HotPDF separa la ruta latina de la ruta de escrituras complejas a nivel de API. TextOut dibuja lo que recibe, en el orden en que lo recibe; RtLTextOut ejecuta primero el reordenamiento y el análisis contextual. El parámetro charset de SetFont indica al motor qué reglas de escritura aplican: 178 selecciona procesamiento árabe y 177 selecciona hebreo.

// Arabic: pass logical order; RtLTextOut reorders and joins
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF');

// Hebrew: reordering only, no contextual joining
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 660, 0, 'קובץ PDF זה');

La trampa que más tiempo de depuración consume: RtLTextOut realiza la inversión por sí mismo. Pasarle texto preinvertido, normalmente una "corrección" heredada de un intento anterior con TextOut plano, invierte la línea dos veces. Incluso puede verse correcto con una cadena de prueba puramente árabe, y luego romperse en la primera línea que contiene letras latinas o dígitos, porque los tramos mixtos ya no siguen el orden UAX #9. Pasen siempre orden lógico y dejen que la API haga el trabajo.

El contenido de direcciones mixtas también es donde las expectativas manuales fallan durante las revisiones: dentro de una línea de derecha a izquierda, los números y las palabras latinas incrustadas siguen leyéndose de izquierda a derecha. Revisores no familiarizados con el diseño bidireccional suelen reportarlo como un error; es el comportamiento correcto según la especificación y merece una nota en la documentación de aceptación antes de la primera revisión por hablantes nativos.

La cobertura de glifos decide el resultado antes del moldeado

El moldeado selecciona glifos; la fuente debe contenerlos realmente. La falla clásica de despliegue es un reporte que se renderiza perfectamente en la estación del desarrollador, donde Arial Unicode MS está instalada, y produce cuadros vacíos en el servidor del cliente, donde Windows sustituyó silenciosamente una fuente sin cobertura árabe. La corrección es dejar de depender de fuentes instaladas en el sistema y registrar un archivo de fuente que ustedes distribuyan:

// Ship a known font instead of relying on installed system fonts
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansArabic.ttf');
Pdf.CurrentPage.SetFont('NotoSansArabic', [], 12);

// Audit coverage for the codepoints your data actually uses
GID := Pdf.GetUnicodeGlyphForCodepoint($0628);  // U+0628 ARABIC LETTER BEH
LogGlyphAudit($0628, GID);

Se aplican dos límites de versión. Las fuentes registradas de esta forma deben embeberse, y el manejo de fuentes Unicode embebidas de HotPDF requiere que la versión PDF del documento sea 1.5 o posterior, relevante solo si algún sistema posterior fija la salida en PDF 1.4. Además, la licencia de la fuente debe permitir embedding: los archivos TrueType llevan bits de permiso de embedding, y una fuente que se ve bien en pantalla puede ser legalmente inadecuada para distribuirse dentro de documentos de clientes.

GetUnicodeGlyphForCodepoint es el punto de auditoría: recorran en el arranque del servicio los rangos de codepoints que usan sus datos y registren los ID de glifo resueltos, para que una brecha de cobertura aparezca en una línea de log durante el despliegue y no como caracteres faltantes en la factura de un cliente.

Para texto Unicode que no es de derecha a izquierda, cadenas CJK, diacríticos vietnamitas o escrituras europeas mixtas, se aplica la tubería plana: TextOut acepta un WideString y lo dibuja con la fuente registrada sin análisis bidireccional. Mantener las dos rutas de llamada separadas en el código de reportes, una rutina para tramos RTL y otra para todo lo demás, hace explícito el comportamiento por locale en lugar de enterrarlo en una bandera que nadie recuerda configurar.

El orden de lectura también es una propiedad del documento

La corrección a nivel de glifo no es el final del trabajo. ISO 32000-1 §12.2 define una preferencia de visor, /Direction, que declara el orden de lectura predominante del documento. No cambia ningún glifo; indica a los visores cómo ordenar pliegos de dos páginas, dónde anclar la progresión en diseños de páginas enfrentadas y qué dirección debe asumir la interfaz, detalles que importan en folletos y en cualquier documento que un usuario hojea.

// Declare right-to-left reading order at the document level
Pdf.Direction := RightToLeft;  // adds vpDirection to ViewerPreferences

Asignar Direction basta por sí solo: el setter de la propiedad agrega vpDirection a ViewerPreferences automáticamente, así que la preferencia llega al archivo con una sola línea. La omisión que deben vigilar es saltarse por completo la declaración, algo fácil precisamente porque nada visible cambia en una página individual; solo aparece cuando alguien imprime un folleto dúplex y los pliegos salen espejados.

Dónde se detiene el moldeado de HotPDF

Un mapa honesto de capacidades ahorra una semana de evaluación. RtLTextOut maneja automáticamente el reordenamiento bidireccional y la unión contextual árabe. Las ligaduras tipográficas opcionales y la aplicación más amplia de características OpenType no son automáticas: GetSingleSubstituteGlyph(GID, 'liga') resuelve una sustitución a la vez, ID de glifo de entrada y etiqueta de característica al lado, lo cual funciona para una lista conocida y finita de ligaduras que ustedes aplican, pero no es un motor GSUB general. Para escrituras cuyas exigencias de moldeado van más lejos, los sistemas índicos con signos vocálicos que se reordenan son el ejemplo habitual, ejecuten un piloto con cadenas reales de clientes antes de comprometer el locale, en lugar de extrapolar a partir de resultados árabes.

La verificación debe ser de extremo a extremo, porque una página puede verse correcta y aun así fallar en todos los usos posteriores. Tres revisiones capturan la mayoría de los problemas: copien el texto de vuelta desde Acrobat y comparen codepoints con la cadena fuente; busquen dentro del documento una palabra que aparece en la página; y revisen la salida en una máquina que no tenga instaladas sus fuentes de desarrollo. Un colega que lee nativamente un documento real vale más que cualquier cantidad de datos sintéticos de prueba; programen esa revisión antes de publicar el formato, no después de la primera queja.

Elijan las cadenas de prueba deliberadamente en lugar de reutilizar lo que un traductor envió el año pasado. Un conjunto mínimo útil por locale: una oración puramente de la escritura, una oración con nombres de marca latinos incrustados, una línea con dígitos y moneda, y nombres con diacríticos o marcas combinantes. Los nombres reales de clientes rompen supuestos de moldeado que el texto de relleno nunca toca; el corpus de regresión debe crecer con una entrada cada vez que un caso de soporte expone un nuevo patrón.

El registro de fuentes, el subsetting y la API general de dibujo de texto se cubren en el artículo sobre salida de reportes, fuentes e imágenes con HotPDF; si los mismos documentos también deben cumplir perfiles de accesibilidad, los requisitos de etiquetado de idioma y estructura en el artículo de validación PDF/A y PDF/UA se apilan sobre el trabajo de moldeado descrito aquí.

Las API de derecha a izquierda y fuentes Unicode de este artículo se incluyen con HotPDF Component para Delphi y C++Builder; la página del producto enlaza la referencia completa de salida de texto.