Um PDF que parece perfeito em sua máquina e é renderizado como uma fileira de caixas vazias na de outra pessoa é o defeito de fonte mais comum em softwares de documentos e quase nunca significa que o texto está errado. Os caracteres estão intactos, a codificação está boa, os glifos simplesmente não estão lá. O que mudou entre as duas máquinas foi quais fontes o sistema operacional tinha instalado, e a lacuna entre um arquivo portátil e um arquivo frágil é uma decisão tomada quando a página foi escrita: se a fonte viajou dentro do PDF ou se foi presumida como presente na outra extremidade
Entender por que isso acontece, e por que uma falha separada produz um texto de aparência pesquisável que, quando copiado, vira lixo (gibberish), significa olhar para como o PDF armazena texto. Ele não armazena frases. Ele armazena códigos de glifo mais um programa de fonte mais tabelas que mapeiam um para o outro, e todo bug de renderização ou extração vive em uma lacuna entre esses três. O que se segue é um tour por esse mecanismo, baseado no ISO 32000, com as chamadas do Delphi que o controlam onde são importantes
Caracteres, códigos e glifos são três coisas diferentes
O vocabulário confunde as pessoas porque a fala cotidiana colapsa três ideias distintas na palavra "letra". Um caractere é uma unidade abstrata de escrita, a ideia do A maiúsculo, identificado no Unicode como U+0041. Um glifo é uma forma desenhada, o contorno de curva e haste que uma fonte particular usa para representar esse caractere. Entre eles fica o código: o byte ou os bytes no fluxo de conteúdo que dizem ao visualizador qual glifo da fonte atual deve ser pintado
O PDF funciona em códigos. Quando um fluxo de conteúdo exibe uma string, esses bytes são índices para a fonte ativa, não Unicode. A codificação da fonte decide que um código de 65 significa "desenhar o glifo arquivado em 65", e nada nessa operação sabe que o resultado parece um A para um humano. É isso que faz com que o PDF seja renderizado de forma idêntica em todos os lugares onde ele pode encontrar os glifos, e também é por isso que a extração é um problema separado da exibição: desenhar precisa apenas de código-para-glifo, a leitura precisa de código-para-Unicode, e essas são duas tabelas diferentes que podem discordar ou desaparecer de forma independente
Os tipos de fonte que você realmente vai encontrar
A ISO 32000 define vários tipos de dicionário de fontes e, na prática, um documento que você recebe ou gera usa um de três. Saber qual deles você está observando explica a maior parte do que pode dar errado
Type 1 é o formato de contorno PostScript original da Adobe, construído a partir de curvas Bezier cúbicas. As quatorze fontes padrão que todo leitor em conformidade deve fornecer, as famílias Helvetica, Times, Courier, Symbol e ZapfDingbats, são Type 1, e um dicionário de fontes que nomeia uma delas pode legalmente omitir o programa de fontes. Esse é o único caso em que deixar uma fonte não incorporada é seguro por especificação, em vez de por sorte. Para qualquer outra fonte Type 1, o programa deve ser incorporado ou o visualizador substitui por algo, geralmente uma fonte metricamente semelhante, mas visivelmente diferente
TrueType usa curvas quadráticas e veio do mundo Apple e Microsoft. É o que a maioria das fontes de sistema é, e o que você incorporará com mais frequência. Uma fonte TrueType simples em PDF é limitada a códigos de byte único, então uma dessas fontes pode endereçar no máximo 256 glifos por vez. Esse limite é o motivo estrutural pelo qual CJK e outras grandes escritas não podem usar uma fonte simples
Type 0, a fonte composta ou chaveada por CID (CID-keyed font), é a resposta para esse limite. Ela usa códigos de vários bytes e um CMap para roteá-los por uma CIDFont descendente, cujos contornos são eles próprios TrueType ou CFF/Type 1. Este é o único tipo de fonte que pode carregar milhares de glifos, então qualquer PDF que contenha chinês, japonês, coreano ou uma ampla mistura multilíngue está usando Type 0, quer o autor tenha pensado sobre isso ou não. A troca é a complexidade: mais peças móveis, mais das quais devem estar corretas tanto para renderização quanto para extração

Um detalhe por trás dessa imagem direciona o tamanho do arquivo. Uma fonte é uma biblioteca de contornos, não bitmaps de tamanho fixo, então o mesmo programa incorporado serve a todos os tamanhos de ponto da página. O dimensionamento é uma transformação aplicada no momento do desenho, que é o motivo pelo qual um título e seu corpo de texto compartilham a mesma fonte incorporada e por que o custo da incorporação é por fonte, não por tamanho
A incorporação é a diferença entre ser portátil e ser frágil
A incorporação (embedding) significa que o programa da fonte, os dados de contorno reais, é escrito no PDF como um fluxo (stream). Um leitor em uma máquina que nunca ouviu falar de sua fonte lê esses contornos direto do arquivo e desenha glifos exatos. Pular a incorporação é apostar que o destino tem uma fonte com o mesmo nome; quando não tem, o visualizador recorre a uma substituição. Para as quatorze fontes padrão, essa substituição é definida e benigna. Para todo o resto, varia de algo bem próximo com um tipo de fonte diferente, ao resultado de caixa vazia, quando nenhum substituto cobre aquela escrita
Com o HotPDF, o controle é uma propriedade única, definida antes que o documento abra. FontEmbedding diz à biblioteca para compactar as fontes que ela usa para desenhar dentro do arquivo:
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'report.pdf';
Pdf.Compression := cmFlateDecode;
Pdf.FontEmbedding := True; // outlines travel inside the file
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Calibri', [], 11);
Pdf.CurrentPage.TextOut(72, 760, 0, 'This renders the same on a machine without Calibri.');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
A ordem não é cosmética. O BeginDoc é onde o HotPDF faz o commit da estrutura do documento, então o FontEmbedding tem que ser verdadeiro antes dessa chamada. Se atribuí-lo depois, não haverá erro, nenhum aviso, apenas um arquivo que silenciosamente foi gerado sem suas fontes. Esse é o pior tipo de bug: ele passa em todos os testes na máquina do desenvolvedor, onde a fonte, por acaso, está instalada, e só aparece na do cliente, onde não está
A incorporação também é onde o licenciamento se encontra com a engenharia. Um programa de fonte traz sinalizadores (flags) descrevendo se ele pode ser incorporado livremente, apenas para visualização, ou de forma alguma. Honrar esses sinalizadores é sua responsabilidade, não do renderizador, e "funcionou" não é o mesmo que "foi permitido"
Subconjunto (Subsetting): incorpore apenas os glifos que você usou
A incorporação completa (full embedding) grava todo o programa da fonte no arquivo. Uma grande fonte TrueType CJK pode chegar a vários megabytes, e incorporá-la por inteira para mostrar uma dúzia de caracteres é um desperdício de uma maneira que se agrava em um documento multipáginas. A técnica de subconjunto (subsetting) resolve isso gravando apenas os glifos que o documento faz referência, renomeando a fonte com uma tag de seis letras e um sinal de mais, a forma ABCDEF+Calibri na lista de fontes de qualquer PDF em subconjunto, para que o leitor nunca confunda a fonte parcial com a fonte completa de sistema com o mesmo nome
Para a maioria dos documentos gerados, os subconjuntos são o padrão correto. Ele mantém o tamanho do arquivo proporcional ao conteúdo em vez de ser à fonte de origem, o que importa mais para as grandes fontes multilíngues que de outra forma dominariam o arquivo. A única ressalva é que um subconjunto contém apenas o que foi usado no momento da criação. Se um processo posterior tentar adicionar texto a uma fonte em subconjunto mais tarde, os glifos que ele precisa podem não estar no arquivo, uma verdadeira restrição na edição incremental do PDF de outra pessoa
Fontes Unicode e o problema da caixa CJK
Quando o texto não é um Latim simples, o caminho de fonte simples (simple-font) se esgota, e a solução é registrar uma fonte capaz de Unicode explicitamente e deixar o HotPDF construir uma fonte Type 0 a partir dela. O RegisterUnicodeTTF carrega um arquivo TrueType por caminho; depois disso o nome registrado pode ser usado no SetFont como qualquer outro:
Pdf.FontEmbedding := True;
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansCJKsc-Regular.ttf');
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('NotoSansCJKsc-Regular', [], 14);
Pdf.CurrentPage.TextOut(72, 720, 0, '你好,世界 こんにちは 안녕하세요');
Pdf.EndDoc;
Duas coisas determinam o sucesso ou o fracasso disso. A fonte deve cobrir os scripts na string: um TrueType apenas com o idioma Latim não fará crescer glifos chineses porque você pediu que fizesse, e o resultado serão caixas vazias novamente, dessa vez porque os glifos genuinamente não existem nessa fonte. E a incorporação deve permanecer ativada, porque uma fonte Type 0 montada a partir de um TTF registrado não faz sentido para um leitor que não consiga encontrar os contornos. Para conteúdo misto, a escolha durável é uma fonte de ampla cobertura, como as famílias Noto e Arial Unicode MS sendo as respostas usuais, incorporadas e em subconjuntos
Scripts complexos e da direita-para-a-esquerda adicionam uma camada de modelagem (shaping) sobre a cobertura. O HotPDF expõe o RtLTextOut para o Árabe e Hebraico, que lida com o reordenamento direcional para que você passe a ordem lógica e deixe a biblioteca fazer o layout. Fazer o Árabe corretamente significa cobertura mais modelagem mais direção, três coisas separadas, e uma caixa ali pode significar que alguma delas falhou
A tabela ToUnicode: onde a cópia e colagem vivem
Tudo acima diz respeito ao desenho. A extração é a imagem espelhada e falha por suas próprias razões. Um visualizador renderiza uma página usando o mapeamento de código para glifo da fonte, mas quando um usuário seleciona o texto e o copia, o visualizador precisa transformar esses mesmos códigos de volta em Unicode. Esse mapeamento reverso é o ToUnicode CMap, um fluxo opcional anexado à fonte
Quando ele está presente e correto, o texto copiado sai com os caracteres certos. Quando está ausente ou incorreto, ou a fonte foi configurada como subconjunto com códigos de glifo personalizados e nenhum ToUnicode foi escrito, a página fica perfeita e a área de transferência é preenchida com lixo: códigos de glifo lidos como se fossem Unicode, o que em um subconjunto codificado de forma personalizada, eles não são. É por isso que um documento digitalizado com uma camada de texto OCR pode ser pesquisável enquanto que um PDF nascido-digital (born-digital) de um gerador descuidado não é. A renderização e extração desenham em tabelas diferentes, então o arquivo pode satisfazer uma e falhar na outra. Se a extração for importante para a sua saída, trate um mapa ToUnicode correto como um requisito e verifique-o copiando o texto de uma amostra em vez de confiar que ele esteja lá
Como diagnosticar rapidamente um bug de fonte
O modo de falha diz a você onde procurar. Caixas vazias em outra máquina quase sempre significam uma fonte que não foi incorporada, então verifique primeiro a incorporação e, em segundo, a cobertura do glifo. Caixas que aparecem mesmo na sua própria máquina apontam para cobertura: a fonte não contém essa escrita, independentemente da incorporação. O texto que é renderizado de forma correta, mas que, ao ser copiado gera frases sem sentido é um problema com o ToUnicode, e não com a renderização, e mexer com fontes ou com a incorporação não resolverá isso, pois o desenho nunca esteve quebrado. Para ler um arquivo finalizado, abra-o no Acrobat e veja em Propriedades do Documento, Fontes: uma entrada saudável mostra o tipo, diz Incorporada (Embedded) ou Subconjunto Incorporado (Embedded Subset), e o nome da codificação. Uma fonte que deveria estar incorporada e que não está, se anuncia lá antes que o cliente perceba
Nada disso é exótico uma vez que a divisão entre caractere, código e glifo seja clara. Incorpore as fontes com as quais você desenha, aplique subconjuntos às grandes, busque por uma fonte Unicode e pelo RegisterUnicodeTTF no momento que o texto deixar de ser Latim, e mantenha um mapa ToUnicode correto se alguém for extrair o texto. Faça tudo isso certo, e as caixas param de aparecer. Sobre a mecânica circundante, a anatomia de um PDF mínimo mostra onde o dicionário de fontes está posicionado na árvore de objetos, e a revisão da estrutura do documento cobre como os recursos são compartilhados através das páginas
As chamadas do SetFont, FontEmbedding, e RegisterUnicodeTTF mostradas aqui fazem parte do Componente HotPDF para Delphi e C++Builder