gpdf の 12 列グリッドはどう動くのか?
gpdf の 12 列グリッドは r.Col(span, fn) に 1–12 の整数を渡すだけ。幅は span/12、ガターもブレークポイントもない PDF 向けの割り切った設計。
質問の言い換え
gpdf の API を触っていると、列のコンストラクタが数字を受け取る: r.Col(4, fn)、r.Col(8, fn)。この数字は何で、合計が 12 にならなかったらどうなり、CSS で見慣れたグリッドと何が違うのか?
結論
r.Col(span, fn) は 1〜12 の整数を取る。その整数は行幅のうち span / 12 の比率を占める。1 未満は 1 に、12 超は 12 にクランプされ、行ごとの合計を 12 にするかどうかはライブラリ側では強制しない。グリッドの分割数が 12 で固定されているだけで、残りは行をどう切り分けるかの選択の話。
動くサンプル
package main
import (
"log"
"os"
"github.com/gpdf-dev/gpdf"
"github.com/gpdf-dev/gpdf/document"
"github.com/gpdf-dev/gpdf/template"
)
func main() {
doc := gpdf.NewDocument(
gpdf.WithPageSize(document.A4),
gpdf.WithMargins(document.UniformEdges(document.Mm(15))),
)
page := doc.AddPage()
// 全幅
page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Text("請求書 #2026-0416", template.FontSize(18), template.Bold())
})
})
// 2 列ヘッダー (6 + 6)
page.AutoRow(func(r *template.RowBuilder) {
r.Col(6, func(c *template.ColBuilder) {
c.Text("請求先")
c.Text("株式会社アクメ")
})
r.Col(6, func(c *template.ColBuilder) {
c.Text("発行日")
c.Text("2026-04-16")
})
})
// 3 列サマリー (4 + 4 + 4)
page.AutoRow(func(r *template.RowBuilder) {
r.Col(4, func(c *template.ColBuilder) {
c.Text("小計")
})
r.Col(4, func(c *template.ColBuilder) {
c.Text("消費税")
})
r.Col(4, func(c *template.ColBuilder) {
c.Text("合計")
})
})
// 非対称 (8 + 4) — 本文 + サイドパネル
page.AutoRow(func(r *template.RowBuilder) {
r.Col(8, func(c *template.ColBuilder) {
c.Text("明細はここに並ぶ")
})
r.Col(4, func(c *template.ColBuilder) {
c.Text("備考")
})
})
data, err := doc.Generate()
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile("layout.pdf", data, 0o644); err != nil {
log.Fatal(err)
}
}
go run main.go で 1 ページの PDF が出る。4 つの行がそれぞれ違う割り方をしている。
なぜ 12 なのか
12 は 2、3、4、6 でそれぞれ綺麗に割れる。半分 (6+6)、三分割 (4+4+4)、四分割 (3+3+3+3)、サイドバー付き (3+9 や 4+8)、本文 + コラム (8+4) — 現実のレイアウトはだいたいこの中に収まる。因数が少ない数字を選ぶとこのどれかが成立しない。Bootstrap が 2011 年に 12 列を採用してから「12 カラムグリッド」は業界の共通語になっていて、デザイナーもフロントエンドエンジニアも共通の語彙で話せる。gpdf はこのイディオムを意図的に持ち込んでいる。出力が固定幅の紙だろうと、レイアウトの考え方は Web と別物ではない。
計算を具体的に
A4 縦で 15 mm のマージンを四辺に置くと、使える幅は 180 mm。行の中の Col(4) はそのうち 4/12 つまり 60 mm。Col(8) は 120 mm。列と列の間にガターは入らない (デフォルト 0)。隙間が欲しければ短い方の列の中に c.Spacer を追加するか、Col(1) を空のまま挟む。
幅の計算はビルド時に百分率で行われ (該当箇所は gpdf/template/grid.go)、レイアウトエンジンが現在のページ幅からマージンを引いた値に対して実際のポイントに解決する。つまり同じ r.Col(6, fn) でも A4 と Letter では物理的な幅は変わるが、行に対する比率は変わらない。
合計が 12 に届かない / 超える
gpdf は span の合計を検証しない。これは意図的な設計。
- 合計 < 12: 行の右側が空く。左端にだけ要素を置いて残りを意図的に空けたい場合に便利。
- 合計 > 12: 最後の列が右マージンからはみ出す。たいていバグで、PDF の見た目はおかしくなるがクラッシュはしない。
ほとんどのレイアウトは行あたり 12 でぴったり埋まる。ただし「中央に 6 幅の塊だけ置きたい」なら Col(3) 空、Col(6) 本体、Col(3) 空、というショートハンドが自然に書ける — このグリッドはそういう表現を狙って作られている。
AutoRow と Row の違い
page.AutoRow(fn) は一番高い列に合わせて行の高さが伸びる。ほとんどの行はこれでいい。
page.Row(height, fn) は高さを固定する。高さを超えたコンテンツはクリップされる。後工程のステープル位置を合わせるためにヘッダーを必ず 30 mm にしたい、といった「見た目の一貫性 > コンテンツの自由度」の場面で使う。
page.Row(document.Mm(30), func(r *template.RowBuilder) {
r.Col(8, func(c *template.ColBuilder) {
c.Text("ロゴ")
})
r.Col(4, func(c *template.ColBuilder) {
c.Text("請求番号")
})
})
グリッドにないもの
ネストはできない。ColBuilder はコンテンツ要素 (Text / Image / Table / List / Spacer) を受け取るが、中に別の行を入れることはできない。ネストが必要に見える構造は、ページ直下の兄弟行として 2 行に分けた方がすっきりすることが多い。
オフセット列もない。Bootstrap の .offset-2 相当は提供していない。右に寄せたければ空の Col(n) を左に挟む。
ブレークポイントもない。PDF はリサイズしない。どの端末で開いても同じレイアウトになる — 出力が DOM ではなく固定座標のラスタなので、再レイアウトという概念が存在しない。
この「ないもの」が設計上の利点になっている。グリッドが持たない機能ごとに、PDF の出力結果を読むときに考えなくていい曖昧さが 1 つずつ減っていく。
関連記事
- gpdf で日本語フォントを埋め込むには? — グリッド列の中で CJK を扱う
- Go PDF ライブラリ比較 2026 — Builder API が gofpdf / gopdf / Maroto とどう違うか
- レイアウトガイド — 行・列・スペーシングの完全リファレンス
gpdf を使ってみる
gpdf は Go の PDF 生成ライブラリ。MIT、ゼロ依存、CJK 対応。
go get github.com/gpdf-dev/gpdf