gpdf のテーブルで列幅を指定する方法
c.Table に template.ColumnWidths(...) を渡す。値は親 Col 幅に対するパーセンテージ。合計 100 で全幅、末尾を省くと残りが自動分配される。
質問を言い換えると
4 列のテーブルがある。デフォルトの c.Table(header, rows) は全列を等幅にするが、請求書なら 品目 の列は広く、数量 の列は狭くしたい。列ごとの幅をどう指定するのか。そして、その値は何の単位なのか。
即答
template.ColumnWidths(...) を TableOption として渡す:
c.Table(header, rows, template.ColumnWidths(40, 15, 20, 25))
値は 親 Col の content 幅に対するパーセンテージ。pt ではない。合計が 100 である必要はないが、通常はそうすべき — 足りなければ右側に空白が残り、超過すればセルからはみ出す。
普通のケースはこれだけ。面白いのは、合計が 100 にならないとき、列数より少ない値を渡したとき、そして「親幅」が実際に何を指すのか、という 3 点。
動くコード (4 列の請求書テーブル)
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", "¥120,000", "¥120,000"},
{"オンサイト研修 (1 日)", "3", "¥80,000", "¥240,000"},
{"カスタムテンプレート開発", "12", "¥9,500", "¥114,000"},
}
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 の content 幅の 100%。ColumnWidths(40, 15, 20, 25) で合計 100 にすると、横の PDF ポイントは隅から隅まで使い切られる。
パーセンテージは「何」のパーセンテージか
渡した数値は内部で document.Pct(w) に包まれ、テーブルの content 幅 に対して解決される。テーブルの content 幅は、配置先の grid Col の幅から、テーブル自身のマージン・パディング・ボーダーを引いたもの (実用上は Col の幅そのまま — テーブルの装飾はデフォルトでは無し)。
つまり r.Col(6, ...) (行の半分) の中で ColumnWidths(50, 50) と書いた場合、各テーブル列は 行幅の 25% になる。50% ではない。パーセンテージはテーブルローカルであって、ページ基準ではない。
これはレイアウト変更の時に効く。テーブルを全幅から横並び 2 つに変えても、ColumnWidths の数値は変えなくていい — そのままスケールする。
数値が合わないときに gpdf は何をするか
実装で頻繁にぶつかるケースが 2 つある。レイアウトエンジンの挙動は明確に決まっていて、知っておく価値がある。
ケース 1: パーセンテージの合計が 100 にならない。 各値はそのまま使われる。3 列テーブルで ColumnWidths(40, 30, 20) なら、列は 40% / 30% / 20% になり、右に 10% の空白が残る。ColumnWidths(50, 50, 50) は overflow する — 3 列目が親の右端を超えて、隣の列に食い込んだりページ外に出たりする。
正規化は行われない。算数は書き手の責任。
ケース 2: 列数より少ない値を渡した。 こちらの方が興味深い。明示値のない末尾列は auto-width になり、余った幅を均等に分け合う:
// 3 列テーブル、値は 2 つだけ。
c.Table(header3, rows3, template.ColumnWidths(40, 30))
// → 40% / 30% / 30% (3 列目は 100 - 40 - 30 = 30%)
明示値の合計が既に 100 以上なら、auto 列は 0 幅 になり、事実上消える。100 未満なら、残差を auto 列の数で均等に割る:
// 5 列テーブル、値は 2 つ。
c.Table(header5, rows5, template.ColumnWidths(50, 10))
// → 50% / 10% / 13.33% / 13.33% / 13.33% (40% を 3 等分)
このルールには便利な裏技がある: 値に 0 を渡すと、その列も auto 扱いになる。3 列テーブルで ColumnWidths(0, 30, 30) と書けば、後ろ 2 列を 30% ずつに固定して、残りの 40% を先頭列に渡せる。「この列とこの列だけ正確に決めたい、あとは任せる」と書きたいときに便利。
逆方向: 値が多すぎる場合
列数を超えた値は黙って無視される。2 列テーブルで ColumnWidths(40, 30, 20, 10) を渡しても、最初の 2 つしか使われない。寛容ではあるが、バグの温床でもある — ヘッダーから列を 1 つ消したのに対応する ColumnWidths の値を消し忘れても、gpdf は何も警告しない。ログも出ない。
列数自体はヘッダー行の長さから推定される (ヘッダーがなければ最初のボディ行から)。ヘッダーセルを 1 つ足せば列が 1 つ増え、gpdf は気づいて ColumnWidths を新しい列数に合わせて再分配する。
パーセンテージが嫌なとき
ビルダー API はパーセンテージしか公開していない。「数量」列を 50pt で固定してページサイズに依存させたくない、というケースでは、1 つ下のレイヤー (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 列は、固定 3 列を引いた残りを取る。CSS の <col> 要素に近い。
c.Table(header, rows, ...) がやってくれていたセル構築は自分で書くことになるが、紙のレターヘッドに印刷する請求書で列位置を厳密に揃えたい用途では、十分割に合う。
関連レシピ
- gpdf の 12 カラムグリッドの仕組み — 行の 12 列が、テーブルのパーセンテージ計算の基準になる「親幅」をどう決めるか
- Go で 50 行以内の請求書 PDF を生成する —
ColumnWidths(40, 15, 20, 25)を請求書全体の文脈で見る
gpdf を使ってみる
gpdf は Go の PDF 生成ライブラリ。MIT、ゼロ依存、CJK 対応。
go get github.com/gpdf-dev/gpdf