Pular para o conteúdo

Destrinchando o PDF com PDFLang

Na tela, um PDF parece uma coisa só e chapada, uma foto de uma página. Não é. É uma caixa de peças, e quase toda pergunta que se faz sobre um arquivo — isso vai imprimir direito? está faltando alguma coisa? — é na verdade uma pergunta sobre uma dessas peças.

O PDFLang te dá cinco maneiras de alcançar o que está dentro. Quatro já estão ali quando o seu script começa. A quinta você desenha.

Todo script começa com uma variável chamada doc. Não tem nada para abrir nem carregar: doc é o arquivo que está sendo conferido.

check "O que eu estou olhando" {
print("arquivo:", doc.filename)
print("páginas:", doc.page_count)
print("título:", doc.title)
}

doc.title e doc.author vêm dos metadados do arquivo — a informação que o PDF carrega sobre si mesmo e que você nunca vê na página. Costuma estar vazia, porque quem exportou o arquivo nunca preencheu. Isso já vale uma verificação:

check "O arquivo diz o que ele é" {
assert doc.title != "", "o PDF não tem título nos metadados"
}

O doc também guarda três listas: doc.pages, doc.fonts e doc.images. São listas comuns, então tudo que você faz com uma lista funciona nelas.

doc.pages entrega as páginas uma a uma. Cada uma sabe o próprio tamanho, quanta tinta carrega e quais dos seus boxes estão definidos.

Tamanhos vêm em pontos, a unidade em que o PDF pensa: 72 pontos por polegada, então uma página A4 tem 595 × 842. Mas você não precisa pensar em pontos — escreva 210mm ou 8.5in e a comparação funciona:

check "Tudo em A4" {
doc.pages.each { |page|
assert page.width > 200mm,
"a página #{page.number} está estreita demais: #{page.width}pt"
}
}

Repare no page.number. As páginas contam a partir de 1, como na conversa, então o número que aparece numa mensagem de erro é a página que a pessoa vai procurar.

E tem os boxes. Uma página pronta para impressão carrega vários retângulos que dizem o que fazer com ela: o TrimBox é onde a guilhotina corta, e o BleedBox é a área um pouco maior onde a cor precisa passar do corte, para que um milímetro de desvio não deixe uma borda branca. Um arquivo sem eles pode parecer perfeito na tela e ser inútil na gráfica:

check "Pronto para a guilhotina" {
doc.pages.each { |page|
assert page.has_trim_box, "a página #{page.number} não tem TrimBox"
assert page.has_bleed_box, "a página #{page.number} não tem BleedBox"
}
}

A página também informa o page.tac: quanta tinta cai no ponto mais carregado dela, em porcentagem. Quatro tintas na força total dariam 400%, que nenhuma máquina aceita; 300% é um teto comum.

doc.fonts lista as tipografias que o arquivo usa, e cada uma responde a uma única pergunta importante: ela está embutida?

Uma fonte embutida viaja dentro do PDF. Uma fonte que não está embutida é apenas citada pelo nome, e a máquina que abre o arquivo substitui por alguma que tenha. O texto não some — ele muda de forma. As linhas quebram em outros lugares, um título que cabia em uma linha passa a ocupar duas, e ninguém percebe até estar impresso.

check "Toda fonte viaja com o arquivo" {
doc.fonts.each { |font|
assert font.is_embedded, "a fonte '#{font.name}' não está embutida"
}
}

doc.images entrega todas as imagens do arquivo, e page.images as de uma página específica. A propriedade que vale entender é o dpi.

DPI é pontos por polegada — quanto detalhe existe em cada polegada impressa. O PDFLang não informa o número guardado nos metadados da imagem. Ele informa a resolução efetiva: os pixels que a imagem realmente tem, divididos pelo tamanho em que ela é impressa. Estique uma foto de 1000 pixels por 20 centímetros e ela é uma imagem de baixa resolução, diga o que disser sobre si mesma. É esse número que decide se a impressão sai nítida.

check "Nítida o bastante para imprimir" {
doc.images.each { |img|
assert img.dpi >= 300,
"imagem na página #{img.page_number}: #{img.dpi} DPI, precisa de 300"
}
}

O img.color_space é o outro que as pessoas procuram. O offset trabalha em CMYK; uma imagem que ainda está em RGB precisa ser convertida, e a conversão desloca as cores. Melhor descobrir antes de gravar as chapas.

As quatro anteriores vêm com o arquivo. A quinta não: uma region é um retângulo que você define, para poder perguntar sobre uma parte da página em vez da página inteira.

rodape = region(0, 0, 595, 60, "rodapé")
tarja = region(20mm, 250mm, 60mm, 15mm, "tarja de advertência")

A única coisa que você precisa lembrar é onde fica a origem. Num PDF, 0, 0 é o canto inferior esquerdo da página, não o superior. O Y cresce para cima.

As regions são o que permite escrever as regras que de fato importam num trabalho. Não “esse arquivo contém o texto legal”, mas “o texto legal está na tarja reservada para ele”:

check "Tarja de advertência" {
tarja = region(0, 700, 595, 142, "tarja")
conteudo = text::extract_from_region(1, tarja)
assert conteudo.contains("VENDA SOB PRESCRIÇÃO"),
"a tarja está sem o texto obrigatório"
}

Regions também se comparam. rodape.intersects(cabecalho) diz se duas áreas se sobrepõem, que é como se pega um elemento que invadiu espaço reservado para outra coisa. E rodape.expand(5mm) e rodape.inset(3mm) devolvem uma cópia mais folgada ou mais apertada, para dar tolerância.

O capítulo de tipos do documento lista todas as propriedades e métodos dos cinco, com exemplos de cada um. É material de referência — leia quando precisar de algo específico, não do começo ao fim.

Se você quiser ver os cinco funcionando juntos em trabalhos reais, as receitas e os exemplos são o lugar.

DigitalOceanObrigado à DigitalOcean pela hospedagem deste site.