Todas las publicaciones

¿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.Footer se 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

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

⭐ Star on GitHub · Read the docs