Um boneco de 201 páginas, de um livro que o autor já tinha escrito, estava publicado em um link, e todos os gates estavam verdes. Quando alguém comparou o PDF com o texto original, faltavam 1.370 palavras do autor.
Não foi invenção de texto, e sim o contrário. Uma regra do nosso gerador reconhecia legendas de figura e as tratava como legendas curtas de uma linha. Só que, ao lado de cada figura da apostila, o texto seguinte era parágrafo do autor, e a regra engolia até quatro linhas dele por figura.
O gate que existia perguntava: "toda palavra do PDF existe na fonte?". Passou. Porque ele só enxerga o que entrou no PDF, nunca o que saiu dele.
Por que o gate passou
Um gate de texto tem uma direção. A pergunta "toda palavra do PDF existe na fonte?" pega invenção: texto que a diagramação ou a IA acrescentou. Ela é cega para perda: texto que existia e sumiu. Para pegar a perda, a pergunta tem de ser a inversa: "toda palavra da fonte está no PDF?".
O mesmo erro de direção aparece em outro lugar. Em uma bateria de conferência, todos os gates rodaram sobre o texto-fonte e deram verde. O PDF, ao mesmo tempo, tinha 15 entidades HTML impressas como texto, um "Apêndice: Apêndice" duplicado e 10 negritos que sumiram. Gate que lê a fonte não vê o que o render estragou. A lição vale para os dois casos: confira o PDF final, e confira nas duas direções.
O que mais a troca do motor descobriu
Além das 1.370 palavras, achamos 538 palavras de rótulo de gráfico, corpo de tabela e legenda coladas no meio de parágrafos. A extração de texto jogava esses elementos no meio do fluxo. Também havia uma URL trocada por [endereço], o que mudava o texto do autor. E a origem de cada trecho, um código interno, aparecia na margem. Nenhum gate de fidelidade enxerga isso. Só olhar a página mostrou.
A regra que saiu disso, e que vale para qualquer livro com texto de autor: na dúvida, o texto fica no corpo. Sobrar uma linha de legenda é um defeito pequeno. Engolir texto do autor não é.
O gate de conservação
A ideia é simples. Pegue o texto do autor em um arquivo, com parágrafos separados por linha em branco. Extraia o texto do PDF. Confira, parágrafo por parágrafo, se ele chegou. Fazemos isso por janelas de oito palavras, que resistem à hifenização e a quebras de página no meio do parágrafo. Este é o script:
#!/usr/bin/env python3
"""Confere se o texto do autor chegou inteiro ao PDF. Mede a perda, não só a invenção.
Uso: python3 conserva.py fonte.txt livro.pdf
fonte.txt = o texto do autor, parágrafos separados por linha em branco.
Sai com 1 se algum parágrafo estiver faltando."""
import re, subprocess, sys, unicodedata
def base(s): # sem acento, sem caixa, sem pontuação, sem hífen e sem espaço
s = unicodedata.normalize("NFD", s.lower())
s = "".join(c for c in s if unicodedata.category(c) != "Mn")
return re.sub(r"[^a-z0-9]", "", s)
def palavras(s):
return re.findall(r"[^\W_]+", s.lower())
fonte = open(sys.argv[1], encoding="utf-8").read()
pdf = subprocess.run(["pdftotext", sys.argv[2], "-"], stdout=subprocess.PIPE, text=True).stdout
alvo = base(pdf)
JANELA, MINIMO = 8, 0.9
faltando, total_pal, perdidas = [], 0, 0
for par in [p.strip() for p in re.split(r"\n\s*\n", fonte) if p.strip()]:
w = palavras(par)
total_pal += len(w)
if len(w) < JANELA:
achou = base(par) in alvo
if not achou: faltando.append((0.0, len(w), par)); perdidas += len(w)
continue
jan = [base(" ".join(w[i:i + JANELA])) for i in range(len(w) - JANELA + 1)]
frac = sum(j in alvo for j in jan) / len(jan)
if frac < MINIMO:
faltando.append((frac, len(w), par)); perdidas += len(w)
print(f"palavras na fonte: {total_pal} | parágrafos com texto faltando: {len(faltando)} | palavras sob suspeita: {perdidas}")
for frac, n, par in faltando[:10]:
print(f" FALTA ({n} palavras, {frac:.0%} presente): {par[:70]}...")
sys.exit(1 if faltando else 0)
Testamos em um livro de 40 parágrafos e 1.143 palavras, gerado em duas versões. No PDF completo, o script saiu com 0 parágrafos faltando e código de saída 0. No PDF em que removemos dois parágrafos, ele apontou os dois, acusou 58 palavras sob suspeita e saiu com código 1.
Três causas comuns de perda
Quando o script aponta parágrafos faltando, a causa quase sempre é uma destas:
- Uma regra que reconhece elemento do texto. Legenda de figura, nota, citação em destaque. A regra decide o que é legenda e o que é parágrafo, e erra para o lado de engolir. Foi o nosso caso das 1.370 palavras.
- A extração misturando elementos. Rótulos de gráfico e corpo de tabela são jogados no meio do fluxo pelo extrator de texto. Na nossa conferência, 538 palavras de gráfico, tabela e legenda tinham aparecido como se fossem texto do autor. O sintoma é o inverso da perda: sobra texto.
- Uma limpeza que remove demais. Marcas internas, endereços e códigos são retirados do texto antes da diagramação. Se a regra de limpeza for larga, leva junto o que era do autor. Uma URL trocada por
[endereço]mudou o texto dele.
Em todas, a correção é na origem, na regra que errou. Consertar o PDF à mão resolve uma vez e deixa o gerador errado para o próximo livro.
Passo a passo
- Guarde o texto do autor em um arquivo de texto simples, parágrafos separados por linha em branco. É a fonte da verdade.
- Gere o PDF final, o mesmo que vai ser enviado, e não uma versão intermediária.
- Rode o script de conservação.
- Leia cada parágrafo apontado. Pode ser perda real ou diferença de norma. Se for perda, corrija na origem (a regra do gerador), não no PDF.
- Rode a conferência inversa, que procura palavras do PDF que não existem na fonte, para pegar texto acrescentado. É o gate "toda palavra do PDF existe na fonte", que continua útil, só que não basta.
- Confira o que o texto não mostra. O negrito, por exemplo, pode ser lido do PDF com
pdftohtml:
pdftohtml -xml -i -stdout livro.pdf | grep -o "<b>[^<]*</b>" | wc -l
Compare a contagem com os negritos do texto-fonte. Duas armadilhas, em que caímos. A hifenização quebra uma palavra no fim da linha ("visí" mais "vel") e dá falsa perda, então compare sem hífen e sem espaço, dos dois lados. E um boneco mostra só parte do texto, de propósito, então o gate precisa saber qual é o perfil do arquivo.
- Meça a perda também dentro do gerador. Se uma regra consome linhas de texto, ela deve contar quantas entraram e quantas saíram, e abortar quando a conta não fecha. No nosso gerador, essa conservação de linhas é parte do gate.
Os limites do script
- Trechos repetidos. Se uma janela de oito palavras aparece em outro lugar do livro, ela "prova" presença sem provar. Em texto real isso é raro, mas em texto muito repetitivo reduz a sensibilidade. No nosso teste, os parágrafos eram muito parecidos entre si, e os dois removidos apareceram com 50% de presença.
- Texto trocado por texto parecido. O script não vê uma palavra mudada por outra.
- Tabelas e figuras. A extração de texto de tabelas pode embaralhar a ordem. Para elas, a conferência é visual.
- A extração importa. Uma extração por outra biblioteca já perdeu dígitos soltos em um caso nosso. A prova usa o poppler.
O erro que custa caro
Considerar que um gate "verde" significa "texto inteiro". Cada gate responde a uma pergunta, e a pergunta tem direção. Antes de confiar em um gate, escreva a pergunta que ele faz e pergunte qual defeito ele não consegue ver. E, um passo antes: um gate que nunca foi visto reprovando não vale. Estrague o arquivo de propósito e veja o gate falhar. A técnica, o teste de mutação, aparece no checklist de preflight do miolo.
Quando NÃO usar
Se o texto do autor passa por edição de propósito (copidesque, revisão), a fonte deixa de ser o original. Compare então com a versão editada e aprovada, que vira a base de verdade. Se o texto vai direto para a página, sem regras que reconheçam elementos, o risco de uma regra engolir texto é menor. A conferência continua barata.
Alternativa sem o nosso ambiente
O script usa só o Python e o pdftotext, do poppler. Qualquer ferramenta de comparação de texto serve para a mesma ideia: diff entre o texto extraído e o original, ou o recurso de comparar documentos de um editor de texto. A direção é que importa.
O que o Claude faz hoje, e como fazer sem ele
Hoje o Claude roda a conferência do PDF final em cada boneco antes de promover o arquivo e relata os parágrafos apontados. Sem ele, você roda o script do artigo e lê cada ocorrência.
Onde entra na operação
Este é o gate do passo em que o boneco ou o miolo viram arquivo final. Ele convive com a conferência de sangria, de cor e de geometria, que estão no checklist de preflight do miolo. O desenho completo está em Como produzimos o miolo de um livro profissional sem InDesign. O compilador que gera o PDF está em Typst no lugar do InDesign.
Baixe, para guardar: checklist de preflight do miolo, com comando e limite por item (PDF).
Versões usadas neste artigo: veja a matriz de versões.



