PDF no tiene concepto de fuente variable. Una fuente incrustada en un archivo PDF es un conjunto fijo de contornos con métricas fijas, así que una fuente variable debe reducirse a una única instancia estática antes de poder incluirse en un documento. HotPDF realiza esa instanciación internamente: se inspeccionan los ejes de una fuente variable, se seleccionan coordenadas como un grosor de 620 o un ancho de 87,5, y la biblioteca fija esos valores en un programa de fuente completo y autocontenido que cualquier lector de PDF conforme puede renderizar
El motivo por el que esto importa es práctico, no teórico. Cada vez más fundiciones tipográficas distribuyen un único archivo variable en lugar de una docena de grosores estáticos, y los equipos de diseño eligen valores que ninguna instancia con nombre ofrece. Sin instanciación, un generador de informes o bien recurre a la instancia predeterminada, descartando así la decisión de diseño, o incrusta la fuente variable completa y confía en que el visor respete unas coordenadas de eje que no tiene forma de conocer, algo que ningún lector está obligado a hacer
¿Qué tiene que reconstruir realmente la instanciación?
Una fuente variable OpenType almacena un contorno predeterminado por glifo más un conjunto de deltas indexados por posición en el espacio de diseño. Aplicar una coordenada de eje no consiste en escribir un número en una cabecera; implica recorrer la tabla gvar, interpolar los deltas para la ubicación solicitada, mover puntos y después recalcular todo lo que se derivaba de esos puntos. HotPDF reconstruye los contornos de los glifos, la tabla loca larga, las métricas horizontales y verticales completas, el cuadro delimitador global de la fuente y el ajuste de suma de comprobación sfnt
Igual de importante es lo que se elimina. Una instancia estática no debe conservar fvar, avar, gvar, HVAR, VVAR, MVAR, STAT ni cvar, y una DSIG obsoleta también debe desaparecer, ya que los bytes firmados ya no existen. Dejar cualquiera de estos elementos produce una fuente que afirma ser variable mientras lleva contornos que ya se han movido, y los lectores que sí apliquen variaciones las aplicarán entonces una segunda vez
Puntos fantasma y la trampa de la doble aplicación
La regla más sutil de todo el proceso tiene que ver con las métricas. En gvar, el recuento de puntos de un glifo cubre los puntos del contorno, o los puntos de componente en el caso de un glifo compuesto, más cuatro puntos fantasma que codifican el margen lateral izquierdo, el ancho de avance y sus equivalentes verticales. Esos puntos fantasma están a su vez sujetos a deltas
Así, cuando una fuente tiene una tabla gvar, HotPDF deriva las métricas horizontales y verticales a partir de los puntos fantasma interpolados y no aplica además HVAR ni VVAR. Aplicar ambas cosas es el error clásico: la misma variación se aplica dos veces y cada ancho de avance resulta ligeramente demasiado ancho, lo que se manifiesta como un texto que va desplazándose gradualmente hacia la derecha a lo largo de una línea justificada. Solo cuando una fuente carece de gvar, la biblioteca fija el almacén de variación de métricas directamente en hmtx o vmtx
Dos detalles más mantienen la geometría fiel. Los puntos fantasma nunca participan en la interpolación de contornos, así que los puntos no listados explícitamente para un glifo simple se infieren mediante IUP por contorno, excluyendo los puntos fantasma. Y los glifos compuestos aplican sus deltas a los desplazamientos de componente que usan parámetros XY, tras lo cual los límites de los hijos se recalculan de forma recursiva. Esa recursión tiene una profundidad acotada y se comprueba frente a ciclos, porque un grafo de componentes malintencionado o simplemente defectuoso podría, de lo contrario, recurrir sin fin
Inspeccionar el espacio de diseño antes de elegir
La primera llamada de cualquier flujo de instanciación es InspectVariableFont, que informa de los ejes y de las instancias con nombre que definió la fundición. Los registros de eje llevan la etiqueta de cuatro bytes, los valores mínimo, predeterminado y máximo, indicadores y un ID de nombre; las instancias con nombre llevan un ID de nombre de subfamilia, indicadores, un ID de nombre PostScript opcional y una coordenada por eje:
var
Pdf: THotPDF;
Axes: THPDFVariableFontAxisArray;
Instances: THPDFVariableFontNamedInstanceArray;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.InspectVariableFont('C:\Fonts\Inter.ttf', Axes, Instances) then
begin
for I := 0 to High(Axes) do
Writeln(Format('%s min=%.1f default=%.1f max=%.1f',
[string(Axes[I].Tag), Axes[I].MinimumValue,
Axes[I].DefaultValue, Axes[I].MaximumValue]));
Writeln(Format('%d named instance(s) defined', [Length(Instances)]));
end
else
Writeln('not a variable font - embed it as an ordinary TrueType face');
finally
Pdf.Free;
end;
end;
Informar del rango de ejes importa porque los valores de eje se limitan al rango que declara la fuente, no al rango que ofrece la interfaz de usuario. Un control deslizante que permita a un usuario solicitar un grosor de 1000 en una fuente cuyo eje wght se detiene en 900 debería corregirse en la interfaz, no de forma silenciosa en la capa de fuente, o la salida impresa no coincidirá con la vista previa
Seleccionar coordenadas y generar el documento
La selección de ejes es un estado que se aplica a las fuentes registradas a partir de ese momento. SetVariableFontAxis recibe una etiqueta ASCII imprimible de cuatro bytes y un valor finito, y rechaza cualquier otra cosa con una excepción en lugar de ignorarla silenciosamente. ClearVariableFontAxes restablece la selección, y GetVariableFontAxisSelections informa de lo que está pendiente en cada momento, algo que merece la pena registrar en motores de informes donde varias rutas de código pueden haber tocado el mismo objeto de documento. La familia en sí se selecciona por nombre mediante SetFont, exactamente igual que cualquier otra fuente TrueType incrustada:
begin
Pdf := THotPDF.Create(nil);
try
Pdf.BeginDoc;
Pdf.SetVariableFontAxis('wght', 620); // seminegrita, no es una instancia con nombre
Pdf.SetVariableFontAxis('wdth', 87.5); // ligeramente condensada
Pdf.CurrentPage.SetFont('Inter', [], 11);
Pdf.CurrentPage.TextOut(72, 720, 0, 'Quarterly results');
Pdf.ClearVariableFontAxes; // vuelve a la instancia predeterminada
Pdf.CurrentPage.SetFont('Inter', [], 10);
Pdf.CurrentPage.TextOut(72, 700, 0, 'Prepared by the finance team');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
El evento OnVariableFontInstance se dispara a medida que se produce cada instancia e informa de los valores de eje utilizados, la forma más económica de demostrar en un registro qué contiene realmente un PDF concreto. Como cada conjunto de coordenadas distinto produce un programa de fuente distinto, conviene tratar las selecciones de eje como parte de la clave de caché de fuentes; la mecánica de caché se describe en la caché persistente de subconjuntos de fuentes
Cómo interactúa la instanciación con la subdivisión y la composición tipográfica
La instanciación se ejecuta antes de la subdivisión, y ese orden es el correcto. La fuente instanciada es una fuente TrueType estática normal, así que el subdivisor habitual la trata como a cualquier otra: calcula el cierre de glifos, conserva los glifos que el documento realmente usa y descarta el resto. La interacción que conviene tener presente es que dos selecciones de eje distintas de la misma familia son dos programas de fuente distintos, así que un documento que mezcla el grosor 400 y el grosor 620 incrusta dos subconjuntos, no una única fuente compartida con dos instancias
La composición tipográfica no se ve afectada en principio, aunque conviene comprobarlo en la práctica. Las características de composición residen en GSUB y GPOS, que la instanciación conserva, así que las ligaduras y los alternantes estilísticos siguen funcionando como se describe en los alternantes estilísticos GSUB de OpenType. Lo que cambia es el posicionamiento: una instancia condensada tiene avances más estrechos que la predeterminada, así que cualquier disposición que midiera el texto antes de la instanciación midió anchos incorrectos. Si se mide con la misma selección de eje con la que se va a renderizar, la discrepancia desaparece
Una última nota defensiva de la implementación, útil para quien amplíe esta vía. Las fuentes sin métricas verticales igualmente evalúan los argumentos de array dinámico en el punto de llamada de Delphi, así que los arrays en tiempo de análisis siempre se reservan en lugar de confiar en una comprobación HasVerticalMetrics para evitar un índice vacío mediante cortocircuito. Es el tipo de detalle a nivel de lenguaje que convierte una rama aparentemente protegida en una violación de acceso justo en las fuentes con las que no se hicieron pruebas
El soporte de fuentes variables encaja en el mismo flujo de fuentes que la incrustación, la subdivisión y el cierre de glifos, descrito con más detalle en el cierre de subconjuntos de fuente y los glifos compuestos. El conjunto completo de funciones tipográficas para Delphi y C++Builder aparece en la página del componente PDF para Delphi de HotPDF