O PDF não tem qualquer noção de fonte variável. Uma fonte incorporada num ficheiro PDF é um conjunto fixo de contornos com métricas fixas, pelo que uma fonte variável tem de ser reduzida a uma instância estática antes de poder entrar num documento. O HotPDF realiza essa instanciação internamente: inspeciona-se os eixos de uma fonte variável, selecionam-se coordenadas como peso 620 ou largura 87,5, e a biblioteca grava esses valores num programa de fonte completo e autónomo que qualquer leitor de PDF conforme consegue representar
A razão pela qual isto importa é prática, não teórica. As fundições tipográficas enviam cada vez mais um único ficheiro variável em vez de uma dezena de pesos estáticos, e as equipas de design escolhem valores que nenhuma instância nomeada fornece. Sem instanciação, um gerador de relatórios ou recua para a instância predefinida, o que deita fora a decisão de design, ou incorpora a fonte variável inteira e espera que o visualizador respeite coordenadas de eixo que não tem forma de conhecer, algo que nenhum leitor é obrigado a fazer
O que é que a instanciação tem de facto de reconstruir?
Uma fonte variável OpenType guarda um contorno predefinido por glifo, mais um conjunto de deltas indexados pela posição no espaço de design. Aplicar uma coordenada de eixo não é escrever um número num cabeçalho; significa percorrer a tabela gvar, interpolar os deltas para a localização pedida, mover pontos e depois recalcular tudo o que foi derivado desses pontos. O HotPDF reconstrói os contornos dos glifos, a tabela loca longa, as métricas horizontais e verticais completas, a caixa delimitadora global da fonte e o ajuste de soma de verificação sfnt
Tão importante quanto isso é o que é removido. Uma instância estática não pode manter fvar, avar, gvar, HVAR, VVAR, MVAR, STAT nem cvar, e uma DSIG obsoleta também tem de desaparecer, já que os bytes assinados deixam de existir. Deixar qualquer uma destas tabelas produz uma fonte que afirma ser variável mas transporta contornos que já foram movidos, e os leitores que efetivamente aplicam variações vão aplicá-las uma segunda vez
Pontos fantasma, e a armadilha da dupla aplicação
A regra mais subtil de todo o processo diz respeito às métricas. Em gvar, a contagem de pontos de um glifo abrange os pontos do contorno, ou os pontos de componente no caso de um glifo composto, mais quatro pontos fantasma que codificam o avanço lateral esquerdo, a largura de avanço e os respetivos equivalentes verticais. Esses pontos fantasma estão eles próprios sujeitos a deltas
Assim, quando uma fonte tem uma tabela gvar, o HotPDF deriva as métricas horizontais e verticais a partir dos pontos fantasma interpolados e não aplica adicionalmente HVAR nem VVAR. Aplicar ambos é o erro clássico: a mesma variação é aplicada duas vezes e cada largura de avanço sai ligeiramente demasiado larga, o que se manifesta como texto que vai gradualmente deslizando para a direita ao longo de uma linha justificada. Só quando uma fonte não tem gvar é que a biblioteca grava o armazém de variação de métricas diretamente em hmtx ou vmtx
Dois detalhes adicionais mantêm a geometria correta. Os pontos fantasma nunca participam na interpolação de contornos, pelo que os pontos não explicitamente listados para um glifo simples são inferidos com IUP por contorno, excluindo os pontos fantasma. E os glifos compostos têm os seus deltas aplicados aos deslocamentos de componente que usam parâmetros XY, após o que os limites dos filhos são recalculados recursivamente. Essa recursão tem profundidade limitada e é verificada quanto a ciclos, porque um grafo de componentes hostil ou simplesmente com defeito pode, de outro modo, recursar sem fim
Inspecionar o espaço de design antes de escolher
A primeira chamada em qualquer fluxo de instanciação é InspectVariableFont, que reporta os eixos e as instâncias nomeadas que a fundição definiu. Os registos de eixo transportam a etiqueta de quatro bytes, os valores mínimo, predefinido e máximo, sinalizadores e um ID de nome; as instâncias nomeadas transportam um ID de nome de subfamília, sinalizadores, um ID de nome PostScript opcional e uma coordenada por eixo:
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 o intervalo de eixo importa porque os valores de eixo são limitados ao intervalo que a fonte declara, não ao intervalo que a interface oferece. Um cursor que permita a um utilizador pedir peso 1000 numa fonte cujo eixo wght termina em 900 deve ser corrigido na interface, não silenciosamente na camada da fonte, ou o resultado impresso vai divergir da pré-visualização
Selecionar coordenadas e emitir o documento
A seleção de eixo tem estado e aplica-se a fontes registadas posteriormente. SetVariableFontAxis recebe uma etiqueta ASCII imprimível de quatro bytes e um valor finito, e rejeita qualquer outra coisa com uma exceção em vez de a ignorar silenciosamente. ClearVariableFontAxes repõe a seleção, e GetVariableFontAxisSelections reporta o que está atualmente pendente, algo que vale a pena registar em motores de relatórios onde vários caminhos de código podem ter tocado no mesmo objeto de documento. A própria família é selecionada por nome através de SetFont, exatamente como qualquer outra face TrueType incorporada:
begin
Pdf := THotPDF.Create(nil);
try
Pdf.BeginDoc;
Pdf.SetVariableFontAxis('wght', 620); // semibold, não uma instância nomeada
Pdf.SetVariableFontAxis('wdth', 87.5); // ligeiramente condensada
Pdf.CurrentPage.SetFont('Inter', [], 11);
Pdf.CurrentPage.TextOut(72, 720, 0, 'Quarterly results');
Pdf.ClearVariableFontAxes; // volta à instância predefinida
Pdf.CurrentPage.SetFont('Inter', [], 10);
Pdf.CurrentPage.TextOut(72, 700, 0, 'Prepared by the finance team');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
O evento OnVariableFontInstance dispara-se à medida que cada instância é produzida e reporta os valores de eixo que foram usados, o que é a forma mais económica de comprovar num registo o que um determinado PDF contém de facto. Como cada conjunto de coordenadas distinto produz um programa de fonte distinto, trate as seleções de eixo como parte da chave da sua cache de fontes; os mecanismos de cache estão descritos em a cache persistente de subconjuntos de fontes
Como a instanciação interage com o subconjunto e a composição
A instanciação corre antes do subconjunto (subsetting), e essa ordem é a correta. A fonte instanciada é uma face TrueType estática normal, pelo que o subsetter comum a trata como a qualquer outra: calcula o fecho de glifos, mantém os glifos que o documento realmente usa e descarta o resto. A interação a ter em conta é que duas seleções de eixo diferentes da mesma família são dois programas de fonte diferentes, pelo que um documento que mistura peso 400 e peso 620 incorpora dois subconjuntos, não uma face partilhada com duas instâncias
A composição (shaping) não é afetada em princípio, e vale a pena verificá-lo na prática. As funcionalidades de layout residem em GSUB e GPOS, que a instanciação preserva, pelo que os ligamentos e as alternativas estilísticas continuam a funcionar conforme descrito em alternativas estilísticas GSUB do OpenType. O que muda é o posicionamento: uma instância condensada tem avanços mais estreitos do que a predefinida, pelo que qualquer layout que tenha medido o texto antes da instanciação mediu as larguras erradas. Meça com a mesma seleção de eixo com que vai renderizar, e a discrepância desaparece
Uma última nota defensiva da implementação, útil para quem estender este caminho. As fontes sem métricas verticais continuam a avaliar argumentos de array dinâmico no ponto de chamada em Delphi, pelo que os arrays em tempo de análise são sempre alocados em vez de se confiar numa verificação HasVerticalMetrics para saltar um índice vazio. É o tipo de detalhe ao nível da linguagem que transforma um ramo aparentemente protegido numa violação de acesso exatamente nas fontes com que não testou
O suporte a fontes variáveis integra-se no mesmo pipeline de fontes que a incorporação, o subconjunto e o fecho de glifos, descrito com mais profundidade em fecho de subconjunto de fontes e glifos compostos. O conjunto completo de funcionalidades tipográficas para Delphi e C++Builder está listado na página do componente PDF para Delphi HotPDF