全部文章

如何在 gpdf 表格中设置列宽?

在 c.Table 中传入 template.ColumnWidths(...)。值是相对于父 Col 宽度的百分比。合计 100 占满全宽,未传完的尾部列会自动均分剩余空间。

换个说法的问题

我有一个四列的表格。默认 c.Table(header, rows) 会把所有列做成等宽,但发票里 品名 列应该宽,数量 列应该窄。要怎么告诉 gpdf 每列的宽度?我设置的到底是什么单位?

一句话回答

template.ColumnWidths(...) 作为 TableOption 传入:

c.Table(header, rows, template.ColumnWidths(40, 15, 20, 25))

值是 父 Col 内容宽度的百分比,不是磅。合计不必是 100,但通常应该是——少了右边会留白,多了会从单元格溢出。

常规情况就这样。有意思的是百分比合计不到 100、传入的值少于列数、以及"父宽度"到底指什么。

可运行的代码(四列发票表格)

package main

import (
    "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))),
    )

    header := []string{"品名", "数量", "单价", "金额"}
    rows := [][]string{
        {"年度支持合同", "1", "¥1,200.00", "¥1,200.00"},
        {"现场培训(每天)", "3", "¥800.00", "¥2,400.00"},
        {"自定义模板开发", "12", "¥95.00", "¥1,140.00"},
    }

    page := doc.AddPage()
    page.AutoRow(func(r *template.RowBuilder) {
        r.Col(12, func(c *template.ColBuilder) {
            c.Table(header, rows,
                template.ColumnWidths(40, 15, 20, 25),
                template.TableHeaderStyle(template.BgColor(pdf.Gray(0.92))),
            )
        })
    })

    data, err := doc.Generate()
    if err != nil {
        log.Fatal(err)
    }
    if err := os.WriteFile("invoice.pdf", data, 0o644); err != nil {
        log.Fatal(err)
    }
}

r.Col(12, ...) 占满整行宽度。表格放在这个 Col 里,所以表格的 100% = grid Col 内容区的 100%。ColumnWidths(40, 15, 20, 25) 合计为 100,每一磅水平空间都被用满。

百分比是相对于"什么"的百分比

传入的数值会被包装成 document.Pct(w),对照 表格的内容宽度 来解析。表格内容宽度 = 所在 grid Col 的宽度 - 表格自身的 margin / padding / border(实际就是 Col 宽度——表格默认没有装饰)。

所以如果用 r.Col(6, ...)(半行)配 ColumnWidths(50, 50),每列实际是 行宽的 25%,不是 50%。百分比是相对于表格的,不是相对于页面的。

布局变更时这点很有用。把表格从全宽改成两个并排,ColumnWidths 不用改——会自动缩放。

数学不对劲时 gpdf 会怎么做

实际写代码时常见两种情况,布局引擎对它们的处理是确定的,值得了解。

情况 1:百分比合计不到 100。 每个值按字面使用。三列表格用 ColumnWidths(40, 30, 20) 得到 40% / 30% / 20%,右边留白 10%。ColumnWidths(50, 50, 50) 会溢出——第三列伸出父容器右边,可能挤进相邻列或跑出页面。

不会做归一化。算术由你负责。

情况 2:传入的值少于列数。 这一种更有意思。没有显式值的尾部列会变成 auto-width,平分剩余宽度:

// 三列表格,只给两个宽度。
c.Table(header3, rows3, template.ColumnWidths(40, 30))
// → 40% / 30% / 30%   (第三列拿到 100 - 40 - 30 = 30%)

如果显式值合计已经达到 100 或更多,auto 列会得到 0 宽,等于消失。如果合计不到 100,剩余按 auto 列的数量均分:

// 五列表格,给两个宽度。
c.Table(header5, rows5, template.ColumnWidths(50, 10))
// → 50% / 10% / 13.33% / 13.33% / 13.33%   (40% 三等分)

这条规则里藏着一个小技巧:传 0 也会让列变成 auto。三列表格写 ColumnWidths(0, 30, 30) 把后两列固定为 30%,第一列拿剩下的 40%。当你想说"这几列我要精确控制,其余交给你"时很合用。

反方向:值多于列数

超出列数的值会被默默忽略。两列表格传 ColumnWidths(40, 30, 20, 10),只用前两个。这种宽容也是 bug 温床——你从表头删掉一列但忘了删对应的宽度值,gpdf 不会警告,也不打日志。

列数本身从表头行的长度推断(没有表头则用第一条 body 行)。加一个表头单元格就多一列,gpdf 会发现并对你传的 ColumnWidths 重新分配。

如果不想用百分比

构建器 API 只暴露百分比。如果你确实需要固定宽度的列——比如不随页面变化的 50pt "数量"列——就要降到下一层(document 树):

import "github.com/gpdf-dev/gpdf/document"

tbl := &document.Table{
    Columns: []document.TableColumn{
        {Width: document.Auto},
        {Width: document.Pt(50)},
        {Width: document.Pt(80)},
        {Width: document.Pt(80)},
    },
    Header: /* ... */,
    Body:   /* ... */,
}

可以混用:document 层的 Auto / Pt / Mm / Pct 都支持。第一列设为 Auto 时拿走三列固定宽度之后剩下的部分。这更接近 CSS 的 <col> 元素,而不是百分比体系。

代价是失去 c.Table(header, rows, ...) 自动构建单元格的便利,但对那种要在实体信纸抬头上打印、要求列位置稳定的发票,这个权衡值得。

相关菜谱

试用 gpdf

gpdf 是用于生成 PDF 的 Go 库。MIT 许可,零外部依赖,原生支持 CJK。

go get github.com/gpdf-dev/gpdf

⭐ Star on GitHub · 阅读文档