[{"data":1,"prerenderedAt":2518},["ShallowReactive",2],{"blog-ko-invoice-pdf-go-under-50-lines":3},{"id":4,"title":5,"author":6,"body":10,"date":2484,"description":2485,"draft":2486,"extension":2487,"howTo":2488,"image":2509,"meta":2510,"navigation":130,"path":2511,"seo":2512,"stem":2513,"tags":2514,"updated":2509,"__hash__":2517},"blogKo/ko/blog/012.invoice-pdf-go-under-50-lines.md","Go로 50줄 이하에서 인보이스 PDF 생성하기",{"name":7,"url":8,"avatar":9},"Taiki Noda","https://nadai.dev/en/about","https://nadai.dev/og-default.png",{"type":11,"value":12,"toc":2464},"minimark",[13,18,36,39,64,70,74,77,80,96,99,104,107,110,1144,1147,1156,1160,1163,1166,1233,1248,1252,1288,1301,1305,1412,1427,1436,1447,1451,1614,1622,1631,1633,1684,1690,1694,1801,1817,1821,1824,1969,1972,1976,1986,2110,2116,2222,2232,2235,2291,2295,2340,2343,2347,2353,2366,2376,2385,2400,2404,2407,2419,2433,2437,2460],[14,15,17],"h2",{"id":16},"tldr","TL;DR",[19,20,21,22,26,27,31,32,35],"p",{},"실행 가능한 Go 인보이스 PDF를 처음부터 끝까지 ",[23,24,25],"strong",{},"50줄","로. ",[28,29,30],"code",{},"main.go"," 하나, ",[28,33,34],{},"go get"," 한 번, Chromium 없음, CGO 없음, 템플릿 언어 없음, HTML 없음. 테이블, 줄무늬 행, 우측 정렬 합계 포함. 코드는 아래에 있고, 나머지는 각 블록의 역할과 이 패턴이 한계에 부딪히는 조건에 대한 이야기다.",[19,37,38],{},"먼저 코드만 보려면:",[40,41,46],"pre",{"className":42,"code":43,"language":44,"meta":45,"style":45},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go get github.com/gpdf-dev/gpdf\n","bash","",[28,47,48],{"__ignoreMap":45},[49,50,53,57,61],"span",{"class":51,"line":52},"line",1,[49,54,56],{"class":55},"sBMFI","go",[49,58,60],{"class":59},"sfazB"," get",[49,62,63],{"class":59}," github.com/gpdf-dev/gpdf\n",[19,65,66,67,69],{},"그런 다음 다음 섹션의 ",[28,68,30],{},"를 붙여넣는다.",[14,71,73],{"id":72},"왜-50줄-이하가-우리가-관심-두는-기준인가","왜 \"50줄 이하\"가 우리가 관심 두는 기준인가",[19,75,76],{},"솔직하게 말하면, \"go 인보이스 pdf 생성\"을 검색하면 (a) 헤드리스 Chromium을 띄우라거나, (b) 테이블 하나 그리려고 저수준 PDF 연산자 400줄을 보여주는 글이 대부분이다. 둘 다 기술적으로 틀리지 않았지만, 일의 형태와 맞지 않는다.",[19,78,79],{},"괜찮은 인보이스에 필요한 것:",[81,82,83,87,90,93],"ul",{},[84,85,86],"li",{},"발행자와 고객 정보가 담긴 헤더",[84,88,89],{},"인보이스 번호와 만기일",[84,91,92],{},"명세행 테이블",[84,94,95],{},"합계",[19,97,98],{},"네 가지. 그래서 코드도 네 블록이어야 한다. 한 화면에 안 들어가면 라이브러리 선택이 잘못된 것이다.",[19,100,101,103],{},[23,102,25],{},"은 일반 에디터에서 한 화면에 들어가는 한계점이며, 리뷰어가 테스트로 건너뛰지 않고 처음부터 끝까지 읽는 기준점이다. 이 선을 넘으면 결과물을 Slack 메시지 하나에 붙일 수 있고, 그 메시지만으로 누군가가 라이브러리를 익힐 수 있다.",[19,105,106],{},"아래 코드는 gofmt 적용, import 완전 전개, 모든 에러 경로 처리. 숨겨진 헬퍼 패키지도 꼼수도 없다. 보이는 그대로 컴파일된다.",[14,108,25],{"id":109},"_50줄",[40,111,114],{"className":112,"code":113,"language":56,"meta":45,"style":45},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/pdf\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    doc := gpdf.NewDocument(template.WithPageSize(document.A4))\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(6, func(c *template.ColBuilder) {\n            c.Text(\"ACME 주식회사\", template.FontSize(22), template.Bold())\n            c.Text(\"서울특별시 강남구 테헤란로 100\")\n        })\n        r.Col(6, func(c *template.ColBuilder) {\n            c.Text(\"인보이스 #INV-2026-001\", template.Bold(), template.AlignRight())\n            c.Text(\"만기일: 2026-03-31\", template.AlignRight())\n        })\n    })\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Spacer(document.Mm(6))\n            c.Table(\n                []string{\"항목\", \"수량\", \"단가\", \"금액\"},\n                [][]string{\n                    {\"프런트엔드 개발\", \"40시간\", \"₩200,000\", \"₩8,000,000\"},\n                    {\"백엔드 개발\", \"60시간\", \"₩200,000\", \"₩12,000,000\"},\n                    {\"UI/UX 디자인\", \"20시간\", \"₩170,000\", \"₩3,400,000\"},\n                },\n                template.ColumnWidths(40, 15, 20, 25),\n                template.TableHeaderStyle(template.Bold(), template.BgColor(pdf.RGBHex(0xF0F0F0))),\n                template.TableStripe(pdf.RGBHex(0xFAFAFA)),\n            )\n            c.Text(\"합계: ₩23,400,000\", template.AlignRight(), template.Bold(), template.FontSize(14))\n        })\n    })\n    b, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"invoice.pdf\", b, 0644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[28,115,116,125,132,142,154,164,169,179,189,199,209,215,220,236,279,298,331,370,419,439,445,476,514,542,547,553,578,610,635,648,699,710,752,792,833,839,873,918,944,950,999,1004,1009,1031,1047,1065,1071,1118,1133,1138],{"__ignoreMap":45},[49,117,118,122],{"class":51,"line":52},[49,119,121],{"class":120},"sMK4o","package",[49,123,124],{"class":55}," main\n",[49,126,128],{"class":51,"line":127},2,[49,129,131],{"emptyLinePlaceholder":130},true,"\n",[49,133,135,139],{"class":51,"line":134},3,[49,136,138],{"class":137},"s7zQu","import",[49,140,141],{"class":120}," (\n",[49,143,145,148,151],{"class":51,"line":144},4,[49,146,147],{"class":120},"    \"",[49,149,150],{"class":55},"log",[49,152,153],{"class":120},"\"\n",[49,155,157,159,162],{"class":51,"line":156},5,[49,158,147],{"class":120},[49,160,161],{"class":55},"os",[49,163,153],{"class":120},[49,165,167],{"class":51,"line":166},6,[49,168,131],{"emptyLinePlaceholder":130},[49,170,172,174,177],{"class":51,"line":171},7,[49,173,147],{"class":120},[49,175,176],{"class":55},"github.com/gpdf-dev/gpdf",[49,178,153],{"class":120},[49,180,182,184,187],{"class":51,"line":181},8,[49,183,147],{"class":120},[49,185,186],{"class":55},"github.com/gpdf-dev/gpdf/document",[49,188,153],{"class":120},[49,190,192,194,197],{"class":51,"line":191},9,[49,193,147],{"class":120},[49,195,196],{"class":55},"github.com/gpdf-dev/gpdf/pdf",[49,198,153],{"class":120},[49,200,202,204,207],{"class":51,"line":201},10,[49,203,147],{"class":120},[49,205,206],{"class":55},"github.com/gpdf-dev/gpdf/template",[49,208,153],{"class":120},[49,210,212],{"class":51,"line":211},11,[49,213,214],{"class":120},")\n",[49,216,218],{"class":51,"line":217},12,[49,219,131],{"emptyLinePlaceholder":130},[49,221,223,226,230,233],{"class":51,"line":222},13,[49,224,225],{"class":120},"func",[49,227,229],{"class":228},"s2Zo4"," main",[49,231,232],{"class":120},"()",[49,234,235],{"class":120}," {\n",[49,237,239,243,246,249,252,255,258,261,263,266,268,271,273,276],{"class":51,"line":238},14,[49,240,242],{"class":241},"sTEyZ","    doc ",[49,244,245],{"class":120},":=",[49,247,248],{"class":241}," gpdf",[49,250,251],{"class":120},".",[49,253,254],{"class":228},"NewDocument",[49,256,257],{"class":120},"(",[49,259,260],{"class":241},"template",[49,262,251],{"class":120},[49,264,265],{"class":228},"WithPageSize",[49,267,257],{"class":120},[49,269,270],{"class":241},"document",[49,272,251],{"class":120},[49,274,275],{"class":241},"A4",[49,277,278],{"class":120},"))\n",[49,280,282,285,287,290,292,295],{"class":51,"line":281},15,[49,283,284],{"class":241},"    page ",[49,286,245],{"class":120},[49,288,289],{"class":241}," doc",[49,291,251],{"class":120},[49,293,294],{"class":228},"AddPage",[49,296,297],{"class":120},"()\n",[49,299,301,304,306,309,312,316,319,321,323,326,329],{"class":51,"line":300},16,[49,302,303],{"class":241},"    page",[49,305,251],{"class":120},[49,307,308],{"class":228},"AutoRow",[49,310,311],{"class":120},"(func(",[49,313,315],{"class":314},"sHdIc","r",[49,317,318],{"class":120}," *",[49,320,260],{"class":55},[49,322,251],{"class":120},[49,324,325],{"class":55},"RowBuilder",[49,327,328],{"class":120},")",[49,330,235],{"class":120},[49,332,334,337,339,342,344,348,351,354,357,359,361,363,366,368],{"class":51,"line":333},17,[49,335,336],{"class":241},"        r",[49,338,251],{"class":120},[49,340,341],{"class":228},"Col",[49,343,257],{"class":120},[49,345,347],{"class":346},"sbssI","6",[49,349,350],{"class":120},",",[49,352,353],{"class":120}," func(",[49,355,356],{"class":314},"c",[49,358,318],{"class":120},[49,360,260],{"class":55},[49,362,251],{"class":120},[49,364,365],{"class":55},"ColBuilder",[49,367,328],{"class":120},[49,369,235],{"class":120},[49,371,373,376,378,381,383,386,389,391,393,396,398,401,403,406,409,411,413,416],{"class":51,"line":372},18,[49,374,375],{"class":241},"            c",[49,377,251],{"class":120},[49,379,380],{"class":228},"Text",[49,382,257],{"class":120},[49,384,385],{"class":120},"\"",[49,387,388],{"class":59},"ACME 주식회사",[49,390,385],{"class":120},[49,392,350],{"class":120},[49,394,395],{"class":241}," template",[49,397,251],{"class":120},[49,399,400],{"class":228},"FontSize",[49,402,257],{"class":120},[49,404,405],{"class":346},"22",[49,407,408],{"class":120},"),",[49,410,395],{"class":241},[49,412,251],{"class":120},[49,414,415],{"class":228},"Bold",[49,417,418],{"class":120},"())\n",[49,420,422,424,426,428,430,432,435,437],{"class":51,"line":421},19,[49,423,375],{"class":241},[49,425,251],{"class":120},[49,427,380],{"class":228},[49,429,257],{"class":120},[49,431,385],{"class":120},[49,433,434],{"class":59},"서울특별시 강남구 테헤란로 100",[49,436,385],{"class":120},[49,438,214],{"class":120},[49,440,442],{"class":51,"line":441},20,[49,443,444],{"class":120},"        })\n",[49,446,448,450,452,454,456,458,460,462,464,466,468,470,472,474],{"class":51,"line":447},21,[49,449,336],{"class":241},[49,451,251],{"class":120},[49,453,341],{"class":228},[49,455,257],{"class":120},[49,457,347],{"class":346},[49,459,350],{"class":120},[49,461,353],{"class":120},[49,463,356],{"class":314},[49,465,318],{"class":120},[49,467,260],{"class":55},[49,469,251],{"class":120},[49,471,365],{"class":55},[49,473,328],{"class":120},[49,475,235],{"class":120},[49,477,479,481,483,485,487,489,492,494,496,498,500,502,505,507,509,512],{"class":51,"line":478},22,[49,480,375],{"class":241},[49,482,251],{"class":120},[49,484,380],{"class":228},[49,486,257],{"class":120},[49,488,385],{"class":120},[49,490,491],{"class":59},"인보이스 #INV-2026-001",[49,493,385],{"class":120},[49,495,350],{"class":120},[49,497,395],{"class":241},[49,499,251],{"class":120},[49,501,415],{"class":228},[49,503,504],{"class":120},"(),",[49,506,395],{"class":241},[49,508,251],{"class":120},[49,510,511],{"class":228},"AlignRight",[49,513,418],{"class":120},[49,515,517,519,521,523,525,527,530,532,534,536,538,540],{"class":51,"line":516},23,[49,518,375],{"class":241},[49,520,251],{"class":120},[49,522,380],{"class":228},[49,524,257],{"class":120},[49,526,385],{"class":120},[49,528,529],{"class":59},"만기일: 2026-03-31",[49,531,385],{"class":120},[49,533,350],{"class":120},[49,535,395],{"class":241},[49,537,251],{"class":120},[49,539,511],{"class":228},[49,541,418],{"class":120},[49,543,545],{"class":51,"line":544},24,[49,546,444],{"class":120},[49,548,550],{"class":51,"line":549},25,[49,551,552],{"class":120},"    })\n",[49,554,556,558,560,562,564,566,568,570,572,574,576],{"class":51,"line":555},26,[49,557,303],{"class":241},[49,559,251],{"class":120},[49,561,308],{"class":228},[49,563,311],{"class":120},[49,565,315],{"class":314},[49,567,318],{"class":120},[49,569,260],{"class":55},[49,571,251],{"class":120},[49,573,325],{"class":55},[49,575,328],{"class":120},[49,577,235],{"class":120},[49,579,581,583,585,587,589,592,594,596,598,600,602,604,606,608],{"class":51,"line":580},27,[49,582,336],{"class":241},[49,584,251],{"class":120},[49,586,341],{"class":228},[49,588,257],{"class":120},[49,590,591],{"class":346},"12",[49,593,350],{"class":120},[49,595,353],{"class":120},[49,597,356],{"class":314},[49,599,318],{"class":120},[49,601,260],{"class":55},[49,603,251],{"class":120},[49,605,365],{"class":55},[49,607,328],{"class":120},[49,609,235],{"class":120},[49,611,613,615,617,620,622,624,626,629,631,633],{"class":51,"line":612},28,[49,614,375],{"class":241},[49,616,251],{"class":120},[49,618,619],{"class":228},"Spacer",[49,621,257],{"class":120},[49,623,270],{"class":241},[49,625,251],{"class":120},[49,627,628],{"class":228},"Mm",[49,630,257],{"class":120},[49,632,347],{"class":346},[49,634,278],{"class":120},[49,636,638,640,642,645],{"class":51,"line":637},29,[49,639,375],{"class":241},[49,641,251],{"class":120},[49,643,644],{"class":228},"Table",[49,646,647],{"class":120},"(\n",[49,649,651,654,658,661,663,666,668,670,673,676,678,680,682,685,687,689,691,694,696],{"class":51,"line":650},30,[49,652,653],{"class":120},"                []",[49,655,657],{"class":656},"spNyl","string",[49,659,660],{"class":120},"{",[49,662,385],{"class":120},[49,664,665],{"class":59},"항목",[49,667,385],{"class":120},[49,669,350],{"class":120},[49,671,672],{"class":120}," \"",[49,674,675],{"class":59},"수량",[49,677,385],{"class":120},[49,679,350],{"class":120},[49,681,672],{"class":120},[49,683,684],{"class":59},"단가",[49,686,385],{"class":120},[49,688,350],{"class":120},[49,690,672],{"class":120},[49,692,693],{"class":59},"금액",[49,695,385],{"class":120},[49,697,698],{"class":120},"},\n",[49,700,702,705,707],{"class":51,"line":701},31,[49,703,704],{"class":120},"                [][]",[49,706,657],{"class":656},[49,708,709],{"class":120},"{\n",[49,711,713,716,718,721,723,725,727,730,732,734,736,739,741,743,745,748,750],{"class":51,"line":712},32,[49,714,715],{"class":120},"                    {",[49,717,385],{"class":120},[49,719,720],{"class":59},"프런트엔드 개발",[49,722,385],{"class":120},[49,724,350],{"class":120},[49,726,672],{"class":120},[49,728,729],{"class":59},"40시간",[49,731,385],{"class":120},[49,733,350],{"class":120},[49,735,672],{"class":120},[49,737,738],{"class":59},"₩200,000",[49,740,385],{"class":120},[49,742,350],{"class":120},[49,744,672],{"class":120},[49,746,747],{"class":59},"₩8,000,000",[49,749,385],{"class":120},[49,751,698],{"class":120},[49,753,755,757,759,762,764,766,768,771,773,775,777,779,781,783,785,788,790],{"class":51,"line":754},33,[49,756,715],{"class":120},[49,758,385],{"class":120},[49,760,761],{"class":59},"백엔드 개발",[49,763,385],{"class":120},[49,765,350],{"class":120},[49,767,672],{"class":120},[49,769,770],{"class":59},"60시간",[49,772,385],{"class":120},[49,774,350],{"class":120},[49,776,672],{"class":120},[49,778,738],{"class":59},[49,780,385],{"class":120},[49,782,350],{"class":120},[49,784,672],{"class":120},[49,786,787],{"class":59},"₩12,000,000",[49,789,385],{"class":120},[49,791,698],{"class":120},[49,793,795,797,799,802,804,806,808,811,813,815,817,820,822,824,826,829,831],{"class":51,"line":794},34,[49,796,715],{"class":120},[49,798,385],{"class":120},[49,800,801],{"class":59},"UI/UX 디자인",[49,803,385],{"class":120},[49,805,350],{"class":120},[49,807,672],{"class":120},[49,809,810],{"class":59},"20시간",[49,812,385],{"class":120},[49,814,350],{"class":120},[49,816,672],{"class":120},[49,818,819],{"class":59},"₩170,000",[49,821,385],{"class":120},[49,823,350],{"class":120},[49,825,672],{"class":120},[49,827,828],{"class":59},"₩3,400,000",[49,830,385],{"class":120},[49,832,698],{"class":120},[49,834,836],{"class":51,"line":835},35,[49,837,838],{"class":120},"                },\n",[49,840,842,845,847,850,852,855,857,860,862,865,867,870],{"class":51,"line":841},36,[49,843,844],{"class":241},"                template",[49,846,251],{"class":120},[49,848,849],{"class":228},"ColumnWidths",[49,851,257],{"class":120},[49,853,854],{"class":346},"40",[49,856,350],{"class":120},[49,858,859],{"class":346}," 15",[49,861,350],{"class":120},[49,863,864],{"class":346}," 20",[49,866,350],{"class":120},[49,868,869],{"class":346}," 25",[49,871,872],{"class":120},"),\n",[49,874,876,878,880,883,885,887,889,891,893,895,897,900,902,905,907,910,912,915],{"class":51,"line":875},37,[49,877,844],{"class":241},[49,879,251],{"class":120},[49,881,882],{"class":228},"TableHeaderStyle",[49,884,257],{"class":120},[49,886,260],{"class":241},[49,888,251],{"class":120},[49,890,415],{"class":228},[49,892,504],{"class":120},[49,894,395],{"class":241},[49,896,251],{"class":120},[49,898,899],{"class":228},"BgColor",[49,901,257],{"class":120},[49,903,904],{"class":241},"pdf",[49,906,251],{"class":120},[49,908,909],{"class":228},"RGBHex",[49,911,257],{"class":120},[49,913,914],{"class":346},"0xF0F0F0",[49,916,917],{"class":120},"))),\n",[49,919,921,923,925,928,930,932,934,936,938,941],{"class":51,"line":920},38,[49,922,844],{"class":241},[49,924,251],{"class":120},[49,926,927],{"class":228},"TableStripe",[49,929,257],{"class":120},[49,931,904],{"class":241},[49,933,251],{"class":120},[49,935,909],{"class":228},[49,937,257],{"class":120},[49,939,940],{"class":346},"0xFAFAFA",[49,942,943],{"class":120},")),\n",[49,945,947],{"class":51,"line":946},39,[49,948,949],{"class":120},"            )\n",[49,951,953,955,957,959,961,963,966,968,970,972,974,976,978,980,982,984,986,988,990,992,994,997],{"class":51,"line":952},40,[49,954,375],{"class":241},[49,956,251],{"class":120},[49,958,380],{"class":228},[49,960,257],{"class":120},[49,962,385],{"class":120},[49,964,965],{"class":59},"합계: ₩23,400,000",[49,967,385],{"class":120},[49,969,350],{"class":120},[49,971,395],{"class":241},[49,973,251],{"class":120},[49,975,511],{"class":228},[49,977,504],{"class":120},[49,979,395],{"class":241},[49,981,251],{"class":120},[49,983,415],{"class":228},[49,985,504],{"class":120},[49,987,395],{"class":241},[49,989,251],{"class":120},[49,991,400],{"class":228},[49,993,257],{"class":120},[49,995,996],{"class":346},"14",[49,998,278],{"class":120},[49,1000,1002],{"class":51,"line":1001},41,[49,1003,444],{"class":120},[49,1005,1007],{"class":51,"line":1006},42,[49,1008,552],{"class":120},[49,1010,1012,1015,1017,1020,1022,1024,1026,1029],{"class":51,"line":1011},43,[49,1013,1014],{"class":241},"    b",[49,1016,350],{"class":120},[49,1018,1019],{"class":241}," err ",[49,1021,245],{"class":120},[49,1023,289],{"class":241},[49,1025,251],{"class":120},[49,1027,1028],{"class":228},"Generate",[49,1030,297],{"class":120},[49,1032,1034,1037,1039,1042,1045],{"class":51,"line":1033},44,[49,1035,1036],{"class":137},"    if",[49,1038,1019],{"class":241},[49,1040,1041],{"class":120},"!=",[49,1043,1044],{"class":120}," nil",[49,1046,235],{"class":120},[49,1048,1050,1053,1055,1058,1060,1063],{"class":51,"line":1049},45,[49,1051,1052],{"class":241},"        log",[49,1054,251],{"class":120},[49,1056,1057],{"class":228},"Fatal",[49,1059,257],{"class":120},[49,1061,1062],{"class":241},"err",[49,1064,214],{"class":120},[49,1066,1068],{"class":51,"line":1067},46,[49,1069,1070],{"class":120},"    }\n",[49,1072,1074,1076,1078,1080,1083,1085,1088,1090,1092,1095,1097,1099,1102,1104,1107,1110,1112,1114,1116],{"class":51,"line":1073},47,[49,1075,1036],{"class":137},[49,1077,1019],{"class":241},[49,1079,245],{"class":120},[49,1081,1082],{"class":241}," os",[49,1084,251],{"class":120},[49,1086,1087],{"class":228},"WriteFile",[49,1089,257],{"class":120},[49,1091,385],{"class":120},[49,1093,1094],{"class":59},"invoice.pdf",[49,1096,385],{"class":120},[49,1098,350],{"class":120},[49,1100,1101],{"class":241}," b",[49,1103,350],{"class":120},[49,1105,1106],{"class":346}," 0644",[49,1108,1109],{"class":120},");",[49,1111,1019],{"class":241},[49,1113,1041],{"class":120},[49,1115,1044],{"class":120},[49,1117,235],{"class":120},[49,1119,1121,1123,1125,1127,1129,1131],{"class":51,"line":1120},48,[49,1122,1052],{"class":241},[49,1124,251],{"class":120},[49,1126,1057],{"class":228},[49,1128,257],{"class":120},[49,1130,1062],{"class":241},[49,1132,214],{"class":120},[49,1134,1136],{"class":51,"line":1135},49,[49,1137,1070],{"class":120},[49,1139,1141],{"class":51,"line":1140},50,[49,1142,1143],{"class":120},"}\n",[19,1145,1146],{},"주의: 위 코드의 한글은 기본 폰트에서 두부 블록 (□)으로 표시된다. 한글 폰트 임베딩은 마지막 \"한글 지원\" 섹션에서 다룬다. 먼저 구조를 보자.",[19,1148,1149,1152,1153,1155],{},[28,1150,1151],{},"go run .","으로 현재 디렉터리에 ",[28,1154,1094],{},"가 생성된다. M1에서 프로그램 전체는 몇 밀리초, 실제 PDF 생성은 150 µs 미만이다.",[14,1157,1159],{"id":1158},"각-블록이-하는-일","각 블록이 하는 일",[1161,1162,138],"h3",{"id":138},[19,1164,1165],{},"gpdf의 4개 패키지:",[81,1167,1168,1181,1214,1228],{},[84,1169,1170,1172,1173,1176,1177,1180],{},[28,1171,176],{}," — 파사드. ",[28,1174,1175],{},"gpdf.NewDocument","만 쓰며, 내부는 ",[28,1178,1179],{},"template.New","의 얇은 래퍼.",[84,1182,1183,1185,1186,1188,1189,1188,1192,1188,1195,1188,1198,1188,1201,1204,1205,1188,1207,1188,1210,1213],{},[28,1184,186],{}," — 단위 (",[28,1187,628],{},", ",[28,1190,1191],{},"Pt",[28,1193,1194],{},"Cm",[28,1196,1197],{},"In",[28,1199,1200],{},"Em",[28,1202,1203],{},"Pct","), 용지 크기 (",[28,1206,275],{},[28,1208,1209],{},"Letter",[28,1211,1212],{},"Legal","), 여백.",[84,1215,1216,1218,1219,1188,1221,1188,1224,1227],{},[28,1217,196],{}," — 색상 프리미티브 (",[28,1220,909],{},[28,1222,1223],{},"Gray",[28,1225,1226],{},"pdf.White"," 등 상수).",[84,1229,1230,1232],{},[28,1231,206],{}," — Builder API 본체.",[19,1234,1235,1236,1239,1240,1243,1244,1247],{},"외부 의존성 제로. ",[28,1237,1238],{},"go get github.com/gpdf-dev/gpdf"," 후 ",[28,1241,1242],{},"go.mod","의 ",[28,1245,1246],{},"require","는 한 줄.",[1161,1249,1251],{"id":1250},"문서-구성","문서 구성",[40,1253,1255],{"className":112,"code":1254,"language":56,"meta":45,"style":45},"doc := gpdf.NewDocument(template.WithPageSize(document.A4))\n",[28,1256,1257],{"__ignoreMap":45},[49,1258,1259,1262,1264,1266,1268,1270,1272,1274,1276,1278,1280,1282,1284,1286],{"class":51,"line":52},[49,1260,1261],{"class":241},"doc ",[49,1263,245],{"class":120},[49,1265,248],{"class":241},[49,1267,251],{"class":120},[49,1269,254],{"class":228},[49,1271,257],{"class":120},[49,1273,260],{"class":241},[49,1275,251],{"class":120},[49,1277,265],{"class":228},[49,1279,257],{"class":120},[49,1281,270],{"class":241},[49,1283,251],{"class":120},[49,1285,275],{"class":241},[49,1287,278],{"class":120},[19,1289,1290,1292,1293,1296,1297,1300],{},[28,1291,1175],{},"는 ",[28,1294,1295],{},"...template.Option"," 가변 인자. 페이지 크기, 여백, 기본 폰트, 메타데이터, 커스텀 폰트 모두 ",[28,1298,1299],{},"WithXxx"," 옵션. 기본 여백은 20 mm.",[1161,1302,1304],{"id":1303},"헤더-행","헤더 행",[40,1306,1308],{"className":112,"code":1307,"language":56,"meta":45,"style":45},"page.AutoRow(func(r *template.RowBuilder) {\n    r.Col(6, func(c *template.ColBuilder) { ... })\n    r.Col(6, func(c *template.ColBuilder) { ... })\n})\n",[28,1309,1310,1335,1373,1407],{"__ignoreMap":45},[49,1311,1312,1315,1317,1319,1321,1323,1325,1327,1329,1331,1333],{"class":51,"line":52},[49,1313,1314],{"class":241},"page",[49,1316,251],{"class":120},[49,1318,308],{"class":228},[49,1320,311],{"class":120},[49,1322,315],{"class":314},[49,1324,318],{"class":120},[49,1326,260],{"class":55},[49,1328,251],{"class":120},[49,1330,325],{"class":55},[49,1332,328],{"class":120},[49,1334,235],{"class":120},[49,1336,1337,1340,1342,1344,1346,1348,1350,1352,1354,1356,1358,1360,1362,1364,1367,1370],{"class":51,"line":127},[49,1338,1339],{"class":241},"    r",[49,1341,251],{"class":120},[49,1343,341],{"class":228},[49,1345,257],{"class":120},[49,1347,347],{"class":346},[49,1349,350],{"class":120},[49,1351,353],{"class":120},[49,1353,356],{"class":314},[49,1355,318],{"class":120},[49,1357,260],{"class":55},[49,1359,251],{"class":120},[49,1361,365],{"class":55},[49,1363,328],{"class":120},[49,1365,1366],{"class":120}," {",[49,1368,1369],{"class":120}," ...",[49,1371,1372],{"class":120}," })\n",[49,1374,1375,1377,1379,1381,1383,1385,1387,1389,1391,1393,1395,1397,1399,1401,1403,1405],{"class":51,"line":134},[49,1376,1339],{"class":241},[49,1378,251],{"class":120},[49,1380,341],{"class":228},[49,1382,257],{"class":120},[49,1384,347],{"class":346},[49,1386,350],{"class":120},[49,1388,353],{"class":120},[49,1390,356],{"class":314},[49,1392,318],{"class":120},[49,1394,260],{"class":55},[49,1396,251],{"class":120},[49,1398,365],{"class":55},[49,1400,328],{"class":120},[49,1402,1366],{"class":120},[49,1404,1369],{"class":120},[49,1406,1372],{"class":120},[49,1408,1409],{"class":51,"line":144},[49,1410,1411],{"class":120},"})\n",[19,1413,1414,1415,1418,1419,1422,1423,1426],{},"gpdf는 ",[23,1416,1417],{},"12컬럼 그리드","를 채택했다. Bootstrap과 동일한 멘탈 모델. 한 행은 수평으로 12 단위, ",[28,1420,1421],{},"r.Col(6, ...)","은 절반. ",[28,1424,1425],{},"Col(6)"," 두 개로 행이 꽉 찬다.",[19,1428,1429,1431,1432,1435],{},[28,1430,308],{},"는 행 높이를 가장 높은 컬럼 기준으로 결정한다. 컬럼 안에서 ",[28,1433,1434],{},"c.Text(...)","를 세로로 쌓는다. 명시적 좌표 지정 없이 빌더가 커서를 관리한다.",[19,1437,1438,1439,1442,1443,1446],{},"우측 컬럼은 ",[28,1440,1441],{},"template.AlignRight()","로 우측 정렬. 텍스트 옵션은 합성 가능해서 ",[28,1444,1445],{},"c.Text(\"인보이스\", template.Bold(), template.AlignRight(), template.FontSize(20))","처럼 한 호출에 여러 수정자를 겹칠 수 있다. 순서 무관.",[1161,1448,1450],{"id":1449},"명세-테이블","명세 테이블",[40,1452,1454],{"className":112,"code":1453,"language":56,"meta":45,"style":45},"c.Table(\n    []string{\"항목\", \"수량\", \"단가\", \"금액\"},\n    [][]string{ /* rows */ },\n    template.ColumnWidths(40, 15, 20, 25),\n    template.TableHeaderStyle(template.Bold(), template.BgColor(pdf.RGBHex(0xF0F0F0))),\n    template.TableStripe(pdf.RGBHex(0xFAFAFA)),\n)\n",[28,1455,1456,1466,1507,1523,1550,1588,1610],{"__ignoreMap":45},[49,1457,1458,1460,1462,1464],{"class":51,"line":52},[49,1459,356],{"class":241},[49,1461,251],{"class":120},[49,1463,644],{"class":228},[49,1465,647],{"class":120},[49,1467,1468,1471,1473,1475,1477,1479,1481,1483,1485,1487,1489,1491,1493,1495,1497,1499,1501,1503,1505],{"class":51,"line":127},[49,1469,1470],{"class":120},"    []",[49,1472,657],{"class":656},[49,1474,660],{"class":120},[49,1476,385],{"class":120},[49,1478,665],{"class":59},[49,1480,385],{"class":120},[49,1482,350],{"class":120},[49,1484,672],{"class":120},[49,1486,675],{"class":59},[49,1488,385],{"class":120},[49,1490,350],{"class":120},[49,1492,672],{"class":120},[49,1494,684],{"class":59},[49,1496,385],{"class":120},[49,1498,350],{"class":120},[49,1500,672],{"class":120},[49,1502,693],{"class":59},[49,1504,385],{"class":120},[49,1506,698],{"class":120},[49,1508,1509,1512,1514,1516,1520],{"class":51,"line":134},[49,1510,1511],{"class":120},"    [][]",[49,1513,657],{"class":656},[49,1515,660],{"class":120},[49,1517,1519],{"class":1518},"sHwdD"," /* rows */",[49,1521,1522],{"class":120}," },\n",[49,1524,1525,1528,1530,1532,1534,1536,1538,1540,1542,1544,1546,1548],{"class":51,"line":144},[49,1526,1527],{"class":241},"    template",[49,1529,251],{"class":120},[49,1531,849],{"class":228},[49,1533,257],{"class":120},[49,1535,854],{"class":346},[49,1537,350],{"class":120},[49,1539,859],{"class":346},[49,1541,350],{"class":120},[49,1543,864],{"class":346},[49,1545,350],{"class":120},[49,1547,869],{"class":346},[49,1549,872],{"class":120},[49,1551,1552,1554,1556,1558,1560,1562,1564,1566,1568,1570,1572,1574,1576,1578,1580,1582,1584,1586],{"class":51,"line":156},[49,1553,1527],{"class":241},[49,1555,251],{"class":120},[49,1557,882],{"class":228},[49,1559,257],{"class":120},[49,1561,260],{"class":241},[49,1563,251],{"class":120},[49,1565,415],{"class":228},[49,1567,504],{"class":120},[49,1569,395],{"class":241},[49,1571,251],{"class":120},[49,1573,899],{"class":228},[49,1575,257],{"class":120},[49,1577,904],{"class":241},[49,1579,251],{"class":120},[49,1581,909],{"class":228},[49,1583,257],{"class":120},[49,1585,914],{"class":346},[49,1587,917],{"class":120},[49,1589,1590,1592,1594,1596,1598,1600,1602,1604,1606,1608],{"class":51,"line":166},[49,1591,1527],{"class":241},[49,1593,251],{"class":120},[49,1595,927],{"class":228},[49,1597,257],{"class":120},[49,1599,904],{"class":241},[49,1601,251],{"class":120},[49,1603,909],{"class":228},[49,1605,257],{"class":120},[49,1607,940],{"class":346},[49,1609,943],{"class":120},[49,1611,1612],{"class":51,"line":171},[49,1613,214],{"class":120},[19,1615,1616,1292,1618,1621],{},[28,1617,849],{},[23,1619,1620],{},"백분율","이며 절대 포인트가 아니다. 네 수의 합은 100이어야 한다. 합이 맞지 않아도 에러는 아니지만 우측이 넘칠 수 있다 — 유일한 함정.",[19,1623,1624,1626,1627,1630],{},[28,1625,882],{},"은 모든 텍스트 옵션을 받는다. ",[28,1628,1629],{},"TableStripe(color)","는 얼룩말 줄. 테이블이 자동으로 행 높이를 측정하고 페이지를 분할한다 — 연속 페이지 상단에 헤더를 다시 그린다.",[1161,1632,95],{"id":95},[40,1634,1636],{"className":112,"code":1635,"language":56,"meta":45,"style":45},"c.Text(\"합계: ₩23,400,000\", template.AlignRight(), template.Bold(), template.FontSize(14))\n",[28,1637,1638],{"__ignoreMap":45},[49,1639,1640,1642,1644,1646,1648,1650,1652,1654,1656,1658,1660,1662,1664,1666,1668,1670,1672,1674,1676,1678,1680,1682],{"class":51,"line":52},[49,1641,356],{"class":241},[49,1643,251],{"class":120},[49,1645,380],{"class":228},[49,1647,257],{"class":120},[49,1649,385],{"class":120},[49,1651,965],{"class":59},[49,1653,385],{"class":120},[49,1655,350],{"class":120},[49,1657,395],{"class":241},[49,1659,251],{"class":120},[49,1661,511],{"class":228},[49,1663,504],{"class":120},[49,1665,395],{"class":241},[49,1667,251],{"class":120},[49,1669,415],{"class":228},[49,1671,504],{"class":120},[49,1673,395],{"class":241},[49,1675,251],{"class":120},[49,1677,400],{"class":228},[49,1679,257],{"class":120},[49,1681,996],{"class":346},[49,1683,278],{"class":120},[19,1685,1686,1687,1689],{},"테이블 밖의 또 다른 ",[28,1688,380],{}," 호출. 우측 정렬, 약간 더 큰 글씨.",[1161,1691,1693],{"id":1692},"생성과-쓰기","생성과 쓰기",[40,1695,1697],{"className":112,"code":1696,"language":56,"meta":45,"style":45},"b, err := doc.Generate()\nif err != nil { log.Fatal(err) }\nif err := os.WriteFile(\"invoice.pdf\", b, 0644); err != nil { log.Fatal(err) }\n",[28,1698,1699,1718,1747],{"__ignoreMap":45},[49,1700,1701,1704,1706,1708,1710,1712,1714,1716],{"class":51,"line":52},[49,1702,1703],{"class":241},"b",[49,1705,350],{"class":120},[49,1707,1019],{"class":241},[49,1709,245],{"class":120},[49,1711,289],{"class":241},[49,1713,251],{"class":120},[49,1715,1028],{"class":228},[49,1717,297],{"class":120},[49,1719,1720,1723,1725,1727,1729,1731,1734,1736,1738,1740,1742,1744],{"class":51,"line":127},[49,1721,1722],{"class":137},"if",[49,1724,1019],{"class":241},[49,1726,1041],{"class":120},[49,1728,1044],{"class":120},[49,1730,1366],{"class":120},[49,1732,1733],{"class":241}," log",[49,1735,251],{"class":120},[49,1737,1057],{"class":228},[49,1739,257],{"class":120},[49,1741,1062],{"class":241},[49,1743,328],{"class":120},[49,1745,1746],{"class":120}," }\n",[49,1748,1749,1751,1753,1755,1757,1759,1761,1763,1765,1767,1769,1771,1773,1775,1777,1779,1781,1783,1785,1787,1789,1791,1793,1795,1797,1799],{"class":51,"line":134},[49,1750,1722],{"class":137},[49,1752,1019],{"class":241},[49,1754,245],{"class":120},[49,1756,1082],{"class":241},[49,1758,251],{"class":120},[49,1760,1087],{"class":228},[49,1762,257],{"class":120},[49,1764,385],{"class":120},[49,1766,1094],{"class":59},[49,1768,385],{"class":120},[49,1770,350],{"class":120},[49,1772,1101],{"class":241},[49,1774,350],{"class":120},[49,1776,1106],{"class":346},[49,1778,1109],{"class":120},[49,1780,1019],{"class":241},[49,1782,1041],{"class":120},[49,1784,1044],{"class":120},[49,1786,1366],{"class":120},[49,1788,1733],{"class":241},[49,1790,251],{"class":120},[49,1792,1057],{"class":228},[49,1794,257],{"class":120},[49,1796,1062],{"class":241},[49,1798,328],{"class":120},[49,1800,1746],{"class":120},[19,1802,1803,1292,1806,1809,1810,1813,1814,251],{},[28,1804,1805],{},"doc.Generate()",[28,1807,1808],{},"([]byte, error)","를 반환하며 파일 시스템을 건드리지 않는다. 바이트 슬라이스 자체가 완전한 PDF로, 디스크 쓰기, S3 업로드, HTTP 응답 ",[28,1811,1812],{},"w.Write(b)",", 메일 첨부 모두 가능. 스트리밍이 필요하면 ",[28,1815,1816],{},"doc.Render(w io.Writer)",[14,1818,1820],{"id":1819},"한글-지원","한글 지원",[19,1822,1823],{},"기본 폰트는 Latin만 지원하므로 한글은 두부로 렌더된다. Noto Sans KR TTF 하나로 해결:",[40,1825,1827],{"className":112,"code":1826,"language":56,"meta":45,"style":45},"ttf, err := os.ReadFile(\"NotoSansKR-Regular.ttf\")\nif err != nil { log.Fatal(err) }\ndoc := gpdf.NewDocument(\n    template.WithPageSize(document.A4),\n    template.WithFont(\"NotoSansKR\", ttf),\n    template.WithDefaultFont(\"NotoSansKR\", 10),\n)\n",[28,1828,1829,1858,1884,1898,1916,1941,1965],{"__ignoreMap":45},[49,1830,1831,1834,1836,1838,1840,1842,1844,1847,1849,1851,1854,1856],{"class":51,"line":52},[49,1832,1833],{"class":241},"ttf",[49,1835,350],{"class":120},[49,1837,1019],{"class":241},[49,1839,245],{"class":120},[49,1841,1082],{"class":241},[49,1843,251],{"class":120},[49,1845,1846],{"class":228},"ReadFile",[49,1848,257],{"class":120},[49,1850,385],{"class":120},[49,1852,1853],{"class":59},"NotoSansKR-Regular.ttf",[49,1855,385],{"class":120},[49,1857,214],{"class":120},[49,1859,1860,1862,1864,1866,1868,1870,1872,1874,1876,1878,1880,1882],{"class":51,"line":127},[49,1861,1722],{"class":137},[49,1863,1019],{"class":241},[49,1865,1041],{"class":120},[49,1867,1044],{"class":120},[49,1869,1366],{"class":120},[49,1871,1733],{"class":241},[49,1873,251],{"class":120},[49,1875,1057],{"class":228},[49,1877,257],{"class":120},[49,1879,1062],{"class":241},[49,1881,328],{"class":120},[49,1883,1746],{"class":120},[49,1885,1886,1888,1890,1892,1894,1896],{"class":51,"line":134},[49,1887,1261],{"class":241},[49,1889,245],{"class":120},[49,1891,248],{"class":241},[49,1893,251],{"class":120},[49,1895,254],{"class":228},[49,1897,647],{"class":120},[49,1899,1900,1902,1904,1906,1908,1910,1912,1914],{"class":51,"line":144},[49,1901,1527],{"class":241},[49,1903,251],{"class":120},[49,1905,265],{"class":228},[49,1907,257],{"class":120},[49,1909,270],{"class":241},[49,1911,251],{"class":120},[49,1913,275],{"class":241},[49,1915,872],{"class":120},[49,1917,1918,1920,1922,1925,1927,1929,1932,1934,1936,1939],{"class":51,"line":156},[49,1919,1527],{"class":241},[49,1921,251],{"class":120},[49,1923,1924],{"class":228},"WithFont",[49,1926,257],{"class":120},[49,1928,385],{"class":120},[49,1930,1931],{"class":59},"NotoSansKR",[49,1933,385],{"class":120},[49,1935,350],{"class":120},[49,1937,1938],{"class":241}," ttf",[49,1940,872],{"class":120},[49,1942,1943,1945,1947,1950,1952,1954,1956,1958,1960,1963],{"class":51,"line":166},[49,1944,1527],{"class":241},[49,1946,251],{"class":120},[49,1948,1949],{"class":228},"WithDefaultFont",[49,1951,257],{"class":120},[49,1953,385],{"class":120},[49,1955,1931],{"class":59},[49,1957,385],{"class":120},[49,1959,350],{"class":120},[49,1961,1962],{"class":346}," 10",[49,1964,872],{"class":120},[49,1966,1967],{"class":51,"line":171},[49,1968,214],{"class":120},[19,1970,1971],{},"gpdf가 자동으로 폰트 서브셋화를 수행하여 실제 사용된 글리프만 PDF에 임베드된다. 3 MB TTF 전체가 들어가지 않는다.",[14,1973,1975],{"id":1974},"_50줄을-넘지-않으면서-보기-좋게","50줄을 넘지 않으면서 보기 좋게",[19,1977,1978,1981,1982,1985],{},[23,1979,1980],{},"브랜드 색상."," hex 값 (예: 네이비 ",[28,1983,1984],{},"0x1A237E",")을 회사명과 테이블 헤더에 적용:",[40,1987,1989],{"className":112,"code":1988,"language":56,"meta":45,"style":45},"brand := pdf.RGBHex(0x1A237E)\nc.Text(\"ACME 주식회사\", template.FontSize(22), template.Bold(), template.TextColor(brand))\ntemplate.TableHeaderStyle(template.Bold(), template.TextColor(pdf.White), template.BgColor(brand)),\n",[28,1990,1991,2011,2063],{"__ignoreMap":45},[49,1992,1993,1996,1998,2001,2003,2005,2007,2009],{"class":51,"line":52},[49,1994,1995],{"class":241},"brand ",[49,1997,245],{"class":120},[49,1999,2000],{"class":241}," pdf",[49,2002,251],{"class":120},[49,2004,909],{"class":228},[49,2006,257],{"class":120},[49,2008,1984],{"class":346},[49,2010,214],{"class":120},[49,2012,2013,2015,2017,2019,2021,2023,2025,2027,2029,2031,2033,2035,2037,2039,2041,2043,2045,2047,2049,2051,2053,2056,2058,2061],{"class":51,"line":127},[49,2014,356],{"class":241},[49,2016,251],{"class":120},[49,2018,380],{"class":228},[49,2020,257],{"class":120},[49,2022,385],{"class":120},[49,2024,388],{"class":59},[49,2026,385],{"class":120},[49,2028,350],{"class":120},[49,2030,395],{"class":241},[49,2032,251],{"class":120},[49,2034,400],{"class":228},[49,2036,257],{"class":120},[49,2038,405],{"class":346},[49,2040,408],{"class":120},[49,2042,395],{"class":241},[49,2044,251],{"class":120},[49,2046,415],{"class":228},[49,2048,504],{"class":120},[49,2050,395],{"class":241},[49,2052,251],{"class":120},[49,2054,2055],{"class":228},"TextColor",[49,2057,257],{"class":120},[49,2059,2060],{"class":241},"brand",[49,2062,278],{"class":120},[49,2064,2065,2067,2069,2071,2073,2075,2077,2079,2081,2083,2085,2087,2089,2091,2093,2096,2098,2100,2102,2104,2106,2108],{"class":51,"line":134},[49,2066,260],{"class":241},[49,2068,251],{"class":120},[49,2070,882],{"class":228},[49,2072,257],{"class":120},[49,2074,260],{"class":241},[49,2076,251],{"class":120},[49,2078,415],{"class":228},[49,2080,504],{"class":120},[49,2082,395],{"class":241},[49,2084,251],{"class":120},[49,2086,2055],{"class":228},[49,2088,257],{"class":120},[49,2090,904],{"class":241},[49,2092,251],{"class":120},[49,2094,2095],{"class":241},"White",[49,2097,408],{"class":120},[49,2099,395],{"class":241},[49,2101,251],{"class":120},[49,2103,899],{"class":228},[49,2105,257],{"class":120},[49,2107,2060],{"class":241},[49,2109,943],{"class":120},[19,2111,2112,2115],{},[23,2113,2114],{},"소계와 세금."," 합계 위에 분리 표시:",[40,2117,2119],{"className":112,"code":2118,"language":56,"meta":45,"style":45},"c.Text(\"소계: ₩23,400,000\", template.AlignRight())\nc.Text(\"부가세 (10%): ₩2,340,000\", template.AlignRight())\nc.Text(\"합계: ₩25,740,000\", template.AlignRight(), template.Bold(), template.FontSize(14))\n",[28,2120,2121,2148,2175],{"__ignoreMap":45},[49,2122,2123,2125,2127,2129,2131,2133,2136,2138,2140,2142,2144,2146],{"class":51,"line":52},[49,2124,356],{"class":241},[49,2126,251],{"class":120},[49,2128,380],{"class":228},[49,2130,257],{"class":120},[49,2132,385],{"class":120},[49,2134,2135],{"class":59},"소계: ₩23,400,000",[49,2137,385],{"class":120},[49,2139,350],{"class":120},[49,2141,395],{"class":241},[49,2143,251],{"class":120},[49,2145,511],{"class":228},[49,2147,418],{"class":120},[49,2149,2150,2152,2154,2156,2158,2160,2163,2165,2167,2169,2171,2173],{"class":51,"line":127},[49,2151,356],{"class":241},[49,2153,251],{"class":120},[49,2155,380],{"class":228},[49,2157,257],{"class":120},[49,2159,385],{"class":120},[49,2161,2162],{"class":59},"부가세 (10%): ₩2,340,000",[49,2164,385],{"class":120},[49,2166,350],{"class":120},[49,2168,395],{"class":241},[49,2170,251],{"class":120},[49,2172,511],{"class":228},[49,2174,418],{"class":120},[49,2176,2177,2179,2181,2183,2185,2187,2190,2192,2194,2196,2198,2200,2202,2204,2206,2208,2210,2212,2214,2216,2218,2220],{"class":51,"line":134},[49,2178,356],{"class":241},[49,2180,251],{"class":120},[49,2182,380],{"class":228},[49,2184,257],{"class":120},[49,2186,385],{"class":120},[49,2188,2189],{"class":59},"합계: ₩25,740,000",[49,2191,385],{"class":120},[49,2193,350],{"class":120},[49,2195,395],{"class":241},[49,2197,251],{"class":120},[49,2199,511],{"class":228},[49,2201,504],{"class":120},[49,2203,395],{"class":241},[49,2205,251],{"class":120},[49,2207,415],{"class":228},[49,2209,504],{"class":120},[49,2211,395],{"class":241},[49,2213,251],{"class":120},[49,2215,400],{"class":228},[49,2217,257],{"class":120},[49,2219,996],{"class":346},[49,2221,278],{"class":120},[19,2223,2224,2227,2228,2231],{},[23,2225,2226],{},"전자세금계산서 대응."," 발행자의 사업자등록번호, 공급받는 자의 상호와 등록번호는 ",[28,2229,2230],{},"c.Text"," 몇 줄 추가로 해결. 레이아웃 변경 없음.",[14,2233,2234],{"id":2234},"실행",[40,2236,2238],{"className":42,"code":2237,"language":44,"meta":45,"style":45},"mkdir invoice-demo\ncd invoice-demo\ngo mod init example.com/invoice-demo\ngo get github.com/gpdf-dev/gpdf\n# main.go 붙여넣기\ngo run .\n",[28,2239,2240,2248,2255,2268,2276,2281],{"__ignoreMap":45},[49,2241,2242,2245],{"class":51,"line":52},[49,2243,2244],{"class":55},"mkdir",[49,2246,2247],{"class":59}," invoice-demo\n",[49,2249,2250,2253],{"class":51,"line":127},[49,2251,2252],{"class":228},"cd",[49,2254,2247],{"class":59},[49,2256,2257,2259,2262,2265],{"class":51,"line":134},[49,2258,56],{"class":55},[49,2260,2261],{"class":59}," mod",[49,2263,2264],{"class":59}," init",[49,2266,2267],{"class":59}," example.com/invoice-demo\n",[49,2269,2270,2272,2274],{"class":51,"line":144},[49,2271,56],{"class":55},[49,2273,60],{"class":59},[49,2275,63],{"class":59},[49,2277,2278],{"class":51,"line":156},[49,2279,2280],{"class":1518},"# main.go 붙여넣기\n",[49,2282,2283,2285,2288],{"class":51,"line":166},[49,2284,56],{"class":55},[49,2286,2287],{"class":59}," run",[49,2289,2290],{"class":59}," .\n",[14,2292,2294],{"id":2293},"이-패턴이-깨지는-지점","이 패턴이 깨지는 지점",[81,2296,2297,2307,2317,2334],{},[84,2298,2299,2302,2303,2306],{},[23,2300,2301],{},"명세가 데이터가 될 때."," DB 쿼리나 JSON 페이로드에서 올 때 테이블 코드는 그대로. ",[28,2304,2305],{},"[][]string"," 구성 로직만 추가.",[84,2308,2309,2312,2313,2316],{},[23,2310,2311],{},"레이아웃 재사용이 필요할 때."," 루프로 여러 인보이스를 생성한다면 본문을 ",[28,2314,2315],{},"func renderInvoice(doc *template.Document, inv Invoice)","로 분리.",[84,2318,2319,2322,2323,2328,2329,2333],{},[23,2320,2321],{},"레이아웃에 분기가 생길 때."," Builder API가 장황해지기 시작하면 ",[2324,2325,2327],"a",{"href":2326},"/ko/docs/guide/json-templates","JSON 스키마 엔트리","나 ",[2324,2330,2332],{"href":2331},"/ko/docs/guide/go-templates","Go 템플릿 엔트리","가 적합.",[84,2335,2336,2339],{},[23,2337,2338],{},"CJK 텍스트가 필요할 때."," 위 \"한글 지원\" 섹션 참조.",[19,2341,2342],{},"모두 처음부터 다시 쓸 필요 없이 증분 확장.",[14,2344,2346],{"id":2345},"faq","FAQ",[19,2348,2349,2352],{},[23,2350,2351],{},"상용 인보이스에 써도 되나요?","\n됩니다. gpdf는 MIT 라이선스. 클로즈드 상용에도 무료로 임베드 가능, 표기 의무 없음.",[19,2354,2355,2362,2363,251],{},[23,2356,2357,2358,2361],{},"바이트 슬라이스 없이 ",[28,2359,2360],{},"io.Writer","에 직접 쓸 수 있나요?","\n네. ",[28,2364,2365],{},"doc.Render(w io.Writer) error",[19,2367,2368,2371,2372,2375],{},[23,2369,2370],{},"실제로 얼마나 빠른가요?","\n위 50줄은 M1에서 약 100 µs에 PDF를 생성. 단일 페이지 hello world는 ",[23,2373,2374],{},"13 µs",". 배치 워크로드에서 gpdf가 병목이 되는 경우는 드물다.",[19,2377,2378,2384],{},[23,2379,2380,2383],{},[28,2381,2382],{},"gpdf.Invoice"," 헬퍼는 없나요?","\n없습니다. 나라마다 인보이스 양식이 달라 어떤 단순화도 누군가를 배제한다. 50줄 출발점이 컨스트럭터 하나보다 유연.",[19,2386,2387,2390,2391,2394,2395,2399],{},[23,2388,2389],{},"PDF/A를 지원하나요?","\nPDF/A-2b 지원. 구성 시 ",[28,2392,2393],{},"gpdf.WithPDFA(pdfa.Level2B)"," 전달. ",[2324,2396,2398],{"href":2397},"/ko/docs/guide/pdf-a","순수 Go로 PDF/A-2b 만들기"," 참조.",[14,2401,2403],{"id":2402},"gpdf-사용해보기","gpdf 사용해보기",[19,2405,2406],{},"gpdf는 Go용 PDF 생성 라이브러리. MIT, 의존성 제로, CJK 네이티브 지원, 벤치마크 워크로드에서 다른 라이브러리 대비 10–30배 빠름.",[40,2408,2409],{"className":42,"code":43,"language":44,"meta":45,"style":45},[28,2410,2411],{"__ignoreMap":45},[49,2412,2413,2415,2417],{"class":51,"line":52},[49,2414,56],{"class":55},[49,2416,60],{"class":59},[49,2418,63],{"class":59},[19,2420,2421,2427,2428],{},[2324,2422,2426],{"href":2423,"rel":2424},"https://github.com/gpdf-dev/gpdf",[2425],"nofollow","⭐ GitHub에서 Star"," · ",[2324,2429,2432],{"href":2430,"rel":2431},"https://gpdf.dev/ko/docs/quickstart",[2425],"문서 읽기",[14,2434,2436],{"id":2435},"다음-읽을거리","다음 읽을거리",[81,2438,2439,2446,2453],{},[84,2440,2441,2445],{},[2324,2442,2444],{"href":2443},"/ko/blog/why-gpdf-is-faster","왜 gpdf가 다른 Go PDF 라이브러리보다 10–30배 빠른가"," — 위 \"수백 마이크로초\" 주장의 벤치 수치.",[84,2447,2448,2452],{},[2324,2449,2451],{"href":2450},"/ko/blog/go-pdf-library-showdown-2026","2026 Go PDF 라이브러리 쇼다운"," — 같은 인보이스를 gofpdf / gopdf / Maroto로 쓰면 몇 줄인가.",[84,2454,2455,2459],{},[2324,2456,2458],{"href":2457},"/ko/blog/12-column-grid","gpdf의 12컬럼 그리드는 어떻게 동작하는가"," — 위 헤더 행에서 사용한 레이아웃 모델의 깊은 설명.",[2461,2462,2463],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}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}",{"title":45,"searchDepth":127,"depth":127,"links":2465},[2466,2467,2468,2469,2477,2478,2479,2480,2481,2482,2483],{"id":16,"depth":127,"text":17},{"id":72,"depth":127,"text":73},{"id":109,"depth":127,"text":25},{"id":1158,"depth":127,"text":1159,"children":2470},[2471,2472,2473,2474,2475,2476],{"id":138,"depth":134,"text":138},{"id":1250,"depth":134,"text":1251},{"id":1303,"depth":134,"text":1304},{"id":1449,"depth":134,"text":1450},{"id":95,"depth":134,"text":95},{"id":1692,"depth":134,"text":1693},{"id":1819,"depth":127,"text":1820},{"id":1974,"depth":127,"text":1975},{"id":2234,"depth":127,"text":2234},{"id":2293,"depth":127,"text":2294},{"id":2345,"depth":127,"text":2346},{"id":2402,"depth":127,"text":2403},{"id":2435,"depth":127,"text":2436},"2026-04-21","실행 가능한 Go 인보이스 PDF를 50줄로. gpdf 하나로 의존성 제로, Chromium/CGO 불필요. 헤더, 테이블, 합계까지 모두 포함.",false,"md",{"name":2489,"totalTime":2490,"tools":2491,"steps":2493},"gpdf로 Go에서 인보이스 PDF 생성하기","PT10M",[2492],"Go 1.22+",[2494,2497,2500,2503,2506],{"name":2495,"text":2496},"gpdf 설치","모듈에서 go get github.com/gpdf-dev/gpdf를 실행한다. gpdf는 의존성이 없어 go.sum은 한 줄이고 CGO도 포함되지 않는다.",{"name":2498,"text":2499},"Document 구성","gpdf.NewDocument에 template.WithPageSize(document.A4)를 전달한다. doc.AddPage()가 PageBuilder를 반환하고 여기에 AutoRow를 쌓는다.",{"name":2501,"text":2502},"6컬럼 2개로 헤더 배치","page.AutoRow 안에서 r.Col(6, ...)을 두 번 호출한다. 왼쪽에 회사 정보, 오른쪽에 template.AlignRight()로 인보이스 번호와 만기일을 우측 정렬한다.",{"name":2504,"text":2505},"명세 테이블 렌더링","12컬럼 셀 안에서 c.Table에 헤더 슬라이스와 행 슬라이스를 전달한다. template.ColumnWidths와 template.TableHeaderStyle로 너비와 스타일을 지정한다.",{"name":2507,"text":2508},"PDF 바이트 출력","doc.Generate()로 []byte를 얻고 os.WriteFile(\"invoice.pdf\", b, 0644)로 저장한다. 임시 파일도 외부 바이너리도 필요 없다.",null,{},"/ko/blog/invoice-pdf-go-under-50-lines",{"title":5,"description":2485},"ko/blog/012.invoice-pdf-go-under-50-lines",[2515,2516],"tutorial","templates","ddbVbKCGdoY9guwPFIBH-cY2k7wL5EHHEIvaRPvoKUc",1779199027182]