Technical Article

Fuentes y texto en PDF: por qué los glifos se convierten en cuadros

Un PDF que se ve perfecto en tu equipo y se renderiza como una fila de cuadros vacíos en el de otra persona es el defecto de fuente más habitual en el software de documentos, y casi nunca significa que el texto esté mal. Los caracteres están intactos, la codificación es correcta, simplemente los glifos no están. Lo que cambió entre las dos máquinas es qué fuentes tenía instaladas el sistema operativo, y la diferencia entre un fichero portable y uno frágil es una decisión tomada al escribir la página: si la fuente viajó dentro del PDF o se asumió que estaría disponible en el destino.

Entender por qué ocurre eso, y por qué un fallo distinto produce texto de aspecto buscable que al copiar sale como caracteres sin sentido, exige mirar cómo almacena texto el PDF. No almacena frases. Almacena códigos de glifo más un programa de fuente más tablas que relacionan unos con otros, y cada error de renderizado o extracción vive en un hueco entre esos tres elementos. Lo que sigue es un recorrido por esa maquinaria, fundamentado en ISO 32000, con las llamadas Delphi que la controlan allí donde importan.

Caracteres, códigos y glifos son tres cosas distintas

La terminología confunde porque el lenguaje cotidiano colapsa tres ideas distintas en la palabra «letra». Un carácter es una unidad abstracta de escritura, la idea de la A mayúscula, identificada en Unicode como U+0041. Un glifo es una forma dibujada, el contorno de curvas y trazos que una fuente concreta usa para representar ese carácter. Entre ambos se sitúa el código: el byte o bytes del flujo de contenido que indican al visor qué glifo pintar con la fuente activa.

El PDF trabaja con códigos. Cuando un flujo de contenido muestra una cadena, esos bytes son índices en la fuente activa, no Unicode. La codificación de la fuente decide que un código 65 significa «dibuja el glifo registrado bajo el 65», y nada en esa operación sabe que el resultado parece una A para un ser humano. Eso es lo que hace que el PDF se renderice de forma idéntica en cualquier sitio donde pueda encontrar los glifos, y también por eso la extracción es un problema distinto al de la visualización: dibujar solo necesita código a glifo, leer necesita código a Unicode, y esas son dos tablas distintas que pueden discrepar o faltar de forma independiente.

Los tipos de fuente que encontrarás en la práctica

ISO 32000 define varios tipos de diccionario de fuente, y en la práctica un documento que recibes o generas usa uno de tres. Saber cuál tienes delante explica la mayoría de lo que puede salir mal.

Type 1 es el formato vectorial PostScript original de Adobe, construido con curvas de Bezier cúbicas. Las catorce fuentes estándar que todo lector conforme debe suministrar (las familias Helvetica, Times, Courier, Symbol y ZapfDingbats) son Type 1, y un diccionario de fuente que nombre a una de ellas puede omitir legalmente el programa de fuente. Ese es el único caso en que dejar una fuente sin incrustar es seguro por especificación y no por suerte. Para cualquier otra cara Type 1, el programa debe estar incrustado o el visor sustituirá algo, habitualmente una fuente métricamente similar pero visualmente diferente.

TrueType usa curvas cuadráticas y proviene del mundo Apple y Microsoft. Es lo que son la mayoría de las fuentes del sistema, y lo que incrustarás con más frecuencia. Una fuente TrueType simple en PDF está limitada a códigos de un solo byte, por lo que una fuente así puede direccionar como máximo 256 glifos a la vez. Esa limitación es la razón estructural por la que los scripts CJK y otros de gran tamaño no pueden usarse con una fuente simple.

Type 0, la fuente compuesta o CID-keyed, es la respuesta a esa limitación. Usa códigos multibyte y un CMap para dirigirlos a través de una CIDFont descendiente, cuyos contornos son a su vez TrueType o CFF/Type 1. Este es el único tipo de fuente que puede contener miles de glifos, por lo que cualquier PDF que contenga chino, japonés, coreano o una amplia mezcla multilingüe usa Type 0 lo haya pensado el autor o no. El precio es la complejidad: más piezas en movimiento, más que tienen que ser correctas tanto para el renderizado como para la extracción.

Una fuente TrueType renderizada a 12, 18, 24 y 36 puntos en un PDF, que muestra que un único contorno incrustado escala a cualquier tamaño

Un detalle detrás de esa imagen determina el tamaño del fichero. Una fuente es una biblioteca de contornos, no mapas de bits de tamaño fijo, por lo que el mismo programa incrustado sirve para todos los tamaños de punto de la página. El escalado es una transformación aplicada en tiempo de dibujo, por eso un encabezado y el cuerpo de texto comparten una cara incrustada y por eso el coste de incrustación es por fuente, no por tamaño.

La incrustación es la diferencia entre portable y frágil

Incrustar significa que el programa de fuente, los datos de contorno reales, se escribe en el PDF como un flujo. Un lector en una máquina que nunca ha oído hablar de tu fuente lee esos contornos directamente del fichero y dibuja los glifos exactos. Si no incrustas, estás apostando a que el destino tiene una fuente con el mismo nombre; cuando no es así, el visor recurre a un sustituto. Para las catorce estándar esa sustitución está definida y es inocua. Para todo lo demás va desde un resultado aproximado con otro tipo de letra hasta cuadros vacíos cuando ningún sustituto cubre el script en absoluto.

Con HotPDF el control es una sola propiedad, configurada antes de que se abra el documento. FontEmbedding indica a la biblioteca que empaquete en el fichero las caras que utiliza para dibujar:

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'report.pdf';
    Pdf.Compression := cmFlateDecode;
    Pdf.FontEmbedding := True;          // outlines travel inside the file
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Calibri', [], 11);
    Pdf.CurrentPage.TextOut(72, 760, 0, 'This renders the same on a machine without Calibri.');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

El orden no es cosmético. BeginDoc es donde HotPDF consolida la estructura del documento, por lo que FontEmbedding debe estar a true antes de esa llamada. Asignarlo después no produce ningún error ni aviso, solo un fichero que sale silenciosamente sin sus fuentes. Ese es el peor tipo de error: supera todas las pruebas en la máquina del desarrollador, donde la fuente resulta estar instalada, y solo aflora en la del cliente, donde no lo está.

La incrustación también es donde las licencias se encuentran con la ingeniería. Un programa de fuente lleva indicadores que describen si puede incrustarse libremente, solo para previsualización o en absoluto. Respetar esos indicadores es tu responsabilidad, no la del renderizador, y «funcionó» no es lo mismo que «estaba permitido».

Subconjuntos: incrustar solo los glifos utilizados

La incrustación completa escribe el programa de fuente entero en el fichero. Una cara TrueType CJK grande puede ocupar varios megabytes, e incrustarla entera para mostrar una docena de caracteres es un desperdicio que se multiplica a lo largo de un documento multipágina. Los subconjuntos resuelven esto escribiendo solo los glifos que el documento referencia, y luego renombrando la fuente con una etiqueta de seis letras y un signo más, la forma ABCDEF+Calibri en el listado de fuentes de cualquier PDF con subconjunto, para que un lector nunca confunda la cara parcial con una fuente completa del sistema con el mismo nombre.

Para la mayoría de documentos generados, los subconjuntos son la opción predeterminada correcta. Mantienen el tamaño del fichero proporcional al contenido y no a la fuente de origen, lo que importa sobre todo para las grandes fuentes multilingües que de otro modo dominarían el fichero. La única salvedad es que un subconjunto solo contiene lo que se utilizó en el momento de la creación. Si un proceso posterior intenta añadir texto a una fuente con subconjunto, los glifos que necesite pueden no estar en el fichero, una restricción real para la edición incremental de un PDF ajeno.

Fuentes Unicode y el problema de los cuadros CJK

Cuando el texto no es latín simple se agota la vía de las fuentes simples, y la solución es registrar explícitamente una fuente con capacidad Unicode y dejar que HotPDF construya una fuente Type 0 a partir de ella. RegisterUnicodeTTF carga un fichero TrueType por ruta; después el nombre registrado es utilizable en SetFont como cualquier otro:

Pdf.FontEmbedding := True;
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansCJKsc-Regular.ttf');
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('NotoSansCJKsc-Regular', [], 14);
Pdf.CurrentPage.TextOut(72, 720, 0, '你好,世界 こんにちは 안녕하세요');
Pdf.EndDoc;

Dos cosas determinan el éxito o el fracaso. La fuente tiene que cubrir los scripts de la cadena: una fuente TrueType solo latina no generará glifos chinos porque se lo pidas, y el resultado serán cuadros vacíos de nuevo, esta vez porque el glifo realmente no existe en esa cara. Y la incrustación debe mantenerse activa, porque una fuente Type 0 ensamblada a partir de un TTF registrado no tiene sentido para un lector que no puede encontrar los contornos. Para contenido mixto, la opción duradera es una cara de amplia cobertura (las familias Noto y Arial Unicode MS son las respuestas habituales), incrustada y con subconjunto.

Los scripts de derecha a izquierda y los complejos añaden una capa de conformación sobre la cobertura. HotPDF expone RtLTextOut para árabe y hebreo, que gestiona la reordenación direccional para que pases el orden lógico y dejes que la biblioteca lo componga. Hacer bien el árabe es cobertura más conformación más dirección, tres cosas distintas, y un cuadro puede significar que falló cualquiera de ellas.

La tabla ToUnicode: donde vive el copiar y pegar

Todo lo anterior concierne al dibujo. La extracción es la imagen especular y falla por sus propias razones. Un visor renderiza una página usando la asignación de código a glifo de la fuente, pero cuando un usuario selecciona texto y lo copia, el visor necesita volver a convertir esos mismos códigos a Unicode. Esa asignación inversa es el CMap ToUnicode, un flujo opcional adjunto a la fuente.

Cuando está presente y es correcto, el texto copiado sale como los caracteres correctos. Cuando falta o es incorrecto, o la fuente se creó como subconjunto con códigos de glifo personalizados y no se escribió ningún ToUnicode, la página se ve perfecta y el portapapeles se llena de caracteres sin sentido: los códigos de glifo se leen como si fueran Unicode, lo que para un subconjunto codificado a medida no son. Por eso un documento escaneado con una capa de texto OCR puede ser buscable mientras que un PDF de origen digital de un generador descuidado no lo es. El renderizado y la extracción utilizan tablas distintas, por lo que un fichero puede satisfacer uno y fallar el otro. Si la extracción importa para tu salida, trata un mapa ToUnicode correcto como un requisito, y verifícalo copiando texto de una muestra en lugar de confiar en que está ahí.

Cómo diagnosticar un error de fuente rápidamente

El modo de fallo te indica dónde mirar. Los cuadros vacíos en otra máquina casi siempre significan una fuente que no estaba incrustada, así que comprueba primero la incrustación y después la cobertura de glifos. Los cuadros que aparecen incluso en tu propia máquina apuntan a la cobertura: la fuente no contiene ese script, independientemente de la incrustación. El texto que se renderiza correctamente pero al copiarlo sale como caracteres sin sentido es un problema de ToUnicode, no de renderizado, y modificar las fuentes o la incrustación no lo solucionará porque el dibujo nunca estuvo roto. Para leer un fichero terminado, ábrelo en Acrobat y mira en Propiedades del documento, Fuentes: una entrada sana muestra el tipo, indica Embedded o Embedded Subset, y nombra la codificación. Una fuente que debería estar incrustada y no lo está se anuncia ahí antes de que lo haga un cliente.

Nada de esto es exótico una vez que queda clara la separación entre carácter, código y glifo. Incrusta las fuentes con las que dibujas, crea subconjuntos de las grandes, usa una cara Unicode y RegisterUnicodeTTF en el momento en que el texto salga del latín, y mantén un mapa ToUnicode correcto si alguien va a extraer el texto. Haz bien eso y los cuadros dejan de aparecer. Para la mecánica circundante, la anatomía de un PDF mínimo muestra dónde se sitúa el diccionario de fuente en el árbol de objetos, y el recorrido por la estructura del documento cubre cómo se comparten los recursos entre páginas.

Las llamadas SetFont, FontEmbedding y RegisterUnicodeTTF mostradas aquí forman parte del componente HotPDF para Delphi y C++Builder.