A PDF Library for Delphi aceita as fontes que um projeto web já usa. Ela importa contêineres WOFF2, enumera os eixos de design de uma fonte variável como JSON, e instancia um conjunto escolhido de coordenadas de eixo em uma fonte estática que os caminhos comuns de incorporação, subsetagem e extração tratam sem saber nada sobre variações. Tanto os contornos TrueType quanto os contornos CFF2 são cobertos
A situação que isso resolve é comum e irritante. A tipografia de uma marca é definida em um design system, o design system distribui arquivos WOFF2 variáveis, e o gerador de PDF não consegue ler nem o contêiner nem os dados de variação. A solução alternativa habitual — pedir à fundição instâncias TTF estáticas de todo peso realmente usado — leva semanas e produz arquivos que ninguém mantém sincronizados
Por que WOFF2 não é apenas uma fonte comprimida?
Tratar WOFF2 como um wrapper Brotli e descomprimi-lo dá a você bytes que não são um sfnt válido. O formato também permite tabelas transformadas e reordenadas: glyf, loca e hmtx podem ser armazenadas em uma forma reorganizada que precisa ser revertida antes que a fonte seja analisável. Um descompressor de uso geral não consegue fazer isso
No Windows, a plataforma fornece análise e desempacotamento de contêiner desde o Windows 10 versão 1703, então a biblioteca usa a implementação do sistema em vez de distribuir outro decodificador. Essa decisão mantém a implantação livre de uma DLL extra e significa que a reversão de transformação é tratada pelo mesmo código que a própria pilha de texto do sistema operacional usa. Em outras plataformas, o WOFF1 ainda percorre um caminho Pascal puro e o WOFF2 explicitamente relata como não suportado, o que é uma resposta honesta em vez de uma fonte silenciosamente corrompida
O caminho de importação valida antes de decodificar e depois: o cabeçalho de 48 bytes, o comprimento do contêiner, os campos reservados, o tamanho sfnt declarado e um teto de 512 MiB, tudo é verificado primeiro, e o tamanho do fluxo decodificado é comparado com a declaração depois. Uma fonte web é conteúdo baixado, e os dois modos de falha que vale a pena bloquear são uma incompatibilidade de tamanho e uma bomba de descompressão
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 e TTC entram todos pela mesma chamada
FontID := Lib.AddOpenTypeFontFromFile('Inter.woff2', 0);
if FontID <= 0 then
raise Exception.Create('font could not be imported');
// O que essa fonte realmente consegue fazer?
if Lib.GetFontVariationAxes(FontID, AxesJSON) = 1 then
Log(AxesJSON); // tags, mínimo, padrão, máximo por eixo
// Fixa uma localização de design em uma fonte 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;
O que a instanciação precisa reconstruir, e o que ela remove
Uma fonte variável armazena um contorno padrão por glifo mais deltas indexados pela posição no espaço de design. Produzir uma instância estática significa interpolar esses deltas nas coordenadas solicitadas, mover os pontos e depois recalcular tudo o que deriva deles
Para contornos TrueType isso significa analisar fvar, avar, gvar, HVAR e MVAR, aplicar deltas de tupla com numeração de pontos compartilhada e privada, e interpolar os pontos que não carregam nenhum delta explícito. Pontos fantasmas horizontais, junto com HVAR, determinam a largura de avanço e o side bearing esquerdo. Glifos compostos precisam ter suas dependências de componente e mapeamentos de ponto resolvidos primeiro, depois translação, escala, matriz e deslocamentos de anexação aplicados — com limites de profundidade e detecção de ciclos, porque um grafo de componentes malicioso do contrário recursaria sem fim
Os metadados também se movem, e essa é a parte que a maioria das implementações pula. MVAR atualiza as métricas globais em hhea, OS/2 e post, de modo que o espaçamento de linha, a posição de sublinhado e o ângulo itálico correspondam à instância, e não ao padrão. Os eixos wght, wdth, ital e slnt adicionalmente atualizam a classe de peso, a classe de largura, as flags de seleção, o macStyle e o ângulo itálico, de modo que uma ferramenta que inspeciona a fonte estática relata o estilo que você pediu. Uma fonte cuja geometria é negrito e cujos metadados dizem regular será rotulada errado em toda parte adiante
A saída então achata contornos compostos e remove as tabelas que não se aplicam mais: as tabelas de variação, hints, tabelas de dispositivo e qualquer assinatura. Os limites de glifo, as métricas resumidas de hhea, os tetos de hint de maxp, os checksums por tabela e o checksum da fonte inteira são todos recalculados
O CFF2 percorre um caminho diferente até o mesmo lugar
O CFF2 não pode ser incorporado diretamente como um programa de fonte PDF: seus charstrings não carregam larguras, e ele depende das tabelas OpenType ao redor para ordem de glifo e métricas. Então instanciar uma fonte CFF2 produz, em vez disso, uma fonte OpenType CFF indexada por CID, que os caminhos existentes de desenho, incorporação e subsetagem já tratam
Carregar uma fonte CFF2 produz imediatamente uma instância estática de coordenada padrão para o documento atual, enquanto o sfnt CFF2 original é retido no objeto de fonte, de modo que a enumeração de eixos e a instanciação posterior ainda enxergam o espaço de design completo. Cada glifo tem suas sub-rotinas removidas individualmente durante a instanciação, o que elimina inteiramente a aritmética de variação e permite que o subsetter existente funcione sem mudanças. O custo é que uma instância estática pode ser ligeiramente maior do que uma que manteve sub-rotinas compartilhadas — um preço justo por não precisar de uma segunda implementação de subsetagem
Verificando uma instância antes de confiar nela
A instanciação é o tipo de recurso que pode estar sutilmente errado de formas que nenhuma exceção relata. Três verificações capturam quase tudo
Exporte a instância e abra-a em uma ferramenta de fonte. SaveFontToFile grava o programa de fonte retido, de modo que uma instância em memória pode ser despejada sem antes produzir um PDF. O arquivo exportado deve ser analisado sem erros, não conter nenhuma tabela de variação, e relatar os metadados de estilo que você solicitou
Compare avanços em uma localização não padrão. Se seu pipeline conseguir produzir a mesma instância de outra forma, compare larguras de avanço e limites de curva glifo por glifo; a implementação de referência aqui bateu com uma instanciação independente de uma fonte de dois eixos com 313 glifos, com uma diferença máxima de zero em todo glifo
E extraia o texto de volta do PDF gerado. Uma instância que incorpora, mas produz texto não extraível, perdeu seu mapeamento ToUnicode, o que importa para busca e acessibilidade muito depois de a verificação visual ter passado. O caminho de extração mais amplo é abordado em extração de texto, imagem e fonte
// Despeja uma instância gerada para inspeção ou reutilização
Lib.SaveFontToFile('Inter-620-87.ttf');
// Pedir coordenadas fora do intervalo declarado as limita (clamp),
// então leia o JSON de eixos em vez de supor os limites de uma fonte
// {"axes":[{"tag":"wght","min":100,"default":400,"max":900}, ...]}
Notas práticas para um pipeline de fonte de marca
Armazene instâncias em cache por conjunto de coordenadas. Cada conjunto distinto de coordenadas é um programa de fonte distinto, então um documento misturando peso 400 e peso 620 incorpora dois subsets em vez de uma face compartilhada. Gerar a mesma instância repetidamente é trabalho desperdiçado; gerar dezenas de pesos quase idênticos é tamanho de arquivo desperdiçado
Meça com a instância com a qual você vai desenhar. Uma instância condensada tem avanços mais estreitos do que o padrão, então qualquer layout medido antes da instanciação mediu as larguras erradas. Essa é a causa isolada mais comum de texto que transborda de sua caixa apenas em produção
Mantenha um caminho alternativo para os glifos que a fonte da sua marca não tem. Uma fonte de marca variável tipicamente cobre latim e pouco mais, e um relatório contendo um nome de cliente em cirílico do contrário mostrará glifos ausentes; o mecanismo automático de fallback é descrito em fallback automático de fonte para glifos ausentes. E quando o objetivo é um arquivo menor em vez de um peso específico, a subsetagem faz mais trabalho do que a instanciação, como abordado em otimização de tamanho de arquivo PDF e subsetagem de fontes
A importação de fonte web, a instanciação variável, a subsetagem e o fallback fazem parte do mesmo pipeline de fontes para Delphi, C++Builder e Free Pascal; a lista completa de recursos está na página da PDF Library for Delphi