Todas as publicações

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.

por gpdf team

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

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

⭐ Favoritar no GitHub · Ler a documentação