Artículo técnico

Fuentes variables en PDF: instanciación estática en Delphi

PDF no tiene ningún concepto de fuente variable. Una fuente incrustada en un archivo PDF es un conjunto fijo de contornos con métricas fijas, de modo que una fuente variable debe reducirse a una sola 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 peso 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 PDF conforme puede renderizar

El motivo por el que esto importa es práctico, no teórico. Las fundiciones tipográficas cada vez distribuyen más un solo archivo variable en lugar de una docena de pesos 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, lo que descarta la decisión de diseño, o incrusta toda la fuente variable y confía en que el visor respete coordenadas de eje que no tiene forma de conocer, algo que ningún lector está obligado a hacer

¿Qué es lo que la instanciación realmente tiene que reconstruir?

Una fuente variable de 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 un encabezado; significa recorrer la tabla gvar, interpolar los deltas para la ubicación solicitada, mover puntos y luego recalcular todo lo que se derivaba de esos puntos. HotPDF reconstruye los contornos de los glifos, la tabla larga loca, las métricas horizontales y verticales completas, el cuadro delimitador global de la fuente y el ajuste de suma de comprobación del 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 eliminarse, ya que los bytes firmados ya no existen. Dejar cualquiera de estas produce una fuente que afirma ser variable mientras lleva contornos que ya fueron desplazados, y los lectores que sí aplican variaciones las aplicarán entonces por segunda vez

Puntos fantasma y la trampa de la doble aplicación

La regla más sutil de todo el proceso concierne a las métricas. En gvar, el conteo de puntos de un glifo cubre los puntos del contorno, o los puntos de componente para 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í que, 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 adicionalmente HVAR ni VVAR. Agregar ambas cosas es el error clásico: la misma variación se aplica dos veces y cada ancho de avance resulta ligeramente demasiado amplio, lo cual se manifiesta como texto que se desplaza gradualmente hacia la derecha a lo largo de una línea justificada. Solo cuando una fuente no tiene gvar, la biblioteca fija el almacén de variación de métricas directamente en hmtx o vmtx

Dos detalles adicionales mantienen la geometría honesta. Los puntos fantasma nunca participan en la interpolación de contornos, de modo que los puntos no listados explícitamente para un glifo simple se infieren con IUP por contorno, excluyendo los puntos fantasma. Y los glifos compuestos reciben sus deltas aplicados a los desplazamientos de componentes 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 verifica contra ciclos, porque un grafo de componentes malicioso o simplemente roto podría, de lo contrario, recursar sin fin

Inspeccionar el espacio de diseño antes de elegir

La primera llamada en cualquier flujo de instanciación es InspectVariableFont, que reporta los ejes y 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;

Reportar el rango de eje importa porque los valores de eje se acotan al rango que declara la fuente, no al rango que ofrece tu interfaz. Un control deslizante que permita a un usuario solicitar un peso 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 fuentes, o el resultado impreso no coincidirá con la vista previa

Selección de coordenadas y emisión del documento

La selección de eje tiene estado y se aplica a las fuentes registradas después. SetVariableFontAxis toma 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 reporta lo que está pendiente en ese momento, algo que vale 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 a través de SetFont, exactamente como cualquier otra fuente TrueType incrustada:

begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.BeginDoc;

    Pdf.SetVariableFontAxis('wght', 620);   // seminegrita, no 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;              // de vuelta 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 y reporta los valores de eje que se usaron, lo cual es la forma más económica de dejar constancia en un registro de lo que realmente contiene un PDF dado. Como cada conjunto de coordenadas distinto genera un programa de fuente distinto, trata las selecciones de eje como parte de la clave de tu 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 el subconjunto y la conformación

La instanciación se ejecuta antes del subconjunto, y ese orden es el correcto. La fuente instanciada es una fuente TrueType estática normal, así que el subconjunto ordinario la trata como a cualquier otra: calcula la clausura de glifos, conserva los glifos que el documento realmente usa y descarta el resto. La interacción a tener en cuenta es que dos selecciones de eje distintas de la misma familia son dos programas de fuente distintos, así que un documento que mezcla el peso 400 y el peso 620 incrusta dos subconjuntos, no una sola fuente compartida con dos instancias

La conformación no se ve afectada en principio, y vale la pena verificarlo en la práctica. Las características de composición residen en GSUB y GPOS, que la instanciación preserva, de modo que las ligaduras y los alternos estilísticos siguen funcionando como se describe en alternos 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 composición que haya medido el texto antes de la instanciación midió los anchos equivocados. Mide con la misma selección de eje con la que vas a renderizar, y la discrepancia desaparece

Una última nota defensiva desde la implementación, útil para quien extienda esta ruta. Las fuentes sin métricas verticales igualmente evalúan los argumentos de arreglo dinámico en el sitio de la llamada Delphi, así que los arreglos en tiempo de análisis siempre se asignan en lugar de depender de una comprobación HasVerticalMetrics para evitar por completo un índice vacío. 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 probó

El soporte de fuentes variables encaja en el mismo flujo de fuentes que la incrustación, el subconjunto y la clausura de glifos, descrito con más profundidad en clausura de subconjuntos de fuentes y glifos conformados. El conjunto completo de funciones tipográficas para Delphi y C++Builder aparece en la página del componente PDF para Delphi HotPDF