[{"data":1,"prerenderedAt":2138},["ShallowReactive",2],{"blog-ko-bootstrap-grid-thinking-for-pdf":3},{"id":4,"title":5,"author":6,"body":10,"date":2124,"description":2125,"draft":2126,"extension":2127,"howTo":2128,"image":2128,"meta":2129,"navigation":897,"path":2130,"seo":2131,"stem":2132,"tags":2133,"updated":2128,"__hash__":2137},"blogKo/ko/blog/017.bootstrap-grid-thinking-for-pdf.md","Bootstrap 식 사고를 PDF로: gpdf의 12 컬럼 그리드",{"name":7,"url":8,"avatar":9},"Taiki Noda","https://nadai.dev/en/about","https://nadai.dev/og-default.png",{"type":11,"value":12,"toc":2101},"minimark",[13,18,26,33,37,49,62,69,78,82,85,109,116,119,123,126,159,171,185,188,192,195,218,221,225,229,240,264,267,270,280,283,294,298,305,316,440,443,447,454,603,628,634,638,655,659,806,824,828,850,865,873,877,880,1914,1924,1928,1931,1956,1959,1963,1973,1991,2001,2015,2025,2028,2035,2039,2042,2059,2073,2077,2097],[14,15,17],"h2",{"id":16},"tldr","TL;DR",[19,20,21,25],"p",{},[22,23,24],"strong",{},"gpdf는 Bootstrap의 12 컬럼 그리드를 그대로 가져왔다. 12를 고른 이유는 1·2·3·4·6으로 깔끔하게 나뉘기 때문이다 — 실무에서 진짜로 쓰는 분할이 전부 들어간다. 정수 span 모델만 남기고 나머지는 전부 버렸다: 브레이크포인트 없음, 거터 없음, order 없음, auto-fill 없음."," 페이지는 행의 스택, 행은 한 개의 수평 Box, 그 안의 컬럼은 「12 중 몇 개」 단위로 너비가 결정된다.",[19,27,28,29,32],{},"그게 전부다. 구현은 Go 약 30줄. 흥미로운 부분은 ",[22,30,31],{},"이식하지 않은 것","이다.",[14,34,36],{"id":35},"이-글을-쓰는-이유","이 글을 쓰는 이유",[19,38,39,40,44,45,48],{},"gpdf는 Go용 PDF 생성 라이브러리다. 상위 레이아웃 API는 빌더 패턴이다: ",[41,42,43],"code",{},"page.AutoRow → r.Col(span, fn) → c.Text/Image/Table",". 처음 보는 사람이 ",[41,46,47],{},"r.Col(4, ...)","를 보고 보통 세 가지를 묻는다.",[50,51,52,56,59],"ol",{},[53,54,55],"li",{},"왜 12인가? 16, 24, 아니면 「원하는 만큼」은 안 되는가?",[53,57,58],{},"이건 CSS Grid? Bootstrap? 아니면 다른 무엇?",[53,60,61],{},"span 합이 12가 안 되면 어떻게 되는가?",[19,63,64,65,68],{},"이 글은 그 결정의 과정을 순서대로 풀어 답한다. 모든 판단은 한 가지 원칙으로 수렴한다: ",[22,66,67],{},"PDF 렌더링은 「적응적」이 아니라 「예측 가능」해야 한다."," 웹 페이지는 리사이즈에 반응해 다시 흐른다. PDF는 그러지 않는다. 이 차이 하나로 웹 그리드 시스템을 어렵게 만드는 요소 대부분이 사라지고, 훨씬 작은 디자인을 출시할 수 있게 된다.",[19,70,71,72,77],{},"「쓰는 법만 알고 싶다」면 ",[73,74,76],"a",{"href":75},"/ko/blog/12-column-grid","/blog/12-column-grid","의 레시피가 더 직접적이다. 이 글은 「왜 이런 모양인가」에 대한 이야기다.",[14,79,81],{"id":80},"pdf-레이아웃을-짜는-세-가지-선택지","PDF 레이아웃을 짜는 세 가지 선택지",[19,83,84],{},"상위 API를 시작했을 때 현실적인 선택지는 셋이었다.",[50,86,87,93,99],{},[53,88,89,92],{},[22,90,91],{},"절대 좌표."," 「(72, 540) pt에 텍스트를 그린다」. Go의 저수준 PDF 라이브러리 대부분이 이 방식이다. 자유도는 최대, UX는 최악. 좌표를 전부 직접 계산해야 한다.",[53,94,95,98],{},[22,96,97],{},"Flow + flexbox."," 콘텐츠를 위에서 아래로 쌓고, 행 안에서는 grow/shrink 비율로 자식을 가로로 분배. 강력하지만 레이아웃 패스가 비자명하다 — 제약 솔버가 필요하고 반올림 오차가 누적된다.",[53,100,101,104,105,108],{},[22,102,103],{},"고정 그리드 + 비율."," 페이지는 행의 스택. 행은 N개의 균등 슬롯으로 나뉜다. 컬럼은 정수 개의 슬롯을 차지한다. 너비 = ",[41,106,107],{},"슬롯 수 / N × 행 너비",". 제약 솔버 없음. grow/shrink 없음.",[19,110,111,112,115],{},"3번을 골랐다. Bootstrap이 10여 년 전 같은 이유로 같은 결론에 도달했다: ",[22,113,114],{},"실무에서 필요한 레이아웃의 대부분은 정수비 레이아웃이다."," 동일 너비 2 컬럼. 1/3 + 2/3 분할. 4 카드 행. 25-50-25 행. 어느 것도 제약 솔버를 필요로 하지 않는다.",[19,117,118],{},"남은 질문은 「N은 몇으로?」였다.",[14,120,122],{"id":121},"왜-12인가","왜 12인가",[19,124,125],{},"12는 마법이 아니지만, 임의도 아니다. 문서에서 정말로 원하는 정수 분할들을 떠올려보자:",[127,128,129,135,141,147,153],"ul",{},[53,130,131,134],{},[22,132,133],{},"2 컬럼"," — 좌우 절반",[53,136,137,140],{},[22,138,139],{},"3 컬럼"," — 1/3씩 (3 카드 갤러리)",[53,142,143,146],{},[22,144,145],{},"4 컬럼"," — 1/4씩 (KPI 스트립)",[53,148,149,152],{},[22,150,151],{},"6 컬럼"," — 1/6씩 (좁은 사이드 패널, 가끔)",[53,154,155,158],{},[22,156,157],{},"12 컬럼"," — 1/12씩 (희귀, 얇은 구분선)",[19,160,161,162,166,167,170],{},"12의 약수: 1, 2, 3, 4, 6, 12. 즉 1/6까지 「실제로 쓰는」 정수 분할이 ",[163,164,165],"em",{},"전부"," 들어간다. 10은 1/3을 못 만든다. 16도 못 만든다. 24는 다 만들 수 있지만 인지 부담이 두 배다 — ",[41,168,169],{},"r.Col(8, ...)","를 보고 1/3 (24÷3)인지 2/3 (8÷12)인지 매번 머릿속 변환이 필요해진다. 12는 사람들이 자주 쓰는 분할을 모두 커버하는 최소의 수다.",[19,172,173,174,177,178,180,181,184],{},"Bootstrap이 2011년에 12에 도달한 것도 같은 이유. 이후 CSS Grid는 추상을 한 단계 더 올려 ",[41,175,176],{},"1fr 2fr 1fr"," 같은 비율을 직접 쓰게 만들어 매직 넘버를 없앴다. 하지만 분수는 공짜가 아니다 — 읽는 사람에게 「형제를 다 봐야 의미가 정해진다」는 비용을 떠넘긴다. ",[41,179,47],{},"는 곧장 「행의 1/3」이라고 알 수 있다. ",[41,182,183],{},"r.Col(2fr, ...)","는 주변을 다 봐야 의미가 잡힌다.",[19,186,187],{},"레이아웃이 고정이고 눈으로 디버깅하는 PDF에서는 정수 모델이 더 잘 맞는다.",[14,189,191],{"id":190},"bootstrap에서-가져온-것","Bootstrap에서 가져온 것",[19,193,194],{},"세 개뿐이다.",[50,196,197,203,212],{},[53,198,199,202],{},[22,200,201],{},"12."," 분모. 다이얼에 새겨진 유일한 숫자.",[53,204,205,208,209,211],{},[22,206,207],{},"정수 1〜12의 span."," 분수도, CSS 단위도 아니다. ",[41,210,47],{},"는 「12 중 4」.",[53,213,214,217],{},[22,215,216],{},"사고 모델."," 페이지는 행의 스택, 행은 컬럼으로 분할. HTML로 10년간 써온 그리드와 같은 형태다.",[19,219,220],{},"여기까지는 Bootstrap과 같다. 진짜로 흥미로운 건 그다음이다.",[14,222,224],{"id":223},"버린-것","버린 것",[226,227,228],"h3",{"id":228},"브레이크포인트",[19,230,231,232,235,236,239],{},"Bootstrap의 ",[41,233,234],{},"col-md-6 col-lg-4","는 「태블릿에선 절반, 데스크톱에선 1/3」 식 지정이다. 웹에선 유용. ",[22,237,238],{},"PDF에선 의미가 없다."," PDF 페이지는 고정 캔버스다. 조회할 viewport도, resize 이벤트도, media query도 없다. 브레이크포인트는 통째로 삭제했다.",[19,241,242,243,246,247,246,250,246,253,246,256,259,260,263],{},"여기서 절약되는 양은 보이는 것보다 훨씬 크다. CSS 프레임워크가 ",[41,244,245],{},"col-xs-*",", ",[41,248,249],{},"col-sm-*",[41,251,252],{},"col-md-*",[41,254,255],{},"col-lg-*",[41,257,258],{},"col-xl-*","의 다섯 변종을 끌고 다니는 이유 자체가 브레이크포인트다. gpdf엔 그것들이 전혀 없다. API는 ",[41,261,262],{},"r.Col(span int, fn func(*ColBuilder))",". 시그니처 하나, 기억할 축 하나.",[226,265,266],{"id":266},"거터",[19,268,269],{},"Bootstrap 행은 컬럼 사이에 기본 horizontal padding이 들어간다. PDF엔 기본 거터가 필요 없다 — 컬럼 사이의 마진은 그리는 내용에 따라 완전히 달라지기 때문이다. 빽빽한 표는 0, 히어로 섹션은 24pt, 청구서 줄은 0.5pt 짜리 구분선 정도다. 그래서 간격은 명시적으로 만들었다.",[19,271,272,273,276,277,32],{},"거터가 필요하면 직접 넣는다: 컬럼 사이에 ",[41,274,275],{},"c.Spacer(...)","를 끼우거나, 안쪽을 padding 있는 Box로 감싼다. 그리드 자신은 요청하지 않은 픽셀을 절대 끼워 넣지 않는다. ",[22,278,279],{},"모든 점이 의미 있는 인쇄 매체에서는 「거터 없음」이 옳은 기본값",[226,281,282],{"id":282},"order",[19,284,285,286,289,290,293],{},"CSS는 ",[41,287,288],{},"order: 2","로 컬럼의 시각적 순서를 바꿀 수 있다. 같은 DOM이 좁은 화면에서는 다른 순서로 보이게 하는 반응형 기능. ",[22,291,292],{},"PDF에선 쓸 데가 없다."," 파일에 등장하는 순서가 곧 페이지에 등장하는 순서다. 검토조차 하지 않았다.",[226,295,297],{"id":296},"auto-fill-auto-fit","auto-fill / auto-fit",[19,299,300,301,304],{},"CSS Grid엔 ",[41,302,303],{},"repeat(auto-fit, minmax(200px, 1fr))","가 있다. 「200px 이상 컬럼을 들어가는 만큼 채워라」. 웹 갤러리에는 아름답다. PDF는 빌드 시점에 페이지 너비를 알고 있다. 레이아웃 엔진에 추측시킬 필요가 없다.",[19,306,307,308,311,312,315],{},"4 카드 행이 필요하면 ",[41,309,310],{},"r.Col(3, ...)","를 네 번. 6 카드면 ",[41,313,314],{},"r.Col(2, ...)","를 여섯 번. 「auto」 버전은 사용자 코드의 for 루프 한 줄이면 된다.",[317,318,323],"pre",{"className":319,"code":320,"language":321,"meta":322,"style":322},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","for _, item := range items {\n    r.Col(3, func(c *template.ColBuilder) {\n        c.Text(item.Name)\n    })\n}\n","go","",[41,324,325,357,404,428,434],{"__ignoreMap":322},[326,327,330,334,338,342,345,348,351,354],"span",{"class":328,"line":329},"line",1,[326,331,333],{"class":332},"s7zQu","for",[326,335,337],{"class":336},"sTEyZ"," _",[326,339,341],{"class":340},"sMK4o",",",[326,343,344],{"class":336}," item ",[326,346,347],{"class":340},":=",[326,349,350],{"class":332}," range",[326,352,353],{"class":336}," items ",[326,355,356],{"class":340},"{\n",[326,358,360,363,366,370,373,377,379,382,386,389,393,395,398,401],{"class":328,"line":359},2,[326,361,362],{"class":336},"    r",[326,364,365],{"class":340},".",[326,367,369],{"class":368},"s2Zo4","Col",[326,371,372],{"class":340},"(",[326,374,376],{"class":375},"sbssI","3",[326,378,341],{"class":340},[326,380,381],{"class":340}," func(",[326,383,385],{"class":384},"sHdIc","c",[326,387,388],{"class":340}," *",[326,390,392],{"class":391},"sBMFI","template",[326,394,365],{"class":340},[326,396,397],{"class":391},"ColBuilder",[326,399,400],{"class":340},")",[326,402,403],{"class":340}," {\n",[326,405,407,410,412,415,417,420,422,425],{"class":328,"line":406},3,[326,408,409],{"class":336},"        c",[326,411,365],{"class":340},[326,413,414],{"class":368},"Text",[326,416,372],{"class":340},[326,418,419],{"class":336},"item",[326,421,365],{"class":340},[326,423,424],{"class":336},"Name",[326,426,427],{"class":340},")\n",[326,429,431],{"class":328,"line":430},4,[326,432,433],{"class":340},"    })\n",[326,435,437],{"class":328,"line":436},5,[326,438,439],{"class":340},"}\n",[19,441,442],{},"세 줄. 프레임워크에 박을 필요는 없었다.",[226,444,446],{"id":445},"span-합-강제","span 합 강제",[19,448,449,450,453],{},"의외라 할 만한 부분: ",[22,451,452],{},"gpdf는 컬럼 span의 합이 12이기를 요구하지 않는다."," 의도된 것이다.",[317,455,457],{"className":319,"code":456,"language":321,"meta":322,"style":322},"page.AutoRow(func(r *template.RowBuilder) {\n    r.Col(4, func(c *template.ColBuilder) { c.Text(\"좌 1/3\") })\n    r.Col(4, func(c *template.ColBuilder) { c.Text(\"중 1/3\") })\n    // 합 = 8. 우측 1/3은 그냥 빈 공간.\n})\n",[41,458,459,488,543,592,598],{"__ignoreMap":322},[326,460,461,464,466,469,472,475,477,479,481,484,486],{"class":328,"line":329},[326,462,463],{"class":336},"page",[326,465,365],{"class":340},[326,467,468],{"class":368},"AutoRow",[326,470,471],{"class":340},"(func(",[326,473,474],{"class":384},"r",[326,476,388],{"class":340},[326,478,392],{"class":391},[326,480,365],{"class":340},[326,482,483],{"class":391},"RowBuilder",[326,485,400],{"class":340},[326,487,403],{"class":340},[326,489,490,492,494,496,498,501,503,505,507,509,511,513,515,517,520,523,525,527,529,532,536,538,540],{"class":328,"line":359},[326,491,362],{"class":336},[326,493,365],{"class":340},[326,495,369],{"class":368},[326,497,372],{"class":340},[326,499,500],{"class":375},"4",[326,502,341],{"class":340},[326,504,381],{"class":340},[326,506,385],{"class":384},[326,508,388],{"class":340},[326,510,392],{"class":391},[326,512,365],{"class":340},[326,514,397],{"class":391},[326,516,400],{"class":340},[326,518,519],{"class":340}," {",[326,521,522],{"class":336}," c",[326,524,365],{"class":340},[326,526,414],{"class":368},[326,528,372],{"class":340},[326,530,531],{"class":340},"\"",[326,533,535],{"class":534},"sfazB","좌 1/3",[326,537,531],{"class":340},[326,539,400],{"class":340},[326,541,542],{"class":340}," })\n",[326,544,545,547,549,551,553,555,557,559,561,563,565,567,569,571,573,575,577,579,581,583,586,588,590],{"class":328,"line":406},[326,546,362],{"class":336},[326,548,365],{"class":340},[326,550,369],{"class":368},[326,552,372],{"class":340},[326,554,500],{"class":375},[326,556,341],{"class":340},[326,558,381],{"class":340},[326,560,385],{"class":384},[326,562,388],{"class":340},[326,564,392],{"class":391},[326,566,365],{"class":340},[326,568,397],{"class":391},[326,570,400],{"class":340},[326,572,519],{"class":340},[326,574,522],{"class":336},[326,576,365],{"class":340},[326,578,414],{"class":368},[326,580,372],{"class":340},[326,582,531],{"class":340},[326,584,585],{"class":534},"중 1/3",[326,587,531],{"class":340},[326,589,400],{"class":340},[326,591,542],{"class":340},[326,593,594],{"class":328,"line":430},[326,595,597],{"class":596},"sHwdD","    // 합 = 8. 우측 1/3은 그냥 빈 공간.\n",[326,599,600],{"class":328,"line":436},[326,601,602],{"class":340},"})\n",[19,604,605,606,609,610,613,614,246,617,613,620,623,624,627],{},"라이브러리는 각 컬럼을 ",[41,607,608],{},"span/12 × 행 너비","로만 처리한다. 한 행에 4 + 4를 넣으면 우측 슬롯이 빈다. 7 + 8을 넣으면 두 번째 컬럼이 행 경계를 넘어 흘러나간다 — 이것도 의도다. 가끔은 페이지보다 넓은 레이아웃 그리드에 맞추려고 일부러 넘쳐야 할 때가 있다. span은 1〜12로 클램프된다 (",[41,611,612],{},"Col(0, ...)"," → ",[41,615,616],{},"Col(1, ...)",[41,618,619],{},"Col(99, ...)",[41,621,622],{},"Col(12, ...)",". ",[41,625,626],{},"gpdf/template/grid.go:120"," 참조). 자동 wrap도, 자동 밸런싱도 없다.",[19,629,630,631],{},"Bootstrap 구버전의 「합이 12를 넘으면 다음 행으로 wrap」 동작은 웹 반응형 문제에 대한 해법이었다. PDF엔 그 문제가 없다. 그 자리에 더 단순한 계약을 두었다: ",[22,632,633],{},"쓴 대로 나온다.",[226,635,637],{"id":636},"container-fluid-모드-no-gutters-offset-pushpull","container, fluid 모드, no-gutters, offset, push/pull",[19,639,640,641,644,645,644,648,651,652,654],{},"전부 안 넣었다. ",[41,642,643],{},"container-fluid","도, ",[41,646,647],{},"col-md-offset-3",[41,649,650],{},"col-md-push-2","도, Bootstrap 유틸리티 클래스 등가물은 하나도 없다. 컬럼을 오른쪽으로 밀고 싶으면 직접 감싼다: 빈 ",[41,653,310],{},"를 앞에 둔다. 여덟 글자 더, 새 개념은 없음.",[14,656,658],{"id":657},"gpdf-vs-bootstrap-vs-css-grid","gpdf vs Bootstrap vs CSS Grid",[660,661,662,681],"table",{},[663,664,665],"thead",{},[666,667,668,672,675,678],"tr",{},[669,670,671],"th",{},"기능",[669,673,674],{},"Bootstrap (CSS)",[669,676,677],{},"CSS Grid (CSS)",[669,679,680],{},"gpdf (Go)",[682,683,684,700,718,731,747,764,776,790],"tbody",{},[666,685,686,690,692,698],{},[687,688,689],"td",{},"그리드 크기",[687,691,157],{},[687,693,694,695,400],{},"임의 (",[41,696,697],{},"grid-template-columns",[687,699,157],{},[666,701,702,705,708,715],{},[687,703,704],{},"단위",[687,706,707],{},"클래스명",[687,709,710,711,714],{},"비율 (",[41,712,713],{},"fr","), px, %",[687,716,717],{},"정수 span 1〜12",[666,719,720,722,725,728],{},[687,721,228],{},[687,723,724],{},"5종 (xs/sm/md/lg/xl)",[687,726,727],{},"media query 통해",[687,729,730],{},"없음",[666,732,733,736,743,745],{},[687,734,735],{},"기본 거터",[687,737,738,739,742],{},"있음 (",[41,740,741],{},"gx-*"," 제어)",[687,744,730],{},[687,746,730],{},[666,748,749,752,757,762],{},[687,750,751],{},"시각 재정렬",[687,753,754],{},[41,755,756],{},"order-*",[687,758,759,761],{},[41,760,282],{}," 속성",[687,763,730],{},[666,765,766,769,771,774],{},[687,767,768],{},"auto-fill",[687,770,730],{},[687,772,773],{},"있음",[687,775,730],{},[666,777,778,781,784,787],{},[687,779,780],{},"합 > 12 시 wrap",[687,782,783],{},"있음(구버전) / 없음(flex)",[687,785,786],{},"해당 없음",[687,788,789],{},"없음 (오버플로 허용)",[666,791,792,795,798,801],{},[687,793,794],{},"구현 규모",[687,796,797],{},"약 3,000 LoC SCSS",[687,799,800],{},"브라우저 내부",[687,802,803],{},[22,804,805],{},"약 30 LoC Go",[19,807,808,809,812,813,816,817,820,821],{},"「30 LoC」는 진짜 숫자다. ",[41,810,811],{},"gpdf/template/grid.go","를 열어 세보면 된다: 상수 하나(",[41,814,815],{},"gridColumns = 12","), 정수를 클램프하는 빌더 메서드 하나, 행마다 한 개의 수평 Box를 내고 자식 너비는 ",[41,818,819],{},"Pct(span/12*100)","인 build 패스. 측정 패스 없음, flex 알고리즘 없음, 재밸런싱 없음. ",[22,822,823],{},"너비의 산술이 곧 알고리즘이다.",[14,825,827],{"id":826},"내부에서-무슨-일이-일어나는가","내부에서 무슨 일이 일어나는가",[19,829,830,833,834,837,838,841,842,845,846,849],{},[41,831,832],{},"r.Col(4, fn)","을 호출하면 gpdf는 행에 ",[41,835,836],{},"colEntry{span: 4, fn: fn}","를 append한다. 문서를 build할 때 각 entry는 ",[41,839,840],{},"Width: document.Pct(33.333…)","인 ",[41,843,844],{},"document.Box","가 되고, 컬럼 콘텐츠가 그 안에 중첩된다. 행 자신은 ",[41,847,848],{},"Direction: DirectionHorizontal","인 Box. PDF Writer(Layer 1)는 문서 순서대로 Box를 순회하며 콘텐츠 스트림을 출력하고, 레이아웃 엔진(Layer 2)은 너비/높이 해석을 하고, 그리드(Layer 3)는 정수 → 퍼센트 변환을 한다. 끝.",[19,851,852,853,856,857,860,861,864],{},"이게 30줄로 끝나는 이유는 ",[22,854,855],{},"퍼센트와 정수가 레이아웃 경계에서 반올림 오차 없이 합성된다","는 데 있다. 컬럼 안에 컬럼, 그 안에 또 컬럼이 들어가도 결국 ",[41,858,859],{},"float64"," 위의 ",[41,862,863],{},"Pct"," 곱셈 체인일 뿐이다. 깊이 중첩된 레이아웃에서도 오차는 1 typographic point 이내에 들어온다.",[19,866,867,868,872],{},"전체 파이프라인을 보고 싶다면 ",[73,869,871],{"href":870},"/ko/blog/why-gpdf-is-faster","gpdf가 다른 라이브러리보다 10배 빠른 이유","에서 렌더링 파이프라인을 다룬다. 그리드는 그중 가장 가벼운 층이다 — M1에서 페이지당 약 13 µs 중 그리드는 수백 나노초 정도다.",[14,874,876],{"id":875},"완전히-동작하는-예제","완전히 동작하는 예제",[19,878,879],{},"4/8 분할 헤더, 12 전폭 표 행, 3/3/3/3 KPI 스트립:",[317,881,883],{"className":319,"code":882,"language":321,"meta":322,"style":322},"package main\n\nimport (\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    doc := template.NewDocument(document.PageSize(document.A4))\n\n    doc.Page(func(p *template.PageBuilder) {\n        // 4/8 분할: 좌측 로고, 우측 주소\n        p.AutoRow(func(r *template.RowBuilder) {\n            r.Col(4, func(c *template.ColBuilder) {\n                c.Text(\"ACME, Inc.\", template.FontSize(18), template.Bold())\n            })\n            r.Col(8, func(c *template.ColBuilder) {\n                c.Text(\"서울특별시 강남구 테헤란로 123\", template.AlignRight())\n                c.Text(\"우편번호 06234\", template.AlignRight())\n            })\n        })\n\n        p.Spacer(document.Mm(10))\n\n        // 전폭(12 span 1 컬럼)의 표\n        p.AutoRow(func(r *template.RowBuilder) {\n            r.Col(12, func(c *template.ColBuilder) {\n                c.Table([]string{\"품목\", \"수량\", \"금액\"}, [][]string{\n                    {\"상품 A\", \"2\", \"₩10,000\"},\n                    {\"상품 B\", \"1\", \"₩25,000\"},\n                })\n            })\n        })\n\n        p.Spacer(document.Mm(10))\n\n        // KPI 스트립: 3 span × 4 컬럼\n        kpis := []struct{ label, value string }{\n            {\"소계\", \"₩45,000\"},\n            {\"부가세 (10%)\", \"₩4,500\"},\n            {\"배송비\", \"₩0\"},\n            {\"합계\", \"₩49,500\"},\n        }\n        p.AutoRow(func(r *template.RowBuilder) {\n            for _, k := range kpis {\n                k := k\n                r.Col(3, func(c *template.ColBuilder) {\n                    c.Text(k.label, template.FontSize(8))\n                    c.Text(k.value, template.FontSize(14), template.Bold())\n                })\n            }\n        })\n    })\n\n    f, _ := os.Create(\"invoice.pdf\")\n    defer f.Close()\n    doc.Render(f)\n}\n",[41,884,885,893,899,907,918,922,932,942,947,952,966,1004,1009,1037,1043,1069,1101,1147,1153,1185,1214,1242,1247,1253,1258,1284,1289,1295,1320,1352,1408,1442,1474,1480,1485,1490,1495,1518,1523,1529,1553,1577,1600,1623,1646,1652,1677,1699,1710,1742,1776,1817,1822,1828,1833,1838,1843,1875,1892,1909],{"__ignoreMap":322},[326,886,887,890],{"class":328,"line":329},[326,888,889],{"class":340},"package",[326,891,892],{"class":391}," main\n",[326,894,895],{"class":328,"line":359},[326,896,898],{"emptyLinePlaceholder":897},true,"\n",[326,900,901,904],{"class":328,"line":406},[326,902,903],{"class":332},"import",[326,905,906],{"class":340}," (\n",[326,908,909,912,915],{"class":328,"line":430},[326,910,911],{"class":340},"    \"",[326,913,914],{"class":391},"os",[326,916,917],{"class":340},"\"\n",[326,919,920],{"class":328,"line":436},[326,921,898],{"emptyLinePlaceholder":897},[326,923,925,927,930],{"class":328,"line":924},6,[326,926,911],{"class":340},[326,928,929],{"class":391},"github.com/gpdf-dev/gpdf/document",[326,931,917],{"class":340},[326,933,935,937,940],{"class":328,"line":934},7,[326,936,911],{"class":340},[326,938,939],{"class":391},"github.com/gpdf-dev/gpdf/template",[326,941,917],{"class":340},[326,943,945],{"class":328,"line":944},8,[326,946,427],{"class":340},[326,948,950],{"class":328,"line":949},9,[326,951,898],{"emptyLinePlaceholder":897},[326,953,955,958,961,964],{"class":328,"line":954},10,[326,956,957],{"class":340},"func",[326,959,960],{"class":368}," main",[326,962,963],{"class":340},"()",[326,965,403],{"class":340},[326,967,969,972,974,977,979,982,984,987,989,992,994,996,998,1001],{"class":328,"line":968},11,[326,970,971],{"class":336},"    doc ",[326,973,347],{"class":340},[326,975,976],{"class":336}," template",[326,978,365],{"class":340},[326,980,981],{"class":368},"NewDocument",[326,983,372],{"class":340},[326,985,986],{"class":336},"document",[326,988,365],{"class":340},[326,990,991],{"class":368},"PageSize",[326,993,372],{"class":340},[326,995,986],{"class":336},[326,997,365],{"class":340},[326,999,1000],{"class":336},"A4",[326,1002,1003],{"class":340},"))\n",[326,1005,1007],{"class":328,"line":1006},12,[326,1008,898],{"emptyLinePlaceholder":897},[326,1010,1012,1015,1017,1020,1022,1024,1026,1028,1030,1033,1035],{"class":328,"line":1011},13,[326,1013,1014],{"class":336},"    doc",[326,1016,365],{"class":340},[326,1018,1019],{"class":368},"Page",[326,1021,471],{"class":340},[326,1023,19],{"class":384},[326,1025,388],{"class":340},[326,1027,392],{"class":391},[326,1029,365],{"class":340},[326,1031,1032],{"class":391},"PageBuilder",[326,1034,400],{"class":340},[326,1036,403],{"class":340},[326,1038,1040],{"class":328,"line":1039},14,[326,1041,1042],{"class":596},"        // 4/8 분할: 좌측 로고, 우측 주소\n",[326,1044,1046,1049,1051,1053,1055,1057,1059,1061,1063,1065,1067],{"class":328,"line":1045},15,[326,1047,1048],{"class":336},"        p",[326,1050,365],{"class":340},[326,1052,468],{"class":368},[326,1054,471],{"class":340},[326,1056,474],{"class":384},[326,1058,388],{"class":340},[326,1060,392],{"class":391},[326,1062,365],{"class":340},[326,1064,483],{"class":391},[326,1066,400],{"class":340},[326,1068,403],{"class":340},[326,1070,1072,1075,1077,1079,1081,1083,1085,1087,1089,1091,1093,1095,1097,1099],{"class":328,"line":1071},16,[326,1073,1074],{"class":336},"            r",[326,1076,365],{"class":340},[326,1078,369],{"class":368},[326,1080,372],{"class":340},[326,1082,500],{"class":375},[326,1084,341],{"class":340},[326,1086,381],{"class":340},[326,1088,385],{"class":384},[326,1090,388],{"class":340},[326,1092,392],{"class":391},[326,1094,365],{"class":340},[326,1096,397],{"class":391},[326,1098,400],{"class":340},[326,1100,403],{"class":340},[326,1102,1104,1107,1109,1111,1113,1115,1118,1120,1122,1124,1126,1129,1131,1134,1137,1139,1141,1144],{"class":328,"line":1103},17,[326,1105,1106],{"class":336},"                c",[326,1108,365],{"class":340},[326,1110,414],{"class":368},[326,1112,372],{"class":340},[326,1114,531],{"class":340},[326,1116,1117],{"class":534},"ACME, Inc.",[326,1119,531],{"class":340},[326,1121,341],{"class":340},[326,1123,976],{"class":336},[326,1125,365],{"class":340},[326,1127,1128],{"class":368},"FontSize",[326,1130,372],{"class":340},[326,1132,1133],{"class":375},"18",[326,1135,1136],{"class":340},"),",[326,1138,976],{"class":336},[326,1140,365],{"class":340},[326,1142,1143],{"class":368},"Bold",[326,1145,1146],{"class":340},"())\n",[326,1148,1150],{"class":328,"line":1149},18,[326,1151,1152],{"class":340},"            })\n",[326,1154,1156,1158,1160,1162,1164,1167,1169,1171,1173,1175,1177,1179,1181,1183],{"class":328,"line":1155},19,[326,1157,1074],{"class":336},[326,1159,365],{"class":340},[326,1161,369],{"class":368},[326,1163,372],{"class":340},[326,1165,1166],{"class":375},"8",[326,1168,341],{"class":340},[326,1170,381],{"class":340},[326,1172,385],{"class":384},[326,1174,388],{"class":340},[326,1176,392],{"class":391},[326,1178,365],{"class":340},[326,1180,397],{"class":391},[326,1182,400],{"class":340},[326,1184,403],{"class":340},[326,1186,1188,1190,1192,1194,1196,1198,1201,1203,1205,1207,1209,1212],{"class":328,"line":1187},20,[326,1189,1106],{"class":336},[326,1191,365],{"class":340},[326,1193,414],{"class":368},[326,1195,372],{"class":340},[326,1197,531],{"class":340},[326,1199,1200],{"class":534},"서울특별시 강남구 테헤란로 123",[326,1202,531],{"class":340},[326,1204,341],{"class":340},[326,1206,976],{"class":336},[326,1208,365],{"class":340},[326,1210,1211],{"class":368},"AlignRight",[326,1213,1146],{"class":340},[326,1215,1217,1219,1221,1223,1225,1227,1230,1232,1234,1236,1238,1240],{"class":328,"line":1216},21,[326,1218,1106],{"class":336},[326,1220,365],{"class":340},[326,1222,414],{"class":368},[326,1224,372],{"class":340},[326,1226,531],{"class":340},[326,1228,1229],{"class":534},"우편번호 06234",[326,1231,531],{"class":340},[326,1233,341],{"class":340},[326,1235,976],{"class":336},[326,1237,365],{"class":340},[326,1239,1211],{"class":368},[326,1241,1146],{"class":340},[326,1243,1245],{"class":328,"line":1244},22,[326,1246,1152],{"class":340},[326,1248,1250],{"class":328,"line":1249},23,[326,1251,1252],{"class":340},"        })\n",[326,1254,1256],{"class":328,"line":1255},24,[326,1257,898],{"emptyLinePlaceholder":897},[326,1259,1261,1263,1265,1268,1270,1272,1274,1277,1279,1282],{"class":328,"line":1260},25,[326,1262,1048],{"class":336},[326,1264,365],{"class":340},[326,1266,1267],{"class":368},"Spacer",[326,1269,372],{"class":340},[326,1271,986],{"class":336},[326,1273,365],{"class":340},[326,1275,1276],{"class":368},"Mm",[326,1278,372],{"class":340},[326,1280,1281],{"class":375},"10",[326,1283,1003],{"class":340},[326,1285,1287],{"class":328,"line":1286},26,[326,1288,898],{"emptyLinePlaceholder":897},[326,1290,1292],{"class":328,"line":1291},27,[326,1293,1294],{"class":596},"        // 전폭(12 span 1 컬럼)의 표\n",[326,1296,1298,1300,1302,1304,1306,1308,1310,1312,1314,1316,1318],{"class":328,"line":1297},28,[326,1299,1048],{"class":336},[326,1301,365],{"class":340},[326,1303,468],{"class":368},[326,1305,471],{"class":340},[326,1307,474],{"class":384},[326,1309,388],{"class":340},[326,1311,392],{"class":391},[326,1313,365],{"class":340},[326,1315,483],{"class":391},[326,1317,400],{"class":340},[326,1319,403],{"class":340},[326,1321,1323,1325,1327,1329,1331,1334,1336,1338,1340,1342,1344,1346,1348,1350],{"class":328,"line":1322},29,[326,1324,1074],{"class":336},[326,1326,365],{"class":340},[326,1328,369],{"class":368},[326,1330,372],{"class":340},[326,1332,1333],{"class":375},"12",[326,1335,341],{"class":340},[326,1337,381],{"class":340},[326,1339,385],{"class":384},[326,1341,388],{"class":340},[326,1343,392],{"class":391},[326,1345,365],{"class":340},[326,1347,397],{"class":391},[326,1349,400],{"class":340},[326,1351,403],{"class":340},[326,1353,1355,1357,1359,1362,1365,1369,1372,1374,1377,1379,1381,1384,1387,1389,1391,1393,1396,1398,1401,1404,1406],{"class":328,"line":1354},30,[326,1356,1106],{"class":336},[326,1358,365],{"class":340},[326,1360,1361],{"class":368},"Table",[326,1363,1364],{"class":340},"([]",[326,1366,1368],{"class":1367},"spNyl","string",[326,1370,1371],{"class":340},"{",[326,1373,531],{"class":340},[326,1375,1376],{"class":534},"품목",[326,1378,531],{"class":340},[326,1380,341],{"class":340},[326,1382,1383],{"class":340}," \"",[326,1385,1386],{"class":534},"수량",[326,1388,531],{"class":340},[326,1390,341],{"class":340},[326,1392,1383],{"class":340},[326,1394,1395],{"class":534},"금액",[326,1397,531],{"class":340},[326,1399,1400],{"class":340},"},",[326,1402,1403],{"class":340}," [][]",[326,1405,1368],{"class":1367},[326,1407,356],{"class":340},[326,1409,1411,1414,1416,1419,1421,1423,1425,1428,1430,1432,1434,1437,1439],{"class":328,"line":1410},31,[326,1412,1413],{"class":340},"                    {",[326,1415,531],{"class":340},[326,1417,1418],{"class":534},"상품 A",[326,1420,531],{"class":340},[326,1422,341],{"class":340},[326,1424,1383],{"class":340},[326,1426,1427],{"class":534},"2",[326,1429,531],{"class":340},[326,1431,341],{"class":340},[326,1433,1383],{"class":340},[326,1435,1436],{"class":534},"₩10,000",[326,1438,531],{"class":340},[326,1440,1441],{"class":340},"},\n",[326,1443,1445,1447,1449,1452,1454,1456,1458,1461,1463,1465,1467,1470,1472],{"class":328,"line":1444},32,[326,1446,1413],{"class":340},[326,1448,531],{"class":340},[326,1450,1451],{"class":534},"상품 B",[326,1453,531],{"class":340},[326,1455,341],{"class":340},[326,1457,1383],{"class":340},[326,1459,1460],{"class":534},"1",[326,1462,531],{"class":340},[326,1464,341],{"class":340},[326,1466,1383],{"class":340},[326,1468,1469],{"class":534},"₩25,000",[326,1471,531],{"class":340},[326,1473,1441],{"class":340},[326,1475,1477],{"class":328,"line":1476},33,[326,1478,1479],{"class":340},"                })\n",[326,1481,1483],{"class":328,"line":1482},34,[326,1484,1152],{"class":340},[326,1486,1488],{"class":328,"line":1487},35,[326,1489,1252],{"class":340},[326,1491,1493],{"class":328,"line":1492},36,[326,1494,898],{"emptyLinePlaceholder":897},[326,1496,1498,1500,1502,1504,1506,1508,1510,1512,1514,1516],{"class":328,"line":1497},37,[326,1499,1048],{"class":336},[326,1501,365],{"class":340},[326,1503,1267],{"class":368},[326,1505,372],{"class":340},[326,1507,986],{"class":336},[326,1509,365],{"class":340},[326,1511,1276],{"class":368},[326,1513,372],{"class":340},[326,1515,1281],{"class":375},[326,1517,1003],{"class":340},[326,1519,1521],{"class":328,"line":1520},38,[326,1522,898],{"emptyLinePlaceholder":897},[326,1524,1526],{"class":328,"line":1525},39,[326,1527,1528],{"class":596},"        // KPI 스트립: 3 span × 4 컬럼\n",[326,1530,1532,1535,1537,1540,1543,1545,1548,1550],{"class":328,"line":1531},40,[326,1533,1534],{"class":336},"        kpis ",[326,1536,347],{"class":340},[326,1538,1539],{"class":340}," []struct{",[326,1541,1542],{"class":336}," label",[326,1544,341],{"class":340},[326,1546,1547],{"class":336}," value ",[326,1549,1368],{"class":1367},[326,1551,1552],{"class":340}," }{\n",[326,1554,1556,1559,1561,1564,1566,1568,1570,1573,1575],{"class":328,"line":1555},41,[326,1557,1558],{"class":340},"            {",[326,1560,531],{"class":340},[326,1562,1563],{"class":534},"소계",[326,1565,531],{"class":340},[326,1567,341],{"class":340},[326,1569,1383],{"class":340},[326,1571,1572],{"class":534},"₩45,000",[326,1574,531],{"class":340},[326,1576,1441],{"class":340},[326,1578,1580,1582,1584,1587,1589,1591,1593,1596,1598],{"class":328,"line":1579},42,[326,1581,1558],{"class":340},[326,1583,531],{"class":340},[326,1585,1586],{"class":534},"부가세 (10%)",[326,1588,531],{"class":340},[326,1590,341],{"class":340},[326,1592,1383],{"class":340},[326,1594,1595],{"class":534},"₩4,500",[326,1597,531],{"class":340},[326,1599,1441],{"class":340},[326,1601,1603,1605,1607,1610,1612,1614,1616,1619,1621],{"class":328,"line":1602},43,[326,1604,1558],{"class":340},[326,1606,531],{"class":340},[326,1608,1609],{"class":534},"배송비",[326,1611,531],{"class":340},[326,1613,341],{"class":340},[326,1615,1383],{"class":340},[326,1617,1618],{"class":534},"₩0",[326,1620,531],{"class":340},[326,1622,1441],{"class":340},[326,1624,1626,1628,1630,1633,1635,1637,1639,1642,1644],{"class":328,"line":1625},44,[326,1627,1558],{"class":340},[326,1629,531],{"class":340},[326,1631,1632],{"class":534},"합계",[326,1634,531],{"class":340},[326,1636,341],{"class":340},[326,1638,1383],{"class":340},[326,1640,1641],{"class":534},"₩49,500",[326,1643,531],{"class":340},[326,1645,1441],{"class":340},[326,1647,1649],{"class":328,"line":1648},45,[326,1650,1651],{"class":340},"        }\n",[326,1653,1655,1657,1659,1661,1663,1665,1667,1669,1671,1673,1675],{"class":328,"line":1654},46,[326,1656,1048],{"class":336},[326,1658,365],{"class":340},[326,1660,468],{"class":368},[326,1662,471],{"class":340},[326,1664,474],{"class":384},[326,1666,388],{"class":340},[326,1668,392],{"class":391},[326,1670,365],{"class":340},[326,1672,483],{"class":391},[326,1674,400],{"class":340},[326,1676,403],{"class":340},[326,1678,1680,1683,1685,1687,1690,1692,1694,1697],{"class":328,"line":1679},47,[326,1681,1682],{"class":332},"            for",[326,1684,337],{"class":336},[326,1686,341],{"class":340},[326,1688,1689],{"class":336}," k ",[326,1691,347],{"class":340},[326,1693,350],{"class":332},[326,1695,1696],{"class":336}," kpis ",[326,1698,356],{"class":340},[326,1700,1702,1705,1707],{"class":328,"line":1701},48,[326,1703,1704],{"class":336},"                k ",[326,1706,347],{"class":340},[326,1708,1709],{"class":336}," k\n",[326,1711,1713,1716,1718,1720,1722,1724,1726,1728,1730,1732,1734,1736,1738,1740],{"class":328,"line":1712},49,[326,1714,1715],{"class":336},"                r",[326,1717,365],{"class":340},[326,1719,369],{"class":368},[326,1721,372],{"class":340},[326,1723,376],{"class":375},[326,1725,341],{"class":340},[326,1727,381],{"class":340},[326,1729,385],{"class":384},[326,1731,388],{"class":340},[326,1733,392],{"class":391},[326,1735,365],{"class":340},[326,1737,397],{"class":391},[326,1739,400],{"class":340},[326,1741,403],{"class":340},[326,1743,1745,1748,1750,1752,1754,1757,1759,1762,1764,1766,1768,1770,1772,1774],{"class":328,"line":1744},50,[326,1746,1747],{"class":336},"                    c",[326,1749,365],{"class":340},[326,1751,414],{"class":368},[326,1753,372],{"class":340},[326,1755,1756],{"class":336},"k",[326,1758,365],{"class":340},[326,1760,1761],{"class":336},"label",[326,1763,341],{"class":340},[326,1765,976],{"class":336},[326,1767,365],{"class":340},[326,1769,1128],{"class":368},[326,1771,372],{"class":340},[326,1773,1166],{"class":375},[326,1775,1003],{"class":340},[326,1777,1779,1781,1783,1785,1787,1789,1791,1794,1796,1798,1800,1802,1804,1807,1809,1811,1813,1815],{"class":328,"line":1778},51,[326,1780,1747],{"class":336},[326,1782,365],{"class":340},[326,1784,414],{"class":368},[326,1786,372],{"class":340},[326,1788,1756],{"class":336},[326,1790,365],{"class":340},[326,1792,1793],{"class":336},"value",[326,1795,341],{"class":340},[326,1797,976],{"class":336},[326,1799,365],{"class":340},[326,1801,1128],{"class":368},[326,1803,372],{"class":340},[326,1805,1806],{"class":375},"14",[326,1808,1136],{"class":340},[326,1810,976],{"class":336},[326,1812,365],{"class":340},[326,1814,1143],{"class":368},[326,1816,1146],{"class":340},[326,1818,1820],{"class":328,"line":1819},52,[326,1821,1479],{"class":340},[326,1823,1825],{"class":328,"line":1824},53,[326,1826,1827],{"class":340},"            }\n",[326,1829,1831],{"class":328,"line":1830},54,[326,1832,1252],{"class":340},[326,1834,1836],{"class":328,"line":1835},55,[326,1837,433],{"class":340},[326,1839,1841],{"class":328,"line":1840},56,[326,1842,898],{"emptyLinePlaceholder":897},[326,1844,1846,1849,1851,1854,1856,1859,1861,1864,1866,1868,1871,1873],{"class":328,"line":1845},57,[326,1847,1848],{"class":336},"    f",[326,1850,341],{"class":340},[326,1852,1853],{"class":336}," _ ",[326,1855,347],{"class":340},[326,1857,1858],{"class":336}," os",[326,1860,365],{"class":340},[326,1862,1863],{"class":368},"Create",[326,1865,372],{"class":340},[326,1867,531],{"class":340},[326,1869,1870],{"class":534},"invoice.pdf",[326,1872,531],{"class":340},[326,1874,427],{"class":340},[326,1876,1878,1881,1884,1886,1889],{"class":328,"line":1877},58,[326,1879,1880],{"class":332},"    defer",[326,1882,1883],{"class":336}," f",[326,1885,365],{"class":340},[326,1887,1888],{"class":368},"Close",[326,1890,1891],{"class":340},"()\n",[326,1893,1895,1897,1899,1902,1904,1907],{"class":328,"line":1894},59,[326,1896,1014],{"class":336},[326,1898,365],{"class":340},[326,1900,1901],{"class":368},"Render",[326,1903,372],{"class":340},[326,1905,1906],{"class":336},"f",[326,1908,427],{"class":340},[326,1910,1912],{"class":328,"line":1911},60,[326,1913,439],{"class":340},[19,1915,1916,1917,1920,1921,1923],{},"실제로 동작하는 프로그램이다. ",[41,1918,1919],{},"go get github.com/gpdf-dev/gpdf"," 후 실행하면 작업 디렉터리에 ",[41,1922,1870],{},"가 생긴다. M1에서 렌더링 시간은 약 130 µs.",[14,1925,1927],{"id":1926},"정수-모델이-안-맞는-경우","정수 모델이 안 맞는 경우",[19,1929,1930],{},"정수 12분의 n 모델이 진짜로 잘못된 선택인 경우는 두 가지다. 솔직히 적어둔다.",[50,1932,1933,1950],{},[53,1934,1935,1938,1939,1941,1942,1945,1946,1949],{},[22,1936,1937],{},"픽셀 단위 정확한 너비가 필요할 때."," 「이 컬럼은 정확히 73.5pt」 같은 요구. ",[41,1940,863],{},"로는 사실상 안 된다 (",[41,1943,1944],{},"73.5 / 총 너비 × 12","가 정수일 일이 거의 없다). 고정 좌표가 필요한 요소만 ",[41,1947,1948],{},"page.Absolute(...)","로 처리하고 나머지는 그리드에 맡긴다. 둘은 같은 페이지에서 공존한다.",[53,1951,1952,1955],{},[22,1953,1954],{},"신문식 컬럼 흐름이 필요할 때."," 한 컬럼이 차면 다음 컬럼으로 이어지는 텍스트 흐름. 그리드는 안 한다. 컬럼 흐름 텍스트 엔진은 아직 없다. 필요하면 issue를 열어달라 — 부재를 알고 있다.",[19,1957,1958],{},"그 외, 즉 청구서·보고서·계약서·브로슈어·덱에서는 12 그리드가 CSS보다 오히려 더 타이트하게 맞는다.",[14,1960,1962],{"id":1961},"자주-묻는-질문","자주 묻는 질문",[19,1964,1965,1968,1969,1972],{},[22,1966,1967],{},"Q: 12를 24 같은 다른 값으로 바꿀 수 있나요?","\n없다. ",[41,1970,1971],{},"gridColumns","는 상수다. 바꾸면 기존 템플릿이 전부 깨진다. 12로 한 번 결정하고 커밋했다.",[19,1974,1975,1978,1979,1982,1983,1986,1987,1990],{},[22,1976,1977],{},"Q: 컬럼 안에 행을 중첩하고 싶으면?","\n가능. ",[41,1980,1981],{},"c.AutoRow(...)","로 컬럼 안에 서브 행을 만든다. 서브 행 안의 1〜12는 ",[163,1984,1985],{},"부모 컬럼 너비"," 기준이지 페이지 너비 기준이 아니다. 각 레벨이 「부모에 대한 ",[41,1988,1989],{},"Pct(span/12 × 100)","」이라 중첩 합성이 깔끔하다.",[19,1992,1993,1996,1997,2000],{},[22,1994,1995],{},"Q: 가로 페이지에서도 동작하나요?","\n한다. 그리드는 페이지 크기에 무관하다. ",[41,1998,1999],{},"r.Col(6, ...)","는 행이 210mm(A4 세로)이든 297mm(A4 가로)든 늘 행의 절반이다.",[19,2002,2003,2010,2011,2014],{},[22,2004,2005,2006,2009],{},"Q: 2 컬럼 행용 ",[41,2007,2008],{},"r.Col2(span, span, fn1, fn2)"," 단축이 왜 없나요?","\n한 줄 줄이려고 API 표면을 늘리는 건 손해라서다. 같은 행 패턴을 반복한다면 ",[41,2012,2013],{},"*template.PageBuilder","를 받는 Go 함수를 직접 만들어 추가하면 된다. 그리드가 최소이기에 사용자 패턴이 충돌 없이 자랄 수 있다.",[19,2016,2017,2024],{},[22,2018,2019,2020,2023],{},"Q: CSS Grid의 ",[41,2021,2022],{},"grid-area","나 명명된 라인은?","\ngpdf엔 없고, 로드맵에도 없다. PDF에는 비용 대비 효과가 안 나온다.",[14,2026,2027],{"id":2027},"정리",[19,2029,2030,2031,2034],{},"12 컬럼 그리드는 「실제 문서가 필요로 하는 분할」을 최소 비용으로 제공하는 레이아웃 프리미티브다. 숫자는 Bootstrap에서 빌렸고, 정수 모델만 남기고, 브레이크포인트·거터·order·auto-fill·span 합 강제와 그 외 반응형 웹의 짐은 전부 버렸다. 남은 것은 상수 하나, 빌더 메서드 하나, 너비 식 하나 — Go 약 30줄. 중첩으로 깔끔하게 합성되고, 그리드로 표현 불가한 소수의 케이스에는 ",[41,2032,2033],{},"Absolute","와 자연스럽게 공존하며, 작성한 내용을 절대 몰래 재밸런싱하지 않는다.",[14,2036,2038],{"id":2037},"gpdf-사용해보기","gpdf 사용해보기",[19,2040,2041],{},"gpdf는 Go용 PDF 생성 라이브러리. MIT, 무의존, CJK 기본 지원.",[317,2043,2047],{"className":2044,"code":2045,"language":2046,"meta":322,"style":322},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go get github.com/gpdf-dev/gpdf\n","bash",[41,2048,2049],{"__ignoreMap":322},[326,2050,2051,2053,2056],{"class":328,"line":329},[326,2052,321],{"class":391},[326,2054,2055],{"class":534}," get",[326,2057,2058],{"class":534}," github.com/gpdf-dev/gpdf\n",[19,2060,2061,2067,2068],{},[73,2062,2066],{"href":2063,"rel":2064},"https://github.com/gpdf-dev/gpdf",[2065],"nofollow","⭐ Star on GitHub"," · ",[73,2069,2072],{"href":2070,"rel":2071},"https://gpdf.dev/ko/docs/quickstart",[2065],"문서 읽기",[14,2074,2076],{"id":2075},"다음으로-읽을-것","다음으로 읽을 것",[127,2078,2079,2085,2090],{},[53,2080,2081,2084],{},[73,2082,2083],{"href":75},"gpdf의 12 컬럼 그리드는 어떻게 동작하나요?"," — 레시피 버전, 더 많은 코드 패턴",[53,2086,2087,2089],{},[73,2088,871],{"href":870}," — 렌더링 파이프라인 내부 해설",[53,2091,2092,2096],{},[73,2093,2095],{"href":2094},"/ko/docs/quickstart","Quickstart"," — 5분 만에 첫 PDF 생성",[2098,2099,2100],"style",{},"html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}",{"title":322,"searchDepth":359,"depth":359,"links":2102},[2103,2104,2105,2106,2107,2108,2116,2117,2118,2119,2120,2121,2122,2123],{"id":16,"depth":359,"text":17},{"id":35,"depth":359,"text":36},{"id":80,"depth":359,"text":81},{"id":121,"depth":359,"text":122},{"id":190,"depth":359,"text":191},{"id":223,"depth":359,"text":224,"children":2109},[2110,2111,2112,2113,2114,2115],{"id":228,"depth":406,"text":228},{"id":266,"depth":406,"text":266},{"id":282,"depth":406,"text":282},{"id":296,"depth":406,"text":297},{"id":445,"depth":406,"text":446},{"id":636,"depth":406,"text":637},{"id":657,"depth":359,"text":658},{"id":826,"depth":359,"text":827},{"id":875,"depth":359,"text":876},{"id":1926,"depth":359,"text":1927},{"id":1961,"depth":359,"text":1962},{"id":2027,"depth":359,"text":2027},{"id":2037,"depth":359,"text":2038},{"id":2075,"depth":359,"text":2076},"2026-04-29","gpdf는 PDF 레이아웃에 Bootstrap의 12 컬럼 그리드를 차용했다. 정수 span 모델만 남기고 브레이크포인트·거터·order는 전부 버린 설계 판단을 정리한다.",false,"md",null,{},"/ko/blog/bootstrap-grid-thinking-for-pdf",{"title":5,"description":2125},"ko/blog/017.bootstrap-grid-thinking-for-pdf",[2134,2135,2136],"internals","templates","comparison","Evj4UUq2UhNn6w02zF61XPfozhsssTClAsdB9uc-oyQ",1779199026817]