Todas as publicações

Como definir um tamanho de papel personalizado no gpdf?

document.Size usa pontos: converta com document.Mm(100).Resolve(0, 0) e passe para WithPageSize. Etiquetas, cupons, JIS B5 e as armadilhas.

Publicado em 10 de novembro de 2026
por Taiki Noda

A pergunta, com outras palavras

A página que você precisa não é A4 nem Letter. É uma etiqueta de envio de 4×6 polegadas, um cupom térmico de 80 mm, um cartão-postal de 100×148 mm ou um JIS B5 — que, para complicar, não é a mesma coisa que a constante document.B5. WithPageSize recebe um document.Size. Como montar um com medidas arbitrárias, e o que dá errado quando você faz isso?

TL;DR

document.Size são dois campos float64 em pontos PDF (1/72 de polegada). Converta com Resolve e passe adiante:

label := document.Size{
	Width:  document.In(4).Resolve(0, 0), // 288 pt
	Height: document.In(6).Resolve(0, 0), // 432 pt
}
doc := template.New(template.WithPageSize(label))

No caminho feliz, é só isso. O resto do post trata das três formas de dar errado.

O código

Uma etiqueta de envio 4×6 polegadas completa. Verificado com gpdf v1.0.17 — o /MediaBox gerado é [0 0 288 432].

package main

import (
	"log"
	"os"

	"github.com/gpdf-dev/gpdf/document"
	"github.com/gpdf-dev/gpdf/template"
)

// mm converts millimetres to PDF points, the unit document.Size uses.
func mm(v float64) float64 { return document.Mm(v).Resolve(0, 0) }

// in converts inches to PDF points.
func in(v float64) float64 { return document.In(v).Resolve(0, 0) }

func main() {
	// A 4×6 inch shipping label: 288 × 432 pt.
	label := document.Size{Width: in(4), Height: in(6)}

	doc := template.New(
		template.WithPageSize(label),
		template.WithMargins(document.UniformEdges(document.Mm(6))),
	)

	page := doc.AddPage()
	page.AutoRow(func(r *template.RowBuilder) {
		r.Col(12, func(c *template.ColBuilder) {
			c.Text("SHIP TO", template.Bold(), template.FontSize(10))
			c.Text("Jane Doe", template.FontSize(18), template.Bold())
			c.Text("742 Evergreen Terrace")
			c.Text("Springfield, OR 97477")
			c.Spacer(document.Mm(6))
			c.QRCode("TRACK-1Z999AA10123456784", template.QRSize(document.Mm(40)))
		})
	})

	data, err := doc.Generate()
	if err != nil {
		log.Fatal(err)
	}
	if err := os.WriteFile("label.pdf", data, 0o644); err != nil {
		log.Fatal(err)
	}
}

Vale copiar os helpers mm / in para o seu projeto. Você vai escrevê-los de qualquer jeito.

Por que Size não é um Value

No resto do gpdf você passa document.Mm(20) e o motor de layout calcula os pontos depois. Margens, spacers, tamanho de QR — tudo é document.Value. Então é natural tentar isto:

document.Size{Width: document.Mm(100), Height: document.Mm(150)} // não compila

Não compila, porque Size é definido assim:

type Size struct {
	Width, Height float64
}

O motivo é que Size é a base do sistema de coordenadas. Um Value pode ser Pct ou Em, e resolvê-los exige um tamanho pai ou um tamanho de fonte. A página é o pai; não existe nada acima dela para servir de base a uma porcentagem. Então a página precisa ser números concretos, e esses números são pontos porque é assim que o /MediaBox de um PDF é escrito.

Resolve(parentSize, fontSize) é o método que o motor chama internamente. Para unidades absolutas os dois argumentos são ignorados, então Resolve(0, 0) significa só "me dê pontos". Os fatores são fixos: Mm ×2.83465, Cm ×28.3465, In ×72.

E a armadilha que vem junto. Isto compila:

document.Size{Width: 100, Height: 150} // você pensou em mm

e gera uma página de 35 × 53 mm. Sem erro, sem aviso — 100 pt é uma largura perfeitamente válida. Você descobre quando abre o PDF e a etiqueta parece um selo postal. Se for levar uma coisa só deste post: nunca escreva um número solto em um Size, a menos que esteja pensando em pontos.

Orientação e o problema do JIS B5

Largura primeiro, altura depois. Para paisagem, troque você mesmo ou chame Landscape():

template.WithPageSize(document.A4.Landscape())

Landscape() só troca se a altura for maior que a largura, então em um tamanho já em paisagem não faz nada. Útil quando o tamanho vem de configuração e você não sabe a orientação.

Uma observação: o document.B5 do gpdf é B5 ISO, 176 × 250 mm. O B5 japonês é JIS B5, 182 × 257 mm. No Brasil, o caso mais comum de tamanho personalizado é o cupom térmico de 80 mm e a etiqueta de envio — por exemplo, o DANFE simplificado de etiqueta usado por marketplaces costuma sair em 100 × 150 mm:

jisB5 := document.Size{Width: mm(182), Height: mm(257)}
etiqueta := document.Size{Width: mm(100), Height: mm(150)}

Um tamanho por documento

Não existe AddPage(size). WithPageSize é uma opção do documento, e todas as páginas — incluindo as de transbordo criadas pelo paginador — usam esse tamanho.

Se você precisa de uma carta A4 seguida de uma etiqueta 4×6 no mesmo arquivo, gere separadamente e mescle:

merged, err := gpdf.Merge([]gpdf.Source{
	{Data: coverA4},  // gerado com document.A4
	{Data: labelPDF}, // gerado com o tamanho 4×6
})

Cada página mantém seu próprio /MediaBox após a mesclagem — conferimos: 595.28 × 841.89 e depois 288 × 432.

Schema JSON: só nomes

O page.size do schema JSON aceita tamanhos nomeados — "A0"–"A10", "B0"–"B10", "C0"–"C10", "DL", "Letter", "Legal" — mais "orientation": "landscape". Uma string como "100mm x 150mm" falha com unknown page size: "100mm x 150mm".

Mas as opções passadas pelo Go têm prioridade sobre o schema. Então mantenha o template em JSON e defina o tamanho no código:

doc, err := gpdf.NewDocumentFromJSON(schema, data,
	gpdf.WithPageSize(document.Size{Width: mm(80), Height: mm(200)}),
)

O mesmo vale para NewDocumentFromTemplate.

Páginas pequenas e a margem de 20 mm

Essa nos pegou enquanto escrevíamos o post, então ganhou seção própria.

template.New usa por padrão margens de 20 mm nos quatro lados. No A4, faz sentido. Em uma etiqueta de produto de 50 × 30 mm, 40 mm de margem vertical deixam −10 mm de altura de conteúdo. Em uma página de 60 × 40 pontos (a armadilha do número solto de novo) com margens de 5 mm, sobram uns 12 pt — menos que uma linha de texto de 12 pt.

Nos dois casos, até a v1.0.16, Generate() não retornava erro. Ele simplesmente não retornava. O paginador continuava tentando posicionar uma linha que nunca ia caber. Três programas de teste travaram antes de acharmos o fator comum, e sinceramente foi meia hora jogada fora. E, enquanto tirávamos as capturas da correção, um quarto caso ainda travava: uma palavra mais larga que a coluna era quebrada no meio, e o texto restante ganhava um espaço a cada página, então nunca parecia parado. Esse a gente não previu.

Desde a v1.0.17, ele falha em microssegundos:

import "github.com/gpdf-dev/gpdf/document/layout"

data, err := doc.Generate()
if errors.Is(err, layout.ErrContentDoesNotFit) {
	// layout: content does not fit on an empty page: body area is
	// 28.35 x 0.00 pt on a 141.73 x 85.04 pt page; ...
	log.Fatal(err)
}

Os números da área de conteúdo costumam bastar para achar o erro. Altura zero ou negativa significa que as margens, o cabeçalho ou o rodapé comeram a página inteira.

Então: em qualquer coisa menor que A6, continue definindo as margens explicitamente. O erro avisa que algo está errado; ele não escolhe as margens por você.

Cupons são o caso mais amigável. Uma página de 80 mm de largura com altura fixa simplesmente pagina — 40 linhas em uma página de 80 × 120 mm deram três páginas. Se você quer uma tira contínua, precisa escolher a altura antes; o gpdf não estica a página para caber o conteúdo.

Receitas relacionadas

Experimente o gpdf

gpdf é uma biblioteca Go para gerar PDFs. Licença MIT, zero dependências externas, suporte nativo a CJK.

go get github.com/gpdf-dev/gpdf

⭐ Star no GitHub · Ler a documentação