Um PDF que parece perfeito no seu computador e é renderizado como uma linha de caixas vazias no de outra pessoa é o defeito de tipo de letra mais comum em software de documentos, e quase nunca significa que o texto esteja incorreto. Os caracteres estão intactos, a codificação está correta, os glifos simplesmente não estão lá. O que mudou entre os dois computadores foi quais os tipos de letra que o sistema operativo tinha instalados, e a diferença entre um ficheiro portátil e um frágil é uma decisão tomada quando a página foi escrita: se o tipo de letra viajou dentro do PDF ou se foi assumido como estando presente no destino.
Compreender porque é que isto acontece, e porque é que uma falha separada produz texto aparentemente pesquisável que é copiado como caracteres ilegíveis, implica olhar para a forma como o PDF armazena texto. O PDF não armazena frases. Armazena códigos de glifos, mais um programa de tipo de letra, mais tabelas que mapeiam um ao outro, e cada erro de renderização ou extração reside numa lacuna entre estes três elementos. O que se segue é uma visita a esse funcionamento, baseada na norma ISO 32000, com as chamadas Delphi que o controlam onde tal é relevante.
Caracteres, códigos e glifos são três coisas diferentes
O vocabulário confunde as pessoas porque a linguagem quotidiana funde três ideias distintas na palavra "letra". Um caractere é uma unidade abstrata de escrita, a ideia do A maiúsculo, identificado em Unicode como U+0041. Um glifo é uma forma desenhada, o contorno de curvas e traços que um tipo de letra específico utiliza para representar esse caractere. Entre eles encontra-se o código: o byte ou bytes no fluxo de conteúdo (content stream) que indicam ao visualizador qual o glifo no tipo de letra atual a desenhar.
O PDF funciona com códigos. Quando um fluxo de conteúdo mostra uma cadeia de caracteres (string), esses bytes são índices no tipo de letra ativo, não em Unicode. A codificação do tipo de letra determina que um código 65 significa "desenhar o glifo registado sob o número 65", e nada nessa operação sabe que o resultado se assemelha a um A para um ser humano. É isso que faz com que o PDF seja renderizado de forma idêntica em qualquer lugar onde consiga encontrar os glifos, e é também a razão pela qual a extração é um problema diferente da exibição: o desenho necessita apenas do mapeamento de código para glifo, enquanto a leitura precisa do mapeamento de código para Unicode, e estas são duas tabelas diferentes que podem divergir ou desaparecer de forma independente.
Os tipos de letra que irá realmente encontrar
A norma ISO 32000 define vários tipos de dicionários de tipos de letra e, na prática, um documento que recebe ou gera utiliza um de três. Saber com qual deles está a lidar explica a maior parte do que pode correr mal.
Type 1 é o formato original de contorno PostScript da Adobe, construído a partir de curvas Bezier cúbicas. Os catorze tipos de letra padrão que qualquer leitor em conformidade deve fornecer, as famílias Helvetica, Times, Courier, Symbol e ZapfDingbats, são Type 1, e um dicionário de tipo de letra que nomeie um deles pode legalmente omitir o programa do tipo de letra. Este é o único caso em que deixar um tipo de letra não incorporado é seguro por especificação e não por sorte. Para qualquer outro tipo de letra Type 1, o programa tem de ser incorporado ou o visualizador substituirá por outra coisa, geralmente um tipo de letra metricamente semelhante mas visualmente diferente.
TrueType utiliza curvas quadráticas e teve origem no mundo da Apple e da Microsoft. É o que a maioria dos tipos de letra do sistema são, e o que irá incorporar com mais frequência. Um tipo de letra TrueType simples no PDF está limitado a códigos de byte único, pelo que um tipo de letra deste tipo pode endereçar no máximo 256 glifos de cada vez. Este limite é a razão estrutural pela qual o CJK e outros sistemas de escrita grandes não podem ser suportados por um tipo de letra simples.
Type 0, o tipo de letra composto ou codificado por CID (CID-keyed font), é a solução para esse limite. Utiliza códigos multibyte e um CMap para os encaminhar através de um CIDFont descendente, cujos contornos são TrueType ou CFF/Type 1. Este é o único tipo de letra que pode conter milhares de glifos, pelo que qualquer PDF que contenha chinês, japonês, coreano ou uma ampla mistura multilíngue está a utilizar o Type 0, quer o autor tenha pensado nisso ou não. A desvantagem é a complexidade: mais peças móveis, mais das quais têm de estar corretas tanto para a renderização como para a extração.

Um detalhe por trás dessa imagem dita o tamanho do ficheiro. Um tipo de letra é uma biblioteca de contornos, não bitmaps de tamanho fixo, pelo que o mesmo programa incorporado serve para todos os tamanhos de ponto na página. O dimensionamento é uma transformação aplicada no momento do desenho, razão pela qual um cabeçalho e o seu texto de corpo partilham o mesmo tipo de letra incorporado, e a penalização de tamanho da incorporação é por tipo de letra, não por tamanho.
A incorporação é a diferença entre o portátil e o frágil
A incorporação significa que o programa do tipo de letra, os dados reais do contorno, é escrito no PDF como um fluxo (stream). Um leitor num computador que nunca ouviu falar do seu tipo de letra lê esses contornos diretamente do ficheiro e desenha os glifos exatos. Se ignorar a incorporação, estará a apostar que o destino tem um tipo de letra com o mesmo nome; quando não tem, o visualizador recorre a um substituto. Para os catorze padrão, esta substituição é definida e inofensiva. Para tudo o resto, varia entre uma aproximação num tipo de letra diferente até ao resultado de caixas vazias quando nenhum substituto suporta de todo o sistema de escrita.
Com o HotPDF, o controlo é uma única propriedade, definida antes de o documento ser aberto. A propriedade FontEmbedding indica à biblioteca para empacotar os tipos de letra com os quais desenha no ficheiro:
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 método BeginDoc é onde o HotPDF confirma a estrutura do documento, pelo que a propriedade FontEmbedding tem de ser verdadeira antes dessa chamada. Se a atribuir depois, não haverá erro nem aviso, apenas um ficheiro que saiu silenciosamente sem os seus tipos de letra. Esse é o pior tipo de erro: passa em todos os testes no computador do programador, onde o tipo de letra por acaso está instalado, e apenas surge no do cliente, onde não está.
A incorporação é também onde o licenciamento se cruza com a engenharia. Um programa de tipo de letra contém sinalizadores (flags) que descrevem se pode ser incorporado livremente, apenas para visualização ou de todo não permitido. Respeitar esses sinalizadores é da sua responsabilidade, não do renderizador, e "funcionou" não é o mesmo que "era permitido".
Subsetting: incorpore apenas os glifos que utilizou
A incorporação total escreve todo o programa do tipo de letra no ficheiro. Um tipo de letra TrueType CJK grande pode ter vários megabytes, e incorporá-lo por completo para mostrar uma dúzia de caracteres é um desperdício que se acumula num documento de várias páginas. O subsetting resolve isto escrevendo apenas os glifos aos quais o documento faz referência e, em seguida, renomeando o tipo de letra com uma etiqueta de seis letras e um sinal de mais, como na forma ABCDEF+Calibri na lista de tipos de letra de qualquer PDF com subsetting, para que um leitor nunca confunda a versão parcial com um tipo de letra completo do sistema com o mesmo nome.
Para a maioria dos documentos gerados, o subsetting é o padrão correto. Mantém o tamanho do ficheiro proporcional ao conteúdo e não ao tipo de letra de origem, o que é mais importante para os grandes tipos de letra multilíngues que, de outra forma, dominariam o ficheiro. A única ressalva é que um subconjunto (subset) contém apenas o que foi utilizado no momento da criação. Se um processo posterior tentar adicionar texto a um tipo de letra com subsetting mais tarde, os glifos de que necessita podem não estar no ficheiro, o que constitui uma limitação real na edição incremental de um PDF de terceiros.
Tipos de letra Unicode e o problema das caixas CJK
Quando o texto não é latim simples, o caminho do tipo de letra simples esgota-se, e a solução é registar explicitamente um tipo de letra compatível com Unicode e deixar que o HotPDF construa um tipo de letra Type 0 a partir dele. O método RegisterUnicodeTTF carrega um ficheiro TrueType pelo caminho; depois disso, o nome registado pode ser utilizado em 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;
Há duas coisas que determinam o sucesso disto. O tipo de letra tem de cobrir os sistemas de escrita na string: um TrueType apenas para latim não criará glifos em chinês só porque o solicitou, e o resultado será novamente caixas vazias, desta vez porque o glifo genuinamente não existe nesse tipo de letra. E a incorporação tem de continuar ativada, porque um tipo de letra Type 0 montado a partir de um TTF registado não tem significado para um leitor que não consiga encontrar os contornos. Para conteúdo misto, a escolha duradoura é um tipo de letra de cobertura ampla, sendo as famílias Noto e Arial Unicode MS as respostas habituais, incorporadas e com subsetting.
Os sistemas de escrita da direita para a esquerda e complexos adicionam uma camada de modelação (shaping) sobre a cobertura. O HotPDF expõe a função RtLTextOut para árabe e hebraico, que lida com a reordenação direcional para que possa passar a ordem lógica e deixar que a biblioteca faça a paginação. Obter o árabe correto exige cobertura mais modelação mais direção, três coisas distintas, e uma caixa nesse caso pode significar que qualquer uma delas falhou.
A tabela ToUnicode: onde vive o copiar e colar
Tudo o que foi exposto acima diz respeito ao desenho. A extração é a imagem espelhada e falha pelas suas próprias razões. Um visualizador renderiza uma página utilizando o mapeamento de código para glifo do tipo de letra, mas quando um utilizador seleciona texto e o copia, o visualizador precisa de transformar esses mesmos códigos de volta em Unicode. Esse mapeamento inverso é o CMap ToUnicode, um fluxo opcional anexado ao tipo de letra.
Quando está presente e correto, o texto copiado sai com os caracteres certos. Quando está ausente ou incorreto, ou se o tipo de letra foi subconfigurado com códigos de glifo personalizados e nenhum ToUnicode foi escrito, a página parece perfeita e a área de transferência enche-se de lixo: códigos de glifo lidos como se fossem Unicode, o que não acontece num subconjunto com codificação personalizada. É por isso que um documento digitalizado com uma camada de texto OCR pode ser pesquisável, enquanto um PDF digital nativo gerado de forma negligente não o é. A renderização e a extração baseiam-se em tabelas diferentes, pelo que um ficheiro pode satisfazer uma e falhar na outra. Se a extração for importante para o seu resultado, trate um mapa ToUnicode correto como um requisito e verifique-o copiando texto de uma amostra, em vez de confiar que ele está presente.
Como diagnosticar rapidamente um erro de tipo de letra
O modo de falha indica-lhe onde procurar. Caixas vazias noutro computador significam quase sempre um tipo de letra que não foi incorporado, pelo que deve verificar primeiro a incorporação e, em seguida, a cobertura de glifos. As caixas que aparecem mesmo no seu próprio computador apontam para a cobertura: o tipo de letra não contém esse sistema de escrita, independentemente da incorporação. O texto que é renderizado corretamente mas é copiado como caracteres sem sentido é um problema de ToUnicode, não de renderização, e alterar os tipos de letra ou a incorporação não o resolverá porque o desenho nunca esteve incorreto. Para ler um ficheiro concluído, abra-o no Acrobat e consulte as Propriedades do Documento, Fontes: uma entrada correta mostra o tipo, indica Incorporado ou Subconjunto Incorporado e nomeia a codificação. Um tipo de letra que deveria estar incorporado e não está anuncia-se aí antes de o cliente o fazer.
Nada disto é exótico a partir do momento em que a divisão entre caractere, código e glifo é clara. Incorpore os tipos de letra com os quais desenha, realize o subsetting dos grandes, utilize um tipo de letra Unicode e chame RegisterUnicodeTTF no instante em que o texto sai do latim, e mantenha um mapa ToUnicode correto se alguém for extrair o texto. Faça isto corretamente e as caixas deixarão de aparecer. Para o funcionamento geral, a anatomia de um PDF mínimo mostra onde o dicionário de tipos de letra se situa na árvore de objetos, e o guia detalhado da estrutura do documento aborda como os recursos são partilhados entre páginas.
As chamadas SetFont, FontEmbedding e RegisterUnicodeTTF apresentadas aqui fazem parte do Componente HotPDF para Delphi e C++Builder.