[{"data":1,"prerenderedAt":19195},["ShallowReactive",2],{"blog-tag-ko-internals":3},[4,1329,2960,4053,5292,6659,8413,10119,11726,13051,14419],{"id":5,"title":6,"author":7,"body":10,"date":1293,"description":1294,"draft":1295,"extension":1296,"howTo":1297,"image":1319,"meta":1320,"navigation":86,"path":1321,"seo":1322,"stem":1323,"tags":1324,"updated":1319,"__hash__":1328},"blogKo/ko/blog/010.source-han-sans-jp-with-gpdf.md","gpdf에서 Source Han Sans JP(본고딕 JP)를 사용하려면?",{"name":8,"url":9},"gpdf team","https://gpdf.dev",{"type":11,"value":12,"toc":1281},"minimark",[13,18,34,38,55,59,708,722,726,737,740,825,836,840,875,882,906,916,920,923,931,934,1089,1102,1105,1109,1112,1152,1166,1170,1173,1187,1190,1205,1208,1212,1241,1245,1248,1265,1277],[14,15,17],"h2",{"id":16},"질문을-다시-말하면","질문을 다시 말하면",[19,20,21,28,29,33],"p",{},[22,23,27],"a",{"href":24,"rel":25},"https://github.com/gpdf-dev/gpdf",[26],"nofollow","gpdf"," 문서에서 ",[30,31,32],"strong",{},"Source Han Sans JP(본고딕 JP)"," — 2014년 Adobe와 Google 협력으로 공개된 pan-CJK 산세리프의 Adobe 측 브랜드 — 를 쓰고 싶다. 이유는 다양하다. 팀이 재현성을 위해 폰트를 GitHub 릴리스 태그에 핀 해두고 싶거나, 오래 전 Source Han으로 표준화된 디자인 시스템을 인수받았거나, 그냥 Adobe 릴리스 주기가 더 편하거나. 뭐든 상관없다. 다운로드하기 전에 세 가지만 정리해 두면 된다 — 어떤 파일을 받을지, Noto Sans JP와의 실제 관계, 그리고 gpdf가 읽을 수 있는 포맷이 무엇인지.",[14,35,37],{"id":36},"빠른-답","빠른 답",[19,39,40,45,46,50,51,54],{},[22,41,44],{"href":42,"rel":43},"https://github.com/adobe-fonts/source-han-sans/releases",[26],"adobe-fonts/source-han-sans"," 릴리스 페이지에서 ",[47,48,49],"code",{},"SourceHanSansJP-Regular.ttf","를 받아(TTF 번들, OTF 아님), ",[47,52,53],{},"gpdf.WithFont(\"SourceHanSansJP\", bytes)","로 등록하고 기본 폰트로 설정한다. Source Han Sans JP와 Noto Sans JP는 글리프 윤곽이 같으므로, Adobe 배포 생태계에 특별히 얽매일 이유가 없다면 Noto Sans JP가 더 간단한 다운로드다.",[14,56,58],{"id":57},"완성된-예제","완성된 예제",[60,61,66],"pre",{"className":62,"code":63,"language":64,"meta":65,"style":65},"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/template\"\n)\n\nfunc main() {\n    font, err := os.ReadFile(\"SourceHanSansJP-Regular.ttf\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(gpdf.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n        gpdf.WithFont(\"SourceHanSansJP\", font),\n        gpdf.WithDefaultFont(\"SourceHanSansJP\", 11),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"報告書\", template.FontSize(24), template.Bold())\n            c.Text(\"Source Han Sans JP — Adobe 배포 무료 CJK 폰트.\")\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"report.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n","go","",[47,67,68,81,88,98,110,120,125,135,145,155,161,166,182,220,236,254,260,265,284,307,345,371,396,402,407,426,460,497,545,565,571,577,582,603,616,631,636,682,697,702],{"__ignoreMap":65},[69,70,73,77],"span",{"class":71,"line":72},"line",1,[69,74,76],{"class":75},"sMK4o","package",[69,78,80],{"class":79},"sBMFI"," main\n",[69,82,84],{"class":71,"line":83},2,[69,85,87],{"emptyLinePlaceholder":86},true,"\n",[69,89,91,95],{"class":71,"line":90},3,[69,92,94],{"class":93},"s7zQu","import",[69,96,97],{"class":75}," (\n",[69,99,101,104,107],{"class":71,"line":100},4,[69,102,103],{"class":75},"    \"",[69,105,106],{"class":79},"log",[69,108,109],{"class":75},"\"\n",[69,111,113,115,118],{"class":71,"line":112},5,[69,114,103],{"class":75},[69,116,117],{"class":79},"os",[69,119,109],{"class":75},[69,121,123],{"class":71,"line":122},6,[69,124,87],{"emptyLinePlaceholder":86},[69,126,128,130,133],{"class":71,"line":127},7,[69,129,103],{"class":75},[69,131,132],{"class":79},"github.com/gpdf-dev/gpdf",[69,134,109],{"class":75},[69,136,138,140,143],{"class":71,"line":137},8,[69,139,103],{"class":75},[69,141,142],{"class":79},"github.com/gpdf-dev/gpdf/document",[69,144,109],{"class":75},[69,146,148,150,153],{"class":71,"line":147},9,[69,149,103],{"class":75},[69,151,152],{"class":79},"github.com/gpdf-dev/gpdf/template",[69,154,109],{"class":75},[69,156,158],{"class":71,"line":157},10,[69,159,160],{"class":75},")\n",[69,162,164],{"class":71,"line":163},11,[69,165,87],{"emptyLinePlaceholder":86},[69,167,169,172,176,179],{"class":71,"line":168},12,[69,170,171],{"class":75},"func",[69,173,175],{"class":174},"s2Zo4"," main",[69,177,178],{"class":75},"()",[69,180,181],{"class":75}," {\n",[69,183,185,189,192,195,198,201,204,207,210,213,216,218],{"class":71,"line":184},13,[69,186,188],{"class":187},"sTEyZ","    font",[69,190,191],{"class":75},",",[69,193,194],{"class":187}," err ",[69,196,197],{"class":75},":=",[69,199,200],{"class":187}," os",[69,202,203],{"class":75},".",[69,205,206],{"class":174},"ReadFile",[69,208,209],{"class":75},"(",[69,211,212],{"class":75},"\"",[69,214,49],{"class":215},"sfazB",[69,217,212],{"class":75},[69,219,160],{"class":75},[69,221,223,226,228,231,234],{"class":71,"line":222},14,[69,224,225],{"class":93},"    if",[69,227,194],{"class":187},[69,229,230],{"class":75},"!=",[69,232,233],{"class":75}," nil",[69,235,181],{"class":75},[69,237,239,242,244,247,249,252],{"class":71,"line":238},15,[69,240,241],{"class":187},"        log",[69,243,203],{"class":75},[69,245,246],{"class":174},"Fatal",[69,248,209],{"class":75},[69,250,251],{"class":187},"err",[69,253,160],{"class":75},[69,255,257],{"class":71,"line":256},16,[69,258,259],{"class":75},"    }\n",[69,261,263],{"class":71,"line":262},17,[69,264,87],{"emptyLinePlaceholder":86},[69,266,268,271,273,276,278,281],{"class":71,"line":267},18,[69,269,270],{"class":187},"    doc ",[69,272,197],{"class":75},[69,274,275],{"class":187}," gpdf",[69,277,203],{"class":75},[69,279,280],{"class":174},"NewDocument",[69,282,283],{"class":75},"(\n",[69,285,287,290,292,295,297,299,301,304],{"class":71,"line":286},19,[69,288,289],{"class":187},"        gpdf",[69,291,203],{"class":75},[69,293,294],{"class":174},"WithPageSize",[69,296,209],{"class":75},[69,298,27],{"class":187},[69,300,203],{"class":75},[69,302,303],{"class":187},"A4",[69,305,306],{"class":75},"),\n",[69,308,310,312,314,317,319,322,324,327,329,331,333,336,338,342],{"class":71,"line":309},20,[69,311,289],{"class":187},[69,313,203],{"class":75},[69,315,316],{"class":174},"WithMargins",[69,318,209],{"class":75},[69,320,321],{"class":187},"document",[69,323,203],{"class":75},[69,325,326],{"class":174},"UniformEdges",[69,328,209],{"class":75},[69,330,321],{"class":187},[69,332,203],{"class":75},[69,334,335],{"class":174},"Mm",[69,337,209],{"class":75},[69,339,341],{"class":340},"sbssI","20",[69,343,344],{"class":75},"))),\n",[69,346,348,350,352,355,357,359,362,364,366,369],{"class":71,"line":347},21,[69,349,289],{"class":187},[69,351,203],{"class":75},[69,353,354],{"class":174},"WithFont",[69,356,209],{"class":75},[69,358,212],{"class":75},[69,360,361],{"class":215},"SourceHanSansJP",[69,363,212],{"class":75},[69,365,191],{"class":75},[69,367,368],{"class":187}," font",[69,370,306],{"class":75},[69,372,374,376,378,381,383,385,387,389,391,394],{"class":71,"line":373},22,[69,375,289],{"class":187},[69,377,203],{"class":75},[69,379,380],{"class":174},"WithDefaultFont",[69,382,209],{"class":75},[69,384,212],{"class":75},[69,386,361],{"class":215},[69,388,212],{"class":75},[69,390,191],{"class":75},[69,392,393],{"class":340}," 11",[69,395,306],{"class":75},[69,397,399],{"class":71,"line":398},23,[69,400,401],{"class":75},"    )\n",[69,403,405],{"class":71,"line":404},24,[69,406,87],{"emptyLinePlaceholder":86},[69,408,410,413,415,418,420,423],{"class":71,"line":409},25,[69,411,412],{"class":187},"    page ",[69,414,197],{"class":75},[69,416,417],{"class":187}," doc",[69,419,203],{"class":75},[69,421,422],{"class":174},"AddPage",[69,424,425],{"class":75},"()\n",[69,427,429,432,434,437,440,444,447,450,452,455,458],{"class":71,"line":428},26,[69,430,431],{"class":187},"    page",[69,433,203],{"class":75},[69,435,436],{"class":174},"AutoRow",[69,438,439],{"class":75},"(func(",[69,441,443],{"class":442},"sHdIc","r",[69,445,446],{"class":75}," *",[69,448,449],{"class":79},"template",[69,451,203],{"class":75},[69,453,454],{"class":79},"RowBuilder",[69,456,457],{"class":75},")",[69,459,181],{"class":75},[69,461,463,466,468,471,473,476,478,481,484,486,488,490,493,495],{"class":71,"line":462},27,[69,464,465],{"class":187},"        r",[69,467,203],{"class":75},[69,469,470],{"class":174},"Col",[69,472,209],{"class":75},[69,474,475],{"class":340},"12",[69,477,191],{"class":75},[69,479,480],{"class":75}," func(",[69,482,483],{"class":442},"c",[69,485,446],{"class":75},[69,487,449],{"class":79},[69,489,203],{"class":75},[69,491,492],{"class":79},"ColBuilder",[69,494,457],{"class":75},[69,496,181],{"class":75},[69,498,500,503,505,508,510,512,515,517,519,522,524,527,529,532,535,537,539,542],{"class":71,"line":499},28,[69,501,502],{"class":187},"            c",[69,504,203],{"class":75},[69,506,507],{"class":174},"Text",[69,509,209],{"class":75},[69,511,212],{"class":75},[69,513,514],{"class":215},"報告書",[69,516,212],{"class":75},[69,518,191],{"class":75},[69,520,521],{"class":187}," template",[69,523,203],{"class":75},[69,525,526],{"class":174},"FontSize",[69,528,209],{"class":75},[69,530,531],{"class":340},"24",[69,533,534],{"class":75},"),",[69,536,521],{"class":187},[69,538,203],{"class":75},[69,540,541],{"class":174},"Bold",[69,543,544],{"class":75},"())\n",[69,546,548,550,552,554,556,558,561,563],{"class":71,"line":547},29,[69,549,502],{"class":187},[69,551,203],{"class":75},[69,553,507],{"class":174},[69,555,209],{"class":75},[69,557,212],{"class":75},[69,559,560],{"class":215},"Source Han Sans JP — Adobe 배포 무료 CJK 폰트.",[69,562,212],{"class":75},[69,564,160],{"class":75},[69,566,568],{"class":71,"line":567},30,[69,569,570],{"class":75},"        })\n",[69,572,574],{"class":71,"line":573},31,[69,575,576],{"class":75},"    })\n",[69,578,580],{"class":71,"line":579},32,[69,581,87],{"emptyLinePlaceholder":86},[69,583,585,588,590,592,594,596,598,601],{"class":71,"line":584},33,[69,586,587],{"class":187},"    data",[69,589,191],{"class":75},[69,591,194],{"class":187},[69,593,197],{"class":75},[69,595,417],{"class":187},[69,597,203],{"class":75},[69,599,600],{"class":174},"Generate",[69,602,425],{"class":75},[69,604,606,608,610,612,614],{"class":71,"line":605},34,[69,607,225],{"class":93},[69,609,194],{"class":187},[69,611,230],{"class":75},[69,613,233],{"class":75},[69,615,181],{"class":75},[69,617,619,621,623,625,627,629],{"class":71,"line":618},35,[69,620,241],{"class":187},[69,622,203],{"class":75},[69,624,246],{"class":174},[69,626,209],{"class":75},[69,628,251],{"class":187},[69,630,160],{"class":75},[69,632,634],{"class":71,"line":633},36,[69,635,259],{"class":75},[69,637,639,641,643,645,647,649,652,654,656,659,661,663,666,668,671,674,676,678,680],{"class":71,"line":638},37,[69,640,225],{"class":93},[69,642,194],{"class":187},[69,644,197],{"class":75},[69,646,200],{"class":187},[69,648,203],{"class":75},[69,650,651],{"class":174},"WriteFile",[69,653,209],{"class":75},[69,655,212],{"class":75},[69,657,658],{"class":215},"report.pdf",[69,660,212],{"class":75},[69,662,191],{"class":75},[69,664,665],{"class":187}," data",[69,667,191],{"class":75},[69,669,670],{"class":340}," 0o644",[69,672,673],{"class":75},");",[69,675,194],{"class":187},[69,677,230],{"class":75},[69,679,233],{"class":75},[69,681,181],{"class":75},[69,683,685,687,689,691,693,695],{"class":71,"line":684},38,[69,686,241],{"class":187},[69,688,203],{"class":75},[69,690,246],{"class":174},[69,692,209],{"class":75},[69,694,251],{"class":187},[69,696,160],{"class":75},[69,698,700],{"class":71,"line":699},39,[69,701,259],{"class":75},[69,703,705],{"class":71,"line":704},40,[69,706,707],{"class":75},"}\n",[19,709,710,711,714,715,718,719,721],{},"TTF를 ",[47,712,713],{},"main.go"," 옆에 두고 ",[47,716,717],{},"go run main.go",". 일본어가 들어간 한 페이지짜리 PDF가 ",[47,720,658],{},"로 떨어진다.",[14,723,725],{"id":724},"source-han-sans-jp-noto-sans-cjk-jp","Source Han Sans JP = Noto Sans CJK JP",[19,727,728,729,732,733,736],{},"검색 시간 몇 시간을 아껴줄 사실 하나: ",[30,730,731],{},"Source Han Sans와 Noto Sans CJK는 같은 폰트다",". Adobe가 글리프 설계, 메트릭 테이블, 문자셋 커버리지를 담당했다. Google은 Noto 이름으로 병행 유통했다. 두 배포판 모두 2014-07-15에 출시되었다. 윤곽 데이터, ",[47,734,735],{},"hmtx",", JIS X 0213 / Adobe-Japan1-6 커버리지는 비트 단위로 동일하다. Adobe가 버전을 올리면 Noto에도 몇 주 안에 반영된다.",[19,738,739],{},"차이는 전부 브랜딩과 패키징 층에 있다:",[741,742,743,758],"table",{},[744,745,746],"thead",{},[747,748,749,752,755],"tr",{},[750,751],"th",{},[750,753,754],{},"Source Han Sans JP",[750,756,757],{},"Noto Sans JP",[759,760,761,773,792,803,814],"tbody",{},[747,762,763,767,770],{},[764,765,766],"td",{},"발행자",[764,768,769],{},"Adobe",[764,771,772],{},"Google",[747,774,775,778,784],{},[764,776,777],{},"정식 출처",[764,779,780],{},[22,781,44],{"href":782,"rel":783},"https://github.com/adobe-fonts/source-han-sans",[26],[764,785,786,791],{},[22,787,790],{"href":788,"rel":789},"https://notofonts.github.io",[26],"notofonts.github.io"," + Google Fonts",[747,793,794,797,800],{},[764,795,796],{},"기본 포맷",[764,798,799],{},"OTF (CFF 윤곽)",[764,801,802],{},"TTF (static) + variable",[747,804,805,808,811],{},[764,806,807],{},"릴리스 모델",[764,809,810],{},"GitHub 릴리스 태그로 수동 버전 관리",[764,812,813],{},"Google Fonts CDN + git 저장소",[747,815,816,819,822],{},[764,817,818],{},"언어 묶음",[764,820,821],{},"언어별 TTF + pan-CJK OTC",[764,823,824],{},"JP 전용",[19,826,827,828,831,832,835],{},"팀이 Adobe의 GitHub 태그에 폰트를 고정하려 하거나, 이미 ",[47,829,830],{},"github.com/adobe-fonts","를 사내 미러링하거나, 파이프라인 다른 공정에서 pan-CJK OTC가 필요하다면 Source Han Sans JP가 맞다. 그렇지 않다면 TTF를 바로 받을 수 있는 ",[22,833,757],{"href":834},"/ko/blog/noto-sans-jp-with-gpdf"," 쪽이 손이 덜 간다.",[14,837,839],{"id":838},"왜-otf가-아니라-ttf인가","왜 OTF가 아니라 TTF인가",[19,841,842,843,846,847,850,851,854,855,854,858,854,861,863,864,867,868,871,872,874],{},"Adobe의 기본 자산은 ",[47,844,845],{},".otf",", 정확히는 CFF 기반 OpenType이다. gpdf의 폰트 파서는 ",[47,848,849],{},"pdf/font/truetype.go"," 한 파일에 모여 있고 ",[47,852,853],{},"glyf",", ",[47,856,857],{},"loca",[47,859,860],{},"cmap",[47,862,735],{},"와 복합 글리프를 읽는다. ",[47,865,866],{},"CFF "," / ",[47,869,870],{},"CFF2"," 윤곽은 읽지 않는다. CFF 기반 ",[47,873,845],{},"를 넘기면 렌더 시점이 아니라 문서 생성 시점에 파싱 에러가 난다.",[19,876,877,878,881],{},"Adobe 릴리스 페이지는 OTF와 TTF를 모두 제공하므로 ",[30,879,880],{},"TTF 번들"," 쪽을 받는다. 특정 포인트 릴리스에 TTF가 없는 경우엔 깔끔한 대안이 둘 있다:",[883,884,885,892],"ol",{},[886,887,888,891],"li",{},[30,889,890],{},"Noto Sans JP로 바꾼다."," Google Fonts가 정적 TTF를 직접 제공한다. 글리프 데이터는 동일하다. 변환이 필요 없다.",[886,893,894,897,898,901,902,905],{},[30,895,896],{},"한 번만 변환하고 결과를 커밋한다."," ",[47,899,900],{},"fonttools","의 ",[47,903,904],{},"otf2ttf","가 1분이면 TTF를 만든다. 결과를 저장소나 사내 아티팩트 서버에 올리고, 변환 단계는 빌드 파이프라인에서 빼둔다.",[19,907,908,909,911,912,915],{},"빌드 시점에 변환하는 건 피한다. 폰트 변환 도구는 버전 간 동작이 미세하게 다르고, ",[47,910,735],{}," 테이블이 조금만 바뀌어도 ",[47,913,914],{},"pip install -U"," 하나로 행 넘김 위치가 바뀐다.",[14,917,919],{"id":918},"일곱-굵기","일곱 굵기",[19,921,922],{},"Source Han Sans JP는 ExtraLight부터 Heavy까지 한 굵기당 한 파일로 배포한다:",[60,924,929],{"className":925,"code":927,"language":928},[926],"language-text","SourceHanSansJP-ExtraLight.ttf\nSourceHanSansJP-Light.ttf\nSourceHanSansJP-Normal.ttf\nSourceHanSansJP-Regular.ttf\nSourceHanSansJP-Medium.ttf\nSourceHanSansJP-Bold.ttf\nSourceHanSansJP-Heavy.ttf\n","text",[47,930,927],{"__ignoreMap":65},[19,932,933],{},"대부분의 업무 문서는 Regular와 Bold 둘이면 충분하다:",[60,935,937],{"className":62,"code":936,"language":64,"meta":65,"style":65},"reg,  _ := os.ReadFile(\"SourceHanSansJP-Regular.ttf\")\nbold, _ := os.ReadFile(\"SourceHanSansJP-Bold.ttf\")\n\ndoc := gpdf.NewDocument(\n    gpdf.WithFont(\"SourceHanSansJP\", reg),\n    gpdf.WithFont(\"SourceHanSansJP-Bold\", bold),\n    gpdf.WithDefaultFont(\"SourceHanSansJP\", 11),\n)\n",[47,938,939,967,996,1000,1015,1039,1063,1085],{"__ignoreMap":65},[69,940,941,944,946,949,951,953,955,957,959,961,963,965],{"class":71,"line":72},[69,942,943],{"class":187},"reg",[69,945,191],{"class":75},[69,947,948],{"class":187},"  _ ",[69,950,197],{"class":75},[69,952,200],{"class":187},[69,954,203],{"class":75},[69,956,206],{"class":174},[69,958,209],{"class":75},[69,960,212],{"class":75},[69,962,49],{"class":215},[69,964,212],{"class":75},[69,966,160],{"class":75},[69,968,969,972,974,977,979,981,983,985,987,989,992,994],{"class":71,"line":83},[69,970,971],{"class":187},"bold",[69,973,191],{"class":75},[69,975,976],{"class":187}," _ ",[69,978,197],{"class":75},[69,980,200],{"class":187},[69,982,203],{"class":75},[69,984,206],{"class":174},[69,986,209],{"class":75},[69,988,212],{"class":75},[69,990,991],{"class":215},"SourceHanSansJP-Bold.ttf",[69,993,212],{"class":75},[69,995,160],{"class":75},[69,997,998],{"class":71,"line":90},[69,999,87],{"emptyLinePlaceholder":86},[69,1001,1002,1005,1007,1009,1011,1013],{"class":71,"line":100},[69,1003,1004],{"class":187},"doc ",[69,1006,197],{"class":75},[69,1008,275],{"class":187},[69,1010,203],{"class":75},[69,1012,280],{"class":174},[69,1014,283],{"class":75},[69,1016,1017,1020,1022,1024,1026,1028,1030,1032,1034,1037],{"class":71,"line":112},[69,1018,1019],{"class":187},"    gpdf",[69,1021,203],{"class":75},[69,1023,354],{"class":174},[69,1025,209],{"class":75},[69,1027,212],{"class":75},[69,1029,361],{"class":215},[69,1031,212],{"class":75},[69,1033,191],{"class":75},[69,1035,1036],{"class":187}," reg",[69,1038,306],{"class":75},[69,1040,1041,1043,1045,1047,1049,1051,1054,1056,1058,1061],{"class":71,"line":122},[69,1042,1019],{"class":187},[69,1044,203],{"class":75},[69,1046,354],{"class":174},[69,1048,209],{"class":75},[69,1050,212],{"class":75},[69,1052,1053],{"class":215},"SourceHanSansJP-Bold",[69,1055,212],{"class":75},[69,1057,191],{"class":75},[69,1059,1060],{"class":187}," bold",[69,1062,306],{"class":75},[69,1064,1065,1067,1069,1071,1073,1075,1077,1079,1081,1083],{"class":71,"line":127},[69,1066,1019],{"class":187},[69,1068,203],{"class":75},[69,1070,380],{"class":174},[69,1072,209],{"class":75},[69,1074,212],{"class":75},[69,1076,361],{"class":215},[69,1078,212],{"class":75},[69,1080,191],{"class":75},[69,1082,393],{"class":340},[69,1084,306],{"class":75},[69,1086,1087],{"class":71,"line":137},[69,1088,160],{"class":75},[19,1090,1091,1094,1095,1098,1099,1101],{},[47,1092,1093],{},"-Bold"," 접미사가 ",[47,1096,1097],{},"template.Bold()","와 Bold TTF를 연결하는 규약이다. 등록하지 않은 채 ",[47,1100,1097],{},"를 쓰면 Regular 글리프 위에 0.4 pt 획을 덧댄 합성 볼드로 대체된다 — 제목 정도는 버티지만, 큰 글자 크기에서는 진짜 Bold 윤곽보다 가늘어 보인다.",[19,1103,1104],{},"CJK 폰트는 관례적으로 이탤릭을 만들지 않고, Source Han Sans JP도 마찬가지다. 일본어에 이탤릭 강조가 필요한 레이아웃이라면 굵기나 색으로 대신한다. 한자에 기울임 변환을 걸면 강조가 아니라 깨진 글자로 보인다.",[14,1106,1108],{"id":1107},"pan-cjk-jp-전용-super-otc","Pan-CJK, JP 전용, Super OTC",[19,1110,1111],{},"Adobe는 Source Han Sans를 여러 입도로 배포한다. Go PDF 생성에서는 혼용할 수 없다:",[1113,1114,1115,1128,1138],"ul",{},[886,1116,1117,1120,1121,1124,1125,1127],{},[30,1118,1119],{},"SourceHanSans.ttc","(Super OTC) — 모든 CJK 언어가 20 MB+ TrueType Collection 하나에 들어 있다. gpdf는 ",[47,1122,1123],{},".ttc"," 내부의 face 인덱스를 직접 풀지 않기 때문에, 먼저 ",[47,1126,900],{},"로 JP face를 추출해 TTF로 등록해야 한다. 보통은 쓰지 않는다.",[886,1129,1130,1133,1134,1137],{},[30,1131,1132],{},"지역 통합 OTF","(예: ",[47,1135,1136],{},"SourceHanSans-Regular.otf",") — 전체 CJK 통합, CFF 윤곽. gpdf가 못 읽는다.",[886,1139,1140,209,1143,1145,1146,1148,1149,203],{},[30,1141,1142],{},"언어별 TTF",[47,1144,49],{},") — JP 전용, ",[47,1147,853],{}," 윤곽. ",[30,1150,1151],{},"이걸 쓴다",[19,1153,1154,1155,1157,1158,1161,1162,1165],{},"한 페이지에서 일본어와 한국어/중국어가 섞이는 문서라면 pan-CJK OTF에 의존하지 말고 언어별 패밀리를 나란히 등록한다: ",[47,1156,361],{}," + ",[47,1159,1160],{},"SourceHanSansKR",". 스크립트가 바뀌는 지점에서 ",[47,1163,1164],{},"template.FontFamily","로 명시한다. pan-CJK OTF는 한자를 Han unification으로 하나의 형으로 묶어버리기 때문에, 일본어 본문에 있어야 할 일본어 자형의 한자가 중국어 자형으로 나오는 사고가 생긴다.",[14,1167,1169],{"id":1168},"noto-대신-source-han을-고를-때","Noto 대신 Source Han을 고를 때",[19,1171,1172],{},"윤곽은 같고 유통 채널이 다르다. Source Han Sans JP가 자연스러운 경우:",[1113,1174,1175,1178,1184],{},[886,1176,1177],{},"운영 팀이 폰트를 Adobe의 GitHub 릴리스 태그에 고정하는 운용을 선호함(재현성, 감사성)",[886,1179,1180,1181,1183],{},"사내에서 이미 ",[47,1182,830],{},"를 미러링하고 있음(엄격한 아티팩트 정책의 기업에서 흔함)",[886,1185,1186],{},"파이프라인 다른 단계에서도 pan-CJK OTC 번들이 필요함(DTP 핸드오프, Adobe 이름으로 통일된 브랜드 시스템 등)",[19,1188,1189],{},"Noto Sans JP가 더 잘 맞는 경우:",[1113,1191,1192,1199,1202],{},[886,1193,1194,1195,1198],{},"TTF를 최단 경로로 받고 싶다(",[47,1196,1197],{},"fonts.google.com/noto/specimen/Noto+Sans+JP"," → zip → 끝)",[886,1200,1201],{},"OTF → TTF 변환을 빌드에 끌어들이고 싶지 않다",[886,1203,1204],{},"프로젝트가 이미 기존 워크플로로 다른 Google Fonts를 받고 있다",[19,1206,1207],{},"렌더링 결과는 같다. 판단은 운영상의 것 — 파일이 어디에 있고 어떻게 버전을 관리하고 팀이 어느 쪽에 익숙한가 — 이지, 미감의 문제가 아니다.",[14,1209,1211],{"id":1210},"관련-레시피","관련 레시피",[1113,1213,1214,1220,1227,1234],{},[886,1215,1216,1219],{},[22,1217,1218],{"href":834},"gpdf에서 Noto Sans JP를 사용하려면?"," — 같은 글리프, 박스에서 꺼내면 TTF",[886,1221,1222,1226],{},[22,1223,1225],{"href":1224},"/ko/blog/embed-japanese-font","gpdf에서 일본어 폰트를 임베드하려면?"," — CJK 임베딩의 일반 레시피",[886,1228,1229,1233],{},[22,1230,1232],{"href":1231},"/ko/blog/ipaex-gothic-gpdf","gpdf에서 IPAex 고딕을 사용하려면?"," — 일본 공공 제출용 IPA 라이선스 대안",[886,1235,1236,1240],{},[22,1237,1239],{"href":1238},"/ko/blog/tofu-boxes-japanese","gpdf로 만든 PDF에서 일본어가 네모로 나올 때"," — 글리프 누락 트러블슈팅",[14,1242,1244],{"id":1243},"gpdf를-써보자","gpdf를 써보자",[19,1246,1247],{},"gpdf는 Go용 PDF 생성 라이브러리다. MIT 라이선스, 외부 의존성 0, 네이티브 CJK 지원.",[60,1249,1253],{"className":1250,"code":1251,"language":1252,"meta":65,"style":65},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go get github.com/gpdf-dev/gpdf\n","bash",[47,1254,1255],{"__ignoreMap":65},[69,1256,1257,1259,1262],{"class":71,"line":72},[69,1258,64],{"class":79},[69,1260,1261],{"class":215}," get",[69,1263,1264],{"class":215}," github.com/gpdf-dev/gpdf\n",[19,1266,1267,1271,1272],{},[22,1268,1270],{"href":24,"rel":1269},[26],"⭐ GitHub에서 Star"," · ",[22,1273,1276],{"href":1274,"rel":1275},"https://gpdf.dev/ko/docs/quickstart",[26],"문서 읽기",[1278,1279,1280],"style",{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .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);}",{"title":65,"searchDepth":83,"depth":83,"links":1282},[1283,1284,1285,1286,1287,1288,1289,1290,1291,1292],{"id":16,"depth":83,"text":17},{"id":36,"depth":83,"text":37},{"id":57,"depth":83,"text":58},{"id":724,"depth":83,"text":725},{"id":838,"depth":83,"text":839},{"id":918,"depth":83,"text":919},{"id":1107,"depth":83,"text":1108},{"id":1168,"depth":83,"text":1169},{"id":1210,"depth":83,"text":1211},{"id":1243,"depth":83,"text":1244},"2026-04-19","Adobe의 GitHub 릴리스에서 TTF 버전을 받아 gpdf.WithFont로 등록한다. Noto Sans JP와 같은 글리프, 7가지 굵기, SIL OFL.",false,"md",{"name":1298,"totalTime":1299,"tools":1300,"steps":1303},"gpdf 문서에서 Source Han Sans JP(본고딕 JP)를 기본 폰트로 설정하기","PT15M",[1301,1302],"Go 1.22+","SourceHanSansJP-Regular.ttf (adobe-fonts/source-han-sans)",[1304,1307,1310,1313,1316],{"name":1305,"text":1306},"Adobe의 GitHub 릴리스에서 TTF 버전을 받는다","github.com/adobe-fonts/source-han-sans/releases를 열고 최신 릴리스에서 TTF 번들(OTF나 SuperOTC 말고)을 다운로드한 뒤 SourceHanSansJP-Regular.ttf를 꺼낸다. gpdf는 TrueType만 파싱하고 CFF 기반 OpenType은 읽지 않는다.",{"name":1308,"text":1309},"시작 시 바이트를 로드한다","os.ReadFile(\"SourceHanSansJP-Regular.ttf\") 또는 //go:embed를 쓴다. CI 재현성을 위해 빌드 시점에 다운로드하지 말고 Adobe 릴리스 태그를 저장소에 고정한다.",{"name":1311,"text":1312},"문서 생성 시점에 등록한다","gpdf.WithFont(\"SourceHanSansJP\", fontBytes)와 gpdf.WithDefaultFont(\"SourceHanSansJP\", 11)을 gpdf.NewDocument에 전달한다. AddUTF8Font도 파일 경로도 필요 없다.",{"name":1314,"text":1315},"필요하면 다른 굵기도 등록한다","Source Han Sans JP는 ExtraLight부터 Heavy까지 7개 굵기를 각각 TTF로 배포한다. Bold TTF를 SourceHanSansJP-Bold라는 키로 등록하면 template.Bold()가 합성 볼드가 아니라 진짜 Bold 윤곽을 쓴다.",{"name":1317,"text":1318},"배포물에 OFL.txt를 함께 둔다","SIL OFL 1.1은 폰트 바이너리가 배포되는 곳에 라이선스 전문을 동반할 것을 요구한다. //go:embed로 TTF를 넣는다면 OFL.txt도 LICENSES/에 같이 넣고 NOTICE에서 참조한다.",null,{},"/ko/blog/source-han-sans-jp-with-gpdf",{"title":6,"description":1294},"ko/blog/010.source-han-sans-jp-with-gpdf",[1325,1326,1327],"recipe","cjk","tutorial","4SJzNvBt1VrHylWf5j9E1AfSvZHvgKIYIQFtVJXImC8",{"id":1330,"title":1331,"author":1332,"body":1333,"date":1293,"description":2950,"draft":1295,"extension":1296,"howTo":1319,"image":1319,"meta":2951,"navigation":86,"path":2952,"seo":2953,"stem":2954,"tags":2955,"updated":1319,"__hash__":2959},"blogKo/ko/blog/011.why-gpdf-is-faster.md","gpdf가 다른 Go PDF 라이브러리보다 10–30배 빠른 이유",{"name":8,"url":9},{"type":11,"value":1334,"toc":2929},[1335,1339,1362,1386,1389,1400,1407,1411,1418,1527,1537,1544,1551,1555,1558,1564,1575,1581,1588,1886,1921,1943,1946,1951,1954,1972,1983,1993,1996,2000,2003,2010,2107,2136,2139,2142,2288,2297,2307,2311,2318,2324,2334,2337,2343,2346,2350,2364,2367,2371,2374,2381,2384,2410,2421,2424,2431,2560,2563,2567,2580,2583,2587,2601,2608,2612,2615,2693,2700,2703,2707,2710,2720,2729,2743,2751,2754,2758,2761,2801,2810,2813,2824,2828,2839,2845,2858,2864,2870,2874,2877,2889,2899,2903,2926],[14,1336,1338],{"id":1337},"tldr","TL;DR",[19,1340,1341,1342,1345,1346,1349,1350,1353,1354,1357,1358,1361],{},"gpdf는 단일 페이지를 ",[30,1343,1344],{},"13 µs",", 4×10 인보이스 표를 ",[30,1347,1348],{},"108 µs",", 100페이지 분할 보고서를 ",[30,1351,1352],{},"683 µs","에 생성한다. 그다음으로 빠른 유지보수 중인 Go PDF 라이브러리 ",[47,1355,1356],{},"jung-kurt/gofpdf","는 같은 100페이지를 ",[30,1359,1360],{},"11.7 ms","에 처리 — 약 17배 느리다. 튜닝 차이가 아니다. 세 가지 설계 결정이 쌓인 결과다.",[883,1363,1364,1370,1380],{},[886,1365,1366,1369],{},[30,1367,1368],{},"단일 패스 레이아웃."," Builder API와 PDF 콘텐츠 스트림 사이에 중간 AST가 없다.",[886,1371,1372,1375,1376,1379],{},[30,1373,1374],{},"핫패스의 구체 타입."," 레이아웃 루프 안에 리플렉션, ",[47,1377,1378],{},"interface{}",", 가상 디스패치 없음.",[886,1381,1382,1385],{},[30,1383,1384],{},"cmap을 한 번만 해석하는 TrueType 서브세터."," 글리프마다도, 페이지마다도 아닌 단 한 번.",[19,1387,1388],{},"각각 하나씩이 2–3배. 셋을 합하면 한 자릿수 차이가 된다.",[19,1390,1391,1392,1399],{},"이 글은 그 숫자를 만든 코드 경로를 그대로 따라간다. 벤치마크 소스는 ",[22,1393,1396],{"href":1394,"rel":1395},"https://github.com/gpdf-dev/gpdf/tree/main/_benchmark",[26],[47,1397,1398],{},"_benchmark/benchmark_test.go","에 공개되어 있다. 클론해서 자신의 머신에서 돌려보고, 숫자가 맞지 않으면 이슈를 열어 주면 된다.",[19,1401,1402,1403,1406],{},"선공개 바이어스: 우리는 gpdf를 만드는 팀이다. \"우리 게 더 빠르다\"의 정직한 번역은 \"우리는 다른 트레이드오프를 선택했다\"이고, 흥미로운 질문은 ",[30,1404,1405],{},"그 속도를 위해 무엇을 포기했나","다. 후반부에서 그 이야기를 한다.",[14,1408,1410],{"id":1409},"여기서-빠르다는-무엇인가","여기서 \"빠르다\"는 무엇인가",[19,1412,1413,1414,1417],{},"아키텍처 전에, 설명하려는 스코어보드를 먼저 (Apple M1, Go 1.25, CGO 비활성, ",[47,1415,1416],{},"-benchmem"," 활성):",[741,1419,1420,1441],{},[744,1421,1422],{},[747,1423,1424,1427,1429,1432,1435,1438],{},[750,1425,1426],{},"워크로드",[750,1428,27],{},[750,1430,1431],{},"gofpdf",[750,1433,1434],{},"go-pdf/fpdf",[750,1436,1437],{},"signintech/gopdf",[750,1439,1440],{},"Maroto v2",[759,1442,1443,1464,1485,1505],{},[747,1444,1445,1448,1452,1455,1458,1461],{},[764,1446,1447],{},"단일 페이지 Hello World",[764,1449,1450],{},[30,1451,1344],{},[764,1453,1454],{},"132 µs",[764,1456,1457],{},"135 µs",[764,1459,1460],{},"423 µs",[764,1462,1463],{},"237 µs",[747,1465,1466,1469,1473,1476,1479,1482],{},[764,1467,1468],{},"4×10 인보이스 표",[764,1470,1471],{},[30,1472,1348],{},[764,1474,1475],{},"241 µs",[764,1477,1478],{},"243 µs",[764,1480,1481],{},"835 µs",[764,1483,1484],{},"8,600 µs",[747,1486,1487,1490,1494,1497,1500,1502],{},[764,1488,1489],{},"100페이지 분할 보고서",[764,1491,1492],{},[30,1493,1352],{},[764,1495,1496],{},"11,700 µs",[764,1498,1499],{},"11,900 µs",[764,1501,1484],{},[764,1503,1504],{},"19,800 µs",[747,1506,1507,1510,1515,1518,1521,1524],{},[764,1508,1509],{},"복잡한 CJK 인보이스",[764,1511,1512],{},[30,1513,1514],{},"133 µs",[764,1516,1517],{},"254 µs",[764,1519,1520],{},"n/a",[764,1522,1523],{},"997 µs",[764,1525,1526],{},"10,400 µs",[19,1528,1529,1530,1533,1534,1536],{},"설명을 시작하기 전에 보이는 두 가지 모양이 있다. 페이지 수가 늘수록 ",[30,1531,1532],{},"격차가 벌어진다"," (Hello World 10배, 100페이지 17배). 복잡도가 올라갈수록 ",[30,1535,1532],{}," (표 단독으로 108 µs인데 Maroto의 gofpdf 백엔드로는 8.6 ms).",[19,1538,1539,1540,1543],{},"두 모양 모두 원인은 같다. ",[30,1541,1542],{},"gpdf의 레이아웃 루프는 공통 경로에서 할당하지 않기 때문에"," 요소당 비용이 거의 평탄하다. 왜 그런지 아래에서 설명한다.",[19,1545,1546,1547,1550],{},"아무도 읽고 싶어 하지 않는 면책 조항: ",[30,1548,1549],{},"대부분의 PDF 워크로드에서 절대 속도는 생각만큼 중요하지 않다",". 가장 큰 문서가 한 페이지짜리 영수증이라면 이 표의 유지보수 중인 어떤 라이브러리든 요청 경로에서 생성 가능하다. 격차가 의미를 갖는 임계점은 \"100개 인보이스를 큐로 미루지 않고 동기 생성 가능한가\" 근처부터다.",[14,1552,1554],{"id":1553},"결정-1-중간-ast를-만들지-않는다","결정 1: 중간 AST를 만들지 않는다",[19,1556,1557],{},"일반적인 PDF Builder 라이브러리는 이렇게 동작한다:",[60,1559,1562],{"className":1560,"code":1561,"language":928},[926],"builder API → 문서 트리 (AST) → 레이아웃 패스 → 직렬화기 → 바이트\n",[47,1563,1561],{"__ignoreMap":65},[19,1565,1566,1567,1570,1571,1574],{},"가운데의 문서 트리 단계가 문제다. 매 ",[47,1568,1569],{},".Text()","마다 노드를 할당하고, 매 ",[47,1572,1573],{},".Row()","마다 컨테이너를 할당한다. 레이아웃 패스는 트리를 돌아 위치를 계산하고, 직렬화기는 다시 트리를 돌아 바이트를 낸다. 세 번의 패스, 세 번의 할당, 동일 데이터에 대한 세 번의 CPU 캐시 왕복.",[19,1576,1577,1578,203],{},"gpdf에는 그 2단계가 없다. Builder는 레이아웃 컨텍스트에 직접 쓰고, 레이아웃 컨텍스트는 콘텐츠 스트림에 직접 쓴다. ",[30,1579,1580],{},"한 번의 패스",[19,1582,1583,1584,1587],{},"텍스트 요소의 실제 코드 경로 (",[47,1585,1586],{},"template/col_builder.go","에서 길이 조정):",[60,1589,1591],{"className":62,"code":1590,"language":64,"meta":65,"style":65},"func (c *ColBuilder) Text(s string, opts ...TextOption) {\n    opt := c.resolveOptions(opts)\n    box := c.currentBox()\n    w := c.measureText(s, opt)\n    h := opt.FontSize.Pt() * opt.LineHeight\n    c.writer.BeginText()\n    c.writer.SetFont(opt.Font, opt.FontSize)\n    c.writer.MoveTo(box.X, box.Y-opt.FontSize.Pt())\n    c.writer.ShowString(s)\n    c.writer.EndText()\n    c.advance(w, h)\n}\n",[47,1592,1593,1637,1659,1675,1700,1729,1746,1779,1827,1846,1861,1882],{"__ignoreMap":65},[69,1594,1595,1597,1600,1603,1606,1608,1610,1613,1615,1618,1622,1624,1627,1630,1633,1635],{"class":71,"line":72},[69,1596,171],{"class":75},[69,1598,1599],{"class":75}," (",[69,1601,1602],{"class":442},"c ",[69,1604,1605],{"class":75},"*",[69,1607,492],{"class":79},[69,1609,457],{"class":75},[69,1611,1612],{"class":174}," Text",[69,1614,209],{"class":75},[69,1616,1617],{"class":442},"s",[69,1619,1621],{"class":1620},"spNyl"," string",[69,1623,191],{"class":75},[69,1625,1626],{"class":442}," opts",[69,1628,1629],{"class":75}," ...",[69,1631,1632],{"class":79},"TextOption",[69,1634,457],{"class":75},[69,1636,181],{"class":75},[69,1638,1639,1642,1644,1647,1649,1652,1654,1657],{"class":71,"line":83},[69,1640,1641],{"class":187},"    opt ",[69,1643,197],{"class":75},[69,1645,1646],{"class":187}," c",[69,1648,203],{"class":75},[69,1650,1651],{"class":174},"resolveOptions",[69,1653,209],{"class":75},[69,1655,1656],{"class":187},"opts",[69,1658,160],{"class":75},[69,1660,1661,1664,1666,1668,1670,1673],{"class":71,"line":90},[69,1662,1663],{"class":187},"    box ",[69,1665,197],{"class":75},[69,1667,1646],{"class":187},[69,1669,203],{"class":75},[69,1671,1672],{"class":174},"currentBox",[69,1674,425],{"class":75},[69,1676,1677,1680,1682,1684,1686,1689,1691,1693,1695,1698],{"class":71,"line":100},[69,1678,1679],{"class":187},"    w ",[69,1681,197],{"class":75},[69,1683,1646],{"class":187},[69,1685,203],{"class":75},[69,1687,1688],{"class":174},"measureText",[69,1690,209],{"class":75},[69,1692,1617],{"class":187},[69,1694,191],{"class":75},[69,1696,1697],{"class":187}," opt",[69,1699,160],{"class":75},[69,1701,1702,1705,1707,1709,1711,1713,1715,1718,1720,1722,1724,1726],{"class":71,"line":112},[69,1703,1704],{"class":187},"    h ",[69,1706,197],{"class":75},[69,1708,1697],{"class":187},[69,1710,203],{"class":75},[69,1712,526],{"class":187},[69,1714,203],{"class":75},[69,1716,1717],{"class":174},"Pt",[69,1719,178],{"class":75},[69,1721,446],{"class":75},[69,1723,1697],{"class":187},[69,1725,203],{"class":75},[69,1727,1728],{"class":187},"LineHeight\n",[69,1730,1731,1734,1736,1739,1741,1744],{"class":71,"line":122},[69,1732,1733],{"class":187},"    c",[69,1735,203],{"class":75},[69,1737,1738],{"class":187},"writer",[69,1740,203],{"class":75},[69,1742,1743],{"class":174},"BeginText",[69,1745,425],{"class":75},[69,1747,1748,1750,1752,1754,1756,1759,1761,1764,1766,1769,1771,1773,1775,1777],{"class":71,"line":127},[69,1749,1733],{"class":187},[69,1751,203],{"class":75},[69,1753,1738],{"class":187},[69,1755,203],{"class":75},[69,1757,1758],{"class":174},"SetFont",[69,1760,209],{"class":75},[69,1762,1763],{"class":187},"opt",[69,1765,203],{"class":75},[69,1767,1768],{"class":187},"Font",[69,1770,191],{"class":75},[69,1772,1697],{"class":187},[69,1774,203],{"class":75},[69,1776,526],{"class":187},[69,1778,160],{"class":75},[69,1780,1781,1783,1785,1787,1789,1792,1794,1797,1799,1802,1804,1807,1809,1812,1815,1817,1819,1821,1823,1825],{"class":71,"line":137},[69,1782,1733],{"class":187},[69,1784,203],{"class":75},[69,1786,1738],{"class":187},[69,1788,203],{"class":75},[69,1790,1791],{"class":174},"MoveTo",[69,1793,209],{"class":75},[69,1795,1796],{"class":187},"box",[69,1798,203],{"class":75},[69,1800,1801],{"class":187},"X",[69,1803,191],{"class":75},[69,1805,1806],{"class":187}," box",[69,1808,203],{"class":75},[69,1810,1811],{"class":187},"Y",[69,1813,1814],{"class":75},"-",[69,1816,1763],{"class":187},[69,1818,203],{"class":75},[69,1820,526],{"class":187},[69,1822,203],{"class":75},[69,1824,1717],{"class":174},[69,1826,544],{"class":75},[69,1828,1829,1831,1833,1835,1837,1840,1842,1844],{"class":71,"line":147},[69,1830,1733],{"class":187},[69,1832,203],{"class":75},[69,1834,1738],{"class":187},[69,1836,203],{"class":75},[69,1838,1839],{"class":174},"ShowString",[69,1841,209],{"class":75},[69,1843,1617],{"class":187},[69,1845,160],{"class":75},[69,1847,1848,1850,1852,1854,1856,1859],{"class":71,"line":157},[69,1849,1733],{"class":187},[69,1851,203],{"class":75},[69,1853,1738],{"class":187},[69,1855,203],{"class":75},[69,1857,1858],{"class":174},"EndText",[69,1860,425],{"class":75},[69,1862,1863,1865,1867,1870,1872,1875,1877,1880],{"class":71,"line":163},[69,1864,1733],{"class":187},[69,1866,203],{"class":75},[69,1868,1869],{"class":174},"advance",[69,1871,209],{"class":75},[69,1873,1874],{"class":187},"w",[69,1876,191],{"class":75},[69,1878,1879],{"class":187}," h",[69,1881,160],{"class":75},[69,1883,1884],{"class":71,"line":168},[69,1885,707],{"class":75},[19,1887,1888,1889,1892,1893,1896,1897,1900,1901,867,1903,867,1905,1907,1908,867,1911,867,1914,867,1917,1920],{},"노드가 트리에 쌓이지 않는다. 위치가 지연되지 않는다. writer는 ",[47,1890,1891],{},"io.Writer","(보통 ",[47,1894,1895],{},"bytes.Buffer",")를 가진 ",[47,1898,1899],{},"*pdf.Writer","이고, ",[47,1902,1743],{},[47,1904,1791],{},[47,1906,1839],{},"은 ",[47,1909,1910],{},"BT",[47,1912,1913],{},"Td",[47,1915,1916],{},"Tj",[47,1918,1919],{},"ET"," PDF 연산자를 그 버퍼에 즉시 쓴다.",[19,1922,1923,1924,1927,1928,1157,1931,1934,1935,1938,1939,1942],{},"gofpdf가 같은 논리 연산을 어떻게 하는지 비교. gofpdf는 ",[47,1925,1926],{},"page"," 객체에 연산의 슬라이스를 가진다. 각 ",[47,1929,1930],{},"SetXY",[47,1932,1933],{},"Cell"," 호출이 그 슬라이스에 append. 마지막에 ",[47,1936,1937],{},"Output"," (또는 ",[47,1940,1941],{},"OutputFileAndClose",")이 슬라이스를 돌며 바이트를 낸다. 셀당 할당 두 번 — 연산 구조체 하나, 문자열 복사 하나 — 과 데이터에 대한 추가 패스 하나.",[19,1944,1945],{},"100페이지 보고서에 페이지당 약 40줄이면, gpdf가 하지 않는 추가 할당이 4,000개다.",[1947,1948,1950],"h3",{"id":1949},"단일-패스가-아픈-곳","단일 패스가 아픈 곳",[19,1952,1953],{},"당연한 질문: 바이트 출력을 시작하기 전에 최종 페이지 레이아웃을 알아야 하는 건 어떻게 하나. 페이지 번호 넣은 헤더. 페이지를 넘나드는 표. 본문 마지막 줄 아래에 고정된 푸터.",[19,1955,1956,1957,1960,1961,1964,1965,854,1968,1971],{},"두 가지 답. 첫째, 버퍼링의 단위는 ",[30,1958,1959],{},"문서가 아니라 페이지","다. 페이지는 수십 KB 단위의 경계 단위다. 다음 ",[47,1962,1963],{},"AddPage()","가 실행되면 현재 페이지 콘텐츠 스트림이 확정되고(",[47,1966,1967],{},"Length",[47,1969,1970],{},"Filter",", 오프셋) xref 엔트리가 쓰이고, 페이지 버퍼는 리셋된다. 메모리 최고 수위는 O(페이지 하나) 크기로 유지된다.",[19,1973,1974,1975,1978,1979,1982],{},"둘째, 진짜 전역 요소(\"Page 3 of 27\")에 대해서는 ",[30,1976,1977],{},"그 범위만"," fix-up 패스로 지연한다. 나머지 내용은 이미 스트림에 있다. fix-up은 짧은 deferred-reference 마커 리스트를 돌며 패치한다. 코드베이스에서 AST 비용에 근접한 비용을 내는 유일한 지점이고, ",[30,1980,1981],{},"실제로 필요한 부분에만"," 낸다.",[19,1984,1985,1986,1989,1990,1992],{},"포기한 것: 노드 트리에 대한 임의의 후처리가 불가능하다. 노드 트리가 없기 때문이다. \"",[47,1987,1988],{},"bold: true","인 ",[47,1991,507],{}," 노드 전부 재정렬\" 같은 플러그인은 쓸 수 없다. 그런 모양의 API가 필요하면 Maroto v2를 쓰면 된다.",[19,1994,1995],{},"gpdf가 타깃으로 하는 용도에는 이 트레이드오프가 옳다고 본다. PDF 대부분은 왼쪽에서 오른쪽으로, 위에서 아래로, 구성 시점에 알려진 레이아웃으로 생성된다. 소수 사례를 위해 AST를 유지하는 비용을 다수가 모든 페이지에서 지불해 왔다. 그 비율을 뒤집었다.",[14,1997,1999],{"id":1998},"결정-2-핫패스에-리플렉션과-interface를-두지-않는다","결정 2: 핫패스에 리플렉션과 interface를 두지 않는다",[19,2001,2002],{},"이야기로는 덜 흥미롭지만 프로파일로 보면 남은 속도 차의 절반이 여기서 나온다.",[19,2004,2005,2006,2009],{},"gofpdf의 ",[47,2007,2008],{},"CellFormat"," 시그니처:",[60,2011,2013],{"className":62,"code":2012,"language":64,"meta":65,"style":65},"func (f *Fpdf) CellFormat(w, h float64, txtStr, borderStr string,\n    ln int, alignStr string, fill bool, link int, linkStr string) { ... }\n",[47,2014,2015,2060],{"__ignoreMap":65},[69,2016,2017,2019,2021,2024,2026,2029,2031,2034,2036,2038,2040,2042,2045,2047,2050,2052,2055,2057],{"class":71,"line":72},[69,2018,171],{"class":75},[69,2020,1599],{"class":75},[69,2022,2023],{"class":442},"f ",[69,2025,1605],{"class":75},[69,2027,2028],{"class":79},"Fpdf",[69,2030,457],{"class":75},[69,2032,2033],{"class":174}," CellFormat",[69,2035,209],{"class":75},[69,2037,1874],{"class":442},[69,2039,191],{"class":75},[69,2041,1879],{"class":442},[69,2043,2044],{"class":1620}," float64",[69,2046,191],{"class":75},[69,2048,2049],{"class":442}," txtStr",[69,2051,191],{"class":75},[69,2053,2054],{"class":442}," borderStr",[69,2056,1621],{"class":1620},[69,2058,2059],{"class":75},",\n",[69,2061,2062,2065,2068,2070,2073,2075,2077,2080,2083,2085,2088,2090,2092,2095,2097,2099,2102,2104],{"class":71,"line":83},[69,2063,2064],{"class":442},"    ln",[69,2066,2067],{"class":1620}," int",[69,2069,191],{"class":75},[69,2071,2072],{"class":442}," alignStr",[69,2074,1621],{"class":1620},[69,2076,191],{"class":75},[69,2078,2079],{"class":442}," fill",[69,2081,2082],{"class":1620}," bool",[69,2084,191],{"class":75},[69,2086,2087],{"class":442}," link",[69,2089,2067],{"class":1620},[69,2091,191],{"class":75},[69,2093,2094],{"class":442}," linkStr",[69,2096,1621],{"class":1620},[69,2098,457],{"class":75},[69,2100,2101],{"class":75}," {",[69,2103,1629],{"class":75},[69,2105,2106],{"class":75}," }\n",[19,2108,2109,2110,2113,2114,2117,2118,2121,2122,2125,2126,2128,2129,2132,2133,2135],{},"문제없다. Maroto의 컴포넌트 트리를 보자. ",[47,2111,2112],{},"Row","는 ",[47,2115,2116],{},"[]Component","를 가진다. ",[47,2119,2120],{},"Component","는 interface다. 레이아웃 연산마다 가상 디스패치: ",[47,2123,2124],{},"component.Render(ctx)",". ",[47,2127,507],{},"와 ",[47,2130,2131],{},"Spacer","가 든 하나의 ",[47,2134,470],{},"이면 세 번의 디스패치. 100페이지 × 페이지당 30줄 × 3개 컴포넌트면 9,000번.",[19,2137,2138],{},"Go interface 디스패치는 회당 2–3 ns 정도. 단독으로 죄는 아니다. 하지만 interface는 컴파일러가 박싱된 값을 힙에 두도록 강제한다 — Go 컴파일러가 항상 해 주지 않는 devirtualization 없이는 interface 너머로 스택 할당이 안 된다. 비용은 디스패치 자체가 아니라 그걸 먹이는 할당이다.",[19,2140,2141],{},"gpdf의 레이아웃 엔진은 구체 구조체를 쓴다:",[60,2143,2145],{"className":62,"code":2144,"language":64,"meta":65,"style":65},"type RowBuilder struct {\n    doc    *Document\n    parent *pageState\n    spans  [12]int\n    cols   [12]ColBuilder  // 값 배열, 포인터도 interface도 아님\n    n      uint8\n}\n\ntype ColBuilder struct {\n    row    *RowBuilder\n    span   int\n    cursor document.Point\n    writer *pdf.Writer\n}\n",[47,2146,2147,2160,2170,2180,2196,2213,2221,2225,2229,2240,2250,2257,2269,2284],{"__ignoreMap":65},[69,2148,2149,2152,2155,2158],{"class":71,"line":72},[69,2150,2151],{"class":75},"type",[69,2153,2154],{"class":79}," RowBuilder",[69,2156,2157],{"class":75}," struct",[69,2159,181],{"class":75},[69,2161,2162,2165,2167],{"class":71,"line":83},[69,2163,2164],{"class":187},"    doc    ",[69,2166,1605],{"class":75},[69,2168,2169],{"class":79},"Document\n",[69,2171,2172,2175,2177],{"class":71,"line":90},[69,2173,2174],{"class":187},"    parent ",[69,2176,1605],{"class":75},[69,2178,2179],{"class":79},"pageState\n",[69,2181,2182,2185,2188,2190,2193],{"class":71,"line":100},[69,2183,2184],{"class":187},"    spans  ",[69,2186,2187],{"class":75},"[",[69,2189,475],{"class":340},[69,2191,2192],{"class":75},"]",[69,2194,2195],{"class":1620},"int\n",[69,2197,2198,2201,2203,2205,2207,2209],{"class":71,"line":112},[69,2199,2200],{"class":187},"    cols   ",[69,2202,2187],{"class":75},[69,2204,475],{"class":340},[69,2206,2192],{"class":75},[69,2208,492],{"class":79},[69,2210,2212],{"class":2211},"sHwdD","  // 값 배열, 포인터도 interface도 아님\n",[69,2214,2215,2218],{"class":71,"line":122},[69,2216,2217],{"class":187},"    n      ",[69,2219,2220],{"class":1620},"uint8\n",[69,2222,2223],{"class":71,"line":127},[69,2224,707],{"class":75},[69,2226,2227],{"class":71,"line":137},[69,2228,87],{"emptyLinePlaceholder":86},[69,2230,2231,2233,2236,2238],{"class":71,"line":147},[69,2232,2151],{"class":75},[69,2234,2235],{"class":79}," ColBuilder",[69,2237,2157],{"class":75},[69,2239,181],{"class":75},[69,2241,2242,2245,2247],{"class":71,"line":157},[69,2243,2244],{"class":187},"    row    ",[69,2246,1605],{"class":75},[69,2248,2249],{"class":79},"RowBuilder\n",[69,2251,2252,2255],{"class":71,"line":163},[69,2253,2254],{"class":187},"    span   ",[69,2256,2195],{"class":1620},[69,2258,2259,2262,2264,2266],{"class":71,"line":168},[69,2260,2261],{"class":187},"    cursor ",[69,2263,321],{"class":79},[69,2265,203],{"class":75},[69,2267,2268],{"class":79},"Point\n",[69,2270,2271,2274,2276,2279,2281],{"class":71,"line":184},[69,2272,2273],{"class":187},"    writer ",[69,2275,1605],{"class":75},[69,2277,2278],{"class":79},"pdf",[69,2280,203],{"class":75},[69,2282,2283],{"class":79},"Writer\n",[69,2285,2286],{"class":71,"line":222},[69,2287,707],{"class":75},[19,2289,2290,2293,2294,203],{},[47,2291,2292],{},"cols","는 그리드 최대 열 수(12)에 맞춘 값 배열. 힙 할당 없음. 행이 컬럼을 순회할 때 interface 디스패치 없음. Builder가 writer의 포인터를 가지고, ",[30,2295,2296],{},"writer는 Builder 트리의 존재를 모른다",[19,2298,2299,2300,2303,2304,2306],{},"콜백 패턴 (",[47,2301,2302],{},"r.Col(4, func(c *ColBuilder) { ... })",")은 우연이 아니다. 프로토타입한 다른 모양 — 체이닝 가능한 struct 반환 API, Component interface의 박싱 트리 — 전부 느렸다. 이 클로저가 제로 할당인 이유는 ",[47,2305,492],{},"가 호출자가 포인터 매개변수로 가진 값이고, 클로저 자체가 대부분 escape analysis로 스택에 올라가기 때문이다.",[1947,2308,2310],{"id":2309},"효과를-확인한-방법","효과를 확인한 방법",[19,2312,2313,2314,2317],{},"gpdf에서 ",[47,2315,2316],{},"go test -run=XXX -bench=BenchmarkSinglePage -memprofile=mem.out",":",[60,2319,2322],{"className":2320,"code":2321,"language":928},[926],"BenchmarkSinglePage-8   91270   13120 ns/op   8321 B/op   52 allocs/op\n",[47,2323,2321],{"__ignoreMap":65},[19,2325,2326,2327,2330,2331,2333],{},"전체 PDF 페이지 하나에 ",[30,2328,2329],{},"52회 할당",". 거의 전부가 초기 페이지 버퍼, 폰트 메트릭스 조회(폰트당 한 번, 글리프당이 아님), 마지막 ",[47,2332,1895],{}," 확장. 레이아웃 루프는 제로 할당 — 프로파일을 보면 보인다.",[19,2335,2336],{},"같은 페이지의 gofpdf:",[60,2338,2341],{"className":2339,"code":2340,"language":928},[926],"BenchmarkGofpdfSinglePage-8   7500   132400 ns/op   71200 B/op   430 allocs/op\n",[47,2342,2340],{"__ignoreMap":65},[19,2344,2345],{},"430회 할당. 대부분 연산 슬라이스와 그걸 채우는 문자열 복사. 할당 약 8배 차이가 GC를 거쳐 실행 시간 약 10배 차이로 이어지는 건 자연스러운 귀결.",[1947,2347,2349],{"id":2348},"포기한-것","포기한 것",[19,2351,2352,2353,2356,2357,2359,2360,2363],{},"핫패스 에르고노믹스 제로는 곧 ",[30,2354,2355],{},"확장 지점이 적다","는 뜻이다. gpdf 레이아웃에 플러그인되는 커스텀 요소 타입 — Maroto에서 ",[47,2358,2120],{},"를 구현하는 것과 같은 일 — 은 쓸 수 없다. 만족시킬 interface가 없다. 대신 제공하는 것은 ",[47,2361,2362],{},"template.WithWriterSetup()","이다. PDF writer에 대한 훅으로 커스텀 어노테이션, PDF/A 메타데이터, 암호화 등을 주입할 수 있다. 레이아웃 레벨 확장은 사용자가 호출하는 것과 동일한 Builder 메서드를 호출하는 헬퍼 함수로 작성한다.",[19,2365,2366],{},"확장 지점이 적은 것은 진짜 비용이다. 현재 판단에서는 균형이 맞는다. 프로젝트 방향이 바뀌어 그 판단이 성립하지 않으면 재검토한다.",[14,2368,2370],{"id":2369},"결정-3-재주행하지-않는-truetype-서브세터","결정 3: 재주행하지 않는 TrueType 서브세터",[19,2372,2373],{},"CJK 벤치마크(gpdf 133 µs 대 gofpdf 254 µs)의 격차 대부분이 여기서 나온다.",[19,2375,2376,2377,2380],{},"TrueType 서브세팅이 하는 일 요약. PDF에 일본어 폰트를 임베드할 때 20,000+ 글리프 전부를 넣고 싶지 않다 — 100 KB 문서에 15 MB 폰트 데이터다. ",[30,2378,2379],{},"문서가 실제로 쓰는 글리프만"," PDF 리더가 디코드할 수 있는 유효한 서브셋 TTF로 패키징하고 싶다.",[19,2382,2383],{},"절차:",[883,2385,2386,2401,2404,2407],{},[886,2387,2388,2389,2391,2392,2394,2395,2397,2398,2400],{},"완전한 TTF 테이블 파싱: ",[47,2390,860],{}," (문자→글리프 매핑), ",[47,2393,853],{}," (아웃라인), ",[47,2396,857],{}," (glyf 오프셋), ",[47,2399,735],{}," (수평 메트릭) 등.",[886,2402,2403],{},"문서의 각 문자에 대해 cmap으로 글리프 ID 조회.",[886,2405,2406],{},"합성 글리프가 참조하는 하위 글리프를 추이적으로 수집.",[886,2408,2409],{},"그 글리프만 번호를 새로 매긴 TTF 출력.",[19,2411,2412,2413,2416,2417,2420],{},"핫패스는 2단계 — ",[30,2414,2415],{},"cmap 조회",". gofpdf 구현은 글리프 조회마다 cmap 테이블을 ",[30,2418,2419],{},"맨 위부터 걷는다",". Latin만 쓰는 페이지는 문제없다 — cmap이 작고 캐시가 잘 돈다. 150개 고유 글리프가 있는 CJK 페이지는 테이블 전체 주행을 150번 한다.",[19,2422,2423],{},"cmap format 12 (대부분의 현대 CJK 폰트가 사용)는 (start, end, startGlyphID) 트리플을 정렬한 배열. 1회 주행은 범위 수에 대해 O(n), NotoSansJP의 경우 200–500 범위. 150회 조회 × 400 범위당 비교 = 필요한 것보다 훨씬 많은 일.",[19,2425,2426,2427,2430],{},"gpdf는 폰트 최초 로드 시 cmap 전체를 ",[47,2428,2429],{},"map[rune]uint16","으로 풀어낸다. 이후 모든 조회는 O(1). NotoSansJP의 경우 일회성 비용 약 150 µs, 이후 문자당 10 ns.",[60,2432,2434],{"className":62,"code":2433,"language":64,"meta":65,"style":65},"// pdf/font/ttf.go 단순화\ntype Font struct {\n    runeToGID map[rune]uint16  // 로드 시 1회 해석\n    glyphs    []glyph          // GID로 인덱싱\n    metrics   []glyphMetric\n}\n\nfunc (f *Font) GlyphFor(r rune) uint16 {\n    return f.runeToGID[r]  // O(1), 캐시 친화적, 테이블 주행 없음\n}\n",[47,2435,2436,2441,2452,2471,2485,2495,2499,2503,2534,2556],{"__ignoreMap":65},[69,2437,2438],{"class":71,"line":72},[69,2439,2440],{"class":2211},"// pdf/font/ttf.go 단순화\n",[69,2442,2443,2445,2448,2450],{"class":71,"line":83},[69,2444,2151],{"class":75},[69,2446,2447],{"class":79}," Font",[69,2449,2157],{"class":75},[69,2451,181],{"class":75},[69,2453,2454,2457,2460,2463,2465,2468],{"class":71,"line":90},[69,2455,2456],{"class":187},"    runeToGID ",[69,2458,2459],{"class":75},"map[",[69,2461,2462],{"class":1620},"rune",[69,2464,2192],{"class":75},[69,2466,2467],{"class":1620},"uint16",[69,2469,2470],{"class":2211},"  // 로드 시 1회 해석\n",[69,2472,2473,2476,2479,2482],{"class":71,"line":100},[69,2474,2475],{"class":187},"    glyphs    ",[69,2477,2478],{"class":75},"[]",[69,2480,2481],{"class":79},"glyph",[69,2483,2484],{"class":2211},"          // GID로 인덱싱\n",[69,2486,2487,2490,2492],{"class":71,"line":112},[69,2488,2489],{"class":187},"    metrics   ",[69,2491,2478],{"class":75},[69,2493,2494],{"class":79},"glyphMetric\n",[69,2496,2497],{"class":71,"line":122},[69,2498,707],{"class":75},[69,2500,2501],{"class":71,"line":127},[69,2502,87],{"emptyLinePlaceholder":86},[69,2504,2505,2507,2509,2511,2513,2515,2517,2520,2522,2524,2527,2529,2532],{"class":71,"line":137},[69,2506,171],{"class":75},[69,2508,1599],{"class":75},[69,2510,2023],{"class":442},[69,2512,1605],{"class":75},[69,2514,1768],{"class":79},[69,2516,457],{"class":75},[69,2518,2519],{"class":174}," GlyphFor",[69,2521,209],{"class":75},[69,2523,443],{"class":442},[69,2525,2526],{"class":1620}," rune",[69,2528,457],{"class":75},[69,2530,2531],{"class":1620}," uint16",[69,2533,181],{"class":75},[69,2535,2536,2539,2542,2544,2547,2549,2551,2553],{"class":71,"line":147},[69,2537,2538],{"class":93},"    return",[69,2540,2541],{"class":187}," f",[69,2543,203],{"class":75},[69,2545,2546],{"class":187},"runeToGID",[69,2548,2187],{"class":75},[69,2550,443],{"class":187},[69,2552,2192],{"class":75},[69,2554,2555],{"class":2211},"  // O(1), 캐시 친화적, 테이블 주행 없음\n",[69,2557,2558],{"class":71,"line":157},[69,2559,707],{"class":75},[19,2561,2562],{},"rune으로 인덱싱된 맵 하나, cmap 테이블의 선형 스캔 한 번으로 구성. 같은 폰트를 여러 페이지(보통 전 페이지)에 쓰는 문서에서 글리프 조회가 \"페이지 × 글리프의 거의 이차\"에서 \"총 글리프 수 + 상수\"로 바뀐다.",[1947,2564,2566],{"id":2565},"왜-format-12가-중요한가","왜 \"format 12\"가 중요한가",[19,2568,2569,2570,901,2572,2575,2576,2579],{},"많은 오래된 Go PDF 라이브러리는 Latin만 신경 쓰던 시절에 작성되었고, 구현한 cmap은 format 4 — Basic Multilingual Plane (U+0000–U+FFFF) 분할 범위. BMP 바깥의 일본어(드물지만 일부 이체 Kanji)는 format 12가 필요. ",[47,2571,1434],{},[47,2573,2574],{},"AddUTF8Font","는 NotoSansJP-Regular.ttf에서 ",[30,2577,2578],{},"panic","한다. format 12 파서가 끝까지 작성되지 않아서다.",[19,2581,2582],{},"이건 비방이 아니다. 유물이다. gofpdf는 2015년경 Latin 중심 웹 앱에 필요한 것으로서 훌륭한 라이브러리였고, 포크는 그 스코프를 그대로 물려받았다. 세상이 바뀌었다. CJK는 \"다른 사람 문제\"에서 \"일본어와 중국어 Go 생태계 다수의 문제\"가 되었다. gpdf는 cmap 명세를 완전히 구현했다. 하지 않으면 \"品目\"에 두부 박스가 박힌 인보이스가 생긴다 — 공개 첫 주에 실제 들어온 버그 리포트다.",[1947,2584,2586],{"id":2585},"문서-수가-아닌-폰트-수로-스케일하는-캐시","문서 수가 아닌 폰트 수로 스케일하는 캐시",[19,2588,2589,2590,2593,2594,2596,2597,2600],{},"폰트 캐시는 ",[47,2591,2592],{},"Document","별이고 전역이 아니다. 같은 폰트로 PDF 10,000개 생성하면 150 µs 해석 비용을 10,000번 낸다 — 문서 간 ",[47,2595,1768],{}," 인스턴스를 공유하지 않는 이상. API는 ",[47,2598,2599],{},"gpdf.WithSharedFont(preloadedFont)","를 지원.",[19,2602,2603,2604,2607],{},"고처리량 배치 생성(SaaS ",[47,2605,2606],{},"gpdf-api","가 이 방식)에서 공유 폰트 패턴이 P95 레이턴시를 예측 가능하게 만든다. 문서에 있다. OSS 사용자 대부분은 필요 없다.",[14,2609,2611],{"id":2610},"결합된-효과","결합된 효과",[19,2613,2614],{},"세 결정을 100페이지 벤치(gpdf 683 µs, gofpdf 11.7 ms)에 대입:",[741,2616,2617,2630],{},[744,2618,2619],{},[747,2620,2621,2624,2627],{},[750,2622,2623],{},"시간의 출처",[750,2625,2626],{},"gofpdf (페이지당 개략)",[750,2628,2629],{},"gpdf (페이지당 개략)",[759,2631,2632,2643,2654,2665,2676],{},[747,2633,2634,2637,2640],{},[764,2635,2636],{},"연산 슬라이스 구축",[764,2638,2639],{},"약 60 µs",[764,2641,2642],{},"0 (스트림 직접)",[747,2644,2645,2648,2651],{},[764,2646,2647],{},"연산 직렬화",[764,2649,2650],{},"약 35 µs",[764,2652,2653],{},"0 (이미 기록됨)",[747,2655,2656,2659,2662],{},[764,2657,2658],{},"글리프 조회 (40자)",[764,2660,2661],{},"약 6 µs",[764,2663,2664],{},"약 0.4 µs",[747,2666,2667,2670,2673],{},[764,2668,2669],{},"할당 / GC 압박",[764,2671,2672],{},"약 20 µs",[764,2674,2675],{},"약 2 µs",[747,2677,2678,2683,2688],{},[764,2679,2680],{},[30,2681,2682],{},"합계",[764,2684,2685],{},[30,2686,2687],{},"약 120 µs",[764,2689,2690],{},[30,2691,2692],{},"약 7 µs",[19,2694,2695,2696,2699],{},"숫자는 프로파일 추정이고 실제 분해는 내용에 따른다. 모양은 맞다. ",[30,2697,2698],{},"세 가지 중 어느 하나도 단독으로 10배를 내지 못한다",". 쌓여서 10배가 된다.",[19,2701,2702],{},"따라서: 기존 라이브러리에 하나만 복사해도 2–3배는 얻을 수 있다. 10배를 원하면 셋 다 필요하고, 첫 번째(단일 패스)를 AST 기반 라이브러리에 뒤늦게 넣는 건 재작성 말고는 길이 없다.",[14,2704,2706],{"id":2705},"포기한-것-정직한-섹션","포기한 것 (정직한 섹션)",[19,2708,2709],{},"빙빙 돌려 말했다. 전부 나열:",[19,2711,2712,2715,2716,2719],{},[30,2713,2714],{},"AST 기반 후처리."," 플러그인 아키텍처 없음. \"노드 트리 돌며 변환 적용\" 없음. 렌더링 전에 문서 전체 텍스트 스타일을 일괄 편집하고 싶으면 Builder 호출 ",[30,2717,2718],{},"전에"," 한다.",[19,2721,2722,897,2725,2728],{},[30,2723,2724],{},"인트로스펙션.",[47,2726,2727],{},"doc.Components()","로 넣은 걸 돌려주는 메서드는 없다. 의미 있는 메서드가 돌아갈 수 있는 시점엔 문서가 이미 연산자 스트림이다. 대부분 사용자는 쓸 일 없다. 문서 조작 도구를 만드는 소수 사용자는 쓴다.",[19,2730,2731,2734,2735,2738,2739,2742],{},[30,2732,2733],{},"리플렉션 기반 직렬화."," 임의 struct를 PDF로 바꾸는 ",[47,2736,2737],{},"json.Unmarshal"," 스타일 API는 없다. JSON Schema 진입점(",[47,2740,2741],{},"template.FromJSON",")은 지원 형태를 명시한다. 의도적. 임의 Go struct를 넣어서 PDF를 받는 API가 필요하면 unidoc 영역.",[19,2744,2745,897,2748,2750],{},[30,2746,2747],{},"interface의 확장성.",[47,2749,2120],{},"를 구현해 커스텀 요소를 등록할 수 없다. Builder 호출을 감싸는 헬퍼 함수는 쓸 수 있다. 실용상 95% 요구를 커버하지만 모델이 다르다.",[19,2752,2753],{},"전부 의도한 결과다. 하나라도 채택하면 속도가 죽는다. \"빠르고 고집 있는 것\"이 맞는 사용자 버킷을 우선하고, \"유연하고 플러그인 풍부\"가 필요한 버킷은 Maroto v2나 unidoc이 더 맞는다.",[14,2755,2757],{"id":2756},"벤치-재현-가능한가","벤치 재현 가능한가",[19,2759,2760],{},"가능하다. 코드를 공개한 목적이 바로 그것이다.",[60,2762,2764],{"className":1250,"code":2763,"language":1252,"meta":65,"style":65},"git clone https://github.com/gpdf-dev/gpdf\ncd gpdf/_benchmark\ngo test -bench=. -benchmem -benchtime=5s\n",[47,2765,2766,2777,2785],{"__ignoreMap":65},[69,2767,2768,2771,2774],{"class":71,"line":72},[69,2769,2770],{"class":79},"git",[69,2772,2773],{"class":215}," clone",[69,2775,2776],{"class":215}," https://github.com/gpdf-dev/gpdf\n",[69,2778,2779,2782],{"class":71,"line":83},[69,2780,2781],{"class":174},"cd",[69,2783,2784],{"class":215}," gpdf/_benchmark\n",[69,2786,2787,2789,2792,2795,2798],{"class":71,"line":90},[69,2788,64],{"class":79},[69,2790,2791],{"class":215}," test",[69,2793,2794],{"class":215}," -bench=.",[69,2796,2797],{"class":215}," -benchmem",[69,2799,2800],{"class":215}," -benchtime=5s\n",[19,2802,2803,2804,2809],{},"해당 디렉토리 README에 네 가지 워크로드와 측정 내용이 있다. 같은 CPU 아키텍처, 같은 Go 버전에서 20% 이상 차이가 나면 ",[22,2805,2808],{"href":2806,"rel":2807},"https://github.com/gpdf-dev/gpdf/issues",[26],"이슈","를 열어 주기 바란다 — drift는 실재한다.",[19,2811,2812],{},"두 가지 단서:",[1113,2814,2815,2821],{},[886,2816,2817,2818,2820],{},"벤치는 ",[47,2819,1416],{},"와 함께 돈다. 끄면 전반적으로 약 5% 향상되지만, 실제 코드 운용 방식이 아니라 공개 수치에는 넣지 않는다.",[886,2822,2823],{},"CGO 비활성. CGO로 FreeType 백엔드를 붙이면 폰트 연산이 빨라질지 질문받아 실험했다. FFI 경계의 마샬링 비용이 이득을 삼켰다. PDF 생성기의 접근 패턴에는 순수 Go 서브세터가 이긴다.",[14,2825,2827],{"id":2826},"faq","FAQ",[19,2829,2830,2833,2834,2838],{},[30,2831,2832],{},"왜 아카이브된 gofpdf와 비교하나?","\n여전히 GitHub \"go pdf\" 검색 1위이고, gpdf로 착지하는 팀 대부분이 거기서 이주해 오기 때문. 벤치는 이 청중에 \"이주할 가치가 있나\"에 답해야 한다. 짧은 답: 있다. ",[22,2835,2837],{"href":2836},"/ko/blog/gofpdf-migration","이주 가이드","도 있다.",[19,2840,2841,2844],{},[30,2842,2843],{},"PDF 생성에서 10배 빠른 게 실질적으로 의미 있나?","\n워크로드에 따라. 요청당 한 문서면 — 딱히 없다, 양쪽 다 \"요청 경로에서 생성\" 임계를 넘는다. 배치(야간 명세, 대량 인보이스, DB 쿼리 기반 보고서 생성)에서는 격차가 그대로 머신 수 감소로 번역된다. 배치 파이프라인을 처음 이주한 팀에서 \"워커 수가 10분의 1\"이라는 피드백을 들었고, 계산을 감사하지 않았지만 벤치 모양과 정합한다.",[19,2846,2847,2850,2851,2854,2855,2857],{},[30,2848,2849],{},"CJK 숫자의 함정은?","\n폰트 파일은 직접 실어야 한다. gpdf가 서브세팅해 주지만 3 MB NotoSansJP TTF는 3 MB다. Go 바이너리에 임베드하거나 기동 시 ",[47,2852,2853],{},"os.ReadFile"," 한다. distroless 이미지에서는 영향을 준다. SaaS ",[47,2856,2606],{},"는 이미지에 대표 폰트를 동봉해 해결. OSS 사용자는 직접 다룬다.",[19,2859,2860,2863],{},[30,2861,2862],{},"기능이 늘면 느려지나?","\n가장 신경 쓰는 질문. 답: 릴리스마다 이전 버전과 벤치마크를 재고, 네 워크로드 중 하나라도 5% 이상 악화되면 릴리스를 막는다. 벤치가 라이브러리와 같은 리포지토리에 있는 이유가 바로 그것이다.",[19,2865,2866,2869],{},[30,2867,2868],{},"이름의 유래는?","\ngpdf = Go + PDF. 영리할 것 없다. 의도적으로 단순.",[14,2871,2873],{"id":2872},"gpdf를-써-본다","gpdf를 써 본다",[19,2875,2876],{},"gpdf는 PDF를 생성하는 Go 라이브러리다. MIT, 제로 의존성, 네이티브 CJK.",[60,2878,2879],{"className":1250,"code":1251,"language":1252,"meta":65,"style":65},[47,2880,2881],{"__ignoreMap":65},[69,2882,2883,2885,2887],{"class":71,"line":72},[69,2884,64],{"class":79},[69,2886,1261],{"class":215},[69,2888,1264],{"class":215},[19,2890,2891,1271,2895],{},[22,2892,2894],{"href":24,"rel":2893},[26],"⭐ Star on GitHub",[22,2896,2898],{"href":1274,"rel":2897},[26],"문서",[14,2900,2902],{"id":2901},"다음에-읽을-것","다음에 읽을 것",[1113,2904,2905,2912,2918],{},[886,2906,2907,2911],{},[22,2908,2910],{"href":2909},"/ko/blog/go-pdf-library-showdown-2026","2026 Go PDF 라이브러리 비교"," — 라이선스와 의존 포함 전체 라이브러리 비교.",[886,2913,2914,2917],{},[22,2915,2916],{"href":2836},"gofpdf는 아카이브됐다. gpdf로 이주하는 법"," — Before/After API 다섯 쌍, 전부 실행 가능.",[886,2919,2920,2921,203],{},"벤치마크 코드: ",[22,2922,2924],{"href":1394,"rel":2923},[26],[47,2925,1398],{},[1278,2927,2928],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}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 pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}",{"title":65,"searchDepth":83,"depth":83,"links":2930},[2931,2932,2933,2936,2940,2944,2945,2946,2947,2948,2949],{"id":1337,"depth":83,"text":1338},{"id":1409,"depth":83,"text":1410},{"id":1553,"depth":83,"text":1554,"children":2934},[2935],{"id":1949,"depth":90,"text":1950},{"id":1998,"depth":83,"text":1999,"children":2937},[2938,2939],{"id":2309,"depth":90,"text":2310},{"id":2348,"depth":90,"text":2349},{"id":2369,"depth":83,"text":2370,"children":2941},[2942,2943],{"id":2565,"depth":90,"text":2566},{"id":2585,"depth":90,"text":2586},{"id":2610,"depth":83,"text":2611},{"id":2705,"depth":83,"text":2706},{"id":2756,"depth":83,"text":2757},{"id":2826,"depth":83,"text":2827},{"id":2872,"depth":83,"text":2873},{"id":2901,"depth":83,"text":2902},"단일 페이지 13 µs, 100페이지 보고서 683 µs. 튜닝이 아니라 세 가지 아키텍처 결정이 쌓인 결과. 실제 코드 경로를 짚어본다.",{},"/ko/blog/why-gpdf-is-faster",{"title":1331,"description":2950},"ko/blog/011.why-gpdf-is-faster",[2956,2957,2958],"benchmark","internals","comparison","vVcrmS2v5RV6IoJxeTe6TGcD31_YkrHCVVwFlaO6j2E",{"id":2961,"title":2962,"author":2963,"body":2964,"date":4025,"description":4026,"draft":1295,"extension":1296,"howTo":4027,"image":1319,"meta":4047,"navigation":86,"path":1238,"seo":4048,"stem":4049,"tags":4050,"updated":1319,"__hash__":4052},"blogKo/ko/blog/008.tofu-boxes-japanese.md","gpdf로 만든 PDF에서 일본어가 네모 (두부 문자) 로 나오는 이유와 해결법",{"name":8,"url":9},{"type":11,"value":2965,"toc":4013},[2966,2968,2971,2973,2976,2979,3030,3034,3037,3507,3520,3530,3534,3537,3553,3572,3578,3582,3590,3731,3762,3766,3773,3776,3779,3807,3810,3814,3820,3884,3895,3898,3931,3935,3958,3960,3986,3988,3990,4002,4010],[14,2967,17],{"id":16},[19,2969,2970],{},"gpdf로 일본어를 썼는데 출력된 PDF에서 그 문자들이 전부 빈 네모로 나온다. 이게 뭐고, 어떻게 해야 실제 일본어 글리프가 파일 안에 들어가는가.",[14,2972,37],{"id":36},[19,2974,2975],{},"이것이 두부 문자(tofu)다. PDF 뷰어가 임베드된 폰트에서 해당 유니코드 코드포인트의 글리프를 찾지 못해 자리 표시용 사각형을 그리는 것. 원인은 네 가지이고, 그중 하나가 압도적으로 많다.",[19,2977,2978],{},"빈도 순:",[883,2980,2981,2993,3010,3020],{},[886,2982,2983,897,2986,2989,2990,2992],{},[30,2984,2985],{},"CJK 폰트를 등록하지 않았다.",[47,2987,2988],{},"gpdf.NewDocument","에 ",[47,2991,354],{}," 호출이 없어서 문서가 PDF Base-14 폰트(Helvetica / Times / Courier)로 폴백한 상태. 이들 중 어느 것도 U+3040–U+9FFF를 커버하지 않는다.",[886,2994,2995,897,3002,3005,3006,3009],{},[30,2996,2997,2998,3001],{},"CJK 폰트는 등록했는데 ",[47,2999,3000],{},"c.Text","의 패밀리명이 다르다.",[47,3003,3004],{},"WithFont(\"NotoSansJP\", ...)","는 설정했지만 텍스트에 ",[47,3007,3008],{},"template.FontFamily(\"Arial\")","가 지정되어 있어서, gpdf가 Latin 폰트에서 일본어를 조회한다.",[886,3011,3012,3015,3016,3019],{},[30,3013,3014],{},"폰트 파일 자체에 CJK 글리프가 없다."," 디스크의 TTF가 Latin 서브셋(",[47,3017,3018],{},"NotoSans-Regular.ttf",")이다. 이름은 맞아 보이지만 커버리지가 비어 있다.",[886,3021,3022,3025,3026,3029],{},[30,3023,3024],{},"gpdf에 도달하기 전에 바이트가 깨졌다."," 문자열이 상류에서 Shift-JIS나 Latin-1로 디코딩돼, 렌더링하려는 것이 이미 일본어 코드포인트가 아니다. 네모가 아니라 ",[47,3027,3028],{},"縺ゅ→縺","처럼 나오면 이 경우다.",[14,3031,3033],{"id":3032},"원인-1의-표준-수정","원인 #1의 표준 수정",[19,3035,3036],{},"열에 아홉은 이것이다:",[60,3038,3040],{"className":62,"code":3039,"language":64,"meta":65,"style":65},"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/template\"\n)\n\nfunc main() {\n    font, err := os.ReadFile(\"NotoSansJP-Regular.ttf\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(gpdf.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n        gpdf.WithFont(\"NotoSansJP\", font),\n        gpdf.WithDefaultFont(\"NotoSansJP\", 12),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"こんにちは、世界。\")\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"hello.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[47,3041,3042,3048,3052,3058,3066,3074,3078,3086,3094,3102,3106,3110,3120,3147,3159,3173,3177,3181,3195,3213,3243,3266,3289,3293,3297,3311,3335,3365,3384,3388,3392,3396,3414,3426,3440,3444,3485,3499,3503],{"__ignoreMap":65},[69,3043,3044,3046],{"class":71,"line":72},[69,3045,76],{"class":75},[69,3047,80],{"class":79},[69,3049,3050],{"class":71,"line":83},[69,3051,87],{"emptyLinePlaceholder":86},[69,3053,3054,3056],{"class":71,"line":90},[69,3055,94],{"class":93},[69,3057,97],{"class":75},[69,3059,3060,3062,3064],{"class":71,"line":100},[69,3061,103],{"class":75},[69,3063,106],{"class":79},[69,3065,109],{"class":75},[69,3067,3068,3070,3072],{"class":71,"line":112},[69,3069,103],{"class":75},[69,3071,117],{"class":79},[69,3073,109],{"class":75},[69,3075,3076],{"class":71,"line":122},[69,3077,87],{"emptyLinePlaceholder":86},[69,3079,3080,3082,3084],{"class":71,"line":127},[69,3081,103],{"class":75},[69,3083,132],{"class":79},[69,3085,109],{"class":75},[69,3087,3088,3090,3092],{"class":71,"line":137},[69,3089,103],{"class":75},[69,3091,142],{"class":79},[69,3093,109],{"class":75},[69,3095,3096,3098,3100],{"class":71,"line":147},[69,3097,103],{"class":75},[69,3099,152],{"class":79},[69,3101,109],{"class":75},[69,3103,3104],{"class":71,"line":157},[69,3105,160],{"class":75},[69,3107,3108],{"class":71,"line":163},[69,3109,87],{"emptyLinePlaceholder":86},[69,3111,3112,3114,3116,3118],{"class":71,"line":168},[69,3113,171],{"class":75},[69,3115,175],{"class":174},[69,3117,178],{"class":75},[69,3119,181],{"class":75},[69,3121,3122,3124,3126,3128,3130,3132,3134,3136,3138,3140,3143,3145],{"class":71,"line":184},[69,3123,188],{"class":187},[69,3125,191],{"class":75},[69,3127,194],{"class":187},[69,3129,197],{"class":75},[69,3131,200],{"class":187},[69,3133,203],{"class":75},[69,3135,206],{"class":174},[69,3137,209],{"class":75},[69,3139,212],{"class":75},[69,3141,3142],{"class":215},"NotoSansJP-Regular.ttf",[69,3144,212],{"class":75},[69,3146,160],{"class":75},[69,3148,3149,3151,3153,3155,3157],{"class":71,"line":222},[69,3150,225],{"class":93},[69,3152,194],{"class":187},[69,3154,230],{"class":75},[69,3156,233],{"class":75},[69,3158,181],{"class":75},[69,3160,3161,3163,3165,3167,3169,3171],{"class":71,"line":238},[69,3162,241],{"class":187},[69,3164,203],{"class":75},[69,3166,246],{"class":174},[69,3168,209],{"class":75},[69,3170,251],{"class":187},[69,3172,160],{"class":75},[69,3174,3175],{"class":71,"line":256},[69,3176,259],{"class":75},[69,3178,3179],{"class":71,"line":262},[69,3180,87],{"emptyLinePlaceholder":86},[69,3182,3183,3185,3187,3189,3191,3193],{"class":71,"line":267},[69,3184,270],{"class":187},[69,3186,197],{"class":75},[69,3188,275],{"class":187},[69,3190,203],{"class":75},[69,3192,280],{"class":174},[69,3194,283],{"class":75},[69,3196,3197,3199,3201,3203,3205,3207,3209,3211],{"class":71,"line":286},[69,3198,289],{"class":187},[69,3200,203],{"class":75},[69,3202,294],{"class":174},[69,3204,209],{"class":75},[69,3206,27],{"class":187},[69,3208,203],{"class":75},[69,3210,303],{"class":187},[69,3212,306],{"class":75},[69,3214,3215,3217,3219,3221,3223,3225,3227,3229,3231,3233,3235,3237,3239,3241],{"class":71,"line":309},[69,3216,289],{"class":187},[69,3218,203],{"class":75},[69,3220,316],{"class":174},[69,3222,209],{"class":75},[69,3224,321],{"class":187},[69,3226,203],{"class":75},[69,3228,326],{"class":174},[69,3230,209],{"class":75},[69,3232,321],{"class":187},[69,3234,203],{"class":75},[69,3236,335],{"class":174},[69,3238,209],{"class":75},[69,3240,341],{"class":340},[69,3242,344],{"class":75},[69,3244,3245,3247,3249,3251,3253,3255,3258,3260,3262,3264],{"class":71,"line":347},[69,3246,289],{"class":187},[69,3248,203],{"class":75},[69,3250,354],{"class":174},[69,3252,209],{"class":75},[69,3254,212],{"class":75},[69,3256,3257],{"class":215},"NotoSansJP",[69,3259,212],{"class":75},[69,3261,191],{"class":75},[69,3263,368],{"class":187},[69,3265,306],{"class":75},[69,3267,3268,3270,3272,3274,3276,3278,3280,3282,3284,3287],{"class":71,"line":373},[69,3269,289],{"class":187},[69,3271,203],{"class":75},[69,3273,380],{"class":174},[69,3275,209],{"class":75},[69,3277,212],{"class":75},[69,3279,3257],{"class":215},[69,3281,212],{"class":75},[69,3283,191],{"class":75},[69,3285,3286],{"class":340}," 12",[69,3288,306],{"class":75},[69,3290,3291],{"class":71,"line":398},[69,3292,401],{"class":75},[69,3294,3295],{"class":71,"line":404},[69,3296,87],{"emptyLinePlaceholder":86},[69,3298,3299,3301,3303,3305,3307,3309],{"class":71,"line":409},[69,3300,412],{"class":187},[69,3302,197],{"class":75},[69,3304,417],{"class":187},[69,3306,203],{"class":75},[69,3308,422],{"class":174},[69,3310,425],{"class":75},[69,3312,3313,3315,3317,3319,3321,3323,3325,3327,3329,3331,3333],{"class":71,"line":428},[69,3314,431],{"class":187},[69,3316,203],{"class":75},[69,3318,436],{"class":174},[69,3320,439],{"class":75},[69,3322,443],{"class":442},[69,3324,446],{"class":75},[69,3326,449],{"class":79},[69,3328,203],{"class":75},[69,3330,454],{"class":79},[69,3332,457],{"class":75},[69,3334,181],{"class":75},[69,3336,3337,3339,3341,3343,3345,3347,3349,3351,3353,3355,3357,3359,3361,3363],{"class":71,"line":462},[69,3338,465],{"class":187},[69,3340,203],{"class":75},[69,3342,470],{"class":174},[69,3344,209],{"class":75},[69,3346,475],{"class":340},[69,3348,191],{"class":75},[69,3350,480],{"class":75},[69,3352,483],{"class":442},[69,3354,446],{"class":75},[69,3356,449],{"class":79},[69,3358,203],{"class":75},[69,3360,492],{"class":79},[69,3362,457],{"class":75},[69,3364,181],{"class":75},[69,3366,3367,3369,3371,3373,3375,3377,3380,3382],{"class":71,"line":499},[69,3368,502],{"class":187},[69,3370,203],{"class":75},[69,3372,507],{"class":174},[69,3374,209],{"class":75},[69,3376,212],{"class":75},[69,3378,3379],{"class":215},"こんにちは、世界。",[69,3381,212],{"class":75},[69,3383,160],{"class":75},[69,3385,3386],{"class":71,"line":547},[69,3387,570],{"class":75},[69,3389,3390],{"class":71,"line":567},[69,3391,576],{"class":75},[69,3393,3394],{"class":71,"line":573},[69,3395,87],{"emptyLinePlaceholder":86},[69,3397,3398,3400,3402,3404,3406,3408,3410,3412],{"class":71,"line":579},[69,3399,587],{"class":187},[69,3401,191],{"class":75},[69,3403,194],{"class":187},[69,3405,197],{"class":75},[69,3407,417],{"class":187},[69,3409,203],{"class":75},[69,3411,600],{"class":174},[69,3413,425],{"class":75},[69,3415,3416,3418,3420,3422,3424],{"class":71,"line":584},[69,3417,225],{"class":93},[69,3419,194],{"class":187},[69,3421,230],{"class":75},[69,3423,233],{"class":75},[69,3425,181],{"class":75},[69,3427,3428,3430,3432,3434,3436,3438],{"class":71,"line":605},[69,3429,241],{"class":187},[69,3431,203],{"class":75},[69,3433,246],{"class":174},[69,3435,209],{"class":75},[69,3437,251],{"class":187},[69,3439,160],{"class":75},[69,3441,3442],{"class":71,"line":618},[69,3443,259],{"class":75},[69,3445,3446,3448,3450,3452,3454,3456,3458,3460,3462,3465,3467,3469,3471,3473,3475,3477,3479,3481,3483],{"class":71,"line":633},[69,3447,225],{"class":93},[69,3449,194],{"class":187},[69,3451,197],{"class":75},[69,3453,200],{"class":187},[69,3455,203],{"class":75},[69,3457,651],{"class":174},[69,3459,209],{"class":75},[69,3461,212],{"class":75},[69,3463,3464],{"class":215},"hello.pdf",[69,3466,212],{"class":75},[69,3468,191],{"class":75},[69,3470,665],{"class":187},[69,3472,191],{"class":75},[69,3474,670],{"class":340},[69,3476,673],{"class":75},[69,3478,194],{"class":187},[69,3480,230],{"class":75},[69,3482,233],{"class":75},[69,3484,181],{"class":75},[69,3486,3487,3489,3491,3493,3495,3497],{"class":71,"line":638},[69,3488,241],{"class":187},[69,3490,203],{"class":75},[69,3492,246],{"class":174},[69,3494,209],{"class":75},[69,3496,251],{"class":187},[69,3498,160],{"class":75},[69,3500,3501],{"class":71,"line":684},[69,3502,259],{"class":75},[69,3504,3505],{"class":71,"line":699},[69,3506,707],{"class":75},[19,3508,3509,3510,3512,3513,3516,3517,3519],{},"두 줄로 폰트 등록과 기본 설정이 끝난다. CGO도, ",[47,3511,2574],{}," 설정 단계도 필요 없다. ",[47,3514,3515],{},"□□□□□、□□。","로 나오던 것이 이 코드와 실제 ",[47,3518,3142],{},"를 나란히 두고 실행하면 제대로 된 글리프로 나온다.",[19,3521,3522,2113,3524,3529],{},[47,3523,3142],{},[22,3525,3528],{"href":3526,"rel":3527},"https://fonts.google.com/noto/specimen/Noto+Sans+JP",[26],"Google Fonts","에서 받는다.",[14,3531,3533],{"id":3532},"어떤-원인인지-판별하기","어떤 원인인지 판별하기",[19,3535,3536],{},"봐야 할 곳은 세 군데다. 문서를 만드는 부분, 텍스트를 쓰는 부분, TTF 파일 그 자체.",[19,3538,3539,3545,3546,3549,3550,3552],{},[30,3540,3541,3542],{},"출력이 일관된 ",[47,3543,3544],{},"□□□"," (모든 네모가 같은 모양)이라면 원인 1, 2, 3 중 하나. PDF에는 폰트가 임베드됐지만 그 폰트에 글리프가 없는 상태다. Acrobat에서 PDF를 열고 ",[47,3547,3548],{},"파일 → 속성 → 폰트","에서 실제로 임베드된 폰트를 본다. Helvetica / Times / Courier뿐이면 원인 1. ",[47,3551,3257],{},"가 나열돼 있는데도 네모면 원인 2 또는 3.",[19,3554,3555,3564,3565,3567,3568,3571],{},[30,3556,3557,3558,3560,3561],{},"출력이 ",[47,3559,3028],{},"이나 ",[47,3562,3563],{},"ã\"ã‚\"ã«ã¡ã¯"," 같은 Latin 잡음이면 원인 4. 일본어 문자열이 gpdf에 도달하기 전에 다시 인코딩됐다. 가장 흔한 범인은 Excel이 Shift-JIS로 저장한 CSV를 ",[47,3566,2853],{},"로 읽어 UTF-8로 그대로 쓰는 경우, 또는 ",[47,3569,3570],{},"charset=utf-8","을 선언하지 않은 HTTP 엔드포인트. 고쳐야 할 건 디코더지 PDF가 아니다.",[19,3573,3574,3577],{},[30,3575,3576],{},"섞여서 나옴"," — 일부는 정상, 일부는 네모 — 라면 폰트 커버리지가 부분적이라는 뜻. \"일본어 지원\"이라고 표기된 폰트가 히라가나·가타카나는 포함해도 鬱, 龠 같은 드문 한자를 빠뜨리는 경우가 있다. JIS X 0213을 커버하는 Noto Sans JP나 Source Han Sans JP로 바꾸면 해결된다.",[14,3579,3581],{"id":3580},"원인-2-상세-폰트는-맞는데-패밀리명이-틀림","원인 2 상세: 폰트는 맞는데 패밀리명이 틀림",[19,3583,3584,3585,3589],{},"이게 까다로운 이유는 폰트가 ",[3586,3587,3588],"em",{},"실제로"," 임베드됐기 때문이다 — 그냥 안 쓰일 뿐. 최소 재현:",[60,3591,3593],{"className":62,"code":3592,"language":64,"meta":65,"style":65},"doc := gpdf.NewDocument(\n    gpdf.WithFont(\"NotoSansJP\", font),\n    // WithDefaultFont 누락\n)\n\npage.AutoRow(func(r *template.RowBuilder) {\n    r.Col(12, func(c *template.ColBuilder) {\n        c.Text(\"こんにちは\") // 기본 폰트 = Helvetica로 그려짐\n    })\n})\n",[47,3594,3595,3609,3631,3636,3640,3644,3668,3699,3722,3726],{"__ignoreMap":65},[69,3596,3597,3599,3601,3603,3605,3607],{"class":71,"line":72},[69,3598,1004],{"class":187},[69,3600,197],{"class":75},[69,3602,275],{"class":187},[69,3604,203],{"class":75},[69,3606,280],{"class":174},[69,3608,283],{"class":75},[69,3610,3611,3613,3615,3617,3619,3621,3623,3625,3627,3629],{"class":71,"line":83},[69,3612,1019],{"class":187},[69,3614,203],{"class":75},[69,3616,354],{"class":174},[69,3618,209],{"class":75},[69,3620,212],{"class":75},[69,3622,3257],{"class":215},[69,3624,212],{"class":75},[69,3626,191],{"class":75},[69,3628,368],{"class":187},[69,3630,306],{"class":75},[69,3632,3633],{"class":71,"line":90},[69,3634,3635],{"class":2211},"    // WithDefaultFont 누락\n",[69,3637,3638],{"class":71,"line":100},[69,3639,160],{"class":75},[69,3641,3642],{"class":71,"line":112},[69,3643,87],{"emptyLinePlaceholder":86},[69,3645,3646,3648,3650,3652,3654,3656,3658,3660,3662,3664,3666],{"class":71,"line":122},[69,3647,1926],{"class":187},[69,3649,203],{"class":75},[69,3651,436],{"class":174},[69,3653,439],{"class":75},[69,3655,443],{"class":442},[69,3657,446],{"class":75},[69,3659,449],{"class":79},[69,3661,203],{"class":75},[69,3663,454],{"class":79},[69,3665,457],{"class":75},[69,3667,181],{"class":75},[69,3669,3670,3673,3675,3677,3679,3681,3683,3685,3687,3689,3691,3693,3695,3697],{"class":71,"line":127},[69,3671,3672],{"class":187},"    r",[69,3674,203],{"class":75},[69,3676,470],{"class":174},[69,3678,209],{"class":75},[69,3680,475],{"class":340},[69,3682,191],{"class":75},[69,3684,480],{"class":75},[69,3686,483],{"class":442},[69,3688,446],{"class":75},[69,3690,449],{"class":79},[69,3692,203],{"class":75},[69,3694,492],{"class":79},[69,3696,457],{"class":75},[69,3698,181],{"class":75},[69,3700,3701,3704,3706,3708,3710,3712,3715,3717,3719],{"class":71,"line":137},[69,3702,3703],{"class":187},"        c",[69,3705,203],{"class":75},[69,3707,507],{"class":174},[69,3709,209],{"class":75},[69,3711,212],{"class":75},[69,3713,3714],{"class":215},"こんにちは",[69,3716,212],{"class":75},[69,3718,457],{"class":75},[69,3720,3721],{"class":2211}," // 기본 폰트 = Helvetica로 그려짐\n",[69,3723,3724],{"class":71,"line":147},[69,3725,576],{"class":75},[69,3727,3728],{"class":71,"line":157},[69,3729,3730],{"class":75},"})\n",[19,3732,3733,3734,2989,3736,3739,3740,2989,3742,3745,3746,3748,3749,3751,3752,3755,3756,2128,3758,3761],{},"수정: ",[47,3735,280],{},[47,3737,3738],{},"gpdf.WithDefaultFont(\"NotoSansJP\", 12)","를 추가하거나, 일본어를 쓰는 모든 ",[47,3741,3000],{},[47,3743,3744],{},"template.FontFamily(\"NotoSansJP\")","를 넘긴다. ",[47,3747,354],{},"의 패밀리명과 ",[47,3750,3000],{},"의 패밀리명은 ",[30,3753,3754],{},"대소문자까지 완전히 일치","해야 한다. ",[47,3757,3257],{},[47,3759,3760],{},"notosansjp","는 gpdf 입장에서 다른 폰트다.",[14,3763,3765],{"id":3764},"원인-3-상세-잘못된-ttf-파일","원인 3 상세: 잘못된 TTF 파일",[19,3767,3768,2128,3770,3772],{},[47,3769,3018],{},[47,3771,3142],{},"는 다른 파일이다. 전자는 CJK 커버리지가 0인 Latin 폰트. 후자는 약 17,000자의 일본어 컷. 디렉토리 리스트에서 거의 똑같이 보이고, 자동완성이 틀린 쪽을 집어주기 쉽다.",[19,3774,3775],{},"gpdf는 등록 시점에 글리프 커버리지를 검증하지 않는다. 바이트를 주면 믿는다. 실패는 렌더링 시점의 두부 문자로만 드러난다.",[19,3777,3778],{},"빠른 확인:",[1113,3780,3781,3788,3795],{},[886,3782,3783,3784,3787],{},"macOS: ",[47,3785,3786],{},"Font Book","에서 파일을 더블 클릭하면 글리프 그리드가 나온다",[886,3789,3790,3791,3794],{},"Linux: ",[47,3792,3793],{},"otfinfo -u NotoSans-Regular.ttf","가 유니코드 커버리지를 덤프한다",[886,3796,3797,3798,901,3803,3806],{},"크로스플랫폼: ",[22,3799,3802],{"href":3800,"rel":3801},"https://github.com/fonttools/fonttools",[26],"fontTools",[47,3804,3805],{},"ttx -t cmap NotoSans-Regular.ttf","가 cmap 테이블을 XML로 내보낸다",[19,3808,3809],{},"목록에 U+3042 (あ)가 없으면 Latin 서브셋을 쥐고 있는 것이다.",[14,3811,3813],{"id":3812},"원인-4-상세-인코딩-손상","원인 4 상세: 인코딩 손상",[19,3815,3816,3817,3819],{},"이건 사실 gpdf와 무관하다. ",[47,3818,3000],{},"에 넘긴 시점에 문자열이 이미 깨져 있다. 렌더 전에 출력해본다:",[60,3821,3823],{"className":62,"code":3822,"language":64,"meta":65,"style":65},"text := loadLabelFromSomewhere()\nfmt.Printf(\"%q\\n\", text) // 실제 rune 출력\nc.Text(text)\n",[47,3824,3825,3837,3870],{"__ignoreMap":65},[69,3826,3827,3830,3832,3835],{"class":71,"line":72},[69,3828,3829],{"class":187},"text ",[69,3831,197],{"class":75},[69,3833,3834],{"class":174}," loadLabelFromSomewhere",[69,3836,425],{"class":75},[69,3838,3839,3842,3844,3847,3849,3851,3855,3858,3860,3862,3865,3867],{"class":71,"line":83},[69,3840,3841],{"class":187},"fmt",[69,3843,203],{"class":75},[69,3845,3846],{"class":174},"Printf",[69,3848,209],{"class":75},[69,3850,212],{"class":75},[69,3852,3854],{"class":3853},"swJcz","%q",[69,3856,3857],{"class":187},"\\n",[69,3859,212],{"class":75},[69,3861,191],{"class":75},[69,3863,3864],{"class":187}," text",[69,3866,457],{"class":75},[69,3868,3869],{"class":2211}," // 실제 rune 출력\n",[69,3871,3872,3874,3876,3878,3880,3882],{"class":71,"line":90},[69,3873,483],{"class":187},[69,3875,203],{"class":75},[69,3877,507],{"class":174},[69,3879,209],{"class":75},[69,3881,928],{"class":187},[69,3883,160],{"class":75},[19,3885,3886,3887,3890,3891,3894],{},"여기서 ",[47,3888,3889],{},"\"あいうえ\""," 대신 ",[47,3892,3893],{},"\"縺ゅ→縺\"","가 나오면 손상은 상류에서 일어났다. gpdf는 못 고친다 — UTF-8이 잘못 디코딩된 지점을 찾아 수정한다.",[19,3896,3897],{},"흔한 상류 범인:",[1113,3899,3900,3910,3925],{},[886,3901,3902,3903,3905,3906,3909],{},"Excel이 Shift-JIS(CP949와 혼동 주의, 일본은 CP932)로 저장한 CSV를 ",[47,3904,2853],{}," 후 바로 ",[47,3907,3908],{},"string()"," 캐스팅",[886,3911,3912,3913,3916,3917,3920,3921,3924],{},"이미 mojibake를 저장하고 있는 ",[47,3914,3915],{},"latin1"," 또는 ",[47,3918,3919],{},"utf8mb3"," 컬럼(",[47,3922,3923],{},"utf8mb4"," 아님)",[886,3926,3927,3930],{},[47,3928,3929],{},"Content-Type: application/json; charset=utf-8"," 선언이 없어서 Latin-1로 추측한 HTTP 응답",[14,3932,3934],{"id":3933},"한-가지-잊기-쉬운-엣지-케이스","한 가지 잊기 쉬운 엣지 케이스",[19,3936,3937,3938,3941,3942,3944,3945,3948,3949,3952,3953,3955,3956,2719],{},"gpdf의 서브셋 고정은 ",[47,3939,3940],{},"Generate()"," 시점에 일어난다. 문서 구성 중에 ",[47,3943,3714],{},"를 그린 뒤 나중에 ",[47,3946,3947],{},"鬱陶しい","를 그려도, 두 번째도 서브셋에 제대로 추가된다. 하지만 ",[30,3950,3951],{},"이미 생성된 PDF를 Acrobat에서 열어 원본에 없던 한자를 타이핑","하면 그 자리는 두부가 된다. 서브셋은 ",[47,3954,3940],{}," 순간에 얼어붙기 때문이다. PDF를 후편집하지 말고 Go에서 다시 ",[47,3957,3940],{},[14,3959,1211],{"id":1210},[1113,3961,3962,3970,3979],{},[886,3963,3964,3966,3967,3969],{},[22,3965,1225],{"href":1224}," — 볼드/이탤릭 변형과 다중 CJK 문서를 포함한 ",[47,3968,354],{}," 전체 안내",[886,3971,3972,3974,3975,3978],{},[22,3973,1218],{"href":834}," — 어떤 Noto 파일을 고를지, ",[47,3976,3977],{},"go:embed","로 배포를 어떻게 단순화할지",[886,3980,3981,3985],{},[22,3982,3984],{"href":3983},"/ko/blog/japanese-pdf-in-go","Go에서 일본어 PDF 만들기 결정판 가이드(2026)"," — 폰트, 세로쓰기, ruby, 일본어 특유 레이아웃을 다루는 장편 가이드",[14,3987,1244],{"id":1243},[19,3989,1247],{},[60,3991,3992],{"className":1250,"code":1251,"language":1252,"meta":65,"style":65},[47,3993,3994],{"__ignoreMap":65},[69,3995,3996,3998,4000],{"class":71,"line":72},[69,3997,64],{"class":79},[69,3999,1261],{"class":215},[69,4001,1264],{"class":215},[19,4003,4004,1271,4007],{},[22,4005,1270],{"href":24,"rel":4006},[26],[22,4008,1276],{"href":1274,"rel":4009},[26],[1278,4011,4012],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .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 .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 .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}",{"title":65,"searchDepth":83,"depth":83,"links":4014},[4015,4016,4017,4018,4019,4020,4021,4022,4023,4024],{"id":16,"depth":83,"text":17},{"id":36,"depth":83,"text":37},{"id":3032,"depth":83,"text":3033},{"id":3532,"depth":83,"text":3533},{"id":3580,"depth":83,"text":3581},{"id":3764,"depth":83,"text":3765},{"id":3812,"depth":83,"text":3813},{"id":3933,"depth":83,"text":3934},{"id":1210,"depth":83,"text":1211},{"id":1243,"depth":83,"text":1244},"2026-04-17","일본어가 □ 로 나오는 건 대부분 폰트 미등록. 흔한 4가지 원인과 최단 수정법을 정리한다.",{"name":4028,"totalTime":1299,"tools":4029,"steps":4031},"gpdf 문서의 두부 문자를 진단하고 고친다",[1301,4030],"CJK를 지원하는 TTF (예: NotoSansJP-Regular.ttf)",[4032,4035,4038,4041,4044],{"name":4033,"text":4034},"증상이 두부 문자인지 mojibake인지 구분한다","PDF를 연다. 일본어가 빈 사각형(□)이면 폰트 조회 실패. 縺ゅ→縺 같은 형태면 gpdf에 도달하기 전에 UTF-8이 잘못 디코딩된 것이다. 해결 경로가 완전히 다르다.",{"name":4036,"text":4037},"CJK 폰트가 등록되어 있는지 확인한다","문서 생성 부분에서 gpdf.WithFont를 검색한다. CJK TTF가 등록되어 있지 않으면 gpdf는 PDF Base-14 폰트로 폴백하고, 이들은 CJK 코드포인트를 전혀 다루지 않는다.",{"name":4039,"text":4040},"각 c.Text의 폰트 패밀리명을 검증한다","WithDefaultFont가 없다면 일본어를 그리는 모든 c.Text에 template.FontFamily(\"NotoSansJP\")를 명시해야 한다. 이름이 맞지 않으면 조용히 기본 폰트로 폴백한다.",{"name":4042,"text":4043},"TTF 파일에 실제로 CJK 글리프가 있는지 확인한다","NotoSans-Regular.ttf (Latin 서브셋) 와 NotoSansJP-Regular.ttf는 다른 파일이다. gpdf는 등록 시점에 커버리지를 검증하지 않는다.",{"name":4045,"text":4046},"두 개의 뷰어에서 재확인한다","생성한 PDF를 Adobe Acrobat과 Chrome 양쪽에서 연다. 둘 다 정상 렌더링되어야 OK. 한쪽만 정상이면 글리프는 임베드됐지만 서브셋 등록이 어긋난 경우다.",{},{"title":2962,"description":4026},"ko/blog/008.tofu-boxes-japanese",[1325,4051,1326],"troubleshooting","GIOTuFQGRC84xnT5i01kWQ0hpkKcc8JRyxTc4jsIS3Y",{"id":4054,"title":4055,"author":4056,"body":4057,"date":4025,"description":5266,"draft":1295,"extension":1296,"howTo":5267,"image":1319,"meta":5287,"navigation":86,"path":1231,"seo":5288,"stem":5289,"tags":5290,"updated":1319,"__hash__":5291},"blogKo/ko/blog/009.ipaex-gothic-gpdf.md","gpdf에서 IPAex 고딕(IPAex Gothic)을 사용하려면?",{"name":8,"url":9},{"type":11,"value":4058,"toc":5255},[4059,4061,4073,4075,4088,4090,4619,4636,4640,4643,4699,4706,4718,4722,4725,4728,4736,4768,4774,5057,5060,5064,5073,5098,5105,5108,5112,5196,5199,5201,5229,5231,5233,5245,5253],[14,4060,17],{"id":16},[19,4062,4063,4066,4067,4072],{},[22,4064,27],{"href":24,"rel":4065},[26]," 문서에서 IPAex Gothic — 일본 ",[22,4068,4071],{"href":4069,"rel":4070},"https://moji.or.jp/ipafont/",[26],"정보처리추진기구","(IPA)가 관리하는 프로포셔널 서양 문자 고딕체 — 를 쓰고 싶다. 전형적인 사용처: e-Tax PDF 첨부, 정부 제출 서류, 2010년대 초부터 IPAex로 통일해 온 사내 스타일. 매번 걸리는 곳은 세 군데다. 어떤 파일을 고를지, Bold가 없는 문제를 어떻게 다룰지, IPA Font License가 실제로 무엇을 요구하는지.",[14,4074,37],{"id":36},[19,4076,4077,4080,4081,4084,4085,4087],{},[47,4078,4079],{},"ipaexg.ttf","를 ",[47,4082,4083],{},"gpdf.WithFont(\"IPAexGothic\", bytes)","로 등록하고 기본 폰트로 설정한다. Bold는 ",[47,4086,1097],{},"로 합성하거나 IPAex 명조와 페어링한다. IPAex는 Regular 한 가지 굵기만 제공하기 때문이다. 바이너리 배포 시 라이선스 전문을 동봉한다.",[14,4089,58],{"id":57},[60,4091,4093],{"className":62,"code":4092,"language":64,"meta":65,"style":65},"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/template\"\n)\n\nfunc main() {\n    font, err := os.ReadFile(\"ipaexg.ttf\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(gpdf.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(25))),\n        gpdf.WithFont(\"IPAexGothic\", font),\n        gpdf.WithDefaultFont(\"IPAexGothic\", 10.5),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"請求書\", template.FontSize(24), template.Bold())\n            c.Text(\"令和8年4月17日発行\")\n            c.Text(\"金額: ¥100,000 (税込)\")\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"invoice.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[47,4094,4095,4101,4105,4111,4119,4127,4131,4139,4147,4155,4159,4163,4173,4199,4211,4225,4229,4233,4247,4265,4296,4319,4342,4346,4350,4364,4388,4418,4457,4476,4495,4499,4503,4507,4525,4537,4551,4555,4596,4610,4614],{"__ignoreMap":65},[69,4096,4097,4099],{"class":71,"line":72},[69,4098,76],{"class":75},[69,4100,80],{"class":79},[69,4102,4103],{"class":71,"line":83},[69,4104,87],{"emptyLinePlaceholder":86},[69,4106,4107,4109],{"class":71,"line":90},[69,4108,94],{"class":93},[69,4110,97],{"class":75},[69,4112,4113,4115,4117],{"class":71,"line":100},[69,4114,103],{"class":75},[69,4116,106],{"class":79},[69,4118,109],{"class":75},[69,4120,4121,4123,4125],{"class":71,"line":112},[69,4122,103],{"class":75},[69,4124,117],{"class":79},[69,4126,109],{"class":75},[69,4128,4129],{"class":71,"line":122},[69,4130,87],{"emptyLinePlaceholder":86},[69,4132,4133,4135,4137],{"class":71,"line":127},[69,4134,103],{"class":75},[69,4136,132],{"class":79},[69,4138,109],{"class":75},[69,4140,4141,4143,4145],{"class":71,"line":137},[69,4142,103],{"class":75},[69,4144,142],{"class":79},[69,4146,109],{"class":75},[69,4148,4149,4151,4153],{"class":71,"line":147},[69,4150,103],{"class":75},[69,4152,152],{"class":79},[69,4154,109],{"class":75},[69,4156,4157],{"class":71,"line":157},[69,4158,160],{"class":75},[69,4160,4161],{"class":71,"line":163},[69,4162,87],{"emptyLinePlaceholder":86},[69,4164,4165,4167,4169,4171],{"class":71,"line":168},[69,4166,171],{"class":75},[69,4168,175],{"class":174},[69,4170,178],{"class":75},[69,4172,181],{"class":75},[69,4174,4175,4177,4179,4181,4183,4185,4187,4189,4191,4193,4195,4197],{"class":71,"line":184},[69,4176,188],{"class":187},[69,4178,191],{"class":75},[69,4180,194],{"class":187},[69,4182,197],{"class":75},[69,4184,200],{"class":187},[69,4186,203],{"class":75},[69,4188,206],{"class":174},[69,4190,209],{"class":75},[69,4192,212],{"class":75},[69,4194,4079],{"class":215},[69,4196,212],{"class":75},[69,4198,160],{"class":75},[69,4200,4201,4203,4205,4207,4209],{"class":71,"line":222},[69,4202,225],{"class":93},[69,4204,194],{"class":187},[69,4206,230],{"class":75},[69,4208,233],{"class":75},[69,4210,181],{"class":75},[69,4212,4213,4215,4217,4219,4221,4223],{"class":71,"line":238},[69,4214,241],{"class":187},[69,4216,203],{"class":75},[69,4218,246],{"class":174},[69,4220,209],{"class":75},[69,4222,251],{"class":187},[69,4224,160],{"class":75},[69,4226,4227],{"class":71,"line":256},[69,4228,259],{"class":75},[69,4230,4231],{"class":71,"line":262},[69,4232,87],{"emptyLinePlaceholder":86},[69,4234,4235,4237,4239,4241,4243,4245],{"class":71,"line":267},[69,4236,270],{"class":187},[69,4238,197],{"class":75},[69,4240,275],{"class":187},[69,4242,203],{"class":75},[69,4244,280],{"class":174},[69,4246,283],{"class":75},[69,4248,4249,4251,4253,4255,4257,4259,4261,4263],{"class":71,"line":286},[69,4250,289],{"class":187},[69,4252,203],{"class":75},[69,4254,294],{"class":174},[69,4256,209],{"class":75},[69,4258,27],{"class":187},[69,4260,203],{"class":75},[69,4262,303],{"class":187},[69,4264,306],{"class":75},[69,4266,4267,4269,4271,4273,4275,4277,4279,4281,4283,4285,4287,4289,4291,4294],{"class":71,"line":309},[69,4268,289],{"class":187},[69,4270,203],{"class":75},[69,4272,316],{"class":174},[69,4274,209],{"class":75},[69,4276,321],{"class":187},[69,4278,203],{"class":75},[69,4280,326],{"class":174},[69,4282,209],{"class":75},[69,4284,321],{"class":187},[69,4286,203],{"class":75},[69,4288,335],{"class":174},[69,4290,209],{"class":75},[69,4292,4293],{"class":340},"25",[69,4295,344],{"class":75},[69,4297,4298,4300,4302,4304,4306,4308,4311,4313,4315,4317],{"class":71,"line":347},[69,4299,289],{"class":187},[69,4301,203],{"class":75},[69,4303,354],{"class":174},[69,4305,209],{"class":75},[69,4307,212],{"class":75},[69,4309,4310],{"class":215},"IPAexGothic",[69,4312,212],{"class":75},[69,4314,191],{"class":75},[69,4316,368],{"class":187},[69,4318,306],{"class":75},[69,4320,4321,4323,4325,4327,4329,4331,4333,4335,4337,4340],{"class":71,"line":373},[69,4322,289],{"class":187},[69,4324,203],{"class":75},[69,4326,380],{"class":174},[69,4328,209],{"class":75},[69,4330,212],{"class":75},[69,4332,4310],{"class":215},[69,4334,212],{"class":75},[69,4336,191],{"class":75},[69,4338,4339],{"class":340}," 10.5",[69,4341,306],{"class":75},[69,4343,4344],{"class":71,"line":398},[69,4345,401],{"class":75},[69,4347,4348],{"class":71,"line":404},[69,4349,87],{"emptyLinePlaceholder":86},[69,4351,4352,4354,4356,4358,4360,4362],{"class":71,"line":409},[69,4353,412],{"class":187},[69,4355,197],{"class":75},[69,4357,417],{"class":187},[69,4359,203],{"class":75},[69,4361,422],{"class":174},[69,4363,425],{"class":75},[69,4365,4366,4368,4370,4372,4374,4376,4378,4380,4382,4384,4386],{"class":71,"line":428},[69,4367,431],{"class":187},[69,4369,203],{"class":75},[69,4371,436],{"class":174},[69,4373,439],{"class":75},[69,4375,443],{"class":442},[69,4377,446],{"class":75},[69,4379,449],{"class":79},[69,4381,203],{"class":75},[69,4383,454],{"class":79},[69,4385,457],{"class":75},[69,4387,181],{"class":75},[69,4389,4390,4392,4394,4396,4398,4400,4402,4404,4406,4408,4410,4412,4414,4416],{"class":71,"line":462},[69,4391,465],{"class":187},[69,4393,203],{"class":75},[69,4395,470],{"class":174},[69,4397,209],{"class":75},[69,4399,475],{"class":340},[69,4401,191],{"class":75},[69,4403,480],{"class":75},[69,4405,483],{"class":442},[69,4407,446],{"class":75},[69,4409,449],{"class":79},[69,4411,203],{"class":75},[69,4413,492],{"class":79},[69,4415,457],{"class":75},[69,4417,181],{"class":75},[69,4419,4420,4422,4424,4426,4428,4430,4433,4435,4437,4439,4441,4443,4445,4447,4449,4451,4453,4455],{"class":71,"line":499},[69,4421,502],{"class":187},[69,4423,203],{"class":75},[69,4425,507],{"class":174},[69,4427,209],{"class":75},[69,4429,212],{"class":75},[69,4431,4432],{"class":215},"請求書",[69,4434,212],{"class":75},[69,4436,191],{"class":75},[69,4438,521],{"class":187},[69,4440,203],{"class":75},[69,4442,526],{"class":174},[69,4444,209],{"class":75},[69,4446,531],{"class":340},[69,4448,534],{"class":75},[69,4450,521],{"class":187},[69,4452,203],{"class":75},[69,4454,541],{"class":174},[69,4456,544],{"class":75},[69,4458,4459,4461,4463,4465,4467,4469,4472,4474],{"class":71,"line":547},[69,4460,502],{"class":187},[69,4462,203],{"class":75},[69,4464,507],{"class":174},[69,4466,209],{"class":75},[69,4468,212],{"class":75},[69,4470,4471],{"class":215},"令和8年4月17日発行",[69,4473,212],{"class":75},[69,4475,160],{"class":75},[69,4477,4478,4480,4482,4484,4486,4488,4491,4493],{"class":71,"line":567},[69,4479,502],{"class":187},[69,4481,203],{"class":75},[69,4483,507],{"class":174},[69,4485,209],{"class":75},[69,4487,212],{"class":75},[69,4489,4490],{"class":215},"金額: ¥100,000 (税込)",[69,4492,212],{"class":75},[69,4494,160],{"class":75},[69,4496,4497],{"class":71,"line":573},[69,4498,570],{"class":75},[69,4500,4501],{"class":71,"line":579},[69,4502,576],{"class":75},[69,4504,4505],{"class":71,"line":584},[69,4506,87],{"emptyLinePlaceholder":86},[69,4508,4509,4511,4513,4515,4517,4519,4521,4523],{"class":71,"line":605},[69,4510,587],{"class":187},[69,4512,191],{"class":75},[69,4514,194],{"class":187},[69,4516,197],{"class":75},[69,4518,417],{"class":187},[69,4520,203],{"class":75},[69,4522,600],{"class":174},[69,4524,425],{"class":75},[69,4526,4527,4529,4531,4533,4535],{"class":71,"line":618},[69,4528,225],{"class":93},[69,4530,194],{"class":187},[69,4532,230],{"class":75},[69,4534,233],{"class":75},[69,4536,181],{"class":75},[69,4538,4539,4541,4543,4545,4547,4549],{"class":71,"line":633},[69,4540,241],{"class":187},[69,4542,203],{"class":75},[69,4544,246],{"class":174},[69,4546,209],{"class":75},[69,4548,251],{"class":187},[69,4550,160],{"class":75},[69,4552,4553],{"class":71,"line":638},[69,4554,259],{"class":75},[69,4556,4557,4559,4561,4563,4565,4567,4569,4571,4573,4576,4578,4580,4582,4584,4586,4588,4590,4592,4594],{"class":71,"line":684},[69,4558,225],{"class":93},[69,4560,194],{"class":187},[69,4562,197],{"class":75},[69,4564,200],{"class":187},[69,4566,203],{"class":75},[69,4568,651],{"class":174},[69,4570,209],{"class":75},[69,4572,212],{"class":75},[69,4574,4575],{"class":215},"invoice.pdf",[69,4577,212],{"class":75},[69,4579,191],{"class":75},[69,4581,665],{"class":187},[69,4583,191],{"class":75},[69,4585,670],{"class":340},[69,4587,673],{"class":75},[69,4589,194],{"class":187},[69,4591,230],{"class":75},[69,4593,233],{"class":75},[69,4595,181],{"class":75},[69,4597,4598,4600,4602,4604,4606,4608],{"class":71,"line":699},[69,4599,241],{"class":187},[69,4601,203],{"class":75},[69,4603,246],{"class":174},[69,4605,209],{"class":75},[69,4607,251],{"class":187},[69,4609,160],{"class":75},[69,4611,4612],{"class":71,"line":704},[69,4613,259],{"class":75},[69,4615,4617],{"class":71,"line":4616},41,[69,4618,707],{"class":75},[19,4620,4621,4625,4626,4629,4630,4080,4632,714,4634,203],{},[22,4622,4624],{"href":4069,"rel":4623},[26],"moji.or.jp/ipafont","에서 ",[47,4627,4628],{},"IPAex00401.zip","을 받아 ",[47,4631,4079],{},[47,4633,713],{},[47,4635,717],{},[14,4637,4639],{"id":4638},"어느-ipa-파일이-맞는가","어느 IPA 파일이 맞는가",[19,4641,4642],{},"zip을 열면 TTF 세 개와 라이선스가 들어 있다. 이름이 비슷해 혼동하기 쉽다:",[741,4644,4645,4655],{},[744,4646,4647],{},[747,4648,4649,4652],{},[750,4650,4651],{},"파일",[750,4653,4654],{},"내용",[759,4656,4657,4669,4682],{},[747,4658,4659,4663],{},[764,4660,4661],{},[47,4662,4079],{},[764,4664,4665,4668],{},[30,4666,4667],{},"IPAex Gothic"," — 산세리프, 서양 문자는 프로포셔널. 일반 문서는 이것.",[747,4670,4671,4676],{},[764,4672,4673],{},[47,4674,4675],{},"ipaexm.ttf",[764,4677,4678,4681],{},[30,4679,4680],{},"IPAex Mincho"," — 세리프(명조), 서양 문자는 프로포셔널. 본문 길거나 고딕과 짝지어 강조할 때.",[747,4683,4684,4689],{},[764,4685,4686],{},[47,4687,4688],{},"ipag.ttf",[764,4690,4691,4694,4695,4698],{},[30,4692,4693],{},"IPA Gothic"," (\"ex\" 없음) — 산세리프, ",[30,4696,4697],{},"서양 문자가 고정폭",". 요즘은 잘 안 쓴다.",[19,4700,4701,4702,4705],{},"IPAex의 \"ex\"는 extended proportional의 약어다. 원본 IPA 폰트는 서양 문자를 CJK 전각 격자에 맞춰 배치하기 때문에 영문·국문 혼용 시 늘어져 보인다. IPAex는 서양 문자를 프로포셔널로 만들면서 CJK는 기본 격자에 유지한다. 영문 단어, URL, 숫자가 들어가는 거의 모든 업무 문서에서 ",[30,4703,4704],{},"IPAex","가 맞다.",[19,4707,4708,4709,4711,4712,4714,4715,4717],{},"기존 프로젝트가 ",[47,4710,4688],{},"를 쓴다면 대개 역사적 이유다(원본 IPA Gothic 2003년, IPAex 2010년 출시). 패밀리명을 ",[47,4713,4310],{},"으로 유지하고 파일만 ",[47,4716,4079],{},"로 바꾸면 코드는 한 줄만 수정하면 된다.",[14,4719,4721],{"id":4720},"bold-파일이-없는-문제","Bold 파일이 없는 문제",[19,4723,4724],{},"IPAex는 패밀리당 Regular 한 굵기만 배포된다. 9단 굵기를 내는 Noto Sans JP에 비하면 눈에 띄는 단점이고, IPAex를 고려했다가 접는 가장 흔한 이유이기도 하다.",[19,4726,4727],{},"gpdf에서 대응법은 둘이다.",[19,4729,4730,897,4733,4735],{},[30,4731,4732],{},"합성 볼드.",[47,4734,1097],{},"가 Regular 글리프 위에 스트로크를 덧입힌다. 타이포그래피 관점에서는 치트다 — 진짜 볼드 굵기는 두꺼운 획으로 새로 그려진 윤곽을 가진다. 하지만 10 pt 이상 인보이스 제목이나 표 라벨이라면 독자 대부분에게는 구분되지 않는다:",[60,4737,4739],{"className":62,"code":4738,"language":64,"meta":65,"style":65},"c.Text(\"合計金額\", template.Bold())\n",[47,4740,4741],{"__ignoreMap":65},[69,4742,4743,4745,4747,4749,4751,4753,4756,4758,4760,4762,4764,4766],{"class":71,"line":72},[69,4744,483],{"class":187},[69,4746,203],{"class":75},[69,4748,507],{"class":174},[69,4750,209],{"class":75},[69,4752,212],{"class":75},[69,4754,4755],{"class":215},"合計金額",[69,4757,212],{"class":75},[69,4759,191],{"class":75},[69,4761,521],{"class":187},[69,4763,203],{"class":75},[69,4765,541],{"class":174},[69,4767,544],{"class":75},[19,4769,4770,4773],{},[30,4771,4772],{},"IPAex 명조와 페어링."," 일본 조판의 고전적인 강조법은 볼드화가 아니라 세리프/산세리프 전환이다. 두 패밀리를 함께 등록한다:",[60,4775,4777],{"className":62,"code":4776,"language":64,"meta":65,"style":65},"gothic, _ := os.ReadFile(\"ipaexg.ttf\")\nmincho, _ := os.ReadFile(\"ipaexm.ttf\")\n\ndoc := gpdf.NewDocument(\n    gpdf.WithFont(\"IPAexGothic\", gothic),\n    gpdf.WithFont(\"IPAexMincho\", mincho),\n    gpdf.WithDefaultFont(\"IPAexGothic\", 10.5),\n)\n\npage.AutoRow(func(r *template.RowBuilder) {\n    r.Col(12, func(c *template.ColBuilder) {\n        c.Text(\"請求書\", template.FontFamily(\"IPAexMincho\"), template.FontSize(24))\n        c.Text(\"ご請求内容は下記の通りです。\")\n    })\n})\n",[47,4778,4779,4806,4833,4837,4851,4874,4898,4920,4924,4928,4952,4982,5030,5049,5053],{"__ignoreMap":65},[69,4780,4781,4784,4786,4788,4790,4792,4794,4796,4798,4800,4802,4804],{"class":71,"line":72},[69,4782,4783],{"class":187},"gothic",[69,4785,191],{"class":75},[69,4787,976],{"class":187},[69,4789,197],{"class":75},[69,4791,200],{"class":187},[69,4793,203],{"class":75},[69,4795,206],{"class":174},[69,4797,209],{"class":75},[69,4799,212],{"class":75},[69,4801,4079],{"class":215},[69,4803,212],{"class":75},[69,4805,160],{"class":75},[69,4807,4808,4811,4813,4815,4817,4819,4821,4823,4825,4827,4829,4831],{"class":71,"line":83},[69,4809,4810],{"class":187},"mincho",[69,4812,191],{"class":75},[69,4814,976],{"class":187},[69,4816,197],{"class":75},[69,4818,200],{"class":187},[69,4820,203],{"class":75},[69,4822,206],{"class":174},[69,4824,209],{"class":75},[69,4826,212],{"class":75},[69,4828,4675],{"class":215},[69,4830,212],{"class":75},[69,4832,160],{"class":75},[69,4834,4835],{"class":71,"line":90},[69,4836,87],{"emptyLinePlaceholder":86},[69,4838,4839,4841,4843,4845,4847,4849],{"class":71,"line":100},[69,4840,1004],{"class":187},[69,4842,197],{"class":75},[69,4844,275],{"class":187},[69,4846,203],{"class":75},[69,4848,280],{"class":174},[69,4850,283],{"class":75},[69,4852,4853,4855,4857,4859,4861,4863,4865,4867,4869,4872],{"class":71,"line":112},[69,4854,1019],{"class":187},[69,4856,203],{"class":75},[69,4858,354],{"class":174},[69,4860,209],{"class":75},[69,4862,212],{"class":75},[69,4864,4310],{"class":215},[69,4866,212],{"class":75},[69,4868,191],{"class":75},[69,4870,4871],{"class":187}," gothic",[69,4873,306],{"class":75},[69,4875,4876,4878,4880,4882,4884,4886,4889,4891,4893,4896],{"class":71,"line":122},[69,4877,1019],{"class":187},[69,4879,203],{"class":75},[69,4881,354],{"class":174},[69,4883,209],{"class":75},[69,4885,212],{"class":75},[69,4887,4888],{"class":215},"IPAexMincho",[69,4890,212],{"class":75},[69,4892,191],{"class":75},[69,4894,4895],{"class":187}," mincho",[69,4897,306],{"class":75},[69,4899,4900,4902,4904,4906,4908,4910,4912,4914,4916,4918],{"class":71,"line":127},[69,4901,1019],{"class":187},[69,4903,203],{"class":75},[69,4905,380],{"class":174},[69,4907,209],{"class":75},[69,4909,212],{"class":75},[69,4911,4310],{"class":215},[69,4913,212],{"class":75},[69,4915,191],{"class":75},[69,4917,4339],{"class":340},[69,4919,306],{"class":75},[69,4921,4922],{"class":71,"line":137},[69,4923,160],{"class":75},[69,4925,4926],{"class":71,"line":147},[69,4927,87],{"emptyLinePlaceholder":86},[69,4929,4930,4932,4934,4936,4938,4940,4942,4944,4946,4948,4950],{"class":71,"line":157},[69,4931,1926],{"class":187},[69,4933,203],{"class":75},[69,4935,436],{"class":174},[69,4937,439],{"class":75},[69,4939,443],{"class":442},[69,4941,446],{"class":75},[69,4943,449],{"class":79},[69,4945,203],{"class":75},[69,4947,454],{"class":79},[69,4949,457],{"class":75},[69,4951,181],{"class":75},[69,4953,4954,4956,4958,4960,4962,4964,4966,4968,4970,4972,4974,4976,4978,4980],{"class":71,"line":163},[69,4955,3672],{"class":187},[69,4957,203],{"class":75},[69,4959,470],{"class":174},[69,4961,209],{"class":75},[69,4963,475],{"class":340},[69,4965,191],{"class":75},[69,4967,480],{"class":75},[69,4969,483],{"class":442},[69,4971,446],{"class":75},[69,4973,449],{"class":79},[69,4975,203],{"class":75},[69,4977,492],{"class":79},[69,4979,457],{"class":75},[69,4981,181],{"class":75},[69,4983,4984,4986,4988,4990,4992,4994,4996,4998,5000,5002,5004,5007,5009,5011,5013,5015,5017,5019,5021,5023,5025,5027],{"class":71,"line":168},[69,4985,3703],{"class":187},[69,4987,203],{"class":75},[69,4989,507],{"class":174},[69,4991,209],{"class":75},[69,4993,212],{"class":75},[69,4995,4432],{"class":215},[69,4997,212],{"class":75},[69,4999,191],{"class":75},[69,5001,521],{"class":187},[69,5003,203],{"class":75},[69,5005,5006],{"class":174},"FontFamily",[69,5008,209],{"class":75},[69,5010,212],{"class":75},[69,5012,4888],{"class":215},[69,5014,212],{"class":75},[69,5016,534],{"class":75},[69,5018,521],{"class":187},[69,5020,203],{"class":75},[69,5022,526],{"class":174},[69,5024,209],{"class":75},[69,5026,531],{"class":340},[69,5028,5029],{"class":75},"))\n",[69,5031,5032,5034,5036,5038,5040,5042,5045,5047],{"class":71,"line":184},[69,5033,3703],{"class":187},[69,5035,203],{"class":75},[69,5037,507],{"class":174},[69,5039,209],{"class":75},[69,5041,212],{"class":75},[69,5043,5044],{"class":215},"ご請求内容は下記の通りです。",[69,5046,212],{"class":75},[69,5048,160],{"class":75},[69,5050,5051],{"class":71,"line":222},[69,5052,576],{"class":75},[69,5054,5055],{"class":71,"line":238},[69,5056,3730],{"class":75},[19,5058,5059],{},"일본의 청첩장, 공식 보고서에서 흔히 보이는 조합이다 — 제목은 명조, 본문은 고딕. 문서가 관공서로 갈 예정이라면 이 조합이 저쪽에서 기대하는 모양새일 가능성이 높다.",[14,5061,5063],{"id":5062},"ipa-font-license-간단히","IPA Font License 간단히",[19,5065,5066,5067,5072],{},"IPAex는 SIL OFL이 아니라 ",[22,5068,5071],{"href":5069,"rel":5070},"https://opensource.org/licenses/IPA",[26],"IPA Font License Agreement v1.0","이다 (OSI 승인). 전반적으로 관대하지만 짚어둘 두 가지:",[883,5074,5075,5088],{},[886,5076,5077,897,5080,5083,5084,5087],{},[30,5078,5079],{},"라이선스 전문을 폰트 바이너리와 함께 유지한다.",[47,5081,5082],{},"//go:embed","로 TTF를 Go 바이너리에 넣는다면 라이선스 파일도 같이 넣는다. 프로젝트 루트에 ",[47,5085,5086],{},"LICENSES/IPA-FONT-1.0.txt","를 두면 대부분의 배포 상황에서 충분하다.",[886,5089,5090,5093,5094,5097],{},[30,5091,5092],{},"폰트 이름을 바꾸지 않는다."," TTF 자체를 수정해 재배포하는 경우, 파생물에는 \"IPA\"나 \"IPAex\"를 포함하지 않는 다른 이름을 붙여야 한다. 단, 이 제약은 렌더링 시의 글리프 서브셋에는 ",[30,5095,5096],{},"적용되지 않는다",". 라이선스 제3조 4항이 폰트로 생성한 \"출력 문서\"를 명명 제약에서 명시적으로 제외한다.",[19,5099,5100,5101,5104],{},"정리하자면, ",[47,5102,5103],{},"doc.Generate()","에서 gpdf가 서브셋을 만드는 것은 라이선스상 안전하다. PDF에 포함되는 서브셋은 다른 이름을 필요로 하지 않고 \"파생 폰트 프로그램\" 조항을 트리거하지도 않는다. 폰트를 재배포하는 것이 아니라 문서를 만드는 것이다.",[19,5106,5107],{},"덧붙이자면, gpdf 코어 OSS 저장소에는 IPAex를 넣지 않는다 (golden 테스트는 Noto 계열 SIL OFL 폰트를 쓴다). 하위 사용자가 자기 프로젝트 최상위 LICENSE와 IPA 라이선스 호환성을 일일이 따지지 않도록 하기 위해서다. 애플리케이션에서 IPAex를 쓰는 것은 그 프로젝트의 판단이지 우리의 결정이 아니다.",[14,5109,5111],{"id":5110},"ipaex-vs-noto-sans-jp-어느-쪽을-고르나","IPAex vs Noto Sans JP, 어느 쪽을 고르나",[741,5113,5114,5125],{},[744,5115,5116],{},[747,5117,5118,5121,5123],{},[750,5119,5120],{},"축",[750,5122,4667],{},[750,5124,757],{},[759,5126,5127,5138,5149,5160,5174,5185],{},[747,5128,5129,5132,5135],{},[764,5130,5131],{},"굵기 수",[764,5133,5134],{},"1 (Regular)",[764,5136,5137],{},"9 (Thin → Black)",[747,5139,5140,5143,5146],{},[764,5141,5142],{},"라이선스",[764,5144,5145],{},"IPA Font License v1.0",[764,5147,5148],{},"SIL OFL 1.1",[747,5150,5151,5154,5157],{},[764,5152,5153],{},"서양 문자",[764,5155,5156],{},"프로포셔널(IPAex) 또는 고정폭(IPA)",[764,5158,5159],{},"프로포셔널",[747,5161,5162,5165,5171],{},[764,5163,5164],{},"기본 설치",[764,5166,5167,5168],{},"일부 일본 Linux 배포판, TeX Live ",[47,5169,5170],{},"ptex-fonts",[764,5172,5173],{},"Android, ChromeOS",[747,5175,5176,5179,5182],{},[764,5177,5178],{},"전형적 용도",[764,5180,5181],{},"일본 정부, 법률, 학술",[764,5183,5184],{},"소비자 웹, 국제용",[747,5186,5187,5190,5193],{},[764,5188,5189],{},"파일 크기",[764,5191,5192],{},"7.5 MB (Gothic)",[764,5194,5195],{},"5 MB (Regular 단독)",[19,5197,5198],{},"출력이 일본의 제도적 경계를 넘는 경우 — e-Tax PDF 첨부, 법원 제출 문서, 일본 저널로의 학술 논문 제출 — 에는 IPAex를 고른다. 그 생태계의 평가자, 심사자, OCR 도구가 IPA에 맞춰져 있기 때문이다. 그 외에는 Noto Sans JP로 충분하다. 렌더링 결과는 실용상 거의 같고, 선택 기준은 미감이 아니라 \"어느 생태계에 출력하느냐\"다.",[14,5200,1211],{"id":1210},[1113,5202,5203,5208,5213,5218],{},[886,5204,5205,5207],{},[22,5206,1225],{"href":1224}," — 모든 CJK TTF에 적용되는 일반 레시피",[886,5209,5210,5212],{},[22,5211,1218],{"href":834}," — SIL OFL에 9굵기가 있는 대안",[886,5214,5215,5217],{},[22,5216,1239],{"href":1238}," — 글리프가 표시되지 않을 때의 트러블슈팅",[886,5219,5220,5225,5226,5228],{},[22,5221,5224],{"href":5222,"rel":5223},"https://gpdf.dev/ko/docs/guide/fonts",[26],"폰트 가이드"," — ",[47,5227,354],{}," 전체 레퍼런스",[14,5230,1244],{"id":1243},[19,5232,1247],{},[60,5234,5235],{"className":1250,"code":1251,"language":1252,"meta":65,"style":65},[47,5236,5237],{"__ignoreMap":65},[69,5238,5239,5241,5243],{"class":71,"line":72},[69,5240,64],{"class":79},[69,5242,1261],{"class":215},[69,5244,1264],{"class":215},[19,5246,5247,1271,5250],{},[22,5248,1270],{"href":24,"rel":5249},[26],[22,5251,1276],{"href":1274,"rel":5252},[26],[1278,5254,1280],{},{"title":65,"searchDepth":83,"depth":83,"links":5256},[5257,5258,5259,5260,5261,5262,5263,5264,5265],{"id":16,"depth":83,"text":17},{"id":36,"depth":83,"text":37},{"id":57,"depth":83,"text":58},{"id":4638,"depth":83,"text":4639},{"id":4720,"depth":83,"text":4721},{"id":5062,"depth":83,"text":5063},{"id":5110,"depth":83,"text":5111},{"id":1210,"depth":83,"text":1211},{"id":1243,"depth":83,"text":1244},"ipaexg.ttf를 gpdf.WithFont로 등록한다. IPAex는 Regular 한 가지 굵기만 제공하므로 볼드는 합성하거나 명조와 페어링한다.",{"name":5268,"totalTime":5269,"tools":5270,"steps":5272},"gpdf 문서에서 IPAex Gothic을 기본 폰트로 쓰기","PT10M",[1301,5271],"ipaexg.ttf (moji.or.jp의 IPAex Gothic v4.01)",[5273,5276,5279,5281,5284],{"name":5274,"text":5275},"moji.or.jp에서 IPAex 폰트 묶음을 다운로드한다","moji.or.jp/ipafont에서 IPAex00401.zip을 받아 압축을 풀고 ipaexg.ttf와 함께 들어 있는 IPA Font License Agreement v1.0 텍스트 파일을 함께 보관한다.",{"name":5277,"text":5278},"TTF 바이트를 읽는다","프로그램 시작 시 os.ReadFile(\"ipaexg.ttf\")로 []byte에 로드한다. 컨테이너 배포라면 //go:embed로 Go 바이너리에 포함시키는 편이 배포하기 쉽다.",{"name":1311,"text":5280},"gpdf.WithFont(\"IPAexGothic\", fontBytes)와 gpdf.WithDefaultFont(\"IPAexGothic\", 10.5)를 gpdf.NewDocument에 전달한다. 10.5 pt는 Word의 일본어 문서 기본 포인트와 동일하다.",{"name":5282,"text":5283},"Bold 파일이 없는 문제를 다룬다","IPAex Gothic에는 볼드 변형이 없다. template.Bold()로 합성(gpdf가 0.4 pt 스트로크 덧그림)하거나, IPAex 명조를 별도 패밀리로 등록해 강조용으로 쓴다.",{"name":5285,"text":5286},"배포물에 라이선스 파일을 동봉한다","IPA Font License v1.0은 폰트 바이너리가 배포되는 곳에 라이선스 전문이 함께 있기를 요구한다. //go:embed로 TTF를 넣는다면 LICENSES/IPA-FONT-1.0.txt도 함께 넣고 NOTICE에서 참조한다.",{},{"title":4055,"description":5266},"ko/blog/009.ipaex-gothic-gpdf",[1325,1326,1327],"dE7TE_idJEGimuYycCNJCNfMqSTeI2W1tJNtMSjpqso",{"id":5293,"title":5294,"author":5295,"body":5296,"date":5780,"description":6632,"draft":1295,"extension":1296,"howTo":6633,"image":1319,"meta":6652,"navigation":86,"path":6653,"seo":6654,"stem":6655,"tags":6656,"updated":1319,"__hash__":6658},"blogKo/ko/blog/005.12-column-grid.md","gpdf의 12 컬럼 그리드는 어떻게 동작하나요?",{"name":8,"url":9},{"type":11,"value":5297,"toc":6620},[5298,5300,5310,5314,5328,5332,6276,6281,6285,6291,6295,6314,6328,6332,6335,6349,6363,6367,6373,6379,6533,6537,6543,6554,6557,6563,6567,6589,6593,6596,6608,6617],[14,5299,17],{"id":16},[19,5301,5302,5303,854,5306,5309],{},"gpdf API를 보면 — 페이지 빌더, 로우 빌더, 컬럼 빌더 — 컬럼 생성자가 숫자를 받습니다: ",[47,5304,5305],{},"r.Col(4, fn)",[47,5307,5308],{},"r.Col(8, fn)",". 이 숫자는 무엇이고, 합이 12가 안 되면 어떻게 되며, CSS에서 익숙한 그리드와는 어떻게 다른가?",[14,5311,5313],{"id":5312},"짧은-답","짧은 답",[19,5315,5316,5319,5320,5323,5324,5327],{},[47,5317,5318],{},"r.Col(span, fn)","은 1부터 12까지의 정수를 받습니다. 이 정수가 행 너비에서 해당 컬럼이 차지하는 비율 — ",[47,5321,5322],{},"span / 12",". 1 미만은 1로, 12 초과는 12로 클램프되고, ",[30,5325,5326],{},"행별 합계를 12로 맞출지 여부를 라이브러리는 강제하지 않습니다",". 그리드는 12 분할로 고정되어 있을 뿐, 나머지는 행을 어떻게 자를지에 대한 선택입니다.",[14,5329,5331],{"id":5330},"동작하는-예제","동작하는 예제",[60,5333,5335],{"className":62,"code":5334,"language":64,"meta":65,"style":65},"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/template\"\n)\n\nfunc main() {\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(document.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(15))),\n    )\n\n    page := doc.AddPage()\n\n    // 전체 너비\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"세금계산서 #2026-0416\", template.FontSize(18), template.Bold())\n        })\n    })\n\n    // 2 컬럼 헤더 (6 + 6)\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(6, func(c *template.ColBuilder) {\n            c.Text(\"공급받는자\")\n            c.Text(\"Acme 주식회사\")\n        })\n        r.Col(6, func(c *template.ColBuilder) {\n            c.Text(\"발행일\")\n            c.Text(\"2026-04-16\")\n        })\n    })\n\n    // 3 컬럼 요약 (4 + 4 + 4)\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(4, func(c *template.ColBuilder) {\n            c.Text(\"소계\")\n        })\n        r.Col(4, func(c *template.ColBuilder) {\n            c.Text(\"부가세\")\n        })\n        r.Col(4, func(c *template.ColBuilder) {\n            c.Text(\"합계\")\n        })\n    })\n\n    // 비대칭 (8 + 4) — 본문 + 사이드 패널\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(8, func(c *template.ColBuilder) {\n            c.Text(\"품목은 여기에 나열됩니다\")\n        })\n        r.Col(4, func(c *template.ColBuilder) {\n            c.Text(\"비고\")\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"layout.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[47,5336,5337,5343,5347,5353,5361,5369,5373,5381,5389,5397,5401,5405,5415,5429,5447,5478,5482,5486,5500,5504,5509,5533,5563,5603,5607,5611,5615,5620,5644,5675,5694,5713,5717,5747,5766,5785,5789,5793,5797,5802,5826,5857,5877,5882,5913,5933,5938,5969,5988,5993,5998,6003,6009,6034,6066,6086,6091,6122,6142,6147,6152,6157,6176,6189,6204,6209,6251,6266,6271],{"__ignoreMap":65},[69,5338,5339,5341],{"class":71,"line":72},[69,5340,76],{"class":75},[69,5342,80],{"class":79},[69,5344,5345],{"class":71,"line":83},[69,5346,87],{"emptyLinePlaceholder":86},[69,5348,5349,5351],{"class":71,"line":90},[69,5350,94],{"class":93},[69,5352,97],{"class":75},[69,5354,5355,5357,5359],{"class":71,"line":100},[69,5356,103],{"class":75},[69,5358,106],{"class":79},[69,5360,109],{"class":75},[69,5362,5363,5365,5367],{"class":71,"line":112},[69,5364,103],{"class":75},[69,5366,117],{"class":79},[69,5368,109],{"class":75},[69,5370,5371],{"class":71,"line":122},[69,5372,87],{"emptyLinePlaceholder":86},[69,5374,5375,5377,5379],{"class":71,"line":127},[69,5376,103],{"class":75},[69,5378,132],{"class":79},[69,5380,109],{"class":75},[69,5382,5383,5385,5387],{"class":71,"line":137},[69,5384,103],{"class":75},[69,5386,142],{"class":79},[69,5388,109],{"class":75},[69,5390,5391,5393,5395],{"class":71,"line":147},[69,5392,103],{"class":75},[69,5394,152],{"class":79},[69,5396,109],{"class":75},[69,5398,5399],{"class":71,"line":157},[69,5400,160],{"class":75},[69,5402,5403],{"class":71,"line":163},[69,5404,87],{"emptyLinePlaceholder":86},[69,5406,5407,5409,5411,5413],{"class":71,"line":168},[69,5408,171],{"class":75},[69,5410,175],{"class":174},[69,5412,178],{"class":75},[69,5414,181],{"class":75},[69,5416,5417,5419,5421,5423,5425,5427],{"class":71,"line":184},[69,5418,270],{"class":187},[69,5420,197],{"class":75},[69,5422,275],{"class":187},[69,5424,203],{"class":75},[69,5426,280],{"class":174},[69,5428,283],{"class":75},[69,5430,5431,5433,5435,5437,5439,5441,5443,5445],{"class":71,"line":222},[69,5432,289],{"class":187},[69,5434,203],{"class":75},[69,5436,294],{"class":174},[69,5438,209],{"class":75},[69,5440,321],{"class":187},[69,5442,203],{"class":75},[69,5444,303],{"class":187},[69,5446,306],{"class":75},[69,5448,5449,5451,5453,5455,5457,5459,5461,5463,5465,5467,5469,5471,5473,5476],{"class":71,"line":238},[69,5450,289],{"class":187},[69,5452,203],{"class":75},[69,5454,316],{"class":174},[69,5456,209],{"class":75},[69,5458,321],{"class":187},[69,5460,203],{"class":75},[69,5462,326],{"class":174},[69,5464,209],{"class":75},[69,5466,321],{"class":187},[69,5468,203],{"class":75},[69,5470,335],{"class":174},[69,5472,209],{"class":75},[69,5474,5475],{"class":340},"15",[69,5477,344],{"class":75},[69,5479,5480],{"class":71,"line":256},[69,5481,401],{"class":75},[69,5483,5484],{"class":71,"line":262},[69,5485,87],{"emptyLinePlaceholder":86},[69,5487,5488,5490,5492,5494,5496,5498],{"class":71,"line":267},[69,5489,412],{"class":187},[69,5491,197],{"class":75},[69,5493,417],{"class":187},[69,5495,203],{"class":75},[69,5497,422],{"class":174},[69,5499,425],{"class":75},[69,5501,5502],{"class":71,"line":286},[69,5503,87],{"emptyLinePlaceholder":86},[69,5505,5506],{"class":71,"line":309},[69,5507,5508],{"class":2211},"    // 전체 너비\n",[69,5510,5511,5513,5515,5517,5519,5521,5523,5525,5527,5529,5531],{"class":71,"line":347},[69,5512,431],{"class":187},[69,5514,203],{"class":75},[69,5516,436],{"class":174},[69,5518,439],{"class":75},[69,5520,443],{"class":442},[69,5522,446],{"class":75},[69,5524,449],{"class":79},[69,5526,203],{"class":75},[69,5528,454],{"class":79},[69,5530,457],{"class":75},[69,5532,181],{"class":75},[69,5534,5535,5537,5539,5541,5543,5545,5547,5549,5551,5553,5555,5557,5559,5561],{"class":71,"line":373},[69,5536,465],{"class":187},[69,5538,203],{"class":75},[69,5540,470],{"class":174},[69,5542,209],{"class":75},[69,5544,475],{"class":340},[69,5546,191],{"class":75},[69,5548,480],{"class":75},[69,5550,483],{"class":442},[69,5552,446],{"class":75},[69,5554,449],{"class":79},[69,5556,203],{"class":75},[69,5558,492],{"class":79},[69,5560,457],{"class":75},[69,5562,181],{"class":75},[69,5564,5565,5567,5569,5571,5573,5575,5578,5580,5582,5584,5586,5588,5590,5593,5595,5597,5599,5601],{"class":71,"line":398},[69,5566,502],{"class":187},[69,5568,203],{"class":75},[69,5570,507],{"class":174},[69,5572,209],{"class":75},[69,5574,212],{"class":75},[69,5576,5577],{"class":215},"세금계산서 #2026-0416",[69,5579,212],{"class":75},[69,5581,191],{"class":75},[69,5583,521],{"class":187},[69,5585,203],{"class":75},[69,5587,526],{"class":174},[69,5589,209],{"class":75},[69,5591,5592],{"class":340},"18",[69,5594,534],{"class":75},[69,5596,521],{"class":187},[69,5598,203],{"class":75},[69,5600,541],{"class":174},[69,5602,544],{"class":75},[69,5604,5605],{"class":71,"line":404},[69,5606,570],{"class":75},[69,5608,5609],{"class":71,"line":409},[69,5610,576],{"class":75},[69,5612,5613],{"class":71,"line":428},[69,5614,87],{"emptyLinePlaceholder":86},[69,5616,5617],{"class":71,"line":462},[69,5618,5619],{"class":2211},"    // 2 컬럼 헤더 (6 + 6)\n",[69,5621,5622,5624,5626,5628,5630,5632,5634,5636,5638,5640,5642],{"class":71,"line":499},[69,5623,431],{"class":187},[69,5625,203],{"class":75},[69,5627,436],{"class":174},[69,5629,439],{"class":75},[69,5631,443],{"class":442},[69,5633,446],{"class":75},[69,5635,449],{"class":79},[69,5637,203],{"class":75},[69,5639,454],{"class":79},[69,5641,457],{"class":75},[69,5643,181],{"class":75},[69,5645,5646,5648,5650,5652,5654,5657,5659,5661,5663,5665,5667,5669,5671,5673],{"class":71,"line":547},[69,5647,465],{"class":187},[69,5649,203],{"class":75},[69,5651,470],{"class":174},[69,5653,209],{"class":75},[69,5655,5656],{"class":340},"6",[69,5658,191],{"class":75},[69,5660,480],{"class":75},[69,5662,483],{"class":442},[69,5664,446],{"class":75},[69,5666,449],{"class":79},[69,5668,203],{"class":75},[69,5670,492],{"class":79},[69,5672,457],{"class":75},[69,5674,181],{"class":75},[69,5676,5677,5679,5681,5683,5685,5687,5690,5692],{"class":71,"line":567},[69,5678,502],{"class":187},[69,5680,203],{"class":75},[69,5682,507],{"class":174},[69,5684,209],{"class":75},[69,5686,212],{"class":75},[69,5688,5689],{"class":215},"공급받는자",[69,5691,212],{"class":75},[69,5693,160],{"class":75},[69,5695,5696,5698,5700,5702,5704,5706,5709,5711],{"class":71,"line":573},[69,5697,502],{"class":187},[69,5699,203],{"class":75},[69,5701,507],{"class":174},[69,5703,209],{"class":75},[69,5705,212],{"class":75},[69,5707,5708],{"class":215},"Acme 주식회사",[69,5710,212],{"class":75},[69,5712,160],{"class":75},[69,5714,5715],{"class":71,"line":579},[69,5716,570],{"class":75},[69,5718,5719,5721,5723,5725,5727,5729,5731,5733,5735,5737,5739,5741,5743,5745],{"class":71,"line":584},[69,5720,465],{"class":187},[69,5722,203],{"class":75},[69,5724,470],{"class":174},[69,5726,209],{"class":75},[69,5728,5656],{"class":340},[69,5730,191],{"class":75},[69,5732,480],{"class":75},[69,5734,483],{"class":442},[69,5736,446],{"class":75},[69,5738,449],{"class":79},[69,5740,203],{"class":75},[69,5742,492],{"class":79},[69,5744,457],{"class":75},[69,5746,181],{"class":75},[69,5748,5749,5751,5753,5755,5757,5759,5762,5764],{"class":71,"line":605},[69,5750,502],{"class":187},[69,5752,203],{"class":75},[69,5754,507],{"class":174},[69,5756,209],{"class":75},[69,5758,212],{"class":75},[69,5760,5761],{"class":215},"발행일",[69,5763,212],{"class":75},[69,5765,160],{"class":75},[69,5767,5768,5770,5772,5774,5776,5778,5781,5783],{"class":71,"line":618},[69,5769,502],{"class":187},[69,5771,203],{"class":75},[69,5773,507],{"class":174},[69,5775,209],{"class":75},[69,5777,212],{"class":75},[69,5779,5780],{"class":215},"2026-04-16",[69,5782,212],{"class":75},[69,5784,160],{"class":75},[69,5786,5787],{"class":71,"line":633},[69,5788,570],{"class":75},[69,5790,5791],{"class":71,"line":638},[69,5792,576],{"class":75},[69,5794,5795],{"class":71,"line":684},[69,5796,87],{"emptyLinePlaceholder":86},[69,5798,5799],{"class":71,"line":699},[69,5800,5801],{"class":2211},"    // 3 컬럼 요약 (4 + 4 + 4)\n",[69,5803,5804,5806,5808,5810,5812,5814,5816,5818,5820,5822,5824],{"class":71,"line":704},[69,5805,431],{"class":187},[69,5807,203],{"class":75},[69,5809,436],{"class":174},[69,5811,439],{"class":75},[69,5813,443],{"class":442},[69,5815,446],{"class":75},[69,5817,449],{"class":79},[69,5819,203],{"class":75},[69,5821,454],{"class":79},[69,5823,457],{"class":75},[69,5825,181],{"class":75},[69,5827,5828,5830,5832,5834,5836,5839,5841,5843,5845,5847,5849,5851,5853,5855],{"class":71,"line":4616},[69,5829,465],{"class":187},[69,5831,203],{"class":75},[69,5833,470],{"class":174},[69,5835,209],{"class":75},[69,5837,5838],{"class":340},"4",[69,5840,191],{"class":75},[69,5842,480],{"class":75},[69,5844,483],{"class":442},[69,5846,446],{"class":75},[69,5848,449],{"class":79},[69,5850,203],{"class":75},[69,5852,492],{"class":79},[69,5854,457],{"class":75},[69,5856,181],{"class":75},[69,5858,5860,5862,5864,5866,5868,5870,5873,5875],{"class":71,"line":5859},42,[69,5861,502],{"class":187},[69,5863,203],{"class":75},[69,5865,507],{"class":174},[69,5867,209],{"class":75},[69,5869,212],{"class":75},[69,5871,5872],{"class":215},"소계",[69,5874,212],{"class":75},[69,5876,160],{"class":75},[69,5878,5880],{"class":71,"line":5879},43,[69,5881,570],{"class":75},[69,5883,5885,5887,5889,5891,5893,5895,5897,5899,5901,5903,5905,5907,5909,5911],{"class":71,"line":5884},44,[69,5886,465],{"class":187},[69,5888,203],{"class":75},[69,5890,470],{"class":174},[69,5892,209],{"class":75},[69,5894,5838],{"class":340},[69,5896,191],{"class":75},[69,5898,480],{"class":75},[69,5900,483],{"class":442},[69,5902,446],{"class":75},[69,5904,449],{"class":79},[69,5906,203],{"class":75},[69,5908,492],{"class":79},[69,5910,457],{"class":75},[69,5912,181],{"class":75},[69,5914,5916,5918,5920,5922,5924,5926,5929,5931],{"class":71,"line":5915},45,[69,5917,502],{"class":187},[69,5919,203],{"class":75},[69,5921,507],{"class":174},[69,5923,209],{"class":75},[69,5925,212],{"class":75},[69,5927,5928],{"class":215},"부가세",[69,5930,212],{"class":75},[69,5932,160],{"class":75},[69,5934,5936],{"class":71,"line":5935},46,[69,5937,570],{"class":75},[69,5939,5941,5943,5945,5947,5949,5951,5953,5955,5957,5959,5961,5963,5965,5967],{"class":71,"line":5940},47,[69,5942,465],{"class":187},[69,5944,203],{"class":75},[69,5946,470],{"class":174},[69,5948,209],{"class":75},[69,5950,5838],{"class":340},[69,5952,191],{"class":75},[69,5954,480],{"class":75},[69,5956,483],{"class":442},[69,5958,446],{"class":75},[69,5960,449],{"class":79},[69,5962,203],{"class":75},[69,5964,492],{"class":79},[69,5966,457],{"class":75},[69,5968,181],{"class":75},[69,5970,5972,5974,5976,5978,5980,5982,5984,5986],{"class":71,"line":5971},48,[69,5973,502],{"class":187},[69,5975,203],{"class":75},[69,5977,507],{"class":174},[69,5979,209],{"class":75},[69,5981,212],{"class":75},[69,5983,2682],{"class":215},[69,5985,212],{"class":75},[69,5987,160],{"class":75},[69,5989,5991],{"class":71,"line":5990},49,[69,5992,570],{"class":75},[69,5994,5996],{"class":71,"line":5995},50,[69,5997,576],{"class":75},[69,5999,6001],{"class":71,"line":6000},51,[69,6002,87],{"emptyLinePlaceholder":86},[69,6004,6006],{"class":71,"line":6005},52,[69,6007,6008],{"class":2211},"    // 비대칭 (8 + 4) — 본문 + 사이드 패널\n",[69,6010,6012,6014,6016,6018,6020,6022,6024,6026,6028,6030,6032],{"class":71,"line":6011},53,[69,6013,431],{"class":187},[69,6015,203],{"class":75},[69,6017,436],{"class":174},[69,6019,439],{"class":75},[69,6021,443],{"class":442},[69,6023,446],{"class":75},[69,6025,449],{"class":79},[69,6027,203],{"class":75},[69,6029,454],{"class":79},[69,6031,457],{"class":75},[69,6033,181],{"class":75},[69,6035,6037,6039,6041,6043,6045,6048,6050,6052,6054,6056,6058,6060,6062,6064],{"class":71,"line":6036},54,[69,6038,465],{"class":187},[69,6040,203],{"class":75},[69,6042,470],{"class":174},[69,6044,209],{"class":75},[69,6046,6047],{"class":340},"8",[69,6049,191],{"class":75},[69,6051,480],{"class":75},[69,6053,483],{"class":442},[69,6055,446],{"class":75},[69,6057,449],{"class":79},[69,6059,203],{"class":75},[69,6061,492],{"class":79},[69,6063,457],{"class":75},[69,6065,181],{"class":75},[69,6067,6069,6071,6073,6075,6077,6079,6082,6084],{"class":71,"line":6068},55,[69,6070,502],{"class":187},[69,6072,203],{"class":75},[69,6074,507],{"class":174},[69,6076,209],{"class":75},[69,6078,212],{"class":75},[69,6080,6081],{"class":215},"품목은 여기에 나열됩니다",[69,6083,212],{"class":75},[69,6085,160],{"class":75},[69,6087,6089],{"class":71,"line":6088},56,[69,6090,570],{"class":75},[69,6092,6094,6096,6098,6100,6102,6104,6106,6108,6110,6112,6114,6116,6118,6120],{"class":71,"line":6093},57,[69,6095,465],{"class":187},[69,6097,203],{"class":75},[69,6099,470],{"class":174},[69,6101,209],{"class":75},[69,6103,5838],{"class":340},[69,6105,191],{"class":75},[69,6107,480],{"class":75},[69,6109,483],{"class":442},[69,6111,446],{"class":75},[69,6113,449],{"class":79},[69,6115,203],{"class":75},[69,6117,492],{"class":79},[69,6119,457],{"class":75},[69,6121,181],{"class":75},[69,6123,6125,6127,6129,6131,6133,6135,6138,6140],{"class":71,"line":6124},58,[69,6126,502],{"class":187},[69,6128,203],{"class":75},[69,6130,507],{"class":174},[69,6132,209],{"class":75},[69,6134,212],{"class":75},[69,6136,6137],{"class":215},"비고",[69,6139,212],{"class":75},[69,6141,160],{"class":75},[69,6143,6145],{"class":71,"line":6144},59,[69,6146,570],{"class":75},[69,6148,6150],{"class":71,"line":6149},60,[69,6151,576],{"class":75},[69,6153,6155],{"class":71,"line":6154},61,[69,6156,87],{"emptyLinePlaceholder":86},[69,6158,6160,6162,6164,6166,6168,6170,6172,6174],{"class":71,"line":6159},62,[69,6161,587],{"class":187},[69,6163,191],{"class":75},[69,6165,194],{"class":187},[69,6167,197],{"class":75},[69,6169,417],{"class":187},[69,6171,203],{"class":75},[69,6173,600],{"class":174},[69,6175,425],{"class":75},[69,6177,6179,6181,6183,6185,6187],{"class":71,"line":6178},63,[69,6180,225],{"class":93},[69,6182,194],{"class":187},[69,6184,230],{"class":75},[69,6186,233],{"class":75},[69,6188,181],{"class":75},[69,6190,6192,6194,6196,6198,6200,6202],{"class":71,"line":6191},64,[69,6193,241],{"class":187},[69,6195,203],{"class":75},[69,6197,246],{"class":174},[69,6199,209],{"class":75},[69,6201,251],{"class":187},[69,6203,160],{"class":75},[69,6205,6207],{"class":71,"line":6206},65,[69,6208,259],{"class":75},[69,6210,6212,6214,6216,6218,6220,6222,6224,6226,6228,6231,6233,6235,6237,6239,6241,6243,6245,6247,6249],{"class":71,"line":6211},66,[69,6213,225],{"class":93},[69,6215,194],{"class":187},[69,6217,197],{"class":75},[69,6219,200],{"class":187},[69,6221,203],{"class":75},[69,6223,651],{"class":174},[69,6225,209],{"class":75},[69,6227,212],{"class":75},[69,6229,6230],{"class":215},"layout.pdf",[69,6232,212],{"class":75},[69,6234,191],{"class":75},[69,6236,665],{"class":187},[69,6238,191],{"class":75},[69,6240,670],{"class":340},[69,6242,673],{"class":75},[69,6244,194],{"class":187},[69,6246,230],{"class":75},[69,6248,233],{"class":75},[69,6250,181],{"class":75},[69,6252,6254,6256,6258,6260,6262,6264],{"class":71,"line":6253},67,[69,6255,241],{"class":187},[69,6257,203],{"class":75},[69,6259,246],{"class":174},[69,6261,209],{"class":75},[69,6263,251],{"class":187},[69,6265,160],{"class":75},[69,6267,6269],{"class":71,"line":6268},68,[69,6270,259],{"class":75},[69,6272,6274],{"class":71,"line":6273},69,[69,6275,707],{"class":75},[19,6277,6278,6280],{},[47,6279,717],{},"를 실행하면 서로 다르게 분할된 네 개의 행이 있는 한 페이지 PDF가 나옵니다.",[14,6282,6284],{"id":6283},"왜-12인가","왜 12인가",[19,6286,6287,6288,203],{},"12는 2, 3, 4, 6으로 깔끔하게 나눠집니다. 절반 (6+6), 삼등분 (4+4+4), 사등분 (3+3+3+3), 사이드바 + 본문 (3+9 또는 4+8), 본문 + 레일 (8+4) — 실제 레이아웃은 거의 이 조합 안에 들어옵니다. 인수가 적은 숫자를 고르면 이 중 하나가 쉽게 깨집니다. Bootstrap이 2011년에 12를 택한 것도 같은 이유이고, 지금 \"12 컬럼 그리드\"는 디자이너와 프론트엔드 엔지니어가 공유하는 공통어가 되었습니다. gpdf는 이 관용 표현을 의도적으로 가져왔습니다 — ",[30,6289,6290],{},"출력이 고정 너비의 종이라고 해서 레이아웃의 사고방식이 웹과 다를 이유는 없습니다",[14,6292,6294],{"id":6293},"계산을-구체적으로","계산을 구체적으로",[19,6296,6297,6298,6301,6302,6305,6306,6309,6310,6313],{},"A4 세로에 사면 15 mm 균일 여백이면 사용 가능한 너비는 180 mm. 행 안의 ",[47,6299,6300],{},"Col(4)","는 그중 4/12, 즉 60 mm. ",[47,6303,6304],{},"Col(8)","은 120 mm. 컬럼 사이 거터는 기본적으로 없습니다. 여백을 두고 싶다면 짧은 쪽 컬럼 안에 ",[47,6307,6308],{},"c.Spacer","를 넣거나, ",[47,6311,6312],{},"Col(1)","을 비워두세요.",[19,6315,6316,6317,6320,6321,6324,6325,203],{},"너비는 빌드 시점에 백분율로 계산되고 (구현은 ",[47,6318,6319],{},"gpdf/template/grid.go","), 레이아웃 엔진이 \"현재 페이지 너비에서 여백을 뺀 값\"을 기준으로 실제 포인트 값으로 해석합니다. 따라서 같은 ",[47,6322,6323],{},"r.Col(6, fn)","이라도 A4와 Letter에서 물리 너비는 다르지만 ",[30,6326,6327],{},"행에 대한 비율은 동일합니다",[14,6329,6331],{"id":6330},"합이-12가-아닐-때","합이 12가 아닐 때",[19,6333,6334],{},"gpdf는 span의 합을 검증하지 않습니다. 의도적인 선택입니다.",[1113,6336,6337,6343],{},[886,6338,6339,6342],{},[30,6340,6341],{},"합 \u003C 12",": 행 오른쪽이 빕니다. 왼쪽 끝에만 요소를 고정하고 나머지를 의도적으로 비워두고 싶을 때 유용합니다.",[886,6344,6345,6348],{},[30,6346,6347],{},"합 > 12",": 마지막 컬럼이 오른쪽 여백을 넘어갑니다. 대개 버그입니다. PDF가 어긋나 보이지만 크래시는 나지 않습니다.",[19,6350,6351,6352,6355,6356,6359,6360,6362],{},"대부분의 레이아웃은 행당 정확히 12로 채워집니다. 그게 페이지를 꽉 채우는 방식이기 때문입니다. 다만 \"행 가운데에 폭 6짜리 블록만 두고 싶다\"면 ",[47,6353,6354],{},"Col(3)"," 빔, ",[47,6357,6358],{},"Col(6)"," 내용, ",[47,6361,6354],{}," 빔 — 이런 약식 표기가 가장 자연스럽습니다. 그리드는 이런 표현을 염두에 두고 설계되었습니다.",[14,6364,6366],{"id":6365},"autorow와-row의-차이","AutoRow와 Row의 차이",[19,6368,6369,6372],{},[47,6370,6371],{},"page.AutoRow(fn)","은 가장 키가 큰 컬럼에 맞춰 행 높이가 늘어납니다. 대부분의 행은 이걸 쓰면 됩니다.",[19,6374,6375,6378],{},[47,6376,6377],{},"page.Row(height, fn)","은 높이를 고정합니다. 높이를 넘는 콘텐츠는 잘립니다. 후공정의 스테이플 위치를 맞추기 위해 헤더를 반드시 30 mm로 유지해야 하는 경우처럼 \"시각적 일관성 > 콘텐츠 자유도\"인 상황에서 씁니다.",[60,6380,6382],{"className":62,"code":6381,"language":64,"meta":65,"style":65},"page.Row(document.Mm(30), func(r *template.RowBuilder) {\n    r.Col(8, func(c *template.ColBuilder) {\n        c.Text(\"로고\")\n    })\n    r.Col(4, func(c *template.ColBuilder) {\n        c.Text(\"세금계산서 번호\")\n    })\n})\n",[47,6383,6384,6423,6453,6472,6476,6506,6525,6529],{"__ignoreMap":65},[69,6385,6386,6388,6390,6392,6394,6396,6398,6400,6402,6405,6407,6409,6411,6413,6415,6417,6419,6421],{"class":71,"line":72},[69,6387,1926],{"class":187},[69,6389,203],{"class":75},[69,6391,2112],{"class":174},[69,6393,209],{"class":75},[69,6395,321],{"class":187},[69,6397,203],{"class":75},[69,6399,335],{"class":174},[69,6401,209],{"class":75},[69,6403,6404],{"class":340},"30",[69,6406,534],{"class":75},[69,6408,480],{"class":75},[69,6410,443],{"class":442},[69,6412,446],{"class":75},[69,6414,449],{"class":79},[69,6416,203],{"class":75},[69,6418,454],{"class":79},[69,6420,457],{"class":75},[69,6422,181],{"class":75},[69,6424,6425,6427,6429,6431,6433,6435,6437,6439,6441,6443,6445,6447,6449,6451],{"class":71,"line":83},[69,6426,3672],{"class":187},[69,6428,203],{"class":75},[69,6430,470],{"class":174},[69,6432,209],{"class":75},[69,6434,6047],{"class":340},[69,6436,191],{"class":75},[69,6438,480],{"class":75},[69,6440,483],{"class":442},[69,6442,446],{"class":75},[69,6444,449],{"class":79},[69,6446,203],{"class":75},[69,6448,492],{"class":79},[69,6450,457],{"class":75},[69,6452,181],{"class":75},[69,6454,6455,6457,6459,6461,6463,6465,6468,6470],{"class":71,"line":90},[69,6456,3703],{"class":187},[69,6458,203],{"class":75},[69,6460,507],{"class":174},[69,6462,209],{"class":75},[69,6464,212],{"class":75},[69,6466,6467],{"class":215},"로고",[69,6469,212],{"class":75},[69,6471,160],{"class":75},[69,6473,6474],{"class":71,"line":100},[69,6475,576],{"class":75},[69,6477,6478,6480,6482,6484,6486,6488,6490,6492,6494,6496,6498,6500,6502,6504],{"class":71,"line":112},[69,6479,3672],{"class":187},[69,6481,203],{"class":75},[69,6483,470],{"class":174},[69,6485,209],{"class":75},[69,6487,5838],{"class":340},[69,6489,191],{"class":75},[69,6491,480],{"class":75},[69,6493,483],{"class":442},[69,6495,446],{"class":75},[69,6497,449],{"class":79},[69,6499,203],{"class":75},[69,6501,492],{"class":79},[69,6503,457],{"class":75},[69,6505,181],{"class":75},[69,6507,6508,6510,6512,6514,6516,6518,6521,6523],{"class":71,"line":122},[69,6509,3703],{"class":187},[69,6511,203],{"class":75},[69,6513,507],{"class":174},[69,6515,209],{"class":75},[69,6517,212],{"class":75},[69,6519,6520],{"class":215},"세금계산서 번호",[69,6522,212],{"class":75},[69,6524,160],{"class":75},[69,6526,6527],{"class":71,"line":127},[69,6528,576],{"class":75},[69,6530,6531],{"class":71,"line":137},[69,6532,3730],{"class":75},[14,6534,6536],{"id":6535},"그리드가-하지-않는-것","그리드가 하지 않는 것",[19,6538,6539,6540,6542],{},"중첩 불가. ",[47,6541,492],{},"는 콘텐츠 요소 (Text / Image / Table / List / Spacer)를 받지만, 안에 또 다른 행을 넣을 수는 없습니다. 중첩이 필요해 보이는 구조는 보통 페이지 레벨에서 두 형제 행으로 나누는 편이 더 깔끔합니다.",[19,6544,6545,6546,6549,6550,6553],{},"오프셋 컬럼 없음. Bootstrap의 ",[47,6547,6548],{},".offset-2","에 대응하는 기능은 없습니다. 오른쪽으로 밀고 싶다면 왼쪽에 빈 ",[47,6551,6552],{},"Col(n)","을 두세요.",[19,6555,6556],{},"브레이크포인트 없음. PDF 페이지는 리사이즈되지 않습니다. 어떤 기기에서 열든 동일한 레이아웃 — 출력이 재배치되는 DOM이 아니라 고정 좌표의 래스터이기 때문입니다.",[19,6558,6559,6562],{},[30,6560,6561],{},"이 \"없음\"들이 설계의 핵심입니다",". 그리드가 갖지 않는 기능 하나당, PDF 결과를 읽을 때 추론해야 할 모호성이 하나씩 줄어듭니다.",[14,6564,6566],{"id":6565},"관련-글","관련 글",[1113,6568,6569,6575,6581],{},[886,6570,6571,6574],{},[22,6572,6573],{"href":1224},"gpdf에 일본어 폰트를 임베드하려면?"," — 그리드 컬럼 안에서 CJK 다루기",[886,6576,6577,6580],{},[22,6578,6579],{"href":2909},"Go PDF 라이브러리 비교 2026"," — Builder API와 gofpdf / gopdf / Maroto 비교",[886,6582,6583,6588],{},[22,6584,6587],{"href":6585,"rel":6586},"https://gpdf.dev/ko/docs/guide/layout",[26],"레이아웃 가이드"," — 행, 컬럼, 간격의 전체 레퍼런스",[14,6590,6592],{"id":6591},"gpdf-써보기","gpdf 써보기",[19,6594,6595],{},"gpdf는 Go PDF 생성 라이브러리입니다. MIT 라이선스, 외부 의존성 0, 네이티브 CJK 지원.",[60,6597,6598],{"className":1250,"code":1251,"language":1252,"meta":65,"style":65},[47,6599,6600],{"__ignoreMap":65},[69,6601,6602,6604,6606],{"class":71,"line":72},[69,6603,64],{"class":79},[69,6605,1261],{"class":215},[69,6607,1264],{"class":215},[19,6609,6610,1271,6614],{},[22,6611,6613],{"href":24,"rel":6612},[26],"⭐ GitHub에서 별 누르기",[22,6615,1276],{"href":1274,"rel":6616},[26],[1278,6618,6619],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .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 .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);}",{"title":65,"searchDepth":83,"depth":83,"links":6621},[6622,6623,6624,6625,6626,6627,6628,6629,6630,6631],{"id":16,"depth":83,"text":17},{"id":5312,"depth":83,"text":5313},{"id":5330,"depth":83,"text":5331},{"id":6283,"depth":83,"text":6284},{"id":6293,"depth":83,"text":6294},{"id":6330,"depth":83,"text":6331},{"id":6365,"depth":83,"text":6366},{"id":6535,"depth":83,"text":6536},{"id":6565,"depth":83,"text":6566},{"id":6591,"depth":83,"text":6592},"gpdf의 12 컬럼 그리드는 r.Col(span, fn)에 1–12 정수를 넘깁니다. 컬럼 너비는 span/12 비율, 거터도 브레이크포인트도 없는 PDF 전용 설계.",{"name":6634,"totalTime":5269,"tools":6635,"steps":6636},"gpdf의 12 컬럼 그리드로 페이지 레이아웃 잡기",[1301,132],[6637,6640,6643,6646,6649],{"name":6638,"text":6639},"페이지에 행 열기","가장 키가 큰 컬럼에 맞춰 행 높이가 자동으로 늘어나길 원하면 page.AutoRow(fn)을, 높이를 고정하려면 page.Row(height, fn)을 호출합니다.",{"name":6641,"text":6642},"r.Col(span, fn)으로 컬럼 선언","행 안에서 컬럼 수만큼 r.Col(span, fn)을 호출합니다. span은 1~12 정수로, 해당 컬럼이 행 너비에서 차지하는 비율을 나타냅니다.",{"name":6644,"text":6645},"행당 span 합을 12 이하로 유지","합이 12 미만이면 오른쪽이 비고, 12를 초과하면 마지막 컬럼이 오른쪽 여백을 넘어갑니다 — 보통 버그 신호입니다.",{"name":6647,"text":6648},"컬럼 안에 콘텐츠 채우기","ColBuilder 콜백 안에서 c.Text, c.Image, c.Table, c.Spacer를 호출합니다. 추가한 순서대로 세로로 쌓입니다.",{"name":6650,"text":6651},"다음 행 시작","다음 시각적 행은 page.AutoRow를 다시 호출합니다. 행은 서로 독립적이어서 4+8 행 바로 밑에 3+3+3+3 행을 두어도 됩니다.",{},"/ko/blog/12-column-grid",{"title":5294,"description":6632},"ko/blog/005.12-column-grid",[1325,1327,6657],"templates","jV5hmGH71S2Zm14imSGwJ_DwbGJm7lp3L2qR0JsZpJc",{"id":6660,"title":6661,"author":6662,"body":6663,"date":5780,"description":8405,"draft":1295,"extension":1296,"howTo":1319,"image":1319,"meta":8406,"navigation":86,"path":8407,"seo":8408,"stem":8409,"tags":8410,"updated":1319,"__hash__":8412},"blogKo/ko/blog/006.go-pdf-fpdf-archived.md","go-pdf/fpdf도 아카이브됐다. 2026년의 Go PDF 스택.",{"name":8,"url":9},{"type":11,"value":6664,"toc":8389},[6665,6667,6688,6692,6710,6713,6730,6740,6744,6747,6780,6786,6790,6796,6811,6814,6823,6829,6833,6836,7012,7015,7027,7039,7045,7051,7054,7058,7064,7067,7111,7114,7295,7308,7312,7327,7330,7335,7654,7659,8038,8053,8062,8066,8073,8156,8163,8169,8172,8180,8183,8232,8239,8243,8250,8259,8270,8272,8284,8290,8300,8319,8327,8333,8335,8337,8349,8357,8361,8386],[14,6666,1338],{"id":1337},[19,6668,6669,6672,6673,1907,6675,6678,6679,1907,6681,6684,6685,6687],{},[47,6670,6671],{},"fpdf"," 계보에서 유지되던 두 포크가 모두 read-only가 됐다. ",[47,6674,1356],{},[30,6676,6677],{},"2021년 9월",", 커뮤니티 포크 ",[47,6680,1434],{},[30,6682,6683],{},"2025년","에 아카이브. \"다음 유지보수자\"는 오지 않는다. 새 Go 프로젝트의 현대 기본값은 ",[30,6686,27],{}," — 순수 Go, 외부 의존 0, CJK 네이티브, 일반적인 워크로드에서 10–30배 빠름. 이 글은 2026년 지형과 \"gpdf를 언제 선택하고 언제 선택하지 않는가\"에 대한 솔직한 답.",[14,6689,6691],{"id":6690},"지금-상황","지금 상황",[19,6693,6694,6695,6698,6699,6702,6703,6706,6707,6709],{},"지난주 팀원이 ",[47,6696,6697],{},"go get github.com/go-pdf/fpdf","을 치다가 GitHub 배너에서 멈췄다: ",[3586,6700,6701],{},"\"This repository has been archived by the owner. It is now read-only.\""," — 이건 ",[3586,6704,6705],{},"고쳐진 쪽"," 이어야 했다. 2021년에 아카이브된 ",[47,6708,1356],{},"의 계보를 이어받기로 했던 커뮤니티 포크.",[19,6711,6712],{},"그것도 아카이브됐다. README는 이제 다른 라이브러리를 찾아보라고 권한다.",[19,6714,6715,6716,6719,6720,6722,6723,6725,6726,6729],{},"지난 5년간 Go로 서비스를 돌리며 PDF를 뽑아왔다면(세금계산서, 리포트, 배송 라벨, 전자문서), ",[47,6717,6718],{},"go.mod"," 맨 아래 줄은 거의 확실히 이 둘 중 하나다. Stack Overflow 답변은 ",[47,6721,1356],{},"을, 좀 더 최근 튜토리얼은 ",[47,6724,1434],{},"을 가리킨다. ",[30,6727,6728],{},"둘 다 이제 공급망 부채다"," — CVE 대응, Go 버전 호환 작업, 성능 수정, 스펙 업데이트가 전부 정지 상태.",[19,6731,6732,6733,6736,6737,203],{},"이 글은 한 줄씩 대응시키는 마이그레이션 가이드가 아니다 — ",[22,6734,6735],{"href":2836},"그건 이미 썼다",". 이 글은 마이그레이션 가이드가 답하지 않는 더 긴 질문에 답한다: ",[30,6738,6739],{},"2026년 Go에서 PDF를 생성할 때 실제로 뭘 쓸 것인가, 그리고 왜 생태계가 여기까지 왔는가",[14,6741,6743],{"id":6742},"아카이브가-실제로-치르는-비용","\"아카이브\"가 실제로 치르는 비용",[19,6745,6746],{},"GitHub의 \"archived\" 라벨은 부드럽게 보인다. import 그래프에 들어 있는 라이브러리 기준으로는, 실제로는 네 가지 구체적인 결과를 뜻한다.",[883,6748,6749,6755,6765,6771],{},[886,6750,6751,6754],{},[30,6752,6753],{},"보안 패치가 없다."," TTF 파서에 메모리 안전 이슈가 생겨도 upstream에 병합되지 않는다. 직접 포크해서 고칠 수는 있지만, 대부분의 팀은 하지 않는다.",[886,6756,6757,6760,6761,6764],{},[30,6758,6759],{},"Go 툴체인 전방 호환이 없다."," Go 1.25의 루프 변수 시맨틱은 지금 gofpdf에서 잘 돈다. 하지만 내일 ",[47,6762,6763],{},"for range"," 주변이나 표준 라이브러리의 deprecation이 뭔가 깨뜨리면, read-only 저장소의 포크를 고치는 건 당신 몫.",[886,6766,6767,6770],{},[30,6768,6769],{},"스펙 업데이트가 없다."," PDF 2.0 (ISO 32000-2)은 2020년에 확정됐다. gofpdf는 대부분 PDF 1.7 수준. 페이지별 연관 파일, 리치 XMP 메타데이터, 현대 디지털 서명(PAdES-B-LT)은 서드파티 접착제에 의존하거나 아예 없다.",[886,6772,6773,6776,6777,6779],{},[30,6774,6775],{},"CJK 진전이 없다."," gofpdf의 Unicode 경로는 단일 바이트 폰트 설계 위에 덧붙인 것이다. 돌긴 하지만 대부분의 실사용 설정에서 서브셋이 아니라 전체 폰트를 임베드한다. 특정 CJK TTF에서 글리프 ID 충돌로 출력이 깨지기도 한다. ",[47,6778,1434],{},"은 같은 아키텍처를 그대로 물려받았다.",[19,6781,6782,6783,203],{},"보안과 전방 호환은 컴플라이언스 미팅에서 아프게 찔린다. \"우리 PDF 라이브러리는 아카이브됐고 CVE 패치가 안 옵니다\"는 감사 담당이 듣고 싶어하는 답이 아니다. ",[30,6784,6785],{},"특히 전자세금계산서나 전자문서 인증 범위 안에 PDF가 들어 있다면, 이 논점은 더 미룰 수 없다",[14,6787,6789],{"id":6788},"왜-두-포크가-다-죽었나","왜 두 포크가 다 죽었나",[19,6791,6792,6793,203],{},"아카이브를 메인테이너 번아웃 한 가지로 설명하고 싶어진다 — PR 리뷰에 지친 한 사람, 버스 팩터 1이 오프라인으로 간다. 그것도 이유지만 전부는 아니다. ",[30,6794,6795],{},"아키텍처가 따라잡는 걸 어렵게 만들었다",[19,6797,6798,6800,6801,854,6804,854,6807,6810],{},[47,6799,1356],{},"은 FPDF — 2002년 PHP 라이브러리의 포팅이었다. PHP 원본은 페이지 위에서 커서를 밀면서 절차적으로 콘텐츠를 토해낸다: ",[47,6802,6803],{},"SetXY(x, y)",[47,6805,6806],{},"Cell(w, h, text)",[47,6808,6809],{},"Ln(h)",". 그 모델은 2002년 PHP에서는 합리적 타협이었다 — 당시 대안은 원시 PostScript 아니면 상용 툴킷. Go로 포팅되면서 커서가 남았고, 단일 바이트 폰트 테이블도 남았고, 수동 페이지 브레이크 관리도 남았다.",[19,6812,6813],{},"해가 갈수록 \"사람들이 생성하고 싶은 것\"과 \"커서 모델로 표현 가능한 것\" 사이 격차는 커졌다. 세금계산서는 테이블. 리포트는 반복 헤더/푸터 붙은 그리드. 배송 라벨은 QR 코드 + 현지 언어 텍스트. 커서는 헬퍼로 감싸지고, 그 헬퍼는 튜토리얼로 감싸지고, 2023년 쯤에는 사람들이 \"gofpdf에 대해 쓴 코드\"는 사실 gofpdf가 아니었다 — 팀마다 쌓은 접착제 레이어였고, 그 레이어가 커서를 레이아웃 엔진인 척 위장시키려 했다.",[19,6815,6816,6818,6819,6822],{},[47,6817,1434],{},"은 이걸 그대로 이어받았다. 포크는 내부를 리팩토링하고 오래된 버그를 고쳤지만, ",[30,6820,6821],{},"공개 API의 형태는 바꿀 수 없었다"," — 바꾸는 순간 모든 하위 프로젝트가 깨지기 때문이다. 라이브러리의 모양은 2002년 PHP에 동결됐고, 그 모양을 유지하는 비용이 혜택보다 빠르게 증가했다.",[19,6824,6825,6828],{},[30,6826,6827],{},"즉",": 유지보수자 두 명, 아카이브 두 번, 아키텍처상의 이유 하나. 2026년에 다시 시작한다면, PDF가 실제로 생성되는 방식에 맞는 접근을 골라야 한다 — 오늘의 방식은 플로터를 움직이는 것보다 웹 페이지를 조립하는 쪽에 훨씬 가깝다.",[14,6830,6832],{"id":6831},"_2026년-go-pdf-지형","2026년 Go PDF 지형",[19,6834,6835],{},"뭔가를 추천하기 전에 일단 판부터 깔자. \"유지보수 중\"은 \"최근 6개월 내 커밋이 있고 이슈에 응답이 있다\"는 느슨한 의미로 쓴다.",[741,6837,6838,6858],{},[744,6839,6840],{},[747,6841,6842,6845,6848,6850,6853,6856],{},[750,6843,6844],{},"라이브러리",[750,6846,6847],{},"상태 (2026-04)",[750,6849,5142],{},[750,6851,6852],{},"CJK 네이티브",[750,6854,6855],{},"의존성 0",[750,6857,6137],{},[759,6859,6860,6883,6903,6922,6946,6967,6990],{},[747,6861,6862,6866,6871,6874,6877,6880],{},[764,6863,6864],{},[47,6865,1356],{},[764,6867,6868],{},[30,6869,6870],{},"2021 아카이브",[764,6872,6873],{},"MIT",[764,6875,6876],{},"덧붙임",[764,6878,6879],{},"예",[764,6881,6882],{},"원본. 대부분 로케일에서 여전히 검색 1위.",[747,6884,6885,6889,6894,6896,6898,6900],{},[764,6886,6887],{},[47,6888,1434],{},[764,6890,6891],{},[30,6892,6893],{},"2025 아카이브",[764,6895,6873],{},[764,6897,6876],{},[764,6899,6879],{},[764,6901,6902],{},"위의 커뮤니티 포크. 같은 아키텍처, 같은 천장.",[747,6904,6905,6909,6912,6914,6917,6919],{},[764,6906,6907],{},[47,6908,1437],{},[764,6910,6911],{},"유지보수 중",[764,6913,6873],{},[764,6915,6916],{},"부분",[764,6918,6879],{},[764,6920,6921],{},"저수준. 좌표를 직접 쓴다. 폼 오버레이에 적합.",[747,6923,6924,6930,6932,6934,6937,6940],{},[764,6925,6926,6929],{},[47,6927,6928],{},"johnfercher/maroto"," v2",[764,6931,6911],{},[764,6933,6873],{},[764,6935,6936],{},"gofpdf 경유",[764,6938,6939],{},"아니오",[764,6941,6942,6943,6945],{},"그리드 우선 빌더, 하지만 바닥에 ",[47,6944,1434],{},"가 있음.",[747,6947,6948,6953,6955,6960,6962,6964],{},[764,6949,6950],{},[47,6951,6952],{},"unidoc/unipdf",[764,6954,6911],{},[764,6956,6957],{},[30,6958,6959],{},"상용",[764,6961,6879],{},[764,6963,6939],{},[764,6965,6966],{},"기능 완비 PDF SDK. 상용 사용에는 유료 라이선스 필수.",[747,6968,6969,6975,6977,6980,6982,6987],{},[764,6970,6971,6974],{},[47,6972,6973],{},"chromedp"," + Chromium",[764,6976,6911],{},[764,6978,6979],{},"MIT + Chrome",[764,6981,6879],{},[764,6983,6984],{},[30,6985,6986],{},"아니오 — 브라우저 동봉",[764,6988,6989],{},"헤드리스 Chrome으로 HTML→PDF. 런타임 거대.",[747,6991,6992,6996,6998,7000,7005,7009],{},[764,6993,6994],{},[47,6995,27],{},[764,6997,6911],{},[764,6999,6873],{},[764,7001,7002],{},[30,7003,7004],{},"네이티브",[764,7006,7007],{},[30,7008,6879],{},[764,7010,7011],{},"순수 Go 재구현. 빌더 API, 12 컬럼 그리드.",[19,7013,7014],{},"표만 봐도 몇 가지는 명확하다.",[19,7016,7017,7020,7021,7023,7024,7026],{},[30,7018,7019],{},"유지보수되는 모든 선택지는 상용 라이선스이거나, 거대한 런타임을 끌고 오거나, 곧 낡아질 기반 위에 서 있다",". 예외는 ",[47,7022,1437],{}," — 진짜로 유지되고 의존성도 가볍다. 하지만 좌표 수준 라이브러리다. 패키지 이름만 바꿔 ",[47,7025,1930],{},"를 다시 쓰는 셈이다.",[19,7028,7029,7032,7033,7035,7036,7038],{},[30,7030,7031],{},"Maroto v2는 API가 좋은 그리드 우선 빌더",". 문제는 ",[47,7034,6718],{}," 바닥에 ",[47,7037,1434],{},"가 있다는 것. fpdf의 성능 천장과 CJK 한계가 그대로 Maroto의 천장이 된다. v3가 이걸 벗어날 가능성은 있지만, 아직 안 나왔다.",[19,7040,7041,7044],{},[30,7042,7043],{},"unipdf는 풍부하지만 상용에는 MIT 호환이 아니다",". 시트 단위 또는 배포 단위 과금. 매출이 그걸 받쳐준다면 괜찮은 선택, OSS 사이드 프로젝트나 초기 스타트업에는 라이선스 계산이 안 맞는다.",[19,7046,7047,7050],{},[30,7048,7049],{},"chromedp는 돌긴 하지만 브라우저를 출하하는 것",". 100 MB 베이스 이미지가 1 GB+로 불어난다. 서버리스 콜드 스타트가 괴롭다. 폰트도 따로 컨테이너에 넣어야 한다. 장점은 React 템플릿을 재사용할 수 있다는 것, 단점은 세금계산서 한 장을 뽑으려고 Chromium을 계속 돌린다는 것.",[19,7052,7053],{},"빈 자리는 명확하다: 순수 Go, 의존성 0, CJK 네이티브, 그리드 우선, 상용 라이선스도 브라우저 런타임도 필요 없는 라이브러리. 그게 gpdf다.",[14,7055,7057],{"id":7056},"gpdf란-무엇인가","gpdf란 무엇인가",[19,7059,7060,7061,7063],{},"gpdf (",[47,7062,132],{},")는 깨끗한 재구현. 포크가 아니다. PDF 와이어 포맷 writer, 레이아웃 엔진, TrueType 서브세터 — 전부 순수 Go로 처음부터 썼다.",[19,7065,7066],{},"대부분의 팀에 중요한 세 가지 속성:",[1113,7068,7069,7082,7099],{},[886,7070,7071,2125,7074,7077,7078,7081],{},[30,7072,7073],{},"순수 Go, CGO 없음",[47,7075,7076],{},"go build","는 정적. ",[47,7079,7080],{},"GOOS=linux GOARCH=arm64 go build","가 MacBook에서 툴체인 세팅 없이 통과한다. Docker 이미지도 작게 유지 — 12 MB distroless 컨테이너가 구동한다.",[886,7083,7084,2125,7087,7090,7091,7094,7095,7098],{},[30,7085,7086],{},"외부 의존성 0",[47,7088,7089],{},"go get github.com/gpdf-dev/gpdf"," 후 ",[47,7092,7093],{},"go mod graph","는 한 줄만 출력: gpdf 자체. 코어는 ",[47,7096,7097],{},"std","만 사용. (HTML→PDF나 디지털 서명 같은 옵션 애드온은 작은 의존성을 가져오지만 모두 opt-in.)",[886,7100,7101,2125,7104,7106,7107,7110],{},[30,7102,7103],{},"네이티브 CJK",[47,7105,354],{},"가 document 구축 시점에 TrueType 폰트를 등록한다. 서브셋 임베딩은 렌더링 시점에 자동. ",[30,7108,7109],{},"200자짜리 한국어/일본어 세금계산서의 임베디드 폰트는 약 30 KB 서브셋",". 5 MB 풀 폰트가 아니다.",[19,7112,7113],{},"API는 선언적. 행/컬럼의 트리를 기술하면 레이아웃 엔진이 배치한다. 그리드는 12 컬럼 — Bootstrap이 2011년부터 출시한 같은 idiom. HTML/CSS를 한 줄이라도 써본 사람이면 gpdf API는 익숙하다:",[60,7115,7117],{"className":62,"code":7116,"language":64,"meta":65,"style":65},"page := doc.AddPage()\npage.AutoRow(func(r *template.RowBuilder) {\n    r.Col(8, func(c *template.ColBuilder) {\n        c.Text(\"세금계산서 #2026-0416\", template.FontSize(18), template.Bold())\n    })\n    r.Col(4, func(c *template.ColBuilder) {\n        c.Text(\"2026-04-16\", template.AlignRight())\n    })\n})\n",[47,7118,7119,7134,7158,7188,7226,7230,7260,7287,7291],{"__ignoreMap":65},[69,7120,7121,7124,7126,7128,7130,7132],{"class":71,"line":72},[69,7122,7123],{"class":187},"page ",[69,7125,197],{"class":75},[69,7127,417],{"class":187},[69,7129,203],{"class":75},[69,7131,422],{"class":174},[69,7133,425],{"class":75},[69,7135,7136,7138,7140,7142,7144,7146,7148,7150,7152,7154,7156],{"class":71,"line":83},[69,7137,1926],{"class":187},[69,7139,203],{"class":75},[69,7141,436],{"class":174},[69,7143,439],{"class":75},[69,7145,443],{"class":442},[69,7147,446],{"class":75},[69,7149,449],{"class":79},[69,7151,203],{"class":75},[69,7153,454],{"class":79},[69,7155,457],{"class":75},[69,7157,181],{"class":75},[69,7159,7160,7162,7164,7166,7168,7170,7172,7174,7176,7178,7180,7182,7184,7186],{"class":71,"line":90},[69,7161,3672],{"class":187},[69,7163,203],{"class":75},[69,7165,470],{"class":174},[69,7167,209],{"class":75},[69,7169,6047],{"class":340},[69,7171,191],{"class":75},[69,7173,480],{"class":75},[69,7175,483],{"class":442},[69,7177,446],{"class":75},[69,7179,449],{"class":79},[69,7181,203],{"class":75},[69,7183,492],{"class":79},[69,7185,457],{"class":75},[69,7187,181],{"class":75},[69,7189,7190,7192,7194,7196,7198,7200,7202,7204,7206,7208,7210,7212,7214,7216,7218,7220,7222,7224],{"class":71,"line":100},[69,7191,3703],{"class":187},[69,7193,203],{"class":75},[69,7195,507],{"class":174},[69,7197,209],{"class":75},[69,7199,212],{"class":75},[69,7201,5577],{"class":215},[69,7203,212],{"class":75},[69,7205,191],{"class":75},[69,7207,521],{"class":187},[69,7209,203],{"class":75},[69,7211,526],{"class":174},[69,7213,209],{"class":75},[69,7215,5592],{"class":340},[69,7217,534],{"class":75},[69,7219,521],{"class":187},[69,7221,203],{"class":75},[69,7223,541],{"class":174},[69,7225,544],{"class":75},[69,7227,7228],{"class":71,"line":112},[69,7229,576],{"class":75},[69,7231,7232,7234,7236,7238,7240,7242,7244,7246,7248,7250,7252,7254,7256,7258],{"class":71,"line":122},[69,7233,3672],{"class":187},[69,7235,203],{"class":75},[69,7237,470],{"class":174},[69,7239,209],{"class":75},[69,7241,5838],{"class":340},[69,7243,191],{"class":75},[69,7245,480],{"class":75},[69,7247,483],{"class":442},[69,7249,446],{"class":75},[69,7251,449],{"class":79},[69,7253,203],{"class":75},[69,7255,492],{"class":79},[69,7257,457],{"class":75},[69,7259,181],{"class":75},[69,7261,7262,7264,7266,7268,7270,7272,7274,7276,7278,7280,7282,7285],{"class":71,"line":127},[69,7263,3703],{"class":187},[69,7265,203],{"class":75},[69,7267,507],{"class":174},[69,7269,209],{"class":75},[69,7271,212],{"class":75},[69,7273,5780],{"class":215},[69,7275,212],{"class":75},[69,7277,191],{"class":75},[69,7279,521],{"class":187},[69,7281,203],{"class":75},[69,7283,7284],{"class":174},"AlignRight",[69,7286,544],{"class":75},[69,7288,7289],{"class":71,"line":137},[69,7290,576],{"class":75},[69,7292,7293],{"class":71,"line":147},[69,7294,3730],{"class":75},[19,7296,7297,7298,7300,7301,7304,7305,7307],{},"그리드 상세는 ",[22,7299,5294],{"href":6653},". 한 줄 요약: ",[47,7302,7303],{},"Col(span, fn)","은 1–12 사이의 span을 받고, ",[47,7306,5322],{},"가 그 컬럼이 행 너비에서 차지하는 비율.",[14,7309,7311],{"id":7310},"최소-go-pdffpdf-gpdf-디프","최소 go-pdf/fpdf → gpdf 디프",[19,7313,7314,7316,7317,7319,7320,7322,7323,7326],{},[47,7315,1434],{},"에서 오는 사람에게는 (",[47,7318,1356],{},"이 아니라) 좋은 소식이 있다: API 표면이 거의 같다. ",[47,7321,1434],{},"은 호출 측에서 보면 아무것도 바꾸지 않은 포크다. gpdf로의 마이그레이션은 ",[22,7324,7325],{"href":2836},"gofpdf 가이드","와 동일하고, import 경로를 한 줄 바꾸는 것에서 시작한다.",[19,7328,7329],{},"가장 작은 디프 — \"PDF 반환\" HTTP 핸들러:",[19,7331,7332],{},[30,7333,7334],{},"Before — go-pdf/fpdf:",[60,7336,7338],{"className":62,"code":7337,"language":64,"meta":65,"style":65},"package main\n\nimport (\n    \"net/http\"\n\n    \"github.com/go-pdf/fpdf\"\n)\n\nfunc handler(w http.ResponseWriter, r *http.Request) {\n    pdf := fpdf.New(\"P\", \"mm\", \"A4\", \"\")\n    pdf.AddPage()\n    pdf.SetFont(\"Arial\", \"B\", 16)\n    pdf.Cell(40, 10, \"Hello, World!\")\n\n    w.Header().Set(\"Content-Type\", \"application/pdf\")\n    if err := pdf.Output(w); err != nil {\n        http.Error(w, err.Error(), 500)\n    }\n}\n",[47,7339,7340,7346,7350,7356,7365,7369,7378,7382,7386,7424,7473,7484,7517,7546,7550,7586,7615,7646,7650],{"__ignoreMap":65},[69,7341,7342,7344],{"class":71,"line":72},[69,7343,76],{"class":75},[69,7345,80],{"class":79},[69,7347,7348],{"class":71,"line":83},[69,7349,87],{"emptyLinePlaceholder":86},[69,7351,7352,7354],{"class":71,"line":90},[69,7353,94],{"class":93},[69,7355,97],{"class":75},[69,7357,7358,7360,7363],{"class":71,"line":100},[69,7359,103],{"class":75},[69,7361,7362],{"class":79},"net/http",[69,7364,109],{"class":75},[69,7366,7367],{"class":71,"line":112},[69,7368,87],{"emptyLinePlaceholder":86},[69,7370,7371,7373,7376],{"class":71,"line":122},[69,7372,103],{"class":75},[69,7374,7375],{"class":79},"github.com/go-pdf/fpdf",[69,7377,109],{"class":75},[69,7379,7380],{"class":71,"line":127},[69,7381,160],{"class":75},[69,7383,7384],{"class":71,"line":137},[69,7385,87],{"emptyLinePlaceholder":86},[69,7387,7388,7390,7393,7395,7397,7400,7402,7405,7407,7410,7412,7415,7417,7420,7422],{"class":71,"line":147},[69,7389,171],{"class":75},[69,7391,7392],{"class":174}," handler",[69,7394,209],{"class":75},[69,7396,1874],{"class":442},[69,7398,7399],{"class":79}," http",[69,7401,203],{"class":75},[69,7403,7404],{"class":79},"ResponseWriter",[69,7406,191],{"class":75},[69,7408,7409],{"class":442}," r",[69,7411,446],{"class":75},[69,7413,7414],{"class":79},"http",[69,7416,203],{"class":75},[69,7418,7419],{"class":79},"Request",[69,7421,457],{"class":75},[69,7423,181],{"class":75},[69,7425,7426,7429,7431,7434,7436,7439,7441,7443,7446,7448,7450,7453,7456,7458,7460,7462,7464,7466,7468,7471],{"class":71,"line":157},[69,7427,7428],{"class":187},"    pdf ",[69,7430,197],{"class":75},[69,7432,7433],{"class":187}," fpdf",[69,7435,203],{"class":75},[69,7437,7438],{"class":174},"New",[69,7440,209],{"class":75},[69,7442,212],{"class":75},[69,7444,7445],{"class":215},"P",[69,7447,212],{"class":75},[69,7449,191],{"class":75},[69,7451,7452],{"class":75}," \"",[69,7454,7455],{"class":215},"mm",[69,7457,212],{"class":75},[69,7459,191],{"class":75},[69,7461,7452],{"class":75},[69,7463,303],{"class":215},[69,7465,212],{"class":75},[69,7467,191],{"class":75},[69,7469,7470],{"class":75}," \"\"",[69,7472,160],{"class":75},[69,7474,7475,7478,7480,7482],{"class":71,"line":163},[69,7476,7477],{"class":187},"    pdf",[69,7479,203],{"class":75},[69,7481,422],{"class":174},[69,7483,425],{"class":75},[69,7485,7486,7488,7490,7492,7494,7496,7499,7501,7503,7505,7508,7510,7512,7515],{"class":71,"line":168},[69,7487,7477],{"class":187},[69,7489,203],{"class":75},[69,7491,1758],{"class":174},[69,7493,209],{"class":75},[69,7495,212],{"class":75},[69,7497,7498],{"class":215},"Arial",[69,7500,212],{"class":75},[69,7502,191],{"class":75},[69,7504,7452],{"class":75},[69,7506,7507],{"class":215},"B",[69,7509,212],{"class":75},[69,7511,191],{"class":75},[69,7513,7514],{"class":340}," 16",[69,7516,160],{"class":75},[69,7518,7519,7521,7523,7525,7527,7530,7532,7535,7537,7539,7542,7544],{"class":71,"line":184},[69,7520,7477],{"class":187},[69,7522,203],{"class":75},[69,7524,1933],{"class":174},[69,7526,209],{"class":75},[69,7528,7529],{"class":340},"40",[69,7531,191],{"class":75},[69,7533,7534],{"class":340}," 10",[69,7536,191],{"class":75},[69,7538,7452],{"class":75},[69,7540,7541],{"class":215},"Hello, World!",[69,7543,212],{"class":75},[69,7545,160],{"class":75},[69,7547,7548],{"class":71,"line":222},[69,7549,87],{"emptyLinePlaceholder":86},[69,7551,7552,7555,7557,7560,7563,7566,7568,7570,7573,7575,7577,7579,7582,7584],{"class":71,"line":238},[69,7553,7554],{"class":187},"    w",[69,7556,203],{"class":75},[69,7558,7559],{"class":174},"Header",[69,7561,7562],{"class":75},"().",[69,7564,7565],{"class":174},"Set",[69,7567,209],{"class":75},[69,7569,212],{"class":75},[69,7571,7572],{"class":215},"Content-Type",[69,7574,212],{"class":75},[69,7576,191],{"class":75},[69,7578,7452],{"class":75},[69,7580,7581],{"class":215},"application/pdf",[69,7583,212],{"class":75},[69,7585,160],{"class":75},[69,7587,7588,7590,7592,7594,7597,7599,7601,7603,7605,7607,7609,7611,7613],{"class":71,"line":256},[69,7589,225],{"class":93},[69,7591,194],{"class":187},[69,7593,197],{"class":75},[69,7595,7596],{"class":187}," pdf",[69,7598,203],{"class":75},[69,7600,1937],{"class":174},[69,7602,209],{"class":75},[69,7604,1874],{"class":187},[69,7606,673],{"class":75},[69,7608,194],{"class":187},[69,7610,230],{"class":75},[69,7612,233],{"class":75},[69,7614,181],{"class":75},[69,7616,7617,7620,7622,7625,7627,7629,7631,7634,7636,7638,7641,7644],{"class":71,"line":262},[69,7618,7619],{"class":187},"        http",[69,7621,203],{"class":75},[69,7623,7624],{"class":174},"Error",[69,7626,209],{"class":75},[69,7628,1874],{"class":187},[69,7630,191],{"class":75},[69,7632,7633],{"class":187}," err",[69,7635,203],{"class":75},[69,7637,7624],{"class":174},[69,7639,7640],{"class":75},"(),",[69,7642,7643],{"class":340}," 500",[69,7645,160],{"class":75},[69,7647,7648],{"class":71,"line":267},[69,7649,259],{"class":75},[69,7651,7652],{"class":71,"line":286},[69,7653,707],{"class":75},[19,7655,7656],{},[30,7657,7658],{},"After — gpdf:",[60,7660,7662],{"className":62,"code":7661,"language":64,"meta":65,"style":65},"package main\n\nimport (\n    \"net/http\"\n\n    \"github.com/gpdf-dev/gpdf\"\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc handler(w http.ResponseWriter, r *http.Request) {\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(document.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"Hello, World!\", template.FontSize(16), template.Bold())\n        })\n    })\n\n    w.Header().Set(\"Content-Type\", \"application/pdf\")\n    if err := doc.Render(w); err != nil {\n        http.Error(w, err.Error(), 500)\n    }\n}\n",[47,7663,7664,7670,7674,7680,7688,7692,7700,7708,7716,7720,7724,7756,7770,7788,7818,7822,7826,7840,7864,7894,7933,7937,7941,7945,7975,8004,8030,8034],{"__ignoreMap":65},[69,7665,7666,7668],{"class":71,"line":72},[69,7667,76],{"class":75},[69,7669,80],{"class":79},[69,7671,7672],{"class":71,"line":83},[69,7673,87],{"emptyLinePlaceholder":86},[69,7675,7676,7678],{"class":71,"line":90},[69,7677,94],{"class":93},[69,7679,97],{"class":75},[69,7681,7682,7684,7686],{"class":71,"line":100},[69,7683,103],{"class":75},[69,7685,7362],{"class":79},[69,7687,109],{"class":75},[69,7689,7690],{"class":71,"line":112},[69,7691,87],{"emptyLinePlaceholder":86},[69,7693,7694,7696,7698],{"class":71,"line":122},[69,7695,103],{"class":75},[69,7697,132],{"class":79},[69,7699,109],{"class":75},[69,7701,7702,7704,7706],{"class":71,"line":127},[69,7703,103],{"class":75},[69,7705,142],{"class":79},[69,7707,109],{"class":75},[69,7709,7710,7712,7714],{"class":71,"line":137},[69,7711,103],{"class":75},[69,7713,152],{"class":79},[69,7715,109],{"class":75},[69,7717,7718],{"class":71,"line":147},[69,7719,160],{"class":75},[69,7721,7722],{"class":71,"line":157},[69,7723,87],{"emptyLinePlaceholder":86},[69,7725,7726,7728,7730,7732,7734,7736,7738,7740,7742,7744,7746,7748,7750,7752,7754],{"class":71,"line":163},[69,7727,171],{"class":75},[69,7729,7392],{"class":174},[69,7731,209],{"class":75},[69,7733,1874],{"class":442},[69,7735,7399],{"class":79},[69,7737,203],{"class":75},[69,7739,7404],{"class":79},[69,7741,191],{"class":75},[69,7743,7409],{"class":442},[69,7745,446],{"class":75},[69,7747,7414],{"class":79},[69,7749,203],{"class":75},[69,7751,7419],{"class":79},[69,7753,457],{"class":75},[69,7755,181],{"class":75},[69,7757,7758,7760,7762,7764,7766,7768],{"class":71,"line":168},[69,7759,270],{"class":187},[69,7761,197],{"class":75},[69,7763,275],{"class":187},[69,7765,203],{"class":75},[69,7767,280],{"class":174},[69,7769,283],{"class":75},[69,7771,7772,7774,7776,7778,7780,7782,7784,7786],{"class":71,"line":184},[69,7773,289],{"class":187},[69,7775,203],{"class":75},[69,7777,294],{"class":174},[69,7779,209],{"class":75},[69,7781,321],{"class":187},[69,7783,203],{"class":75},[69,7785,303],{"class":187},[69,7787,306],{"class":75},[69,7789,7790,7792,7794,7796,7798,7800,7802,7804,7806,7808,7810,7812,7814,7816],{"class":71,"line":222},[69,7791,289],{"class":187},[69,7793,203],{"class":75},[69,7795,316],{"class":174},[69,7797,209],{"class":75},[69,7799,321],{"class":187},[69,7801,203],{"class":75},[69,7803,326],{"class":174},[69,7805,209],{"class":75},[69,7807,321],{"class":187},[69,7809,203],{"class":75},[69,7811,335],{"class":174},[69,7813,209],{"class":75},[69,7815,341],{"class":340},[69,7817,344],{"class":75},[69,7819,7820],{"class":71,"line":238},[69,7821,401],{"class":75},[69,7823,7824],{"class":71,"line":256},[69,7825,87],{"emptyLinePlaceholder":86},[69,7827,7828,7830,7832,7834,7836,7838],{"class":71,"line":262},[69,7829,412],{"class":187},[69,7831,197],{"class":75},[69,7833,417],{"class":187},[69,7835,203],{"class":75},[69,7837,422],{"class":174},[69,7839,425],{"class":75},[69,7841,7842,7844,7846,7848,7850,7852,7854,7856,7858,7860,7862],{"class":71,"line":267},[69,7843,431],{"class":187},[69,7845,203],{"class":75},[69,7847,436],{"class":174},[69,7849,439],{"class":75},[69,7851,443],{"class":442},[69,7853,446],{"class":75},[69,7855,449],{"class":79},[69,7857,203],{"class":75},[69,7859,454],{"class":79},[69,7861,457],{"class":75},[69,7863,181],{"class":75},[69,7865,7866,7868,7870,7872,7874,7876,7878,7880,7882,7884,7886,7888,7890,7892],{"class":71,"line":286},[69,7867,465],{"class":187},[69,7869,203],{"class":75},[69,7871,470],{"class":174},[69,7873,209],{"class":75},[69,7875,475],{"class":340},[69,7877,191],{"class":75},[69,7879,480],{"class":75},[69,7881,483],{"class":442},[69,7883,446],{"class":75},[69,7885,449],{"class":79},[69,7887,203],{"class":75},[69,7889,492],{"class":79},[69,7891,457],{"class":75},[69,7893,181],{"class":75},[69,7895,7896,7898,7900,7902,7904,7906,7908,7910,7912,7914,7916,7918,7920,7923,7925,7927,7929,7931],{"class":71,"line":309},[69,7897,502],{"class":187},[69,7899,203],{"class":75},[69,7901,507],{"class":174},[69,7903,209],{"class":75},[69,7905,212],{"class":75},[69,7907,7541],{"class":215},[69,7909,212],{"class":75},[69,7911,191],{"class":75},[69,7913,521],{"class":187},[69,7915,203],{"class":75},[69,7917,526],{"class":174},[69,7919,209],{"class":75},[69,7921,7922],{"class":340},"16",[69,7924,534],{"class":75},[69,7926,521],{"class":187},[69,7928,203],{"class":75},[69,7930,541],{"class":174},[69,7932,544],{"class":75},[69,7934,7935],{"class":71,"line":347},[69,7936,570],{"class":75},[69,7938,7939],{"class":71,"line":373},[69,7940,576],{"class":75},[69,7942,7943],{"class":71,"line":398},[69,7944,87],{"emptyLinePlaceholder":86},[69,7946,7947,7949,7951,7953,7955,7957,7959,7961,7963,7965,7967,7969,7971,7973],{"class":71,"line":404},[69,7948,7554],{"class":187},[69,7950,203],{"class":75},[69,7952,7559],{"class":174},[69,7954,7562],{"class":75},[69,7956,7565],{"class":174},[69,7958,209],{"class":75},[69,7960,212],{"class":75},[69,7962,7572],{"class":215},[69,7964,212],{"class":75},[69,7966,191],{"class":75},[69,7968,7452],{"class":75},[69,7970,7581],{"class":215},[69,7972,212],{"class":75},[69,7974,160],{"class":75},[69,7976,7977,7979,7981,7983,7985,7987,7990,7992,7994,7996,7998,8000,8002],{"class":71,"line":409},[69,7978,225],{"class":93},[69,7980,194],{"class":187},[69,7982,197],{"class":75},[69,7984,417],{"class":187},[69,7986,203],{"class":75},[69,7988,7989],{"class":174},"Render",[69,7991,209],{"class":75},[69,7993,1874],{"class":187},[69,7995,673],{"class":75},[69,7997,194],{"class":187},[69,7999,230],{"class":75},[69,8001,233],{"class":75},[69,8003,181],{"class":75},[69,8005,8006,8008,8010,8012,8014,8016,8018,8020,8022,8024,8026,8028],{"class":71,"line":428},[69,8007,7619],{"class":187},[69,8009,203],{"class":75},[69,8011,7624],{"class":174},[69,8013,209],{"class":75},[69,8015,1874],{"class":187},[69,8017,191],{"class":75},[69,8019,7633],{"class":187},[69,8021,203],{"class":75},[69,8023,7624],{"class":174},[69,8025,7640],{"class":75},[69,8027,7643],{"class":340},[69,8029,160],{"class":75},[69,8031,8032],{"class":71,"line":462},[69,8033,259],{"class":75},[69,8035,8036],{"class":71,"line":499},[69,8037,707],{"class":75},[19,8039,8040,8041,8043,8044,8047,8048,8050,8051,203],{},"3줄 커서 코드가 3개의 빌더 호출로 바뀐다. 구조가 ",[47,8042,1933],{}," 호출 순서에 숨지 않고 소스 코드에 그대로 드러난다. CJK는 ",[47,8045,8046],{},"gpdf.WithFont(\"NotoSansJP\", ttfBytes)","만 추가하면 된다 — ",[47,8049,2574],{},"도, 파일시스템 경로도, UTF-8 플래그도 필요 없다. 자세한 건 ",[22,8052,6573],{"href":1224},[19,8054,8055,8058,8059,8061],{},[22,8056,8057],{"href":2836},"gofpdf 마이그레이션 가이드","에는 테이블·반복 헤더/푸터·페이지 번호·절대 위치 지정의 before/after 5쌍이 더 있다. 거기 적힌 내용은 ",[47,8060,1434],{}," 사용자에게도 그대로 적용된다 — import 경로만 바꾸면 된다.",[14,8063,8065],{"id":8064},"벤치마크-그림","벤치마크 그림",[19,8067,8068,8069,8072],{},"\"빠르다\"는 쉽게 주장할 수 있고 어렵게 증명된다. 아래 표는 ",[47,8070,8071],{},"gpdf/_benchmark/benchmark_test.go"," 결과 — Apple M1, Go 1.25. 워크로드는 프로덕션 코드가 실제로 하는 일 — 어떤 라이브러리를 좋게 보이게 하려고 고른 마이크로 벤치가 아니다.",[741,8074,8075,8091],{},[744,8076,8077],{},[747,8078,8079,8082,8084,8086,8089],{},[750,8080,8081],{},"벤치마크",[750,8083,27],{},[750,8085,1431],{},[750,8087,8088],{},"gopdf",[750,8090,1440],{},[759,8092,8093,8108,8124,8140],{},[747,8094,8095,8098,8102,8104,8106],{},[764,8096,8097],{},"단일 페이지 (hello)",[764,8099,8100],{},[30,8101,1344],{},[764,8103,1454],{},[764,8105,1460],{},[764,8107,1463],{},[747,8109,8110,8113,8117,8119,8121],{},[764,8111,8112],{},"4×10 품목 테이블",[764,8114,8115],{},[30,8116,1348],{},[764,8118,1475],{},[764,8120,1481],{},[764,8122,8123],{},"8.6 ms",[747,8125,8126,8129,8133,8135,8137],{},[764,8127,8128],{},"100페이지 리포트",[764,8130,8131],{},[30,8132,1352],{},[764,8134,1360],{},[764,8136,8123],{},[764,8138,8139],{},"19.8 ms",[747,8141,8142,8145,8149,8151,8153],{},[764,8143,8144],{},"복잡한 CJK 세금계산서",[764,8146,8147],{},[30,8148,1514],{},[764,8150,1517],{},[764,8152,1523],{},[764,8154,8155],{},"10.4 ms",[19,8157,8158,8159,8162],{},"단일 페이지 13 µs면 1코어로 초당 약 75,000장. 품목 테이블 108 µs면 초당 약 9,000장. ",[30,8160,8161],{},"포인트는 벤치 자랑이 아니다"," — PDF 생성을 캐시해야 할지, 비동기 큐로 보내야 할지 고민하지 않아도 된다는 것. 대부분의 워크로드에서는 요청 경로에서 바로 생성해도 충분하다.",[19,8164,8165,8166,8168],{},"테이블 벤치에서 Maroto v2가 느리게 나오는 이유는 바닥에 ",[47,8167,1434],{},"를 놓고 그 위에 자체 레이아웃 패스를 하나 더 얹었기 때문이다. Maroto API에 대한 비판이 아니라 — API는 좋다 — fpdf 기반에 앉는 구조적 비용이다. Maroto v3가 fpdf 의존을 벗으면 이 열의 숫자는 바뀔 것이다.",[19,8170,8171],{},"100페이지 벤치는 조금 더 짚을 만하다. gpdf의 스트리밍 writer는 행을 레이아웃하면서 내용을 흘려보내고, gofpdf는 페이지별로 더 많은 상태를 버퍼링한다. 페이징이 많은 워크로드(월간 리포트, 카탈로그, 컴플라이언스 익스포트)에서는 문서 크기의 상한에서 차이가 \"분 vs 초\"가 된다.",[14,8173,8175,8176,8179],{"id":8174},"gpdf를-선택하지-말아야-할-때","gpdf를 ",[3586,8177,8178],{},"선택하지 말아야"," 할 때",[19,8181,8182],{},"마이그레이션 글은 \"언제 안 옮길까\"에 정직하게 답해야 한다:",[1113,8184,8185,8198,8207,8219],{},[886,8186,8187,8190,8191,8194,8195,8197],{},[30,8188,8189],{},"AcroForm / 입력 가능한 폼",". Acrobat에서 사용자가 입력하는 PDF를 만들려면 gpdf의 폼 필드 지원은 아직 최소한. ",[47,8192,8193],{},"unidoc","이 이 영역에서 더 완성도 높고, ",[47,8196,1437],{},"은 부분 지원한다. 미래 릴리스에서 채울 예정이지만 오늘은 구멍.",[886,8199,8200,2125,8203,8206],{},[30,8201,8202],{},"임의 벡터 경로와 복잡한 그리기",[47,8204,8205],{},"c.Line()","은 컬럼 안에 가로선 한 줄 긋는다. 베지어·커스텀 패스·그라디언트 채우기로 차트/기술 도면을 그려야 한다면 gpdf는 아직 거기 못 간다. (미리 렌더링한 차트 이미지 임베딩은 문제없음 — 여기서 말하는 건 그리기 프리미티브.)",[886,8208,8209,8214,8215,8218],{},[30,8210,8211,8213],{},[47,8212,1930],{}," 사용이 많은 기존 gofpdf 코드베이스",". 2,000줄의 커서 조작이면 마이그레이션은 치환이 아니라 재작성에 가깝다. 재작성 후 코드는 거의 항상 더 짧지만, 마감일에 \"거의 항상\"은 차가운 위로. ",[22,8216,8217],{"href":2836},"마이그레이션 가이드","에 솔직한 공수 추정을 적어뒀다.",[886,8220,8221,8224,8225,8228,8229,8231],{},[30,8222,8223],{},"지금 당장 풀 CSS 지원의 HTML → PDF가 필요하다",". gpdf의 ",[47,8226,8227],{},"gpdf-pro"," 애드온에 HTML 서브셋이 있지만 Chromium과의 완전한 CSS 패리티는 목표가 아니다. 템플릿이 복잡한 React 컴포넌트라면 ",[47,8230,6973],{},"나 상용 API가 더 직접적이다.",[19,8233,8234,8235,8238],{},"위 중 어느 것도 안 찌른다면 gpdf가 기본값. 하나라도 찌른다면 ",[30,8236,8237],{},"두 라이브러리를 병존","하는 게 보통이다 — 새 PDF는 gpdf, 엣지 케이스는 기존 것에 남겨두고, gpdf가 따라잡으면 그때 옮긴다.",[14,8240,8242],{"id":8241},"컴플라이언스-관점","컴플라이언스 관점",[19,8244,8245,8246,8249],{},"생태계 글에서 잘 다루지 않는 포인트: ",[30,8247,8248],{},"아카이브된 의존성은 SOC 2와 ISO 27001 감사 보고서에 뜬다",". 감사관은 공급망의 서드파티 코드가 적극 유지되는지 알고 싶어 한다. \"2021 아카이브\"는 finding을 띄우고, \"2025 아카이브\"도 띄운다. \"내부 포크\"는 0-day 패치 절차에 대한 추가 질문을 유발한다.",[19,8251,8252,8253,2125,8256,8258],{},"이게 큰 회사의 보안 리뷰를 통과하는 팀들이 조용히 \"gpdf 안정 v1은 언제인가\"를 묻는 주된 이유다. 답은: ",[30,8254,8255],{},"이미 나왔다",[47,8257,132],{},"은 semver 태그가 있고 v1 API 표면이 동결됐다. 프로젝트에는 보안 연락처, 책임 있는 공개 정책, Go 1.22–1.26을 CI에서 돌리는 체계가 있다.",[19,8260,8261,8262,8265,8266,8269],{},"감사 ",[3586,8263,8264],{},"때문에"," 옮기는 게 아니다 — ",[30,8267,8268],{},"감사가 요구하기 전에 움직이는 것","이 목적.",[14,8271,2827],{"id":2826},[19,8273,8274,8277,8278,8280,8281,8283],{},[30,8275,8276],{},"\"현대 Go PDF 스택\"은 gpdf 한 라이브러리인가, 여러 라이브러리 조합인가?","\n대부분의 팀에선 gpdf 하나. 단일 라이브러리가 문서 생성·CJK·테이블·그리드·페이지네이션·출력을 커버한다. 폼 필드 요구사항이 있는 팀은 그 종류의 문서에만 ",[47,8279,1437],{},"나 ",[47,8282,8193],{},"을 추가로 쓴다. 차트 위주 팀은 차트를 PNG로 미리 렌더링해서 임베드한다. 여기서 \"스택\"은 층상 아키텍처가 아니라 짧은 리스트의 의미.",[19,8285,8286,8289],{},[30,8287,8288],{},"마이그레이션 중 gpdf와 go-pdf/fpdf를 병존할 수 있나?","\n할 수 있다. import 경로도 타입도 다르다. 새 엔드포인트는 gpdf, 오래된 것은 시간이 될 때까지 go-pdf/fpdf에 남겨두면 된다. 런타임 충돌은 없다.",[19,8291,8292,8295,8296,8299],{},[30,8293,8294],{},"go-pdf/fpdf v3나 새 포크가 나올까?","\n혹시 모른다. gpdf의 베팅은 \"그 포크가 영원히 아카이브로 남는다\"가 아니라 — ",[30,8297,8298],{},"아키텍처가 오늘 만드는 것에 스케일하지 않는다"," 는 쪽. 새 포크가 레이아웃 모델을 안 고치면 같은 제약을 물려받는다. 고치면 그건 fpdf보다 gpdf에 가깝다.",[19,8301,8302,8308,8309,854,8312,854,8315,8318],{},[30,8303,8304,8305,8307],{},"현대 대안으로 ",[47,8306,1437],{},"은 어떤가?","\n진짜로 유지되고 진짜로 의존성 0. API는 좌표 수준 — ",[47,8310,8311],{},"SetX",[47,8313,8314],{},"SetY",[47,8316,8317],{},"CellWithOption"," — 폼 오버레이와 고정 템플릿에 잘 맞는다. 테이블과 반복 헤더/푸터가 있는 세금계산서류 문서에서는 결국 위에 레이아웃 헬퍼를 쓰게 되고, gofpdf 사용자가 빠진 같은 함정으로 돌아간다. gpdf와 gopdf는 사실 경쟁 관계가 아니다 — 인접한 문제를 푼다.",[19,8320,8321,8324,8326],{},[30,8322,8323],{},"gpdf에 상용/호스티드 버전이 있나?",[47,8325,2606],{}," 준비 중 — JSON 템플릿을 POST하면 PDF를 돌려주는 호스티드 API. 아직 공개하지 않았다. 출시할 때 이 블로그에 글이 올라온다. OSS 라이브러리는 계속 MIT, 의존성 0, 독립적으로 유용한 상태로 유지된다.",[19,8328,8329,8332],{},[30,8330,8331],{},"로드맵 우선순위는?","\n2026-04 시점의 공개 로드맵: (1) AcroForm 폼 필드, (2) 완전한 PDF/A-3 준수, (3) gpdf-pro의 HTML→PDF 커버리지 확장, (4) RTL 텍스트 지원(아랍어, 히브리어). 우선순위 피드백은 GitHub 이슈에서 환영.",[14,8334,6592],{"id":6591},[19,8336,6595],{},[60,8338,8339],{"className":1250,"code":1251,"language":1252,"meta":65,"style":65},[47,8340,8341],{"__ignoreMap":65},[69,8342,8343,8345,8347],{"class":71,"line":72},[69,8344,64],{"class":79},[69,8346,1261],{"class":215},[69,8348,1264],{"class":215},[19,8350,8351,1271,8354],{},[22,8352,6613],{"href":24,"rel":8353},[26],[22,8355,1276],{"href":1274,"rel":8356},[26],[14,8358,8360],{"id":8359},"다음-읽기","다음 읽기",[1113,8362,8363,8369,8374,8379],{},[886,8364,8365,8368],{},[22,8366,8367],{"href":2836},"gofpdf이 아카이브됐다. gpdf로 마이그레이션하는 법."," — 한 API씩 매핑",[886,8370,8371,8373],{},[22,8372,6579],{"href":2909}," — 더 깊은 헤드투헤드 벤치마크와 기능 그리드",[886,8375,8376,8378],{},[22,8377,5294],{"href":6653}," — 커서 조작을 대체하는 빌더 관용구",[886,8380,8381,5225,8383,8385],{},[22,8382,6573],{"href":1224},[47,8384,2574],{}," 춤 없는 CJK",[1278,8387,8388],{},"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 .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 pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .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}",{"title":65,"searchDepth":83,"depth":83,"links":8390},[8391,8392,8393,8394,8395,8396,8397,8398,8399,8401,8402,8403,8404],{"id":1337,"depth":83,"text":1338},{"id":6690,"depth":83,"text":6691},{"id":6742,"depth":83,"text":6743},{"id":6788,"depth":83,"text":6789},{"id":6831,"depth":83,"text":6832},{"id":7056,"depth":83,"text":7057},{"id":7310,"depth":83,"text":7311},{"id":8064,"depth":83,"text":8065},{"id":8174,"depth":83,"text":8400},"gpdf를 선택하지 말아야 할 때",{"id":8241,"depth":83,"text":8242},{"id":2826,"depth":83,"text":2827},{"id":6591,"depth":83,"text":6592},{"id":8359,"depth":83,"text":8360},"jung-kurt/gofpdf은 2021년, go-pdf/fpdf은 2025년에 아카이브. 2026년에 실제로 쓰는 Go PDF 스택은 gpdf — 이유와 트레이드오프.",{},"/ko/blog/go-pdf-fpdf-archived",{"title":6661,"description":8405},"ko/blog/006.go-pdf-fpdf-archived",[8411,2958,2956],"migration","A_mpHbEbXj57b2z_pnaK3humiJrHb5-Ce-74dLWLNCw",{"id":8414,"title":8415,"author":8416,"body":8417,"date":5780,"description":10089,"draft":1295,"extension":1296,"howTo":10090,"image":1319,"meta":10114,"navigation":86,"path":3983,"seo":10115,"stem":10116,"tags":10117,"updated":1319,"__hash__":10118},"blogKo/ko/blog/007.japanese-pdf-in-go.md","Go로 일본어 PDF 만들기 — 2026 결정판 가이드",{"name":8,"url":9},{"type":11,"value":8418,"toc":10075},[8419,8421,8440,8444,8447,8453,8464,8467,8471,8482,8520,8532,8542,8546,8549,8664,8667,8673,8683,8687,8695,9511,9514,9565,9574,9578,9587,9590,9597,9643,9656,9659,9683,9699,9703,9706,9712,9715,9730,9740,9821,9827,9830,9833,9843,9852,9876,9890,9893,9895,9902,9912,9921,9935,9942,9944,9969,9975,9985,9999,10009,10018,10022,10025,10037,10045,10049,10073],[14,8420,1338],{"id":1337},[19,8422,8423,8424,8426,8427,4080,8430,8432,8433,8436,8437,203],{},"Go PDF에 ",[47,8425,3714],{},"라 썼는데 두부 □□□□□ 다섯 개가 나왔다면, 해법은 재작성이 아니라 설정 2줄이다. TTF를 읽고 ",[47,8428,8429],{},"gpdf.WithFont",[47,8431,280],{},"에 넘긴 뒤 일본어를 쓴다. ",[30,8434,8435],{},"gpdf는 글리프 테이블을 자동으로 서브셋한다"," — 출력에는 실제로 쓴 문자의 글리프만 실린다. 5 MB 풀 폰트가 아닌 약 30 KB. 이 글은 그 전체 지도다: Go에서 일본어 PDF가 왜 묘하게 어려웠는지, 2026년의 현실적 선택지 네 개, 동작하는 예제, 폰트 서브셋의 내부, 혼용 조판의 실무, 그리고 ",[30,8438,8439],{},"아직 풀리지 않은 부분",[14,8441,8443],{"id":8442},"이-가이드가-존재하는-이유","이 가이드가 존재하는 이유",[19,8445,8446],{},"Go에서 일본어 PDF를 뱉는 것은 본래 5분짜리 작업이다. 많은 팀에서 하루 반이 걸린다.",[19,8448,8449,8450,8452],{},"익숙한 전개: 누군가 ",[47,8451,2574],{},"를 끼워넣는다 → PDF에 빈 사각형이 줄지어 나온다 — 그 유명한 豆腐 — 시니어 한 명이 오후 내내 폰트 경로인지, 서브셋 플래그인지, CMap인지, UTF-8 스위치인지, PDF 리더 문제인지를 좁힌다. 저녁이면 Slack에 \"왜 漢字가 아직 깨졌는가\"라는 스레드가 서고, 다음 날 누구나 후회할 헬퍼 세 개가 추가된 PR이 올라온다.",[19,8454,8455,8456,8459,8460,8463],{},"근본 원인은 그중 어느 것도 아니다. ",[30,8457,8458],{},"Go에서 가장 오래 살아남은 PDF 라이브러리가 2002년의 PHP와 Latin-1을 전제로 설계","된 것과, 그 이후 쓰인 일본어 튜토리얼 대부분이 그 유산과 싸워왔다는 것. 이 글은 2026판이다 — 백지에서 시작할 때 실제로 동작하는 방법과, ",[30,8461,8462],{},"여전히 어려운 지점","을 정직하게.",[19,8465,8466],{},"본문 코드는 gpdf v1.x (2026-04 기준)에서 동작 확인. 벤치 수치는 Apple M1 + Go 1.25.",[14,8468,8470],{"id":8469},"두부-문제를-90초에","두부 문제를 90초에",[19,8472,8473,8474,8477,8478,8481],{},"PDF는 Unicode를 모른다. PDF가 아는 것은 ",[30,8475,8476],{},"글리프 ID"," — 임베디드 글리프 테이블의 정수 인덱스. ",[47,8479,8480],{},"\"こんにちは\"","를 PDF에 쓰려면 누군가 다음을 모두 해야 한다:",[883,8483,8484,8492,8498,8504],{},[886,8485,8486,5225,8489,8491],{},[30,8487,8488],{},"TTF 파싱",[47,8490,860],{}," 서브테이블에서 각 코드포인트에 해당하는 글리프 ID를 찾는다.",[886,8493,8494,8497],{},[30,8495,8496],{},"ToUnicode CMap 작성"," — 사용자가 복사·검색할 때 글리프를 텍스트로 되돌릴 수 있게.",[886,8499,8500,8503],{},[30,8501,8502],{},"서브셋화"," — Noto Sans JP의 2만 글리프를 전부 싣지 않도록.",[886,8505,8506,5225,8509,8512,8513,8512,8516,8519],{},[30,8507,8508],{},"임베딩",[47,8510,8511],{},"name","·",[47,8514,8515],{},"OS/2",[47,8517,8518],{},"head"," 테이블과 인코딩 오브젝트를 올바르게 엮어서.",[19,8521,8522,8523,2128,8525,8527,8528,8531],{},"이 중 어느 것이라도 빠지거나 틀리면 리더는 코드포인트에 해당하는 글리프를 못 찾고 두부를 그린다. 아카이브된 ",[47,8524,1356],{},[47,8526,1434],{}," 계열은 이 모든 것을 ",[30,8529,8530],{},"단일 바이트 폰트 전제의 내부 모델에 덧댔다"," — 2002년의 FPDF는 Latin-1만 알았다. 설정이 부서지기 쉬운 이유, 출력이 서브셋이 아닌 풀 폰트를 자주 임베드하는 이유, OS·리더에 따라 부서지는 양상이 다른 이유가 여기에 있다.",[19,8533,8534,8535,8538,8539,8541],{},"gpdf는 CJK를 ",[30,8536,8537],{},"일급 사용례","로 다룬다. TTF 서브셋터가 코어 패키지에 포함된다. ToUnicode CMap은 자동으로 쓰인다. 단일 바이트 폰트 유산이 없으니 ",[47,8540,2574],{}," 춤도 없다.",[14,8543,8545],{"id":8544},"_2026년의-현실적-선택지-네-개","2026년의 현실적 선택지 네 개",[19,8547,8548],{},"코드 전에 정직한 판 — \"일본어 지원\"은 \"올바른 TTF가 주어졌을 때 크래시도 두부도 없이 임의의 일본어를 렌더할 수 있다\"의 의미로 쓴다.",[741,8550,8551,8571],{},[744,8552,8553],{},[747,8554,8555,8558,8560,8563,8566,8569],{},[750,8556,8557],{},"선택지",[750,8559,5142],{},[750,8561,8562],{},"의존",[750,8564,8565],{},"CJK 경로",[750,8567,8568],{},"300자 문서 크기",[750,8570,6137],{},[759,8572,8573,8596,8618,8640],{},[747,8574,8575,8580,8582,8585,8590,8593],{},[764,8576,8577,8579],{},[47,8578,1434],{}," (2025 아카이브)",[764,8581,6873],{},[764,8583,8584],{},"표준 라이브러리",[764,8586,8587,8589],{},[47,8588,2574],{}," 덧댐",[764,8591,8592],{},"약 5 MB (풀)",[764,8594,8595],{},"Latin-1 코어 위에 덧댐. 서브셋은 옵트인이며 불완전.",[747,8597,8598,8602,8604,8606,8612,8615],{},[764,8599,8600],{},[47,8601,1437],{},[764,8603,6873],{},[764,8605,8584],{},[764,8607,8608,8611],{},[47,8609,8610],{},"AddTTFFont"," + 수동",[764,8613,8614],{},"약 3 MB",[764,8616,8617],{},"저수준. 좌표를 직접 쓴다. 서브셋 기능은 있지만 수동 구동.",[747,8619,8620,8624,8626,8631,8634,8637],{},[764,8621,8622,6974],{},[47,8623,6973],{},[764,8625,6979],{},[764,8627,8628],{},[30,8629,8630],{},"Chromium 바이너리",[764,8632,8633],{},"브라우저 네이티브",[764,8635,8636],{},"가변",[764,8638,8639],{},"HTML/CSS. 컨테이너에 폰트 설치 필요. 이미지 500 MB+.",[747,8641,8642,8646,8648,8653,8656,8661],{},[764,8643,8644],{},[47,8645,27],{},[764,8647,6873],{},[764,8649,8650],{},[30,8651,8652],{},"표준만",[764,8654,8655],{},"네이티브, 자동 서브셋",[764,8657,8658],{},[30,8659,8660],{},"약 30 KB",[764,8662,8663],{},"순수 Go. Builder API. ToUnicode CMap 자동 기록.",[19,8665,8666],{},"두 가지 강조:",[19,8668,8669,8672],{},[30,8670,8671],{},"\"풀 임베드\"와 \"자동 서브셋\"의 160배 차이는 오차가 아니다."," 10줄짜리 EC 일본어 청구서 PDF가 사용하는 고유 글리프는 많아야 120개쯤. 매번 풀 Noto Sans JP (5.1 MB)를 임베드하면 연말까지 같은 5 MB 글리프 데이터가 오브젝트 스토리지에 1000만 번 복사된다. 서브셋 임베드는 사용한 글리프만 싣는다.",[19,8674,8675,8678,8679,8682],{},[30,8676,8677],{},"\"chromedp 된다\"는 사실이고, 가장 비싼 답이다."," 스크린샷 용도로 헤드리스 Chrome 함대를 이미 운용 중인 팀이라면 PDF도 거기 얹는 게 괜찮다. 그게 아니라 ",[30,8680,8681],{},"오직 일본어 출력을 위해"," Chromium을 세우는 것은, 40줄의 Go로 풀리는 문제에 과한 인프라다.",[14,8684,8686],{"id":8685},"가장-짧은-동작-경로","가장 짧은 동작 경로",[19,8688,8689,8690,8692,8693,203],{},"먼저 이걸 돌린다. 완전형 — 복사해서 ",[47,8691,713],{},"로 저장하고 TTF 두 개를 옆에 두고 ",[47,8694,717],{},[60,8696,8698],{"className":62,"code":8697,"language":64,"meta":65,"style":65},"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/template\"\n)\n\nfunc main() {\n    regular, err := os.ReadFile(\"NotoSansJP-Regular.ttf\")\n    if err != nil {\n        log.Fatal(err)\n    }\n    bold, err := os.ReadFile(\"NotoSansJP-Bold.ttf\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(document.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n        gpdf.WithFont(\"NotoSansJP\", regular),\n        gpdf.WithFont(\"NotoSansJP-Bold\", bold),\n        gpdf.WithDefaultFont(\"NotoSansJP\", 11),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"請求書\", template.FontFamily(\"NotoSansJP-Bold\"), template.FontSize(22))\n            c.Text(\"2026 年 4 月 16 日\")\n        })\n    })\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(7, func(c *template.ColBuilder) {\n            c.Text(\"株式会社 ABC 御中\", template.FontSize(13))\n            c.Text(\"〒 100-0001 東京都千代田区千代田 1-1\")\n        })\n        r.Col(5, func(c *template.ColBuilder) {\n            c.Text(\"合計 ¥ 128,000\", template.FontFamily(\"NotoSansJP-Bold\"), template.AlignRight())\n            c.Text(\"支払期限: 2026-05-31\", template.AlignRight())\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"invoice-ja.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[47,8699,8700,8706,8710,8716,8724,8732,8736,8744,8752,8760,8764,8768,8778,8805,8817,8831,8835,8863,8875,8889,8893,8897,8911,8929,8959,8982,9005,9027,9031,9035,9049,9073,9103,9150,9169,9173,9177,9201,9232,9264,9283,9287,9318,9361,9388,9392,9396,9400,9418,9430,9444,9448,9489,9503,9507],{"__ignoreMap":65},[69,8701,8702,8704],{"class":71,"line":72},[69,8703,76],{"class":75},[69,8705,80],{"class":79},[69,8707,8708],{"class":71,"line":83},[69,8709,87],{"emptyLinePlaceholder":86},[69,8711,8712,8714],{"class":71,"line":90},[69,8713,94],{"class":93},[69,8715,97],{"class":75},[69,8717,8718,8720,8722],{"class":71,"line":100},[69,8719,103],{"class":75},[69,8721,106],{"class":79},[69,8723,109],{"class":75},[69,8725,8726,8728,8730],{"class":71,"line":112},[69,8727,103],{"class":75},[69,8729,117],{"class":79},[69,8731,109],{"class":75},[69,8733,8734],{"class":71,"line":122},[69,8735,87],{"emptyLinePlaceholder":86},[69,8737,8738,8740,8742],{"class":71,"line":127},[69,8739,103],{"class":75},[69,8741,132],{"class":79},[69,8743,109],{"class":75},[69,8745,8746,8748,8750],{"class":71,"line":137},[69,8747,103],{"class":75},[69,8749,142],{"class":79},[69,8751,109],{"class":75},[69,8753,8754,8756,8758],{"class":71,"line":147},[69,8755,103],{"class":75},[69,8757,152],{"class":79},[69,8759,109],{"class":75},[69,8761,8762],{"class":71,"line":157},[69,8763,160],{"class":75},[69,8765,8766],{"class":71,"line":163},[69,8767,87],{"emptyLinePlaceholder":86},[69,8769,8770,8772,8774,8776],{"class":71,"line":168},[69,8771,171],{"class":75},[69,8773,175],{"class":174},[69,8775,178],{"class":75},[69,8777,181],{"class":75},[69,8779,8780,8783,8785,8787,8789,8791,8793,8795,8797,8799,8801,8803],{"class":71,"line":184},[69,8781,8782],{"class":187},"    regular",[69,8784,191],{"class":75},[69,8786,194],{"class":187},[69,8788,197],{"class":75},[69,8790,200],{"class":187},[69,8792,203],{"class":75},[69,8794,206],{"class":174},[69,8796,209],{"class":75},[69,8798,212],{"class":75},[69,8800,3142],{"class":215},[69,8802,212],{"class":75},[69,8804,160],{"class":75},[69,8806,8807,8809,8811,8813,8815],{"class":71,"line":222},[69,8808,225],{"class":93},[69,8810,194],{"class":187},[69,8812,230],{"class":75},[69,8814,233],{"class":75},[69,8816,181],{"class":75},[69,8818,8819,8821,8823,8825,8827,8829],{"class":71,"line":238},[69,8820,241],{"class":187},[69,8822,203],{"class":75},[69,8824,246],{"class":174},[69,8826,209],{"class":75},[69,8828,251],{"class":187},[69,8830,160],{"class":75},[69,8832,8833],{"class":71,"line":256},[69,8834,259],{"class":75},[69,8836,8837,8840,8842,8844,8846,8848,8850,8852,8854,8856,8859,8861],{"class":71,"line":262},[69,8838,8839],{"class":187},"    bold",[69,8841,191],{"class":75},[69,8843,194],{"class":187},[69,8845,197],{"class":75},[69,8847,200],{"class":187},[69,8849,203],{"class":75},[69,8851,206],{"class":174},[69,8853,209],{"class":75},[69,8855,212],{"class":75},[69,8857,8858],{"class":215},"NotoSansJP-Bold.ttf",[69,8860,212],{"class":75},[69,8862,160],{"class":75},[69,8864,8865,8867,8869,8871,8873],{"class":71,"line":267},[69,8866,225],{"class":93},[69,8868,194],{"class":187},[69,8870,230],{"class":75},[69,8872,233],{"class":75},[69,8874,181],{"class":75},[69,8876,8877,8879,8881,8883,8885,8887],{"class":71,"line":286},[69,8878,241],{"class":187},[69,8880,203],{"class":75},[69,8882,246],{"class":174},[69,8884,209],{"class":75},[69,8886,251],{"class":187},[69,8888,160],{"class":75},[69,8890,8891],{"class":71,"line":309},[69,8892,259],{"class":75},[69,8894,8895],{"class":71,"line":347},[69,8896,87],{"emptyLinePlaceholder":86},[69,8898,8899,8901,8903,8905,8907,8909],{"class":71,"line":373},[69,8900,270],{"class":187},[69,8902,197],{"class":75},[69,8904,275],{"class":187},[69,8906,203],{"class":75},[69,8908,280],{"class":174},[69,8910,283],{"class":75},[69,8912,8913,8915,8917,8919,8921,8923,8925,8927],{"class":71,"line":398},[69,8914,289],{"class":187},[69,8916,203],{"class":75},[69,8918,294],{"class":174},[69,8920,209],{"class":75},[69,8922,321],{"class":187},[69,8924,203],{"class":75},[69,8926,303],{"class":187},[69,8928,306],{"class":75},[69,8930,8931,8933,8935,8937,8939,8941,8943,8945,8947,8949,8951,8953,8955,8957],{"class":71,"line":404},[69,8932,289],{"class":187},[69,8934,203],{"class":75},[69,8936,316],{"class":174},[69,8938,209],{"class":75},[69,8940,321],{"class":187},[69,8942,203],{"class":75},[69,8944,326],{"class":174},[69,8946,209],{"class":75},[69,8948,321],{"class":187},[69,8950,203],{"class":75},[69,8952,335],{"class":174},[69,8954,209],{"class":75},[69,8956,341],{"class":340},[69,8958,344],{"class":75},[69,8960,8961,8963,8965,8967,8969,8971,8973,8975,8977,8980],{"class":71,"line":409},[69,8962,289],{"class":187},[69,8964,203],{"class":75},[69,8966,354],{"class":174},[69,8968,209],{"class":75},[69,8970,212],{"class":75},[69,8972,3257],{"class":215},[69,8974,212],{"class":75},[69,8976,191],{"class":75},[69,8978,8979],{"class":187}," regular",[69,8981,306],{"class":75},[69,8983,8984,8986,8988,8990,8992,8994,8997,8999,9001,9003],{"class":71,"line":428},[69,8985,289],{"class":187},[69,8987,203],{"class":75},[69,8989,354],{"class":174},[69,8991,209],{"class":75},[69,8993,212],{"class":75},[69,8995,8996],{"class":215},"NotoSansJP-Bold",[69,8998,212],{"class":75},[69,9000,191],{"class":75},[69,9002,1060],{"class":187},[69,9004,306],{"class":75},[69,9006,9007,9009,9011,9013,9015,9017,9019,9021,9023,9025],{"class":71,"line":462},[69,9008,289],{"class":187},[69,9010,203],{"class":75},[69,9012,380],{"class":174},[69,9014,209],{"class":75},[69,9016,212],{"class":75},[69,9018,3257],{"class":215},[69,9020,212],{"class":75},[69,9022,191],{"class":75},[69,9024,393],{"class":340},[69,9026,306],{"class":75},[69,9028,9029],{"class":71,"line":499},[69,9030,401],{"class":75},[69,9032,9033],{"class":71,"line":547},[69,9034,87],{"emptyLinePlaceholder":86},[69,9036,9037,9039,9041,9043,9045,9047],{"class":71,"line":567},[69,9038,412],{"class":187},[69,9040,197],{"class":75},[69,9042,417],{"class":187},[69,9044,203],{"class":75},[69,9046,422],{"class":174},[69,9048,425],{"class":75},[69,9050,9051,9053,9055,9057,9059,9061,9063,9065,9067,9069,9071],{"class":71,"line":573},[69,9052,431],{"class":187},[69,9054,203],{"class":75},[69,9056,436],{"class":174},[69,9058,439],{"class":75},[69,9060,443],{"class":442},[69,9062,446],{"class":75},[69,9064,449],{"class":79},[69,9066,203],{"class":75},[69,9068,454],{"class":79},[69,9070,457],{"class":75},[69,9072,181],{"class":75},[69,9074,9075,9077,9079,9081,9083,9085,9087,9089,9091,9093,9095,9097,9099,9101],{"class":71,"line":579},[69,9076,465],{"class":187},[69,9078,203],{"class":75},[69,9080,470],{"class":174},[69,9082,209],{"class":75},[69,9084,475],{"class":340},[69,9086,191],{"class":75},[69,9088,480],{"class":75},[69,9090,483],{"class":442},[69,9092,446],{"class":75},[69,9094,449],{"class":79},[69,9096,203],{"class":75},[69,9098,492],{"class":79},[69,9100,457],{"class":75},[69,9102,181],{"class":75},[69,9104,9105,9107,9109,9111,9113,9115,9117,9119,9121,9123,9125,9127,9129,9131,9133,9135,9137,9139,9141,9143,9145,9148],{"class":71,"line":584},[69,9106,502],{"class":187},[69,9108,203],{"class":75},[69,9110,507],{"class":174},[69,9112,209],{"class":75},[69,9114,212],{"class":75},[69,9116,4432],{"class":215},[69,9118,212],{"class":75},[69,9120,191],{"class":75},[69,9122,521],{"class":187},[69,9124,203],{"class":75},[69,9126,5006],{"class":174},[69,9128,209],{"class":75},[69,9130,212],{"class":75},[69,9132,8996],{"class":215},[69,9134,212],{"class":75},[69,9136,534],{"class":75},[69,9138,521],{"class":187},[69,9140,203],{"class":75},[69,9142,526],{"class":174},[69,9144,209],{"class":75},[69,9146,9147],{"class":340},"22",[69,9149,5029],{"class":75},[69,9151,9152,9154,9156,9158,9160,9162,9165,9167],{"class":71,"line":605},[69,9153,502],{"class":187},[69,9155,203],{"class":75},[69,9157,507],{"class":174},[69,9159,209],{"class":75},[69,9161,212],{"class":75},[69,9163,9164],{"class":215},"2026 年 4 月 16 日",[69,9166,212],{"class":75},[69,9168,160],{"class":75},[69,9170,9171],{"class":71,"line":618},[69,9172,570],{"class":75},[69,9174,9175],{"class":71,"line":633},[69,9176,576],{"class":75},[69,9178,9179,9181,9183,9185,9187,9189,9191,9193,9195,9197,9199],{"class":71,"line":638},[69,9180,431],{"class":187},[69,9182,203],{"class":75},[69,9184,436],{"class":174},[69,9186,439],{"class":75},[69,9188,443],{"class":442},[69,9190,446],{"class":75},[69,9192,449],{"class":79},[69,9194,203],{"class":75},[69,9196,454],{"class":79},[69,9198,457],{"class":75},[69,9200,181],{"class":75},[69,9202,9203,9205,9207,9209,9211,9214,9216,9218,9220,9222,9224,9226,9228,9230],{"class":71,"line":684},[69,9204,465],{"class":187},[69,9206,203],{"class":75},[69,9208,470],{"class":174},[69,9210,209],{"class":75},[69,9212,9213],{"class":340},"7",[69,9215,191],{"class":75},[69,9217,480],{"class":75},[69,9219,483],{"class":442},[69,9221,446],{"class":75},[69,9223,449],{"class":79},[69,9225,203],{"class":75},[69,9227,492],{"class":79},[69,9229,457],{"class":75},[69,9231,181],{"class":75},[69,9233,9234,9236,9238,9240,9242,9244,9247,9249,9251,9253,9255,9257,9259,9262],{"class":71,"line":699},[69,9235,502],{"class":187},[69,9237,203],{"class":75},[69,9239,507],{"class":174},[69,9241,209],{"class":75},[69,9243,212],{"class":75},[69,9245,9246],{"class":215},"株式会社 ABC 御中",[69,9248,212],{"class":75},[69,9250,191],{"class":75},[69,9252,521],{"class":187},[69,9254,203],{"class":75},[69,9256,526],{"class":174},[69,9258,209],{"class":75},[69,9260,9261],{"class":340},"13",[69,9263,5029],{"class":75},[69,9265,9266,9268,9270,9272,9274,9276,9279,9281],{"class":71,"line":704},[69,9267,502],{"class":187},[69,9269,203],{"class":75},[69,9271,507],{"class":174},[69,9273,209],{"class":75},[69,9275,212],{"class":75},[69,9277,9278],{"class":215},"〒 100-0001 東京都千代田区千代田 1-1",[69,9280,212],{"class":75},[69,9282,160],{"class":75},[69,9284,9285],{"class":71,"line":4616},[69,9286,570],{"class":75},[69,9288,9289,9291,9293,9295,9297,9300,9302,9304,9306,9308,9310,9312,9314,9316],{"class":71,"line":5859},[69,9290,465],{"class":187},[69,9292,203],{"class":75},[69,9294,470],{"class":174},[69,9296,209],{"class":75},[69,9298,9299],{"class":340},"5",[69,9301,191],{"class":75},[69,9303,480],{"class":75},[69,9305,483],{"class":442},[69,9307,446],{"class":75},[69,9309,449],{"class":79},[69,9311,203],{"class":75},[69,9313,492],{"class":79},[69,9315,457],{"class":75},[69,9317,181],{"class":75},[69,9319,9320,9322,9324,9326,9328,9330,9333,9335,9337,9339,9341,9343,9345,9347,9349,9351,9353,9355,9357,9359],{"class":71,"line":5879},[69,9321,502],{"class":187},[69,9323,203],{"class":75},[69,9325,507],{"class":174},[69,9327,209],{"class":75},[69,9329,212],{"class":75},[69,9331,9332],{"class":215},"合計 ¥ 128,000",[69,9334,212],{"class":75},[69,9336,191],{"class":75},[69,9338,521],{"class":187},[69,9340,203],{"class":75},[69,9342,5006],{"class":174},[69,9344,209],{"class":75},[69,9346,212],{"class":75},[69,9348,8996],{"class":215},[69,9350,212],{"class":75},[69,9352,534],{"class":75},[69,9354,521],{"class":187},[69,9356,203],{"class":75},[69,9358,7284],{"class":174},[69,9360,544],{"class":75},[69,9362,9363,9365,9367,9369,9371,9373,9376,9378,9380,9382,9384,9386],{"class":71,"line":5884},[69,9364,502],{"class":187},[69,9366,203],{"class":75},[69,9368,507],{"class":174},[69,9370,209],{"class":75},[69,9372,212],{"class":75},[69,9374,9375],{"class":215},"支払期限: 2026-05-31",[69,9377,212],{"class":75},[69,9379,191],{"class":75},[69,9381,521],{"class":187},[69,9383,203],{"class":75},[69,9385,7284],{"class":174},[69,9387,544],{"class":75},[69,9389,9390],{"class":71,"line":5915},[69,9391,570],{"class":75},[69,9393,9394],{"class":71,"line":5935},[69,9395,576],{"class":75},[69,9397,9398],{"class":71,"line":5940},[69,9399,87],{"emptyLinePlaceholder":86},[69,9401,9402,9404,9406,9408,9410,9412,9414,9416],{"class":71,"line":5971},[69,9403,587],{"class":187},[69,9405,191],{"class":75},[69,9407,194],{"class":187},[69,9409,197],{"class":75},[69,9411,417],{"class":187},[69,9413,203],{"class":75},[69,9415,600],{"class":174},[69,9417,425],{"class":75},[69,9419,9420,9422,9424,9426,9428],{"class":71,"line":5990},[69,9421,225],{"class":93},[69,9423,194],{"class":187},[69,9425,230],{"class":75},[69,9427,233],{"class":75},[69,9429,181],{"class":75},[69,9431,9432,9434,9436,9438,9440,9442],{"class":71,"line":5995},[69,9433,241],{"class":187},[69,9435,203],{"class":75},[69,9437,246],{"class":174},[69,9439,209],{"class":75},[69,9441,251],{"class":187},[69,9443,160],{"class":75},[69,9445,9446],{"class":71,"line":6000},[69,9447,259],{"class":75},[69,9449,9450,9452,9454,9456,9458,9460,9462,9464,9466,9469,9471,9473,9475,9477,9479,9481,9483,9485,9487],{"class":71,"line":6005},[69,9451,225],{"class":93},[69,9453,194],{"class":187},[69,9455,197],{"class":75},[69,9457,200],{"class":187},[69,9459,203],{"class":75},[69,9461,651],{"class":174},[69,9463,209],{"class":75},[69,9465,212],{"class":75},[69,9467,9468],{"class":215},"invoice-ja.pdf",[69,9470,212],{"class":75},[69,9472,191],{"class":75},[69,9474,665],{"class":187},[69,9476,191],{"class":75},[69,9478,670],{"class":340},[69,9480,673],{"class":75},[69,9482,194],{"class":187},[69,9484,230],{"class":75},[69,9486,233],{"class":75},[69,9488,181],{"class":75},[69,9490,9491,9493,9495,9497,9499,9501],{"class":71,"line":6011},[69,9492,241],{"class":187},[69,9494,203],{"class":75},[69,9496,246],{"class":174},[69,9498,209],{"class":75},[69,9500,251],{"class":187},[69,9502,160],{"class":75},[69,9504,9505],{"class":71,"line":6036},[69,9506,259],{"class":75},[69,9508,9509],{"class":71,"line":6068},[69,9510,707],{"class":75},[19,9512,9513],{},"눈여겨볼 점:",[1113,9515,9516,9532,9541,9556],{},[886,9517,9518,2125,9526,9528,9529,9531],{},[30,9519,9520,9522,9523,9525],{},[47,9521,2574],{},"도, UTF-8 플래그도, ",[47,9524,507],{},"의 폰트 경로 인자도 없다",[47,9527,8429],{},"로 family를 등록하고 ",[47,9530,3000],{},"는 Unicode를 쓸 뿐. 배선은 전부 내부.",[886,9533,9534,9537,9538,9540],{},[30,9535,9536],{},"굵기는 별도 family이지 플래그가 아니다",". 이것이 TTF의 배포 방식(Noto Sans JP Regular와 Bold는 ",[47,9539,8511],{}," 테이블이 다른 별도 파일)과 부합한다. Gothic/Mincho, Source Han Sans JP Normal/Heavy도 같은 패턴.",[886,9542,9543,2125,9546,2128,9549,9552,9553,203],{},[30,9544,9545],{},"레이아웃은 그리드, 커서가 아니다",[47,9547,9548],{},"r.Col(7, ...)",[47,9550,9551],{},"r.Col(5, ...)","는 합 12. 너비는 선언적이며 x 좌표는 계산하지 않는다. 자세한 건 ",[22,9554,9555],{"href":6653},"gpdf의 12칼럼 그리드 동작 방식",[886,9557,9558,9564],{},[30,9559,9560,9563],{},[47,9561,9562],{},"AlignRight()","는 로케일 독립",". 일본어 \"¥ 128,000\"도 \"$1,280.00\"와 같은 방식으로 우측 정렬. 텍스트 내용에 레이아웃 코드가 반응하지 않는다.",[19,9566,9567,9568,9570,9571,9573],{},"생성된 ",[47,9569,9468],{},"를 아무 리더로 연다. \"株式会社 ABC 御中\"을 선택해 에디터에 붙인다. 깨지지 않고 ",[47,9572,9246],{},"이 나온다. 이것이 ToUnicode CMap의 일이며 gpdf가 기본으로 쓴다.",[14,9575,9577],{"id":9576},"폰트-서브셋화-숨어-있는-크기-폭탄","폰트 서브셋화 — 숨어 있는 크기 폭탄",[19,9579,9580,9581,5225,9584,203],{},"튜토리얼이 건너뛰기 쉬운 CJK-in-PDF의 ",[30,9582,9583],{},"가장 중요한 성질",[30,9585,9586],{},"서브셋 임베드",[19,9588,9589],{},"TTF는 글리프 아웃라인과 메타데이터 테이블의 모음이다. Noto Sans JP Regular는 약 17,500 글리프·5.1 MB. 일반 청구서가 사용하는 고유 일본어 문자는 60〜200. 문서마다 풀 임베드는 자릿수 단위의 낭비다.",[19,9591,9592,9593,9596],{},"서브셋 임베드는 ",[30,9594,9595],{},"사용한 글리프만 남긴다",". gpdf는 이를 자동으로 한다. 위 예제로 확인:",[60,9598,9600],{"className":1250,"code":9599,"language":1252,"meta":65,"style":65},"$ ls -l invoice-ja.pdf\n-rw-r--r--  1 dev  staff  34892 Apr 16 10:12 invoice-ja.pdf\n",[47,9601,9602,9616],{"__ignoreMap":65},[69,9603,9604,9607,9610,9613],{"class":71,"line":72},[69,9605,9606],{"class":79},"$",[69,9608,9609],{"class":215}," ls",[69,9611,9612],{"class":215}," -l",[69,9614,9615],{"class":215}," invoice-ja.pdf\n",[69,9617,9618,9621,9624,9627,9630,9633,9636,9638,9641],{"class":71,"line":83},[69,9619,9620],{"class":79},"-rw-r--r--",[69,9622,9623],{"class":340},"  1",[69,9625,9626],{"class":215}," dev",[69,9628,9629],{"class":215},"  staff",[69,9631,9632],{"class":340},"  34892",[69,9634,9635],{"class":215}," Apr",[69,9637,7514],{"class":340},[69,9639,9640],{"class":215}," 10:12",[69,9642,9615],{"class":215},[19,9644,9645,9646,1157,9648,9651,9652,9655],{},"34 KB. 비교: 같은 문서를 ",[47,9647,1434],{},[47,9649,9650],{},"AddUTF8Font(\"NotoSansJP\", \"NotoSansJP-Regular.ttf\", true)"," (세 번째 인자는 UTF-8 플래그) 로 생성하면 ",[30,9653,9654],{},"4.9 MB",". 입력도 출력 텍스트도 같은데 파일은 143배 크다. 원인은 fpdf 경로가 emit 시점에 서브셋하지 않고 폰트 테이블 전체를 임베드하기 때문.",[19,9657,9658],{},"운영 영향:",[1113,9660,9661,9671,9677],{},[886,9662,9663,9666,9667,9670],{},[30,9664,9665],{},"초당 10건 청구서 생성"," (일반적인 SaaS 규모) 에서 서브셋 차이는 ",[30,9668,9669],{},"0.3 MB/s vs 43 MB/s","의 대역폭 차이. 로드밸런서가 여기에 의견이 있다.",[886,9672,9673,9676],{},[30,9674,9675],{},"콜드 스토리지 비용은 PDF 크기에 선형",". 아카이브 500만 건 × 5 MB = 25 TB. × 30 KB = 150 GB. 오브젝트 스토리지 요금은 월간 네 자리 대 두 자리의 차이.",[886,9678,9679,9682],{},[30,9680,9681],{},"이메일 첨부","는 제공사별 10〜25 MB 상한. 5 MB 일본어 청구서 + 다른 첨부 + MIME 인코딩이면 상한에 쉽게 닿는다.",[19,9684,9685,9686,8512,9689,8512,9692,8512,9695,9698],{},"gpdf는 렌더 시점에 서브셋한다. 켜는 플래그는 없다. 어떤 글리프가 출력에 들어갔는지는 로컬 검증 도구로 볼 수 있지만 요점은: ",[47,9687,9688],{},"株",[47,9690,9691],{},"式",[47,9693,9694],{},"会",[47,9696,9697],{},"社","를 썼다면 그 네 글리프가 출력에 실리고 나머지 17,496은 실리지 않는다.",[14,9700,9702],{"id":9701},"혼용-조판-같은-줄의-漢字-かな-ascii","혼용 조판 — 같은 줄의 漢字 + かな + ASCII",[19,9704,9705],{},"일본어 텍스트가 단독으로 나오는 일은 드물다. 실무의 한 줄은 이렇다:",[60,9707,9710],{"className":9708,"code":9709,"language":928},[926],"API の P95 レイテンシは 50 ms 未満です。\n",[47,9711,9709],{"__ignoreMap":65},[19,9713,9714],{},"다섯 스크립트 공존: 로마자 (ASCII Latin), 가타카나, 히라가나, 한자 (Han), 숫자. 소박한 구현은 ASCII 부분에 잘못된 폰트를 얹어, 비례 일본어 옆에 고정폭 \"API\"가 나란히 서서 시각이 깨진다.",[19,9716,9717,9718,9721,9722,9725,9726,9729],{},"gpdf의 기본 동작은 ",[30,9719,9720],{},"등록된 family로 모든 코드포인트를 그린다",". Noto Sans JP가 기본이면 ",[47,9723,9724],{},"API","도 ",[47,9727,9728],{},"50 ms","도 Noto Sans JP의 라틴 글리프로 그려진다 — Noto는 이를 제공한다 (대부분의 일본어 슈퍼패밀리가 그렇다). 결과는 단일 서체처럼 보이고 실제로 단일 서체다.",[19,9731,9732,9733,9736,9737,9739],{},"family를 ",[30,9734,9735],{},"의도적으로 섞고 싶다면"," (ASCII는 condensed sans, 일본어는 Noto Sans JP) 둘 다 등록하고 ",[47,9738,3000],{}," 단위로 덮어쓴다:",[60,9741,9743],{"className":62,"code":9742,"language":64,"meta":65,"style":65},"c.Text(\"API の P95 レイテンシは 50 ms 未満です。\",\n    template.FontFamily(\"NotoSansJP\"))\nc.Text(\"API latency (P95) is under 50 ms.\",\n    template.FontFamily(\"InterVariable\"))\n",[47,9744,9745,9764,9783,9802],{"__ignoreMap":65},[69,9746,9747,9749,9751,9753,9755,9757,9760,9762],{"class":71,"line":72},[69,9748,483],{"class":187},[69,9750,203],{"class":75},[69,9752,507],{"class":174},[69,9754,209],{"class":75},[69,9756,212],{"class":75},[69,9758,9759],{"class":215},"API の P95 レイテンシは 50 ms 未満です。",[69,9761,212],{"class":75},[69,9763,2059],{"class":75},[69,9765,9766,9769,9771,9773,9775,9777,9779,9781],{"class":71,"line":83},[69,9767,9768],{"class":187},"    template",[69,9770,203],{"class":75},[69,9772,5006],{"class":174},[69,9774,209],{"class":75},[69,9776,212],{"class":75},[69,9778,3257],{"class":215},[69,9780,212],{"class":75},[69,9782,5029],{"class":75},[69,9784,9785,9787,9789,9791,9793,9795,9798,9800],{"class":71,"line":90},[69,9786,483],{"class":187},[69,9788,203],{"class":75},[69,9790,507],{"class":174},[69,9792,209],{"class":75},[69,9794,212],{"class":75},[69,9796,9797],{"class":215},"API latency (P95) is under 50 ms.",[69,9799,212],{"class":75},[69,9801,2059],{"class":75},[69,9803,9804,9806,9808,9810,9812,9814,9817,9819],{"class":71,"line":100},[69,9805,9768],{"class":187},[69,9807,203],{"class":75},[69,9809,5006],{"class":174},[69,9811,209],{"class":75},[69,9813,212],{"class":75},[69,9815,9816],{"class":215},"InterVariable",[69,9818,212],{"class":75},[69,9820,5029],{"class":75},[19,9822,9823,9824,9826],{},"두 번의 ",[47,9825,3000],{},", 두 family, 스크립트 감지 로직은 코드에 없다. 한 줄 안에서 섞고 싶다면 (같은 문장 내 ASCII는 Inter, 일본어는 Noto) gpdf v1.2에서 지원 예정이며, 현재 우회는 스크립트 경계에서 수동 분할 후 가로 열로 배치하는 방식.",[14,9828,8462],{"id":9829},"여전히-어려운-지점",[19,9831,9832],{},"Go의 일본어 PDF 이야기는 95% 풀려 있다. 남은 5%를 정직하게.",[19,9834,9835,9838,9839,9842],{},[30,9836,9837],{},"세로쓰기 (縦書き)는 아직 미지원."," gpdf v1.x는 가로만. 전통적인 일본어 조판 — 우→좌 열, 각 열은 위→아래, 적절한 글리프 회전과 문장부호 재배치 — 는 렌더 미세조정이 아니라 레이아웃 엔진의 깊은 변경이다. 설계안이 달린 오픈 이슈가 있고, 도착할 때 도착한다. 지금 꼭 세로가 필요하다면 (도서·정식 서한), 다른 도구 (Word, InDesign, pandoc + LuaLaTeX 파이프라인) 로 세로 PDF를 만들고 ",[47,9840,9841],{},"gpdf.Merge","로 병합하는 것이 현실해.",[19,9844,9845,897,9848,9851],{},[30,9846,9847],{},"루비 (振り仮名)는 우회만 가능.",[47,9849,9850],{},"c.Ruby(\"漢字\", \"かんじ\")"," 같은 프리미티브는 없다. 어린이 콘텐츠나 언어 교재에서 필요하다면, 위 줄은 작은 가나, 아래 줄은 일반 크기 한자 정렬 — 두 행 구조로. 동작은 하지만 수동이고 가나 경계의 미세 자간 관리가 필요하다.",[19,9853,9854,9857,9858,8512,9861,8512,9864,9867,9868,9870,9871,9875],{},[30,9855,9856],{},"여러 CJK 폰트 간 자동 폴백은 없다."," 사용자 입력이 JP 한자와 CN 전용 자형 (",[47,9859,9860],{},"直",[47,9862,9863],{},"骨",[47,9865,9866],{},"角","은 JP/CN에서 미세하게 다르다) 을 섞는다면, 수동으로 쪼개 두 family를 쓰게 된다. 같은 ",[47,9869,3000],{}," 호출 내에서 family를 건너뛰는 자동 폴백은 아직. 실무에서 여기가 직접 박히는 문서는 많지 않지만, 필요하면 ",[22,9872,9874],{"href":9873},"/ko/blog/","JP/CN/KR/EN 혼합 PDF"," (B-070 예정) 참고.",[19,9877,9878,9881,9882,9885,9886,9889],{},[30,9879,9880],{},"엄격 PDF/A-2b + 일본어."," gpdf는 ",[47,9883,9884],{},"gpdf.WithPDFA","로 PDF/A를 낼 수 있지만, 임베디드 글리프 메타데이터, CJK 런의 ",[47,9887,9888],{},"ActualText",", 태그된 구조 트리 등 엄격 요건은 CJK 케이스에서 아직 다듬는 중이다. 장기 보존 (일본의 전자장부보존법, 한국의 전자세금계산서 보관 등) 이라면 커밋 전에 veraPDF (무료) 등 서드파티로 검증하라.",[19,9891,9892],{},"어느 것도 일반 용도 (청구서·보고서·명세서·영수증·증명서) 의 블로커는 아니다. 적어두는 것은 누군가는 그중 하나에 프로덕션에서 부딪힐 것이기 때문이다 — \"로드맵에 있음\"보다 \"여기가 우회로\"가 유용하다.",[14,9894,8242],{"id":8241},[19,9896,9897,9898,9901],{},"자주 언급되지 않는 맥락 하나: ",[30,9899,9900],{},"2026년의 일본어 PDF 생성은 단순한 조판 문제가 아니다",". 두 규제 축이 이를 컴플라이언스 대화로 밀어넣는다.",[19,9903,9904,9907,9908,9911],{},[30,9905,9906],{},"적격청구서 (適格請求書 인보이스) 제도","는 청구서에 특정 필드 (등록 사업자 번호, 적용 세율, 세액 내역) 와 변조 방지 보관을 요구한다. PDF가 사실상의 기본 포맷이고, \"변조 방지\"는 ",[30,9909,9910],{},"PDF 전자서명"," — 엄격 모드에서는 PAdES-B-LT — 에 매핑된다.",[19,9913,9914,9917,9918,203],{},[30,9915,9916],{},"전자장부보존법"," (2024 개정) 은 전자로 수령한 청구서의 보관 의무를 확장했다. 아카이브 PDF는 일정 무결성 요건을 만족해야 한다. 사실상 목표 포맷은 ",[30,9919,9920],{},"PDF/A-2b 또는 PDF/A-3b",[19,9922,9923,9924,9927,9928,9931,9932,9934],{},"둘 다 ",[30,9925,9926],{},"PDF 네이티브 기능"," — 서명, 장기 검증, PDF/A 임베디드 메타데이터 — 에 기댄다. 헤드리스 브라우저 경유 HTML→PDF는 어느 쪽도 깔끔히 만족하지 못한다. Chromium PDF 출력은 PDF/A가 아니고 단일 단계로 전자서명을 임베드하지도 못한다. 네이티브 Go 스택 (gpdf + ",[47,9929,9930],{},"gpdf/signature","의 PAdES + ",[47,9933,9884],{},") 은 이 체인을 프로세스 이탈 없이 한 파이프라인으로 통과한다.",[19,9936,9937,9938,9941],{},"본문에서는 ",[30,9939,9940],{},"예고","에 그친다 — 서명과 PDF/A는 각자 히어로 한 편 분량이다 (백로그 B-067, B-068). 다만 오늘 일본어 PDF 스택을 고르는데 컴플라이언스가 눈에 들어온다면, 네이티브로 서명과 PDF/A를 낼 수 있는 스택을 고르라. \"지금 동작\"에서 \"감사 통과\"로의 이주세는 실재하며 뒤로 미룰수록 비싸다.",[14,9943,2827],{"id":2826},[19,9945,9946,9949,9950,8280,9953,9956,9957,9960,9961,9964,9965,9968],{},[30,9947,9948],{},"서버나 컨테이너에 폰트를 설치해야 하나?","\n아니. gpdf는 TTF 바이트를 읽는다 — 시스템 폰트 캐시를 보지 않는다. ",[47,9951,9952],{},"os.ReadFile(\"NotoSansJP-Regular.ttf\")",[47,9954,9955],{},"//go:embed NotoSansJP-Regular.ttf","는 macOS / Linux / Windows, distroless 컨테이너, AWS Lambda에서 동일하게 동작한다. ",[47,9958,9959],{},"fontconfig"," 불필요, ",[47,9962,9963],{},"fc-cache -fv"," 불필요. gpdf가 ",[47,9966,9967],{},"FROM scratch"," 이미지에서 돌아가는 이유 중 하나.",[19,9970,9971,9974],{},[30,9972,9973],{},"Noto Sans JP와 Source Han Sans JP 중 어느 쪽?","\n같은 폰트, 두 이름. Adobe가 Source Han Sans JP로 발행한 것을 Google이 Noto Sans JP로 재포장한다. 글리프 커버리지 동일. 둘 다 SIL Open Font License — 법무 리뷰가 수월한 쪽을 고르면 된다. gpdf 예제가 Noto Sans JP를 기본으로 쓰는 것은 파일명이 기억하기 쉬워서.",[19,9976,9977,9980,9981,9984],{},[30,9978,9979],{},"游ゴシック (Yu Gothic) 이나 히라기노는?","\nOS 번들 상용 폰트. 배포 대상이 라이선스를 가졌다면 사용 가능 (Windows Server는 Yu Gothic 번들, macOS는 히라기노 번들) 이지만, TTF 파일의 확보와 컨테이너 빌드에서의 재배포 조건은 각자 확인 필요. 개방형 배포에는 ",[30,9982,9983],{},"Noto Sans JP 또는 IPAex Gothic"," (양쪽 모두 자유 재배포 가능) 권장.",[19,9986,9987,9994,9995,9998],{},[30,9988,9989,9990,9993],{},"PDF는 나오는데 ",[47,9991,9992],{},"Ctrl+F"," 검색이 안 된다","\n거의 확실히 ToUnicode CMap 이슈. gpdf는 기본으로 쓴다. gpdf에서 이 현상이라면 리더 이름을 붙여 이슈를 열어달라. gofpdf라면 UTF-8 플래그 활성화 ",[30,9996,9997],{},"+"," 리더가 CID 폰트를 지원하는지 확인 (구 버전 macOS Preview.app에 알려진 문제 있음). 대조군으로 Adobe Reader 또는 Chrome 사용.",[19,10000,10001,10004,10005,10008],{},[30,10002,10003],{},"폰트에 없는 JIS X 0213 문자는?","\n그릴 글리프가 없으니 나오지 않는다. 실용 답은 \"JIS X 0213을 커버하는 폰트 사용\". Noto Sans JP는 BMP 전역 + JIS X 0213 1수준을 커버한다. 희귀 이체자는 Hanazono Mincho (화원명조) 같은 말단 폴백. 어떤 폰트에도 없는 코드포인트는 gpdf가 Unicode 치환 문자 (U+FFFD) 를 낸다 — 무음의 두부가 아니라 ",[47,10006,10007],{},"�","가 보이므로 조사의 단서가 된다.",[19,10010,10011,10014,10015,10017],{},[30,10012,10013],{},"CJK는 ASCII보다 느린가?","\n살짝. gpdf의 \"complex CJK invoice\" 벤치는 Apple M1에서 133 µs, ASCII 4×10 테이블이 108 µs. 약 23% 오버헤드로 대부분 글리프 룩업과 서브셋 비용이다. 참고: 같은 CJK 벤치에서 ",[47,10016,1434],{},"는 254 µs, Maroto v2는 10.4 ms. 일본어 렌더가 서비스 병목이 되는 일은 거의 없다.",[14,10019,10021],{"id":10020},"gpdf-사용해-보기","gpdf 사용해 보기",[19,10023,10024],{},"gpdf는 Go용 PDF 생성 라이브러리. MIT, 외부 의존 없음, 네이티브 CJK.",[60,10026,10027],{"className":1250,"code":1251,"language":1252,"meta":65,"style":65},[47,10028,10029],{"__ignoreMap":65},[69,10030,10031,10033,10035],{"class":71,"line":72},[69,10032,64],{"class":79},[69,10034,1261],{"class":215},[69,10036,1264],{"class":215},[19,10038,10039,1271,10042],{},[22,10040,2894],{"href":24,"rel":10041},[26],[22,10043,1276],{"href":1274,"rel":10044},[26],[14,10046,10048],{"id":10047},"이어-읽을-글","이어 읽을 글",[1113,10050,10051,10056,10062,10067],{},[886,10052,10053,10055],{},[22,10054,6573],{"href":1224}," — 배경 없는 3행 레시피",[886,10057,10058,10061],{},[22,10059,10060],{"href":834},"gpdf에서 Noto Sans JP 쓰기"," — Regular / Bold / Medium 웨이트 설정",[886,10063,10064,10066],{},[22,10065,9555],{"href":6653}," — 커서 계산을 대체하는 레이아웃 관용구",[886,10068,10069,10072],{},[22,10070,10071],{"href":8407},"go-pdf/fpdf도 아카이브. 2026년의 Go PDF 스택"," — 2026 판도 전체",[1278,10074,1280],{},{"title":65,"searchDepth":83,"depth":83,"links":10076},[10077,10078,10079,10080,10081,10082,10083,10084,10085,10086,10087,10088],{"id":1337,"depth":83,"text":1338},{"id":8442,"depth":83,"text":8443},{"id":8469,"depth":83,"text":8470},{"id":8544,"depth":83,"text":8545},{"id":8685,"depth":83,"text":8686},{"id":9576,"depth":83,"text":9577},{"id":9701,"depth":83,"text":9702},{"id":9829,"depth":83,"text":8462},{"id":8241,"depth":83,"text":8242},{"id":2826,"depth":83,"text":2827},{"id":10020,"depth":83,"text":10021},{"id":10047,"depth":83,"text":10048},"Go에서 일본어 PDF를 생성하는 완전한 순서. CGO 없이, Chromium 없이, 두부 글자 없이. 폰트·서브셋·혼용 조판까지.",{"name":10091,"totalTime":10092,"tools":10093,"steps":10095},"Go에서 TrueType 서브셋 임베딩된 일본어 PDF 생성하기","PT20M",[1301,10094],"NotoSansJP-Regular.ttf 및 NotoSansJP-Bold.ttf (또는 임의의 일본어 지원 TTF 쌍)",[10096,10099,10102,10105,10108,10111],{"name":10097,"text":10098},"gpdf 설치 및 폰트 준비","go get github.com/gpdf-dev/gpdf를 실행. Google Fonts에서 Noto Sans JP Regular와 Bold를 내려받아 main.go 옆에 둔다. CGO도, OS 폰트 설정도 필요 없다.",{"name":10100,"text":10101},"기동 시 TTF 바이트를 읽어들이기","os.ReadFile로 두 TTF 파일을 []byte로 읽는다. 바이너리에 묻고 싶다면 //go:embed도 가능.",{"name":10103,"text":10104},"문서 구성 시 폰트 등록","gpdf.WithFont(\"NotoSansJP\", regular)과 gpdf.WithFont(\"NotoSansJP-Bold\", bold)을 gpdf.NewDocument에 넘긴다. family 이름은 임의 식별자 — 이후 참조할 핸들에 불과하다.",{"name":10106,"text":10107},"일본어 폰트를 기본으로 설정","gpdf.WithDefaultFont(\"NotoSansJP\", 11)을 추가. 이후의 c.Text는 FontFamily 옵션 없이 일본어 폰트를 사용한다.",{"name":10109,"text":10110},"c.Text로 문서 트리를 조립","page.AutoRow 블록 안에서 r.Col(span, fn)을 호출하고 c.Text(\"こんにちは、世界。\")를 쓴다. 굵기와 크기는 메서드가 아니라 template 옵션이다.",{"name":10112,"text":10113},"생성하고 동작 확인","doc.Generate()로 []byte를 받아 os.WriteFile로 저장. PDF를 열어 텍스트를 선택해 에디터에 붙여넣으면 — ToUnicode CMap 덕분에 복사·붙여넣기가 정확히 동작한다.",{},{"title":8415,"description":10089},"ko/blog/007.japanese-pdf-in-go",[1327,1326,4051],"uW2Khc9HlItLbXpkwE4500UyNrvr0zIo6xJx8VjxID0",{"id":10120,"title":10121,"author":10122,"body":10123,"date":11719,"description":11720,"draft":1295,"extension":1296,"howTo":1319,"image":1319,"meta":11721,"navigation":86,"path":2909,"seo":11722,"stem":11723,"tags":11724,"updated":1319,"__hash__":11725},"blogKo/ko/blog/002.go-pdf-library-showdown-2026.md","2026 년 Go PDF 라이브러리 비교 쇼다운",{"name":8,"url":9},{"type":11,"value":10124,"toc":11701},[10125,10127,10133,10153,10156,10162,10166,10169,10200,10203,10206,10209,10385,10392,10394,10406,10409,10434,10440,10443,10528,10537,10541,10548,10557,10563,10569,10575,10578,10588,10665,10668,10670,10724,10727,10733,10737,10775,10779,10782,10822,10825,10829,10832,11551,11560,11564,11567,11605,11607,11613,11623,11629,11635,11641,11645,11648,11660,11669,11673,11698],[14,10126,1338],{"id":1337},[19,10128,10129,10130,10132],{},"5 년 전에 \"Go PDF\"를 검색하면 거의 틀림없이 ",[30,10131,1356],{}," 로 연결됐다. 지금 그 저장소는 아카이브 상태. 커뮤니티 포크 go-pdf/fpdf 도 마찬가지. 검색 결과가 암시하는 것보다 현재 선택지는 훨씬 좁다.",[1113,10134,10135,10141,10147],{},[886,10136,10137,10140],{},[30,10138,10139],{},"활발히 유지보수 중",": gpdf (본 팀), signintech/gopdf, johnfercher/maroto v2 — 단 Maroto 는 아카이브된 gofpdf 에 의존한다.",[886,10142,10143,10146],{},[30,10144,10145],{},"아카이브",": jung-kurt/gofpdf (2021), go-pdf/fpdf (2025).",[886,10148,10149,10152],{},[30,10150,10151],{},"상용 / AGPL",": unidoc/unipdf.",[19,10154,10155],{},"이 글에서는 현역 라이브러리 4 개를 4 가지 워크로드로 벤치마크하고, 라이선스와 의존성 그래프, 유지보수 상태를 표로 정리한다. 용례별 선택 가이드도 끝에 둔다. 내년에 다시 돌린다.",[19,10157,10158,10159,10161],{},"편향 공개: 우리는 gpdf 팀. 벤치마크 코드는 공개 (",[47,10160,1398],{},"). 직접 클론해서 다시 측정해 주고, 숫자가 다르면 알려 달라.",[14,10163,10165],{"id":10164},"go-pdf-라이브러리는-세-종류다","\"Go PDF 라이브러리\"는 세 종류다",[19,10167,10168],{},"\"Go PDF 라이브러리\" 라는 말이 사실은 세 가지 서로 다른 도구를 한 선반에 올려놓고 있다:",[883,10170,10171,10181,10192],{},[886,10172,10173,10176,10177,854,10179,203],{},[30,10174,10175],{},"저수준 PDF 라이터"," — 바이트를 밀고 프리미티브로 그린다. ",[47,10178,1356],{},[47,10180,1437],{},[886,10182,10183,10186,10187,854,10190,203],{},[30,10184,10185],{},"라이터를 감싼 레이아웃 라이브러리"," — 선언적 행/열. ",[47,10188,10189],{},"johnfercher/maroto v2",[47,10191,27],{},[886,10193,10194,10197,10198,203],{},[30,10195,10196],{},"전체 문서 스위트"," — 파싱, 서명, PDF/A, OCR, 블랙아웃. ",[47,10199,6952],{},[19,10201,10202],{},"\"가장 좋은 Go PDF 라이브러리는 뭔가요?\" 같은 질문이 Reddit/인프런 댓글에서 엇나가는 이유가 여기다. 아래 비교에서는 계속 이 구분을 살려 둔다.",[19,10204,10205],{},"라인업에서 빠진 것: headless Chromium 을 띄우는 쪽 (go-rod, chromedp). 그건 PDF 라이브러리가 아니고 \"인쇄 기능을 가진 브라우저\". CSS 가 무거운 디자인 재현엔 좋지만 콜드스타트/메모리/distroless 배포 어디든 무겁다. \"디자이너가 준 HTML+CSS 를 픽셀 단위로 재현\" 이 목표라면 그 도구들이 답이고, 여기서는 그쪽과 경쟁하지 않는다.",[14,10207,10208],{"id":10208},"스코어보드",[741,10210,10211,10236],{},[744,10212,10213],{},[747,10214,10215,10217,10220,10222,10224,10227,10230,10233],{},[750,10216,6844],{},[750,10218,10219],{},"마지막 릴리스",[750,10221,10145],{},[750,10223,5142],{},[750,10225,10226],{},"코어 의존",[750,10228,10229],{},"CJK",[750,10231,10232],{},"레이아웃 그리드",[750,10234,10235],{},"2026 상태",[759,10237,10238,10267,10287,10310,10336,10362],{},[747,10239,10240,10245,10248,10251,10253,10258,10260,10263],{},[764,10241,10242,10244],{},[30,10243,27],{}," (본 팀)",[764,10246,10247],{},"활발",[764,10249,10250],{},"—",[764,10252,6873],{},[764,10254,10255],{},[30,10256,10257],{},"0",[764,10259,7004],{},[764,10261,10262],{},"12 컬럼",[764,10264,10265],{},[30,10266,6911],{},[747,10268,10269,10271,10273,10275,10277,10279,10282,10285],{},[764,10270,1437],{},[764,10272,10247],{},[764,10274,10250],{},[764,10276,6873],{},[764,10278,10257],{},[764,10280,10281],{},"TTF 수동",[764,10283,10284],{},"없음",[764,10286,6911],{},[747,10288,10289,10291,10293,10295,10297,10302,10304,10307],{},[764,10290,10189],{},[764,10292,10247],{},[764,10294,10250],{},[764,10296,6873],{},[764,10298,10299],{},[30,10300,10301],{},"gofpdf (아카이브)",[764,10303,6936],{},[764,10305,10306],{},"행/열",[764,10308,10309],{},"기반이 죽은 상태",[747,10311,10312,10314,10317,10322,10324,10326,10330,10332],{},[764,10313,1356],{},[764,10315,10316],{},"2021",[764,10318,10319],{},[30,10320,10321],{},"2021-09-08",[764,10323,6873],{},[764,10325,10257],{},[764,10327,10328],{},[47,10329,2574],{},[764,10331,10284],{},[764,10333,10334],{},[30,10335,10145],{},[747,10337,10338,10340,10343,10348,10350,10352,10356,10358],{},[764,10339,1434],{},[764,10341,10342],{},"2023",[764,10344,10345],{},[30,10346,10347],{},"2025",[764,10349,6873],{},[764,10351,10257],{},[764,10353,10354],{},[47,10355,2574],{},[764,10357,10284],{},[764,10359,10360],{},[30,10361,10145],{},[747,10363,10364,10366,10368,10370,10375,10378,10381,10383],{},[764,10365,6952],{},[764,10367,10247],{},[764,10369,10250],{},[764,10371,10372],{},[30,10373,10374],{},"AGPL-3.0 / 상용",[764,10376,10377],{},"다수",[764,10379,10380],{},"있음",[764,10382,10284],{},[764,10384,6959],{},[19,10386,10387,10388,10391],{},"주목할 점 셋. 절반이 아카이브됐다. Maroto 는 본체는 활발하지만 기반이 죽어 — 오늘 빌드가 통과해도 공급망 리스크가 있다. AGPL 을 수용 못 하는 조직에서 unidoc 선택은 기술 문제가 아니라 ",[30,10389,10390],{},"상용 라이선스 조달 문제","다.",[14,10393,8081],{"id":8081},[19,10395,10396,10397,10402,10403,10405],{},"코드: gpdf 저장소의 ",[22,10398,10400],{"href":1394,"rel":10399},[26],[47,10401,1398],{},". 환경은 Apple M1 (Max, 32 GB, macOS 14.5), Go 1.25, CGO 없음. 각 케이스는 최소 5 초 실시간으로 돌렸고, ",[47,10404,1416],{}," 을 켜서 ns/op 와 할당 횟수를 기록했다.",[19,10407,10408],{},"4 가지 케이스를 고른 이유는 실제 프로덕션에서 생성되는 모양에 가깝기 때문:",[883,10410,10411,10417,10423,10429],{},[886,10412,10413,10416],{},[30,10414,10415],{},"단일 페이지 hello world",". 1 페이지 / 1 줄 / 1 폰트. 문서당 고정 오버헤드의 하한.",[886,10418,10419,10422],{},[30,10420,10421],{},"4×10 인보이스 테이블",". 헤더 1 행 + 바디 10 행 + 컬럼 정렬 + 얇은 테두리. \"인보이스 생성\" 형.",[886,10424,10425,10428],{},[30,10426,10427],{},"100 페이지 페이지 구분 리포트",". 반복 헤더, 푸터, 페이지 번호, 본문. 페이지 구분 비용 측정.",[886,10430,10431,10433],{},[30,10432,1509],{},". 일본어 (히라가나·가타카나·한자) 혼합, 4×15 명세 표, 헤더, 페이지 번호 포함 푸터, NotoSansJP TrueType 서브셋 임베드.",[19,10435,10436,10437,10439],{},"포함 안 함: ",[47,10438,6952],{},". 바이너리가 라이선스로 게이트돼 있어서, 그들의 공개 벤치 방법론을 공개 벤치 저장소에서 재현하는 건 오해 소지를 만든다. unidoc 를 검토 중이라면 자체 공식 벤치를 돌려 보라.",[1947,10441,10442],{"id":10442},"결과",[741,10444,10445,10461],{},[744,10446,10447],{},[747,10448,10449,10451,10453,10455,10457,10459],{},[750,10450,1426],{},[750,10452,27],{},[750,10454,1437],{},[750,10456,1440],{},[750,10458,1431],{},[750,10460,1434],{},[759,10462,10463,10479,10495,10512],{},[747,10464,10465,10467,10471,10473,10475,10477],{},[764,10466,10415],{},[764,10468,10469],{},[30,10470,1344],{},[764,10472,1460],{},[764,10474,1463],{},[764,10476,1454],{},[764,10478,1457],{},[747,10480,10481,10483,10487,10489,10491,10493],{},[764,10482,10421],{},[764,10484,10485],{},[30,10486,1348],{},[764,10488,1481],{},[764,10490,1484],{},[764,10492,1475],{},[764,10494,1478],{},[747,10496,10497,10500,10504,10506,10508,10510],{},[764,10498,10499],{},"100 페이지 리포트",[764,10501,10502],{},[30,10503,1352],{},[764,10505,1484],{},[764,10507,1504],{},[764,10509,1496],{},[764,10511,1499],{},[747,10513,10514,10516,10520,10522,10524,10526],{},[764,10515,1509],{},[764,10517,10518],{},[30,10519,1514],{},[764,10521,1523],{},[764,10523,1526],{},[764,10525,1517],{},[764,10527,1520],{},[19,10529,10530,10531,10533,10534,10536],{},"go-pdf/fpdf 의 CJK 칸이 ",[47,10532,1520],{}," 인 이유: 테스트한 버전에서 ",[47,10535,2574],{}," 경로가 NotoSansJP 의 cmap format 12 테이블을 읽을 때 panic. 패치로 고칠 수 있지만 포크 자체가 아카이브 — 아무도 수정을 릴리스하지 않는다.",[1947,10538,10540],{"id":10539},"숫자-읽는-법","숫자 읽는 법",[19,10542,10543,10544,10547],{},"순서는 워크로드를 넘나들어도 안정적이다. 모든 케이스에서 gpdf 는 2 위보다 ",[30,10545,10546],{},"10–30 배 빠르다",". 기묘한 기술이 아니라, 3 가지 설계가 누적된 결과:",[19,10549,10550,10553,10554,203],{},[30,10551,10552],{},"단일 패스 레이아웃",". gpdf 는 중간 AST 를 만들었다가 직렬화하지 않는다. 빌더가 해석된 시점에 PDF content stream 에 직접 쓰기 때문에 다른 라이브러리 대비 할당이 대략 절반으로 준다. 100 페이지 벤치에서 683 µs 대 19,800 µs 차이가 나는 건 튜닝 차이가 아니라 ",[30,10555,10556],{},"아키텍처가 다르다",[19,10558,10559,10562],{},[30,10560,10561],{},"핫패스에 리플렉션 없음",". 레이아웃 엔진이 닿는 타입은 전부 구체 타입. 개별로는 미세 최적화이지만 100 페이지 리포트 프로파일에선 인터페이스 디스패치가 보이기 시작. 우리는 피했다.",[19,10564,10565,10568],{},[30,10566,10567],{},"cmap 을 캐시하는 TrueType 서브세터",". gofpdf 는 글리프 조회마다 cmap 테이블을 다시 읽는다; gpdf 는 한 번 해석 후 캐시. Latin 전용이면 거의 차이 없지만 CJK 는 한 문단에 한자 + 가나 + 구두점으로 150 글리프를 건드릴 수 있고, \"동기 생성 가능\" 과 \"큐에 넣어야 함\" 의 경계가 된다.",[19,10570,10571,10572,10574],{},"벤치 표에는 안 나오는 주의 하나: ",[30,10573,1549],{},". 의미 있는 경계는 \"요청 경로에서 동기 생성해도 괜찮은가\" 다. hello world 한 페이지라면 모든 라이브러리가 통과. 반복 chrome 이 있는 100 페이지 리포트에선 gpdf 만 통과. 최대 문서가 영수증 한 장이라면 현역 4 개 모두 괜찮고, API 와 라이선스로 고르면 된다.",[14,10576,10577],{"id":10577},"의존성",[19,10579,10580,10581,10584,10585,10587],{},"각각 방금 ",[47,10582,10583],{},"go get"," 한 후 ",[47,10586,7093],{}," 결과:",[741,10589,10590,10602],{},[744,10591,10592],{},[747,10593,10594,10596,10599],{},[750,10595,6844],{},[750,10597,10598],{},"외부 모듈",[750,10600,10601],{},"전이적 아카이브 의존",[759,10603,10604,10617,10625,10635,10643,10655],{},[747,10605,10606,10611,10615],{},[764,10607,10608,10610],{},[30,10609,27],{}," (코어)",[764,10612,10613],{},[30,10614,10257],{},[764,10616,10250],{},[747,10618,10619,10621,10623],{},[764,10620,1437],{},[764,10622,10257],{},[764,10624,10250],{},[747,10626,10627,10629,10632],{},[764,10628,1431],{},[764,10630,10631],{},"0 (자신이 아카이브)",[764,10633,10634],{},"자신",[747,10636,10637,10639,10641],{},[764,10638,1434],{},[764,10640,10631],{},[764,10642,10634],{},[747,10644,10645,10647,10652],{},[764,10646,10189],{},[764,10648,10649],{},[30,10650,10651],{},"gofpdf (2021 아카이브)",[764,10653,10654],{},"있음 — gofpdf",[747,10656,10657,10659,10662],{},[764,10658,6952],{},[764,10660,10661],{},"다수 (이미지, 암호, 압축)",[764,10663,10664],{},"아카이브 없음",[19,10666,10667],{},"\"프로덕션 의존에 아카이브 레포 금지\" 린트 룰이 있는 팀이라면 오늘의 Maroto v2 는 이 규칙에 걸린다. Maroto 메인테이너들은 1 년 이상 gofpdf 제거 작업을 해왔고, 완료되면 이 행은 바뀐다. 판단 전에 Maroto 레포 현황을 확인하는 편이 좋다.",[14,10669,5142],{"id":5142},[741,10671,10672,10680],{},[744,10673,10674],{},[747,10675,10676,10678],{},[750,10677,6844],{},[750,10679,5142],{},[759,10681,10682,10689,10695,10701,10707,10713],{},[747,10683,10684,10687],{},[764,10685,10686],{},"gpdf (코어)",[764,10688,6873],{},[747,10690,10691,10693],{},[764,10692,1437],{},[764,10694,6873],{},[747,10696,10697,10699],{},[764,10698,10189],{},[764,10700,6873],{},[747,10702,10703,10705],{},[764,10704,1431],{},[764,10706,6873],{},[747,10708,10709,10711],{},[764,10710,1434],{},[764,10712,6873],{},[747,10714,10715,10719],{},[764,10716,10717],{},[30,10718,6952],{},[764,10720,10721],{},[30,10722,10723],{},"AGPL-3.0 또는 상용 라이선스",[19,10725,10726],{},"unidoc 의 AGPL 은 꽤 강한 편. 사용자가 네트워크로 접속하는 서버에서 사용한다면 서버 쪽 코드도 AGPL 로 공개해야 한다 — 대부분의 클로즈드 SaaS 에는 성립하지 않는다. 결국 상용 라이선스가 유일한 현실적 선택이 되고, 가격은 공개되지 않았다. 영업 상담이 전제.",[19,10728,10729,10730,10732],{},"GitHub 스타 수 비교에서 가장 자주 놓치는 지점이 여기. unidoc 는 기능도 가장 많고 스타도 가장 많지만, 상용 용례 대부분에 문을 닫는 라이선스를 가지고 있다 (구매 전제). unidoc 를 깎아내리는 게 아니다 — 비즈니스 모델은 정당하고 제품도 훌륭하다. 다만 ",[47,10731,10583],{}," 전에 알고 있어야 한다.",[14,10734,10736],{"id":10735},"유지보수-상태","유지보수 상태",[1113,10738,10739,10744,10749,10754,10762,10770],{},[886,10740,10741,10743],{},[30,10742,27],{}," — 주 메인테이너는 본 팀 (gpdf-dev). 2–4 주마다 릴리스, 로드맵은 레포 내, CI 는 Go 1.22–1.26 에서 돌고, 메인 레포 이슈는 며칠 안에 응답. 진지하게 투자하고 있다.",[886,10745,10746,10748],{},[30,10747,1437],{}," — 활발하지만 커밋 템포는 낮다. 이슈는 읽히고 PR 은 수 주 내 병합. 주 용도는 여전히 저수준 생성.",[886,10750,10751,10753],{},[30,10752,1440],{}," — 활발. 2023 년 v2 재작성 이후 안정. gofpdf 의존은 알려져 있고 교체 작업 중. 결정 전에 레포 확인.",[886,10755,10756,10758,10759,10761],{},[30,10757,1431],{}," — 2021-09-08 아카이브. 레포 배너: ",[3586,10760,6701],{}," 보안 패치도 버그 수정도 없음.",[886,10763,10764,10766,10767,203],{},[30,10765,1434],{}," — 2025 년 아카이브. README 가 다른 라이브러리 사용을 권장. 별도 마이그레이션 가이드를 썼다: ",[22,10768,10769],{"href":2836},"gofpdf 에서 gpdf 로 마이그레이션",[886,10771,10772,10774],{},[30,10773,6952],{}," — 활발, 상용 팀, 리소스 풍부, 엔터프라이즈 지원 제공.",[14,10776,10778],{"id":10777},"고르는-법","고르는 법",[19,10780,10781],{},"기능 매트릭스 대신 의사결정 트리로 쓴다. \"기능 많음 = 정답\" 은 대개 맞는 질문이 아니다.",[1113,10783,10784,10790,10796,10802,10808,10816],{},[886,10785,10786,10789],{},[30,10787,10788],{},"\"Go 코드베이스에서 인보이스·리포트·문서를 생성한다. MIT 선호, 의존 0 선호, CJK 가 섞이기도 한다.\""," → gpdf.",[886,10791,10792,10795],{},[30,10793,10794],{},"\"맞춤 지오메트리까지 저수준으로 생성한다. 작고 안정적이며 수동 컨트롤이 강한 라이브러리가 좋다.\""," → signintech/gopdf.",[886,10797,10798,10801],{},[30,10799,10800],{},"\"이미 Maroto 스타일 레이아웃 코드가 돌고 있다.\""," → gofpdf 제거 완료까지 Maroto v2 유지 후 재평가. API 자체가 문제는 아니다.",[886,10803,10804,10807],{},[30,10805,10806],{},"\"PDF/A, OCR, 블랙아웃, 전자서명이 필요하고 회사가 상용 라이선스를 낼 수 있다.\""," → unidoc/unipdf. 라이선스 협의부터.",[886,10809,10810,10813,10814,203],{},[30,10811,10812],{},"\"여전히 gofpdf, 잘 돌고 있다.\""," → 오늘은 괜찮다. 다음 관련 의존의 CVE 가 터지기 전에 마이그레이션 계획. ",[22,10815,8217],{"href":2836},[886,10817,10818,10821],{},[30,10819,10820],{},"\"픽셀 단위 HTML/CSS→PDF 가 필요하다.\""," → 위 어느 것도 아님. go-rod / chromedp + headless Chromium, 콜드스타트 감수.",[19,10823,10824],{},"우리는 gpdf 팀이라 1 번과 5 번 다수 케이스에서 gpdf 가 합리적 기본값이라 생각한다 — 당연한 편향. 벤치 코드를 읽고 로컬에서 돌리고, 이 표를 곧이곧대로 믿지 말기를.",[14,10826,10828],{"id":10827},"_30-줄짜리-gpdf-예제","30 줄짜리 gpdf 예제",[19,10830,10831],{},"\"가장 빠름\" 과 \"의존 그래프 최소\" 는 코드가 읽을 만할 때 의미가 있다. 완전히 실행되는 인보이스 한 페이지, 가짜 코드/생략 import 없음:",[60,10833,10835],{"className":62,"code":10834,"language":64,"meta":65,"style":65},"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(\n        gpdf.WithPageSize(document.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"청구서 #2026-0042\", template.Bold(), template.FontSize(20))\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 디자인\",       \"20 시간\", \"₩160,000\", \"₩3,200,000\"},\n                },\n                template.ColumnWidths(50, 15, 15, 20),\n                template.TableHeaderStyle(\n                    template.Bold(),\n                    template.TextColor(pdf.White),\n                    template.BgColor(pdf.RGBHex(0x1A237E)),\n                ),\n            )\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"invoice.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[47,10836,10837,10843,10847,10853,10861,10869,10873,10881,10889,10898,10906,10910,10914,10924,10938,10956,10986,10990,10994,11008,11032,11062,11101,11123,11134,11182,11192,11233,11273,11314,11319,11350,11361,11373,11393,11419,11424,11429,11433,11437,11441,11459,11471,11485,11489,11529,11543,11547],{"__ignoreMap":65},[69,10838,10839,10841],{"class":71,"line":72},[69,10840,76],{"class":75},[69,10842,80],{"class":79},[69,10844,10845],{"class":71,"line":83},[69,10846,87],{"emptyLinePlaceholder":86},[69,10848,10849,10851],{"class":71,"line":90},[69,10850,94],{"class":93},[69,10852,97],{"class":75},[69,10854,10855,10857,10859],{"class":71,"line":100},[69,10856,103],{"class":75},[69,10858,106],{"class":79},[69,10860,109],{"class":75},[69,10862,10863,10865,10867],{"class":71,"line":112},[69,10864,103],{"class":75},[69,10866,117],{"class":79},[69,10868,109],{"class":75},[69,10870,10871],{"class":71,"line":122},[69,10872,87],{"emptyLinePlaceholder":86},[69,10874,10875,10877,10879],{"class":71,"line":127},[69,10876,103],{"class":75},[69,10878,132],{"class":79},[69,10880,109],{"class":75},[69,10882,10883,10885,10887],{"class":71,"line":137},[69,10884,103],{"class":75},[69,10886,142],{"class":79},[69,10888,109],{"class":75},[69,10890,10891,10893,10896],{"class":71,"line":147},[69,10892,103],{"class":75},[69,10894,10895],{"class":79},"github.com/gpdf-dev/gpdf/pdf",[69,10897,109],{"class":75},[69,10899,10900,10902,10904],{"class":71,"line":157},[69,10901,103],{"class":75},[69,10903,152],{"class":79},[69,10905,109],{"class":75},[69,10907,10908],{"class":71,"line":163},[69,10909,160],{"class":75},[69,10911,10912],{"class":71,"line":168},[69,10913,87],{"emptyLinePlaceholder":86},[69,10915,10916,10918,10920,10922],{"class":71,"line":184},[69,10917,171],{"class":75},[69,10919,175],{"class":174},[69,10921,178],{"class":75},[69,10923,181],{"class":75},[69,10925,10926,10928,10930,10932,10934,10936],{"class":71,"line":222},[69,10927,270],{"class":187},[69,10929,197],{"class":75},[69,10931,275],{"class":187},[69,10933,203],{"class":75},[69,10935,280],{"class":174},[69,10937,283],{"class":75},[69,10939,10940,10942,10944,10946,10948,10950,10952,10954],{"class":71,"line":238},[69,10941,289],{"class":187},[69,10943,203],{"class":75},[69,10945,294],{"class":174},[69,10947,209],{"class":75},[69,10949,321],{"class":187},[69,10951,203],{"class":75},[69,10953,303],{"class":187},[69,10955,306],{"class":75},[69,10957,10958,10960,10962,10964,10966,10968,10970,10972,10974,10976,10978,10980,10982,10984],{"class":71,"line":256},[69,10959,289],{"class":187},[69,10961,203],{"class":75},[69,10963,316],{"class":174},[69,10965,209],{"class":75},[69,10967,321],{"class":187},[69,10969,203],{"class":75},[69,10971,326],{"class":174},[69,10973,209],{"class":75},[69,10975,321],{"class":187},[69,10977,203],{"class":75},[69,10979,335],{"class":174},[69,10981,209],{"class":75},[69,10983,341],{"class":340},[69,10985,344],{"class":75},[69,10987,10988],{"class":71,"line":262},[69,10989,401],{"class":75},[69,10991,10992],{"class":71,"line":267},[69,10993,87],{"emptyLinePlaceholder":86},[69,10995,10996,10998,11000,11002,11004,11006],{"class":71,"line":286},[69,10997,412],{"class":187},[69,10999,197],{"class":75},[69,11001,417],{"class":187},[69,11003,203],{"class":75},[69,11005,422],{"class":174},[69,11007,425],{"class":75},[69,11009,11010,11012,11014,11016,11018,11020,11022,11024,11026,11028,11030],{"class":71,"line":309},[69,11011,431],{"class":187},[69,11013,203],{"class":75},[69,11015,436],{"class":174},[69,11017,439],{"class":75},[69,11019,443],{"class":442},[69,11021,446],{"class":75},[69,11023,449],{"class":79},[69,11025,203],{"class":75},[69,11027,454],{"class":79},[69,11029,457],{"class":75},[69,11031,181],{"class":75},[69,11033,11034,11036,11038,11040,11042,11044,11046,11048,11050,11052,11054,11056,11058,11060],{"class":71,"line":347},[69,11035,465],{"class":187},[69,11037,203],{"class":75},[69,11039,470],{"class":174},[69,11041,209],{"class":75},[69,11043,475],{"class":340},[69,11045,191],{"class":75},[69,11047,480],{"class":75},[69,11049,483],{"class":442},[69,11051,446],{"class":75},[69,11053,449],{"class":79},[69,11055,203],{"class":75},[69,11057,492],{"class":79},[69,11059,457],{"class":75},[69,11061,181],{"class":75},[69,11063,11064,11066,11068,11070,11072,11074,11077,11079,11081,11083,11085,11087,11089,11091,11093,11095,11097,11099],{"class":71,"line":373},[69,11065,502],{"class":187},[69,11067,203],{"class":75},[69,11069,507],{"class":174},[69,11071,209],{"class":75},[69,11073,212],{"class":75},[69,11075,11076],{"class":215},"청구서 #2026-0042",[69,11078,212],{"class":75},[69,11080,191],{"class":75},[69,11082,521],{"class":187},[69,11084,203],{"class":75},[69,11086,541],{"class":174},[69,11088,7640],{"class":75},[69,11090,521],{"class":187},[69,11092,203],{"class":75},[69,11094,526],{"class":174},[69,11096,209],{"class":75},[69,11098,341],{"class":340},[69,11100,5029],{"class":75},[69,11102,11103,11105,11107,11109,11111,11113,11115,11117,11119,11121],{"class":71,"line":398},[69,11104,502],{"class":187},[69,11106,203],{"class":75},[69,11108,2131],{"class":174},[69,11110,209],{"class":75},[69,11112,321],{"class":187},[69,11114,203],{"class":75},[69,11116,335],{"class":174},[69,11118,209],{"class":75},[69,11120,5656],{"class":340},[69,11122,5029],{"class":75},[69,11124,11125,11127,11129,11132],{"class":71,"line":404},[69,11126,502],{"class":187},[69,11128,203],{"class":75},[69,11130,11131],{"class":174},"Table",[69,11133,283],{"class":75},[69,11135,11136,11139,11142,11145,11147,11150,11152,11154,11156,11159,11161,11163,11165,11168,11170,11172,11174,11177,11179],{"class":71,"line":409},[69,11137,11138],{"class":75},"                []",[69,11140,11141],{"class":1620},"string",[69,11143,11144],{"class":75},"{",[69,11146,212],{"class":75},[69,11148,11149],{"class":215},"항목",[69,11151,212],{"class":75},[69,11153,191],{"class":75},[69,11155,7452],{"class":75},[69,11157,11158],{"class":215},"수량",[69,11160,212],{"class":75},[69,11162,191],{"class":75},[69,11164,7452],{"class":75},[69,11166,11167],{"class":215},"단가",[69,11169,212],{"class":75},[69,11171,191],{"class":75},[69,11173,7452],{"class":75},[69,11175,11176],{"class":215},"금액",[69,11178,212],{"class":75},[69,11180,11181],{"class":75},"},\n",[69,11183,11184,11187,11189],{"class":71,"line":428},[69,11185,11186],{"class":75},"                [][]",[69,11188,11141],{"class":1620},[69,11190,11191],{"class":75},"{\n",[69,11193,11194,11197,11199,11202,11204,11206,11208,11211,11213,11215,11217,11220,11222,11224,11226,11229,11231],{"class":71,"line":462},[69,11195,11196],{"class":75},"                    {",[69,11198,212],{"class":75},[69,11200,11201],{"class":215},"프론트엔드 개발",[69,11203,212],{"class":75},[69,11205,191],{"class":75},[69,11207,7452],{"class":75},[69,11209,11210],{"class":215},"40 시간",[69,11212,212],{"class":75},[69,11214,191],{"class":75},[69,11216,7452],{"class":75},[69,11218,11219],{"class":215},"₩200,000",[69,11221,212],{"class":75},[69,11223,191],{"class":75},[69,11225,7452],{"class":75},[69,11227,11228],{"class":215},"₩8,000,000",[69,11230,212],{"class":75},[69,11232,11181],{"class":75},[69,11234,11235,11237,11239,11242,11244,11246,11249,11252,11254,11256,11258,11260,11262,11264,11266,11269,11271],{"class":71,"line":499},[69,11236,11196],{"class":75},[69,11238,212],{"class":75},[69,11240,11241],{"class":215},"백엔드 개발",[69,11243,212],{"class":75},[69,11245,191],{"class":75},[69,11247,11248],{"class":75},"     \"",[69,11250,11251],{"class":215},"60 시간",[69,11253,212],{"class":75},[69,11255,191],{"class":75},[69,11257,7452],{"class":75},[69,11259,11219],{"class":215},[69,11261,212],{"class":75},[69,11263,191],{"class":75},[69,11265,7452],{"class":75},[69,11267,11268],{"class":215},"₩12,000,000",[69,11270,212],{"class":75},[69,11272,11181],{"class":75},[69,11274,11275,11277,11279,11282,11284,11286,11289,11292,11294,11296,11298,11301,11303,11305,11307,11310,11312],{"class":71,"line":547},[69,11276,11196],{"class":75},[69,11278,212],{"class":75},[69,11280,11281],{"class":215},"UI 디자인",[69,11283,212],{"class":75},[69,11285,191],{"class":75},[69,11287,11288],{"class":75},"       \"",[69,11290,11291],{"class":215},"20 시간",[69,11293,212],{"class":75},[69,11295,191],{"class":75},[69,11297,7452],{"class":75},[69,11299,11300],{"class":215},"₩160,000",[69,11302,212],{"class":75},[69,11304,191],{"class":75},[69,11306,7452],{"class":75},[69,11308,11309],{"class":215},"₩3,200,000",[69,11311,212],{"class":75},[69,11313,11181],{"class":75},[69,11315,11316],{"class":71,"line":567},[69,11317,11318],{"class":75},"                },\n",[69,11320,11321,11324,11326,11329,11331,11334,11336,11339,11341,11343,11345,11348],{"class":71,"line":573},[69,11322,11323],{"class":187},"                template",[69,11325,203],{"class":75},[69,11327,11328],{"class":174},"ColumnWidths",[69,11330,209],{"class":75},[69,11332,11333],{"class":340},"50",[69,11335,191],{"class":75},[69,11337,11338],{"class":340}," 15",[69,11340,191],{"class":75},[69,11342,11338],{"class":340},[69,11344,191],{"class":75},[69,11346,11347],{"class":340}," 20",[69,11349,306],{"class":75},[69,11351,11352,11354,11356,11359],{"class":71,"line":579},[69,11353,11323],{"class":187},[69,11355,203],{"class":75},[69,11357,11358],{"class":174},"TableHeaderStyle",[69,11360,283],{"class":75},[69,11362,11363,11366,11368,11370],{"class":71,"line":584},[69,11364,11365],{"class":187},"                    template",[69,11367,203],{"class":75},[69,11369,541],{"class":174},[69,11371,11372],{"class":75},"(),\n",[69,11374,11375,11377,11379,11382,11384,11386,11388,11391],{"class":71,"line":605},[69,11376,11365],{"class":187},[69,11378,203],{"class":75},[69,11380,11381],{"class":174},"TextColor",[69,11383,209],{"class":75},[69,11385,2278],{"class":187},[69,11387,203],{"class":75},[69,11389,11390],{"class":187},"White",[69,11392,306],{"class":75},[69,11394,11395,11397,11399,11402,11404,11406,11408,11411,11413,11416],{"class":71,"line":618},[69,11396,11365],{"class":187},[69,11398,203],{"class":75},[69,11400,11401],{"class":174},"BgColor",[69,11403,209],{"class":75},[69,11405,2278],{"class":187},[69,11407,203],{"class":75},[69,11409,11410],{"class":174},"RGBHex",[69,11412,209],{"class":75},[69,11414,11415],{"class":340},"0x1A237E",[69,11417,11418],{"class":75},")),\n",[69,11420,11421],{"class":71,"line":633},[69,11422,11423],{"class":75},"                ),\n",[69,11425,11426],{"class":71,"line":638},[69,11427,11428],{"class":75},"            )\n",[69,11430,11431],{"class":71,"line":684},[69,11432,570],{"class":75},[69,11434,11435],{"class":71,"line":699},[69,11436,576],{"class":75},[69,11438,11439],{"class":71,"line":704},[69,11440,87],{"emptyLinePlaceholder":86},[69,11442,11443,11445,11447,11449,11451,11453,11455,11457],{"class":71,"line":4616},[69,11444,587],{"class":187},[69,11446,191],{"class":75},[69,11448,194],{"class":187},[69,11450,197],{"class":75},[69,11452,417],{"class":187},[69,11454,203],{"class":75},[69,11456,600],{"class":174},[69,11458,425],{"class":75},[69,11460,11461,11463,11465,11467,11469],{"class":71,"line":5859},[69,11462,225],{"class":93},[69,11464,194],{"class":187},[69,11466,230],{"class":75},[69,11468,233],{"class":75},[69,11470,181],{"class":75},[69,11472,11473,11475,11477,11479,11481,11483],{"class":71,"line":5879},[69,11474,241],{"class":187},[69,11476,203],{"class":75},[69,11478,246],{"class":174},[69,11480,209],{"class":75},[69,11482,251],{"class":187},[69,11484,160],{"class":75},[69,11486,11487],{"class":71,"line":5884},[69,11488,259],{"class":75},[69,11490,11491,11493,11495,11497,11499,11501,11503,11505,11507,11509,11511,11513,11515,11517,11519,11521,11523,11525,11527],{"class":71,"line":5915},[69,11492,225],{"class":93},[69,11494,194],{"class":187},[69,11496,197],{"class":75},[69,11498,200],{"class":187},[69,11500,203],{"class":75},[69,11502,651],{"class":174},[69,11504,209],{"class":75},[69,11506,212],{"class":75},[69,11508,4575],{"class":215},[69,11510,212],{"class":75},[69,11512,191],{"class":75},[69,11514,665],{"class":187},[69,11516,191],{"class":75},[69,11518,670],{"class":340},[69,11520,673],{"class":75},[69,11522,194],{"class":187},[69,11524,230],{"class":75},[69,11526,233],{"class":75},[69,11528,181],{"class":75},[69,11530,11531,11533,11535,11537,11539,11541],{"class":71,"line":5935},[69,11532,241],{"class":187},[69,11534,203],{"class":75},[69,11536,246],{"class":174},[69,11538,209],{"class":75},[69,11540,251],{"class":187},[69,11542,160],{"class":75},[69,11544,11545],{"class":71,"line":5940},[69,11546,259],{"class":75},[69,11548,11549],{"class":71,"line":5971},[69,11550,707],{"class":75},[19,11552,11553,11555,11556,11559],{},[47,11554,1930],{}," 0 개. 수동 컬럼 폭 계산 0 개. 문서 옵션에 ",[47,11557,11558],{},"gpdf.WithFont(\"NotoSansKR\", ttfBytes)"," 를 더하면 위 한국어가 그대로 렌더링된다. 두부 현상 없음.",[14,11561,11563],{"id":11562},"싣지-않은-것","싣지 않은 것",[19,11565,11566],{},"모든 비교 글에는 \"X 때문에 제외\" 섹션이 있다. 우리 쪽:",[1113,11568,11569,11575,11587,11596],{},[886,11570,11571,11574],{},[30,11572,11573],{},"사내 gofpdf 포크",". 프로덕션에서 돌고 있는 비공개 포크가 있다. 볼 수 없는 코드는 벤치할 수 없다.",[886,11576,11577,11582,11583,11586],{},[30,11578,11579],{},[47,11580,11581],{},"pdfcpu",". \"Go PDF 라이브러리\" 목록에 늘 보이지만 주 용도는 ",[30,11584,11585],{},"PDF 프로세서"," (병합·분할·암호화·스탬프) 로 생성이 아니다. 이 글 범위 밖; 프로세싱 지향 별도 글을 계획.",[886,11588,11589,11595],{},[30,11590,11591,11594],{},[47,11592,11593],{},"gotenberg"," 이나 headless 브라우저 서비스 래퍼",". 라이브러리가 아니고 공평한 비교도 아니다.",[886,11597,11598,11604],{},[30,11599,11600,11601,11603],{},"자체 ",[47,11602,27],{}," 벤치",". 비교의 초점은 코어 수치.",[14,11606,2827],{"id":2826},[19,11608,11609,11612],{},[30,11610,11611],{},"gpdf 는 왜 gofpdf 보다 10× 빠른가? 어떤 트릭?","\n단일 트릭은 없다. 3 가지 설계가 누적: 단일 패스 레이아웃 (빌더-라이터 간 AST 없음), 핫패스의 구체 타입, cmap 을 캐시하는 TrueType 서브세터. 하나당 2× 가량 기여. 누적해서 자릿수가 바뀐다.",[19,11614,11615,11618,11619,11622],{},[30,11616,11617],{},"이 벤치 직접 재현 가능한가?","\n가능. ",[47,11620,11621],{},"git clone https://github.com/gpdf-dev/gpdf && cd gpdf/_benchmark && go test -bench=. -benchmem",". 같은 CPU 아키, 같은 Go 버전에서 숫자가 안 맞으면 이슈를 열어 달라. 벤치 드리프트는 발생하고 알고 싶다.",[19,11624,11625,11628],{},[30,11626,11627],{},"gofpdf 는 돌아오나?","\n현실적으로는 아니다. 마지막 커밋은 2021 년. 이슈 트래커는 닫혔다. 누가 다시 열어도 커서 + 싱글바이트 폰트 + 그리드 없음 의 아키텍처는 2026 년 출발점으로 맞지 않는다. 유물 취급 후 마이그레이션이 실용적.",[19,11630,11631,11634],{},[30,11632,11633],{},"Java iText / Python ReportLab / Node pdfkit 는?","\n언어 교차 벤치는 별도 글. 짧게: Go 는 처리량과 콜드스타트에서 대체로 이기고, 기능 폭 (특히 HTML→PDF 충실도) 에서 진다. 이미 Go 팀이면 gpdf 가 더 빠르고 작음. Python / Node 팀이 Go 로 옮길 땐 마이그레이션 비용이 커서 대용량일 때만 투자 회수.",[19,11636,11637,11640],{},[30,11638,11639],{},"경쟁사가 개선되면 이 비교는 공평성을 유지하나?","\n유지한다. 매년 돌린다. signintech/gopdf 가 테이블 API 를 내서 시간을 절반으로 줄이면 2027 판에 반영한다. Maroto v2 가 gofpdf 제거를 끝내면 그 행은 바뀐다. 벤치 코드를 공개한 건 어떤 누구도 우리 말을 믿을 필요 없게 하기 위해서다.",[14,11642,11644],{"id":11643},"gpdf-써-보기","gpdf 써 보기",[19,11646,11647],{},"gpdf 는 Go 의 PDF 생성 라이브러리. MIT, 의존 0, 네이티브 CJK.",[60,11649,11650],{"className":1250,"code":1251,"language":1252,"meta":65,"style":65},[47,11651,11652],{"__ignoreMap":65},[69,11653,11654,11656,11658],{"class":71,"line":72},[69,11655,64],{"class":79},[69,11657,1261],{"class":215},[69,11659,1264],{"class":215},[19,11661,11662,1271,11666],{},[22,11663,11665],{"href":24,"rel":11664},[26],"⭐ GitHub 에서 스타",[22,11667,1276],{"href":1274,"rel":11668},[26],[14,11670,11672],{"id":11671},"이어서-읽기","이어서 읽기",[1113,11674,11675,11681,11690],{},[886,11676,11677,11680],{},[22,11678,11679],{"href":2836},"gofpdf 가 아카이브됐다. gpdf 로의 마이그레이션 가이드"," — 5 쌍의 Before/After 로 전체 API 매핑.",[886,11682,11683,5225,11687,11689],{},[22,11684,11686],{"href":1274,"rel":11685},[26],"Quickstart",[47,11688,6718],{}," 포함 5 분 세팅.",[886,11691,11692,11693,203],{},"벤치마크 코드 그 자체: ",[22,11694,11696],{"href":1394,"rel":11695},[26],[47,11697,1398],{},[1278,11699,11700],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}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);}",{"title":65,"searchDepth":83,"depth":83,"links":11702},[11703,11704,11705,11706,11710,11711,11712,11713,11714,11715,11716,11717,11718],{"id":1337,"depth":83,"text":1338},{"id":10164,"depth":83,"text":10165},{"id":10208,"depth":83,"text":10208},{"id":8081,"depth":83,"text":8081,"children":11707},[11708,11709],{"id":10442,"depth":90,"text":10442},{"id":10539,"depth":90,"text":10540},{"id":10577,"depth":83,"text":10577},{"id":5142,"depth":83,"text":5142},{"id":10735,"depth":83,"text":10736},{"id":10777,"depth":83,"text":10778},{"id":10827,"depth":83,"text":10828},{"id":11562,"depth":83,"text":11563},{"id":2826,"depth":83,"text":2827},{"id":11643,"depth":83,"text":11644},{"id":11671,"depth":83,"text":11672},"2026-04-15","2026 년에도 살아있는 Go PDF 라이브러리를 4 가지 워크로드에서 벤치마크. 라이선스·의존성·유지보수 상태를 정리.",{},{"title":10121,"description":11720},"ko/blog/002.go-pdf-library-showdown-2026",[2958,2956],"teHAfEI1uOobKueYaAVVayAsI2HPDNPhHDNAT5xQYo4",{"id":11727,"title":1225,"author":11728,"body":11729,"date":11719,"description":13029,"draft":1295,"extension":1296,"howTo":13030,"image":1319,"meta":13046,"navigation":86,"path":1224,"seo":13047,"stem":13048,"tags":13049,"updated":1319,"__hash__":13050},"blogKo/ko/blog/003.embed-japanese-font.md",{"name":8,"url":9},{"type":11,"value":11730,"toc":13018},[11731,11733,11742,11745,11758,11762,12266,12279,12283,12286,12292,12301,12314,12318,12328,12473,12490,12494,12501,12927,12934,12938,12944,12950,12964,12966,12991,12993,12996,13008,13016],[14,11732,17],{"id":16},[19,11734,11735,11737,11738,11741],{},[47,11736,2574],{}," 절차, CGO 의존, 문서마다 5 MB짜리 폰트 전부 임베딩 — 이런 것들 없이 ",[22,11739,27],{"href":24,"rel":11740},[26],"로 일본어(또는 CJK) PDF를 만드는 가장 짧은 방법은 무엇인가.",[14,11743,11744],{"id":11744},"요점",[19,11746,11747,11748,4080,11751,11753,11754,11757],{},"TTF 바이트를 읽고, ",[47,11749,11750],{},"gpdf.WithFont(\"NotoSansJP\", fontBytes)",[47,11752,280],{},"에 전달하고, 필요하면 기본 폰트로 지정한다. ",[30,11755,11756],{},"설정 3줄이면 gpdf가 실제로 사용된 글리프만 자동 서브셋 임베딩","한다 — 5 MB 통째로 끌어안지 않는다.",[14,11759,11761],{"id":11760},"완전한-예제","완전한 예제",[60,11763,11765],{"className":62,"code":11764,"language":64,"meta":65,"style":65},"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/template\"\n)\n\nfunc main() {\n    font, err := os.ReadFile(\"NotoSansJP-Regular.ttf\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(gpdf.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n        gpdf.WithFont(\"NotoSansJP\", font),\n        gpdf.WithDefaultFont(\"NotoSansJP\", 12),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"こんにちは、世界。\", template.FontSize(24), template.Bold())\n            c.Text(\"日本語 PDF、これだけ。\")\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"hello.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[47,11766,11767,11773,11777,11783,11791,11799,11803,11811,11819,11827,11831,11835,11845,11871,11883,11897,11901,11905,11919,11937,11967,11989,12011,12015,12019,12033,12057,12087,12125,12144,12148,12152,12156,12174,12186,12200,12204,12244,12258,12262],{"__ignoreMap":65},[69,11768,11769,11771],{"class":71,"line":72},[69,11770,76],{"class":75},[69,11772,80],{"class":79},[69,11774,11775],{"class":71,"line":83},[69,11776,87],{"emptyLinePlaceholder":86},[69,11778,11779,11781],{"class":71,"line":90},[69,11780,94],{"class":93},[69,11782,97],{"class":75},[69,11784,11785,11787,11789],{"class":71,"line":100},[69,11786,103],{"class":75},[69,11788,106],{"class":79},[69,11790,109],{"class":75},[69,11792,11793,11795,11797],{"class":71,"line":112},[69,11794,103],{"class":75},[69,11796,117],{"class":79},[69,11798,109],{"class":75},[69,11800,11801],{"class":71,"line":122},[69,11802,87],{"emptyLinePlaceholder":86},[69,11804,11805,11807,11809],{"class":71,"line":127},[69,11806,103],{"class":75},[69,11808,132],{"class":79},[69,11810,109],{"class":75},[69,11812,11813,11815,11817],{"class":71,"line":137},[69,11814,103],{"class":75},[69,11816,142],{"class":79},[69,11818,109],{"class":75},[69,11820,11821,11823,11825],{"class":71,"line":147},[69,11822,103],{"class":75},[69,11824,152],{"class":79},[69,11826,109],{"class":75},[69,11828,11829],{"class":71,"line":157},[69,11830,160],{"class":75},[69,11832,11833],{"class":71,"line":163},[69,11834,87],{"emptyLinePlaceholder":86},[69,11836,11837,11839,11841,11843],{"class":71,"line":168},[69,11838,171],{"class":75},[69,11840,175],{"class":174},[69,11842,178],{"class":75},[69,11844,181],{"class":75},[69,11846,11847,11849,11851,11853,11855,11857,11859,11861,11863,11865,11867,11869],{"class":71,"line":184},[69,11848,188],{"class":187},[69,11850,191],{"class":75},[69,11852,194],{"class":187},[69,11854,197],{"class":75},[69,11856,200],{"class":187},[69,11858,203],{"class":75},[69,11860,206],{"class":174},[69,11862,209],{"class":75},[69,11864,212],{"class":75},[69,11866,3142],{"class":215},[69,11868,212],{"class":75},[69,11870,160],{"class":75},[69,11872,11873,11875,11877,11879,11881],{"class":71,"line":222},[69,11874,225],{"class":93},[69,11876,194],{"class":187},[69,11878,230],{"class":75},[69,11880,233],{"class":75},[69,11882,181],{"class":75},[69,11884,11885,11887,11889,11891,11893,11895],{"class":71,"line":238},[69,11886,241],{"class":187},[69,11888,203],{"class":75},[69,11890,246],{"class":174},[69,11892,209],{"class":75},[69,11894,251],{"class":187},[69,11896,160],{"class":75},[69,11898,11899],{"class":71,"line":256},[69,11900,259],{"class":75},[69,11902,11903],{"class":71,"line":262},[69,11904,87],{"emptyLinePlaceholder":86},[69,11906,11907,11909,11911,11913,11915,11917],{"class":71,"line":267},[69,11908,270],{"class":187},[69,11910,197],{"class":75},[69,11912,275],{"class":187},[69,11914,203],{"class":75},[69,11916,280],{"class":174},[69,11918,283],{"class":75},[69,11920,11921,11923,11925,11927,11929,11931,11933,11935],{"class":71,"line":286},[69,11922,289],{"class":187},[69,11924,203],{"class":75},[69,11926,294],{"class":174},[69,11928,209],{"class":75},[69,11930,27],{"class":187},[69,11932,203],{"class":75},[69,11934,303],{"class":187},[69,11936,306],{"class":75},[69,11938,11939,11941,11943,11945,11947,11949,11951,11953,11955,11957,11959,11961,11963,11965],{"class":71,"line":309},[69,11940,289],{"class":187},[69,11942,203],{"class":75},[69,11944,316],{"class":174},[69,11946,209],{"class":75},[69,11948,321],{"class":187},[69,11950,203],{"class":75},[69,11952,326],{"class":174},[69,11954,209],{"class":75},[69,11956,321],{"class":187},[69,11958,203],{"class":75},[69,11960,335],{"class":174},[69,11962,209],{"class":75},[69,11964,341],{"class":340},[69,11966,344],{"class":75},[69,11968,11969,11971,11973,11975,11977,11979,11981,11983,11985,11987],{"class":71,"line":347},[69,11970,289],{"class":187},[69,11972,203],{"class":75},[69,11974,354],{"class":174},[69,11976,209],{"class":75},[69,11978,212],{"class":75},[69,11980,3257],{"class":215},[69,11982,212],{"class":75},[69,11984,191],{"class":75},[69,11986,368],{"class":187},[69,11988,306],{"class":75},[69,11990,11991,11993,11995,11997,11999,12001,12003,12005,12007,12009],{"class":71,"line":373},[69,11992,289],{"class":187},[69,11994,203],{"class":75},[69,11996,380],{"class":174},[69,11998,209],{"class":75},[69,12000,212],{"class":75},[69,12002,3257],{"class":215},[69,12004,212],{"class":75},[69,12006,191],{"class":75},[69,12008,3286],{"class":340},[69,12010,306],{"class":75},[69,12012,12013],{"class":71,"line":398},[69,12014,401],{"class":75},[69,12016,12017],{"class":71,"line":404},[69,12018,87],{"emptyLinePlaceholder":86},[69,12020,12021,12023,12025,12027,12029,12031],{"class":71,"line":409},[69,12022,412],{"class":187},[69,12024,197],{"class":75},[69,12026,417],{"class":187},[69,12028,203],{"class":75},[69,12030,422],{"class":174},[69,12032,425],{"class":75},[69,12034,12035,12037,12039,12041,12043,12045,12047,12049,12051,12053,12055],{"class":71,"line":428},[69,12036,431],{"class":187},[69,12038,203],{"class":75},[69,12040,436],{"class":174},[69,12042,439],{"class":75},[69,12044,443],{"class":442},[69,12046,446],{"class":75},[69,12048,449],{"class":79},[69,12050,203],{"class":75},[69,12052,454],{"class":79},[69,12054,457],{"class":75},[69,12056,181],{"class":75},[69,12058,12059,12061,12063,12065,12067,12069,12071,12073,12075,12077,12079,12081,12083,12085],{"class":71,"line":462},[69,12060,465],{"class":187},[69,12062,203],{"class":75},[69,12064,470],{"class":174},[69,12066,209],{"class":75},[69,12068,475],{"class":340},[69,12070,191],{"class":75},[69,12072,480],{"class":75},[69,12074,483],{"class":442},[69,12076,446],{"class":75},[69,12078,449],{"class":79},[69,12080,203],{"class":75},[69,12082,492],{"class":79},[69,12084,457],{"class":75},[69,12086,181],{"class":75},[69,12088,12089,12091,12093,12095,12097,12099,12101,12103,12105,12107,12109,12111,12113,12115,12117,12119,12121,12123],{"class":71,"line":499},[69,12090,502],{"class":187},[69,12092,203],{"class":75},[69,12094,507],{"class":174},[69,12096,209],{"class":75},[69,12098,212],{"class":75},[69,12100,3379],{"class":215},[69,12102,212],{"class":75},[69,12104,191],{"class":75},[69,12106,521],{"class":187},[69,12108,203],{"class":75},[69,12110,526],{"class":174},[69,12112,209],{"class":75},[69,12114,531],{"class":340},[69,12116,534],{"class":75},[69,12118,521],{"class":187},[69,12120,203],{"class":75},[69,12122,541],{"class":174},[69,12124,544],{"class":75},[69,12126,12127,12129,12131,12133,12135,12137,12140,12142],{"class":71,"line":547},[69,12128,502],{"class":187},[69,12130,203],{"class":75},[69,12132,507],{"class":174},[69,12134,209],{"class":75},[69,12136,212],{"class":75},[69,12138,12139],{"class":215},"日本語 PDF、これだけ。",[69,12141,212],{"class":75},[69,12143,160],{"class":75},[69,12145,12146],{"class":71,"line":567},[69,12147,570],{"class":75},[69,12149,12150],{"class":71,"line":573},[69,12151,576],{"class":75},[69,12153,12154],{"class":71,"line":579},[69,12155,87],{"emptyLinePlaceholder":86},[69,12157,12158,12160,12162,12164,12166,12168,12170,12172],{"class":71,"line":584},[69,12159,587],{"class":187},[69,12161,191],{"class":75},[69,12163,194],{"class":187},[69,12165,197],{"class":75},[69,12167,417],{"class":187},[69,12169,203],{"class":75},[69,12171,600],{"class":174},[69,12173,425],{"class":75},[69,12175,12176,12178,12180,12182,12184],{"class":71,"line":605},[69,12177,225],{"class":93},[69,12179,194],{"class":187},[69,12181,230],{"class":75},[69,12183,233],{"class":75},[69,12185,181],{"class":75},[69,12187,12188,12190,12192,12194,12196,12198],{"class":71,"line":618},[69,12189,241],{"class":187},[69,12191,203],{"class":75},[69,12193,246],{"class":174},[69,12195,209],{"class":75},[69,12197,251],{"class":187},[69,12199,160],{"class":75},[69,12201,12202],{"class":71,"line":633},[69,12203,259],{"class":75},[69,12205,12206,12208,12210,12212,12214,12216,12218,12220,12222,12224,12226,12228,12230,12232,12234,12236,12238,12240,12242],{"class":71,"line":638},[69,12207,225],{"class":93},[69,12209,194],{"class":187},[69,12211,197],{"class":75},[69,12213,200],{"class":187},[69,12215,203],{"class":75},[69,12217,651],{"class":174},[69,12219,209],{"class":75},[69,12221,212],{"class":75},[69,12223,3464],{"class":215},[69,12225,212],{"class":75},[69,12227,191],{"class":75},[69,12229,665],{"class":187},[69,12231,191],{"class":75},[69,12233,670],{"class":340},[69,12235,673],{"class":75},[69,12237,194],{"class":187},[69,12239,230],{"class":75},[69,12241,233],{"class":75},[69,12243,181],{"class":75},[69,12245,12246,12248,12250,12252,12254,12256],{"class":71,"line":684},[69,12247,241],{"class":187},[69,12249,203],{"class":75},[69,12251,246],{"class":174},[69,12253,209],{"class":75},[69,12255,251],{"class":187},[69,12257,160],{"class":75},[69,12259,12260],{"class":71,"line":699},[69,12261,259],{"class":75},[69,12263,12264],{"class":71,"line":704},[69,12265,707],{"class":75},[19,12267,12268,4625,12271,12273,12274,714,12276,12278],{},[22,12269,3528],{"href":3526,"rel":12270},[26],[47,12272,3142],{},"를 다운로드해 ",[47,12275,713],{},[47,12277,717],{},"를 실행하면 일본어가 찍힌 한 장짜리 PDF가 나온다.",[14,12280,12282],{"id":12281},"세-줄-뒤에서-벌어지는-일","세 줄 뒤에서 벌어지는 일",[19,12284,12285],{},"내부에서는 두 가지 일이 일어나며, 둘 다 사용자가 신경 쓸 필요가 없다.",[19,12287,12288,12291],{},[30,12289,12290],{},"서브셋 임베딩."," Noto Sans JP은 약 17,000개의 글리프를 담고 있고 Regular 단독으로 약 5 MB다. 폰트 전체를 그대로 임베드한다면 네 줄짜리 영수증 PDF도 5 MB를 넘어버린다. gpdf는 렌더링한 텍스트를 훑어 실제 사용된 글리프 ID만 뽑아 PDF에 기록한다. 짧은 청구서 한 장이라면 폰트 데이터는 보통 20–40 KB 수준이다.",[19,12293,12294,12295,12297,12298,12300],{},"gofpdf도 서브셋화는 가능했지만, ",[47,12296,2574],{},"가 파일 경로와 UTF-8 플래그를 받아 커서 이동 중에 로드하는 구조라 문서 중간에 폰트를 바꾸는 것이 번거로웠다. gpdf는 문서 생성 시 한 번만 등록하고, 이후 모든 ",[47,12299,3000],{},"는 패밀리 이름만 참조한다. 호출마다 준비 작업은 없다.",[19,12302,12303,12306,12307,12310,12311,12313],{},[30,12304,12305],{},"CGO를 쓰지 않는다."," 이 점은 생각보다 크다. 다른 생태계에서는 폰트 처리가 FreeType이나 HarfBuzz를 거치는 일이 많은데, 그러면 C 의존이 생기고, 빌드 캐시 동작이 달라지며, Docker 이미지 레이어가 늘고, macOS에서 ",[47,12308,12309],{},"linux/arm64","로 크로스 컴파일할 때 추가 설정이 필요해진다. gpdf는 TrueType 테이블을 순수 Go로 파싱한다. ",[47,12312,7076],{},"는 여전히 정적 바이너리를 만들고, distroless 컨테이너에 Go 바이너리와 TTF만 넣어 배포할 수 있다.",[14,12315,12317],{"id":12316},"bold-italic-변형","Bold / Italic 변형",[19,12319,12320,12321,12324,12325,12327],{},"일본어 Noto 패밀리는 굵기별로 파일이 나뉘어 있다. ",[30,12322,12323],{},"굵은 글씨","를 쓰려면 Bold TTF를 ",[47,12326,1093],{}," 접미사로 따로 등록한다:",[60,12329,12331],{"className":62,"code":12330,"language":64,"meta":65,"style":65},"reg, _ := os.ReadFile(\"NotoSansJP-Regular.ttf\")\nbold, _ := os.ReadFile(\"NotoSansJP-Bold.ttf\")\n\ndoc := gpdf.NewDocument(\n    gpdf.WithFont(\"NotoSansJP\", reg),\n    gpdf.WithFont(\"NotoSansJP-Bold\", bold),\n    gpdf.WithDefaultFont(\"NotoSansJP\", 12),\n)\n",[47,12332,12333,12359,12385,12389,12403,12425,12447,12469],{"__ignoreMap":65},[69,12334,12335,12337,12339,12341,12343,12345,12347,12349,12351,12353,12355,12357],{"class":71,"line":72},[69,12336,943],{"class":187},[69,12338,191],{"class":75},[69,12340,976],{"class":187},[69,12342,197],{"class":75},[69,12344,200],{"class":187},[69,12346,203],{"class":75},[69,12348,206],{"class":174},[69,12350,209],{"class":75},[69,12352,212],{"class":75},[69,12354,3142],{"class":215},[69,12356,212],{"class":75},[69,12358,160],{"class":75},[69,12360,12361,12363,12365,12367,12369,12371,12373,12375,12377,12379,12381,12383],{"class":71,"line":83},[69,12362,971],{"class":187},[69,12364,191],{"class":75},[69,12366,976],{"class":187},[69,12368,197],{"class":75},[69,12370,200],{"class":187},[69,12372,203],{"class":75},[69,12374,206],{"class":174},[69,12376,209],{"class":75},[69,12378,212],{"class":75},[69,12380,8858],{"class":215},[69,12382,212],{"class":75},[69,12384,160],{"class":75},[69,12386,12387],{"class":71,"line":90},[69,12388,87],{"emptyLinePlaceholder":86},[69,12390,12391,12393,12395,12397,12399,12401],{"class":71,"line":100},[69,12392,1004],{"class":187},[69,12394,197],{"class":75},[69,12396,275],{"class":187},[69,12398,203],{"class":75},[69,12400,280],{"class":174},[69,12402,283],{"class":75},[69,12404,12405,12407,12409,12411,12413,12415,12417,12419,12421,12423],{"class":71,"line":112},[69,12406,1019],{"class":187},[69,12408,203],{"class":75},[69,12410,354],{"class":174},[69,12412,209],{"class":75},[69,12414,212],{"class":75},[69,12416,3257],{"class":215},[69,12418,212],{"class":75},[69,12420,191],{"class":75},[69,12422,1036],{"class":187},[69,12424,306],{"class":75},[69,12426,12427,12429,12431,12433,12435,12437,12439,12441,12443,12445],{"class":71,"line":122},[69,12428,1019],{"class":187},[69,12430,203],{"class":75},[69,12432,354],{"class":174},[69,12434,209],{"class":75},[69,12436,212],{"class":75},[69,12438,8996],{"class":215},[69,12440,212],{"class":75},[69,12442,191],{"class":75},[69,12444,1060],{"class":187},[69,12446,306],{"class":75},[69,12448,12449,12451,12453,12455,12457,12459,12461,12463,12465,12467],{"class":71,"line":127},[69,12450,1019],{"class":187},[69,12452,203],{"class":75},[69,12454,380],{"class":174},[69,12456,209],{"class":75},[69,12458,212],{"class":75},[69,12460,3257],{"class":215},[69,12462,212],{"class":75},[69,12464,191],{"class":75},[69,12466,3286],{"class":340},[69,12468,306],{"class":75},[69,12470,12471],{"class":71,"line":137},[69,12472,160],{"class":75},[19,12474,12475,12476,12478,12479,12481,12482,12485,12486,12489],{},"이제 ",[47,12477,1097],{},"가 ",[47,12480,1093],{}," 변형을 집어든다. ",[47,12483,12484],{},"-Italic","과 ",[47,12487,12488],{},"-BoldItalic","도 같은 규약이다. 변형을 등록하지 않으면 합성 굵기로 폴백된다 — 화면상 읽히지만 타이포그래피적으로는 정확하지 않다. 실전 청구서라면 실제 굵기를 등록하자.",[14,12491,12493],{"id":12492},"같은-문서에-한중일-섞기","같은 문서에 한·중·일 섞기",[19,12495,12496,12497,12500],{},"패밀리는 몇 개든 등록 가능하다. gpdf는 이들을 독립적으로 관리한다. 텍스트 단위로 ",[47,12498,12499],{},"template.FontFamily(...)","를 이용해 전환하면 된다:",[60,12502,12504],{"className":62,"code":12503,"language":64,"meta":65,"style":65},"jp, _ := os.ReadFile(\"NotoSansJP-Regular.ttf\")\nsc, _ := os.ReadFile(\"NotoSansSC-Regular.ttf\")\nkr, _ := os.ReadFile(\"NotoSansKR-Regular.ttf\")\n\ndoc := gpdf.NewDocument(\n    gpdf.WithFont(\"NotoSansJP\", jp),\n    gpdf.WithFont(\"NotoSansSC\", sc),\n    gpdf.WithFont(\"NotoSansKR\", kr),\n    gpdf.WithDefaultFont(\"NotoSansJP\", 12),\n)\n\npage.AutoRow(func(r *template.RowBuilder) {\n    r.Col(4, func(c *template.ColBuilder) {\n        c.Text(\"日本語\")\n    })\n    r.Col(4, func(c *template.ColBuilder) {\n        c.Text(\"中文\", template.FontFamily(\"NotoSansSC\"))\n    })\n    r.Col(4, func(c *template.ColBuilder) {\n        c.Text(\"한국어\", template.FontFamily(\"NotoSansKR\"))\n    })\n})\n",[47,12505,12506,12533,12561,12589,12593,12607,12630,12654,12678,12700,12704,12708,12732,12762,12781,12785,12815,12850,12854,12884,12919,12923],{"__ignoreMap":65},[69,12507,12508,12511,12513,12515,12517,12519,12521,12523,12525,12527,12529,12531],{"class":71,"line":72},[69,12509,12510],{"class":187},"jp",[69,12512,191],{"class":75},[69,12514,976],{"class":187},[69,12516,197],{"class":75},[69,12518,200],{"class":187},[69,12520,203],{"class":75},[69,12522,206],{"class":174},[69,12524,209],{"class":75},[69,12526,212],{"class":75},[69,12528,3142],{"class":215},[69,12530,212],{"class":75},[69,12532,160],{"class":75},[69,12534,12535,12538,12540,12542,12544,12546,12548,12550,12552,12554,12557,12559],{"class":71,"line":83},[69,12536,12537],{"class":187},"sc",[69,12539,191],{"class":75},[69,12541,976],{"class":187},[69,12543,197],{"class":75},[69,12545,200],{"class":187},[69,12547,203],{"class":75},[69,12549,206],{"class":174},[69,12551,209],{"class":75},[69,12553,212],{"class":75},[69,12555,12556],{"class":215},"NotoSansSC-Regular.ttf",[69,12558,212],{"class":75},[69,12560,160],{"class":75},[69,12562,12563,12566,12568,12570,12572,12574,12576,12578,12580,12582,12585,12587],{"class":71,"line":90},[69,12564,12565],{"class":187},"kr",[69,12567,191],{"class":75},[69,12569,976],{"class":187},[69,12571,197],{"class":75},[69,12573,200],{"class":187},[69,12575,203],{"class":75},[69,12577,206],{"class":174},[69,12579,209],{"class":75},[69,12581,212],{"class":75},[69,12583,12584],{"class":215},"NotoSansKR-Regular.ttf",[69,12586,212],{"class":75},[69,12588,160],{"class":75},[69,12590,12591],{"class":71,"line":100},[69,12592,87],{"emptyLinePlaceholder":86},[69,12594,12595,12597,12599,12601,12603,12605],{"class":71,"line":112},[69,12596,1004],{"class":187},[69,12598,197],{"class":75},[69,12600,275],{"class":187},[69,12602,203],{"class":75},[69,12604,280],{"class":174},[69,12606,283],{"class":75},[69,12608,12609,12611,12613,12615,12617,12619,12621,12623,12625,12628],{"class":71,"line":122},[69,12610,1019],{"class":187},[69,12612,203],{"class":75},[69,12614,354],{"class":174},[69,12616,209],{"class":75},[69,12618,212],{"class":75},[69,12620,3257],{"class":215},[69,12622,212],{"class":75},[69,12624,191],{"class":75},[69,12626,12627],{"class":187}," jp",[69,12629,306],{"class":75},[69,12631,12632,12634,12636,12638,12640,12642,12645,12647,12649,12652],{"class":71,"line":127},[69,12633,1019],{"class":187},[69,12635,203],{"class":75},[69,12637,354],{"class":174},[69,12639,209],{"class":75},[69,12641,212],{"class":75},[69,12643,12644],{"class":215},"NotoSansSC",[69,12646,212],{"class":75},[69,12648,191],{"class":75},[69,12650,12651],{"class":187}," sc",[69,12653,306],{"class":75},[69,12655,12656,12658,12660,12662,12664,12666,12669,12671,12673,12676],{"class":71,"line":137},[69,12657,1019],{"class":187},[69,12659,203],{"class":75},[69,12661,354],{"class":174},[69,12663,209],{"class":75},[69,12665,212],{"class":75},[69,12667,12668],{"class":215},"NotoSansKR",[69,12670,212],{"class":75},[69,12672,191],{"class":75},[69,12674,12675],{"class":187}," kr",[69,12677,306],{"class":75},[69,12679,12680,12682,12684,12686,12688,12690,12692,12694,12696,12698],{"class":71,"line":147},[69,12681,1019],{"class":187},[69,12683,203],{"class":75},[69,12685,380],{"class":174},[69,12687,209],{"class":75},[69,12689,212],{"class":75},[69,12691,3257],{"class":215},[69,12693,212],{"class":75},[69,12695,191],{"class":75},[69,12697,3286],{"class":340},[69,12699,306],{"class":75},[69,12701,12702],{"class":71,"line":157},[69,12703,160],{"class":75},[69,12705,12706],{"class":71,"line":163},[69,12707,87],{"emptyLinePlaceholder":86},[69,12709,12710,12712,12714,12716,12718,12720,12722,12724,12726,12728,12730],{"class":71,"line":168},[69,12711,1926],{"class":187},[69,12713,203],{"class":75},[69,12715,436],{"class":174},[69,12717,439],{"class":75},[69,12719,443],{"class":442},[69,12721,446],{"class":75},[69,12723,449],{"class":79},[69,12725,203],{"class":75},[69,12727,454],{"class":79},[69,12729,457],{"class":75},[69,12731,181],{"class":75},[69,12733,12734,12736,12738,12740,12742,12744,12746,12748,12750,12752,12754,12756,12758,12760],{"class":71,"line":184},[69,12735,3672],{"class":187},[69,12737,203],{"class":75},[69,12739,470],{"class":174},[69,12741,209],{"class":75},[69,12743,5838],{"class":340},[69,12745,191],{"class":75},[69,12747,480],{"class":75},[69,12749,483],{"class":442},[69,12751,446],{"class":75},[69,12753,449],{"class":79},[69,12755,203],{"class":75},[69,12757,492],{"class":79},[69,12759,457],{"class":75},[69,12761,181],{"class":75},[69,12763,12764,12766,12768,12770,12772,12774,12777,12779],{"class":71,"line":222},[69,12765,3703],{"class":187},[69,12767,203],{"class":75},[69,12769,507],{"class":174},[69,12771,209],{"class":75},[69,12773,212],{"class":75},[69,12775,12776],{"class":215},"日本語",[69,12778,212],{"class":75},[69,12780,160],{"class":75},[69,12782,12783],{"class":71,"line":238},[69,12784,576],{"class":75},[69,12786,12787,12789,12791,12793,12795,12797,12799,12801,12803,12805,12807,12809,12811,12813],{"class":71,"line":256},[69,12788,3672],{"class":187},[69,12790,203],{"class":75},[69,12792,470],{"class":174},[69,12794,209],{"class":75},[69,12796,5838],{"class":340},[69,12798,191],{"class":75},[69,12800,480],{"class":75},[69,12802,483],{"class":442},[69,12804,446],{"class":75},[69,12806,449],{"class":79},[69,12808,203],{"class":75},[69,12810,492],{"class":79},[69,12812,457],{"class":75},[69,12814,181],{"class":75},[69,12816,12817,12819,12821,12823,12825,12827,12830,12832,12834,12836,12838,12840,12842,12844,12846,12848],{"class":71,"line":262},[69,12818,3703],{"class":187},[69,12820,203],{"class":75},[69,12822,507],{"class":174},[69,12824,209],{"class":75},[69,12826,212],{"class":75},[69,12828,12829],{"class":215},"中文",[69,12831,212],{"class":75},[69,12833,191],{"class":75},[69,12835,521],{"class":187},[69,12837,203],{"class":75},[69,12839,5006],{"class":174},[69,12841,209],{"class":75},[69,12843,212],{"class":75},[69,12845,12644],{"class":215},[69,12847,212],{"class":75},[69,12849,5029],{"class":75},[69,12851,12852],{"class":71,"line":267},[69,12853,576],{"class":75},[69,12855,12856,12858,12860,12862,12864,12866,12868,12870,12872,12874,12876,12878,12880,12882],{"class":71,"line":286},[69,12857,3672],{"class":187},[69,12859,203],{"class":75},[69,12861,470],{"class":174},[69,12863,209],{"class":75},[69,12865,5838],{"class":340},[69,12867,191],{"class":75},[69,12869,480],{"class":75},[69,12871,483],{"class":442},[69,12873,446],{"class":75},[69,12875,449],{"class":79},[69,12877,203],{"class":75},[69,12879,492],{"class":79},[69,12881,457],{"class":75},[69,12883,181],{"class":75},[69,12885,12886,12888,12890,12892,12894,12896,12899,12901,12903,12905,12907,12909,12911,12913,12915,12917],{"class":71,"line":309},[69,12887,3703],{"class":187},[69,12889,203],{"class":75},[69,12891,507],{"class":174},[69,12893,209],{"class":75},[69,12895,212],{"class":75},[69,12897,12898],{"class":215},"한국어",[69,12900,212],{"class":75},[69,12902,191],{"class":75},[69,12904,521],{"class":187},[69,12906,203],{"class":75},[69,12908,5006],{"class":174},[69,12910,209],{"class":75},[69,12912,212],{"class":75},[69,12914,12668],{"class":215},[69,12916,212],{"class":75},[69,12918,5029],{"class":75},[69,12920,12921],{"class":71,"line":347},[69,12922,576],{"class":75},[69,12924,12925],{"class":71,"line":373},[69,12926,3730],{"class":75},[19,12928,12929,12930,12933],{},"한자 통합(Han unification) 때문에 일본어와 중국어 간체는 유니코드 코드포인트를 공유하지만 실제 자형은 다르다. ",[30,12931,12932],{},"같은 코드포인트라도 어떤 폰트를 쓰느냐에 따라 글자 모양이 달라진다"," — 폰트 선택은 미학 문제가 아니라 정확성 문제다. 국가별 세금계산서나 운송장을 생성한다면 두 폰트 모두 등록해야 한다.",[14,12935,12937],{"id":12936},"두부-문자-함정","두부 문자 함정",[19,12939,12940,12941,12943],{},"일본어를 썼는데 ",[47,12942,354],{},"를 빠뜨리면 gpdf는 Base-14 표준 PDF 폰트로 폴백한다. 거기엔 CJK 글리프가 없으므로 문자가 빈 사각형으로 렌더링된다. 유니코드 쪽에서 흔히 말하는 \"두부 문자\"다:",[60,12945,12948],{"className":12946,"code":12947,"language":928},[926],"□□□□□、□□。\n",[47,12949,12947],{"__ignoreMap":65},[19,12951,12952,12953,12955,12956,12958,12959,2989,12961,12963],{},"이 출력을 보면 원인은 하나다: CJK 폰트를 등록하지 않았거나, 해당 글리프가 없는 패밀리로 쓰고 있다. 해결책도 하나다: ",[47,12954,354],{},"를 추가하고 ",[47,12957,380],{},"로 기본값을 잡거나 ",[47,12960,3000],{},[47,12962,1164],{},"를 명시하자.",[14,12965,6566],{"id":6565},[1113,12967,12968,12977,12983],{},[886,12969,12970,5225,12973,12976],{},[22,12971,12972],{"href":2836},"gofpdf가 아카이브됐다. gpdf로 이관하기.",[47,12974,12975],{},"pdf.AddUTF8Font","에서 옮겨가는 경우의 전체 매핑",[886,12978,12979,12982],{},[22,12980,12981],{"href":2909},"Go PDF 라이브러리 쇼다운 2026"," — gofpdf / gopdf / Maroto / unipdf와 gpdf의 CJK 비교",[886,12984,12985,5225,12988,12990],{},[22,12986,5224],{"href":5222,"rel":12987},[26],[47,12989,354],{}," 전체 레퍼런스 및 변형 명명 규약",[14,12992,6592],{"id":6591},[19,12994,12995],{},"gpdf는 Go용 PDF 생성 라이브러리다. MIT, 외부 의존 없음, CJK 기본 지원.",[60,12997,12998],{"className":1250,"code":1251,"language":1252,"meta":65,"style":65},[47,12999,13000],{"__ignoreMap":65},[69,13001,13002,13004,13006],{"class":71,"line":72},[69,13003,64],{"class":79},[69,13005,1261],{"class":215},[69,13007,1264],{"class":215},[19,13009,13010,1271,13013],{},[22,13011,1270],{"href":24,"rel":13012},[26],[22,13014,1276],{"href":1274,"rel":13015},[26],[1278,13017,1280],{},{"title":65,"searchDepth":83,"depth":83,"links":13019},[13020,13021,13022,13023,13024,13025,13026,13027,13028],{"id":16,"depth":83,"text":17},{"id":11744,"depth":83,"text":11744},{"id":11760,"depth":83,"text":11761},{"id":12281,"depth":83,"text":12282},{"id":12316,"depth":83,"text":12317},{"id":12492,"depth":83,"text":12493},{"id":12936,"depth":83,"text":12937},{"id":6565,"depth":83,"text":6566},{"id":6591,"depth":83,"text":6592},"TTF 바이트를 gpdf.WithFont에 전달하면 끝. 서브셋 임베딩은 자동, CGO도 필요 없음. Go에서 일본어 PDF를 만드는 최단 경로.",{"name":13031,"totalTime":5269,"tools":13032,"steps":13034},"gpdf 문서에 일본어 TrueType 폰트를 임베드한다",[1301,13033],"NotoSansJP-Regular.ttf (또는 임의의 CJK 지원 TTF)",[13035,13037,13040,13043],{"name":5277,"text":13036},"프로그램 시작 시 os.ReadFile로 NotoSansJP-Regular.ttf를 []byte에 읽어들인다. 바이너리에 포함하고 싶다면 //go:embed를 사용해도 된다.",{"name":13038,"text":13039},"문서 생성 시 WithFont로 등록한다","gpdf.WithFont(\"NotoSansJP\", fontBytes)를 gpdf.NewDocument에 전달한다. 패밀리 이름은 자유롭게 지정 가능 — 이후 참조할 이름과 일치하기만 하면 된다. 서브셋화는 렌더링 시점에 자동으로 수행된다.",{"name":13041,"text":13042},"기본 폰트로 설정한다","gpdf.WithDefaultFont(\"NotoSansJP\", 12)를 추가하면 c.Text 호출 시마다 FontFamily를 넘기지 않아도 일본어 폰트가 적용된다.",{"name":13044,"text":13045},"일본어를 쓰고 PDF를 생성한다","컬럼 안에서 c.Text(\"こんにちは、世界。\")를 호출한다. doc.Generate()가 []byte를 돌려주므로 os.WriteFile로 디스크에 기록한다.",{},{"title":1225,"description":13029},"ko/blog/003.embed-japanese-font",[1325,1326,1327],"q6pAYkicKsHJcq0nPA-lF7_4cBBih3fQI3_pvoipxDs",{"id":13052,"title":1218,"author":13053,"body":13054,"date":11719,"description":14396,"draft":1295,"extension":1296,"howTo":14397,"image":1319,"meta":14414,"navigation":86,"path":834,"seo":14415,"stem":14416,"tags":14417,"updated":1319,"__hash__":14418},"blogKo/ko/blog/004.noto-sans-jp-with-gpdf.md",{"name":8,"url":9},{"type":11,"value":13055,"toc":14383},[13056,13060,13069,13073,13086,13088,13593,13606,13610,13617,13644,13651,13669,13672,13676,13679,13746,13766,13777,13781,13784,13929,13942,13945,14014,14024,14028,14031,14034,14094,14097,14100,14104,14107,14112,14125,14134,14147,14151,14154,14311,14318,14322,14351,14355,14358,14370,14380],[14,13057,13059],{"id":13058},"질문을-다시-정리하면","질문을 다시 정리하면",[19,13061,13062,13065,13066,203],{},[22,13063,27],{"href":24,"rel":13064},[26]," 문서에 일본어를 렌더링하려 하고, 폰트는 Noto Sans JP — Google이 배포하는 SIL OFL 라이선스의 고딕체, JIS 영역을 완전히 커버하는 그 폰트 — 를 쓰기로 정했다. Google Fonts의 zip은 이미 받았다. 여기서부터 알고 싶은 세 가지: ",[30,13067,13068],{},"어느 파일을 고를지, 어느 굵기를 등록할지, zip 안에 숨어 있는 한 가지 함정은 무엇인지",[14,13070,13072],{"id":13071},"결론-tldr","결론 (TL;DR)",[19,13074,13075,13076,13081,13082,13085],{},"zip을 풀면 ",[30,13077,13078],{},[47,13079,13080],{},"static/NotoSansJP-Regular.ttf"," 를 씁니다 — zip 루트의 variable 폰트가 아닙니다. 이 파일을 ",[47,13083,13084],{},"gpdf.WithFont(\"NotoSansJP\", bytes)","에 넘기고 기본 폰트로 지정하면 끝. gpdf는 약 17,000개 글리프 중 실제로 렌더링된 글리프만 서브셋해서 PDF에 포함합니다 — 일반적인 청구서 한 장은 최종 PDF에 20–40 KB 정도의 폰트 데이터를 담게 됩니다.",[14,13087,11761],{"id":11760},[60,13089,13091],{"className":62,"code":13090,"language":64,"meta":65,"style":65},"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/template\"\n)\n\nfunc main() {\n    font, err := os.ReadFile(\"NotoSansJP-Regular.ttf\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(gpdf.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n        gpdf.WithFont(\"NotoSansJP\", font),\n        gpdf.WithDefaultFont(\"NotoSansJP\", 11),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"請求書\", template.FontSize(28), template.Bold())\n            c.Text(\"Noto Sans JP、これで十分。\")\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"invoice.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[47,13092,13093,13099,13103,13109,13117,13125,13129,13137,13145,13153,13157,13161,13171,13197,13209,13223,13227,13231,13245,13263,13293,13315,13337,13341,13345,13359,13383,13413,13452,13471,13475,13479,13483,13501,13513,13527,13531,13571,13585,13589],{"__ignoreMap":65},[69,13094,13095,13097],{"class":71,"line":72},[69,13096,76],{"class":75},[69,13098,80],{"class":79},[69,13100,13101],{"class":71,"line":83},[69,13102,87],{"emptyLinePlaceholder":86},[69,13104,13105,13107],{"class":71,"line":90},[69,13106,94],{"class":93},[69,13108,97],{"class":75},[69,13110,13111,13113,13115],{"class":71,"line":100},[69,13112,103],{"class":75},[69,13114,106],{"class":79},[69,13116,109],{"class":75},[69,13118,13119,13121,13123],{"class":71,"line":112},[69,13120,103],{"class":75},[69,13122,117],{"class":79},[69,13124,109],{"class":75},[69,13126,13127],{"class":71,"line":122},[69,13128,87],{"emptyLinePlaceholder":86},[69,13130,13131,13133,13135],{"class":71,"line":127},[69,13132,103],{"class":75},[69,13134,132],{"class":79},[69,13136,109],{"class":75},[69,13138,13139,13141,13143],{"class":71,"line":137},[69,13140,103],{"class":75},[69,13142,142],{"class":79},[69,13144,109],{"class":75},[69,13146,13147,13149,13151],{"class":71,"line":147},[69,13148,103],{"class":75},[69,13150,152],{"class":79},[69,13152,109],{"class":75},[69,13154,13155],{"class":71,"line":157},[69,13156,160],{"class":75},[69,13158,13159],{"class":71,"line":163},[69,13160,87],{"emptyLinePlaceholder":86},[69,13162,13163,13165,13167,13169],{"class":71,"line":168},[69,13164,171],{"class":75},[69,13166,175],{"class":174},[69,13168,178],{"class":75},[69,13170,181],{"class":75},[69,13172,13173,13175,13177,13179,13181,13183,13185,13187,13189,13191,13193,13195],{"class":71,"line":184},[69,13174,188],{"class":187},[69,13176,191],{"class":75},[69,13178,194],{"class":187},[69,13180,197],{"class":75},[69,13182,200],{"class":187},[69,13184,203],{"class":75},[69,13186,206],{"class":174},[69,13188,209],{"class":75},[69,13190,212],{"class":75},[69,13192,3142],{"class":215},[69,13194,212],{"class":75},[69,13196,160],{"class":75},[69,13198,13199,13201,13203,13205,13207],{"class":71,"line":222},[69,13200,225],{"class":93},[69,13202,194],{"class":187},[69,13204,230],{"class":75},[69,13206,233],{"class":75},[69,13208,181],{"class":75},[69,13210,13211,13213,13215,13217,13219,13221],{"class":71,"line":238},[69,13212,241],{"class":187},[69,13214,203],{"class":75},[69,13216,246],{"class":174},[69,13218,209],{"class":75},[69,13220,251],{"class":187},[69,13222,160],{"class":75},[69,13224,13225],{"class":71,"line":256},[69,13226,259],{"class":75},[69,13228,13229],{"class":71,"line":262},[69,13230,87],{"emptyLinePlaceholder":86},[69,13232,13233,13235,13237,13239,13241,13243],{"class":71,"line":267},[69,13234,270],{"class":187},[69,13236,197],{"class":75},[69,13238,275],{"class":187},[69,13240,203],{"class":75},[69,13242,280],{"class":174},[69,13244,283],{"class":75},[69,13246,13247,13249,13251,13253,13255,13257,13259,13261],{"class":71,"line":286},[69,13248,289],{"class":187},[69,13250,203],{"class":75},[69,13252,294],{"class":174},[69,13254,209],{"class":75},[69,13256,27],{"class":187},[69,13258,203],{"class":75},[69,13260,303],{"class":187},[69,13262,306],{"class":75},[69,13264,13265,13267,13269,13271,13273,13275,13277,13279,13281,13283,13285,13287,13289,13291],{"class":71,"line":309},[69,13266,289],{"class":187},[69,13268,203],{"class":75},[69,13270,316],{"class":174},[69,13272,209],{"class":75},[69,13274,321],{"class":187},[69,13276,203],{"class":75},[69,13278,326],{"class":174},[69,13280,209],{"class":75},[69,13282,321],{"class":187},[69,13284,203],{"class":75},[69,13286,335],{"class":174},[69,13288,209],{"class":75},[69,13290,341],{"class":340},[69,13292,344],{"class":75},[69,13294,13295,13297,13299,13301,13303,13305,13307,13309,13311,13313],{"class":71,"line":347},[69,13296,289],{"class":187},[69,13298,203],{"class":75},[69,13300,354],{"class":174},[69,13302,209],{"class":75},[69,13304,212],{"class":75},[69,13306,3257],{"class":215},[69,13308,212],{"class":75},[69,13310,191],{"class":75},[69,13312,368],{"class":187},[69,13314,306],{"class":75},[69,13316,13317,13319,13321,13323,13325,13327,13329,13331,13333,13335],{"class":71,"line":373},[69,13318,289],{"class":187},[69,13320,203],{"class":75},[69,13322,380],{"class":174},[69,13324,209],{"class":75},[69,13326,212],{"class":75},[69,13328,3257],{"class":215},[69,13330,212],{"class":75},[69,13332,191],{"class":75},[69,13334,393],{"class":340},[69,13336,306],{"class":75},[69,13338,13339],{"class":71,"line":398},[69,13340,401],{"class":75},[69,13342,13343],{"class":71,"line":404},[69,13344,87],{"emptyLinePlaceholder":86},[69,13346,13347,13349,13351,13353,13355,13357],{"class":71,"line":409},[69,13348,412],{"class":187},[69,13350,197],{"class":75},[69,13352,417],{"class":187},[69,13354,203],{"class":75},[69,13356,422],{"class":174},[69,13358,425],{"class":75},[69,13360,13361,13363,13365,13367,13369,13371,13373,13375,13377,13379,13381],{"class":71,"line":428},[69,13362,431],{"class":187},[69,13364,203],{"class":75},[69,13366,436],{"class":174},[69,13368,439],{"class":75},[69,13370,443],{"class":442},[69,13372,446],{"class":75},[69,13374,449],{"class":79},[69,13376,203],{"class":75},[69,13378,454],{"class":79},[69,13380,457],{"class":75},[69,13382,181],{"class":75},[69,13384,13385,13387,13389,13391,13393,13395,13397,13399,13401,13403,13405,13407,13409,13411],{"class":71,"line":462},[69,13386,465],{"class":187},[69,13388,203],{"class":75},[69,13390,470],{"class":174},[69,13392,209],{"class":75},[69,13394,475],{"class":340},[69,13396,191],{"class":75},[69,13398,480],{"class":75},[69,13400,483],{"class":442},[69,13402,446],{"class":75},[69,13404,449],{"class":79},[69,13406,203],{"class":75},[69,13408,492],{"class":79},[69,13410,457],{"class":75},[69,13412,181],{"class":75},[69,13414,13415,13417,13419,13421,13423,13425,13427,13429,13431,13433,13435,13437,13439,13442,13444,13446,13448,13450],{"class":71,"line":499},[69,13416,502],{"class":187},[69,13418,203],{"class":75},[69,13420,507],{"class":174},[69,13422,209],{"class":75},[69,13424,212],{"class":75},[69,13426,4432],{"class":215},[69,13428,212],{"class":75},[69,13430,191],{"class":75},[69,13432,521],{"class":187},[69,13434,203],{"class":75},[69,13436,526],{"class":174},[69,13438,209],{"class":75},[69,13440,13441],{"class":340},"28",[69,13443,534],{"class":75},[69,13445,521],{"class":187},[69,13447,203],{"class":75},[69,13449,541],{"class":174},[69,13451,544],{"class":75},[69,13453,13454,13456,13458,13460,13462,13464,13467,13469],{"class":71,"line":547},[69,13455,502],{"class":187},[69,13457,203],{"class":75},[69,13459,507],{"class":174},[69,13461,209],{"class":75},[69,13463,212],{"class":75},[69,13465,13466],{"class":215},"Noto Sans JP、これで十分。",[69,13468,212],{"class":75},[69,13470,160],{"class":75},[69,13472,13473],{"class":71,"line":567},[69,13474,570],{"class":75},[69,13476,13477],{"class":71,"line":573},[69,13478,576],{"class":75},[69,13480,13481],{"class":71,"line":579},[69,13482,87],{"emptyLinePlaceholder":86},[69,13484,13485,13487,13489,13491,13493,13495,13497,13499],{"class":71,"line":584},[69,13486,587],{"class":187},[69,13488,191],{"class":75},[69,13490,194],{"class":187},[69,13492,197],{"class":75},[69,13494,417],{"class":187},[69,13496,203],{"class":75},[69,13498,600],{"class":174},[69,13500,425],{"class":75},[69,13502,13503,13505,13507,13509,13511],{"class":71,"line":605},[69,13504,225],{"class":93},[69,13506,194],{"class":187},[69,13508,230],{"class":75},[69,13510,233],{"class":75},[69,13512,181],{"class":75},[69,13514,13515,13517,13519,13521,13523,13525],{"class":71,"line":618},[69,13516,241],{"class":187},[69,13518,203],{"class":75},[69,13520,246],{"class":174},[69,13522,209],{"class":75},[69,13524,251],{"class":187},[69,13526,160],{"class":75},[69,13528,13529],{"class":71,"line":633},[69,13530,259],{"class":75},[69,13532,13533,13535,13537,13539,13541,13543,13545,13547,13549,13551,13553,13555,13557,13559,13561,13563,13565,13567,13569],{"class":71,"line":638},[69,13534,225],{"class":93},[69,13536,194],{"class":187},[69,13538,197],{"class":75},[69,13540,200],{"class":187},[69,13542,203],{"class":75},[69,13544,651],{"class":174},[69,13546,209],{"class":75},[69,13548,212],{"class":75},[69,13550,4575],{"class":215},[69,13552,212],{"class":75},[69,13554,191],{"class":75},[69,13556,665],{"class":187},[69,13558,191],{"class":75},[69,13560,670],{"class":340},[69,13562,673],{"class":75},[69,13564,194],{"class":187},[69,13566,230],{"class":75},[69,13568,233],{"class":75},[69,13570,181],{"class":75},[69,13572,13573,13575,13577,13579,13581,13583],{"class":71,"line":684},[69,13574,241],{"class":187},[69,13576,203],{"class":75},[69,13578,246],{"class":174},[69,13580,209],{"class":75},[69,13582,251],{"class":187},[69,13584,160],{"class":75},[69,13586,13587],{"class":71,"line":699},[69,13588,259],{"class":75},[69,13590,13591],{"class":71,"line":704},[69,13592,707],{"class":75},[19,13594,13595,13598,13599,4080,13601,714,13603,13605],{},[22,13596,3528],{"href":3526,"rel":13597},[26],"에서 zip을 받아 풀고, ",[47,13600,13080],{},[47,13602,713],{},[47,13604,717],{},"를 실행하면 한 페이지짜리 PDF가 나옵니다.",[14,13607,13609],{"id":13608},"variable-폰트가-아닌-static-ttf를-고르기","variable 폰트가 아닌 static TTF를 고르기",[19,13611,13612,13613,13616],{},"Google Fonts에서 ",[30,13614,13615],{},"Get font → Download all",", zip 압축을 풀면 보기에 비슷하지만 성격이 전혀 다른 두 그룹이 보입니다:",[1113,13618,13619,13630],{},[886,13620,13621,13622,13625,13626,13629],{},"zip 루트의 ",[47,13623,13624],{},"NotoSansJP-VariableFont_wght.ttf"," — weight 100–900을 한 파일에 담은 ",[30,13627,13628],{},"variable 폰트",", 약 7 MB",[886,13631,13632,13635,13636,13639,13640,13643],{},[47,13633,13634],{},"static/"," 디렉토리 — ",[47,13637,13638],{},"NotoSansJP-Thin.ttf","부터 ",[47,13641,13642],{},"NotoSansJP-Black.ttf","까지 굵기별로 분리된 9개의 TTF, 각 약 5 MB",[19,13645,13646,203],{},[30,13647,13648,13650],{},[47,13649,13634],{}," 쪽을 쓰세요",[19,13652,13653,13654,854,13656,13658,13659,867,13662,867,13665,13668],{},"gpdf의 TrueType 파서는 일부러 범위를 좁게 잡아 뒀습니다. 글리프 아웃라인, 복합 글리프, ",[47,13655,860],{},[47,13657,735],{}," — 고정 굵기 텍스트를 렌더링하는 데 필요한 테이블은 다 다룹니다. 하지만 variable 폰트를 진짜 가변적으로 만드는 ",[47,13660,13661],{},"fvar",[47,13663,13664],{},"gvar",[47,13666,13667],{},"HVAR"," 테이블은 읽지 않습니다. VariableFont_wght.ttf를 넘기면 파서가 깔끔히 에러를 내거나, 운이 나쁘면 기본 인스턴스의 글리프만 뽑고 당신이 지정했다고 생각한 weight 축을 조용히 무시합니다.",[19,13670,13671],{},"파일 크기 측면에서도 static 쪽이 유리합니다. variable 폰트는 weight 축 위의 모든 인스턴스 아웃라인을 한 파일에 담는 — 그게 설계 의도입니다. Regular만 쓴다면 나머지 8개 weight 만큼의 데이터를 그냥 실어 나르게 됩니다. static Regular가 5 MB, variable이 7 MB. 서브셋으로 둘 다 줄어들긴 하지만 입력은 static이 깔끔합니다.",[14,13673,13675],{"id":13674},"핵심은-이-네-줄","핵심은 이 네 줄",[19,13677,13678],{},"의미 있는 건 생성자 옵션뿐입니다:",[60,13680,13682],{"className":62,"code":13681,"language":64,"meta":65,"style":65},"doc := gpdf.NewDocument(\n    gpdf.WithFont(\"NotoSansJP\", font),\n    gpdf.WithDefaultFont(\"NotoSansJP\", 11),\n)\n",[47,13683,13684,13698,13720,13742],{"__ignoreMap":65},[69,13685,13686,13688,13690,13692,13694,13696],{"class":71,"line":72},[69,13687,1004],{"class":187},[69,13689,197],{"class":75},[69,13691,275],{"class":187},[69,13693,203],{"class":75},[69,13695,280],{"class":174},[69,13697,283],{"class":75},[69,13699,13700,13702,13704,13706,13708,13710,13712,13714,13716,13718],{"class":71,"line":83},[69,13701,1019],{"class":187},[69,13703,203],{"class":75},[69,13705,354],{"class":174},[69,13707,209],{"class":75},[69,13709,212],{"class":75},[69,13711,3257],{"class":215},[69,13713,212],{"class":75},[69,13715,191],{"class":75},[69,13717,368],{"class":187},[69,13719,306],{"class":75},[69,13721,13722,13724,13726,13728,13730,13732,13734,13736,13738,13740],{"class":71,"line":90},[69,13723,1019],{"class":187},[69,13725,203],{"class":75},[69,13727,380],{"class":174},[69,13729,209],{"class":75},[69,13731,212],{"class":75},[69,13733,3257],{"class":215},[69,13735,212],{"class":75},[69,13737,191],{"class":75},[69,13739,393],{"class":340},[69,13741,306],{"class":75},[69,13743,13744],{"class":71,"line":100},[69,13745,160],{"class":75},[19,13747,13748,13749,13752,13753,8280,13756,854,13759,13762,13763,13765],{},"폰트 패밀리명 (",[47,13750,13751],{},"\"NotoSansJP\"",") 은 임의로 정해도 됩니다. gpdf는 이를 조회 키로만 씁니다 — 파일 경로도 아니고, 폰트 메타데이터에서 읽는 이름도 아닙니다. 팀에서 ",[47,13754,13755],{},"\"body\"",[47,13757,13758],{},"\"jp\"",[47,13760,13761],{},"\"Noto\"","가 더 읽기 좋다면 그걸 쓰세요. 나중에 ",[47,13764,12499],{},"에 같은 이름을 넘기기만 하면 됩니다.",[19,13767,13768,13770,13771,13773,13774,13776],{},[47,13769,380],{},"는 매번 ",[47,13772,3000],{}," 호출에 ",[47,13775,3744],{},"를 쓰지 않게 해 줍니다. 이걸 빼면 gpdf는 Helvetica로 폴백하는데, Helvetica는 CJK 코드포인트를 하나도 커버하지 않아서 — 제목만 제대로 나오고 본문 전체가 두부 네모 (□□□)가 된 PDF가 나옵니다. 왜 제목만 멀쩡한지 한 시간쯤 헤매게 됩니다.",[14,13778,13780],{"id":13779},"어느-굵기를-등록해야-할까","어느 굵기를 등록해야 할까",[19,13782,13783],{},"청구서·영수증·업무용 리포트라면 Regular와 Bold 두 개면 충분합니다:",[60,13785,13787],{"className":62,"code":13786,"language":64,"meta":65,"style":65},"reg,  _ := os.ReadFile(\"NotoSansJP-Regular.ttf\")\nbold, _ := os.ReadFile(\"NotoSansJP-Bold.ttf\")\n\ndoc := gpdf.NewDocument(\n    gpdf.WithFont(\"NotoSansJP\", reg),\n    gpdf.WithFont(\"NotoSansJP-Bold\", bold),\n    gpdf.WithDefaultFont(\"NotoSansJP\", 11),\n)\n",[47,13788,13789,13815,13841,13845,13859,13881,13903,13925],{"__ignoreMap":65},[69,13790,13791,13793,13795,13797,13799,13801,13803,13805,13807,13809,13811,13813],{"class":71,"line":72},[69,13792,943],{"class":187},[69,13794,191],{"class":75},[69,13796,948],{"class":187},[69,13798,197],{"class":75},[69,13800,200],{"class":187},[69,13802,203],{"class":75},[69,13804,206],{"class":174},[69,13806,209],{"class":75},[69,13808,212],{"class":75},[69,13810,3142],{"class":215},[69,13812,212],{"class":75},[69,13814,160],{"class":75},[69,13816,13817,13819,13821,13823,13825,13827,13829,13831,13833,13835,13837,13839],{"class":71,"line":83},[69,13818,971],{"class":187},[69,13820,191],{"class":75},[69,13822,976],{"class":187},[69,13824,197],{"class":75},[69,13826,200],{"class":187},[69,13828,203],{"class":75},[69,13830,206],{"class":174},[69,13832,209],{"class":75},[69,13834,212],{"class":75},[69,13836,8858],{"class":215},[69,13838,212],{"class":75},[69,13840,160],{"class":75},[69,13842,13843],{"class":71,"line":90},[69,13844,87],{"emptyLinePlaceholder":86},[69,13846,13847,13849,13851,13853,13855,13857],{"class":71,"line":100},[69,13848,1004],{"class":187},[69,13850,197],{"class":75},[69,13852,275],{"class":187},[69,13854,203],{"class":75},[69,13856,280],{"class":174},[69,13858,283],{"class":75},[69,13860,13861,13863,13865,13867,13869,13871,13873,13875,13877,13879],{"class":71,"line":112},[69,13862,1019],{"class":187},[69,13864,203],{"class":75},[69,13866,354],{"class":174},[69,13868,209],{"class":75},[69,13870,212],{"class":75},[69,13872,3257],{"class":215},[69,13874,212],{"class":75},[69,13876,191],{"class":75},[69,13878,1036],{"class":187},[69,13880,306],{"class":75},[69,13882,13883,13885,13887,13889,13891,13893,13895,13897,13899,13901],{"class":71,"line":122},[69,13884,1019],{"class":187},[69,13886,203],{"class":75},[69,13888,354],{"class":174},[69,13890,209],{"class":75},[69,13892,212],{"class":75},[69,13894,8996],{"class":215},[69,13896,212],{"class":75},[69,13898,191],{"class":75},[69,13900,1060],{"class":187},[69,13902,306],{"class":75},[69,13904,13905,13907,13909,13911,13913,13915,13917,13919,13921,13923],{"class":71,"line":127},[69,13906,1019],{"class":187},[69,13908,203],{"class":75},[69,13910,380],{"class":174},[69,13912,209],{"class":75},[69,13914,212],{"class":75},[69,13916,3257],{"class":215},[69,13918,212],{"class":75},[69,13920,191],{"class":75},[69,13922,393],{"class":340},[69,13924,306],{"class":75},[69,13926,13927],{"class":71,"line":137},[69,13928,160],{"class":75},[19,13930,13931,13933,13934,13936,13937,854,13939,13941],{},[47,13932,1093],{}," 서픽스로 등록하면 ",[47,13935,1097],{},"가 자동으로 잡습니다. ",[47,13938,12484],{},[47,13940,12488],{},"도 같은 규약. 다만 Noto Sans JP에는 이탤릭이 없습니다 — CJK 폰트는 자연스러운 기울임 형태가 없어서 Noto 계열에도 이탤릭이 존재하지 않습니다. 일본어에서 강조가 필요하면 색상·크기·굵기로 대체하세요.",[19,13943,13944],{},"브로슈어나 헤드라인에 Medium이나 SemiBold가 필요하면 원하는 서픽스로 등록하고 패밀리명으로 직접 참조하면 됩니다:",[60,13946,13948],{"className":62,"code":13947,"language":64,"meta":65,"style":65},"gpdf.WithFont(\"NotoSansJP-Medium\", medium)\n// ...\nc.Text(\"見出し\", template.FontFamily(\"NotoSansJP-Medium\"))\n",[47,13949,13950,13974,13979],{"__ignoreMap":65},[69,13951,13952,13954,13956,13958,13960,13962,13965,13967,13969,13972],{"class":71,"line":72},[69,13953,27],{"class":187},[69,13955,203],{"class":75},[69,13957,354],{"class":174},[69,13959,209],{"class":75},[69,13961,212],{"class":75},[69,13963,13964],{"class":215},"NotoSansJP-Medium",[69,13966,212],{"class":75},[69,13968,191],{"class":75},[69,13970,13971],{"class":187}," medium",[69,13973,160],{"class":75},[69,13975,13976],{"class":71,"line":83},[69,13977,13978],{"class":2211},"// ...\n",[69,13980,13981,13983,13985,13987,13989,13991,13994,13996,13998,14000,14002,14004,14006,14008,14010,14012],{"class":71,"line":90},[69,13982,483],{"class":187},[69,13984,203],{"class":75},[69,13986,507],{"class":174},[69,13988,209],{"class":75},[69,13990,212],{"class":75},[69,13992,13993],{"class":215},"見出し",[69,13995,212],{"class":75},[69,13997,191],{"class":75},[69,13999,521],{"class":187},[69,14001,203],{"class":75},[69,14003,5006],{"class":174},[69,14005,209],{"class":75},[69,14007,212],{"class":75},[69,14009,13964],{"class":215},[69,14011,212],{"class":75},[69,14013,5029],{"class":75},[19,14015,14016,14017,867,14019,867,14021,14023],{},"서픽스 기반 Bold/Italic 숏컷은 ",[47,14018,1093],{},[47,14020,12484],{},[47,14022,12488],{}," 이 세 개에만 자동 연결됩니다. 나머지는 패밀리명으로 명시적으로 부릅니다.",[14,14025,14027],{"id":14026},"서브셋-후의-실제-크기","서브셋 후의 실제 크기",[19,14029,14030],{},"Noto Sans JP Regular는 디스크에서 약 5 MB. 이 숫자를 보고 별도 폰트 CDN을 꾸리거나 PDF 후처리로 폰트를 빼내는 팀이 가끔 있는데, gpdf에서는 둘 다 필요 없습니다.",[19,14032,14033],{},"실제로 PDF에 들어가는 양은 이 정도:",[741,14035,14036,14048],{},[744,14037,14038],{},[747,14039,14040,14042,14045],{},[750,14041,2898],{},[750,14043,14044],{},"사용 글리프",[750,14046,14047],{},"PDF 내 폰트 데이터",[759,14049,14050,14061,14072,14083],{},[747,14051,14052,14055,14058],{},[764,14053,14054],{},"한 줄 영수증 (~15자)",[764,14056,14057],{},"~14",[764,14059,14060],{},"~11 KB",[747,14062,14063,14066,14069],{},[764,14064,14065],{},"일반 청구서 (~200자)",[764,14067,14068],{},"~80",[764,14070,14071],{},"~28 KB",[747,14073,14074,14077,14080],{},[764,14075,14076],{},"10페이지 리포트 (~8,000자)",[764,14078,14079],{},"~900",[764,14081,14082],{},"~180 KB",[747,14084,14085,14088,14091],{},[764,14086,14087],{},"사전 수준 풀셋 (JIS Level 1)",[764,14089,14090],{},"~6,800",[764,14092,14093],{},"~2.1 MB",[19,14095,14096],{},"(gpdf v1.0, 정적 서브셋 활성화. 글리프 ID가 CFF와 hmtx 어디에 떨어지느냐에 따라 몇 KB 편차)",[19,14098,14099],{},"최종 50 KB짜리 청구서 PDF라면 그중 절반 이상이 폰트 데이터입니다. 그래도 서브셋 없이 5 MB를 통째로 넣는 것에 비하면 오차 수준이고, 뷰어는 즉시 엽니다.",[14,14101,14103],{"id":14102},"noto-sans-jp와-noto-sans-cjk-jp-혼동-금지","Noto Sans JP와 Noto Sans CJK JP — 혼동 금지",[19,14105,14106],{},"일본어를 처리할 수 있다고 주장하는 Noto 패밀리가 두 개 있고, 이름이 비슷해서 서로 호환된다고 착각하기 쉽습니다. 실제로는 완전히 다릅니다.",[19,14108,14109,14111],{},[30,14110,757],{},"가 쓰려는 쪽입니다. TTF 배포, 단일 언어, 굵기별로 파일이 분리. Google Fonts에서 받는 것이 이것입니다.",[19,14113,14114,14117,14118,14120,14121,14124],{},[30,14115,14116],{},"Noto Sans CJK JP","는 CJK 전체를 아우르는 슈퍼 패밀리. OpenType Collection (",[47,14119,1123],{},") 형식으로, 일본어·간체 중국어·번체 중국어·한국어 글리프를 한자 통합 (Han unification) 방식으로 한 파일에 담아 배포합니다. 초기 Noto 릴리스와 ",[47,14122,14123],{},"notofonts.github.io/noto-cjk","에 있는 것은 이쪽입니다.",[19,14126,14127,14128,14130,14131,14133],{},"gpdf는 TTF를 바로 지원합니다. TTC는 컨테이너 포맷이라 ",[47,14129,354],{},"에 바이트를 넘기기 전에 face 인덱스를 골라야 하고, 각 face 안의 ",[47,14132,860],{},"은 특정 CJK 로케일에 맞춰져 있어 한자 통합에 관한 선택을 암묵적으로 하는 꼴이 됩니다. JP 전용 TTF를 고르면 이런 선택이 명시적이 됩니다.",[19,14135,14136,14137,14140,14141,3560,14144,14146],{},"새 프로젝트면 Noto Sans JP를 씁니다. 레거시 프로젝트에 ",[47,14138,14139],{},"NotoSansCJK-Regular.ttc","가 이미 있다면 ",[47,14142,14143],{},"pyftsubset",[47,14145,900],{},"로 JP face만 추출해 TTF로 저장소에 커밋하는 편이 안전합니다.",[14,14148,14150],{"id":14149},"바이너리에-폰트-임베드하기","바이너리에 폰트 임베드하기",[19,14152,14153],{},"PDF 생성기는 대개 컨테이너에서 돕니다. 폰트를 함께 배포하는 가장 깔끔한 방법은 바이너리에 컴파일해 넣는 것:",[60,14155,14157],{"className":62,"code":14156,"language":64,"meta":65,"style":65},"package main\n\nimport (\n    _ \"embed\"\n\n    \"github.com/gpdf-dev/gpdf\"\n)\n\n//go:embed NotoSansJP-Regular.ttf\nvar notoJP []byte\n\nfunc main() {\n    doc := gpdf.NewDocument(\n        gpdf.WithFont(\"NotoSansJP\", notoJP),\n        gpdf.WithDefaultFont(\"NotoSansJP\", 11),\n    )\n    // ...\n}\n",[47,14158,14159,14165,14169,14175,14187,14191,14199,14203,14207,14212,14225,14229,14239,14253,14276,14298,14302,14307],{"__ignoreMap":65},[69,14160,14161,14163],{"class":71,"line":72},[69,14162,76],{"class":75},[69,14164,80],{"class":79},[69,14166,14167],{"class":71,"line":83},[69,14168,87],{"emptyLinePlaceholder":86},[69,14170,14171,14173],{"class":71,"line":90},[69,14172,94],{"class":93},[69,14174,97],{"class":75},[69,14176,14177,14180,14182,14185],{"class":71,"line":100},[69,14178,14179],{"class":187},"    _ ",[69,14181,212],{"class":75},[69,14183,14184],{"class":79},"embed",[69,14186,109],{"class":75},[69,14188,14189],{"class":71,"line":112},[69,14190,87],{"emptyLinePlaceholder":86},[69,14192,14193,14195,14197],{"class":71,"line":122},[69,14194,103],{"class":75},[69,14196,132],{"class":79},[69,14198,109],{"class":75},[69,14200,14201],{"class":71,"line":127},[69,14202,160],{"class":75},[69,14204,14205],{"class":71,"line":137},[69,14206,87],{"emptyLinePlaceholder":86},[69,14208,14209],{"class":71,"line":147},[69,14210,14211],{"class":2211},"//go:embed NotoSansJP-Regular.ttf\n",[69,14213,14214,14217,14220,14222],{"class":71,"line":157},[69,14215,14216],{"class":75},"var",[69,14218,14219],{"class":187}," notoJP ",[69,14221,2478],{"class":75},[69,14223,14224],{"class":1620},"byte\n",[69,14226,14227],{"class":71,"line":163},[69,14228,87],{"emptyLinePlaceholder":86},[69,14230,14231,14233,14235,14237],{"class":71,"line":168},[69,14232,171],{"class":75},[69,14234,175],{"class":174},[69,14236,178],{"class":75},[69,14238,181],{"class":75},[69,14240,14241,14243,14245,14247,14249,14251],{"class":71,"line":184},[69,14242,270],{"class":187},[69,14244,197],{"class":75},[69,14246,275],{"class":187},[69,14248,203],{"class":75},[69,14250,280],{"class":174},[69,14252,283],{"class":75},[69,14254,14255,14257,14259,14261,14263,14265,14267,14269,14271,14274],{"class":71,"line":222},[69,14256,289],{"class":187},[69,14258,203],{"class":75},[69,14260,354],{"class":174},[69,14262,209],{"class":75},[69,14264,212],{"class":75},[69,14266,3257],{"class":215},[69,14268,212],{"class":75},[69,14270,191],{"class":75},[69,14272,14273],{"class":187}," notoJP",[69,14275,306],{"class":75},[69,14277,14278,14280,14282,14284,14286,14288,14290,14292,14294,14296],{"class":71,"line":238},[69,14279,289],{"class":187},[69,14281,203],{"class":75},[69,14283,380],{"class":174},[69,14285,209],{"class":75},[69,14287,212],{"class":75},[69,14289,3257],{"class":215},[69,14291,212],{"class":75},[69,14293,191],{"class":75},[69,14295,393],{"class":340},[69,14297,306],{"class":75},[69,14299,14300],{"class":71,"line":256},[69,14301,401],{"class":75},[69,14303,14304],{"class":71,"line":262},[69,14305,14306],{"class":2211},"    // ...\n",[69,14308,14309],{"class":71,"line":267},[69,14310,707],{"class":75},[19,14312,14313,14314,14317],{},"바이너리는 약 8 MB에서 약 13 MB로 늘어납니다. 대신 Docker 이미지 산출물이 두 개가 아니라 하나가 되고, ",[47,14315,14316],{},"COPY --from=builder /app /app"," 만으로 충분하며, 폰트 파일 누락으로 망가진 컨테이너를 누군가 올릴 일도 없습니다. 하루에 수천 개 PDF를 생성하는 배치 작업이라면 이 쪽이 올바른 기본값입니다.",[14,14319,14321],{"id":14320},"관련-읽을거리","관련 읽을거리",[1113,14323,14324,14329,14337,14342],{},[886,14325,14326,14328],{},[22,14327,1225],{"href":1224}," — CJK TTF 전반에 적용되는 일반 레시피",[886,14330,14331,5225,14334,14336],{},[22,14332,14333],{"href":2836},"gofpdf가 아카이브되었다. gpdf 마이그레이션 가이드",[47,14335,2574],{},"에서 옮겨 오는 매핑",[886,14338,14339,14341],{},[22,14340,12981],{"href":2909}," — CJK 처리 관점의 비교",[886,14343,14344,5225,14348,14350],{},[22,14345,5224],{"href":14346,"rel":14347},"https://gpdf.dev/docs/guide/fonts",[26],[47,14349,354],{}," 완전 레퍼런스",[14,14352,14354],{"id":14353},"gpdf를-써-보기","gpdf를 써 보기",[19,14356,14357],{},"gpdf는 Go용 PDF 생성 라이브러리입니다. MIT, 외부 의존성 제로, 네이티브 CJK 지원.",[60,14359,14360],{"className":1250,"code":1251,"language":1252,"meta":65,"style":65},[47,14361,14362],{"__ignoreMap":65},[69,14363,14364,14366,14368],{"class":71,"line":72},[69,14365,64],{"class":79},[69,14367,1261],{"class":215},[69,14369,1264],{"class":215},[19,14371,14372,1271,14375],{},[22,14373,1270],{"href":24,"rel":14374},[26],[22,14376,14379],{"href":14377,"rel":14378},"https://gpdf.dev/docs/quickstart",[26],"문서 보기",[1278,14381,14382],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .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 .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":65,"searchDepth":83,"depth":83,"links":14384},[14385,14386,14387,14388,14389,14390,14391,14392,14393,14394,14395],{"id":13058,"depth":83,"text":13059},{"id":13071,"depth":83,"text":13072},{"id":11760,"depth":83,"text":11761},{"id":13608,"depth":83,"text":13609},{"id":13674,"depth":83,"text":13675},{"id":13779,"depth":83,"text":13780},{"id":14026,"depth":83,"text":14027},{"id":14102,"depth":83,"text":14103},{"id":14149,"depth":83,"text":14150},{"id":14320,"depth":83,"text":14321},{"id":14353,"depth":83,"text":14354},"gpdf.WithFont에 static 버전 NotoSansJP-Regular.ttf를 등록합니다. variable 폰트를 쓰지 않는 이유와 17,000개 글리프가 PDF에서 40 KB 미만까지 줄어드는 서브셋 구조를 설명합니다.",{"name":14398,"totalTime":5269,"tools":14399,"steps":14401},"gpdf 문서에서 Noto Sans JP를 기본 폰트로 사용하기",[1301,14400],"NotoSansJP-Regular.ttf (Google Fonts의 static TTF)",[14402,14405,14408,14411],{"name":14403,"text":14404},"Google Fonts에서 static TTF 다운로드","fonts.google.com에서 Noto Sans JP zip을 받아 압축을 풀고 static/NotoSansJP-Regular.ttf를 선택합니다. zip 루트의 NotoSansJP-VariableFont_wght.ttf는 쓰지 않습니다.",{"name":14406,"text":14407},"시작 시점에 바이트로 읽기","os.ReadFile로 NotoSansJP-Regular.ttf를 읽습니다. 바이너리에 포함하고 싶다면 //go:embed를 씁니다.",{"name":14409,"text":14410},"문서 생성 시 폰트 등록","gpdf.NewDocument에 gpdf.WithFont(\"NotoSansJP\", fontBytes)와 gpdf.WithDefaultFont(\"NotoSansJP\", 11)을 전달합니다. AddUTF8Font도 파일 경로도 필요 없습니다.",{"name":14412,"text":14413},"일본어 텍스트 작성 후 PDF 생성","컬럼 안에서 c.Text(\"請求書\")를 호출합니다. doc.Generate()가 []byte를 반환하고, gpdf는 실제로 사용한 글리프만 서브셋으로 최종 PDF에 임베드합니다.",{},{"title":1218,"description":14396},"ko/blog/004.noto-sans-jp-with-gpdf",[1325,1326,1327],"hNM0iCUbRC1dUgryk9bj5q9wPeXGxWoSj3L4d8uNQ7c",{"id":14420,"title":14421,"author":14422,"body":14423,"date":19165,"description":19166,"draft":1295,"extension":1296,"howTo":19167,"image":1319,"meta":19190,"navigation":86,"path":2836,"seo":19191,"stem":19192,"tags":19193,"updated":1319,"__hash__":19194},"blogKo/ko/blog/001.gofpdf-migration.md","gofpdf이 보관됨. gpdf로 마이그레이션하는 완전 가이드",{"name":8,"url":9},{"type":11,"value":14424,"toc":19149},[14425,14427,14442,14452,14455,14466,14469,14473,14479,14482,14489,14496,14499,14503,14506,14532,14538,14542,14545,14810,14824,14851,14855,14858,14863,15035,15039,15420,15430,15442,15446,15456,15460,16194,16197,16201,16563,16580,16583,16587,16593,16597,16759,16772,16776,17220,17223,17233,17236,17239,17243,17262,17266,17581,17587,17591,18251,18266,18270,18283,18287,18549,18553,18872,18889,18893,18902,18979,18982,18985,18989,18992,19029,19032,19034,19040,19059,19068,19079,19088,19094,19096,19099,19111,19120,19124,19146],[14,14426,1338],{"id":1337},[19,14428,14429,14431,14432,14434,14435,14437,14438,14441],{},[30,14430,27],{},"는 순수 Go·외부 의존성 제로·네이티브 CJK 지원 PDF 라이브러리. ",[47,14433,2574],{}," 같은 번거로운 절차도, ",[47,14436,1930],{},"로 좌표를 직접 다루는 일도 없다. Bootstrap 식 12-컬럼 그리드로 선언적으로 작성할 수 있고, 벤치마크에선 ",[30,14439,14440],{},"gofpdf보다 약 10배 빠름",". 마이그레이션은 대체로 \"명령형 커서 조작\"을 \"선언형 빌더\"로 바꾸는 작업이며, Before/After 다섯 쌍으로 전체 매핑을 살펴본다.",[19,14443,14444,14445,14448,14449,14451],{},"지난주 동료가 새 Go 프로젝트를 열고 ",[47,14446,14447],{},"go get github.com/jung-kurt/gofpdf","를 실행했다. 10분 뒤 GitHub 배너 스크린샷이 날아왔다: ",[30,14450,6701],{}," 그리고 한마디, \"잠깐, 포크도 아카이브된 거야?\"",[19,14453,14454],{},"맞다. 둘 다 그렇다.",[19,14456,14457,1907,14459,14462,14463,14465],{},[47,14458,1356],{},[30,14460,14461],{},"2021년 9월 8일"," 보관되었다. 커뮤니티 포크 ",[47,14464,1434],{},"의 마지막 릴리스는 2023년이고 2025년에 정식으로 보관되었다. Stack Overflow와 한국어 블로그의 Go PDF 관련 답변 대부분이 아직도 gofpdf를 가리키지만, 그건 4년 넘게 read-only 상태이고 후계자로 여겨지던 포크마저 사라졌다.",[19,14467,14468],{},"운영 중인 gofpdf 코드가 있다면 이 글은 마이그레이션 지도. 신규 프로젝트에서 검색 결과에 끌려 반사적으로 gofpdf를 집으려 했다면, 이 글이 그대로 대안이다.",[14,14470,14472],{"id":14471},"왜-gofpdf은-정말로-돌아오지-않는가","왜 gofpdf은 정말로 돌아오지 않는가",[19,14474,14475,14476,14478],{},"오픈소스가 반드시 죽는 건 아니다. 원래 메인테이너가 손을 뗐어도 다른 누가 이어받는 경우가 있다. gofpdf도 그렇게 될 줄 알았고, 한동안은 실제로 그랬다. ",[47,14477,1434],{},"는 코드를 정리하고, 오래된 버그를 고치고, PR을 받으며 \"진정한 후계\"처럼 보였다.",[19,14480,14481],{},"그러다 2025년 초 포크도 보관되었다. README에는 이렇게 쓰여 있다: \"이 프로젝트는 더 이상 적극적으로 유지되지 않습니다. 다른 라이브러리 사용을 고려해 주세요.\"",[19,14483,14484,14485,14488],{},"이유보다 결과가 중요하다. ",[30,14486,14487],{},"gofpdf에 의존하는 모든 Go 프로젝트는 지금 두 겹의 미유지 코드 위에 앉아 있다",". 보안 이슈는 패치되지 않는다. PDF 2.0 스펙은 2020년에 나왔지만 gofpdf는 대부분 따라잡지 못했다. Go 1.25의 루프 변수 동작은 지금은 gofpdf와 문제없이 작동하지만 내일 깨진다면 포크해서 고치는 건 당신 몫이다.",[19,14490,14491,14492,14495],{},"\"라이브러리에 버그가 있다\"는 문제가 아니라 ",[30,14493,14494],{},"공급망"," 문제다.",[19,14497,14498],{},"한국 팀엔 특히 민감한 지점이 있다. 전자세금계산서, 전자문서 원본성 인증, 공공기관 납품용 PDF/A 요건 등에서 미유지 라이브러리로 기술 스택을 정당화하기 어렵다.",[14,14500,14502],{"id":14501},"한국-팀이-gofpdf로-실제-하던-일","한국 팀이 gofpdf로 실제 하던 일",[19,14504,14505],{},"GitHub Issues와 한국 기술 블로그 글을 살피면 gofpdf의 주요 용도는 다음 네 가지다:",[883,14507,14508,14514,14520,14526],{},[886,14509,14510,14513],{},[30,14511,14512],{},"세금계산서·영수증·납품서"," — 헤더, 거래처, 명세표, 합계, 푸터",[886,14515,14516,14519],{},[30,14517,14518],{},"리포트"," — 헤더와 페이지 번호가 반복되는 다중 페이지 문서",[886,14521,14522,14525],{},[30,14523,14524],{},"증명서·양식"," — 템플릿 이미지 위에 고정 위치로 텍스트를 얹음",[886,14527,14528,14531],{},[30,14529,14530],{},"CJK 문서"," — 한국어·일본어·중국어 혼용 송장, 배송 라벨",[19,14533,14534,14535,14537],{},"앞의 세 가지는 gpdf 빌더 API로 바로 덮인다. 네 번째 CJK가 gpdf가 gofpdf 대비 가장 격차를 크게 두는 영역. gofpdf은 ",[47,14536,2574],{},"를 호출하고 TTF 경로를 관리하며 기본 문자면 밖으로 글자가 나가지 않기를 기도해야 했다. gpdf는 CJK를 처음부터 일급 시민으로 다룬다 — TrueType 폰트를 등록하고 한국어를 쓰고, PDF가 나온다. 끝.",[14,14539,14541],{"id":14540},"api-매핑표","API 매핑표",[19,14543,14544],{},"아래 표가 치트시트. 이후 섹션에서 다섯 개의 구체적 Before/After를 본다.",[741,14546,14547,14558],{},[744,14548,14549],{},[747,14550,14551,14554,14556],{},[750,14552,14553],{},"하고 싶은 것",[750,14555,1431],{},[750,14557,27],{},[759,14559,14560,14575,14596,14615,14632,14647,14665,14683,14698,14713,14731,14746,14761,14780,14795],{},[747,14561,14562,14565,14570],{},[764,14563,14564],{},"문서 생성",[764,14566,14567],{},[47,14568,14569],{},"gofpdf.New(\"P\", \"mm\", \"A4\", \"\")",[764,14571,14572],{},[47,14573,14574],{},"gpdf.NewDocument(gpdf.WithPageSize(document.A4))",[747,14576,14577,14580,14585],{},[764,14578,14579],{},"페이지 추가",[764,14581,14582],{},[47,14583,14584],{},"pdf.AddPage()",[764,14586,14587,897,14590],{},[47,14588,14589],{},"doc.AddPage()",[3586,14591,209,14592,14595],{},[47,14593,14594],{},"*PageBuilder"," 반환)",[747,14597,14598,14601,14606],{},[764,14599,14600],{},"폰트 지정",[764,14602,14603],{},[47,14604,14605],{},"pdf.SetFont(\"Arial\", \"B\", 16)",[764,14607,14608,867,14610,867,14612],{},[47,14609,12499],{},[47,14611,1097],{},[47,14613,14614],{},"template.FontSize(16)",[747,14616,14617,14620,14625],{},[764,14618,14619],{},"TTF 등록 (CJK)",[764,14621,14622],{},[47,14623,14624],{},"pdf.AddUTF8Font(\"noto\", \"\", \"NotoSansKR-Regular.ttf\")",[764,14626,14627,897,14629],{},[47,14628,11558],{},[3586,14630,14631],{},"(생성 시 전달)",[747,14633,14634,14637,14642],{},[764,14635,14636],{},"한 줄 텍스트",[764,14638,14639],{},[47,14640,14641],{},"pdf.Cell(40, 10, \"hi\")",[764,14643,14644],{},[47,14645,14646],{},"c.Text(\"hi\")",[747,14648,14649,14652,14657],{},[764,14650,14651],{},"자동 줄바꿈 텍스트",[764,14653,14654],{},[47,14655,14656],{},"pdf.MultiCell(0, 10, body, \"\", \"L\", false)",[764,14658,14659,897,14662],{},[47,14660,14661],{},"c.Text(body)",[3586,14663,14664],{},"(자동 줄바꿈)",[747,14666,14667,14670,14675],{},[764,14668,14669],{},"텍스트 색상",[764,14671,14672],{},[47,14673,14674],{},"pdf.SetTextColor(255, 0, 0)",[764,14676,14677,897,14680],{},[47,14678,14679],{},"template.TextColor(pdf.Red)",[3586,14681,14682],{},"(텍스트별 옵션)",[747,14684,14685,14688,14693],{},[764,14686,14687],{},"가로선",[764,14689,14690],{},[47,14691,14692],{},"pdf.Line(x1, y1, x2, y2)",[764,14694,14695],{},[47,14696,14697],{},"c.Line(template.LineThickness(document.Pt(1)))",[747,14699,14700,14703,14708],{},[764,14701,14702],{},"이미지 삽입",[764,14704,14705],{},[47,14706,14707],{},"pdf.ImageOptions(\"logo.png\", x, y, w, h, ...)",[764,14709,14710],{},[47,14711,14712],{},"c.Image(imgBytes, template.FitWidth(document.Mm(50)))",[747,14714,14715,14718,14723],{},[764,14716,14717],{},"커서 위치",[764,14719,14720],{},[47,14721,14722],{},"pdf.SetXY(x, y)",[764,14724,14725],{},[3586,14726,14727,14728,457],{},"(없음 — 행/열로 작성, 또는 ",[47,14729,14730],{},"page.Absolute(x, y, fn)",[747,14732,14733,14736,14741],{},[764,14734,14735],{},"모든 페이지 헤더",[764,14737,14738],{},[47,14739,14740],{},"pdf.SetHeaderFunc(fn)",[764,14742,14743],{},[47,14744,14745],{},"doc.Header(fn)",[747,14747,14748,14751,14756],{},[764,14749,14750],{},"모든 페이지 푸터",[764,14752,14753],{},[47,14754,14755],{},"pdf.SetFooterFunc(fn)",[764,14757,14758],{},[47,14759,14760],{},"doc.Footer(fn)",[747,14762,14763,14766,14772],{},[764,14764,14765],{},"페이지 번호",[764,14767,14768,14771],{},[47,14769,14770],{},"pdf.PageNo()","(수동)",[764,14773,14774,867,14777],{},[47,14775,14776],{},"c.PageNumber()",[47,14778,14779],{},"c.TotalPages()",[747,14781,14782,14785,14790],{},[764,14783,14784],{},"파일로 출력",[764,14786,14787],{},[47,14788,14789],{},"pdf.OutputFileAndClose(\"out.pdf\")",[764,14791,14792],{},[47,14793,14794],{},"data, _ := doc.Generate(); os.WriteFile(\"out.pdf\", data, 0o644)",[747,14796,14797,14800,14805],{},[764,14798,14799],{},"io.Writer로 출력",[764,14801,14802],{},[47,14803,14804],{},"pdf.Output(w)",[764,14806,14807],{},[47,14808,14809],{},"doc.Render(w)",[19,14811,14812,14813,14816,14817,14820,14821,14823],{},"가장 큰 차이는 API 형태다. gofpdf는 ",[30,14814,14815],{},"명령형",", gpdf는 ",[30,14818,14819],{},"선언형",". gofpdf는 커서를 옮기며 쓴다. gpdf는 행과 열의 트리를 기술하고 레이아웃 엔진이 배치한다. 초반 몇 개는 gpdf 쪽이 더 길게 느껴진다. 세 번째쯤 되면 ",[47,14822,1930],{},"가 그리워지지 않는다.",[19,14825,14826,14827,867,14830,867,14833,14836,14837,854,14840,854,14843,14846,14847,14850],{},"단위 얘기. gofpdf는 생성 시 기본 단위(",[47,14828,14829],{},"\"mm\"",[47,14831,14832],{},"\"pt\"",[47,14834,14835],{},"\"in\"",")를 고른다. gpdf는 내부적으로 전부 pt로 고정하고, 호출부에서 ",[47,14838,14839],{},"document.Mm(20)",[47,14841,14842],{},"document.Pt(12)",[47,14844,14845],{},"document.Cm(1)"," 같은 헬퍼를 쓴다. CSS 감각에 가까운데, 헤더 여백을 ",[47,14848,14849],{},"document.Mm(15)","로 잡고 난 뒤로는 단위를 의식하지 않게 된다.",[14,14852,14854],{"id":14853},"before-after-1-가장-단순한-pdf","Before / After 1: 가장 단순한 PDF",[19,14856,14857],{},"\"hello world\" 쌍. gofpdf의 간결함이 인기의 이유였다. gpdf 버전은 몇 줄 더 길다 — 커서를 움직이는 게 아니라 트리를 만들기 때문.",[19,14859,14860],{},[30,14861,14862],{},"Before — gofpdf:",[60,14864,14866],{"className":62,"code":14865,"language":64,"meta":65,"style":65},"package main\n\nimport \"github.com/jung-kurt/gofpdf\"\n\nfunc main() {\n    pdf := gofpdf.New(\"P\", \"mm\", \"A4\", \"\")\n    pdf.AddPage()\n    pdf.SetFont(\"Arial\", \"B\", 24)\n    pdf.Cell(40, 10, \"Hello, World!\")\n    pdf.OutputFileAndClose(\"hello.pdf\")\n}\n",[47,14867,14868,14874,14878,14889,14893,14903,14946,14956,14987,15013,15031],{"__ignoreMap":65},[69,14869,14870,14872],{"class":71,"line":72},[69,14871,76],{"class":75},[69,14873,80],{"class":79},[69,14875,14876],{"class":71,"line":83},[69,14877,87],{"emptyLinePlaceholder":86},[69,14879,14880,14882,14884,14887],{"class":71,"line":90},[69,14881,94],{"class":93},[69,14883,7452],{"class":75},[69,14885,14886],{"class":79},"github.com/jung-kurt/gofpdf",[69,14888,109],{"class":75},[69,14890,14891],{"class":71,"line":100},[69,14892,87],{"emptyLinePlaceholder":86},[69,14894,14895,14897,14899,14901],{"class":71,"line":112},[69,14896,171],{"class":75},[69,14898,175],{"class":174},[69,14900,178],{"class":75},[69,14902,181],{"class":75},[69,14904,14905,14907,14909,14912,14914,14916,14918,14920,14922,14924,14926,14928,14930,14932,14934,14936,14938,14940,14942,14944],{"class":71,"line":122},[69,14906,7428],{"class":187},[69,14908,197],{"class":75},[69,14910,14911],{"class":187}," gofpdf",[69,14913,203],{"class":75},[69,14915,7438],{"class":174},[69,14917,209],{"class":75},[69,14919,212],{"class":75},[69,14921,7445],{"class":215},[69,14923,212],{"class":75},[69,14925,191],{"class":75},[69,14927,7452],{"class":75},[69,14929,7455],{"class":215},[69,14931,212],{"class":75},[69,14933,191],{"class":75},[69,14935,7452],{"class":75},[69,14937,303],{"class":215},[69,14939,212],{"class":75},[69,14941,191],{"class":75},[69,14943,7470],{"class":75},[69,14945,160],{"class":75},[69,14947,14948,14950,14952,14954],{"class":71,"line":127},[69,14949,7477],{"class":187},[69,14951,203],{"class":75},[69,14953,422],{"class":174},[69,14955,425],{"class":75},[69,14957,14958,14960,14962,14964,14966,14968,14970,14972,14974,14976,14978,14980,14982,14985],{"class":71,"line":137},[69,14959,7477],{"class":187},[69,14961,203],{"class":75},[69,14963,1758],{"class":174},[69,14965,209],{"class":75},[69,14967,212],{"class":75},[69,14969,7498],{"class":215},[69,14971,212],{"class":75},[69,14973,191],{"class":75},[69,14975,7452],{"class":75},[69,14977,7507],{"class":215},[69,14979,212],{"class":75},[69,14981,191],{"class":75},[69,14983,14984],{"class":340}," 24",[69,14986,160],{"class":75},[69,14988,14989,14991,14993,14995,14997,14999,15001,15003,15005,15007,15009,15011],{"class":71,"line":147},[69,14990,7477],{"class":187},[69,14992,203],{"class":75},[69,14994,1933],{"class":174},[69,14996,209],{"class":75},[69,14998,7529],{"class":340},[69,15000,191],{"class":75},[69,15002,7534],{"class":340},[69,15004,191],{"class":75},[69,15006,7452],{"class":75},[69,15008,7541],{"class":215},[69,15010,212],{"class":75},[69,15012,160],{"class":75},[69,15014,15015,15017,15019,15021,15023,15025,15027,15029],{"class":71,"line":157},[69,15016,7477],{"class":187},[69,15018,203],{"class":75},[69,15020,1941],{"class":174},[69,15022,209],{"class":75},[69,15024,212],{"class":75},[69,15026,3464],{"class":215},[69,15028,212],{"class":75},[69,15030,160],{"class":75},[69,15032,15033],{"class":71,"line":163},[69,15034,707],{"class":75},[19,15036,15037],{},[30,15038,7658],{},[60,15040,15042],{"className":62,"code":15041,"language":64,"meta":65,"style":65},"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/template\"\n)\n\nfunc main() {\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(document.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"Hello, World!\", template.FontSize(24), template.Bold())\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"hello.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[47,15043,15044,15050,15054,15060,15068,15076,15080,15088,15096,15104,15108,15112,15122,15136,15154,15184,15188,15192,15206,15230,15260,15298,15302,15306,15310,15328,15340,15354,15358,15398,15412,15416],{"__ignoreMap":65},[69,15045,15046,15048],{"class":71,"line":72},[69,15047,76],{"class":75},[69,15049,80],{"class":79},[69,15051,15052],{"class":71,"line":83},[69,15053,87],{"emptyLinePlaceholder":86},[69,15055,15056,15058],{"class":71,"line":90},[69,15057,94],{"class":93},[69,15059,97],{"class":75},[69,15061,15062,15064,15066],{"class":71,"line":100},[69,15063,103],{"class":75},[69,15065,106],{"class":79},[69,15067,109],{"class":75},[69,15069,15070,15072,15074],{"class":71,"line":112},[69,15071,103],{"class":75},[69,15073,117],{"class":79},[69,15075,109],{"class":75},[69,15077,15078],{"class":71,"line":122},[69,15079,87],{"emptyLinePlaceholder":86},[69,15081,15082,15084,15086],{"class":71,"line":127},[69,15083,103],{"class":75},[69,15085,132],{"class":79},[69,15087,109],{"class":75},[69,15089,15090,15092,15094],{"class":71,"line":137},[69,15091,103],{"class":75},[69,15093,142],{"class":79},[69,15095,109],{"class":75},[69,15097,15098,15100,15102],{"class":71,"line":147},[69,15099,103],{"class":75},[69,15101,152],{"class":79},[69,15103,109],{"class":75},[69,15105,15106],{"class":71,"line":157},[69,15107,160],{"class":75},[69,15109,15110],{"class":71,"line":163},[69,15111,87],{"emptyLinePlaceholder":86},[69,15113,15114,15116,15118,15120],{"class":71,"line":168},[69,15115,171],{"class":75},[69,15117,175],{"class":174},[69,15119,178],{"class":75},[69,15121,181],{"class":75},[69,15123,15124,15126,15128,15130,15132,15134],{"class":71,"line":184},[69,15125,270],{"class":187},[69,15127,197],{"class":75},[69,15129,275],{"class":187},[69,15131,203],{"class":75},[69,15133,280],{"class":174},[69,15135,283],{"class":75},[69,15137,15138,15140,15142,15144,15146,15148,15150,15152],{"class":71,"line":222},[69,15139,289],{"class":187},[69,15141,203],{"class":75},[69,15143,294],{"class":174},[69,15145,209],{"class":75},[69,15147,321],{"class":187},[69,15149,203],{"class":75},[69,15151,303],{"class":187},[69,15153,306],{"class":75},[69,15155,15156,15158,15160,15162,15164,15166,15168,15170,15172,15174,15176,15178,15180,15182],{"class":71,"line":238},[69,15157,289],{"class":187},[69,15159,203],{"class":75},[69,15161,316],{"class":174},[69,15163,209],{"class":75},[69,15165,321],{"class":187},[69,15167,203],{"class":75},[69,15169,326],{"class":174},[69,15171,209],{"class":75},[69,15173,321],{"class":187},[69,15175,203],{"class":75},[69,15177,335],{"class":174},[69,15179,209],{"class":75},[69,15181,341],{"class":340},[69,15183,344],{"class":75},[69,15185,15186],{"class":71,"line":256},[69,15187,401],{"class":75},[69,15189,15190],{"class":71,"line":262},[69,15191,87],{"emptyLinePlaceholder":86},[69,15193,15194,15196,15198,15200,15202,15204],{"class":71,"line":267},[69,15195,412],{"class":187},[69,15197,197],{"class":75},[69,15199,417],{"class":187},[69,15201,203],{"class":75},[69,15203,422],{"class":174},[69,15205,425],{"class":75},[69,15207,15208,15210,15212,15214,15216,15218,15220,15222,15224,15226,15228],{"class":71,"line":286},[69,15209,431],{"class":187},[69,15211,203],{"class":75},[69,15213,436],{"class":174},[69,15215,439],{"class":75},[69,15217,443],{"class":442},[69,15219,446],{"class":75},[69,15221,449],{"class":79},[69,15223,203],{"class":75},[69,15225,454],{"class":79},[69,15227,457],{"class":75},[69,15229,181],{"class":75},[69,15231,15232,15234,15236,15238,15240,15242,15244,15246,15248,15250,15252,15254,15256,15258],{"class":71,"line":309},[69,15233,465],{"class":187},[69,15235,203],{"class":75},[69,15237,470],{"class":174},[69,15239,209],{"class":75},[69,15241,475],{"class":340},[69,15243,191],{"class":75},[69,15245,480],{"class":75},[69,15247,483],{"class":442},[69,15249,446],{"class":75},[69,15251,449],{"class":79},[69,15253,203],{"class":75},[69,15255,492],{"class":79},[69,15257,457],{"class":75},[69,15259,181],{"class":75},[69,15261,15262,15264,15266,15268,15270,15272,15274,15276,15278,15280,15282,15284,15286,15288,15290,15292,15294,15296],{"class":71,"line":347},[69,15263,502],{"class":187},[69,15265,203],{"class":75},[69,15267,507],{"class":174},[69,15269,209],{"class":75},[69,15271,212],{"class":75},[69,15273,7541],{"class":215},[69,15275,212],{"class":75},[69,15277,191],{"class":75},[69,15279,521],{"class":187},[69,15281,203],{"class":75},[69,15283,526],{"class":174},[69,15285,209],{"class":75},[69,15287,531],{"class":340},[69,15289,534],{"class":75},[69,15291,521],{"class":187},[69,15293,203],{"class":75},[69,15295,541],{"class":174},[69,15297,544],{"class":75},[69,15299,15300],{"class":71,"line":373},[69,15301,570],{"class":75},[69,15303,15304],{"class":71,"line":398},[69,15305,576],{"class":75},[69,15307,15308],{"class":71,"line":404},[69,15309,87],{"emptyLinePlaceholder":86},[69,15311,15312,15314,15316,15318,15320,15322,15324,15326],{"class":71,"line":409},[69,15313,587],{"class":187},[69,15315,191],{"class":75},[69,15317,194],{"class":187},[69,15319,197],{"class":75},[69,15321,417],{"class":187},[69,15323,203],{"class":75},[69,15325,600],{"class":174},[69,15327,425],{"class":75},[69,15329,15330,15332,15334,15336,15338],{"class":71,"line":428},[69,15331,225],{"class":93},[69,15333,194],{"class":187},[69,15335,230],{"class":75},[69,15337,233],{"class":75},[69,15339,181],{"class":75},[69,15341,15342,15344,15346,15348,15350,15352],{"class":71,"line":462},[69,15343,241],{"class":187},[69,15345,203],{"class":75},[69,15347,246],{"class":174},[69,15349,209],{"class":75},[69,15351,251],{"class":187},[69,15353,160],{"class":75},[69,15355,15356],{"class":71,"line":499},[69,15357,259],{"class":75},[69,15359,15360,15362,15364,15366,15368,15370,15372,15374,15376,15378,15380,15382,15384,15386,15388,15390,15392,15394,15396],{"class":71,"line":547},[69,15361,225],{"class":93},[69,15363,194],{"class":187},[69,15365,197],{"class":75},[69,15367,200],{"class":187},[69,15369,203],{"class":75},[69,15371,651],{"class":174},[69,15373,209],{"class":75},[69,15375,212],{"class":75},[69,15377,3464],{"class":215},[69,15379,212],{"class":75},[69,15381,191],{"class":75},[69,15383,665],{"class":187},[69,15385,191],{"class":75},[69,15387,670],{"class":340},[69,15389,673],{"class":75},[69,15391,194],{"class":187},[69,15393,230],{"class":75},[69,15395,233],{"class":75},[69,15397,181],{"class":75},[69,15399,15400,15402,15404,15406,15408,15410],{"class":71,"line":567},[69,15401,241],{"class":187},[69,15403,203],{"class":75},[69,15405,246],{"class":174},[69,15407,209],{"class":75},[69,15409,251],{"class":187},[69,15411,160],{"class":75},[69,15413,15414],{"class":71,"line":573},[69,15415,259],{"class":75},[69,15417,15418],{"class":71,"line":579},[69,15419,707],{"class":75},[19,15421,15422,15423,15425,15426,15429],{},"그리드가 일을 한다. ",[47,15424,436],{},"는 내용 높이로 결정되는 행을 추가하고 ",[47,15427,15428],{},"r.Col(12, ...)","는 \"12 그리드 전체를 차지하는 컬럼\"을 뜻한다. Bootstrap과 같은 발상을 PDF 페이지에 적용한 것뿐.",[19,15431,15432,15434,15435,15437,15438,15441],{},[47,15433,3940],{},"는 바이트 슬라이스를 돌려준다. ",[47,15436,1891],{},"로 흘려 보내고 싶다면 ",[47,15439,15440],{},"Render(w)",". \"파일을 닫는\" 단계가 없는 건 gpdf가 파일 핸들을 소유하지 않기 때문.",[14,15443,15445],{"id":15444},"before-after-2-명세표","Before / After 2: 명세표",[19,15447,15448,15449,15451,15452,15455],{},"세금계산서 명세표는 gofpdf가 가장 수다스러워지는 지점. 내장 테이블이 없어서 ",[47,15450,1933],{},"을 중첩 루프로 호출하고, 열 너비는 직접 계산하고, ",[47,15453,15454],{},"Ln(-1)","로 개행한다. gofpdf 세금계산서 튜토리얼 글의 절반은 테이블 보일러플레이트로 채워져 있다.",[19,15457,15458],{},[30,15459,14862],{},[60,15461,15463],{"className":62,"code":15462,"language":64,"meta":65,"style":65},"pdf.SetFont(\"Arial\", \"B\", 11)\npdf.SetFillColor(220, 220, 220)\npdf.CellFormat(80, 8, \"품목\",   \"1\", 0, \"L\", true, 0, \"\")\npdf.CellFormat(20, 8, \"수량\",   \"1\", 0, \"C\", true, 0, \"\")\npdf.CellFormat(30, 8, \"단가\",   \"1\", 0, \"R\", true, 0, \"\")\npdf.CellFormat(30, 8, \"금액\",   \"1\", 1, \"R\", true, 0, \"\")\n\npdf.SetFont(\"Arial\", \"\", 11)\nitems := [][]string{\n    {\"프론트엔드 개발\", \"40h\", \"₩180,000\", \"₩7,200,000\"},\n    {\"백엔드 개발\",    \"60h\", \"₩180,000\", \"₩10,800,000\"},\n    {\"UI 디자인\",      \"20h\", \"₩150,000\", \"₩3,000,000\"},\n}\nfor _, row := range items {\n    pdf.CellFormat(80, 8, row[0], \"1\", 0, \"L\", false, 0, \"\")\n    pdf.CellFormat(20, 8, row[1], \"1\", 0, \"C\", false, 0, \"\")\n    pdf.CellFormat(30, 8, row[2], \"1\", 0, \"R\", false, 0, \"\")\n    pdf.CellFormat(30, 8, row[3], \"1\", 1, \"R\", false, 0, \"\")\n}\n",[47,15464,15465,15495,15520,15587,15646,15705,15764,15768,15794,15808,15848,15886,15926,15930,15953,16014,16072,16131,16190],{"__ignoreMap":65},[69,15466,15467,15469,15471,15473,15475,15477,15479,15481,15483,15485,15487,15489,15491,15493],{"class":71,"line":72},[69,15468,2278],{"class":187},[69,15470,203],{"class":75},[69,15472,1758],{"class":174},[69,15474,209],{"class":75},[69,15476,212],{"class":75},[69,15478,7498],{"class":215},[69,15480,212],{"class":75},[69,15482,191],{"class":75},[69,15484,7452],{"class":75},[69,15486,7507],{"class":215},[69,15488,212],{"class":75},[69,15490,191],{"class":75},[69,15492,393],{"class":340},[69,15494,160],{"class":75},[69,15496,15497,15499,15501,15504,15506,15509,15511,15514,15516,15518],{"class":71,"line":83},[69,15498,2278],{"class":187},[69,15500,203],{"class":75},[69,15502,15503],{"class":174},"SetFillColor",[69,15505,209],{"class":75},[69,15507,15508],{"class":340},"220",[69,15510,191],{"class":75},[69,15512,15513],{"class":340}," 220",[69,15515,191],{"class":75},[69,15517,15513],{"class":340},[69,15519,160],{"class":75},[69,15521,15522,15524,15526,15528,15530,15533,15535,15538,15540,15542,15545,15547,15549,15552,15555,15557,15559,15562,15564,15566,15569,15571,15573,15577,15579,15581,15583,15585],{"class":71,"line":90},[69,15523,2278],{"class":187},[69,15525,203],{"class":75},[69,15527,2008],{"class":174},[69,15529,209],{"class":75},[69,15531,15532],{"class":340},"80",[69,15534,191],{"class":75},[69,15536,15537],{"class":340}," 8",[69,15539,191],{"class":75},[69,15541,7452],{"class":75},[69,15543,15544],{"class":215},"품목",[69,15546,212],{"class":75},[69,15548,191],{"class":75},[69,15550,15551],{"class":75},"   \"",[69,15553,15554],{"class":215},"1",[69,15556,212],{"class":75},[69,15558,191],{"class":75},[69,15560,15561],{"class":340}," 0",[69,15563,191],{"class":75},[69,15565,7452],{"class":75},[69,15567,15568],{"class":215},"L",[69,15570,212],{"class":75},[69,15572,191],{"class":75},[69,15574,15576],{"class":15575},"sfNiH"," true",[69,15578,191],{"class":75},[69,15580,15561],{"class":340},[69,15582,191],{"class":75},[69,15584,7470],{"class":75},[69,15586,160],{"class":75},[69,15588,15589,15591,15593,15595,15597,15599,15601,15603,15605,15607,15609,15611,15613,15615,15617,15619,15621,15623,15625,15627,15630,15632,15634,15636,15638,15640,15642,15644],{"class":71,"line":100},[69,15590,2278],{"class":187},[69,15592,203],{"class":75},[69,15594,2008],{"class":174},[69,15596,209],{"class":75},[69,15598,341],{"class":340},[69,15600,191],{"class":75},[69,15602,15537],{"class":340},[69,15604,191],{"class":75},[69,15606,7452],{"class":75},[69,15608,11158],{"class":215},[69,15610,212],{"class":75},[69,15612,191],{"class":75},[69,15614,15551],{"class":75},[69,15616,15554],{"class":215},[69,15618,212],{"class":75},[69,15620,191],{"class":75},[69,15622,15561],{"class":340},[69,15624,191],{"class":75},[69,15626,7452],{"class":75},[69,15628,15629],{"class":215},"C",[69,15631,212],{"class":75},[69,15633,191],{"class":75},[69,15635,15576],{"class":15575},[69,15637,191],{"class":75},[69,15639,15561],{"class":340},[69,15641,191],{"class":75},[69,15643,7470],{"class":75},[69,15645,160],{"class":75},[69,15647,15648,15650,15652,15654,15656,15658,15660,15662,15664,15666,15668,15670,15672,15674,15676,15678,15680,15682,15684,15686,15689,15691,15693,15695,15697,15699,15701,15703],{"class":71,"line":112},[69,15649,2278],{"class":187},[69,15651,203],{"class":75},[69,15653,2008],{"class":174},[69,15655,209],{"class":75},[69,15657,6404],{"class":340},[69,15659,191],{"class":75},[69,15661,15537],{"class":340},[69,15663,191],{"class":75},[69,15665,7452],{"class":75},[69,15667,11167],{"class":215},[69,15669,212],{"class":75},[69,15671,191],{"class":75},[69,15673,15551],{"class":75},[69,15675,15554],{"class":215},[69,15677,212],{"class":75},[69,15679,191],{"class":75},[69,15681,15561],{"class":340},[69,15683,191],{"class":75},[69,15685,7452],{"class":75},[69,15687,15688],{"class":215},"R",[69,15690,212],{"class":75},[69,15692,191],{"class":75},[69,15694,15576],{"class":15575},[69,15696,191],{"class":75},[69,15698,15561],{"class":340},[69,15700,191],{"class":75},[69,15702,7470],{"class":75},[69,15704,160],{"class":75},[69,15706,15707,15709,15711,15713,15715,15717,15719,15721,15723,15725,15727,15729,15731,15733,15735,15737,15739,15742,15744,15746,15748,15750,15752,15754,15756,15758,15760,15762],{"class":71,"line":122},[69,15708,2278],{"class":187},[69,15710,203],{"class":75},[69,15712,2008],{"class":174},[69,15714,209],{"class":75},[69,15716,6404],{"class":340},[69,15718,191],{"class":75},[69,15720,15537],{"class":340},[69,15722,191],{"class":75},[69,15724,7452],{"class":75},[69,15726,11176],{"class":215},[69,15728,212],{"class":75},[69,15730,191],{"class":75},[69,15732,15551],{"class":75},[69,15734,15554],{"class":215},[69,15736,212],{"class":75},[69,15738,191],{"class":75},[69,15740,15741],{"class":340}," 1",[69,15743,191],{"class":75},[69,15745,7452],{"class":75},[69,15747,15688],{"class":215},[69,15749,212],{"class":75},[69,15751,191],{"class":75},[69,15753,15576],{"class":15575},[69,15755,191],{"class":75},[69,15757,15561],{"class":340},[69,15759,191],{"class":75},[69,15761,7470],{"class":75},[69,15763,160],{"class":75},[69,15765,15766],{"class":71,"line":127},[69,15767,87],{"emptyLinePlaceholder":86},[69,15769,15770,15772,15774,15776,15778,15780,15782,15784,15786,15788,15790,15792],{"class":71,"line":137},[69,15771,2278],{"class":187},[69,15773,203],{"class":75},[69,15775,1758],{"class":174},[69,15777,209],{"class":75},[69,15779,212],{"class":75},[69,15781,7498],{"class":215},[69,15783,212],{"class":75},[69,15785,191],{"class":75},[69,15787,7470],{"class":75},[69,15789,191],{"class":75},[69,15791,393],{"class":340},[69,15793,160],{"class":75},[69,15795,15796,15799,15801,15804,15806],{"class":71,"line":147},[69,15797,15798],{"class":187},"items ",[69,15800,197],{"class":75},[69,15802,15803],{"class":75}," [][]",[69,15805,11141],{"class":1620},[69,15807,11191],{"class":75},[69,15809,15810,15813,15815,15817,15819,15821,15823,15826,15828,15830,15832,15835,15837,15839,15841,15844,15846],{"class":71,"line":157},[69,15811,15812],{"class":75},"    {",[69,15814,212],{"class":75},[69,15816,11201],{"class":215},[69,15818,212],{"class":75},[69,15820,191],{"class":75},[69,15822,7452],{"class":75},[69,15824,15825],{"class":215},"40h",[69,15827,212],{"class":75},[69,15829,191],{"class":75},[69,15831,7452],{"class":75},[69,15833,15834],{"class":215},"₩180,000",[69,15836,212],{"class":75},[69,15838,191],{"class":75},[69,15840,7452],{"class":75},[69,15842,15843],{"class":215},"₩7,200,000",[69,15845,212],{"class":75},[69,15847,11181],{"class":75},[69,15849,15850,15852,15854,15856,15858,15860,15862,15865,15867,15869,15871,15873,15875,15877,15879,15882,15884],{"class":71,"line":163},[69,15851,15812],{"class":75},[69,15853,212],{"class":75},[69,15855,11241],{"class":215},[69,15857,212],{"class":75},[69,15859,191],{"class":75},[69,15861,103],{"class":75},[69,15863,15864],{"class":215},"60h",[69,15866,212],{"class":75},[69,15868,191],{"class":75},[69,15870,7452],{"class":75},[69,15872,15834],{"class":215},[69,15874,212],{"class":75},[69,15876,191],{"class":75},[69,15878,7452],{"class":75},[69,15880,15881],{"class":215},"₩10,800,000",[69,15883,212],{"class":75},[69,15885,11181],{"class":75},[69,15887,15888,15890,15892,15894,15896,15898,15901,15904,15906,15908,15910,15913,15915,15917,15919,15922,15924],{"class":71,"line":168},[69,15889,15812],{"class":75},[69,15891,212],{"class":75},[69,15893,11281],{"class":215},[69,15895,212],{"class":75},[69,15897,191],{"class":75},[69,15899,15900],{"class":75},"      \"",[69,15902,15903],{"class":215},"20h",[69,15905,212],{"class":75},[69,15907,191],{"class":75},[69,15909,7452],{"class":75},[69,15911,15912],{"class":215},"₩150,000",[69,15914,212],{"class":75},[69,15916,191],{"class":75},[69,15918,7452],{"class":75},[69,15920,15921],{"class":215},"₩3,000,000",[69,15923,212],{"class":75},[69,15925,11181],{"class":75},[69,15927,15928],{"class":71,"line":184},[69,15929,707],{"class":75},[69,15931,15932,15935,15938,15940,15943,15945,15948,15951],{"class":71,"line":222},[69,15933,15934],{"class":93},"for",[69,15936,15937],{"class":187}," _",[69,15939,191],{"class":75},[69,15941,15942],{"class":187}," row ",[69,15944,197],{"class":75},[69,15946,15947],{"class":93}," range",[69,15949,15950],{"class":187}," items ",[69,15952,11191],{"class":75},[69,15954,15955,15957,15959,15961,15963,15965,15967,15969,15971,15974,15976,15978,15981,15983,15985,15987,15989,15991,15993,15995,15997,15999,16001,16004,16006,16008,16010,16012],{"class":71,"line":238},[69,15956,7477],{"class":187},[69,15958,203],{"class":75},[69,15960,2008],{"class":174},[69,15962,209],{"class":75},[69,15964,15532],{"class":340},[69,15966,191],{"class":75},[69,15968,15537],{"class":340},[69,15970,191],{"class":75},[69,15972,15973],{"class":187}," row",[69,15975,2187],{"class":75},[69,15977,10257],{"class":340},[69,15979,15980],{"class":75},"],",[69,15982,7452],{"class":75},[69,15984,15554],{"class":215},[69,15986,212],{"class":75},[69,15988,191],{"class":75},[69,15990,15561],{"class":340},[69,15992,191],{"class":75},[69,15994,7452],{"class":75},[69,15996,15568],{"class":215},[69,15998,212],{"class":75},[69,16000,191],{"class":75},[69,16002,16003],{"class":15575}," false",[69,16005,191],{"class":75},[69,16007,15561],{"class":340},[69,16009,191],{"class":75},[69,16011,7470],{"class":75},[69,16013,160],{"class":75},[69,16015,16016,16018,16020,16022,16024,16026,16028,16030,16032,16034,16036,16038,16040,16042,16044,16046,16048,16050,16052,16054,16056,16058,16060,16062,16064,16066,16068,16070],{"class":71,"line":256},[69,16017,7477],{"class":187},[69,16019,203],{"class":75},[69,16021,2008],{"class":174},[69,16023,209],{"class":75},[69,16025,341],{"class":340},[69,16027,191],{"class":75},[69,16029,15537],{"class":340},[69,16031,191],{"class":75},[69,16033,15973],{"class":187},[69,16035,2187],{"class":75},[69,16037,15554],{"class":340},[69,16039,15980],{"class":75},[69,16041,7452],{"class":75},[69,16043,15554],{"class":215},[69,16045,212],{"class":75},[69,16047,191],{"class":75},[69,16049,15561],{"class":340},[69,16051,191],{"class":75},[69,16053,7452],{"class":75},[69,16055,15629],{"class":215},[69,16057,212],{"class":75},[69,16059,191],{"class":75},[69,16061,16003],{"class":15575},[69,16063,191],{"class":75},[69,16065,15561],{"class":340},[69,16067,191],{"class":75},[69,16069,7470],{"class":75},[69,16071,160],{"class":75},[69,16073,16074,16076,16078,16080,16082,16084,16086,16088,16090,16092,16094,16097,16099,16101,16103,16105,16107,16109,16111,16113,16115,16117,16119,16121,16123,16125,16127,16129],{"class":71,"line":262},[69,16075,7477],{"class":187},[69,16077,203],{"class":75},[69,16079,2008],{"class":174},[69,16081,209],{"class":75},[69,16083,6404],{"class":340},[69,16085,191],{"class":75},[69,16087,15537],{"class":340},[69,16089,191],{"class":75},[69,16091,15973],{"class":187},[69,16093,2187],{"class":75},[69,16095,16096],{"class":340},"2",[69,16098,15980],{"class":75},[69,16100,7452],{"class":75},[69,16102,15554],{"class":215},[69,16104,212],{"class":75},[69,16106,191],{"class":75},[69,16108,15561],{"class":340},[69,16110,191],{"class":75},[69,16112,7452],{"class":75},[69,16114,15688],{"class":215},[69,16116,212],{"class":75},[69,16118,191],{"class":75},[69,16120,16003],{"class":15575},[69,16122,191],{"class":75},[69,16124,15561],{"class":340},[69,16126,191],{"class":75},[69,16128,7470],{"class":75},[69,16130,160],{"class":75},[69,16132,16133,16135,16137,16139,16141,16143,16145,16147,16149,16151,16153,16156,16158,16160,16162,16164,16166,16168,16170,16172,16174,16176,16178,16180,16182,16184,16186,16188],{"class":71,"line":267},[69,16134,7477],{"class":187},[69,16136,203],{"class":75},[69,16138,2008],{"class":174},[69,16140,209],{"class":75},[69,16142,6404],{"class":340},[69,16144,191],{"class":75},[69,16146,15537],{"class":340},[69,16148,191],{"class":75},[69,16150,15973],{"class":187},[69,16152,2187],{"class":75},[69,16154,16155],{"class":340},"3",[69,16157,15980],{"class":75},[69,16159,7452],{"class":75},[69,16161,15554],{"class":215},[69,16163,212],{"class":75},[69,16165,191],{"class":75},[69,16167,15741],{"class":340},[69,16169,191],{"class":75},[69,16171,7452],{"class":75},[69,16173,15688],{"class":215},[69,16175,212],{"class":75},[69,16177,191],{"class":75},[69,16179,16003],{"class":15575},[69,16181,191],{"class":75},[69,16183,15561],{"class":340},[69,16185,191],{"class":75},[69,16187,7470],{"class":75},[69,16189,160],{"class":75},[69,16191,16192],{"class":71,"line":286},[69,16193,707],{"class":75},[19,16195,16196],{},"머릿속에서 열 너비를 계산해 가며 써야 한다. 품목명이 줄바꿈되면 망가진다.",[19,16198,16199],{},[30,16200,7658],{},[60,16202,16204],{"className":62,"code":16203,"language":64,"meta":65,"style":65},"page.AutoRow(func(r *template.RowBuilder) {\n    r.Col(12, func(c *template.ColBuilder) {\n        c.Table(\n            []string{\"품목\", \"수량\", \"단가\", \"금액\"},\n            [][]string{\n                {\"프론트엔드 개발\", \"40h\", \"₩180,000\", \"₩7,200,000\"},\n                {\"백엔드 개발\",    \"60h\", \"₩180,000\", \"₩10,800,000\"},\n                {\"UI 디자인\",      \"20h\", \"₩150,000\", \"₩3,000,000\"},\n            },\n            template.ColumnWidths(50, 15, 15, 20),\n            template.TableHeaderStyle(\n                template.Bold(),\n                template.TextColor(pdf.White),\n                template.BgColor(pdf.RGBHex(0x1A237E)),\n            ),\n            template.TableStripe(pdf.RGBHex(0xF5F5F5)),\n        )\n    })\n})\n",[47,16205,16206,16230,16260,16270,16311,16320,16357,16393,16429,16434,16461,16471,16481,16499,16521,16526,16550,16555,16559],{"__ignoreMap":65},[69,16207,16208,16210,16212,16214,16216,16218,16220,16222,16224,16226,16228],{"class":71,"line":72},[69,16209,1926],{"class":187},[69,16211,203],{"class":75},[69,16213,436],{"class":174},[69,16215,439],{"class":75},[69,16217,443],{"class":442},[69,16219,446],{"class":75},[69,16221,449],{"class":79},[69,16223,203],{"class":75},[69,16225,454],{"class":79},[69,16227,457],{"class":75},[69,16229,181],{"class":75},[69,16231,16232,16234,16236,16238,16240,16242,16244,16246,16248,16250,16252,16254,16256,16258],{"class":71,"line":83},[69,16233,3672],{"class":187},[69,16235,203],{"class":75},[69,16237,470],{"class":174},[69,16239,209],{"class":75},[69,16241,475],{"class":340},[69,16243,191],{"class":75},[69,16245,480],{"class":75},[69,16247,483],{"class":442},[69,16249,446],{"class":75},[69,16251,449],{"class":79},[69,16253,203],{"class":75},[69,16255,492],{"class":79},[69,16257,457],{"class":75},[69,16259,181],{"class":75},[69,16261,16262,16264,16266,16268],{"class":71,"line":90},[69,16263,3703],{"class":187},[69,16265,203],{"class":75},[69,16267,11131],{"class":174},[69,16269,283],{"class":75},[69,16271,16272,16275,16277,16279,16281,16283,16285,16287,16289,16291,16293,16295,16297,16299,16301,16303,16305,16307,16309],{"class":71,"line":100},[69,16273,16274],{"class":75},"            []",[69,16276,11141],{"class":1620},[69,16278,11144],{"class":75},[69,16280,212],{"class":75},[69,16282,15544],{"class":215},[69,16284,212],{"class":75},[69,16286,191],{"class":75},[69,16288,7452],{"class":75},[69,16290,11158],{"class":215},[69,16292,212],{"class":75},[69,16294,191],{"class":75},[69,16296,7452],{"class":75},[69,16298,11167],{"class":215},[69,16300,212],{"class":75},[69,16302,191],{"class":75},[69,16304,7452],{"class":75},[69,16306,11176],{"class":215},[69,16308,212],{"class":75},[69,16310,11181],{"class":75},[69,16312,16313,16316,16318],{"class":71,"line":112},[69,16314,16315],{"class":75},"            [][]",[69,16317,11141],{"class":1620},[69,16319,11191],{"class":75},[69,16321,16322,16325,16327,16329,16331,16333,16335,16337,16339,16341,16343,16345,16347,16349,16351,16353,16355],{"class":71,"line":122},[69,16323,16324],{"class":75},"                {",[69,16326,212],{"class":75},[69,16328,11201],{"class":215},[69,16330,212],{"class":75},[69,16332,191],{"class":75},[69,16334,7452],{"class":75},[69,16336,15825],{"class":215},[69,16338,212],{"class":75},[69,16340,191],{"class":75},[69,16342,7452],{"class":75},[69,16344,15834],{"class":215},[69,16346,212],{"class":75},[69,16348,191],{"class":75},[69,16350,7452],{"class":75},[69,16352,15843],{"class":215},[69,16354,212],{"class":75},[69,16356,11181],{"class":75},[69,16358,16359,16361,16363,16365,16367,16369,16371,16373,16375,16377,16379,16381,16383,16385,16387,16389,16391],{"class":71,"line":127},[69,16360,16324],{"class":75},[69,16362,212],{"class":75},[69,16364,11241],{"class":215},[69,16366,212],{"class":75},[69,16368,191],{"class":75},[69,16370,103],{"class":75},[69,16372,15864],{"class":215},[69,16374,212],{"class":75},[69,16376,191],{"class":75},[69,16378,7452],{"class":75},[69,16380,15834],{"class":215},[69,16382,212],{"class":75},[69,16384,191],{"class":75},[69,16386,7452],{"class":75},[69,16388,15881],{"class":215},[69,16390,212],{"class":75},[69,16392,11181],{"class":75},[69,16394,16395,16397,16399,16401,16403,16405,16407,16409,16411,16413,16415,16417,16419,16421,16423,16425,16427],{"class":71,"line":137},[69,16396,16324],{"class":75},[69,16398,212],{"class":75},[69,16400,11281],{"class":215},[69,16402,212],{"class":75},[69,16404,191],{"class":75},[69,16406,15900],{"class":75},[69,16408,15903],{"class":215},[69,16410,212],{"class":75},[69,16412,191],{"class":75},[69,16414,7452],{"class":75},[69,16416,15912],{"class":215},[69,16418,212],{"class":75},[69,16420,191],{"class":75},[69,16422,7452],{"class":75},[69,16424,15921],{"class":215},[69,16426,212],{"class":75},[69,16428,11181],{"class":75},[69,16430,16431],{"class":71,"line":147},[69,16432,16433],{"class":75},"            },\n",[69,16435,16436,16439,16441,16443,16445,16447,16449,16451,16453,16455,16457,16459],{"class":71,"line":157},[69,16437,16438],{"class":187},"            template",[69,16440,203],{"class":75},[69,16442,11328],{"class":174},[69,16444,209],{"class":75},[69,16446,11333],{"class":340},[69,16448,191],{"class":75},[69,16450,11338],{"class":340},[69,16452,191],{"class":75},[69,16454,11338],{"class":340},[69,16456,191],{"class":75},[69,16458,11347],{"class":340},[69,16460,306],{"class":75},[69,16462,16463,16465,16467,16469],{"class":71,"line":163},[69,16464,16438],{"class":187},[69,16466,203],{"class":75},[69,16468,11358],{"class":174},[69,16470,283],{"class":75},[69,16472,16473,16475,16477,16479],{"class":71,"line":168},[69,16474,11323],{"class":187},[69,16476,203],{"class":75},[69,16478,541],{"class":174},[69,16480,11372],{"class":75},[69,16482,16483,16485,16487,16489,16491,16493,16495,16497],{"class":71,"line":184},[69,16484,11323],{"class":187},[69,16486,203],{"class":75},[69,16488,11381],{"class":174},[69,16490,209],{"class":75},[69,16492,2278],{"class":187},[69,16494,203],{"class":75},[69,16496,11390],{"class":187},[69,16498,306],{"class":75},[69,16500,16501,16503,16505,16507,16509,16511,16513,16515,16517,16519],{"class":71,"line":222},[69,16502,11323],{"class":187},[69,16504,203],{"class":75},[69,16506,11401],{"class":174},[69,16508,209],{"class":75},[69,16510,2278],{"class":187},[69,16512,203],{"class":75},[69,16514,11410],{"class":174},[69,16516,209],{"class":75},[69,16518,11415],{"class":340},[69,16520,11418],{"class":75},[69,16522,16523],{"class":71,"line":238},[69,16524,16525],{"class":75},"            ),\n",[69,16527,16528,16530,16532,16535,16537,16539,16541,16543,16545,16548],{"class":71,"line":256},[69,16529,16438],{"class":187},[69,16531,203],{"class":75},[69,16533,16534],{"class":174},"TableStripe",[69,16536,209],{"class":75},[69,16538,2278],{"class":187},[69,16540,203],{"class":75},[69,16542,11410],{"class":174},[69,16544,209],{"class":75},[69,16546,16547],{"class":340},"0xF5F5F5",[69,16549,11418],{"class":75},[69,16551,16552],{"class":71,"line":262},[69,16553,16554],{"class":75},"        )\n",[69,16556,16557],{"class":71,"line":267},[69,16558,576],{"class":75},[69,16560,16561],{"class":71,"line":286},[69,16562,3730],{"class":75},[19,16564,16565,16568,16569,16572,16573,16576,16577,16579],{},[47,16566,16567],{},"ColumnWidths(50, 15, 15, 20)","의 숫자는 ",[30,16570,16571],{},"절대 mm가 아니라, 표가 올라가는 컬럼 내의 비율",". 같은 표를 ",[47,16574,16575],{},"r.Col(6, ...)"," 안에 넣어도 이 비율이 그대로 잘 맞는다. ",[47,16578,2008],{},"을 래핑하지 않고는 닿을 수 없던 추상.",[19,16581,16582],{},"줄바꿈은 자동. 페이지 분리도 자동 — 표가 하단 여백을 넘으면 다음 페이지에 헤더 행이 다시 그려진다.",[14,16584,16586],{"id":16585},"before-after-3-거추장스러운-절차-없이-한국어-쓰기","Before / After 3: 거추장스러운 절차 없이 한국어 쓰기",[19,16588,16589,16590,16592],{},"gofpdf을 포기한 결정적 이유가 여기. gofpdf에서 한국어를 출력하려면 ",[47,16591,2574],{},"를 호출하고, 디스크의 TTF를 가리키고, 폰트를 설정하고, 기도한다. 서브셋팅은 대부분 동작한다. 일부 TTF는 글리프 ID 충돌을 일으켜 깨진 문자를 낸다. 에러 메시지는 도움이 안 된다.",[19,16594,16595],{},[30,16596,14862],{},[60,16598,16600],{"className":62,"code":16599,"language":64,"meta":65,"style":65},"pdf := gofpdf.New(\"P\", \"mm\", \"A4\", \"\")\npdf.AddUTF8Font(\"notosanskr\", \"\", \"NotoSansKR-Regular.ttf\")\npdf.AddPage()\npdf.SetFont(\"notosanskr\", \"\", 14)\npdf.Cell(0, 10, \"안녕하세요, 세계.\")\npdf.OutputFileAndClose(\"ko.pdf\")\n",[47,16601,16602,16645,16676,16686,16713,16740],{"__ignoreMap":65},[69,16603,16604,16607,16609,16611,16613,16615,16617,16619,16621,16623,16625,16627,16629,16631,16633,16635,16637,16639,16641,16643],{"class":71,"line":72},[69,16605,16606],{"class":187},"pdf ",[69,16608,197],{"class":75},[69,16610,14911],{"class":187},[69,16612,203],{"class":75},[69,16614,7438],{"class":174},[69,16616,209],{"class":75},[69,16618,212],{"class":75},[69,16620,7445],{"class":215},[69,16622,212],{"class":75},[69,16624,191],{"class":75},[69,16626,7452],{"class":75},[69,16628,7455],{"class":215},[69,16630,212],{"class":75},[69,16632,191],{"class":75},[69,16634,7452],{"class":75},[69,16636,303],{"class":215},[69,16638,212],{"class":75},[69,16640,191],{"class":75},[69,16642,7470],{"class":75},[69,16644,160],{"class":75},[69,16646,16647,16649,16651,16653,16655,16657,16660,16662,16664,16666,16668,16670,16672,16674],{"class":71,"line":83},[69,16648,2278],{"class":187},[69,16650,203],{"class":75},[69,16652,2574],{"class":174},[69,16654,209],{"class":75},[69,16656,212],{"class":75},[69,16658,16659],{"class":215},"notosanskr",[69,16661,212],{"class":75},[69,16663,191],{"class":75},[69,16665,7470],{"class":75},[69,16667,191],{"class":75},[69,16669,7452],{"class":75},[69,16671,12584],{"class":215},[69,16673,212],{"class":75},[69,16675,160],{"class":75},[69,16677,16678,16680,16682,16684],{"class":71,"line":90},[69,16679,2278],{"class":187},[69,16681,203],{"class":75},[69,16683,422],{"class":174},[69,16685,425],{"class":75},[69,16687,16688,16690,16692,16694,16696,16698,16700,16702,16704,16706,16708,16711],{"class":71,"line":100},[69,16689,2278],{"class":187},[69,16691,203],{"class":75},[69,16693,1758],{"class":174},[69,16695,209],{"class":75},[69,16697,212],{"class":75},[69,16699,16659],{"class":215},[69,16701,212],{"class":75},[69,16703,191],{"class":75},[69,16705,7470],{"class":75},[69,16707,191],{"class":75},[69,16709,16710],{"class":340}," 14",[69,16712,160],{"class":75},[69,16714,16715,16717,16719,16721,16723,16725,16727,16729,16731,16733,16736,16738],{"class":71,"line":112},[69,16716,2278],{"class":187},[69,16718,203],{"class":75},[69,16720,1933],{"class":174},[69,16722,209],{"class":75},[69,16724,10257],{"class":340},[69,16726,191],{"class":75},[69,16728,7534],{"class":340},[69,16730,191],{"class":75},[69,16732,7452],{"class":75},[69,16734,16735],{"class":215},"안녕하세요, 세계.",[69,16737,212],{"class":75},[69,16739,160],{"class":75},[69,16741,16742,16744,16746,16748,16750,16752,16755,16757],{"class":71,"line":122},[69,16743,2278],{"class":187},[69,16745,203],{"class":75},[69,16747,1941],{"class":174},[69,16749,209],{"class":75},[69,16751,212],{"class":75},[69,16753,16754],{"class":215},"ko.pdf",[69,16756,212],{"class":75},[69,16758,160],{"class":75},[19,16760,16761,16762,16765,16766,16768,16769,16771],{},"지뢰 두 개. TTF가 ",[30,16763,16764],{},"런타임에"," 지정 경로에 존재해야 하고, 그래서 Docker 이미지에 폰트를 함께 담아야 한다. ",[47,16767,1933],{}," 너비를 ",[47,16770,10257],{},"으로 주면 \"오른쪽 여백까지\"를 뜻하는데, CJK에서는 폭 추정이 전각 문자를 제대로 계산하지 못해 잘리는 일이 잦다.",[19,16773,16774],{},[30,16775,7658],{},[60,16777,16779],{"className":62,"code":16778,"language":64,"meta":65,"style":65},"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/template\"\n)\n\nfunc main() {\n    fontData, err := os.ReadFile(\"NotoSansKR-Regular.ttf\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(document.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n        gpdf.WithFont(\"NotoSansKR\", fontData),\n        gpdf.WithDefaultFont(\"NotoSansKR\", 14),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"안녕하세요, 세계.\")\n            c.Text(\"대한민국 서울특별시 강남구 테헤란로 427\")\n            c.Text(\"천 리 길도 한 걸음부터.\")\n        })\n    })\n\n    data, _ := doc.Generate()\n    os.WriteFile(\"ko.pdf\", data, 0o644)\n}\n",[47,16780,16781,16787,16791,16797,16805,16813,16817,16825,16833,16841,16845,16849,16859,16886,16898,16912,16916,16920,16934,16952,16982,17005,17027,17031,17035,17049,17073,17103,17121,17140,17159,17163,17167,17171,17189,17216],{"__ignoreMap":65},[69,16782,16783,16785],{"class":71,"line":72},[69,16784,76],{"class":75},[69,16786,80],{"class":79},[69,16788,16789],{"class":71,"line":83},[69,16790,87],{"emptyLinePlaceholder":86},[69,16792,16793,16795],{"class":71,"line":90},[69,16794,94],{"class":93},[69,16796,97],{"class":75},[69,16798,16799,16801,16803],{"class":71,"line":100},[69,16800,103],{"class":75},[69,16802,106],{"class":79},[69,16804,109],{"class":75},[69,16806,16807,16809,16811],{"class":71,"line":112},[69,16808,103],{"class":75},[69,16810,117],{"class":79},[69,16812,109],{"class":75},[69,16814,16815],{"class":71,"line":122},[69,16816,87],{"emptyLinePlaceholder":86},[69,16818,16819,16821,16823],{"class":71,"line":127},[69,16820,103],{"class":75},[69,16822,132],{"class":79},[69,16824,109],{"class":75},[69,16826,16827,16829,16831],{"class":71,"line":137},[69,16828,103],{"class":75},[69,16830,142],{"class":79},[69,16832,109],{"class":75},[69,16834,16835,16837,16839],{"class":71,"line":147},[69,16836,103],{"class":75},[69,16838,152],{"class":79},[69,16840,109],{"class":75},[69,16842,16843],{"class":71,"line":157},[69,16844,160],{"class":75},[69,16846,16847],{"class":71,"line":163},[69,16848,87],{"emptyLinePlaceholder":86},[69,16850,16851,16853,16855,16857],{"class":71,"line":168},[69,16852,171],{"class":75},[69,16854,175],{"class":174},[69,16856,178],{"class":75},[69,16858,181],{"class":75},[69,16860,16861,16864,16866,16868,16870,16872,16874,16876,16878,16880,16882,16884],{"class":71,"line":184},[69,16862,16863],{"class":187},"    fontData",[69,16865,191],{"class":75},[69,16867,194],{"class":187},[69,16869,197],{"class":75},[69,16871,200],{"class":187},[69,16873,203],{"class":75},[69,16875,206],{"class":174},[69,16877,209],{"class":75},[69,16879,212],{"class":75},[69,16881,12584],{"class":215},[69,16883,212],{"class":75},[69,16885,160],{"class":75},[69,16887,16888,16890,16892,16894,16896],{"class":71,"line":222},[69,16889,225],{"class":93},[69,16891,194],{"class":187},[69,16893,230],{"class":75},[69,16895,233],{"class":75},[69,16897,181],{"class":75},[69,16899,16900,16902,16904,16906,16908,16910],{"class":71,"line":238},[69,16901,241],{"class":187},[69,16903,203],{"class":75},[69,16905,246],{"class":174},[69,16907,209],{"class":75},[69,16909,251],{"class":187},[69,16911,160],{"class":75},[69,16913,16914],{"class":71,"line":256},[69,16915,259],{"class":75},[69,16917,16918],{"class":71,"line":262},[69,16919,87],{"emptyLinePlaceholder":86},[69,16921,16922,16924,16926,16928,16930,16932],{"class":71,"line":267},[69,16923,270],{"class":187},[69,16925,197],{"class":75},[69,16927,275],{"class":187},[69,16929,203],{"class":75},[69,16931,280],{"class":174},[69,16933,283],{"class":75},[69,16935,16936,16938,16940,16942,16944,16946,16948,16950],{"class":71,"line":286},[69,16937,289],{"class":187},[69,16939,203],{"class":75},[69,16941,294],{"class":174},[69,16943,209],{"class":75},[69,16945,321],{"class":187},[69,16947,203],{"class":75},[69,16949,303],{"class":187},[69,16951,306],{"class":75},[69,16953,16954,16956,16958,16960,16962,16964,16966,16968,16970,16972,16974,16976,16978,16980],{"class":71,"line":309},[69,16955,289],{"class":187},[69,16957,203],{"class":75},[69,16959,316],{"class":174},[69,16961,209],{"class":75},[69,16963,321],{"class":187},[69,16965,203],{"class":75},[69,16967,326],{"class":174},[69,16969,209],{"class":75},[69,16971,321],{"class":187},[69,16973,203],{"class":75},[69,16975,335],{"class":174},[69,16977,209],{"class":75},[69,16979,341],{"class":340},[69,16981,344],{"class":75},[69,16983,16984,16986,16988,16990,16992,16994,16996,16998,17000,17003],{"class":71,"line":347},[69,16985,289],{"class":187},[69,16987,203],{"class":75},[69,16989,354],{"class":174},[69,16991,209],{"class":75},[69,16993,212],{"class":75},[69,16995,12668],{"class":215},[69,16997,212],{"class":75},[69,16999,191],{"class":75},[69,17001,17002],{"class":187}," fontData",[69,17004,306],{"class":75},[69,17006,17007,17009,17011,17013,17015,17017,17019,17021,17023,17025],{"class":71,"line":373},[69,17008,289],{"class":187},[69,17010,203],{"class":75},[69,17012,380],{"class":174},[69,17014,209],{"class":75},[69,17016,212],{"class":75},[69,17018,12668],{"class":215},[69,17020,212],{"class":75},[69,17022,191],{"class":75},[69,17024,16710],{"class":340},[69,17026,306],{"class":75},[69,17028,17029],{"class":71,"line":398},[69,17030,401],{"class":75},[69,17032,17033],{"class":71,"line":404},[69,17034,87],{"emptyLinePlaceholder":86},[69,17036,17037,17039,17041,17043,17045,17047],{"class":71,"line":409},[69,17038,412],{"class":187},[69,17040,197],{"class":75},[69,17042,417],{"class":187},[69,17044,203],{"class":75},[69,17046,422],{"class":174},[69,17048,425],{"class":75},[69,17050,17051,17053,17055,17057,17059,17061,17063,17065,17067,17069,17071],{"class":71,"line":428},[69,17052,431],{"class":187},[69,17054,203],{"class":75},[69,17056,436],{"class":174},[69,17058,439],{"class":75},[69,17060,443],{"class":442},[69,17062,446],{"class":75},[69,17064,449],{"class":79},[69,17066,203],{"class":75},[69,17068,454],{"class":79},[69,17070,457],{"class":75},[69,17072,181],{"class":75},[69,17074,17075,17077,17079,17081,17083,17085,17087,17089,17091,17093,17095,17097,17099,17101],{"class":71,"line":462},[69,17076,465],{"class":187},[69,17078,203],{"class":75},[69,17080,470],{"class":174},[69,17082,209],{"class":75},[69,17084,475],{"class":340},[69,17086,191],{"class":75},[69,17088,480],{"class":75},[69,17090,483],{"class":442},[69,17092,446],{"class":75},[69,17094,449],{"class":79},[69,17096,203],{"class":75},[69,17098,492],{"class":79},[69,17100,457],{"class":75},[69,17102,181],{"class":75},[69,17104,17105,17107,17109,17111,17113,17115,17117,17119],{"class":71,"line":499},[69,17106,502],{"class":187},[69,17108,203],{"class":75},[69,17110,507],{"class":174},[69,17112,209],{"class":75},[69,17114,212],{"class":75},[69,17116,16735],{"class":215},[69,17118,212],{"class":75},[69,17120,160],{"class":75},[69,17122,17123,17125,17127,17129,17131,17133,17136,17138],{"class":71,"line":547},[69,17124,502],{"class":187},[69,17126,203],{"class":75},[69,17128,507],{"class":174},[69,17130,209],{"class":75},[69,17132,212],{"class":75},[69,17134,17135],{"class":215},"대한민국 서울특별시 강남구 테헤란로 427",[69,17137,212],{"class":75},[69,17139,160],{"class":75},[69,17141,17142,17144,17146,17148,17150,17152,17155,17157],{"class":71,"line":567},[69,17143,502],{"class":187},[69,17145,203],{"class":75},[69,17147,507],{"class":174},[69,17149,209],{"class":75},[69,17151,212],{"class":75},[69,17153,17154],{"class":215},"천 리 길도 한 걸음부터.",[69,17156,212],{"class":75},[69,17158,160],{"class":75},[69,17160,17161],{"class":71,"line":573},[69,17162,570],{"class":75},[69,17164,17165],{"class":71,"line":579},[69,17166,576],{"class":75},[69,17168,17169],{"class":71,"line":584},[69,17170,87],{"emptyLinePlaceholder":86},[69,17172,17173,17175,17177,17179,17181,17183,17185,17187],{"class":71,"line":605},[69,17174,587],{"class":187},[69,17176,191],{"class":75},[69,17178,976],{"class":187},[69,17180,197],{"class":75},[69,17182,417],{"class":187},[69,17184,203],{"class":75},[69,17186,600],{"class":174},[69,17188,425],{"class":75},[69,17190,17191,17194,17196,17198,17200,17202,17204,17206,17208,17210,17212,17214],{"class":71,"line":618},[69,17192,17193],{"class":187},"    os",[69,17195,203],{"class":75},[69,17197,651],{"class":174},[69,17199,209],{"class":75},[69,17201,212],{"class":75},[69,17203,16754],{"class":215},[69,17205,212],{"class":75},[69,17207,191],{"class":75},[69,17209,665],{"class":187},[69,17211,191],{"class":75},[69,17213,670],{"class":340},[69,17215,160],{"class":75},[69,17217,17218],{"class":71,"line":633},[69,17219,707],{"class":75},[19,17221,17222],{},"다른 점이 둘.",[19,17224,17225,17226,2125,17229,17232],{},"첫째, ",[30,17227,17228],{},"경로가 아니라 바이트를 넘긴다",[47,17230,17231],{},"//go:embed NotoSansKR-Regular.ttf","로 TTF를 바이너리에 포함시키면 배포가 자기완결적이 된다. 프로덕션에서 \"폰트를 찾을 수 없음\"이 발생하지 않는다.",[19,17234,17235],{},"둘째, gpdf의 TrueType 서브셋팅은 CJK cmap 형식(4, 6, 12)과 Identity-H 인코딩을 이해한다. 출력 PDF에는 실제로 사용한 글리프만 들어간다 — NotoSansKR을 200자짜리 세금계산서에 넣으면 약 30 KB 서브셋이 된다. 4 MB 풀 임베드가 아니다. gofpdf로 한국어 한 페이지 PDF가 5 MB가 되는 걸 본 적이 있다면 가장 먼저 느끼는 차이가 이것.",[19,17237,17238],{},"나눔스퀘어, Pretendard, 본고딕 등 한국어 폰트별 심화 주제는 별도 글에서 다룰 예정.",[14,17240,17242],{"id":17241},"before-after-4-모든-페이지-헤더-푸터-페이지-번호","Before / After 4: 모든 페이지 헤더 + 푸터 페이지 번호",[19,17244,17245,17246,867,17249,17252,17253,17256,17257,1157,17259,203],{},"gofpdf의 반복 크롬 패턴은 ",[47,17247,17248],{},"SetHeaderFunc",[47,17250,17251],{},"SetFooterFunc",". 각각 현재 커서에 대해 실행되는 ",[47,17254,17255],{},"func()","를 받는다. 페이지 번호는 ",[47,17258,14770],{},[47,17260,17261],{},"pdf.AliasNbPages()",[19,17263,17264],{},[30,17265,14862],{},[60,17267,17269],{"className":62,"code":17268,"language":64,"meta":65,"style":65},"pdf := gofpdf.New(\"P\", \"mm\", \"A4\", \"\")\npdf.SetHeaderFunc(func() {\n    pdf.SetFont(\"Arial\", \"B\", 12)\n    pdf.Cell(0, 10, \"ACME 주식회사\")\n    pdf.Ln(15)\n})\npdf.SetFooterFunc(func() {\n    pdf.SetY(-15)\n    pdf.SetFont(\"Arial\", \"I\", 8)\n    pdf.CellFormat(0, 10,\n        fmt.Sprintf(\"Page %d/{nb}\", pdf.PageNo()),\n        \"\", 0, \"C\", false, 0, \"\")\n})\npdf.AliasNbPages(\"\")\npdf.AddPage()\n// ... body ...\n",[47,17270,17271,17313,17326,17356,17383,17398,17402,17414,17429,17460,17478,17515,17546,17550,17566,17576],{"__ignoreMap":65},[69,17272,17273,17275,17277,17279,17281,17283,17285,17287,17289,17291,17293,17295,17297,17299,17301,17303,17305,17307,17309,17311],{"class":71,"line":72},[69,17274,16606],{"class":187},[69,17276,197],{"class":75},[69,17278,14911],{"class":187},[69,17280,203],{"class":75},[69,17282,7438],{"class":174},[69,17284,209],{"class":75},[69,17286,212],{"class":75},[69,17288,7445],{"class":215},[69,17290,212],{"class":75},[69,17292,191],{"class":75},[69,17294,7452],{"class":75},[69,17296,7455],{"class":215},[69,17298,212],{"class":75},[69,17300,191],{"class":75},[69,17302,7452],{"class":75},[69,17304,303],{"class":215},[69,17306,212],{"class":75},[69,17308,191],{"class":75},[69,17310,7470],{"class":75},[69,17312,160],{"class":75},[69,17314,17315,17317,17319,17321,17324],{"class":71,"line":83},[69,17316,2278],{"class":187},[69,17318,203],{"class":75},[69,17320,17248],{"class":174},[69,17322,17323],{"class":75},"(func()",[69,17325,181],{"class":75},[69,17327,17328,17330,17332,17334,17336,17338,17340,17342,17344,17346,17348,17350,17352,17354],{"class":71,"line":90},[69,17329,7477],{"class":187},[69,17331,203],{"class":75},[69,17333,1758],{"class":174},[69,17335,209],{"class":75},[69,17337,212],{"class":75},[69,17339,7498],{"class":215},[69,17341,212],{"class":75},[69,17343,191],{"class":75},[69,17345,7452],{"class":75},[69,17347,7507],{"class":215},[69,17349,212],{"class":75},[69,17351,191],{"class":75},[69,17353,3286],{"class":340},[69,17355,160],{"class":75},[69,17357,17358,17360,17362,17364,17366,17368,17370,17372,17374,17376,17379,17381],{"class":71,"line":100},[69,17359,7477],{"class":187},[69,17361,203],{"class":75},[69,17363,1933],{"class":174},[69,17365,209],{"class":75},[69,17367,10257],{"class":340},[69,17369,191],{"class":75},[69,17371,7534],{"class":340},[69,17373,191],{"class":75},[69,17375,7452],{"class":75},[69,17377,17378],{"class":215},"ACME 주식회사",[69,17380,212],{"class":75},[69,17382,160],{"class":75},[69,17384,17385,17387,17389,17392,17394,17396],{"class":71,"line":112},[69,17386,7477],{"class":187},[69,17388,203],{"class":75},[69,17390,17391],{"class":174},"Ln",[69,17393,209],{"class":75},[69,17395,5475],{"class":340},[69,17397,160],{"class":75},[69,17399,17400],{"class":71,"line":122},[69,17401,3730],{"class":75},[69,17403,17404,17406,17408,17410,17412],{"class":71,"line":127},[69,17405,2278],{"class":187},[69,17407,203],{"class":75},[69,17409,17251],{"class":174},[69,17411,17323],{"class":75},[69,17413,181],{"class":75},[69,17415,17416,17418,17420,17422,17425,17427],{"class":71,"line":137},[69,17417,7477],{"class":187},[69,17419,203],{"class":75},[69,17421,8314],{"class":174},[69,17423,17424],{"class":75},"(-",[69,17426,5475],{"class":340},[69,17428,160],{"class":75},[69,17430,17431,17433,17435,17437,17439,17441,17443,17445,17447,17449,17452,17454,17456,17458],{"class":71,"line":147},[69,17432,7477],{"class":187},[69,17434,203],{"class":75},[69,17436,1758],{"class":174},[69,17438,209],{"class":75},[69,17440,212],{"class":75},[69,17442,7498],{"class":215},[69,17444,212],{"class":75},[69,17446,191],{"class":75},[69,17448,7452],{"class":75},[69,17450,17451],{"class":215},"I",[69,17453,212],{"class":75},[69,17455,191],{"class":75},[69,17457,15537],{"class":340},[69,17459,160],{"class":75},[69,17461,17462,17464,17466,17468,17470,17472,17474,17476],{"class":71,"line":157},[69,17463,7477],{"class":187},[69,17465,203],{"class":75},[69,17467,2008],{"class":174},[69,17469,209],{"class":75},[69,17471,10257],{"class":340},[69,17473,191],{"class":75},[69,17475,7534],{"class":340},[69,17477,2059],{"class":75},[69,17479,17480,17483,17485,17488,17490,17492,17495,17498,17501,17503,17505,17507,17509,17512],{"class":71,"line":163},[69,17481,17482],{"class":187},"        fmt",[69,17484,203],{"class":75},[69,17486,17487],{"class":174},"Sprintf",[69,17489,209],{"class":75},[69,17491,212],{"class":75},[69,17493,17494],{"class":215},"Page ",[69,17496,17497],{"class":3853},"%d",[69,17499,17500],{"class":215},"/{nb}",[69,17502,212],{"class":75},[69,17504,191],{"class":75},[69,17506,7596],{"class":187},[69,17508,203],{"class":75},[69,17510,17511],{"class":174},"PageNo",[69,17513,17514],{"class":75},"()),\n",[69,17516,17517,17520,17522,17524,17526,17528,17530,17532,17534,17536,17538,17540,17542,17544],{"class":71,"line":168},[69,17518,17519],{"class":75},"        \"\"",[69,17521,191],{"class":75},[69,17523,15561],{"class":340},[69,17525,191],{"class":75},[69,17527,7452],{"class":75},[69,17529,15629],{"class":215},[69,17531,212],{"class":75},[69,17533,191],{"class":75},[69,17535,16003],{"class":15575},[69,17537,191],{"class":75},[69,17539,15561],{"class":340},[69,17541,191],{"class":75},[69,17543,7470],{"class":75},[69,17545,160],{"class":75},[69,17547,17548],{"class":71,"line":184},[69,17549,3730],{"class":75},[69,17551,17552,17554,17556,17559,17561,17564],{"class":71,"line":222},[69,17553,2278],{"class":187},[69,17555,203],{"class":75},[69,17557,17558],{"class":174},"AliasNbPages",[69,17560,209],{"class":75},[69,17562,17563],{"class":75},"\"\"",[69,17565,160],{"class":75},[69,17567,17568,17570,17572,17574],{"class":71,"line":238},[69,17569,2278],{"class":187},[69,17571,203],{"class":75},[69,17573,422],{"class":174},[69,17575,425],{"class":75},[69,17577,17578],{"class":71,"line":256},[69,17579,17580],{"class":2211},"// ... body ...\n",[19,17582,17583,17586],{},[47,17584,17585],{},"{nb}","는 gofpdf가 출력 시 전체 페이지 수로 치환하는 센티넬. 동작은 하지만 \"알고 있어야만 쓴다\"는 부류.",[19,17588,17589],{},[30,17590,7658],{},[60,17592,17594],{"className":62,"code":17593,"language":64,"meta":65,"style":65},"doc := gpdf.NewDocument(\n    gpdf.WithPageSize(document.A4),\n    gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n)\n\ndoc.Header(func(p *template.PageBuilder) {\n    p.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"ACME 주식회사\", template.Bold(), template.FontSize(12))\n            c.Line(template.LineColor(pdf.Gray(0.7)))\n            c.Spacer(document.Mm(4))\n        })\n    })\n})\n\ndoc.Footer(func(p *template.PageBuilder) {\n    p.AutoRow(func(r *template.RowBuilder) {\n        r.Col(6, func(c *template.ColBuilder) {\n            c.Text(\"ACME 주식회사\",\n                template.FontSize(8), template.TextColor(pdf.Gray(0.5)))\n        })\n        r.Col(6, func(c *template.ColBuilder) {\n            // \"페이지 X / Y\" — 둘 다 플레이스홀더로,\n            // 페이지네이션 완료 후 레이아웃 엔진이 해결한다.\n            c.PageNumber(template.AlignRight(),\n                template.FontSize(8), template.TextColor(pdf.Gray(0.5)))\n        })\n    })\n})\n\nfor i := 0; i \u003C 10; i++ {\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(fmt.Sprintf(\"%d 페이지 본문.\", i+1))\n        })\n    })\n}\n",[47,17595,17596,17610,17628,17658,17662,17666,17692,17717,17747,17785,17820,17842,17846,17850,17854,17858,17883,17907,17937,17955,17990,17994,18024,18029,18034,18053,18087,18091,18095,18099,18103,18134,18148,18172,18202,18239,18243,18247],{"__ignoreMap":65},[69,17597,17598,17600,17602,17604,17606,17608],{"class":71,"line":72},[69,17599,1004],{"class":187},[69,17601,197],{"class":75},[69,17603,275],{"class":187},[69,17605,203],{"class":75},[69,17607,280],{"class":174},[69,17609,283],{"class":75},[69,17611,17612,17614,17616,17618,17620,17622,17624,17626],{"class":71,"line":83},[69,17613,1019],{"class":187},[69,17615,203],{"class":75},[69,17617,294],{"class":174},[69,17619,209],{"class":75},[69,17621,321],{"class":187},[69,17623,203],{"class":75},[69,17625,303],{"class":187},[69,17627,306],{"class":75},[69,17629,17630,17632,17634,17636,17638,17640,17642,17644,17646,17648,17650,17652,17654,17656],{"class":71,"line":90},[69,17631,1019],{"class":187},[69,17633,203],{"class":75},[69,17635,316],{"class":174},[69,17637,209],{"class":75},[69,17639,321],{"class":187},[69,17641,203],{"class":75},[69,17643,326],{"class":174},[69,17645,209],{"class":75},[69,17647,321],{"class":187},[69,17649,203],{"class":75},[69,17651,335],{"class":174},[69,17653,209],{"class":75},[69,17655,341],{"class":340},[69,17657,344],{"class":75},[69,17659,17660],{"class":71,"line":100},[69,17661,160],{"class":75},[69,17663,17664],{"class":71,"line":112},[69,17665,87],{"emptyLinePlaceholder":86},[69,17667,17668,17671,17673,17675,17677,17679,17681,17683,17685,17688,17690],{"class":71,"line":122},[69,17669,17670],{"class":187},"doc",[69,17672,203],{"class":75},[69,17674,7559],{"class":174},[69,17676,439],{"class":75},[69,17678,19],{"class":442},[69,17680,446],{"class":75},[69,17682,449],{"class":79},[69,17684,203],{"class":75},[69,17686,17687],{"class":79},"PageBuilder",[69,17689,457],{"class":75},[69,17691,181],{"class":75},[69,17693,17694,17697,17699,17701,17703,17705,17707,17709,17711,17713,17715],{"class":71,"line":127},[69,17695,17696],{"class":187},"    p",[69,17698,203],{"class":75},[69,17700,436],{"class":174},[69,17702,439],{"class":75},[69,17704,443],{"class":442},[69,17706,446],{"class":75},[69,17708,449],{"class":79},[69,17710,203],{"class":75},[69,17712,454],{"class":79},[69,17714,457],{"class":75},[69,17716,181],{"class":75},[69,17718,17719,17721,17723,17725,17727,17729,17731,17733,17735,17737,17739,17741,17743,17745],{"class":71,"line":137},[69,17720,465],{"class":187},[69,17722,203],{"class":75},[69,17724,470],{"class":174},[69,17726,209],{"class":75},[69,17728,475],{"class":340},[69,17730,191],{"class":75},[69,17732,480],{"class":75},[69,17734,483],{"class":442},[69,17736,446],{"class":75},[69,17738,449],{"class":79},[69,17740,203],{"class":75},[69,17742,492],{"class":79},[69,17744,457],{"class":75},[69,17746,181],{"class":75},[69,17748,17749,17751,17753,17755,17757,17759,17761,17763,17765,17767,17769,17771,17773,17775,17777,17779,17781,17783],{"class":71,"line":147},[69,17750,502],{"class":187},[69,17752,203],{"class":75},[69,17754,507],{"class":174},[69,17756,209],{"class":75},[69,17758,212],{"class":75},[69,17760,17378],{"class":215},[69,17762,212],{"class":75},[69,17764,191],{"class":75},[69,17766,521],{"class":187},[69,17768,203],{"class":75},[69,17770,541],{"class":174},[69,17772,7640],{"class":75},[69,17774,521],{"class":187},[69,17776,203],{"class":75},[69,17778,526],{"class":174},[69,17780,209],{"class":75},[69,17782,475],{"class":340},[69,17784,5029],{"class":75},[69,17786,17787,17789,17791,17794,17796,17798,17800,17803,17805,17807,17809,17812,17814,17817],{"class":71,"line":157},[69,17788,502],{"class":187},[69,17790,203],{"class":75},[69,17792,17793],{"class":174},"Line",[69,17795,209],{"class":75},[69,17797,449],{"class":187},[69,17799,203],{"class":75},[69,17801,17802],{"class":174},"LineColor",[69,17804,209],{"class":75},[69,17806,2278],{"class":187},[69,17808,203],{"class":75},[69,17810,17811],{"class":174},"Gray",[69,17813,209],{"class":75},[69,17815,17816],{"class":340},"0.7",[69,17818,17819],{"class":75},")))\n",[69,17821,17822,17824,17826,17828,17830,17832,17834,17836,17838,17840],{"class":71,"line":163},[69,17823,502],{"class":187},[69,17825,203],{"class":75},[69,17827,2131],{"class":174},[69,17829,209],{"class":75},[69,17831,321],{"class":187},[69,17833,203],{"class":75},[69,17835,335],{"class":174},[69,17837,209],{"class":75},[69,17839,5838],{"class":340},[69,17841,5029],{"class":75},[69,17843,17844],{"class":71,"line":168},[69,17845,570],{"class":75},[69,17847,17848],{"class":71,"line":184},[69,17849,576],{"class":75},[69,17851,17852],{"class":71,"line":222},[69,17853,3730],{"class":75},[69,17855,17856],{"class":71,"line":238},[69,17857,87],{"emptyLinePlaceholder":86},[69,17859,17860,17862,17864,17867,17869,17871,17873,17875,17877,17879,17881],{"class":71,"line":256},[69,17861,17670],{"class":187},[69,17863,203],{"class":75},[69,17865,17866],{"class":174},"Footer",[69,17868,439],{"class":75},[69,17870,19],{"class":442},[69,17872,446],{"class":75},[69,17874,449],{"class":79},[69,17876,203],{"class":75},[69,17878,17687],{"class":79},[69,17880,457],{"class":75},[69,17882,181],{"class":75},[69,17884,17885,17887,17889,17891,17893,17895,17897,17899,17901,17903,17905],{"class":71,"line":262},[69,17886,17696],{"class":187},[69,17888,203],{"class":75},[69,17890,436],{"class":174},[69,17892,439],{"class":75},[69,17894,443],{"class":442},[69,17896,446],{"class":75},[69,17898,449],{"class":79},[69,17900,203],{"class":75},[69,17902,454],{"class":79},[69,17904,457],{"class":75},[69,17906,181],{"class":75},[69,17908,17909,17911,17913,17915,17917,17919,17921,17923,17925,17927,17929,17931,17933,17935],{"class":71,"line":267},[69,17910,465],{"class":187},[69,17912,203],{"class":75},[69,17914,470],{"class":174},[69,17916,209],{"class":75},[69,17918,5656],{"class":340},[69,17920,191],{"class":75},[69,17922,480],{"class":75},[69,17924,483],{"class":442},[69,17926,446],{"class":75},[69,17928,449],{"class":79},[69,17930,203],{"class":75},[69,17932,492],{"class":79},[69,17934,457],{"class":75},[69,17936,181],{"class":75},[69,17938,17939,17941,17943,17945,17947,17949,17951,17953],{"class":71,"line":286},[69,17940,502],{"class":187},[69,17942,203],{"class":75},[69,17944,507],{"class":174},[69,17946,209],{"class":75},[69,17948,212],{"class":75},[69,17950,17378],{"class":215},[69,17952,212],{"class":75},[69,17954,2059],{"class":75},[69,17956,17957,17959,17961,17963,17965,17967,17969,17971,17973,17975,17977,17979,17981,17983,17985,17988],{"class":71,"line":309},[69,17958,11323],{"class":187},[69,17960,203],{"class":75},[69,17962,526],{"class":174},[69,17964,209],{"class":75},[69,17966,6047],{"class":340},[69,17968,534],{"class":75},[69,17970,521],{"class":187},[69,17972,203],{"class":75},[69,17974,11381],{"class":174},[69,17976,209],{"class":75},[69,17978,2278],{"class":187},[69,17980,203],{"class":75},[69,17982,17811],{"class":174},[69,17984,209],{"class":75},[69,17986,17987],{"class":340},"0.5",[69,17989,17819],{"class":75},[69,17991,17992],{"class":71,"line":347},[69,17993,570],{"class":75},[69,17995,17996,17998,18000,18002,18004,18006,18008,18010,18012,18014,18016,18018,18020,18022],{"class":71,"line":373},[69,17997,465],{"class":187},[69,17999,203],{"class":75},[69,18001,470],{"class":174},[69,18003,209],{"class":75},[69,18005,5656],{"class":340},[69,18007,191],{"class":75},[69,18009,480],{"class":75},[69,18011,483],{"class":442},[69,18013,446],{"class":75},[69,18015,449],{"class":79},[69,18017,203],{"class":75},[69,18019,492],{"class":79},[69,18021,457],{"class":75},[69,18023,181],{"class":75},[69,18025,18026],{"class":71,"line":398},[69,18027,18028],{"class":2211},"            // \"페이지 X / Y\" — 둘 다 플레이스홀더로,\n",[69,18030,18031],{"class":71,"line":404},[69,18032,18033],{"class":2211},"            // 페이지네이션 완료 후 레이아웃 엔진이 해결한다.\n",[69,18035,18036,18038,18040,18043,18045,18047,18049,18051],{"class":71,"line":409},[69,18037,502],{"class":187},[69,18039,203],{"class":75},[69,18041,18042],{"class":174},"PageNumber",[69,18044,209],{"class":75},[69,18046,449],{"class":187},[69,18048,203],{"class":75},[69,18050,7284],{"class":174},[69,18052,11372],{"class":75},[69,18054,18055,18057,18059,18061,18063,18065,18067,18069,18071,18073,18075,18077,18079,18081,18083,18085],{"class":71,"line":428},[69,18056,11323],{"class":187},[69,18058,203],{"class":75},[69,18060,526],{"class":174},[69,18062,209],{"class":75},[69,18064,6047],{"class":340},[69,18066,534],{"class":75},[69,18068,521],{"class":187},[69,18070,203],{"class":75},[69,18072,11381],{"class":174},[69,18074,209],{"class":75},[69,18076,2278],{"class":187},[69,18078,203],{"class":75},[69,18080,17811],{"class":174},[69,18082,209],{"class":75},[69,18084,17987],{"class":340},[69,18086,17819],{"class":75},[69,18088,18089],{"class":71,"line":462},[69,18090,570],{"class":75},[69,18092,18093],{"class":71,"line":499},[69,18094,576],{"class":75},[69,18096,18097],{"class":71,"line":547},[69,18098,3730],{"class":75},[69,18100,18101],{"class":71,"line":567},[69,18102,87],{"emptyLinePlaceholder":86},[69,18104,18105,18107,18110,18112,18114,18117,18119,18122,18124,18126,18129,18132],{"class":71,"line":573},[69,18106,15934],{"class":93},[69,18108,18109],{"class":187}," i ",[69,18111,197],{"class":75},[69,18113,15561],{"class":340},[69,18115,18116],{"class":75},";",[69,18118,18109],{"class":187},[69,18120,18121],{"class":75},"\u003C",[69,18123,7534],{"class":340},[69,18125,18116],{"class":75},[69,18127,18128],{"class":187}," i",[69,18130,18131],{"class":75},"++",[69,18133,181],{"class":75},[69,18135,18136,18138,18140,18142,18144,18146],{"class":71,"line":579},[69,18137,412],{"class":187},[69,18139,197],{"class":75},[69,18141,417],{"class":187},[69,18143,203],{"class":75},[69,18145,422],{"class":174},[69,18147,425],{"class":75},[69,18149,18150,18152,18154,18156,18158,18160,18162,18164,18166,18168,18170],{"class":71,"line":584},[69,18151,431],{"class":187},[69,18153,203],{"class":75},[69,18155,436],{"class":174},[69,18157,439],{"class":75},[69,18159,443],{"class":442},[69,18161,446],{"class":75},[69,18163,449],{"class":79},[69,18165,203],{"class":75},[69,18167,454],{"class":79},[69,18169,457],{"class":75},[69,18171,181],{"class":75},[69,18173,18174,18176,18178,18180,18182,18184,18186,18188,18190,18192,18194,18196,18198,18200],{"class":71,"line":605},[69,18175,465],{"class":187},[69,18177,203],{"class":75},[69,18179,470],{"class":174},[69,18181,209],{"class":75},[69,18183,475],{"class":340},[69,18185,191],{"class":75},[69,18187,480],{"class":75},[69,18189,483],{"class":442},[69,18191,446],{"class":75},[69,18193,449],{"class":79},[69,18195,203],{"class":75},[69,18197,492],{"class":79},[69,18199,457],{"class":75},[69,18201,181],{"class":75},[69,18203,18204,18206,18208,18210,18212,18214,18216,18218,18220,18222,18224,18227,18229,18231,18233,18235,18237],{"class":71,"line":618},[69,18205,502],{"class":187},[69,18207,203],{"class":75},[69,18209,507],{"class":174},[69,18211,209],{"class":75},[69,18213,3841],{"class":187},[69,18215,203],{"class":75},[69,18217,17487],{"class":174},[69,18219,209],{"class":75},[69,18221,212],{"class":75},[69,18223,17497],{"class":3853},[69,18225,18226],{"class":215}," 페이지 본문.",[69,18228,212],{"class":75},[69,18230,191],{"class":75},[69,18232,18128],{"class":187},[69,18234,9997],{"class":75},[69,18236,15554],{"class":340},[69,18238,5029],{"class":75},[69,18240,18241],{"class":71,"line":633},[69,18242,570],{"class":75},[69,18244,18245],{"class":71,"line":638},[69,18246,576],{"class":75},[69,18248,18249],{"class":71,"line":684},[69,18250,707],{"class":75},[19,18252,18253,2128,18255,18258,18259,18261,18262,18265],{},[47,18254,18042],{},[47,18256,18257],{},"TotalPages","는 플레이스홀더. 레이아웃 엔진이 페이지 수를 확정한 뒤 확장된다. ",[47,18260,17585],{}," 센티넬도, ",[47,18263,18264],{},"SetY(-15)","로 푸터를 바닥에 박아두는 작업도 필요 없다 — 푸터는 그냥 트리일 뿐이고, 엔진이 매 페이지 자동으로 공간을 확보한다.",[14,18267,18269],{"id":18268},"before-after-5-http-핸들러에-바이트-반환","Before / After 5: HTTP 핸들러에 바이트 반환",[19,18271,18272,18273,18275,18276,18278,18279,18282],{},"실제 운영 중인 gofpdf 코드 대부분은 파일이 아니라 ",[47,18274,1891],{},"에 쓴다. 주로 ",[47,18277,7581],{},"를 반환하는 ",[47,18280,18281],{},"http.ResponseWriter",". 이 쌍에서 gpdf API가 gofpdf에 가장 가깝다.",[19,18284,18285],{},[30,18286,14862],{},[60,18288,18290],{"className":62,"code":18289,"language":64,"meta":65,"style":65},"func handler(w http.ResponseWriter, r *http.Request) {\n    pdf := gofpdf.New(\"P\", \"mm\", \"A4\", \"\")\n    pdf.AddPage()\n    pdf.SetFont(\"Arial\", \"\", 12)\n    pdf.Cell(0, 10, \"생성 시간: \"+time.Now().Format(time.RFC3339))\n\n    w.Header().Set(\"Content-Type\", \"application/pdf\")\n    if err := pdf.Output(w); err != nil {\n        http.Error(w, err.Error(), 500)\n    }\n}\n",[47,18291,18292,18324,18366,18376,18402,18453,18457,18487,18515,18541,18545],{"__ignoreMap":65},[69,18293,18294,18296,18298,18300,18302,18304,18306,18308,18310,18312,18314,18316,18318,18320,18322],{"class":71,"line":72},[69,18295,171],{"class":75},[69,18297,7392],{"class":174},[69,18299,209],{"class":75},[69,18301,1874],{"class":442},[69,18303,7399],{"class":79},[69,18305,203],{"class":75},[69,18307,7404],{"class":79},[69,18309,191],{"class":75},[69,18311,7409],{"class":442},[69,18313,446],{"class":75},[69,18315,7414],{"class":79},[69,18317,203],{"class":75},[69,18319,7419],{"class":79},[69,18321,457],{"class":75},[69,18323,181],{"class":75},[69,18325,18326,18328,18330,18332,18334,18336,18338,18340,18342,18344,18346,18348,18350,18352,18354,18356,18358,18360,18362,18364],{"class":71,"line":83},[69,18327,7428],{"class":187},[69,18329,197],{"class":75},[69,18331,14911],{"class":187},[69,18333,203],{"class":75},[69,18335,7438],{"class":174},[69,18337,209],{"class":75},[69,18339,212],{"class":75},[69,18341,7445],{"class":215},[69,18343,212],{"class":75},[69,18345,191],{"class":75},[69,18347,7452],{"class":75},[69,18349,7455],{"class":215},[69,18351,212],{"class":75},[69,18353,191],{"class":75},[69,18355,7452],{"class":75},[69,18357,303],{"class":215},[69,18359,212],{"class":75},[69,18361,191],{"class":75},[69,18363,7470],{"class":75},[69,18365,160],{"class":75},[69,18367,18368,18370,18372,18374],{"class":71,"line":90},[69,18369,7477],{"class":187},[69,18371,203],{"class":75},[69,18373,422],{"class":174},[69,18375,425],{"class":75},[69,18377,18378,18380,18382,18384,18386,18388,18390,18392,18394,18396,18398,18400],{"class":71,"line":100},[69,18379,7477],{"class":187},[69,18381,203],{"class":75},[69,18383,1758],{"class":174},[69,18385,209],{"class":75},[69,18387,212],{"class":75},[69,18389,7498],{"class":215},[69,18391,212],{"class":75},[69,18393,191],{"class":75},[69,18395,7470],{"class":75},[69,18397,191],{"class":75},[69,18399,3286],{"class":340},[69,18401,160],{"class":75},[69,18403,18404,18406,18408,18410,18412,18414,18416,18418,18420,18422,18425,18427,18429,18432,18434,18437,18439,18442,18444,18446,18448,18451],{"class":71,"line":112},[69,18405,7477],{"class":187},[69,18407,203],{"class":75},[69,18409,1933],{"class":174},[69,18411,209],{"class":75},[69,18413,10257],{"class":340},[69,18415,191],{"class":75},[69,18417,7534],{"class":340},[69,18419,191],{"class":75},[69,18421,7452],{"class":75},[69,18423,18424],{"class":215},"생성 시간: ",[69,18426,212],{"class":75},[69,18428,9997],{"class":75},[69,18430,18431],{"class":187},"time",[69,18433,203],{"class":75},[69,18435,18436],{"class":174},"Now",[69,18438,7562],{"class":75},[69,18440,18441],{"class":174},"Format",[69,18443,209],{"class":75},[69,18445,18431],{"class":187},[69,18447,203],{"class":75},[69,18449,18450],{"class":187},"RFC3339",[69,18452,5029],{"class":75},[69,18454,18455],{"class":71,"line":122},[69,18456,87],{"emptyLinePlaceholder":86},[69,18458,18459,18461,18463,18465,18467,18469,18471,18473,18475,18477,18479,18481,18483,18485],{"class":71,"line":127},[69,18460,7554],{"class":187},[69,18462,203],{"class":75},[69,18464,7559],{"class":174},[69,18466,7562],{"class":75},[69,18468,7565],{"class":174},[69,18470,209],{"class":75},[69,18472,212],{"class":75},[69,18474,7572],{"class":215},[69,18476,212],{"class":75},[69,18478,191],{"class":75},[69,18480,7452],{"class":75},[69,18482,7581],{"class":215},[69,18484,212],{"class":75},[69,18486,160],{"class":75},[69,18488,18489,18491,18493,18495,18497,18499,18501,18503,18505,18507,18509,18511,18513],{"class":71,"line":137},[69,18490,225],{"class":93},[69,18492,194],{"class":187},[69,18494,197],{"class":75},[69,18496,7596],{"class":187},[69,18498,203],{"class":75},[69,18500,1937],{"class":174},[69,18502,209],{"class":75},[69,18504,1874],{"class":187},[69,18506,673],{"class":75},[69,18508,194],{"class":187},[69,18510,230],{"class":75},[69,18512,233],{"class":75},[69,18514,181],{"class":75},[69,18516,18517,18519,18521,18523,18525,18527,18529,18531,18533,18535,18537,18539],{"class":71,"line":147},[69,18518,7619],{"class":187},[69,18520,203],{"class":75},[69,18522,7624],{"class":174},[69,18524,209],{"class":75},[69,18526,1874],{"class":187},[69,18528,191],{"class":75},[69,18530,7633],{"class":187},[69,18532,203],{"class":75},[69,18534,7624],{"class":174},[69,18536,7640],{"class":75},[69,18538,7643],{"class":340},[69,18540,160],{"class":75},[69,18542,18543],{"class":71,"line":157},[69,18544,259],{"class":75},[69,18546,18547],{"class":71,"line":163},[69,18548,707],{"class":75},[19,18550,18551],{},[30,18552,7658],{},[60,18554,18556],{"className":62,"code":18555,"language":64,"meta":65,"style":65},"func handler(w http.ResponseWriter, r *http.Request) {\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(document.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"생성 시간: \" + time.Now().Format(time.RFC3339))\n        })\n    })\n\n    w.Header().Set(\"Content-Type\", \"application/pdf\")\n    if err := doc.Render(w); err != nil {\n        http.Error(w, err.Error(), 500)\n    }\n}\n",[47,18557,18558,18590,18604,18622,18652,18656,18660,18674,18698,18728,18768,18772,18776,18780,18810,18838,18864,18868],{"__ignoreMap":65},[69,18559,18560,18562,18564,18566,18568,18570,18572,18574,18576,18578,18580,18582,18584,18586,18588],{"class":71,"line":72},[69,18561,171],{"class":75},[69,18563,7392],{"class":174},[69,18565,209],{"class":75},[69,18567,1874],{"class":442},[69,18569,7399],{"class":79},[69,18571,203],{"class":75},[69,18573,7404],{"class":79},[69,18575,191],{"class":75},[69,18577,7409],{"class":442},[69,18579,446],{"class":75},[69,18581,7414],{"class":79},[69,18583,203],{"class":75},[69,18585,7419],{"class":79},[69,18587,457],{"class":75},[69,18589,181],{"class":75},[69,18591,18592,18594,18596,18598,18600,18602],{"class":71,"line":83},[69,18593,270],{"class":187},[69,18595,197],{"class":75},[69,18597,275],{"class":187},[69,18599,203],{"class":75},[69,18601,280],{"class":174},[69,18603,283],{"class":75},[69,18605,18606,18608,18610,18612,18614,18616,18618,18620],{"class":71,"line":90},[69,18607,289],{"class":187},[69,18609,203],{"class":75},[69,18611,294],{"class":174},[69,18613,209],{"class":75},[69,18615,321],{"class":187},[69,18617,203],{"class":75},[69,18619,303],{"class":187},[69,18621,306],{"class":75},[69,18623,18624,18626,18628,18630,18632,18634,18636,18638,18640,18642,18644,18646,18648,18650],{"class":71,"line":100},[69,18625,289],{"class":187},[69,18627,203],{"class":75},[69,18629,316],{"class":174},[69,18631,209],{"class":75},[69,18633,321],{"class":187},[69,18635,203],{"class":75},[69,18637,326],{"class":174},[69,18639,209],{"class":75},[69,18641,321],{"class":187},[69,18643,203],{"class":75},[69,18645,335],{"class":174},[69,18647,209],{"class":75},[69,18649,341],{"class":340},[69,18651,344],{"class":75},[69,18653,18654],{"class":71,"line":112},[69,18655,401],{"class":75},[69,18657,18658],{"class":71,"line":122},[69,18659,87],{"emptyLinePlaceholder":86},[69,18661,18662,18664,18666,18668,18670,18672],{"class":71,"line":127},[69,18663,412],{"class":187},[69,18665,197],{"class":75},[69,18667,417],{"class":187},[69,18669,203],{"class":75},[69,18671,422],{"class":174},[69,18673,425],{"class":75},[69,18675,18676,18678,18680,18682,18684,18686,18688,18690,18692,18694,18696],{"class":71,"line":137},[69,18677,431],{"class":187},[69,18679,203],{"class":75},[69,18681,436],{"class":174},[69,18683,439],{"class":75},[69,18685,443],{"class":442},[69,18687,446],{"class":75},[69,18689,449],{"class":79},[69,18691,203],{"class":75},[69,18693,454],{"class":79},[69,18695,457],{"class":75},[69,18697,181],{"class":75},[69,18699,18700,18702,18704,18706,18708,18710,18712,18714,18716,18718,18720,18722,18724,18726],{"class":71,"line":147},[69,18701,465],{"class":187},[69,18703,203],{"class":75},[69,18705,470],{"class":174},[69,18707,209],{"class":75},[69,18709,475],{"class":340},[69,18711,191],{"class":75},[69,18713,480],{"class":75},[69,18715,483],{"class":442},[69,18717,446],{"class":75},[69,18719,449],{"class":79},[69,18721,203],{"class":75},[69,18723,492],{"class":79},[69,18725,457],{"class":75},[69,18727,181],{"class":75},[69,18729,18730,18732,18734,18736,18738,18740,18742,18744,18747,18750,18752,18754,18756,18758,18760,18762,18764,18766],{"class":71,"line":157},[69,18731,502],{"class":187},[69,18733,203],{"class":75},[69,18735,507],{"class":174},[69,18737,209],{"class":75},[69,18739,212],{"class":75},[69,18741,18424],{"class":215},[69,18743,212],{"class":75},[69,18745,18746],{"class":75}," +",[69,18748,18749],{"class":187}," time",[69,18751,203],{"class":75},[69,18753,18436],{"class":174},[69,18755,7562],{"class":75},[69,18757,18441],{"class":174},[69,18759,209],{"class":75},[69,18761,18431],{"class":187},[69,18763,203],{"class":75},[69,18765,18450],{"class":187},[69,18767,5029],{"class":75},[69,18769,18770],{"class":71,"line":163},[69,18771,570],{"class":75},[69,18773,18774],{"class":71,"line":168},[69,18775,576],{"class":75},[69,18777,18778],{"class":71,"line":184},[69,18779,87],{"emptyLinePlaceholder":86},[69,18781,18782,18784,18786,18788,18790,18792,18794,18796,18798,18800,18802,18804,18806,18808],{"class":71,"line":222},[69,18783,7554],{"class":187},[69,18785,203],{"class":75},[69,18787,7559],{"class":174},[69,18789,7562],{"class":75},[69,18791,7565],{"class":174},[69,18793,209],{"class":75},[69,18795,212],{"class":75},[69,18797,7572],{"class":215},[69,18799,212],{"class":75},[69,18801,191],{"class":75},[69,18803,7452],{"class":75},[69,18805,7581],{"class":215},[69,18807,212],{"class":75},[69,18809,160],{"class":75},[69,18811,18812,18814,18816,18818,18820,18822,18824,18826,18828,18830,18832,18834,18836],{"class":71,"line":238},[69,18813,225],{"class":93},[69,18815,194],{"class":187},[69,18817,197],{"class":75},[69,18819,417],{"class":187},[69,18821,203],{"class":75},[69,18823,7989],{"class":174},[69,18825,209],{"class":75},[69,18827,1874],{"class":187},[69,18829,673],{"class":75},[69,18831,194],{"class":187},[69,18833,230],{"class":75},[69,18835,233],{"class":75},[69,18837,181],{"class":75},[69,18839,18840,18842,18844,18846,18848,18850,18852,18854,18856,18858,18860,18862],{"class":71,"line":256},[69,18841,7619],{"class":187},[69,18843,203],{"class":75},[69,18845,7624],{"class":174},[69,18847,209],{"class":75},[69,18849,1874],{"class":187},[69,18851,191],{"class":75},[69,18853,7633],{"class":187},[69,18855,203],{"class":75},[69,18857,7624],{"class":174},[69,18859,7640],{"class":75},[69,18861,7643],{"class":340},[69,18863,160],{"class":75},[69,18865,18866],{"class":71,"line":262},[69,18867,259],{"class":75},[69,18869,18870],{"class":71,"line":267},[69,18871,707],{"class":75},[19,18873,18874,18875,18877,18878,18881,18882,18884,18885,18888],{},"형태는 같다. ",[47,18876,14809],{},"가 PDF를 응답으로 바로 흘려보낸다. ",[47,18879,18880],{},"Content-Length","를 세우고 싶다면 먼저 ",[47,18883,3940],{},"로 바이트를 받아 ",[47,18886,18887],{},"len()","을 찍는다.",[14,18890,18892],{"id":18891},"충분히-빠름은-얼마나-빠른가","\"충분히 빠름\"은 얼마나 빠른가",[19,18894,18895,18896,18898,18899,18901],{},"gpdf는 실전 워크로드에서 ",[30,18897,14440],{},". 아래 수치는 ",[47,18900,1398],{},"를 Apple M1, Go 1.25에서 돌린 결과다.",[741,18903,18904,18918],{},[744,18905,18906],{},[747,18907,18908,18910,18912,18914,18916],{},[750,18909,8081],{},[750,18911,27],{},[750,18913,1431],{},[750,18915,8088],{},[750,18917,1440],{},[759,18919,18920,18935,18950,18965],{},[747,18921,18922,18925,18929,18931,18933],{},[764,18923,18924],{},"단일 페이지",[764,18926,18927],{},[30,18928,1344],{},[764,18930,1454],{},[764,18932,1460],{},[764,18934,1463],{},[747,18936,18937,18940,18944,18946,18948],{},[764,18938,18939],{},"4×10 테이블",[764,18941,18942],{},[30,18943,1348],{},[764,18945,1475],{},[764,18947,1481],{},[764,18949,8123],{},[747,18951,18952,18955,18959,18961,18963],{},[764,18953,18954],{},"100 페이지",[764,18956,18957],{},[30,18958,1352],{},[764,18960,1360],{},[764,18962,8123],{},[764,18964,8139],{},[747,18966,18967,18969,18973,18975,18977],{},[764,18968,8144],{},[764,18970,18971],{},[30,18972,1514],{},[764,18974,1517],{},[764,18976,1523],{},[764,18978,8155],{},[19,18980,18981],{},"합성 벤치마크가 아니다. 테이블 벤치는 4열 10행 세금계산서 명세, 100페이지 벤치는 헤더와 페이지 번호가 반복되는 리포트 — 실제 프로덕션 코드 모양에 맞춰져 있다.",[19,18983,18984],{},"의미 얘기도 가볍게. 13 µs/단일 페이지 = 싱글 코어로 초당 7만 5천 장의 hello-world PDF, 108 µs/테이블 포함 = 초당 약 9,000장의 세금계산서. 핵심은 자랑이 아니라, \"PDF 생성을 캐시해야 할까? 비동기 큐로 빼야 할까?\" 같은 고민이 사라진다는 것. 대부분의 워크로드는 요청 경로에서 동기 생성으로 충분하다.",[14,18986,18988],{"id":18987},"마이그레이션에서-잃는-것","마이그레이션에서 잃는 것",[19,18990,18991],{},"가이드가 현실의 간극을 가리면 의미가 없다. gpdf가 아직 gofpdf 대비 약한 지점을 솔직히 적는다:",[1113,18993,18994,19002,19017,19023],{},[886,18995,18996,2125,18999,19001],{},[30,18997,18998],{},"임의 각도 선, 베지어, 복잡한 패스",[47,19000,8205],{},"은 컬럼을 가로지르는 수평선을 그린다. CAD 도면이나 자체 차트 지오메트리는 아직 못 담는다. (차트를 이미지로 사전 렌더링해서 삽입하는 건 문제없이 된다.)",[886,19003,19004,2125,19009,19011,19012,1157,19014,19016],{},[30,19005,19006,19008],{},[47,19007,1930],{}," 기반 절대 좌표 코드",[47,19010,14730],{},"로 비슷한 것은 할 수 있지만, 기존 코드가 2,000줄 ",[47,19013,1930],{},[47,19015,1933],{},"이라면 이전은 사실상 재작성. 다만 재작성본이 보통 원본의 절반 길이가 되는 게 위안.",[886,19018,19019,19022],{},[30,19020,19021],{},"AcroForm (입력 가능 폼)",". gpdf는 아직 생성하지 않는다. PDF가 뷰어에서 사용자가 채우는 템플릿이라면 당분간 AcroForm 지원 라이브러리에 남는 선택지.",[886,19024,19025,19028],{},[30,19026,19027],{},"주석·북마크",". 기본 아웃라인은 되지만 리치 주석은 미지원.",[19,19030,19031],{},"이 중 어느 것도 당신에게 걸리지 않는다면 마이그레이션은 매끄럽게 끝난다. 걸린다면 Issue를 열어 달라 — 로드맵은 요청 기반으로 돌아간다.",[14,19033,2827],{"id":2826},[19,19035,19036,19039],{},[30,19037,19038],{},"gpdf는 gofpdf의 포크인가?","\n아니다. gpdf는 순수 Go로 처음부터 다시 구현했다. PDF 와이어 포맷, 레이아웃 엔진, TrueType 서브셋팅 — 전부 새로 작성. gofpdf나 그 포크와 공유하는 코드는 없다. 왜 포크가 아닌 재구현이냐면, gofpdf 아키텍처는 \"단일 변경 가능 커서\"를 전제로 만들어져 있어서 선언형 그리드를 뒤에 얹으면 기존 호출이 전부 깨지기 때문.",[19,19041,19042,19045,19046,7090,19048,19051,19052,19054,19055,19058],{},[30,19043,19044],{},"외부 의존성은?","\n코어는 제로. ",[47,19047,7089],{},[47,19049,19050],{},"go mod graph | grep gpdf","를 치면 한 줄만 나온다. ",[47,19053,8227],{}," 확장(HTML→PDF, AES 암호화, 서명, PDF/A)은 HTML 파싱을 위해 ",[47,19056,19057],{},"golang.org/x/net","을 끌어오지만, 이건 옵트인이고 마이그레이션에 필수가 아니다.",[19,19060,19061,19064,19065,19067],{},[30,19062,19063],{},"CGO는? gofpdf은 CGO-free였는데 gpdf는?","\n똑같이 순수 Go, CGO 없음. ",[47,19066,7080],{},"로 크로스 컴파일해 정적 바이너리를 배포할 수 있다. Distroless나 Alpine에서는 CGO 툴체인이 없는 것만으로 이미지 크기가 절반이 되기 때문에 중요한 포인트.",[19,19069,19070,19076,19078],{},[30,19071,19072,19073,19075],{},"기존 gofpdf 코드가 ",[47,19074,1930],{}," 투성이. 재작성 없이 이전할 수 있나?",[47,19077,14730],{},"를 래핑하면 비슷한 감각은 낸다. 다만 코드 전체가 커서 조작 중심이라면, 레이아웃 엔진 모델로의 이전은 문법이 아니라 사고방식 전환이다. 많은 팀이 \"재작성한 쪽이 원본보다 짧다\"고 말한다.",[19,19080,19081,19084,19085,19087],{},[30,19082,19083],{},"전자세금계산서, 전자문서 인증은?","\n타임스탬프와 전자서명은 ",[47,19086,8227],{},"에서 구현 중. 구체적 요구가 있다면 Issue로 우선순위를 올려 달라.",[19,19089,19090,19093],{},[30,19091,19092],{},"go-pdf/fpdf가 아카이브 해제되면?","\n선택지가 하나 더 생길 뿐. gpdf의 베팅은 \"gofpdf이 영원히 아카이브\"가 아니라 \"커서 기반 + 싱글바이트 폰트 + CJK 미지원이라는 아키텍처 자체가 누가 유지해도 막다른 길\"이라는 쪽. 2026년의 PDF 생성은 플로터를 조작하는 것보다 웹 페이지를 짜는 것에 가깝고, API도 그걸 반영해야 한다.",[14,19095,10021],{"id":10020},[19,19097,19098],{},"gpdf는 Go의 PDF 생성 라이브러리. MIT, 의존성 제로, CJK 지원.",[60,19100,19101],{"className":1250,"code":1251,"language":1252,"meta":65,"style":65},[47,19102,19103],{"__ignoreMap":65},[69,19104,19105,19107,19109],{"class":71,"line":72},[69,19106,64],{"class":79},[69,19108,1261],{"class":215},[69,19110,1264],{"class":215},[19,19112,19113,1271,19117],{},[22,19114,19116],{"href":24,"rel":19115},[26],"⭐ GitHub에서 스타 누르기",[22,19118,2898],{"href":1274,"rel":19119},[26],[14,19121,19123],{"id":19122},"다음으로-읽을-것","다음으로 읽을 것",[1113,19125,19126,19132,19137],{},[886,19127,19128,19129],{},"12-컬럼 그리드는 gpdf에서 어떻게 동작하나 ",[3586,19130,19131],{},"(곧 공개)",[886,19133,19134,19135],{},"gpdf에 한국어 폰트 넣기 ",[3586,19136,19131],{},[886,19138,19139,19142,19143,19145],{},[22,19140,11686],{"href":1274,"rel":19141},[26]," — 5분 세팅, ",[47,19144,6718],{}," 포함",[1278,19147,19148],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .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 .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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":65,"searchDepth":83,"depth":83,"links":19150},[19151,19152,19153,19154,19155,19156,19157,19158,19159,19160,19161,19162,19163,19164],{"id":1337,"depth":83,"text":1338},{"id":14471,"depth":83,"text":14472},{"id":14501,"depth":83,"text":14502},{"id":14540,"depth":83,"text":14541},{"id":14853,"depth":83,"text":14854},{"id":15444,"depth":83,"text":15445},{"id":16585,"depth":83,"text":16586},{"id":17241,"depth":83,"text":17242},{"id":18268,"depth":83,"text":18269},{"id":18891,"depth":83,"text":18892},{"id":18987,"depth":83,"text":18988},{"id":2826,"depth":83,"text":2827},{"id":10020,"depth":83,"text":10021},{"id":19122,"depth":83,"text":19123},"2026-04-14","gofpdf은 2021년 보관, 후속 go-pdf/fpdf도 2025년 중단. CJK 네이티브·의존성 제로의 순수 Go 라이브러리 gpdf로 옮기는 법.",{"name":19168,"totalTime":19169,"tools":19170,"steps":19171},"Go 프로젝트를 gofpdf에서 gpdf로 마이그레이션하기","PT30M",[1301],[19172,19175,19178,19181,19184,19187],{"name":19173,"text":19174},"import 경로 교체","2021년 보관된 github.com/jung-kurt/gofpdf과 2025년 중단된 github.com/go-pdf/fpdf을 github.com/gpdf-dev/gpdf, github.com/gpdf-dev/gpdf/document, github.com/gpdf-dev/gpdf/template로 바꾼다.",{"name":19176,"text":19177},"커서 대신 빌더로 문서 구성","gpdf.NewDocument에 WithPageSize / WithMargins / WithFont를 넘겨 생성한다. SetXY로 커서를 움직이는 대신 doc.AddPage()로 페이지를 추가하고 RowBuilder와 ColBuilder로 내용을 선언한다.",{"name":19179,"text":19180},"Cell과 MultiCell을 선언형 Text로 변경","pdf.Cell과 pdf.MultiCell을 컬럼 안의 c.Text(...)로 교체한다. 텍스트는 컬럼 경계에서 자동으로 줄바꿈되므로 MultiCell의 마지막 플래그는 필요 없다. 폰트 크기, 굵기, 색상은 per-text 옵션으로 전달한다.",{"name":19182,"text":19183},"CJK 폰트를 WithFont로 등록","한국어·일본어·중국어 텍스트는 pdf.AddUTF8Font 대신 문서 생성 시 gpdf.WithFont(name, ttfBytes)를 넘긴다. TTF 경로 관리도 UTF-8 플래그도 필요 없고, 서브셋 임베딩은 자동으로 처리된다.",{"name":19185,"text":19186},"테이블을 행과 컬럼으로 다시 작성","컬럼 너비를 수동 관리하는 중첩 Cell 루프 대신 AutoRow 안에서 row.Col(n, fn)으로 테이블 행을 만든다. 12컬럼 그리드가 너비 계산과 페이지 분할을 모두 처리한다.",{"name":19188,"text":19189},"출력 호출 전환","pdf.OutputFileAndClose(path) 대신 doc.Generate()로 []byte를 얻고 os.WriteFile(path, data, 0o644)로 저장한다. io.Writer에 바로 쓰려면 doc.Render(w)를 사용한다.",{},{"title":14421,"description":19166},"ko/blog/001.gofpdf-migration",[8411,2958,1327,1326],"8Hxw_yxpwY_bwhDJ7JXtYIFESEHZKv26vsJNo1b8bJM",1776537638803]