¿Cómo hago que una tabla abarque varias páginas?
No haces nada. Le pasas a gpdf una tabla con más filas de las que caben, y pagina el cuerpo y repite la cabecera en cada página automáticamente.
La pregunta, dicha de otra forma
Tengo un informe — líneas de factura, un registro de transacciones, una exportación de 300 filas — y obviamente no cabe en una página A4. En una librería de PDF de Go, ¿qué tengo que hacer para que la tabla fluya a la página 2, la 3 y así, con la cabecera reapareciendo arriba cada vez? En gpdf, la respuesta es corta.
Resumen
Nada. Escribes una llamada a Table, le das todas tus filas, y gpdf la pagina:
c.Table(header, rows) // rows tiene 300 entradas — gpdf la reparte entre páginas
El cuerpo se divide fila por fila entre tantas páginas como haga falta. El slice header se vuelve a emitir arriba de cada página de continuación automáticamente — mismos anchos de columna, mismo estilo. No hay método PageBreak() que llamar, ni opción MaxRowsPerPage, ni bucle de conteo de filas. El desbordamiento es trabajo del motor de layout, no tuyo.
Código que funciona
Un programa completo que produce una tabla de varias páginas. Guárdalo como main.go, ejecuta go run ., obtienes 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 filas en A4 acaban en unas ocho páginas. En cada una de ellas la cabecera azul oscuro se sienta arriba; el cuerpo retoma exactamente donde paró la página anterior. Lo único en ese código que insinúa "varias páginas" es el 200 del límite del bucle.
Cómo funciona
Vale la pena entenderlo para confiar en ello. Cuando el motor de layout coloca la tabla, mide las filas del cuerpo en orden y las va añadiendo a la página actual hasta que la siguiente fila excedería la altura disponible. Las filas que no cupieron se convierten en una tabla de desbordamiento — un nuevo *document.Table que lleva el mismo Header, el mismo Footer y las filas de cuerpo restantes. gpdf vuelca la parte ya colocada a la página, abre la siguiente, y reenvía la tabla de desbordamiento al motor de layout con la altura de la nueva página. Repite hasta que no queda nada.
De ese diseño se desprenden dos cosas:
- La cabecera se repite porque vive en
tbl.Header, no en tu bucle. La tabla de desbordamiento reutiliza el mismo slice, así que se vuelve a renderizar idéntica en cada página. Lo obtienes gratis. - No hay caso límite de "la cabecera no cabe". El motor reserva espacio para la cabecera antes de medir cuántas filas de cuerpo entran. Si una página no puede contener la cabecera más al menos una fila de cuerpo, la tabla entera se empuja a la siguiente página en vez de partirse de forma incómoda.
Pies de página que también se repiten
Si quieres una fila de totales (o un "resumen de página") que también aparezca al pie de cada página, eso es document.Table.Footer — disponible cuando construyes la tabla en la capa de documento en vez de a través del 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},
}
El slice Footer se repite en cada página de continuación, mismo mecanismo que la cabecera. El c.Table(...) del builder no expone un pie porque la mayoría de tablas cortas no lo necesitan — cuando sí, ya has salido de la zona del "caso común", y el análisis a fondo de las tablas recorre la capa de documento.
Forzar que la tabla empiece en página nueva
No hay una opción "empezar en página nueva" por tabla. Lo haces a nivel de página — añade una página antes de la fila que contiene la tabla:
doc.AddPage() // la tabla de abajo empieza arriba de esta página
page2 := doc.AddPage()
page2.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Table(header, rows /* , opts... */)
})
})
Ese es el único control de "salto de página" que necesitas para tablas, porque los saltos internos de la tabla se gestionan por ti y el externo es solo "dónde empieza este bloque".
Lo que no obtienes
- "Mantén estas filas juntas". Toda fila de cuerpo es divisible. No hay anotación que diga "el grupo de filas 4–7 debe quedarse en una página". Es un hueco conocido. Si una línea de factura más sus subfilas realmente no pueden partirse entre páginas, la solución es empezar una página nueva antes de ese grupo, o construir la tabla en la capa de documento e insertar tus propias pistas de salto.
- Un pie solo en la última página.
document.Table.Footerse repite en cada página por diseño (los totales de columna por página son el caso común). Para un total general de una sola vez al final del documento, añádelo como un bloque separado después de la tabla, no dentro. - Numeración de página dentro de la tabla. "Página 3 de 8" pertenece al pie del documento, no a la tabla. Dónde vive eso lo cubre números de página, cabeceras y pies.
Errores que cuestan diez minutos
- Buscar una opción
PageBreak. No la hay y no la quieres — si la estás llamando a mano ya has perdido. Simplemente pásale todas las filas. - Partir tus datos en trozos por página tú mismo. La gente hace
rows[0:40]en la página 1,rows[40:80]en la página 2… No. Te equivocarás en la aritmética de filas en algún punto, la última página quedará corta, y el estilo de la cabecera se desviará. Pásale el slice entero a gpdf. - Esperar la cabecera solo en la página 1. Algunas librerías hacen eso. gpdf la repite en cada página, que es lo que quieres para un informe que alguien imprime y hojea.
- Una fuente CJK de 6 MB en una tabla de 150 páginas. La fuente se subdivide a los glifos realmente usados, así que esto va bien — la salida sigue siendo pequeña. Pero si por algún motivo desactivaste la subdivisión, una tabla larga es donde muerde. Deja la subdivisión activada (es lo predeterminado).
Recetas relacionadas
- Tablas en PDFs de Go: anchos de columna, filas a rayas, saltos de página — la versión larga, incluyendo
document.Tabley pies de página. - ¿Cómo establezco anchos de columna personalizados para una tabla? — los casos límite de
ColumnWidths. - ¿Cómo creo filas de tabla a rayas (zebra)? — y por qué las rayas se mantienen alineadas al cruzar un salto de página.
- Genera un PDF de factura en Go en menos de 50 líneas — un documento real con una tabla que pagina.
Prueba gpdf
gpdf es una librería de Go para generar PDFs. Licencia MIT, cero dependencias externas, soporte nativo de CJK.
go get github.com/gpdf-dev/gpdf