Como faço uma tabela se estender por várias páginas?
Você não faz nada. Passe ao gpdf uma tabela com mais linhas do que cabe, e ele pagina o corpo e repete o cabeçalho em cada página automaticamente.
A pergunta, dita de outro jeito
Tenho um relatório — itens de fatura, um log de transações, uma exportação de 300 linhas — e obviamente não cabe em uma página A4. Numa biblioteca de PDF em Go, o que eu preciso fazer para a tabela fluir para a página 2, a 3 e assim por diante, com o cabeçalho reaparecendo no topo a cada vez? No gpdf, a resposta é curta.
Resumo
Nada. Você escreve uma chamada a Table, dá a ela todas as suas linhas, e o gpdf a pagina:
c.Table(header, rows) // rows tem 300 entradas — o gpdf a divide entre páginas
O corpo é dividido linha por linha entre quantas páginas forem necessárias. A slice header é re-emitida no topo de cada página de continuação automaticamente — mesmas larguras de coluna, mesmo estilo. Não há método PageBreak() a chamar, nem opção MaxRowsPerPage, nem laço de contagem de linhas. O transbordo é trabalho do motor de layout, não seu.
Código que funciona
Um programa completo que produz uma tabela de várias páginas. Salve como main.go, rode go run ., obtenha report.pdf.
package main
import (
"fmt"
"log"
"os"
"github.com/gpdf-dev/gpdf"
"github.com/gpdf-dev/gpdf/document"
"github.com/gpdf-dev/gpdf/pdf"
"github.com/gpdf-dev/gpdf/template"
)
func main() {
doc := gpdf.NewDocument(
gpdf.WithPageSize(gpdf.A4),
gpdf.WithMargins(document.UniformEdges(document.Mm(20))),
)
brand := pdf.RGBHex(0x1A237E)
header := []string{"Date", "Invoice #", "Customer", "Amount"}
rows := make([][]string, 0, 200)
for i := 1; i <= 200; i++ {
rows = append(rows, []string{
fmt.Sprintf("2026-%02d-%02d", (i%6)+1, (i%28)+1),
fmt.Sprintf("INV-%05d", 10000+i),
fmt.Sprintf("Customer #%d", i),
fmt.Sprintf("$%d.00", 100+i*7),
})
}
page := doc.AddPage()
page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Text("2026 Invoice Ledger", template.FontSize(18), template.Bold())
c.Spacer(document.Mm(4))
c.Table(header, rows,
template.ColumnWidths(20, 20, 40, 20),
template.TableHeaderStyle(
template.TextColor(pdf.White),
template.BgColor(brand),
),
)
})
})
data, err := doc.Generate()
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile("report.pdf", data, 0o644); err != nil {
log.Fatal(err)
}
}
200 linhas em A4 acabam em cerca de oito páginas. Em cada uma delas o cabeçalho azul-escuro fica no topo; o corpo retoma exatamente onde a página anterior parou. A única coisa nesse código que sugere "várias páginas" é o 200 no limite do laço.
Como funciona
Vale entender para confiar nisso. Quando o motor de layout posiciona a tabela, ele mede as linhas do corpo em ordem e as adiciona à página atual até que a próxima linha excederia a altura disponível. As linhas que não couberam viram uma tabela de transbordo — um novo *document.Table carregando o mesmo Header, o mesmo Footer e as linhas de corpo restantes. O gpdf descarrega a parte já posicionada na página, abre a próxima, e reenvia a tabela de transbordo ao motor de layout com a altura da nova página. Repete até não sobrar nada.
Desse design decorrem duas coisas:
- O cabeçalho se repete porque ele mora em
tbl.Header, não no seu laço. A tabela de transbordo reutiliza a mesma slice, então é re-renderizada idêntica em cada página. Você ganha de graça. - Não há caso de borda "o cabeçalho não cabe". O motor reserva espaço para o cabeçalho antes de medir quantas linhas de corpo entram. Se uma página não comporta o cabeçalho mais pelo menos uma linha de corpo, a tabela inteira é empurrada para a próxima página em vez de ser dividida de forma estranha.
Rodapés que também se repetem
Se você quer uma linha de totais (ou um "resumo de página") que também apareça no rodapé de cada página, isso é document.Table.Footer — disponível quando você constrói a tabela na camada de documento em vez de pelo builder:
import "github.com/gpdf-dev/gpdf/document"
tbl := &document.Table{
Columns: []document.TableColumn{
{Width: document.Pct(20)}, {Width: document.Pct(20)},
{Width: document.Auto}, {Width: document.Pct(20)},
},
Header: headerRows, // []document.TableRow
Body: bodyRows,
Footer: []document.TableRow{footerRow},
}
A slice Footer se repete em cada página de continuação, mesmo mecanismo do cabeçalho. O c.Table(...) do builder não expõe um rodapé porque a maioria das tabelas curtas não precisa — quando precisa, você já saiu da zona do "caso comum", e o aprofundamento sobre tabelas percorre a camada de documento.
Forçar a tabela a começar em página nova
Não há uma opção "começar em página nova" por tabela. Você faz no nível de página — adicione uma página antes da linha que contém a tabela:
doc.AddPage() // a tabela abaixo começa no topo desta página
page2 := doc.AddPage()
page2.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Table(header, rows /* , opts... */)
})
})
Esse é o único controle de "quebra de página" que você precisa para tabelas, porque as quebras internas da tabela são tratadas para você e a externa é só "onde este bloco começa".
O que você não ganha
- "Mantenha estas linhas juntas". Toda linha de corpo é divisível. Não há anotação dizendo "o grupo de linhas 4–7 deve ficar em uma página". É uma lacuna conhecida. Se um item de fatura mais suas sublinhas realmente não podem ser rasgados entre páginas, a saída é começar uma página nova antes desse grupo, ou construir a tabela na camada de documento e inserir suas próprias dicas de quebra.
- Um rodapé só na última página.
document.Table.Footerse repete em cada página por design (totais de coluna por página são o caso comum). Para um total geral de uma vez só no fim do documento, adicione-o como um bloco separado depois da tabela, não dentro. - Numeração de página dentro da tabela. "Página 3 de 8" pertence ao rodapé do documento, não à tabela. Onde isso mora está em números de página, cabeçalhos e rodapés.
Erros que custam dez minutos
- Procurar uma opção
PageBreak. Não existe e você não quer — se está chamando ela na mão, já perdeu. Apenas passe todas as linhas. - Quebrar seus dados em pedaços por página você mesmo. As pessoas fazem
rows[0:40]na página 1,rows[40:80]na página 2… Não. Você vai errar a aritmética das linhas em algum ponto, a última página vai ficar curta, e o estilo do cabeçalho vai desviar. Entregue a slice inteira ao gpdf. - Esperar o cabeçalho só na página 1. Algumas bibliotecas fazem isso. O gpdf o repete em cada página, que é o que você quer para um relatório que alguém imprime e folheia.
- Uma fonte CJK de 6 MB numa tabela de 150 páginas. A fonte é subdividida para os glifos realmente usados, então isso vai bem — a saída continua pequena. Mas se por algum motivo você desativou a subdivisão, uma tabela longa é onde ela morde. Deixe a subdivisão ligada (é o padrão).
Receitas relacionadas
- Tabelas em PDFs de Go: larguras de coluna, linhas zebradas, quebras de página — a versão longa, incluindo
document.Tablee rodapés. - Como defino larguras de coluna personalizadas para uma tabela? — os casos de borda de
ColumnWidths. - Como crio linhas de tabela zebradas (zebra)? — e por que as listras continuam alinhadas ao cruzar uma quebra de página.
- Gere um PDF de fatura em Go em menos de 50 linhas — um documento real com uma tabela que pagina.
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