- Para alterar o conteúdo de texto de um PDF sem usar uma ferramenta comercial de PDF, é preciso descompactar os streams comprimidos, seguir a codificação de cada fonte e substituir o texto original e o texto de reposição da mesma forma
- Muitos PDFs armazenam o texto real do documento não como texto puro, mas em streams comprimidos e strings que parecem hexadecimal; com
qpdf --qdf --object-streams=disable, é possível expandi-los para algo mais próximo de um formato editável
- Blocos de texto são compostos por comandos PDF como
BT, Tf, Td, Tj e ET, e valores como <002a004800570003003600480057> Tj podem não ser simplesmente code points Unicode
- Se o PDF incluir mapeamento ToUnicode por fonte, é possível verificar com
pdffonts se emb e uni estão como yes e seguir referências como /ToUnicode 19 0 R para encontrar a tabela de codificação
- Por causa da complexidade do padrão PDF, esse método é difícil de generalizar para todos os arquivos, mas em certos PDFs dá para encontrar a codificação original e substituir o texto com find-and-replace
Descompactando streams de texto comprimidos do PDF
- Ao abrir um PDF em um editor de texto, parte da estrutura pode ser legível, mas o texto real do documento pode não aparecer
- Muitos PDFs armazenam os dados de texto em formato comprimido
- É possível descompactar os streams de texto comprimidos com
qpdf para inspecioná-los
- No Mac, há uma fórmula do Homebrew
- Em distribuições Linux, ferramentas relacionadas também podem estar disponíveis
qpdf --qdf --object-streams=disable in.pdf out.pdf
- Depois da edição, é possível comprimir os streams novamente
qpdf out-edited.pdf out-recompressed.pdf
- O comando de recompressão gerou um erro, mas o PDF resultante pôde ser lido no Preview
Encontrando texto dentro dos comandos PDF
- Mesmo depois da descompactação, o texto continua dentro de blocos de comandos PDF
- Um bloco básico de texto tem a seguinte forma
BT
/Font_0 12 Tf
288 720 Td
<002a004800570003003600480057> Tj
ET
- De acordo com o PDF Reference, esse bloco executa as seguintes ações em sequência
- inicia um objeto de texto
- define a fonte e o tamanho a usar
- especifica a posição inicial na página
- desenha os glifos da string naquela posição
- encerra o objeto de texto
Por que o texto não é legível imediatamente
- Valores como
<002a004800570003003600480057> parecem strings hexadecimais, mas podem não ser uma simples lista de code points Unicode
- O PDF tem muitas formas de especificar codificação e pode incluir codificações personalizadas dentro do arquivo
- Essas codificações, na maioria dos casos, podem ser mapeadas para code points Unicode
- Este procedimento assume que o PDF em questão inclui uma codificação embutida
- não aborda como tratar os outros casos
Verificando a fonte e o mapeamento Unicode
- A codificação do texto do PDF está ligada a uma fonte específica
pdffonts mostra informações sobre as fontes e a codificação dentro do PDF
$ pdffonts sample.pdf
name type emb sub uni prob object ID
------------------------------------ ----------------- --- --- --- ---- ---------
CLDQZB+TrebuchetMS,Bold CID TrueType yes yes yes 9 0
YQBAIZ+TrebuchetMS CID TrueType yes yes yes 10 0
- Os campos importantes aqui são os seguintes
emb: indica que a fonte está embutida no PDF
uni: indica que há mapeamento Unicode
- Se ambos estiverem como
yes, há chance de encontrar o mapeamento necessário
- Um identificador de fonte como
/Font_0, no exemplo, pode corresponder ao nome da fonte na saída do pdffonts
- nesse caso,
/Font_0 apontava para CLDQZB+TrebuchetMS,Bold
Seguindo a tabela de mapeamento ToUnicode
- Depois de encontrar o nome completo da fonte usada no texto, procure esse nome dentro do PDF
- Em um certo ponto, pode aparecer uma referência como a seguinte
/ToUnicode 19 0 R
- Esse valor aponta para o ID do objeto da tabela de codificação
- Em seguida, ao procurar por
19 0 obj, foi possível encontrar essa tabela
Substituindo texto com a tabela de codificação
- A parte central da tabela de mapeamento pode ter uma forma como esta
38 beginbfrange^M
<0036><0036><0053>^M
<0057><0057><0074>^M
<0044><0044><0061>^M
<0048><0048><0065>^M
<0050><0050><006D>^M
...
- Neste exemplo, faixas de pontos de codificação personalizados são mapeadas para pontos Unicode
- aqui, cada faixa contém apenas um caractere
0036 é mapeado para o ponto Unicode 0053
- Se o formato da tabela for diferente, consulte o tutorial de arquivo de mapeamento ToUnicode
- Para automatizar a conversão, foi usada Python para transformar a tabela em um dicionário e escrever funções simples de codificação/decodificação
- O fluxo final de substituição foi o seguinte
- verificar o valor em codificação personalizada do texto original que se quer alterar
- codificar o texto de substituição na mesma codificação personalizada
- no PDF, fazer um find-and-replace substituindo o valor antigo
1 comentários
Comentários do Hacker News
A especificação do PDF é bem áspera. Minha curiosidade inútil favorita ultimamente é que, ao renderizar elementos sobrepostos, ela dá suporte a todos os modos de mesclagem de camadas do Photoshop
A segunda favorita é que conteúdo anexado depois pode modificar conteúdo anterior, então é preciso examinar forensicamente todas as diferentes versões representadas dentro de um único arquivo
Também é um exemplo da futilidade do DRM. A especificação tem criptografia baseada em senha, permite separar senhas de “proprietário” e de “usuário”, e também traz campos de bits como “não imprimir” e “não copiar texto”. Mas, para ler o documento, ele precisa acabar sendo descriptografado; então, se você abrir um PDF criptografado com a senha de “usuário” em uma ferramenta que não respeite essas restrições e salvá-lo como uma versão não criptografada, obtém uma cópia equivalente editável
[1] Seção “More than just transparency” de https://blog.adobe.com/en/publish/2022/01/31/20-years-of-tra...
[2] https://blog.didierstevens.com/2008/05/07/solving-a-little-p...
[3] Página 61 de https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandard...
[4] Por exemplo, um script que usa a biblioteca pypdf
Um dos tutoriais de cracking famosos na época removia o DRM do leitor oficial aplicando um patch na função que verificava permissões
Esse assunto aparece periodicamente; a maioria pensa em PDF como um formato binário difícil de penetrar, mas na prática não é
PDF é um grafo de objetos de tipos diferentes, e os próprios tipos são bem descritos na especificação oficial. Tenho o gosto masoquista de ler aquilo por diversão
O que sempre recomendo é, como o autor, converter o PDF para uma versão sem dados comprimidos. Minha ferramenta preferida é o mutool, usando
mutool clean -d in.pdf out.pdf. Depois, ao fuçar, você se surpreende com o quanto consegue acompanharNo texto faltou a etapa de olhar o objeto da página e verificar os recursos. É ali que o nome da fonte usado no fluxo de conteúdo é ligado ao subobjeto real
Outra parte importante que ficou de fora é que a maioria das fontes entra no PDF como subconjunto. Apenas os glifos necessários são mantidos na fonte. A recodificação geralmente parece acontecer aqui, e o ToUnicode é mantido para permitir copiar texto ou pesquisar no PDF. É algo bom para o usuário e, pela minha experiência, em geral existe e está correto
Para começo de conversa, a filosofia de projeto de criar um formato somente leitura já estava errada. Não é óbvio qual seria a primeira funcionalidade que as pessoas pediriam?
Fico curioso se há outros insights sobre como fazer isso bem de forma nativa, sem provedores de nuvem. Especialmente ao lidar com tabelas
Dá para fazer assim:
Converter para PostScript
a.pscompdf2ps a.pdfEditar o PostScript diretamente com
vim a.psConverter de volta para PDF com
ps2pdf a.psPDFs complexos que tenham JavaScript embutido, animações e coisas do tipo às vezes não funcionam corretamente depois desse bate-volta. Ainda assim, para documentos comuns, funciona bem. Remover marcas-d’água e alterar palavras e números é fácil; ajustar espaçamentos é mais difícil. Claro, é preciso conhecer um pouco de PostScript
Mesmo quando a saída estava correta, ainda era necessário reordenar páginas, aplicar códigos de barras para processamento nas máquinas postais e triagem postal, e gerar relatórios. Normalmente usávamos Perl e outras ferramentas para extrair texto das páginas e obter endereços; PS era, em geral, muito mais fácil do que PDF, mas havia casos extremamente frágeis, como quando PDFs “protegidos” não tinham o mapeamento correto de glifos
No pior caso, o cliente mandava PDFs de documentos individuais, e ao mesclá-los a saída passava a ter uma explosão de fontes em subconjunto, enchendo a memória da impressora até ela travar. Quando deixei esse trabalho, acho que ainda não havia uma ferramenta útil para unificar e mesclar fontes em subconjunto vindas da mesma fonte original. Deveria ser possível e seria muito útil, mas eu não tinha tempo nem conhecimento para investigar
Sinceramente, parece que os únicos que se beneficiam da complexidade do PDF são os criadores de malware
Acho que faltou um ponto importante. No fim de um PDF há uma tabela que armazena os offsets em bytes de vários objetos dentro do arquivo, a chamada tabela de referência cruzada
Ao modificar o arquivo, esses offsets normalmente mudam e o arquivo quebra. O texto parecia interessado apenas em trocar um número por outro, então talvez as posições não tenham mudado
Mas, em geral, ao adicionar, remover ou modificar algo no meio do arquivo, é preciso recalcular a tabela xref, então usar uma biblioteca fica muito mais fácil do que editar texto diretamente
O qpdf parece reconstruir a estrutura original do PDF e tentar manter também os números dos objetos, mas os offsets são totalmente recalculados
Se os autores algum dia contarem, eu gostaria de ler por que o formato acabou desse jeito. Parece que, no início, a intenção era ser um formato totalmente textual, e depois decidiram que binário era mais eficiente. Só que a exigência de que as entradas da tabela xref tenham comprimento fixo se encaixa de um jeito meio estranho nessa suposição
mutool cleanpara consertar. Como ela pode ser totalmente derivada do conteúdo, é seguroDesde que você preserve o comprimento, pode modificar o binário à vontade
Um software que eu usava antigamente se autenticava no servidor, mas todo o processamento acontecia no cliente. Era algo como o cliente executar SQL diretamente no servidor, e o servidor apenas verificava se esse cliente estava excedendo o número de licenças compradas
Dava para passar no disassembler, encontrar a parte que fazia a verificação, trocar essa parte diretamente por um JMP e preencher o espaço restante com NOP
Parece que faltou uma etapa importante ao usar o modo
--qdfdo qpdf. Depois de terminar a edição, é preciso passar o arquivo pelo utilitário fix-pdf para recalcular todos os offsets dos objetos e reconstruir a tabela de referência cruzada no fim do arquivo. A exceção é se você apenas alterou bytes no mesmo lugar, sem adicionar nem remover bytesMeu top 3 de fatos interessantes sobre PDF é este
[0] Exemplo: https://lab6.com/2
Se quiser continuar a jornada pelo PDF e aprender mais, você pode ler este documento recente de dissecação de arquivos:
https://pdfsyntax.dev/introduction_pdf_syntax.html
O que as pessoas frequentemente deixam passar sobre PDFs é que, em certo sentido, eles estão mais próximos de um formato de imagem do que de um documento do Word. Na edição de documentos, documentos do Word, PDFs e imagens são como, na música, projetos de DAW, MIDI e MP3; e, em software, algo parecido com código-fonte Java, bytecode da JVM e código de máquina x86 puro.
O principal objetivo de um arquivo PDF é deixar perfeitamente claro o que deve ser exibido ou impresso, usando muito menos bytes do que uma imagem real. Ele aproveita o fato de que o autor do documento conhece os padrões da estrutura do documento e faz com que, quando bem representados, eles sejam comprimidos muito melhor do que por algoritmos reais de compressão de imagem. Por exemplo, se você tem acesso à fonte real, é melhor dizer “coloque estes caracteres nestas coordenadas, com este espaçamento” do que inserir cada ocorrência de letra como parte de uma imagem e esperar que o algoritmo de compressão reduza as repetições por conta própria. Quais caracteres pertencem a quais palavras, ou até qual code point Unicode corresponde a qual glifo de fonte, basicamente não importa se o único objetivo é transmitir a imagem do documento de forma eficiente.
Em um documento editável, a estrutura semântica do conteúdo importa muito mais do que apenas a representação. Importa saber se uma determinada quebra no texto são vários espaços, a próxima coluna de uma tabela ou um layout estranho de página causado por uma imagem. Também importa se o texto no rodapé de cada página foi inserido várias vezes pelo autor, ou digitado uma vez e configurado como rodapé. Se você precisa adicionar um novo parágrafo e alterar o layout da página, importa saber que o último parágrafo desta página é uma nota de rodapé e, portanto, não deve passar para a próxima página. Importa que, se um título de seção for movido para outra página, o sumário seja atualizado automaticamente; ele não deveria ser apenas texto digitado manualmente pelo autor.
Do ponto de vista de uma impressora ou de uma tela, nada disso faz diferença. Basta imprimir ou exibir conforme instruído. Em PDFs, notas de rodapé, títulos de seção, rodapés e sumários não precisam ser especiais; podem ser apenas texto com formatação sem significado. É por isso que, quando se tenta usar PDF para fins que não sejam exibição ou impressão, não dá para chegar a 100% de precisão. Claro que há esforços para compensar isso, e programas que geram PDFs podem inserir livremente os metadados que quiserem, mas isso não é obrigatório.
Talvez esse não seja o modelo mental pretendido pelos autores do PDF, mas é uma perspectiva útil para entender por que o PDF tem essa forma.
Quando impresso em uma impressora PostScript, ele aparecia exatamente igual, e com fontes Type 1 às vezes ficava ainda melhor.
Quem quer fazer isso está perdendo o ponto central do PDF. PDF é um formato de descrição de página, portanto representa apenas as marcas visíveis na página, não a estrutura do documento.
Não tente editar o PDF; edite o documento original que gerou o PDF.
Em algum momento, simplesmente enviar o documento feito para ser editado passou a ser visto como algo “pouco profissional”, e acabamos nesta situação.
É opcional, mas recomendado para acessibilidade. Um exemplo é o PDF/UA. Veja os capítulos 14.7–14.8 de [1].
[1] https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandard...
Não dá para simplesmente alterar o texto do PDF, porque o diagramador não verá essa alteração. As ferramentas de marcação do Acrobat não são horríveis, mas não chegam ao nível da época de papel e lápis vermelho. A menos que você use a ferramenta “pencil” do Acrobat. Seria bom se essa parte melhorasse.
Há 20 anos trabalhei em um jornal como responsável pelas chapas de impressão. Tínhamos duas “impressoras” Kodak de um milhão de dólares para chapas, embora “impressora” não seja bem a palavra certa. Se minha memória não falha, a emulsão da chapa era exposta a luz ultravioleta e depois dissolvida em um banho químico.
O equipamento da Kodak falhava com frequência, e meu chefe costumava abrir diretamente arquivos PostScript ou EPS para corrigir cabeçalhos ou partes erradas enviadas pelo software de layout. Aí tudo voltava a funcionar bem. A propósito, nossa enorme rotativa offset alemã rodava Linux.
Acho que o nome do meu chefe era Bill. Era uma boa pessoa, que me levou a um show do Sigur Rós quando eu tinha 17 anos. Acabei contando duas histórias que não têm nada a ver com PDF.
Ótimo texto. Passei os últimos cerca de 5 anos criando o DocSpring [1] e, mesmo tendo lido bastante a especificação de PDF, ainda sinto que só arranhei a superfície. O qpdf é uma ferramenta excelente, e outra ferramenta de que gosto é o RUPS [2], que permite investigar a estrutura de PDFs em profundidade.
[1] https://docspring.com
[2] https://github.com/itext/i7j-rups