如何把 gpdf 的 Col 对齐到行的右边缘?
需要做两件事:用一个空的填充 Col 把列推到右边缘,再用 template.AlignRight() 让列内文本右对齐。两者是分开的,通常都要。
换个说法
你写了一个只包含一个 r.Col(4, ...) 的行,以为这个列会落在页面右侧。结果它渲染在左边,占了三分之一宽度,右侧留下一大片空白。或者列确实到了右边,但里面的文本还是贴着左边。在 gpdf 里正确的写法是什么?
结论
gpdf 没有 Offset,没有 Push,也没有行级别的 Justify。把列推到右边靠的是一个空的填充 Col,让列内文本靠右靠的是 template.AlignRight()。 这是两件不同的事,而且几乎总是两个都要。
page.AutoRow(func(r *template.RowBuilder) {
r.Col(8, func(c *template.ColBuilder) {}) // 填充
r.Col(4, func(c *template.ColBuilder) {
c.Text("合计: ¥25,575.00", template.AlignRight())
})
})
代码
一个完整的程序 —— 也就是大多数人因此来搜索的那种发票抬头结构:
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()
// 左右两块放在一行:8 + 4 = 12。
page.AutoRow(func(r *template.RowBuilder) {
r.Col(8, func(c *template.ColBuilder) {
c.Text("艾克梅有限公司", template.Bold(), template.FontSize(16))
c.Text("上海市浦东新区世纪大道 1-2-3 号", template.TextColor(pdf.Gray(0.4)))
})
r.Col(4, func(c *template.ColBuilder) {
c.Text("发票 No.1042", template.AlignRight(), template.Bold())
c.Text("开具日期 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))
})
})
// 合计区块:8 跨度的空白,然后 4 跨度紧贴边距。
page.AutoRow(func(r *template.RowBuilder) {
r.Col(8, func(c *template.ColBuilder) {})
r.Col(4, func(c *template.ColBuilder) {
c.Text("小计: ¥23,250.00", template.AlignRight())
c.Text("增值税 10%: ¥2,325.00", template.AlignRight())
c.Spacer(document.Mm(2))
c.Line(template.LineThickness(document.Pt(1)))
c.Spacer(document.Mm(2))
c.Text("合计: ¥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)
}
}
为什么必须要填充列
这一段值得理解而不是死记,因为它同时解释了 gpdf 排版中其他几个让人意外的行为。
行是一个水平方向的盒子。RowBuilder.build 按顺序遍历 r.cols,每个列生成一个子盒子,而它给这个子盒子设置的样式只有宽度:
colBox := &document.Box{
Content: cb.buildNodes(),
BoxStyle: document.BoxStyle{
Width: document.Pct(float64(col.span) / float64(gridColumns) * 100),
},
}
gridColumns 是 12。所以 Col(4) 字面意思就是「一个宽度为行宽 33.33% 的盒子」,仅此而已。行从左到右排列子元素,子元素排完就停。它没有「剩余空间」这个概念,因为 document.BoxStyle 根本没有 JustifyContent 字段 —— 在整个仓库里 grep 也找不到。它只有 Width、Height、MinWidth、MaxWidth、MinHeight、MaxHeight、Margin、Padding、Border、Background、Direction、Position。这就是全部词汇。
所以单独一个 Col(4) 产生的是一个只含 33.33% 宽盒子的行,位于水平流的起点。剩下的 66.67% 不是「你的列右边的空白」,而是根本什么都没有。那里没有元素可以顶住。
填充用的 Col(8) 给了布局引擎一个占据这段空间的东西。代价是树里多一个空盒子,渲染出来什么都没有。
顺带一提,Col(8) 并不特殊。Col(6) + Col(3) + Col(3) 同样能把最后一块推到右边,而且以后往填充列里塞内容也不用重构结构。
陷阱:列在右,文本在左
最浪费时间的错误是这个:
r.Col(8, func(c *template.ColBuilder) {})
r.Col(4, func(c *template.ColBuilder) {
c.Text("合计: ¥25,575.00") // ← 少了 AlignRight
})
列在你想要的位置。文本不在。它从列的左边界开始 —— 也就是页面 66.67% 的位置 —— 所以看上去有点像右对齐。如果你的数字位数恰好都一样,可能一直没发现,直到三位数的合计和五位数的排在一起,小数点对不齐了才注意到。
template.AlignRight() 是一个 TextOption(设置 TextAlign 的 func(*document.Style)),所以它加在单个 c.Text 调用上。没有办法给整个列设置一次。块里每一行都要写。
表格、图片、页码
文本选项进不到其他元素内部。每种元素都有自己的对齐入口:
// Table:按位置对应 header 切片的逐列对齐。
c.Table(header, rows, template.ColumnAlign(
document.AlignLeft, document.AlignRight, document.AlignRight,
))
// Image:在所属列内的对齐。
c.Image(logoBytes, template.WithAlign(document.AlignRight))
// 页码接受 TextOption,所以 AlignRight 直接可用。
c.PageNumber(template.AlignRight())
ColumnAlign 接受的是 document.TextAlign 值,不是 TextOption —— 是 document.AlignRight,不是 template.AlignRight()。容易搞混,不过编译器会立刻告诉你。
当行帮不上忙时
对于要固定在某个坐标、和页面上其他内容无关的东西 —— 已付印章、斜向水印、角标 —— 就离开网格:
page.Absolute(document.Mm(140), document.Mm(60),
func(c *template.ColBuilder) {
c.Text("已付款", template.AlignRight(), template.Bold(),
template.FontSize(28), template.TextColor(pdf.RGBHex(0xC62828)))
},
template.AbsoluteWidth(document.Mm(50)),
)
这是逃生口,不是惯用写法。如果你发现自己在为普通内容计算 x 坐标,答案其实是填充列。
最后还有一点:Col 会把单个跨度限制在 1〜12,但没有人检查总和。一行里写三个 Col(5),宽度百分比加起来是 125%,第三列不会换行,而是溢出右边距。gpdf 不会警告你。跨度请自己数。
相关做法
- gpdf 的 12 列网格如何工作? —— 网格的完整说明
- 如何在 Col 中嵌套一个 Row? —— 扁平网格的另一个问题
- 如何设置表格的列宽? ——
c.Table内部的宽度与对齐 - 50 行以内用 Go 生成发票 PDF —— 在完整文档里使用这个模式
试试 gpdf
gpdf 是一个 Go 的 PDF 生成库。MIT 许可,零外部依赖,原生支持 CJK。
go get github.com/gpdf-dev/gpdf