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("Acme 주식회사")
})
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를 실행하면 서로 다르게 분할된 네 개의 행이 있는 한 페이지 PDF가 나옵니다.
왜 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는 이 관용 표현을 의도적으로 가져왔습니다 — 출력이 고정 너비의 종이라고 해서 레이아웃의 사고방식이 웹과 다를 이유는 없습니다.
계산을 구체적으로
A4 세로에 사면 15 mm 균일 여백이면 사용 가능한 너비는 180 mm. 행 안의 Col(4)는 그중 4/12, 즉 60 mm. Col(8)은 120 mm. 컬럼 사이 거터는 기본적으로 없습니다. 여백을 두고 싶다면 짧은 쪽 컬럼 안에 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)를 받지만, 안에 또 다른 행을 넣을 수는 없습니다. 중첩이 필요해 보이는 구조는 보통 페이지 레벨에서 두 형제 행으로 나누는 편이 더 깔끔합니다.
오프셋 컬럼 없음. Bootstrap의 .offset-2에 대응하는 기능은 없습니다. 오른쪽으로 밀고 싶다면 왼쪽에 빈 Col(n)을 두세요.
브레이크포인트 없음. PDF 페이지는 리사이즈되지 않습니다. 어떤 기기에서 열든 동일한 레이아웃 — 출력이 재배치되는 DOM이 아니라 고정 좌표의 래스터이기 때문입니다.
이 "없음"들이 설계의 핵심입니다. 그리드가 갖지 않는 기능 하나당, PDF 결과를 읽을 때 추론해야 할 모호성이 하나씩 줄어듭니다.
관련 글
- gpdf에 일본어 폰트를 임베드하려면? — 그리드 컬럼 안에서 CJK 다루기
- Go PDF 라이브러리 비교 2026 — Builder API와 gofpdf / gopdf / Maroto 비교
- 레이아웃 가이드 — 행, 컬럼, 간격의 전체 레퍼런스
gpdf 써보기
gpdf는 Go PDF 생성 라이브러리입니다. MIT 라이선스, 외부 의존성 0, 네이티브 CJK 지원.
go get github.com/gpdf-dev/gpdf