Todas as publicações

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 TextOptiondocument.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

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

⭐ Dê uma estrela no GitHub · Leia a documentação