3

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 w para 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.

Carregando publicação patrocinada...
1

Muito bom o cuidado de calcular cada linha a partir do índice e arredondar só na saída.

Sobre a etapa da impressão, dá para o próprio PDF ajudar: no catálogo do documento, /ViewerPreferences << /PrintScaling /None >> (existe desde o PDF 1.6) pede ao leitor que abra o diálogo já em "Tamanho real". O Acrobat respeita; outros visualizadores podem ignorar, então o aviso de imprimir em 100% continua necessário, mas diminui quem imprime encolhido sem perceber. Como você já monta o PDF à mão, é só um item a mais no objeto Catalog:

<< /Type /Catalog /Pages 2 0 R /ViewerPreferences << /PrintScaling /None >> >>