How do I set column widths in a gpdf table?
Pass template.ColumnWidths(...) to c.Table. Values are percentages of the parent column's width. Sum to 100; trailing missing values auto-distribute.
The question, in other words
I have a table with four columns. By default c.Table(header, rows) makes them all the same width, but for an invoice the Description column should be wide and Qty should be narrow. How do I tell gpdf the per-column widths, and what unit am I actually setting?
The quick answer
Pass template.ColumnWidths(...) as a TableOption:
c.Table(header, rows, template.ColumnWidths(40, 15, 20, 25))
The values are percentages of the parent column's content width, not points. They don't have to sum to 100, but they usually should — anything missing leaves empty space on the right, anything extra overflows the cell.
That's it for the common case. The interesting parts are what happens when the percentages don't sum to 100, when you pass fewer values than there are columns, and what the parent width actually is.
Working code (a four-column invoice table)
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{"Description", "Qty", "Unit price", "Total"}
rows := [][]string{
{"Annual support contract", "1", "$1,200.00", "$1,200.00"},
{"On-site training (per day)", "3", "$800.00", "$2,400.00"},
{"Custom template development", "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, ...) claims the full row width. The table sits inside that column, so 100% of the table = 100% of the grid Col content area. With ColumnWidths(40, 15, 20, 25) summing to 100, every PDF point of horizontal space is used.
What the percentages are a percentage of
The number you pass is forwarded to document.Pct(w) and resolved against the table's content width. That's the width of the grid Col the table lives in, minus any margin, padding, and border on the table itself (in practice, just the Col width — table styling defaults to none).
So with r.Col(6, ...) (half the row) and ColumnWidths(50, 50), each table column is 25% of the row width, not 50%. The percentages are local to the table, not to the page.
This matters if you ever swap a table from a full-width row into a side-by-side layout. The ColumnWidths call doesn't need to change — it scales.
What gpdf does when the math doesn't work out
Two cases come up constantly. Both are handled by the layout engine in a specific way that's worth knowing.
Case 1: percentages don't sum to 100. Each value is taken at face value. ColumnWidths(40, 30, 20) on a three-column table produces columns at 40%, 30%, and 20% of the parent — leaving 10% empty on the right. ColumnWidths(50, 50, 50) overflows; the third column extends past the parent edge and may bleed into the next page column or off the page.
There is no normalization step. gpdf trusts you to do the arithmetic.
Case 2: fewer widths than there are columns. This is the more interesting one. The trailing columns become auto-width and share the remainder equally:
// Three-column table, only two widths given.
c.Table(header3, rows3, template.ColumnWidths(40, 30))
// → 40% / 30% / 30% (the third column gets 100 - 40 - 30 = 30%)
If the explicit widths already sum to 100 or more, the auto columns get zero width and effectively disappear. If they sum to less than 100, the leftover is divided equally among the auto columns:
// Five-column table, two widths given.
c.Table(header5, rows5, template.ColumnWidths(50, 10))
// → 50% / 10% / 13.33% / 13.33% / 13.33% (40% split three ways)
There's a useful trick hiding in this rule: passing 0 for a column also makes it auto. So ColumnWidths(0, 30, 30) on a three-column table fixes the last two at 30% each and gives the first column the remaining 40%. This is how you say "I care about these specific widths; let gpdf handle the rest."
The other direction: too many widths
Extra values beyond the column count are silently ignored. ColumnWidths(40, 30, 20, 10) on a two-column table just uses the first two. This is forgiving but it's also a bug magnet — if you delete a column from your header but forget to drop the matching width, gpdf won't tell you. There's no warning log.
The column count itself comes from the header row's length (or the first body row, if there's no header). Add a header cell and you've added a column; gpdf will discover this and re-balance whatever ColumnWidths you passed.
When percentages aren't what you want
The builder API only exposes percentages. If you genuinely need a fixed-width column — say, a 50pt "Qty" column that doesn't scale with the page — you have to drop one layer down to the document tree:
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: /* ... */,
}
Mix and match: Auto, Pt, Mm, Pct all work at the document layer. The first column with Auto gets whatever's left after the three fixed columns are subtracted. This is closer to the CSS <col> element than to a percentage system.
You give up the convenience of c.Table(header, rows, ...) doing the cell construction for you, but for invoices that need to print on physical letterhead with stable column positions, the trade is worth it.
Related recipes
- How does the 12-column grid work in gpdf? — how the row's 12 columns become the parent width that table percentages resolve against
- Generate an invoice PDF in Go in under 50 lines —
ColumnWidths(40, 15, 20, 25)in context with the rest of an invoice document
Try gpdf
gpdf is a Go library for generating PDFs. MIT licensed, zero external dependencies, native CJK support.
go get github.com/gpdf-dev/gpdf