PDF Library for Delphi acepta las fuentes que un proyecto web ya trae consigo. Importa contenedores WOFF2, enumera los ejes de diseño de una fuente variable como JSON, e instancia un conjunto elegido de coordenadas de eje en una fuente estática que las rutas ordinarias de incrustación, subconjuntado y extracción manejan sin saber nada sobre variaciones. Se cubren tanto los contornos TrueType como los contornos CFF2
La situación que esto resuelve es común e irritante. La tipografía de una marca se define en un design system, el design system distribuye archivos WOFF2 variables, y el generador de PDF no puede leer ni el contenedor ni los datos de variación. La solución habitual — pedirle a la fundición instancias TTF estáticas de cada peso realmente usado — lleva semanas y produce archivos que nadie mantiene sincronizados
¿Por qué WOFF2 no es simplemente una fuente comprimida?
Tratar WOFF2 como un envoltorio Brotli y descomprimirlo te da bytes que no son un sfnt válido. El formato también permite tablas transformadas y reordenadas: glyf, loca y hmtx pueden almacenarse en una forma reorganizada que debe revertirse antes de que la fuente sea analizable. Un descompresor de propósito general no puede hacer eso
En Windows, la plataforma ofrece análisis y desempaquetado de contenedores desde Windows 10 versión 1703, así que la biblioteca usa la implementación del sistema en lugar de distribuir otro decodificador. Esa decisión mantiene el despliegue libre de una DLL adicional y hace que la reversión de la transformación la maneje el mismo código que usa la propia pila de texto del sistema operativo. En otras plataformas, WOFF1 sigue transitando por una ruta Pascal pura y WOFF2 se reporta explícitamente como no soportado, lo cual es una respuesta honesta en lugar de una fuente silenciosamente corrupta
La ruta de importación valida antes de decodificar y después: la cabecera de 48 bytes, la longitud del contenedor, los campos reservados, el tamaño sfnt declarado y un tope de 512 MiB se comprueban primero, y el tamaño del flujo decodificado se compara con la declaración después. Una fuente web es contenido descargado, y los dos modos de fallo que vale la pena bloquear son un desajuste de tamaño y una bomba de descompresión
uses
PDFlibrary;
var
Lib: TPDFlib;
FontID, InstanceID: Integer;
AxesJSON: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.NewDocument;
Lib.SetPageSize('A4');
Lib.NewPage;
// WOFF2, WOFF1, TTF, OTF y TTC entran todos por la misma llamada
FontID := Lib.AddOpenTypeFontFromFile('Inter.woff2', 0);
if FontID <= 0 then
raise Exception.Create('font could not be imported');
// ¿Qué puede hacer realmente esta fuente?
if Lib.GetFontVariationAxes(FontID, AxesJSON) = 1 then
Log(AxesJSON); // tags, mínimo, por defecto, máximo por eje
// Fija una ubicación de diseño concreta en una fuente estática
InstanceID := Lib.CreateOpenTypeFontVariation(FontID,
'wght=620,wdth=87.5');
Lib.DrawOpenTypeText(56, 760, InstanceID, 24, 'Quarterly results', 0);
Lib.SaveToFile('report.pdf');
finally
Lib.Free;
end;
end;
Qué tiene que reconstruir la instanciación, y qué elimina
Una fuente variable almacena un contorno por defecto por glifo más deltas indexados por posición en el espacio de diseño. Producir una instancia estática significa interpolar esos deltas en las coordenadas solicitadas, mover los puntos, y luego recalcular todo lo que se deriva de ellos
Para los contornos TrueType eso significa analizar fvar, avar, gvar, HVAR y MVAR, aplicar deltas de tupla con numeración de puntos compartida y privada, e interpolar los puntos que no llevan delta explícito. Los puntos fantasma horizontales junto con HVAR determinan el ancho de avance y el lado izquierdo. Los glifos compuestos necesitan que sus dependencias de componentes y sus mapeos de puntos se resuelvan primero, y luego se apliquen la traslación, la escala, la matriz y los desplazamientos de anclaje — con límites de profundidad y detección de ciclos, porque un grafo de componentes malicioso, de lo contrario, recursionaría sin fin
Los metadatos también se mueven, y esta es la parte que la mayoría de las implementaciones se saltan. MVAR actualiza las métricas globales en hhea, OS/2 y post, de modo que el interlineado, la posición del subrayado y el ángulo itálico coincidan con la instancia y no con el valor por defecto. Los ejes wght, wdth, ital y slnt además actualizan la clase de peso, la clase de anchura, los flags de selección, macStyle y el ángulo itálico, de modo que una herramienta que inspeccione la fuente estática reporte el estilo que pediste. Una fuente cuya geometría es negrita y cuyos metadatos dicen regular quedará mal etiquetada en todas partes aguas abajo
La salida entonces aplana los contornos compuestos y elimina las tablas que ya no aplican: las tablas de variación, los hints, las tablas de dispositivo y cualquier firma. Los límites de los glifos, las métricas de resumen de hhea, los topes de hint de maxp, los checksums por tabla y el checksum de la fuente completa se recalculan todos
CFF2 toma una ruta distinta hacia el mismo destino
CFF2 no puede incrustarse directamente como un programa de fuente PDF: sus charstrings no llevan anchos, y depende de las tablas OpenType circundantes para el orden de glifos y las métricas. Así que instanciar una fuente CFF2 produce en su lugar una fuente OpenType CFF con clave CID, que las rutas ya existentes de dibujo, incrustación y subconjuntado manejan sin cambios
Cargar una fuente CFF2 produce de inmediato una instancia estática de coordenadas por defecto para el documento actual, mientras el sfnt CFF2 original se conserva en el objeto de fuente, de modo que la enumeración de ejes y la instanciación posterior siguen viendo el espacio de diseño completo. Cada glifo se de-subrutina individualmente durante la instanciación, lo cual elimina por completo la aritmética de variación y permite que el subconjuntador existente funcione sin cambios. El coste es que una instancia estática puede ser ligeramente más grande que una que conservara subrutinas compartidas — un precio justo por no necesitar una segunda implementación de subconjuntado
Verificar una instancia antes de confiar en ella
La instanciación es el tipo de función que puede estar sutilmente mal de formas que ninguna excepción reporta. Tres comprobaciones detectan casi todo
Exporta la instancia y ábrela en una herramienta de fuentes. SaveFontToFile escribe el programa de fuente conservado, así que una instancia en memoria puede volcarse sin producir primero un PDF. El archivo exportado debería analizarse sin errores, no contener tablas de variación, y reportar los metadatos de estilo que solicitaste
Compara los avances en una ubicación distinta de la que trae por defecto. Si tu pipeline puede producir la misma instancia de otra forma, compara los anchos de avance y los límites de curva glifo por glifo; la implementación de referencia aquí coincidió con una instanciación independiente de una fuente de dos ejes y 313 glifos, con una diferencia máxima de cero en cada glifo
Y extrae de vuelta el texto del PDF generado. Una instancia que se incrusta pero no produce texto extraíble ha perdido su mapeo ToUnicode, lo cual importa para la búsqueda y la accesibilidad mucho después de que pasara la comprobación visual. La ruta de extracción más amplia se trata en extracción de texto, imágenes y fuentes
// Vuelca una instancia generada para inspección o reutilización
Lib.SaveFontToFile('Inter-620-87.ttf');
// Pedir coordenadas fuera del rango declarado hace que se recorten,
// así que lee el JSON de ejes en vez de asumir los límites de una fuente
// {"axes":[{"tag":"wght","min":100,"default":400,"max":900}, ...]}
Notas prácticas para un pipeline de fuente de marca
Cachea las instancias por conjunto de coordenadas. Cada conjunto distinto de coordenadas es un programa de fuente distinto, así que un documento que mezcla el peso 400 y el peso 620 incrusta dos subconjuntos en lugar de un único tipo compartido. Generar la misma instancia repetidamente es trabajo desperdiciado; generar decenas de pesos casi idénticos es tamaño de archivo desperdiciado
Mide con la instancia con la que vas a dibujar. Una instancia condensada tiene avances más estrechos que la instancia por defecto, así que cualquier maquetación que midiera antes de la instanciación midió los anchos equivocados. Esta es la causa más común, con diferencia, de un texto que se desborda de su caja solo en producción
Conserva una ruta de reserva para los glifos que le falten a la fuente de tu marca. Una fuente de marca variable normalmente cubre el latín y poco más, y un informe que contenga un nombre de cliente en cirílico mostrará de otro modo glifos ausentes; el mecanismo de reserva automática se describe en reserva automática de fuentes para glifos ausentes. Y cuando el objetivo es un archivo más pequeño y no un peso concreto, el subconjuntado hace más trabajo que la instanciación, como se trata en optimización del tamaño de archivo PDF y subconjuntado de fuentes
La importación de fuentes web, la instanciación variable, el subconjuntado y la reserva forman parte del mismo pipeline de fuentes para Delphi, C++Builder y Free Pascal; la lista completa de funciones está en la página de PDF Library for Delphi