¿Cómo alineo una columna al borde derecho de un Row en gpdf?
Hacen falta dos cosas: un Col vacío de relleno que empuje la columna al borde derecho y template.AlignRight() en el texto de dentro. Aquí está el código.
La pregunta, dicho de otro modo
Escribiste un row con un solo r.Col(4, ...) esperando que la columna cayera a la derecha de la página. Se renderizó a la izquierda, ocupando un tercio del ancho, con un hueco grande a su derecha. O conseguiste llevar la columna a la derecha pero el texto de dentro sigue pegado a la izquierda. ¿Cuál es el idioma real en gpdf?
Respuesta corta
En gpdf no hay Offset, ni Push, ni Justify a nivel de row. La columna se empuja a la derecha con un Col vacío de relleno, y el texto de dentro se alinea con template.AlignRight(). Son dos cosas distintas y casi siempre necesitas las dos.
page.AutoRow(func(r *template.RowBuilder) {
r.Col(8, func(c *template.ColBuilder) {}) // relleno
r.Col(4, func(c *template.ColBuilder) {
c.Text("Total: 25.575,00 €", template.AlignRight())
})
})
El código
Un programa completo: la cabecera de factura que trae aquí a la mayoría.
package main
import (
"log"
"os"
"github.com/gpdf-dev/gpdf/document"
"github.com/gpdf-dev/gpdf/pdf"
"github.com/gpdf-dev/gpdf/template"
)
func main() {
doc := template.New(
template.WithPageSize(document.A4),
template.WithMargins(document.UniformEdges(document.Mm(20))),
)
page := doc.AddPage()
// Bloque izquierdo y bloque derecho en un row: 8 + 4 = 12.
page.AutoRow(func(r *template.RowBuilder) {
r.Col(8, func(c *template.ColBuilder) {
c.Text("Acme S.L.", template.Bold(), template.FontSize(16))
c.Text("Calle Mayor 1-2-3, Madrid", template.TextColor(pdf.Gray(0.4)))
})
r.Col(4, func(c *template.ColBuilder) {
c.Text("FACTURA n.º 1042", template.AlignRight(), template.Bold())
c.Text("Emitida 2026-09-02", template.AlignRight(),
template.TextColor(pdf.Gray(0.4)))
})
})
page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Spacer(document.Mm(10))
})
})
// Bloque de totales: 8 spans de nada y 4 spans pegados al margen.
page.AutoRow(func(r *template.RowBuilder) {
r.Col(8, func(c *template.ColBuilder) {})
r.Col(4, func(c *template.ColBuilder) {
c.Text("Base imponible: 21.136,36 €", template.AlignRight())
c.Text("IVA (21 %): 4.438,64 €", template.AlignRight())
c.Spacer(document.Mm(2))
c.Line(template.LineThickness(document.Pt(1)))
c.Spacer(document.Mm(2))
c.Text("Total: 25.575,00 €", template.AlignRight(),
template.Bold(), template.FontSize(14))
})
})
data, err := doc.Generate()
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile("right_aligned.pdf", data, 0o644); err != nil {
log.Fatal(err)
}
}
Por qué hace falta la columna de relleno
Merece la pena entenderlo en vez de memorizarlo, porque de paso explica varias otras sorpresas del layout de gpdf.
Un row es una caja horizontal. RowBuilder.build recorre r.cols en orden y emite una caja hija por columna, y el único estilo que le pone a esa hija es un ancho:
colBox := &document.Box{
Content: cb.buildNodes(),
BoxStyle: document.BoxStyle{
Width: document.Pct(float64(col.span) / float64(gridColumns) * 100),
},
}
gridColumns vale 12. Así que Col(4) es literalmente «una caja que ocupa el 33,33 % del ancho del row», nada más. El row coloca a sus hijos de izquierda a derecha y para cuando se le acaban. No tiene ningún concepto del espacio sobrante, porque document.BoxStyle no tiene campo JustifyContent: haz grep en todo el repositorio y no lo vas a encontrar. Los campos son Width, Height, MinWidth, MaxWidth, MinHeight, MaxHeight, Margin, Padding, Border, Background, Direction y Position. Ese es todo el vocabulario.
Entonces un Col(4) solitario produce un row que contiene exactamente una caja del 33,33 % de ancho, situada al inicio del flujo horizontal. El 66,67 % restante no es «espacio vacío a la derecha de tu columna»: no es nada en absoluto. No hay ningún elemento contra el que empujar.
El Col(8) de relleno le da al motor de layout algo que ocupe ese espacio. Cuesta una caja vacía en el árbol y no dibuja nada.
Lo que también significa que Col(8) no tiene nada de especial. Col(6) + Col(3) + Col(3) alinea el último bloque a la derecha igual de bien, y luego puedes colgar contenido de los rellenos sin reestructurar nada.
La trampa: columna a la derecha, texto a la izquierda
El error que más tiempo se come:
r.Col(8, func(c *template.ColBuilder) {})
r.Col(4, func(c *template.ColBuilder) {
c.Text("Total: 25.575,00 €") // ← sin AlignRight
})
La columna está donde querías. El texto no. Empieza en el borde izquierdo de la columna —al 66,67 % de la página—, así que parece más o menos alineado a la derecha, y si todos tus importes tienen el mismo número de dígitos puede que no lo notes hasta que un total de tres cifras quede junto a uno de cinco y las comas decimales dejen de cuadrar.
template.AlignRight() es un TextOption (func(*document.Style) que fija TextAlign), así que va en la llamada individual a c.Text. No hay forma de fijarlo una sola vez para toda la columna. Repítelo en cada línea del bloque.
Tablas, imágenes, números de página
Las opciones de texto no entran en otros elementos. Cada uno tiene su propia puerta de alineación:
// Table: alineación por columna, emparejada por posición con el slice de cabecera.
c.Table(header, rows, template.ColumnAlign(
document.AlignLeft, document.AlignRight, document.AlignRight,
))
// Image: alineación dentro de la columna que la contiene.
c.Image(logoBytes, template.WithAlign(document.AlignRight))
// Los números de página aceptan TextOptions, así que AlignRight funciona directamente.
c.PageNumber(template.AlignRight())
ColumnAlign recibe valores document.TextAlign, no valores TextOption: document.AlignRight, no template.AlignRight(). Es fácil confundirlos, y el compilador te lo dirá al momento.
Cuando el row no te sirve
Para algo fijado a una coordenada al margen de lo demás —un sello de PAGADO, una marca de agua en diagonal, una marca de esquina— sal de la cuadrícula:
page.Absolute(document.Mm(140), document.Mm(60),
func(c *template.ColBuilder) {
c.Text("PAGADO", template.AlignRight(), template.Bold(),
template.FontSize(28), template.TextColor(pdf.RGBHex(0xC62828)))
},
template.AbsoluteWidth(document.Mm(50)),
)
Esa es la salida de emergencia, no el idioma. Si te descubres calculando coordenadas x para contenido normal, la respuesta era la columna de relleno.
Una última cosa que conviene saber: Col limita cada span individual a 1–12, pero nadie comprueba el total. Tres llamadas a Col(5) en un mismo row te dan anchos porcentuales que suman 125 %, y la tercera columna se sale del margen derecho en lugar de bajar de línea. gpdf no te avisa. Cuenta tus spans.
Recetas relacionadas
- ¿Cómo funciona la cuadrícula de 12 columnas en gpdf? — la cuadrícula al completo
- ¿Cómo anidar un Row dentro de un Col? — la otra pregunta sobre la cuadrícula plana
- ¿Cómo defino el ancho de las columnas de una tabla? — anchos y alineación dentro de
c.Table - Genera una factura PDF en Go en menos de 50 líneas — este patrón en un documento entero
Prueba gpdf
gpdf es una biblioteca de Go para generar PDF. Licencia MIT, cero dependencias externas, soporte CJK nativo.
go get github.com/gpdf-dev/gpdf