Artigo Técnico

Desenhar Tipos de Letra Incorporados em PDFs com o HotPDF em Delphi

O HotPDF desenha os tipos de letra (fontes) incorporados num PDF em Delphi sem necessidade de qualquer instalação no sistema operativo. O fluxo de renderização de glifos em HPDFGlyphRender.pas analisa os programas de fontes integrados no próprio ficheiro PDF — contornos TrueType glyf em FontFile2, strings de caracteres CFF Type 2 (charstrings) em FontFile3 e fluxos de conteúdo de glifos Type 3 —, reproduzindo-os como caminhos vetoriais preenchidos via GDI do Windows. Este artigo apresenta uma análise detalhada da fidelidade das fontes, complementando o artigo sobre conversão de páginas PDF em bitmaps (TBitmap) com o HotPDF

Por que razão as páginas PDF exibem caixas vazias em vez de texto?

A apresentação de caixas vazias, espaços em branco ou caracteres incorretos na conversão de um PDF ocorre habitualmente quando o renderizador solicita os tipos de letra ao sistema operativo em vez de utilizar os dados incorporados no ficheiro. O problema manifesta-se sempre da mesma forma: o documento apresenta-se correto no computador onde foi gerado, mas ao ser aberto num servidor ou ambiente de trabalho com restrições exibe caracteres corrompidos, ou a substituição por uma fonte genérica altera a quebra de linha. Estas fontes residem apenas no PDF e renderizadores que dependem de fontes do sistema não as conseguem mapear. O uso de subconjuntos de fontes (subset fonts) agrava a situação: um subconjunto pode conter apenas quarenta glifos sob mapeamentos específicos criados exclusivamente para esse ficheiro

A norma ISO 32000-1 §9.9 define três contentores para programas de fontes incorporados no descritor: FontFile para fontes Type 1 originais, FontFile2 para TrueType e FontFile3 para CFF (Type1C ou CIDFontType0C) ou OpenType. Um quarto formato, as fontes Type 3 descritas em ISO 32000-1 §9.6.5, não integra dados binários: cada glifo é representado por um pequeno fluxo de conteúdo PDF desenhado na coordenada de visualização. Estes formatos diferem na matemática dos contornos (curvas quadráticas B-splines versus charstrings cúbicas ou operadores gráficos genéricos), requerendo um interpretador específico para cada tipo e um mapeamento que associe os caracteres ao índice de glifo correspondente

Como o HotPDF converte contornos TrueType glyf em caminhos GDI?

A classe THPDFEmbeddedTTF em HPDFGlyphRender.pas consulta a tabela loca para posicionar cada registo de glifo, percorrendo os contornos de glyf ponto a ponto para gerar o caminho GDI correspondente. Há duas regras TrueType cruciais: em primeiro lugar, pontos consecutivos fora da curva definem um ponto intermédio sobre a curva, e caminhos com todos os pontos fora da curva iniciam no ponto médio entre o primeiro e o último nós (ignorar esta lógica deforma os contornos arredondados); em segundo lugar, as curvas TrueType baseiam-se em curvas de Bézier quadráticas, enquanto o método PolyBezierTo da GDI exige cúbicas. Como tal, os segmentos quadráticos sofrem uma elevação de grau matemática exata em vez de serem aproximados por retas:

// Elevação de grau exata: quadrática (P0, Q, P2) -> cúbica (P0, C1, C2, P2)
// C1 = P0 + 2/3 (Q - P0),  C2 = P2 + 2/3 (Q - P2)
C1.X := P0.X + 2 * (Q.X - P0.X) / 3;
C1.Y := P0.Y + 2 * (Q.Y - P0.Y) / 3;
C2.X := P2.X + 2 * (Q.X - P2.X) / 3;
C2.Y := P2.Y + 2 * (Q.Y - P2.Y) / 3;
// PolyBezierTo com C1, C2, P2 — curva geometricamente idêntica

Esta conversão não apresenta perda de qualidade: a cúbica desenha o mesmo contorno, garantindo que o resultado coincide com o visualizado em qualquer zoom. O passo seguinte consiste na escala e posicionamento. Cada glifo é desenhado nas unidades de grelha da fonte (habitualmente 1000 ou 2048 unidades por em), cabendo ao renderizador combinar a matriz de escala, a matriz de texto e a matriz de transformação de visualização (CTM) antes de preencher os contornos. A ordem de combinação das matrizes é crítica: inverter a multiplicação colapsa todos os glifos na origem, resultando numa visualização em branco motivada por uma falha de ordenação algébrica

O interpretador Type 2 charstring para fontes CFF

A classe THPDFEmbeddedCFF disponibiliza um interpretador Type 2 charstring completo para os dados de FontFile3: processa as estruturas CFF INDEX, os dicionários Top DICT e Private DICT, executando as instruções para gerar os caminhos na GDI. Contentores OpenType (tipo OTTO) são abertos para extrair a tabela CFF; fluxos nativos CIDFontType0C e Type1C são lidos diretamente. As charstrings utilizam uma linguagem de pilha (stack language) compacta cuja leitura exige o cumprimento de três convenções: em primeiro lugar, o prefixo de largura opcional permite que o primeiro operador da pilha contenha um operando adicional; em segundo lugar, o operador hintmask assume um vstemhm quando existem operandos na pilha, dependendo os bytes de máscara a ignorar do número de hastes (stems) acumuladas (um erro nesta contagem corrompe os opcodes seguintes); em terceiro lugar, as sub-rotinas exigem aplicar um desvio (bias) ao seu índice (107, 1131 ou 32768, de acordo com o total de sub-rotinas) antes da pesquisa sob pena de aceder a funções erradas

Fontes CFF com chaves CID implicam uma indireção complementar: o código de caractere determina o CID, mas o índice da charstring baseia-se em GID, mapeando a tabela da fonte os índices GID para CID (exigindo que o renderizador monte a tabela inversa CID para GID antes de desenhar e selecione o Private DICT correspondente via FDSelect nas fontes que possuam múltiplos dicionários). Fontes Type1C associadas a nomes resolvem códigos de um byte através da codificação embutida no programa CFF ou na codificação ao nível do PDF. Lembre-se: o interpretador analisa operadores de ajuda (hinting) para manter o fluxo sincronizado, mas não executa o ajuste visual de píxeis (hinting)

O que são fontes Type 3 e como são desenhadas?

Um glifo Type 3 não constitui um contorno convencional: a norma ISO 32000-1 §9.6.5 define-o como um fluxo de conteúdo, pelo que o HotPDF o desenha salvaguardando o estado gráfico, combinando a matriz da fonte, tamanho e escala na CTM, e executando os operadores da mesma forma que desenha uma página comum utilizando o dicionário /Resources da fonte. Há duas regras críticas: as larguras (/Widths) na Type 3 baseiam-se no espaço do glifo (e não na escala 1/1000 das restantes fontes), exigindo aplicar a transformação /FontMatrix (fontes de códigos de barras com matriz 0.01 falhariam as larguras por uma ordem de grandeza); além disso, glifos definidos com o operador d1 impõem duas condições: o desenho é limitado pela caixa de corte (bounding box) e, nos termos de ISO 32000-1 §9.6.5.2, ignoram operadores de cor próprios desenhando com a cor de preenchimento ativa da página (omitindo operadores rg, g e k durante a chamada). Desrespeitar a regra de cor desenharia a preto códigos de barras que a página devesse exibir a azul; omitir o corte desenharia glifos fora dos respetivos limites

Como converter códigos de caracteres em IDs de glifos

Os interpretadores de contornos resolvem metade do problema: os bytes no PDF representam códigos de caracteres e não índices de glifos. A norma ISO 32000-1 define duas metodologias de mapeamento. Para fontes simples, a secção 9.6.6 define uma precedência: o array /Differences sobrepõe-se à codificação base (WinAnsiEncoding, MacRomanEncoding ou StandardEncoding), que por sua vez se sobrepõe ao mapa embutido na fonte. O HotPDF converte estas definições numa tabela de 256 entradas de código para GID, associando nomes de glifos a índices por três caminhos: correspondência exata no CFF, índices literais baseados no padrão gNN/glyphNN e conversão via Adobe Glyph List para Unicode seguida de pesquisa na tabela cmap em TrueType. Para fontes compostas, a secção 9.7 delega o mapeamento no CIDToGIDMap: embora o valor padrão seja /Identity, o registo pode consistir num fluxo de pares big-endian indexados por CID (o gerador Unicode do HotPDF utiliza esta estrutura para criar subconjuntos compactos):

// /CIDToGIDMap em fluxo: pares Word big-endian indexados por CID
if 2 * CID + 1 <= High(MapBytes) then
  GID := (MapBytes[2 * CID] shl 8) or MapBytes[2 * CID + 1]
else
  GID := 0;  // fora dos limites mapeia para .notdef

Para pesquisas na tabela cmap em TrueType, o HotPDF percorre uma cadeia de recurso: valida subtabelas Windows Unicode (formato 4 e formato 12 para planos suplementares), a tabela de símbolos (3,0) com mapeamento na região privada F000 (o que permite a fontes como Wingdings responder a códigos ASCII comuns) e, por fim, formatos legados 6 e 0. O formato 2 é omitido uma vez que mapeia páginas multi-byte legadas (como Shift-JIS ou Big5) e não Unicode, contendo as fontes CJK modernas tabelas em formato 4 ou 12. Glifos sem correspondência nestas tabelas recorrem ao desenho genérico da GDI, restringindo a falha ao caractere em causa

Limitações técnicas e comportamento de recurso

As limitações do processo de desenho: o ajuste visual de píxeis (hinting) não é executado, preenchendo-se os contornos de acordo com as coordenadas originais (efeito irrelevante em escalas superiores a 150 DPI mas que pode divergir por um píxel em resoluções pequenas). Fontes Type 1 originais em FontFile (com cifragem eexec) não são interpretadas e eixos de fontes OpenType variáveis não são aplicados. Em caso de dados corrompidos ou tabelas glyf sem correspondência em cmap, o motor reverte para fontes do sistema para evitar falhas no desenho da página. Esta metodologia de fidelidade gráfica é transversal ao renderizador (como as graduações de cor analisadas no artigo sobre graduações de cor axiais e radiais), e o motor de gravação possui lógicas próprias descritas no artigo sobre ordenação de subconjuntos no método EndDoc

A utilização destas rotinas não exige código específico para gestão de fontes: os mecanismos descritos são executados automaticamente na chamada de desenho da página:

var
  Pdf: THotPDF;
  Bmp: TBitmap;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('invoice-embedded-fonts.pdf') > 0 then
    begin
      // Fontes incorporadas TrueType, CFF e Type 3 são lidas do
      // próprio ficheiro — sem necessidade de instalações no computador
      Bmp := Pdf.RenderLoadedPageToBitmap(0, 144);
      if Bmp <> nil then
      try
        Bmp.SaveToFile('page1.bmp');
      finally
        Bmp.Free;
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

O benefício prático reflete-se na fiabilidade do sistema: os ficheiros PDF que incorporem tipos de letra são exibidos com as fontes corretas em servidores, contentores Windows ou terminais de clientes que não possuam as fontes instaladas. Estas rotinas integram o HotPDF Component para Delphi e C++Builder — uma biblioteca nativa VCL para criação, edição, extração de texto e desenho de páginas PDF sem dependências de DLLs externas