Pular para o conteúdo
getnextpdf.com

Por que o texto em um PDF não é realmente texto

Quando você lê um PDF, você vê palavras. O arquivo não contém palavras. Ele contém instruções para pintar formas em coordenadas, e as formas por acaso se parecem com letras. A lacuna entre o que um PDF desenha e o que ele significa é a razão pela qual uma página pode parecer impecável e ainda assim copiar como algo sem sentido. Esta página trata dessa lacuna e do pequeno mapa, separado, que a fecha.

Tudo o que você faz com um PDF enquanto texto — selecioná-lo, copiá-lo, pesquisá-lo, indexá-lo para recuperação, lê-lo em voz alta com um leitor de tela — depende de recuperar caracteres que o arquivo nunca armazenou diretamente. Se essa recuperação falha, a falha é invisível. A página ainda renderiza. Ninguém percebe até que alguém copie um parágrafo para um e-mail e obtenha □□□□, ou pesquise em um contrato de 400 páginas por uma cláusula que está claramente lá e não encontre nada.

Essa é uma classe de bug cara precisamente porque ela sobrevive a toda revisão visual. Não há nada para ver. O documento parece confiável e é, para fins de máquina, mudo.

  • Um PDF desenha glifos — formas visuais escolhidas por códigos numéricos em uma fonte — não caracteres Unicode.
  • A mesma forma pode significar caracteres diferentes, e o mesmo caractere pode ser desenhado por glifos diferentes. Aparência e significado são desacoplados por design.
  • Para obter o texto de volta, um leitor executa o caminho de exibição ao contrário: código para caractere. O CMap /ToUnicode da fonte é a tabela de consulta para esse passo reverso (Spec: ISO 32000-2, §9.10).
  • Sem /ToUnicode, ou com um errado, o resultado é copiar-colar ilegível e busca falha — com a página ainda perfeita pixel a pixel.
  • Esse é o problema do significado. Se o glifo parece certo é o problema separado da aparência, tratado em Fonts: the hard part. O NextPDF emite um /ToUnicode correto para que o ciclo de ida e volta seja fiel.

Comece por como o texto chega à página, em primeiro lugar. Um content stream não diz “escreva a palavra file.” Ele seleciona uma fonte e, então, entrega uma sequência de códigos a um operador de exibição de texto (Spec: ISO 32000-2, §9). Cada código é um índice — uma posição em um programa de fonte — e a fonte transforma esse índice em um contorno de glifo e o pinta. O código 70 não é o caractere F. Ele é “a posição 70 nesta fonte”, e a posição 70 por acaso contém uma curva em forma de F. Escolha uma fonte diferente e a posição 70 poderia ser um floco de neve.

Então, um código só significa algo em relação à codificação da sua fonte. Para uma fonte simples, essa codificação é mais ou menos um byte por glifo. Para os sistemas de escrita que precisam de milhares de glifos — CJK, ou qualquer documento em Unicode completo — isso não é espaço suficiente, e o PDF recorre a uma fonte composta (Type 0) (Spec: ISO 32000-2, §9.7). Uma fonte composta lê códigos multibyte por meio de um CMap, transforma-os em CIDs (identificadores de caractere) e mapeia CIDs para glifos. É uma indireção elegante que permite que uma fonte enderece um conjunto enorme de glifos. É também mais um lugar onde o rastro de “o que é exibido” para “o que isso significa” pode se perder.

Agora execute-o ao contrário. Para extrair texto, um leitor pega os códigos que encontra no content stream e precisa recuperar caracteres (Spec: ISO 32000-2, §9.10). A codificação que desenhou o glifo foi de caractere para glifo; a extração precisa de código de glifo para caractere, e essa direção não tem garantia de ser invertível. Uma fonte com subset pode ter renumerado seus glifos. Os CIDs de uma fonte composta podem ser privados. A forma na página não carrega significado Unicode inerente.

A correção é fornecer o mapa reverso junto com a fonte. Esse mapa é o CMap /ToUnicode: para cada código que o documento usa, ele registra o valor (ou valores) Unicode que aquele código representa. Com ele, a extração é uma consulta limpa. Sem ele, um leitor fica entregue a adivinhar a partir de codificações de fonte e heurísticas — e adivinhar é exatamente onde fi vira um ponto de interrogação.

  1. Your charactersThe Unicode text you set, for example the word 'file'.
  2. Encoding → codesThe font's encoding turns characters into numeric codes; a composite font routes them through a CMap to CIDs.
  3. Codes → glyphsEach code selects a glyph outline, which is painted to the page. This is the part you see.
  4. Extraction reverses itA reader reads the codes back and looks each one up in /ToUnicode to recover Unicode.
  5. Text againWith a correct /ToUnicode, copy, search, indexing, and screen readers all get the original characters back.
As duas direções do texto em PDF. A renderização vai de caractere → glifo e é o que você vê. A extração vai de código → caractere e é o que você consegue copiar, pesquisar e ouvir em voz alta — e só funciona quando o mapa /ToUnicode fecha o ciclo.

O NextPDF escreve o mapa reverso como rotina. Quando ele incorpora uma fonte composta, emite o descendente CIDFontType2, o pai Type0 e um CMap /ToUnicode, para que os códigos resolvam de volta para o Unicode — o trabalho de codificação descrito em Fonts: the hard part. O ponto que vale separar aqui é o porquê: o stream /ToUnicode não tem a ver com fazer a página parecer correta. Os glifos já parecem corretos sem ele. Ele é o único artefato que mantém o texto extraível.

O lugar mais famoso em que isso aparece é a ligadura fi. Muitas fontes desenham f e i como um único glifo combinado, porque o pingo do i colide com o gancho do f. Na página é uma forma só, desenhada por um único código. A questão é o que acontece quando você a copia.

% A /ToUnicode entry that maps the single ligature code to TWO characters,
% so selecting the 'fi' glyph copies out as 'f' then 'i' — not one mystery box.
1 beginbfchar
<0085> <00660069> % code 0x85 -> U+0066 'f' U+0069 'i'
endbfchar

Essa única entrada é a diferença entre uma busca por “file” que encontra correspondência e uma busca por “file” que silenciosamente deixa de encontrar toda ocorrência renderizada com a ligadura. Mapeie o código da ligadura para a sequência de dois caracteres f + i, e a palavra fica pesquisável e copiável. Deixe-o sem mapeamento, e a página ainda exibe file perfeitamente enquanto o texto silenciosamente não diz nada que uma máquina consiga ler.

É por isso que “fi” pode extrair como um caractere ou dois, e por que a diferença não é aleatória — é o que quer que o mapa /ToUnicode tenha sido instruído a dizer. Um mapa correto decompõe a ligadura de volta em seus caracteres. O NextPDF emite exatamente esse tipo de entrada, para que uma ligadura na página sejam duas letras comuns na área de transferência.

A armadilha, nomeada: “o texto renderiza, então o texto está bom.” A renderização e a extração são mecanismos diferentes lendo dados diferentes. A renderização segue o caminho de código para glifo e é o que seus olhos conferem. A extração segue o caminho de código para caractere por meio de /ToUnicode e é o que todo consumidor de máquina confere. Um documento pode mandar bem no primeiro e falhar completamente no segundo, porque, por construção, não há nada visual que o revele.

Uma segunda armadilha, mais sutil: presumir que um glifo “obviamente” sabe qual caractere ele é. Ele não sabe. Um glifo é uma forma com um índice. O mapeamento de volta para um caractere é informação adicional que o produtor tem de fornecer. Se o produtor nunca a escreveu, nenhum leitor consegue recuperá-la fielmente — ele só pode adivinhar.

Faithful text extraction from documents NextPDF writes — edition availability
EditionAvailability
CoreO NextPDF emite um CMap /ToUnicode correto para as fontes que incorpora, incluindo os casos de ligadura e de fonte composta, de modo que os documentos que ele produz são pesquisáveis e copiáveis.
ProNot in this edition
EnterpriseNot in this edition

Um mapa /ToUnicode só pode carregar a informação que a fonte de origem efetivamente expõe. Onde um glifo não tem caractere determinável — uma marca puramente decorativa, um símbolo de uso privado sem atribuição Unicode — nenhum mapa consegue inventar um significado que nunca esteve lá. O NextPDF emite o mapeamento verídico que consegue derivar; ele não fabrica caracteres para preencher uma lacuna.

Esta página trata dos documentos que o NextPDF escreve. Ela não é uma ferramenta de reparo para PDFs de entrada arbitrários cujo /ToUnicode já está ausente ou errado; recuperar texto desses é um problema separado e heurístico. E a extração fiel é necessária para a acessibilidade, mas não é o todo dela — a ordem de leitura, as tags e o texto alternativo são tratados em What makes a PDF accessible.

Por que o mesmo PDF copia bem em um leitor e mal em outro? Leitores diferentes recorrem a fallbacks diferentes quando /ToUnicode está ausente. Alguns adivinham a partir da codificação interna da fonte e dão sorte em texto latino comum; outros não. Um /ToUnicode correto remove a loteria — todo leitor em conformidade obtém os mesmos caracteres.

Isso é a mesma coisa que a fonte não estar incorporada? Não. A incorporação tem a ver com aparência — se o glifo é pintado sem a fonte instalada. /ToUnicode tem a ver com significado — se o código mapeia de volta para um caractere. Uma fonte pode estar perfeitamente incorporada e ainda assim não ter um /ToUnicode utilizável, que é a falha do tipo bonito-mas-não- pesquisável.

A extração alguma vez precisa de mais de um caractere por código? Sim — o caso da ligadura é exatamente esse: um código mapeia para dois caracteres. Uma entrada /ToUnicode pode mapear um único código para uma curta sequência, que é como ligaduras, e algumas formas compostas, voltam como suas letras constituintes.

  • Fonts: the hard part — a peça complementar sobre o lado da aparência: incorporação, subsetting e como a codificação é construída. Esta página é o lado do significado da mesma moeda.
  • What makes a PDF accessible — o texto fiel é um ingrediente; a ordem de leitura, as tags e o texto alternativo são o resto.
  • What a PDF actually is — o modelo de objetos dentro do qual as fontes, as codificações e os streams /ToUnicode vivem.
  • Glifo — uma forma visual em uma fonte (um contorno). O que um PDF de fato pinta. Um glifo tem um índice, mas nenhum significado de caractere inerente.
  • Caractere — uma unidade de linguagem escrita, identificada por um code point Unicode. O que você quer dizer, e o que a extração tenta recuperar.
  • Código — o valor numérico em um content stream que seleciona um glifo na fonte atual. Não é um caractere Unicode; só tem significado em relação à fonte.
  • CID — um identificador de caractere usado por uma fonte composta para endereçar um glifo dentro de um conjunto grande; um passo intermediário entre código e glifo.
  • Fonte composta (Type 0) — uma fonte que lê códigos multibyte por meio de um CMap para alcançar CIDs e glifos, usada quando uma fonte precisa endereçar milhares de glifos.
  • /ToUnicode — o stream de CMap que mapeia os códigos que um documento usa de volta para valores Unicode; o que torna o texto em PDF pesquisável, copiável e acessível.
  • Ligadura — um único glifo que desenha duas ou mais letras como uma só forma (por exemplo fi); sua entrada /ToUnicode a decompõe de volta em seus caracteres.