Produção de Livros

Um gate que nunca reprovou nada não prova nada: o teste de mutação

Uma bateria de gates deu tudo limpo sobre o texto-fonte, e o PDF tinha 15 entidades HTML impressas e 10 negritos perdidos. Como provar um gate: estrague o arquivo e veja ele reprovar.

JonathanJonathan
7 min de leitura1.365 palavras
Capa do artigo: Um gate que nunca reprovou nada não prova nada: o teste de mutação (eBuz, Produção de Livros)

Um dia, uma bateria de conferência rodou sobre o texto de um livro e deu tudo limpo. Quando alguém abriu o PDF, achou: 15 entidades HTML impressas como texto, um "Apêndice: Apêndice" duplicado e 10 negritos que tinham sumido.

Nenhum dos três defeitos foi achado por um gate. Foram achados por olho. A conclusão da época virou a regra: gate que lê a fonte não vê o que o motor fez com ela.

Este artigo mostra como escrever um gate que lê o PDF final. Mostra, sobretudo, como provar que ele funciona, com um teste que quase ninguém faz: estragar o arquivo de propósito.

O que é um gate

É uma trava que mede o arquivo e reprova. Ele tem sempre uma pergunta e uma direção. "Toda palavra do PDF existe na fonte?" é uma pergunta. "Toda palavra da fonte existe no PDF?" é outra, e pega um defeito diferente. A primeira acha texto acrescentado. A segunda acha texto perdido.

A bateria que usamos tem 26 gates, sobre o PDF renderizado, divididos em cinco blocos:

BlocoO que mede
Voz e marcatravessão, palavra vetada, assinatura do selo
Veracidadepreço inventado, promessa de faturamento, credencial do autor, lugares-comuns proibidos
Marcação de produçãomarca de lacuna que sobrou, entidade HTML, ** cru, título cru, prefixo duplicado
Negritotodo negrito do texto-fonte chegou em negrito no PDF
Estruturaformato, número de páginas, caixas, numeração contínua, sumário, páginas vazias

O gate do negrito, e as duas armadilhas

O pdftohtml marca negrito com a etiqueta <b>. O gate extrai todo negrito do PDF, extrai todo **...** do texto-fonte e compara. Caímos em duas armadilhas ao escrevê-lo.

Hifenização. A palavra quebrada no fim da linha vira "visí" mais "vel" no PDF, e a comparação direta acusa perda. A comparação roda com espaço, hífen e hífen suave removidos dos dois lados.

O perfil do artefato. Rodar o gate do manuscrito em um boneco acusou 83 de 135 negritos perdidos. Os 83 eram notas editoriais do esboço, que nunca iam para o PDF: o boneco renderiza só um subconjunto do texto, de propósito. Por isso o gate tem um parâmetro de perfil.

Uma terceira: o carimbo "prova de validação" que o modelo imprime tem um travessão. A bateria remove as linhas do carimbo antes de medir a voz. Sem isso, todo arquivo de prévia reprovaria por um texto que não é nosso.

Os dois gates de texto

Os dois scripts que usamos estão nos artigos anteriores. O de conservação (o texto do autor chegou inteiro?) está em conferir o PDF final. O inverso, que procura texto que não está na fonte, é este:

#!/usr/bin/env python3
"""Gate inverso: o PDF tem trecho que não está na fonte (texto acrescentado)?
Uso: python3 inversa.py fonte.txt livro.pdf
Janelas de 8 palavras sobre o texto inteiro do PDF; um trecho acrescentado aparece como uma sequência de janelas ausentes da fonte.
Cabeçalho, rodapé e número de página geram janelas isoladas, por isso só contam sequências de 4 janelas seguidas ou mais."""
import re, subprocess, sys, unicodedata
def base(s):
    s = unicodedata.normalize("NFD", s.lower())
    return re.sub(r"[^a-z0-9]", "", "".join(c for c in s if unicodedata.category(c) != "Mn"))
fonte = base(open(sys.argv[1], encoding="utf-8").read())
pdf = subprocess.run(["pdftotext", sys.argv[2], "-"], stdout=subprocess.PIPE, text=True).stdout
w = re.findall(r"[^\W_]+", pdf.lower())
ausente = [base(" ".join(w[i:i + 8])) not in fonte for i in range(len(w) - 7)]
runs, i = [], 0
while i < len(ausente):
    if ausente[i]:
        j = i
        while j < len(ausente) and ausente[j]: j += 1
        if j - i >= 4: runs.append((i, j - i + 7))
        i = j
    else: i += 1
print(f"trechos do PDF que não estão na fonte: {len(runs)}")
for ini, n in runs[:8]: print(f"  ACRESCENTADO ({n} palavras): {' '.join(w[ini:ini + 12])}...")
sys.exit(1 if runs else 0)
python3 inversa.py fonte.txt livro.pdf

Ele compara janelas de oito palavras do PDF contra a fonte. Só conta sequências de quatro janelas seguidas ou mais, porque cabeçalho, rodapé e número de página geram janelas isoladas.

O teste de mutação

A prova de um gate é estragar o arquivo, de formas conhecidas, e conferir que ele reprova. Fizemos isso com um livro de 40 parágrafos e 1.143 palavras, com quatro versões do PDF:

VersãoConservação (fonte para PDF)Inversa (PDF para fonte)
Arquivo bompassapassa
Um parágrafo apagadoreprovareprova
Palavras trocadas dentro de um parágraforeprovareprova
Um parágrafo acrescentadopassareprova

Duas lições saíram da tabela. A primeira é que o gate de conservação sozinho não pega texto acrescentado: o parágrafo extra passou. Só o inverso o viu. Os dois gates juntos cobrem as duas direções. A segunda é que o inverso também reprovou o parágrafo apagado, por efeito da emenda: as janelas que cruzam o buraco não existem na fonte. Dois gates diferentes podem apontar o mesmo defeito, e isso é bom.

No miolo de impressão, o teste de mutação tem seis arquivos estragados, e os seis foram reprovados. São eles: sem TrimBox, PDF cru em RGB, uma página a menos, capítulo em página par, preto de quatro cores e um painel com 400% de tinta. O teste cobre cada grupo do checklist de preflight. Em um boneco com texto de autor, apagar um parágrafo, trocar uma palavra e injetar um termo proibido e uma marca interna foram todos apontados.

A linha de base: o limite certo

Um gate de versão compara o texto da página de revisão com o miolo enviado à gráfica, por janelas de oito palavras por parágrafo. A pergunta: "o texto que o autor está lendo é da mesma versão do que vai ser impresso?".

A tentação é fixar um limite, como 99% de cobertura. Medimos: a cobertura normal desse medidor variou de 87% a 96% entre livros, por razões de formatação. Um limite fixo de 99% acusaria todos os livros e não diria nada. A linha de base certa é a fonte que o próprio build lê: compara-se o arquivo com o que o motor recebeu, e não com um número escolhido a olho.

Passo a passo: escrever um gate novo

  1. Escreva a pergunta e a direção em uma frase.
  2. Leia o PDF final, e não o texto de entrada.
  3. Escreva a mutação que o gate deve pegar: o defeito conhecido, introduzido de propósito.
  4. Veja o gate reprovar. Se ele não reprovar, ele está quebrado.
  5. Rode no arquivo bom. Ele não pode acusar.
  6. Registre o limite e o porquê, com a linha de base medida.
  7. Repita a mutação sempre que mudar o gate.

O erro que custa caro

Confiar em um gate que nunca foi visto reprovando. Ele pode estar quebrado sem ninguém saber, e a sensação de segurança que ele dá é pior que a ausência de gate. O segundo erro é ler a entrada: um gate que olha o texto antes de o motor mexer não vê o que o motor estraga.

Quando NÃO usar

Para um defeito que só um humano reconhece, como uma página equilibrada ou uma capa que "não pesa", o gate não ajuda. Nossa bateria avisa de página par em branco no pré-texto sem reprovar, porque distinguir um verso legítimo de um buraco de diagramação ainda exige olho. O gate não substitui a leitura da página.

Alternativa sem o nosso ambiente

Os scripts usam Python e o poppler. A ideia de estragar o arquivo e conferir a reprovação vale com qualquer ferramenta, de um editor de PDF a um script de três linhas.

O que o Claude faz hoje, e como fazer sem ele

Hoje o Claude escreve o gate, escreve a mutação e roda os dois a cada versão do arquivo. Sem ele, o passo a passo acima é um procedimento de uma tarde, e a tabela de mutação é uma planilha.

Onde entra na operação

Os gates são o último filtro do passo de fecho do procedimento, depois do miolo e antes do pacote da gráfica. O desenho completo está em Como produzimos o miolo de um livro profissional sem InDesign, e o uso sem IA, em rodar tudo sem o Claude.

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.

Perguntas frequentes

O que é teste de mutação em um gate de arquivo?

É estragar o arquivo de propósito, de formas conhecidas, e conferir que o gate reprova. Prova que o gate funciona. Um gate que nunca foi visto reprovando pode estar quebrado sem ninguém saber.

Por que o gate deve ler o PDF e não o texto de origem?

Porque o motor de diagramação pode estragar o texto: entidades impressas, negritos perdidos, prefixos duplicados. Um gate que lê a origem não vê nada disso. Só a conferência do PDF final mostra o que saiu.

O gate de conservação do texto basta?

Não. Ele acha texto perdido, mas não texto acrescentado. No nosso teste, um parágrafo extra passou nele e só foi pego pelo gate inverso. Os dois, juntos, cobrem as duas direções.

Qual limite usar em um gate de comparação de versão?

Meça a linha de base: compare o arquivo com a fonte que o próprio build lê. Um limite fixo, como 99%, pode acusar todos os livros se a cobertura normal do medidor variar, como variou de 87% a 96% nos nossos.