gpdf에서 Col을 행의 오른쪽 끝에 붙이려면?
두 가지가 필요합니다. 빈 채움 Col로 열을 오른쪽 끝까지 밀고, template.AlignRight()로 열 안의 텍스트를 오른쪽 정렬합니다. 서로 다른 문제입니다.
질문을 다른 말로
r.Col(4, ...) 하나만 넣은 행을 만들고 그 열이 페이지 오른쪽에 오기를 기대했습니다. 실제로는 왼쪽에 폭 1/3로 그려지고 오른쪽에 큰 여백이 남았습니다. 아니면 열은 오른쪽으로 갔는데 안의 텍스트가 여전히 왼쪽에 붙어 있습니다. gpdf에서 올바른 방법은 무엇일까요?
결론
gpdf에는 Offset도, Push도, 행 단위 Justify도 없습니다. 열을 오른쪽으로 미는 것은 빈 채움 Col, 안의 텍스트를 오른쪽으로 보내는 것은 template.AlignRight(). 서로 다른 두 가지이고 거의 항상 둘 다 필요합니다.
page.AutoRow(func(r *template.RowBuilder) {
r.Col(8, func(c *template.ColBuilder) {}) // 채움
r.Col(4, func(c *template.ColBuilder) {
c.Text("합계: ₩25,575,000", template.AlignRight())
})
})
코드
완전한 프로그램입니다. 많은 사람이 이 질문에 도달하게 되는 청구서 헤더 모양입니다.
package main
import (
"log"
"os"
"github.com/gpdf-dev/gpdf/document"
"github.com/gpdf-dev/gpdf/pdf"
"github.com/gpdf-dev/gpdf/template"
)
func main() {
doc := template.New(
template.WithPageSize(document.A4),
template.WithMargins(document.UniformEdges(document.Mm(20))),
)
page := doc.AddPage()
// 왼쪽 블록과 오른쪽 블록을 한 행에: 8 + 4 = 12.
page.AutoRow(func(r *template.RowBuilder) {
r.Col(8, func(c *template.ColBuilder) {
c.Text("주식회사 애크미", template.Bold(), template.FontSize(16))
c.Text("서울시 강남구 테헤란로 1-2-3", template.TextColor(pdf.Gray(0.4)))
})
r.Col(4, func(c *template.ColBuilder) {
c.Text("청구서 No.1042", template.AlignRight(), template.Bold())
c.Text("발행일 2026-09-02", template.AlignRight(),
template.TextColor(pdf.Gray(0.4)))
})
})
page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Spacer(document.Mm(10))
})
})
// 합계 블록: 8 스팬의 공백 뒤에 4 스팬을 여백에 밀착시킵니다.
page.AutoRow(func(r *template.RowBuilder) {
r.Col(8, func(c *template.ColBuilder) {})
r.Col(4, func(c *template.ColBuilder) {
c.Text("소계: ₩23,250,000", template.AlignRight())
c.Text("부가세 10%: ₩2,325,000", template.AlignRight())
c.Spacer(document.Mm(2))
c.Line(template.LineThickness(document.Pt(1)))
c.Spacer(document.Mm(2))
c.Text("합계: ₩25,575,000", template.AlignRight(),
template.Bold(), template.FontSize(14))
})
})
data, err := doc.Generate()
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile("right_aligned.pdf", data, 0o644); err != nil {
log.Fatal(err)
}
}
왜 채움 열이 필요한가
이 부분은 외우기보다 이해해 둘 가치가 있습니다. gpdf 레이아웃에서 마주치는 다른 몇 가지 의외의 동작도 같이 설명되기 때문입니다.
행은 수평 방향 박스입니다. RowBuilder.build는 r.cols를 순서대로 돌면서 열마다 자식 박스를 하나씩 만드는데, 그 자식에 설정하는 스타일은 폭 하나뿐입니다.
colBox := &document.Box{
Content: cb.buildNodes(),
BoxStyle: document.BoxStyle{
Width: document.Pct(float64(col.span) / float64(gridColumns) * 100),
},
}
gridColumns는 12입니다. 즉 Col(4)는 말 그대로 "행 폭의 33.33%인 박스"이고 그 이상은 아닙니다. 행은 자식을 왼쪽에서 오른쪽으로 배치하다가 자식이 떨어지면 멈춥니다. 남은 공간이라는 개념 자체가 없는데, document.BoxStyle에 JustifyContent 필드가 없기 때문입니다. 저장소 전체를 grep해도 나오지 않습니다. 있는 것은 Width, Height, MinWidth, MaxWidth, MinHeight, MaxHeight, Margin, Padding, Border, Background, Direction, Position뿐입니다. 어휘는 이게 전부입니다.
그래서 Col(4) 하나만 두면 폭 33.33%짜리 박스 하나만 들어 있는 행이 되고, 그것이 수평 흐름의 시작 지점에 놓입니다. 나머지 66.67%는 "열 오른쪽의 빈 공간"이 아니라 아예 아무것도 아닙니다. 밀어낼 상대가 없습니다.
채움용 Col(8)은 그 공간을 차지할 무언가를 레이아웃 엔진에 줍니다. 트리에 빈 박스 하나가 늘어날 뿐 렌더링 결과에는 아무것도 나오지 않습니다.
덧붙이면 Col(8)이 특별한 것은 아닙니다. Col(6) + Col(3) + Col(3)도 마지막 블록을 똑같이 오른쪽 끝으로 보내고, 나중에 채움 쪽에 콘텐츠를 넣어도 구조를 다시 짤 필요가 없습니다.
함정: 열은 오른쪽, 텍스트는 왼쪽
시간을 가장 많이 잡아먹는 실수가 이것입니다.
r.Col(8, func(c *template.ColBuilder) {})
r.Col(4, func(c *template.ColBuilder) {
c.Text("합계: ₩25,575,000") // ← AlignRight 없음
})
열은 원하는 자리에 있습니다. 텍스트는 그렇지 않습니다. 열의 왼쪽 경계, 즉 페이지의 66.67% 지점에서 시작하기 때문에 어느 정도는 오른쪽 정렬처럼 보입니다. 금액 자릿수가 전부 같은 동안에는 눈치채지 못하다가, 세 자리 숫자와 다섯 자리 숫자가 나란히 놓이면서 소수점이 어긋나는 순간에야 알아차리게 됩니다.
template.AlignRight()는 TextOption(TextAlign을 설정하는 func(*document.Style))이라서 개별 c.Text 호출에 붙입니다. 열 단위로 한 번에 설정하는 방법은 없습니다. 블록 안의 모든 줄에 써야 합니다.
표, 이미지, 페이지 번호
텍스트 옵션은 다른 요소 내부까지 닿지 않습니다. 요소마다 전용 입구가 있습니다.
// Table: header 슬라이스와 위치로 대응되는 열별 정렬.
c.Table(header, rows, template.ColumnAlign(
document.AlignLeft, document.AlignRight, document.AlignRight,
))
// Image: 속한 열 안에서의 정렬.
c.Image(logoBytes, template.WithAlign(document.AlignRight))
// 페이지 번호는 TextOption을 받으므로 AlignRight가 그대로 동작합니다.
c.PageNumber(template.AlignRight())
ColumnAlign이 받는 것은 TextOption이 아니라 document.TextAlign 값입니다. template.AlignRight()가 아니라 document.AlignRight. 헷갈리기 쉽지만 컴파일러가 바로 알려 줍니다.
행으로 해결이 안 될 때
페이지의 다른 요소와 무관하게 좌표에 고정하고 싶은 것 — 결제 완료 도장, 대각선 워터마크, 모서리 표시 — 은 그리드 밖으로 나갑니다.
page.Absolute(document.Mm(140), document.Mm(60),
func(c *template.ColBuilder) {
c.Text("결제완료", template.AlignRight(), template.Bold(),
template.FontSize(28), template.TextColor(pdf.RGBHex(0xC62828)))
},
template.AbsoluteWidth(document.Mm(50)),
)
이건 비상구이지 정석이 아닙니다. 평범한 콘텐츠를 위해 x 좌표를 계산하고 있다면 답은 채움 열 쪽입니다.
마지막으로 하나 더. Col은 개별 스팬을 1〜12로 클램프하지만 합계는 아무도 검사하지 않습니다. 한 행에 Col(5)를 셋 쓰면 폭 백분율 합이 125%가 되고, 세 번째 열은 줄바꿈 없이 오른쪽 여백을 넘어갑니다. gpdf는 경고하지 않습니다. 스팬은 직접 세세요.
관련 레시피
- gpdf의 12-컬럼 그리드는 어떻게 동작하나? — 그리드 전체 설명
- Col 안에 Row를 중첩하려면? — 평면 그리드의 또 다른 질문
- 표의 열 너비를 지정하려면? —
c.Table내부의 너비와 정렬 - Go로 50줄 이내에 청구서 PDF 만들기 — 이 패턴을 문서 전체에서 쓰는 예
gpdf 써 보기
gpdf는 Go용 PDF 생성 라이브러리입니다. MIT 라이선스, 외부 의존성 제로, CJK 기본 지원.
go get github.com/gpdf-dev/gpdf