Como funciona o grid de 12 colunas do gpdf?
O grid de 12 colunas do gpdf usa r.Col(span, fn) com span de 1 a 12. A largura da coluna é (span/12) da linha. Sem breakpoints, sem gutter, previsível por design.
A pergunta, em outras palavras
Você já viu a API do gpdf — page builder, row builder, column builder — e o construtor da coluna recebe um número: r.Col(4, fn), r.Col(8, fn). O que é esse número, o que acontece se os spans não somarem 12, e como isso se compara ao grid que você já conhece do CSS?
Resposta curta
r.Col(span, fn) recebe um inteiro de 1 a 12. Esse inteiro é a fatia da linha que a coluna ocupa — span / 12 da largura disponível. Valores abaixo de 1 são fixados em 1, acima de 12 em 12, e a biblioteca não força que os spans somem 12 por linha. O grid tem 12 divisões fixas; o resto é você decidindo como cortar cada linha.
Um exemplo que funciona
package main
import (
"log"
"os"
"github.com/gpdf-dev/gpdf"
"github.com/gpdf-dev/gpdf/document"
"github.com/gpdf-dev/gpdf/template"
)
func main() {
doc := gpdf.NewDocument(
gpdf.WithPageSize(document.A4),
gpdf.WithMargins(document.UniformEdges(document.Mm(15))),
)
page := doc.AddPage()
// Largura total
page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Text("Nota Fiscal #2026-0416", template.FontSize(18), template.Bold())
})
})
// Cabeçalho em duas colunas (6 + 6)
page.AutoRow(func(r *template.RowBuilder) {
r.Col(6, func(c *template.ColBuilder) {
c.Text("Destinatário")
c.Text("Acme Ltda.")
})
r.Col(6, func(c *template.ColBuilder) {
c.Text("Data de emissão")
c.Text("2026-04-16")
})
})
// Resumo em três colunas (4 + 4 + 4)
page.AutoRow(func(r *template.RowBuilder) {
r.Col(4, func(c *template.ColBuilder) {
c.Text("Subtotal")
})
r.Col(4, func(c *template.ColBuilder) {
c.Text("Imposto")
})
r.Col(4, func(c *template.ColBuilder) {
c.Text("Total")
})
})
// Assimétrica (8 + 4) — corpo + painel lateral
page.AutoRow(func(r *template.RowBuilder) {
r.Col(8, func(c *template.ColBuilder) {
c.Text("Os itens aparecem aqui")
})
r.Col(4, func(c *template.ColBuilder) {
c.Text("Observações")
})
})
data, err := doc.Generate()
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile("layout.pdf", data, 0o644); err != nil {
log.Fatal(err)
}
}
Rode go run main.go. Você obtém uma página com quatro linhas, cada uma dividida de um jeito diferente.
Por que 12
12 é divisível por 2, 3, 4 e 6 sem sobra. Isso cobre quase todo layout real: metades (6+6), terços (4+4+4), quartos (3+3+3+3), sidebar + corpo (3+9 ou 4+8), corpo + trilho (8+4). Pegue um número com menos fatores e uma dessas composições deixa de sair barata. O Bootstrap fixou 12 em 2011 pela mesma razão, e hoje "grid de 12 colunas" é o vocabulário comum que designer e engenheiro de frontend já falam. O gpdf importa o idioma de propósito — um layout de PDF não é um modelo mental diferente do layout web, mesmo que o destino de render seja papel de largura fixa.
A matemática, explicitada
Com A4 retrato e 15 mm de margem uniforme, a largura útil é 180 mm. Um Col(4) dentro de uma linha ocupa 4/12 disso — 60 mm. Col(8) ocupa 120 mm. Entre colunas não há gutter por padrão. Se quiser respiro, adicione um c.Spacer dentro da coluna mais curta ou deixe um Col(1) vazio.
A largura é computada como porcentagem em tempo de build (a implementação está em gpdf/template/grid.go) e o motor de layout converte para pontos usando a largura atual da página menos as margens. Ou seja, o mesmo r.Col(6, fn) tem largura física diferente em A4 e Letter, mas a mesma proporção da linha.
Quando a soma não dá 12
gpdf não valida a soma dos spans. É proposital.
- Soma < 12: o lado direito da linha fica em branco. Útil quando você quer ancorar uma coluna à borda esquerda e deixar o resto vazio de propósito.
- Soma > 12: a última coluna vaza a margem direita. Geralmente é bug. O PDF sai torto, mas não há crash.
A maioria dos layouts fecha em exatamente 12 por linha, porque é o que preenche a página. Mas quando você quer um bloco de largura 6 centralizado, o jeito natural é Col(3) vazia, Col(6) conteúdo, Col(3) vazia — o grid foi desenhado para esse tipo de notação curta.
AutoRow vs Row
page.AutoRow(fn) cresce verticalmente até a coluna mais alta. A maioria das linhas deve usar isso.
page.Row(height, fn) fixa a altura. Conteúdo que passa é cortado. Use para cabeçalhos de nota fiscal que precisam ter exatamente 30 mm para alinhar o grampeamento downstream, e para qualquer situação em que consistência visual pesa mais do que liberdade de conteúdo.
page.Row(document.Mm(30), func(r *template.RowBuilder) {
r.Col(8, func(c *template.ColBuilder) {
c.Text("Logo")
})
r.Col(4, func(c *template.ColBuilder) {
c.Text("Nº da nota")
})
})
O que o grid não faz
Sem aninhamento. O ColBuilder aceita elementos de conteúdo (Text / Image / Table / List / Spacer), mas não outra linha dentro. Layouts que parecem pedir aninhamento normalmente ficam mais limpos expressos como duas linhas irmãs no nível da página.
Sem colunas de offset. O .offset-2 do Bootstrap não existe aqui. Para empurrar conteúdo para a direita, deixe um Col(n) vazio à esquerda.
Sem breakpoints. Páginas PDF não redimensionam. O grid produz o mesmo layout em qualquer dispositivo porque a saída é um raster de coordenadas fixas, não um DOM que re-flua.
Essas ausências são o ponto. Cada recurso que o grid não tem é uma classe de ambiguidade que o PDF final não precisa raciocinar.
Leitura relacionada
- Como embutir uma fonte japonesa no gpdf? — CJK dentro de colunas do grid
- Comparativo de bibliotecas Go PDF 2026 — como a API Builder se compara a gofpdf, gopdf e Maroto
- Guia de layout — referência completa de linhas, colunas e espaçamento
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