Como alinhar uma coluna à borda direita de um Row no gpdf?
Duas coisas são necessárias: um Col vazio de preenchimento para empurrar a coluna até a borda direita e template.AlignRight() no texto de dentro.
A pergunta, em outras palavras
Você escreveu um row com um único r.Col(4, ...), esperando que a coluna caísse do lado direito da página. Ela renderizou à esquerda, ocupando um terço da largura, com um vão grande à direita. Ou você conseguiu levar a coluna para a direita, mas o texto dentro dela continua colado à esquerda. Qual é o idiomatismo real no gpdf?
Resposta curta
Não existe Offset, nem Push, nem Justify no nível do row no gpdf. Você empurra a coluna para a direita com um Col vazio de preenchimento e alinha o texto de dentro com template.AlignRight(). São duas coisas diferentes e quase sempre você precisa das duas.
page.AutoRow(func(r *template.RowBuilder) {
r.Col(8, func(c *template.ColBuilder) {}) // preenchimento
r.Col(4, func(c *template.ColBuilder) {
c.Text("Total: R$ 25.575,00", template.AlignRight())
})
})
O código
Um programa completo — o formato de cabeçalho de nota fiscal que traz a maioria das pessoas até aqui:
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()
// Bloco da esquerda e bloco da direita num row: 8 + 4 = 12.
page.AutoRow(func(r *template.RowBuilder) {
r.Col(8, func(c *template.ColBuilder) {
c.Text("Acme Ltda.", template.Bold(), template.FontSize(16))
c.Text("Av. Paulista 1-2-3, São Paulo", template.TextColor(pdf.Gray(0.4)))
})
r.Col(4, func(c *template.ColBuilder) {
c.Text("NOTA FISCAL 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))
})
})
// Bloco de totais: 8 spans de nada e 4 spans colados na margem.
page.AutoRow(func(r *template.RowBuilder) {
r.Col(8, func(c *template.ColBuilder) {})
r.Col(4, func(c *template.ColBuilder) {
c.Text("Subtotal: R$ 23.250,00", template.AlignRight())
c.Text("ISS (10%): R$ 2.325,00", template.AlignRight())
c.Spacer(document.Mm(2))
c.Line(template.LineThickness(document.Pt(1)))
c.Spacer(document.Mm(2))
c.Text("Total: R$ 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 que a coluna de preenchimento é necessária
Vale entender em vez de decorar, porque isso explica de quebra uma série de outras surpresas de layout do gpdf.
Um row é uma caixa horizontal. RowBuilder.build percorre r.cols em ordem e emite uma caixa filha por coluna, e o único estilo que ele define nessa filha é uma largura:
colBox := &document.Box{
Content: cb.buildNodes(),
BoxStyle: document.BoxStyle{
Width: document.Pct(float64(col.span) / float64(gridColumns) * 100),
},
}
gridColumns é 12. Então Col(4) é literalmente "uma caixa com 33,33 % da largura do row", nada mais. O row posiciona os filhos da esquerda para a direita e para quando os filhos acabam. Ele não tem nenhum conceito do espaço restante, porque document.BoxStyle não tem campo JustifyContent — faça grep no repositório inteiro e você não vai achar. Os campos são Width, Height, MinWidth, MaxWidth, MinHeight, MaxHeight, Margin, Padding, Border, Background, Direction e Position. Esse é todo o vocabulário.
Ou seja, um Col(4) sozinho produz um row contendo exatamente uma caixa de 33,33 % de largura, posicionada no início do fluxo horizontal. Os 66,67 % restantes não são "espaço vazio à direita da sua coluna" — não são nada. Não há elemento algum contra o qual empurrar.
O Col(8) de preenchimento dá ao motor de layout algo que ocupe esse espaço. Custa uma caixa vazia na árvore e não desenha nada.
O que também significa que Col(8) não tem nada de especial. Col(6) + Col(3) + Col(3) alinha o último bloco à direita igualmente bem, e depois você pode pendurar conteúdo nos preenchimentos sem reestruturar nada.
A armadilha: coluna à direita, texto à esquerda
O erro que mais consome tempo:
r.Col(8, func(c *template.ColBuilder) {})
r.Col(4, func(c *template.ColBuilder) {
c.Text("Total: R$ 25.575,00") // ← sem AlignRight
})
A coluna está onde você queria. O texto não. Ele começa na borda esquerda da coluna — a 66,67 % da página — então parece mais ou menos alinhado à direita, e se todos os seus valores tiverem o mesmo número de dígitos você pode não perceber até um total de três casas ficar ao lado de um de cinco e as vírgulas decimais pararem de bater.
template.AlignRight() é um TextOption (func(*document.Style) que define TextAlign), então vai na chamada individual de c.Text. Não há como defini-lo uma única vez para a coluna inteira. Repita em cada linha do bloco.
Tabelas, imagens, números de página
As opções de texto não entram em outros elementos. Cada um tem sua própria porta de alinhamento:
// Table: alinhamento por coluna, casado por posição com o slice de cabeçalho.
c.Table(header, rows, template.ColumnAlign(
document.AlignLeft, document.AlignRight, document.AlignRight,
))
// Image: alinhamento dentro da coluna que a contém.
c.Image(logoBytes, template.WithAlign(document.AlignRight))
// Números de página aceitam TextOptions, então AlignRight funciona direto.
c.PageNumber(template.AlignRight())
ColumnAlign recebe valores document.TextAlign, não valores TextOption — document.AlignRight, não template.AlignRight(). Fácil de trocar, e o compilador avisa na hora.
Quando o row não ajuda
Para algo fixado numa coordenada independentemente do resto da página — um carimbo de PAGO, uma marca d'água diagonal, uma marca de canto — saia da grade:
page.Absolute(document.Mm(140), document.Mm(60),
func(c *template.ColBuilder) {
c.Text("PAGO", template.AlignRight(), template.Bold(),
template.FontSize(28), template.TextColor(pdf.RGBHex(0xC62828)))
},
template.AbsoluteWidth(document.Mm(50)),
)
Essa é a saída de emergência, não o idiomatismo. Se você se pegar calculando coordenadas x para conteúdo comum, a resposta era a coluna de preenchimento.
Uma última coisa que vale saber: Col limita cada span individual a 1–12, mas ninguém verifica o total. Três chamadas Col(5) no mesmo row dão larguras percentuais somando 125 %, e a terceira coluna extrapola a margem direita em vez de quebrar linha. O gpdf não avisa. Conte seus spans.
Receitas relacionadas
- Como funciona a grade de 12 colunas no gpdf? — a grade por inteiro
- Como aninhar um Row dentro de um Col? — a outra pergunta sobre a grade plana
- Como definir a largura das colunas de uma tabela? — larguras e alinhamento dentro de
c.Table - Gere um PDF de nota fiscal em Go em menos de 50 linhas — esse padrão num documento inteiro
Experimente o gpdf
gpdf é uma biblioteca Go para gerar PDF. Licença MIT, zero dependências externas, suporte CJK nativo.
go get github.com/gpdf-dev/gpdf