Papel milimetrado em PDF na escala exata: 469 linhas, 15 KB e nenhum pixel
Papel milimetrado só serve se 10 cm na folha forem exatamente 100 quadradinhos. Muito arquivo para imprimir por aí é uma imagem dentro de um PDF: a resolução não fecha com milímetros, a grade vai até a borda da folha e o diálogo de impressão encolhe tudo para caber na área imprimível. Resultado: um gráfico de laboratório lido com uma régua que mente alguns por cento.
Escrevi um gerador que monta o PDF à mão, só com linhas vetoriais. Para uma folha A4 de papel milimetrado com margem de 10 mm, o arquivo tem 469 linhas, 15 KB, e não existe DPI em lugar nenhum. Seguem as decisões que fizeram diferença.
Milímetro em pontos: um número feio
O sistema de coordenadas do PDF é em pontos, 1/72 de polegada. Polegada é tranquilo (1/4" = 18 pt). Milímetro não:
export const PT_PER_IN = 72;
export const PT_PER_MM = 72 / 25.4; // 2.834645669...
A tentação é arredondar o passo para 2,83 pt e ir somando. Depois de 190 quadradinhos (a largura útil de uma A4 com margens de 10 mm), a soma dá 537,7 pt em vez de 538,58 pt: a grade fica 0,31 mm menor, e o erro cresce para a direita. Num papel cuja graça é ler meio milímetro, isso já aparece.
A correção é calcular cada linha a partir do índice, nunca acumulando:
for (let i = 0; i <= nx; i++) {
const [wt, col] = minorOrMajor(c, i, i === 0 || i === nx);
line(d, x0 + i * s, y0, x0 + i * s, y1, wt, col);
}
Só na hora de escrever o arquivo cada coordenada é arredondada para 0,01 pt:
const fmt = (v) => {
const r = Math.round(v * 100) / 100;
return Object.is(r, -0) ? "0" : String(r);
};
O erro máximo por linha fica em 0,005 pt, cerca de 0,002 mm, e não se acumula.
Só quadrados inteiros, e centralizados
A segunda regra: nunca cortar um quadrado na margem. Pega a área dentro das margens, vê quantos quadrados inteiros cabem e centraliza o bloco:
function fitCells(area, s) {
const nx = Math.max(1, Math.floor((area.x1 - area.x0) / s + 1e-6));
const ny = Math.max(1, Math.floor((area.y1 - area.y0) / s + 1e-6));
const x0 = (area.x0 + area.x1 - nx * s) / 2;
const y0 = (area.y0 + area.y1 - ny * s) / 2;
return { nx, ny, x0, y0 };
}
Na A4 isso dá 190 × 277 quadrados. O 1e-6 existe porque divisões que deveriam ser exatas às vezes saem 31,999999 em ponto flutuante e somem com uma coluna.
O tamanho da página também importa. A4 é 210 mm, ou seja 595,2756 pt; uso 595,28 × 841,89. Se você arredondar a largura para 595 pt, que é comum, a área útil passa a ter 189,9 mm e o floor joga fora a 190ª coluna. Em Letter (612 × 792 pt) a grade fica com 195 × 259 quadrados, centralizada com sobra de menos de meio milímetro de cada lado.
Linhas finas, mas não finas demais
Com 1 mm de passo, a espessura da linha decide se a folha parece papel milimetrado ou papel cinza. As linhas finas têm 0,35 pt (0,12 mm) e as de centímetro 0,8 pt (0,28 mm). Mesmo assim, as linhas finas cobrem uns 23% da área da folha (12% em cada direção). Por isso a cor padrão é um laranja claro, como no papel milimetrado tradicional: com azul escuro ou preto a página inteira fica escura.
Também não uso 0 w. No PDF, largura zero significa "a linha mais fina que o dispositivo consegue", ou seja, um pixel do dispositivo: na tela fica bonito, numa impressora a laser de 1200 dpi praticamente some.
As linhas grossas saem de um teste de módulo, e as bordas da grade são sempre grossas:
function minorOrMajor(c, i, edge) {
const major = (c.major > 0 && i % c.major === 0) || (edge && c.border);
return major ? [c.majorWeight, c.majorColor] : [c.weight, c.color];
}
Como 277 não é múltiplo de 10, a última linha horizontal é grossa por ser borda, não por ser centímetro. Ao todo são 49 linhas grossas e 420 finas.
Um stream curto
O desenho de uma página PDF é um texto com operadores pós-fixos. Agrupando todas as linhas do mesmo estilo, a espessura (w) e a cor (RG) aparecem uma vez por grupo e cada linha vira x y m x y l:
for (const [style, group] of groupBy(d.lines, (l) => `${l.w}|${l.c}|${l.dash ?? ""}`)) {
const [w, c, dash] = style.split("|");
ops.push(`${w} w ${rgb(c)} RG [${dash}] 0 d`);
for (const l of group) ops.push(`${fmt(l.x1)} ${fmt(H - l.y1)} m ${fmt(l.x2)} ${fmt(H - l.y2)} l`);
ops.push("S");
}
(O H - y inverte o eixo: no PDF o y cresce para cima.) A página A4 inteira de papel milimetrado vira um stream de 14,4 KB e um PDF de 15,2 KB. Uma imagem A4 a 300 dpi teria 2480 × 3508 pixels e, mesmo assim, seria reamostrada pela impressora.
A última etapa é sua: a impressão
Um PDF perfeito ainda sai pequeno com "Ajustar à página" marcado. Como a página já tem 10 mm de margem, mais do que a borda não imprimível da maioria das impressoras domésticas, dá para imprimir em "Tamanho real" / 100% sem perder nada. O teste mais rápido: meça 10 cm com uma régua e conte 100 quadrados, ou seja, dez intervalos entre linhas grossas.
Resumo
- Em mm, calcule cada linha a partir do índice (
x0 + i * s) e arredonde só na saída; somar um passo arredondado tira 0,31 mm em 190 quadrados. - Encaixe só quadrados inteiros e centralize; cuidado com A4 arredondada para 595 pt.
- Linha fina de 0,35 pt em cor clara; nunca
0 wpara impressão. - Agrupe linhas por estilo: 469 linhas cabem em 15 KB.
- Imprima em 100% e confira 10 cm = 100 quadrados.
O gerador (gratuito, roda no navegador, A4 ou Letter, com opção de linha grossa a cada 5 mm ou a cada 1 cm) está na fonte deste post.