Artigo Técnico

Subconjunto de Fontes TrueType via fontsub.dll em Delphi

O HotXLS, o componente Excel para Delphi e C++Builder, reduz o tamanho de fontes PDF embutidas por meio de subconjunto (subsetting) de fontes TrueType: no momento da exportação para PDF, ele chama a função CreateFontPackage da biblioteca de sistema do Windows fontsub.dll para reconstruir uma fonte TrueType embutida ao redor apenas dos pontos de código Unicode que uma planilha de fato usou, em vez de enviar o arquivo de tipografia inteiro. Um relatório com duzentas linhas de nomes de produtos em chinês pode precisar de apenas algumas centenas de caracteres Han distintos, mas as fontes CJK que o Windows distribui rotineiramente ocupam de 5 a 20 MB cada. Embuta uma inteira, e a fonte sozinha pode superar em peso todos os outros objetos do PDF combinados

fontsub.dll não é uma biblioteca que a maioria dos desenvolvedores Delphi já ouviu falar, e há um motivo para isso: a Microsoft a distribui como uma pequena DLL utilitária, escassamente documentada, e não como uma API Win32 de destaque. O HotXLS a trata como uma capacidade opcional, não como uma dependência rígida, de modo que a forma como o exportador a carrega, a chama e recua quando ela está ausente diz tanto sobre programação defensiva no Windows quanto sobre formatos de fonte, e vale a pena percorrer as duas metades dessa história

Por que texto Unicode infla uma exportação PDF do HotXLS?

O exportador PDF do HotXLS recorre a uma fonte TrueType embutida apenas quando o texto da planilha sai do WinAnsi, e permanece na família Helvetica embutida o resto do tempo, o caminho padrão que o passo a passo de exportação de planilha para PDF cobre em profundidade. O WinAnsi cobre texto da Europa Ocidental bem o suficiente para que muitas pastas de trabalho nunca disparem um embutimento de fonte: o PDF apenas referencia Helvetica pelo nome e o leitor a fornece localmente, de modo que o arquivo permanece pequeno. No instante em que uma célula contém algo que o WinAnsi não consegue representar — um nome de produto em chinês, uma nota em coreano, um símbolo perdido em um comentário — o exportador precisa embutir um programa de fonte de verdade, porque um leitor de PDF não tem nenhuma fonte de glifo de reserva para caracteres fora das 14 fontes padrão

O HotXLS localiza essa fonte automaticamente, varrendo a pasta Fonts do Windows em busca de uma pequena lista de candidatas instaladas, incluindo as tipografias com capacidade CJK que o Windows distribui para renderização de chinês e coreano, a menos que a propriedade UnicodeFontFile do exportador já aponte para um arquivo específico, e qualquer que seja a fonte em que ele pouse é embutida inteira antes de o subsetting sequer rodar. Essa exigência de embutimento é específica do PDF: os caminhos de exportação RTF e HTML do HotXLS mantêm o texto Unicode intacto escapando pontos de código no fluxo de bytes em vez de enviar um programa de fonte, motivo pelo qual o problema de tamanho coberto neste artigo não tem equivalente nesses dois formatos

uses
  lxHandle, lxPDF;

var
  Book: TXLSWorkbook;
  Exporter: TXLSPDFExport;
begin
  Book := TXLSWorkbook.Create;
  try
    Book.Open('catalog-cn.xlsx');
    Exporter := TXLSPDFExport.Create;
    try
      // Optional: pin a specific CJK-capable font instead of the
      // exporter's automatic Windows\Fonts scan.
      Exporter.UnicodeFontFile := 'C:\Windows\Fonts\simhei.ttf';
      Exporter.SaveAsPDF(Book.ActiveSheet, 'catalog-cn.pdf');
    finally
      Exporter.Free;
    end;
  finally
    Book.Free;
  end;
end;

O que é o fontsub.dll, e por que não escrever um redutor de fonte do zero?

fontsub.dll é uma pequena biblioteca de sistema do Windows, distribuída desde o Windows XP, que expõe uma única função relevante aqui: CreateFontPackage. Entregue a ela os bytes de uma fonte TrueType de origem e uma lista de pontos de código Unicode a manter, e ela devolve uma fonte mínima que ainda satisfaz toda restrição de formato de fonte: índices de glifo renumerados, glyf e loca reconstruídos ao redor apenas dos contornos retidos, hmtx e cmap reescritos para corresponder. O HotXLS declara o tipo de ponteiro de função diretamente contra esse contrato

const
  TTFCFP_FLAGS_SUBSET = 1;
  TTFMFP_SUBSET = 0;
  TTFCFP_MS_PLATFORMID = 3;
  TTFCFP_UNICODE_CHAR_SET = 1;

type
  TCreateFontPackage = function(puchSrcBuffer: Pointer; ulSrcBufferSize: Cardinal;
    var puchFontPackageBuffer: PAnsiChar; var pulFontPackageBufferSize: Cardinal;
    var pulBytesWritten: Cardinal; usFlags, usTTCIndex, usSubsetFormat,
    usSubsetLanguage, usSubsetPlatform, usSubsetEncoding: Word;
    pusSubsetKeepList: PWordArray; usSubsetKeepListCount: Word;
    lpfnAllocate, lpfnReAllocate, lpfnFree, reserved: Pointer): Cardinal; cdecl;

Escrever à mão o trabalho de CreateFontPackage em vez de chamá-lo significaria implementar um redutor de TrueType correto: percorrer glifos compostos para trazer cada glifo componente que um glifo retido referencia, reconstruir os deslocamentos de loca depois que contornos são descartados, respeitar os bits de permissão de embutimento na tabela OS/2 de uma fonte, e acertar tudo isso em quaisquer fontes esquisitas que por acaso estejam instaladas na máquina de um cliente. A Microsoft já resolveu esse problema e distribui a solução como parte do próprio Windows, de modo que chamar uma DLL de sistema que ela mantém, testa contra sua própria pilha de renderização de fontes, e distribui para toda máquina de graça, custa ao HotXLS um carregamento dinâmico e um ponteiro de função; reimplementar a mesma lógica significaria possuir um analisador para um formato binário com décadas de casos extremos, para um recurso que só importa quando uma fonte por acaso é grande

Construindo a lista de retenção a partir de glifos realmente renderizados

O HotXLS constrói a lista de retenção do subsetting a partir de um mapa que já estava mantendo por outro motivo, de modo que a contabilidade não custa nada extra. Toda vez que o código de renderização de página desenha um caractere que precisa da fonte Unicode embutida, ele procura o índice de glifo desse caractere e registra o par em FUnicodeGlyphMap, uma tabela glifo-para-ponto-de-código que também conduz o CMap ToUnicode do PDF, de modo que copiar e colar do documento finalizado retorna o texto original em vez de IDs de glifo brutos. Quando os content streams de página estão finalizados, esse mapa já lista exatamente o conjunto de pontos de código Unicode que o documento usou, nem mais nem menos

var
  keepList: array of Word;
  keepCount, i: Integer;
  codePoint: LongWord;
begin
  SetLength(keepList, FUnicodeGlyphMap.Count);
  keepCount := 0;
  for i := 0 to FUnicodeGlyphMap.Count - 1 do
  begin
    codePoint := LongWord(StrToIntDef('$' + FUnicodeGlyphMap.ValueFromIndex[i], 0));
    if codePoint > 0 then
    begin
      keepList[keepCount] := Word(codePoint);
      Inc(keepCount);
    end;
  end;
end;

No momento da finalização, o HotXLS percorre esse mesmo mapa uma segunda vez para construir a lista de retenção que CreateFontPackage espera, um simples array dos pontos de código Unicode a reter na forma de 16 bits que o argumento de lista de retenção da API exige. Como esse argumento é um array de palavras de 16 bits, ele endereça o Plano Multilíngue Básico de forma limpa, o que cobre texto CJK comum, cirílico, grego e árabe sem complicação; uma planilha que se apoia em caracteres de planos suplementares, certos emojis ou escritas históricas raras, fica fora do que uma única entrada de lista de retenção consegue nomear diretamente, o que vale a pena conhecer como um limite, não como um defeito, já que a vasta maioria das planilhas empresariais pesadas em Unicode nunca chega perto desse plano em primeiro lugar

O que acontece quando o fontsub.dll está ausente?

O HotXLS nunca presume que o fontsub.dll está presente, e a exportação para PDF nunca falha por causa disso. A biblioteca é carregada dinamicamente no momento em que um subconjunto é necessário, com SafeLoadLibrary e GetProcAddress em vez de um import estático, precisamente porque fontsub.dll não é uma API pública documentada e de presença garantida da forma que kernel32.dll é: é uma ferramenta empacotada de embutimento de fonte, e nada no contrato da Microsoft promete que ela sobrevive em toda SKU, toda branch de manutenção, ou toda camada de compatibilidade que tenta emular o Windows

var
  hFontSub: HMODULE;
  CreateFontPackage: TCreateFontPackage;
begin
  hFontSub := SafeLoadLibrary('FontSub.dll');
  if hFontSub = 0 then
    Exit; // no subsetting available - keep the full embedded font
  try
    @CreateFontPackage := GetProcAddress(hFontSub, 'CreateFontPackage');
    if not Assigned(CreateFontPackage) then
      Exit;
    // ... call CreateFontPackage, check its return code ...
  finally
    FreeLibrary(hFontSub);
  end;
end;

Todo caminho de falha se dobra de volta para o mesmo resultado. Uma DLL ausente, um export ausente, um código de retorno diferente de zero, ou uma fonte cuja tabela OS/2 proíbe subsetting por meio de seus bits de permissão de embutimento — o HotXLS simplesmente mantém a fonte completa que já havia embutido e segue em frente. Nada levanta exceção, nada aborta a exportação, e o código que chama nunca precisa envolver uma otimização de fonte em seu próprio tratamento de exceção; o PDF exportado é válido de qualquer forma, e a única variável é se ele acaba pequeno ou um pouco maior

De quanto o PDF realmente diminui?

O subsetting de fonte TrueType do HotXLS tipicamente reduz o PDF exportado de uma planilha pesada em Unicode para algo entre um vigésimo e um oitavo de seu tamanho sem subsetting, uma redução de 8 a 20 vezes cuja escala acompanha quanto de uma fonte completa um determinado documento de fato toca: um pedido de compra construído em torno de algumas centenas de caracteres chineses distintos mantém apenas essas poucas centenas de glifos das dezenas de milhares que uma tipografia CJK distribui, enquanto uma planilha que abrange uma mistura mais ampla de caracteres mantém proporcionalmente mais. O HotXLS aplica uma camada adicional de compressão Flate sobre os bytes da fonte reduzida antes de gravá-los no stream /FontFile2 do PDF, a mesma compressão pela qual os demais content streams do documento já passam, e nada disso pede algo extra do código que chama: uma planilha que nunca sai do WinAnsi nunca toca esse caminho e continua exportando pela Helvetica simples, enquanto uma planilha que de fato dispara o caminho de fonte Unicode recebe subsetting automaticamente, sem propriedade para definir e sem chamada separada para fazer, e a única propriedade envolvida, UnicodeFontFile, apenas escolhe qual fonte é embutida e reduzida, não se o subsetting acontece

O subsetting de fonte é um detalhe dentro da superfície mais ampla de exportação PDF do Componente Excel HotXLS para Delphi, ao lado da paginação, dos metadados de impressão de planilha e dos caminhos de exportação CSV, HTML e RTF com os quais ele já vem