[{"data":1,"prerenderedAt":16533},["ShallowReactive",2],{"blog-list-ko":3},[4,1288,2553,3924,5710,7423,9048,10378,11753],{"id":5,"title":6,"author":7,"body":10,"date":1252,"description":1253,"draft":1254,"extension":1255,"howTo":1256,"image":1278,"meta":1279,"navigation":123,"path":1280,"seo":1281,"stem":1282,"tags":1283,"updated":1278,"__hash__":1287},"blogKo/ko/blog/008.tofu-boxes-japanese.md","gpdf로 만든 PDF에서 일본어가 네모 (두부 문자) 로 나오는 이유와 해결법",{"name":8,"url":9},"gpdf team","https://gpdf.dev",{"type":11,"value":12,"toc":1240},"minimark",[13,18,22,26,29,32,89,93,96,700,714,727,731,734,750,770,776,780,788,933,965,969,976,979,982,1012,1015,1019,1025,1090,1101,1104,1137,1141,1165,1169,1199,1203,1206,1223,1236],[14,15,17],"h2",{"id":16},"질문을-다시-말하면","질문을 다시 말하면",[19,20,21],"p",{},"gpdf로 일본어를 썼는데 출력된 PDF에서 그 문자들이 전부 빈 네모로 나온다. 이게 뭐고, 어떻게 해야 실제 일본어 글리프가 파일 안에 들어가는가.",[14,23,25],{"id":24},"빠른-답","빠른 답",[19,27,28],{},"이것이 두부 문자(tofu)다. PDF 뷰어가 임베드된 폰트에서 해당 유니코드 코드포인트의 글리프를 찾지 못해 자리 표시용 사각형을 그리는 것. 원인은 네 가지이고, 그중 하나가 압도적으로 많다.",[19,30,31],{},"빈도 순:",[33,34,35,52,69,79],"ol",{},[36,37,38,42,43,47,48,51],"li",{},[39,40,41],"strong",{},"CJK 폰트를 등록하지 않았다."," ",[44,45,46],"code",{},"gpdf.NewDocument","에 ",[44,49,50],{},"WithFont"," 호출이 없어서 문서가 PDF Base-14 폰트(Helvetica / Times / Courier)로 폴백한 상태. 이들 중 어느 것도 U+3040–U+9FFF를 커버하지 않는다.",[36,53,54,42,61,64,65,68],{},[39,55,56,57,60],{},"CJK 폰트는 등록했는데 ",[44,58,59],{},"c.Text","의 패밀리명이 다르다.",[44,62,63],{},"WithFont(\"NotoSansJP\", ...)","는 설정했지만 텍스트에 ",[44,66,67],{},"template.FontFamily(\"Arial\")","가 지정되어 있어서, gpdf가 Latin 폰트에서 일본어를 조회한다.",[36,70,71,74,75,78],{},[39,72,73],{},"폰트 파일 자체에 CJK 글리프가 없다."," 디스크의 TTF가 Latin 서브셋(",[44,76,77],{},"NotoSans-Regular.ttf",")이다. 이름은 맞아 보이지만 커버리지가 비어 있다.",[36,80,81,84,85,88],{},[39,82,83],{},"gpdf에 도달하기 전에 바이트가 깨졌다."," 문자열이 상류에서 Shift-JIS나 Latin-1로 디코딩돼, 렌더링하려는 것이 이미 일본어 코드포인트가 아니다. 네모가 아니라 ",[44,86,87],{},"縺ゅ→縺","처럼 나오면 이 경우다.",[14,90,92],{"id":91},"원인-1의-표준-수정","원인 #1의 표준 수정",[19,94,95],{},"열에 아홉은 이것이다:",[97,98,103],"pre",{"className":99,"code":100,"language":101,"meta":102,"style":102},"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(\"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","go","",[44,104,105,118,125,135,147,157,162,172,182,192,198,203,219,258,274,292,298,303,322,346,384,409,434,440,445,464,498,535,557,563,569,574,595,608,623,628,674,689,694],{"__ignoreMap":102},[106,107,110,114],"span",{"class":108,"line":109},"line",1,[106,111,113],{"class":112},"sMK4o","package",[106,115,117],{"class":116},"sBMFI"," main\n",[106,119,121],{"class":108,"line":120},2,[106,122,124],{"emptyLinePlaceholder":123},true,"\n",[106,126,128,132],{"class":108,"line":127},3,[106,129,131],{"class":130},"s7zQu","import",[106,133,134],{"class":112}," (\n",[106,136,138,141,144],{"class":108,"line":137},4,[106,139,140],{"class":112},"    \"",[106,142,143],{"class":116},"log",[106,145,146],{"class":112},"\"\n",[106,148,150,152,155],{"class":108,"line":149},5,[106,151,140],{"class":112},[106,153,154],{"class":116},"os",[106,156,146],{"class":112},[106,158,160],{"class":108,"line":159},6,[106,161,124],{"emptyLinePlaceholder":123},[106,163,165,167,170],{"class":108,"line":164},7,[106,166,140],{"class":112},[106,168,169],{"class":116},"github.com/gpdf-dev/gpdf",[106,171,146],{"class":112},[106,173,175,177,180],{"class":108,"line":174},8,[106,176,140],{"class":112},[106,178,179],{"class":116},"github.com/gpdf-dev/gpdf/document",[106,181,146],{"class":112},[106,183,185,187,190],{"class":108,"line":184},9,[106,186,140],{"class":112},[106,188,189],{"class":116},"github.com/gpdf-dev/gpdf/template",[106,191,146],{"class":112},[106,193,195],{"class":108,"line":194},10,[106,196,197],{"class":112},")\n",[106,199,201],{"class":108,"line":200},11,[106,202,124],{"emptyLinePlaceholder":123},[106,204,206,209,213,216],{"class":108,"line":205},12,[106,207,208],{"class":112},"func",[106,210,212],{"class":211},"s2Zo4"," main",[106,214,215],{"class":112},"()",[106,217,218],{"class":112}," {\n",[106,220,222,226,229,232,235,238,241,244,247,250,254,256],{"class":108,"line":221},13,[106,223,225],{"class":224},"sTEyZ","    font",[106,227,228],{"class":112},",",[106,230,231],{"class":224}," err ",[106,233,234],{"class":112},":=",[106,236,237],{"class":224}," os",[106,239,240],{"class":112},".",[106,242,243],{"class":211},"ReadFile",[106,245,246],{"class":112},"(",[106,248,249],{"class":112},"\"",[106,251,253],{"class":252},"sfazB","NotoSansJP-Regular.ttf",[106,255,249],{"class":112},[106,257,197],{"class":112},[106,259,261,264,266,269,272],{"class":108,"line":260},14,[106,262,263],{"class":130},"    if",[106,265,231],{"class":224},[106,267,268],{"class":112},"!=",[106,270,271],{"class":112}," nil",[106,273,218],{"class":112},[106,275,277,280,282,285,287,290],{"class":108,"line":276},15,[106,278,279],{"class":224},"        log",[106,281,240],{"class":112},[106,283,284],{"class":211},"Fatal",[106,286,246],{"class":112},[106,288,289],{"class":224},"err",[106,291,197],{"class":112},[106,293,295],{"class":108,"line":294},16,[106,296,297],{"class":112},"    }\n",[106,299,301],{"class":108,"line":300},17,[106,302,124],{"emptyLinePlaceholder":123},[106,304,306,309,311,314,316,319],{"class":108,"line":305},18,[106,307,308],{"class":224},"    doc ",[106,310,234],{"class":112},[106,312,313],{"class":224}," gpdf",[106,315,240],{"class":112},[106,317,318],{"class":211},"NewDocument",[106,320,321],{"class":112},"(\n",[106,323,325,328,330,333,335,338,340,343],{"class":108,"line":324},19,[106,326,327],{"class":224},"        gpdf",[106,329,240],{"class":112},[106,331,332],{"class":211},"WithPageSize",[106,334,246],{"class":112},[106,336,337],{"class":224},"gpdf",[106,339,240],{"class":112},[106,341,342],{"class":224},"A4",[106,344,345],{"class":112},"),\n",[106,347,349,351,353,356,358,361,363,366,368,370,372,375,377,381],{"class":108,"line":348},20,[106,350,327],{"class":224},[106,352,240],{"class":112},[106,354,355],{"class":211},"WithMargins",[106,357,246],{"class":112},[106,359,360],{"class":224},"document",[106,362,240],{"class":112},[106,364,365],{"class":211},"UniformEdges",[106,367,246],{"class":112},[106,369,360],{"class":224},[106,371,240],{"class":112},[106,373,374],{"class":211},"Mm",[106,376,246],{"class":112},[106,378,380],{"class":379},"sbssI","20",[106,382,383],{"class":112},"))),\n",[106,385,387,389,391,393,395,397,400,402,404,407],{"class":108,"line":386},21,[106,388,327],{"class":224},[106,390,240],{"class":112},[106,392,50],{"class":211},[106,394,246],{"class":112},[106,396,249],{"class":112},[106,398,399],{"class":252},"NotoSansJP",[106,401,249],{"class":112},[106,403,228],{"class":112},[106,405,406],{"class":224}," font",[106,408,345],{"class":112},[106,410,412,414,416,419,421,423,425,427,429,432],{"class":108,"line":411},22,[106,413,327],{"class":224},[106,415,240],{"class":112},[106,417,418],{"class":211},"WithDefaultFont",[106,420,246],{"class":112},[106,422,249],{"class":112},[106,424,399],{"class":252},[106,426,249],{"class":112},[106,428,228],{"class":112},[106,430,431],{"class":379}," 12",[106,433,345],{"class":112},[106,435,437],{"class":108,"line":436},23,[106,438,439],{"class":112},"    )\n",[106,441,443],{"class":108,"line":442},24,[106,444,124],{"emptyLinePlaceholder":123},[106,446,448,451,453,456,458,461],{"class":108,"line":447},25,[106,449,450],{"class":224},"    page ",[106,452,234],{"class":112},[106,454,455],{"class":224}," doc",[106,457,240],{"class":112},[106,459,460],{"class":211},"AddPage",[106,462,463],{"class":112},"()\n",[106,465,467,470,472,475,478,482,485,488,490,493,496],{"class":108,"line":466},26,[106,468,469],{"class":224},"    page",[106,471,240],{"class":112},[106,473,474],{"class":211},"AutoRow",[106,476,477],{"class":112},"(func(",[106,479,481],{"class":480},"sHdIc","r",[106,483,484],{"class":112}," *",[106,486,487],{"class":116},"template",[106,489,240],{"class":112},[106,491,492],{"class":116},"RowBuilder",[106,494,495],{"class":112},")",[106,497,218],{"class":112},[106,499,501,504,506,509,511,514,516,519,522,524,526,528,531,533],{"class":108,"line":500},27,[106,502,503],{"class":224},"        r",[106,505,240],{"class":112},[106,507,508],{"class":211},"Col",[106,510,246],{"class":112},[106,512,513],{"class":379},"12",[106,515,228],{"class":112},[106,517,518],{"class":112}," func(",[106,520,521],{"class":480},"c",[106,523,484],{"class":112},[106,525,487],{"class":116},[106,527,240],{"class":112},[106,529,530],{"class":116},"ColBuilder",[106,532,495],{"class":112},[106,534,218],{"class":112},[106,536,538,541,543,546,548,550,553,555],{"class":108,"line":537},28,[106,539,540],{"class":224},"            c",[106,542,240],{"class":112},[106,544,545],{"class":211},"Text",[106,547,246],{"class":112},[106,549,249],{"class":112},[106,551,552],{"class":252},"こんにちは、世界。",[106,554,249],{"class":112},[106,556,197],{"class":112},[106,558,560],{"class":108,"line":559},29,[106,561,562],{"class":112},"        })\n",[106,564,566],{"class":108,"line":565},30,[106,567,568],{"class":112},"    })\n",[106,570,572],{"class":108,"line":571},31,[106,573,124],{"emptyLinePlaceholder":123},[106,575,577,580,582,584,586,588,590,593],{"class":108,"line":576},32,[106,578,579],{"class":224},"    data",[106,581,228],{"class":112},[106,583,231],{"class":224},[106,585,234],{"class":112},[106,587,455],{"class":224},[106,589,240],{"class":112},[106,591,592],{"class":211},"Generate",[106,594,463],{"class":112},[106,596,598,600,602,604,606],{"class":108,"line":597},33,[106,599,263],{"class":130},[106,601,231],{"class":224},[106,603,268],{"class":112},[106,605,271],{"class":112},[106,607,218],{"class":112},[106,609,611,613,615,617,619,621],{"class":108,"line":610},34,[106,612,279],{"class":224},[106,614,240],{"class":112},[106,616,284],{"class":211},[106,618,246],{"class":112},[106,620,289],{"class":224},[106,622,197],{"class":112},[106,624,626],{"class":108,"line":625},35,[106,627,297],{"class":112},[106,629,631,633,635,637,639,641,644,646,648,651,653,655,658,660,663,666,668,670,672],{"class":108,"line":630},36,[106,632,263],{"class":130},[106,634,231],{"class":224},[106,636,234],{"class":112},[106,638,237],{"class":224},[106,640,240],{"class":112},[106,642,643],{"class":211},"WriteFile",[106,645,246],{"class":112},[106,647,249],{"class":112},[106,649,650],{"class":252},"hello.pdf",[106,652,249],{"class":112},[106,654,228],{"class":112},[106,656,657],{"class":224}," data",[106,659,228],{"class":112},[106,661,662],{"class":379}," 0o644",[106,664,665],{"class":112},");",[106,667,231],{"class":224},[106,669,268],{"class":112},[106,671,271],{"class":112},[106,673,218],{"class":112},[106,675,677,679,681,683,685,687],{"class":108,"line":676},37,[106,678,279],{"class":224},[106,680,240],{"class":112},[106,682,284],{"class":211},[106,684,246],{"class":112},[106,686,289],{"class":224},[106,688,197],{"class":112},[106,690,692],{"class":108,"line":691},38,[106,693,297],{"class":112},[106,695,697],{"class":108,"line":696},39,[106,698,699],{"class":112},"}\n",[19,701,702,703,706,707,710,711,713],{},"두 줄로 폰트 등록과 기본 설정이 끝난다. CGO도, ",[44,704,705],{},"AddUTF8Font"," 설정 단계도 필요 없다. ",[44,708,709],{},"□□□□□、□□。","로 나오던 것이 이 코드와 실제 ",[44,712,253],{},"를 나란히 두고 실행하면 제대로 된 글리프로 나온다.",[19,715,716,718,719,726],{},[44,717,253],{},"는 ",[720,721,725],"a",{"href":722,"rel":723},"https://fonts.google.com/noto/specimen/Noto+Sans+JP",[724],"nofollow","Google Fonts","에서 받는다.",[14,728,730],{"id":729},"어떤-원인인지-판별하기","어떤 원인인지 판별하기",[19,732,733],{},"봐야 할 곳은 세 군데다. 문서를 만드는 부분, 텍스트를 쓰는 부분, TTF 파일 그 자체.",[19,735,736,742,743,746,747,749],{},[39,737,738,739],{},"출력이 일관된 ",[44,740,741],{},"□□□"," (모든 네모가 같은 모양)이라면 원인 1, 2, 3 중 하나. PDF에는 폰트가 임베드됐지만 그 폰트에 글리프가 없는 상태다. Acrobat에서 PDF를 열고 ",[44,744,745],{},"파일 → 속성 → 폰트","에서 실제로 임베드된 폰트를 본다. Helvetica / Times / Courier뿐이면 원인 1. ",[44,748,399],{},"가 나열돼 있는데도 네모면 원인 2 또는 3.",[19,751,752,761,762,765,766,769],{},[39,753,754,755,757,758],{},"출력이 ",[44,756,87],{},"이나 ",[44,759,760],{},"ã\"ã‚\"ã«ã¡ã¯"," 같은 Latin 잡음이면 원인 4. 일본어 문자열이 gpdf에 도달하기 전에 다시 인코딩됐다. 가장 흔한 범인은 Excel이 Shift-JIS로 저장한 CSV를 ",[44,763,764],{},"os.ReadFile","로 읽어 UTF-8로 그대로 쓰는 경우, 또는 ",[44,767,768],{},"charset=utf-8","을 선언하지 않은 HTTP 엔드포인트. 고쳐야 할 건 디코더지 PDF가 아니다.",[19,771,772,775],{},[39,773,774],{},"섞여서 나옴"," — 일부는 정상, 일부는 네모 — 라면 폰트 커버리지가 부분적이라는 뜻. \"일본어 지원\"이라고 표기된 폰트가 히라가나·가타카나는 포함해도 鬱, 龠 같은 드문 한자를 빠뜨리는 경우가 있다. JIS X 0213을 커버하는 Noto Sans JP나 Source Han Sans JP로 바꾸면 해결된다.",[14,777,779],{"id":778},"원인-2-상세-폰트는-맞는데-패밀리명이-틀림","원인 2 상세: 폰트는 맞는데 패밀리명이 틀림",[19,781,782,783,787],{},"이게 까다로운 이유는 폰트가 ",[784,785,786],"em",{},"실제로"," 임베드됐기 때문이다 — 그냥 안 쓰일 뿐. 최소 재현:",[97,789,791],{"className":99,"code":790,"language":101,"meta":102,"style":102},"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",[44,792,793,808,831,837,841,845,870,901,924,928],{"__ignoreMap":102},[106,794,795,798,800,802,804,806],{"class":108,"line":109},[106,796,797],{"class":224},"doc ",[106,799,234],{"class":112},[106,801,313],{"class":224},[106,803,240],{"class":112},[106,805,318],{"class":211},[106,807,321],{"class":112},[106,809,810,813,815,817,819,821,823,825,827,829],{"class":108,"line":120},[106,811,812],{"class":224},"    gpdf",[106,814,240],{"class":112},[106,816,50],{"class":211},[106,818,246],{"class":112},[106,820,249],{"class":112},[106,822,399],{"class":252},[106,824,249],{"class":112},[106,826,228],{"class":112},[106,828,406],{"class":224},[106,830,345],{"class":112},[106,832,833],{"class":108,"line":127},[106,834,836],{"class":835},"sHwdD","    // WithDefaultFont 누락\n",[106,838,839],{"class":108,"line":137},[106,840,197],{"class":112},[106,842,843],{"class":108,"line":149},[106,844,124],{"emptyLinePlaceholder":123},[106,846,847,850,852,854,856,858,860,862,864,866,868],{"class":108,"line":159},[106,848,849],{"class":224},"page",[106,851,240],{"class":112},[106,853,474],{"class":211},[106,855,477],{"class":112},[106,857,481],{"class":480},[106,859,484],{"class":112},[106,861,487],{"class":116},[106,863,240],{"class":112},[106,865,492],{"class":116},[106,867,495],{"class":112},[106,869,218],{"class":112},[106,871,872,875,877,879,881,883,885,887,889,891,893,895,897,899],{"class":108,"line":164},[106,873,874],{"class":224},"    r",[106,876,240],{"class":112},[106,878,508],{"class":211},[106,880,246],{"class":112},[106,882,513],{"class":379},[106,884,228],{"class":112},[106,886,518],{"class":112},[106,888,521],{"class":480},[106,890,484],{"class":112},[106,892,487],{"class":116},[106,894,240],{"class":112},[106,896,530],{"class":116},[106,898,495],{"class":112},[106,900,218],{"class":112},[106,902,903,906,908,910,912,914,917,919,921],{"class":108,"line":174},[106,904,905],{"class":224},"        c",[106,907,240],{"class":112},[106,909,545],{"class":211},[106,911,246],{"class":112},[106,913,249],{"class":112},[106,915,916],{"class":252},"こんにちは",[106,918,249],{"class":112},[106,920,495],{"class":112},[106,922,923],{"class":835}," // 기본 폰트 = Helvetica로 그려짐\n",[106,925,926],{"class":108,"line":184},[106,927,568],{"class":112},[106,929,930],{"class":108,"line":194},[106,931,932],{"class":112},"})\n",[19,934,935,936,47,938,941,942,47,944,947,948,950,951,953,954,957,958,960,961,964],{},"수정: ",[44,937,318],{},[44,939,940],{},"gpdf.WithDefaultFont(\"NotoSansJP\", 12)","를 추가하거나, 일본어를 쓰는 모든 ",[44,943,59],{},[44,945,946],{},"template.FontFamily(\"NotoSansJP\")","를 넘긴다. ",[44,949,50],{},"의 패밀리명과 ",[44,952,59],{},"의 패밀리명은 ",[39,955,956],{},"대소문자까지 완전히 일치","해야 한다. ",[44,959,399],{},"와 ",[44,962,963],{},"notosansjp","는 gpdf 입장에서 다른 폰트다.",[14,966,968],{"id":967},"원인-3-상세-잘못된-ttf-파일","원인 3 상세: 잘못된 TTF 파일",[19,970,971,960,973,975],{},[44,972,77],{},[44,974,253],{},"는 다른 파일이다. 전자는 CJK 커버리지가 0인 Latin 폰트. 후자는 약 17,000자의 일본어 컷. 디렉토리 리스트에서 거의 똑같이 보이고, 자동완성이 틀린 쪽을 집어주기 쉽다.",[19,977,978],{},"gpdf는 등록 시점에 글리프 커버리지를 검증하지 않는다. 바이트를 주면 믿는다. 실패는 렌더링 시점의 두부 문자로만 드러난다.",[19,980,981],{},"빠른 확인:",[983,984,985,992,999],"ul",{},[36,986,987,988,991],{},"macOS: ",[44,989,990],{},"Font Book","에서 파일을 더블 클릭하면 글리프 그리드가 나온다",[36,993,994,995,998],{},"Linux: ",[44,996,997],{},"otfinfo -u NotoSans-Regular.ttf","가 유니코드 커버리지를 덤프한다",[36,1000,1001,1002,1007,1008,1011],{},"크로스플랫폼: ",[720,1003,1006],{"href":1004,"rel":1005},"https://github.com/fonttools/fonttools",[724],"fontTools","의 ",[44,1009,1010],{},"ttx -t cmap NotoSans-Regular.ttf","가 cmap 테이블을 XML로 내보낸다",[19,1013,1014],{},"목록에 U+3042 (あ)가 없으면 Latin 서브셋을 쥐고 있는 것이다.",[14,1016,1018],{"id":1017},"원인-4-상세-인코딩-손상","원인 4 상세: 인코딩 손상",[19,1020,1021,1022,1024],{},"이건 사실 gpdf와 무관하다. ",[44,1023,59],{},"에 넘긴 시점에 문자열이 이미 깨져 있다. 렌더 전에 출력해본다:",[97,1026,1028],{"className":99,"code":1027,"language":101,"meta":102,"style":102},"text := loadLabelFromSomewhere()\nfmt.Printf(\"%q\\n\", text) // 실제 rune 출력\nc.Text(text)\n",[44,1029,1030,1042,1075],{"__ignoreMap":102},[106,1031,1032,1035,1037,1040],{"class":108,"line":109},[106,1033,1034],{"class":224},"text ",[106,1036,234],{"class":112},[106,1038,1039],{"class":211}," loadLabelFromSomewhere",[106,1041,463],{"class":112},[106,1043,1044,1047,1049,1052,1054,1056,1060,1063,1065,1067,1070,1072],{"class":108,"line":120},[106,1045,1046],{"class":224},"fmt",[106,1048,240],{"class":112},[106,1050,1051],{"class":211},"Printf",[106,1053,246],{"class":112},[106,1055,249],{"class":112},[106,1057,1059],{"class":1058},"swJcz","%q",[106,1061,1062],{"class":224},"\\n",[106,1064,249],{"class":112},[106,1066,228],{"class":112},[106,1068,1069],{"class":224}," text",[106,1071,495],{"class":112},[106,1073,1074],{"class":835}," // 실제 rune 출력\n",[106,1076,1077,1079,1081,1083,1085,1088],{"class":108,"line":127},[106,1078,521],{"class":224},[106,1080,240],{"class":112},[106,1082,545],{"class":211},[106,1084,246],{"class":112},[106,1086,1087],{"class":224},"text",[106,1089,197],{"class":112},[19,1091,1092,1093,1096,1097,1100],{},"여기서 ",[44,1094,1095],{},"\"あいうえ\""," 대신 ",[44,1098,1099],{},"\"縺ゅ→縺\"","가 나오면 손상은 상류에서 일어났다. gpdf는 못 고친다 — UTF-8이 잘못 디코딩된 지점을 찾아 수정한다.",[19,1102,1103],{},"흔한 상류 범인:",[983,1105,1106,1116,1131],{},[36,1107,1108,1109,1111,1112,1115],{},"Excel이 Shift-JIS(CP949와 혼동 주의, 일본은 CP932)로 저장한 CSV를 ",[44,1110,764],{}," 후 바로 ",[44,1113,1114],{},"string()"," 캐스팅",[36,1117,1118,1119,1122,1123,1126,1127,1130],{},"이미 mojibake를 저장하고 있는 ",[44,1120,1121],{},"latin1"," 또는 ",[44,1124,1125],{},"utf8mb3"," 컬럼(",[44,1128,1129],{},"utf8mb4"," 아님)",[36,1132,1133,1136],{},[44,1134,1135],{},"Content-Type: application/json; charset=utf-8"," 선언이 없어서 Latin-1로 추측한 HTTP 응답",[14,1138,1140],{"id":1139},"한-가지-잊기-쉬운-엣지-케이스","한 가지 잊기 쉬운 엣지 케이스",[19,1142,1143,1144,1147,1148,1150,1151,1154,1155,1158,1159,1161,1162,1164],{},"gpdf의 서브셋 고정은 ",[44,1145,1146],{},"Generate()"," 시점에 일어난다. 문서 구성 중에 ",[44,1149,916],{},"를 그린 뒤 나중에 ",[44,1152,1153],{},"鬱陶しい","를 그려도, 두 번째도 서브셋에 제대로 추가된다. 하지만 ",[39,1156,1157],{},"이미 생성된 PDF를 Acrobat에서 열어 원본에 없던 한자를 타이핑","하면 그 자리는 두부가 된다. 서브셋은 ",[44,1160,1146],{}," 순간에 얼어붙기 때문이다. PDF를 후편집하지 말고 Go에서 다시 ",[44,1163,1146],{}," 한다.",[14,1166,1168],{"id":1167},"관련-레시피","관련 레시피",[983,1170,1171,1181,1192],{},[36,1172,1173,1177,1178,1180],{},[720,1174,1176],{"href":1175},"/ko/blog/embed-japanese-font","gpdf에서 일본어 폰트를 임베드하려면?"," — 볼드/이탤릭 변형과 다중 CJK 문서를 포함한 ",[44,1179,50],{}," 전체 안내",[36,1182,1183,1187,1188,1191],{},[720,1184,1186],{"href":1185},"/ko/blog/noto-sans-jp-with-gpdf","gpdf에서 Noto Sans JP를 사용하려면?"," — 어떤 Noto 파일을 고를지, ",[44,1189,1190],{},"go:embed","로 배포를 어떻게 단순화할지",[36,1193,1194,1198],{},[720,1195,1197],{"href":1196},"/ko/blog/japanese-pdf-in-go","Go에서 일본어 PDF 만들기 결정판 가이드(2026)"," — 폰트, 세로쓰기, ruby, 일본어 특유 레이아웃을 다루는 장편 가이드",[14,1200,1202],{"id":1201},"gpdf를-써보자","gpdf를 써보자",[19,1204,1205],{},"gpdf는 Go용 PDF 생성 라이브러리다. MIT 라이선스, 외부 의존성 0, 네이티브 CJK 지원.",[97,1207,1211],{"className":1208,"code":1209,"language":1210,"meta":102,"style":102},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go get github.com/gpdf-dev/gpdf\n","bash",[44,1212,1213],{"__ignoreMap":102},[106,1214,1215,1217,1220],{"class":108,"line":109},[106,1216,101],{"class":116},[106,1218,1219],{"class":252}," get",[106,1221,1222],{"class":252}," github.com/gpdf-dev/gpdf\n",[19,1224,1225,1230,1231],{},[720,1226,1229],{"href":1227,"rel":1228},"https://github.com/gpdf-dev/gpdf",[724],"⭐ GitHub에서 Star"," · ",[720,1232,1235],{"href":1233,"rel":1234},"https://gpdf.dev/ko/docs/quickstart",[724],"문서 읽기",[1237,1238,1239],"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);}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":102,"searchDepth":120,"depth":120,"links":1241},[1242,1243,1244,1245,1246,1247,1248,1249,1250,1251],{"id":16,"depth":120,"text":17},{"id":24,"depth":120,"text":25},{"id":91,"depth":120,"text":92},{"id":729,"depth":120,"text":730},{"id":778,"depth":120,"text":779},{"id":967,"depth":120,"text":968},{"id":1017,"depth":120,"text":1018},{"id":1139,"depth":120,"text":1140},{"id":1167,"depth":120,"text":1168},{"id":1201,"depth":120,"text":1202},"2026-04-17","일본어가 □ 로 나오는 건 대부분 폰트 미등록. 흔한 4가지 원인과 최단 수정법을 정리한다.",false,"md",{"name":1257,"totalTime":1258,"tools":1259,"steps":1262},"gpdf 문서의 두부 문자를 진단하고 고친다","PT15M",[1260,1261],"Go 1.22+","CJK를 지원하는 TTF (예: NotoSansJP-Regular.ttf)",[1263,1266,1269,1272,1275],{"name":1264,"text":1265},"증상이 두부 문자인지 mojibake인지 구분한다","PDF를 연다. 일본어가 빈 사각형(□)이면 폰트 조회 실패. 縺ゅ→縺 같은 형태면 gpdf에 도달하기 전에 UTF-8이 잘못 디코딩된 것이다. 해결 경로가 완전히 다르다.",{"name":1267,"text":1268},"CJK 폰트가 등록되어 있는지 확인한다","문서 생성 부분에서 gpdf.WithFont를 검색한다. CJK TTF가 등록되어 있지 않으면 gpdf는 PDF Base-14 폰트로 폴백하고, 이들은 CJK 코드포인트를 전혀 다루지 않는다.",{"name":1270,"text":1271},"각 c.Text의 폰트 패밀리명을 검증한다","WithDefaultFont가 없다면 일본어를 그리는 모든 c.Text에 template.FontFamily(\"NotoSansJP\")를 명시해야 한다. 이름이 맞지 않으면 조용히 기본 폰트로 폴백한다.",{"name":1273,"text":1274},"TTF 파일에 실제로 CJK 글리프가 있는지 확인한다","NotoSans-Regular.ttf (Latin 서브셋) 와 NotoSansJP-Regular.ttf는 다른 파일이다. gpdf는 등록 시점에 커버리지를 검증하지 않는다.",{"name":1276,"text":1277},"두 개의 뷰어에서 재확인한다","생성한 PDF를 Adobe Acrobat과 Chrome 양쪽에서 연다. 둘 다 정상 렌더링되어야 OK. 한쪽만 정상이면 글리프는 임베드됐지만 서브셋 등록이 어긋난 경우다.",null,{},"/ko/blog/tofu-boxes-japanese",{"title":6,"description":1253},"ko/blog/008.tofu-boxes-japanese",[1284,1285,1286],"recipe","troubleshooting","cjk","GIOTuFQGRC84xnT5i01kWQ0hpkKcc8JRyxTc4jsIS3Y",{"id":1289,"title":1290,"author":1291,"body":1292,"date":1252,"description":2524,"draft":1254,"extension":1255,"howTo":2525,"image":1278,"meta":2546,"navigation":123,"path":2547,"seo":2548,"stem":2549,"tags":2550,"updated":1278,"__hash__":2552},"blogKo/ko/blog/009.ipaex-gothic-gpdf.md","gpdf에서 IPAex 고딕(IPAex Gothic)을 사용하려면?",{"name":8,"url":9},{"type":11,"value":1293,"toc":2513},[1294,1296,1308,1310,1324,1328,1864,1884,1888,1891,1953,1960,1972,1976,1979,1982,1990,2022,2028,2312,2315,2319,2328,2353,2360,2363,2367,2452,2455,2457,2486,2488,2490,2502,2510],[14,1295,17],{"id":16},[19,1297,1298,1301,1302,1307],{},[720,1299,337],{"href":1227,"rel":1300},[724]," 문서에서 IPAex Gothic — 일본 ",[720,1303,1306],{"href":1304,"rel":1305},"https://moji.or.jp/ipafont/",[724],"정보처리추진기구","(IPA)가 관리하는 프로포셔널 서양 문자 고딕체 — 를 쓰고 싶다. 전형적인 사용처: e-Tax PDF 첨부, 정부 제출 서류, 2010년대 초부터 IPAex로 통일해 온 사내 스타일. 매번 걸리는 곳은 세 군데다. 어떤 파일을 고를지, Bold가 없는 문제를 어떻게 다룰지, IPA Font License가 실제로 무엇을 요구하는지.",[14,1309,25],{"id":24},[19,1311,1312,1315,1316,1319,1320,1323],{},[44,1313,1314],{},"ipaexg.ttf","를 ",[44,1317,1318],{},"gpdf.WithFont(\"IPAexGothic\", bytes)","로 등록하고 기본 폰트로 설정한다. Bold는 ",[44,1321,1322],{},"template.Bold()","로 합성하거나 IPAex 명조와 페어링한다. IPAex는 Regular 한 가지 굵기만 제공하기 때문이다. 바이너리 배포 시 라이선스 전문을 동봉한다.",[14,1325,1327],{"id":1326},"완성된-예제","완성된 예제",[97,1329,1331],{"className":99,"code":1330,"language":101,"meta":102,"style":102},"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",[44,1332,1333,1339,1343,1349,1357,1365,1369,1377,1385,1393,1397,1401,1411,1437,1449,1463,1467,1471,1485,1503,1534,1557,1580,1584,1588,1602,1626,1656,1701,1720,1739,1743,1747,1751,1769,1781,1795,1799,1840,1854,1859],{"__ignoreMap":102},[106,1334,1335,1337],{"class":108,"line":109},[106,1336,113],{"class":112},[106,1338,117],{"class":116},[106,1340,1341],{"class":108,"line":120},[106,1342,124],{"emptyLinePlaceholder":123},[106,1344,1345,1347],{"class":108,"line":127},[106,1346,131],{"class":130},[106,1348,134],{"class":112},[106,1350,1351,1353,1355],{"class":108,"line":137},[106,1352,140],{"class":112},[106,1354,143],{"class":116},[106,1356,146],{"class":112},[106,1358,1359,1361,1363],{"class":108,"line":149},[106,1360,140],{"class":112},[106,1362,154],{"class":116},[106,1364,146],{"class":112},[106,1366,1367],{"class":108,"line":159},[106,1368,124],{"emptyLinePlaceholder":123},[106,1370,1371,1373,1375],{"class":108,"line":164},[106,1372,140],{"class":112},[106,1374,169],{"class":116},[106,1376,146],{"class":112},[106,1378,1379,1381,1383],{"class":108,"line":174},[106,1380,140],{"class":112},[106,1382,179],{"class":116},[106,1384,146],{"class":112},[106,1386,1387,1389,1391],{"class":108,"line":184},[106,1388,140],{"class":112},[106,1390,189],{"class":116},[106,1392,146],{"class":112},[106,1394,1395],{"class":108,"line":194},[106,1396,197],{"class":112},[106,1398,1399],{"class":108,"line":200},[106,1400,124],{"emptyLinePlaceholder":123},[106,1402,1403,1405,1407,1409],{"class":108,"line":205},[106,1404,208],{"class":112},[106,1406,212],{"class":211},[106,1408,215],{"class":112},[106,1410,218],{"class":112},[106,1412,1413,1415,1417,1419,1421,1423,1425,1427,1429,1431,1433,1435],{"class":108,"line":221},[106,1414,225],{"class":224},[106,1416,228],{"class":112},[106,1418,231],{"class":224},[106,1420,234],{"class":112},[106,1422,237],{"class":224},[106,1424,240],{"class":112},[106,1426,243],{"class":211},[106,1428,246],{"class":112},[106,1430,249],{"class":112},[106,1432,1314],{"class":252},[106,1434,249],{"class":112},[106,1436,197],{"class":112},[106,1438,1439,1441,1443,1445,1447],{"class":108,"line":260},[106,1440,263],{"class":130},[106,1442,231],{"class":224},[106,1444,268],{"class":112},[106,1446,271],{"class":112},[106,1448,218],{"class":112},[106,1450,1451,1453,1455,1457,1459,1461],{"class":108,"line":276},[106,1452,279],{"class":224},[106,1454,240],{"class":112},[106,1456,284],{"class":211},[106,1458,246],{"class":112},[106,1460,289],{"class":224},[106,1462,197],{"class":112},[106,1464,1465],{"class":108,"line":294},[106,1466,297],{"class":112},[106,1468,1469],{"class":108,"line":300},[106,1470,124],{"emptyLinePlaceholder":123},[106,1472,1473,1475,1477,1479,1481,1483],{"class":108,"line":305},[106,1474,308],{"class":224},[106,1476,234],{"class":112},[106,1478,313],{"class":224},[106,1480,240],{"class":112},[106,1482,318],{"class":211},[106,1484,321],{"class":112},[106,1486,1487,1489,1491,1493,1495,1497,1499,1501],{"class":108,"line":324},[106,1488,327],{"class":224},[106,1490,240],{"class":112},[106,1492,332],{"class":211},[106,1494,246],{"class":112},[106,1496,337],{"class":224},[106,1498,240],{"class":112},[106,1500,342],{"class":224},[106,1502,345],{"class":112},[106,1504,1505,1507,1509,1511,1513,1515,1517,1519,1521,1523,1525,1527,1529,1532],{"class":108,"line":348},[106,1506,327],{"class":224},[106,1508,240],{"class":112},[106,1510,355],{"class":211},[106,1512,246],{"class":112},[106,1514,360],{"class":224},[106,1516,240],{"class":112},[106,1518,365],{"class":211},[106,1520,246],{"class":112},[106,1522,360],{"class":224},[106,1524,240],{"class":112},[106,1526,374],{"class":211},[106,1528,246],{"class":112},[106,1530,1531],{"class":379},"25",[106,1533,383],{"class":112},[106,1535,1536,1538,1540,1542,1544,1546,1549,1551,1553,1555],{"class":108,"line":386},[106,1537,327],{"class":224},[106,1539,240],{"class":112},[106,1541,50],{"class":211},[106,1543,246],{"class":112},[106,1545,249],{"class":112},[106,1547,1548],{"class":252},"IPAexGothic",[106,1550,249],{"class":112},[106,1552,228],{"class":112},[106,1554,406],{"class":224},[106,1556,345],{"class":112},[106,1558,1559,1561,1563,1565,1567,1569,1571,1573,1575,1578],{"class":108,"line":411},[106,1560,327],{"class":224},[106,1562,240],{"class":112},[106,1564,418],{"class":211},[106,1566,246],{"class":112},[106,1568,249],{"class":112},[106,1570,1548],{"class":252},[106,1572,249],{"class":112},[106,1574,228],{"class":112},[106,1576,1577],{"class":379}," 10.5",[106,1579,345],{"class":112},[106,1581,1582],{"class":108,"line":436},[106,1583,439],{"class":112},[106,1585,1586],{"class":108,"line":442},[106,1587,124],{"emptyLinePlaceholder":123},[106,1589,1590,1592,1594,1596,1598,1600],{"class":108,"line":447},[106,1591,450],{"class":224},[106,1593,234],{"class":112},[106,1595,455],{"class":224},[106,1597,240],{"class":112},[106,1599,460],{"class":211},[106,1601,463],{"class":112},[106,1603,1604,1606,1608,1610,1612,1614,1616,1618,1620,1622,1624],{"class":108,"line":466},[106,1605,469],{"class":224},[106,1607,240],{"class":112},[106,1609,474],{"class":211},[106,1611,477],{"class":112},[106,1613,481],{"class":480},[106,1615,484],{"class":112},[106,1617,487],{"class":116},[106,1619,240],{"class":112},[106,1621,492],{"class":116},[106,1623,495],{"class":112},[106,1625,218],{"class":112},[106,1627,1628,1630,1632,1634,1636,1638,1640,1642,1644,1646,1648,1650,1652,1654],{"class":108,"line":500},[106,1629,503],{"class":224},[106,1631,240],{"class":112},[106,1633,508],{"class":211},[106,1635,246],{"class":112},[106,1637,513],{"class":379},[106,1639,228],{"class":112},[106,1641,518],{"class":112},[106,1643,521],{"class":480},[106,1645,484],{"class":112},[106,1647,487],{"class":116},[106,1649,240],{"class":112},[106,1651,530],{"class":116},[106,1653,495],{"class":112},[106,1655,218],{"class":112},[106,1657,1658,1660,1662,1664,1666,1668,1671,1673,1675,1678,1680,1683,1685,1688,1691,1693,1695,1698],{"class":108,"line":537},[106,1659,540],{"class":224},[106,1661,240],{"class":112},[106,1663,545],{"class":211},[106,1665,246],{"class":112},[106,1667,249],{"class":112},[106,1669,1670],{"class":252},"請求書",[106,1672,249],{"class":112},[106,1674,228],{"class":112},[106,1676,1677],{"class":224}," template",[106,1679,240],{"class":112},[106,1681,1682],{"class":211},"FontSize",[106,1684,246],{"class":112},[106,1686,1687],{"class":379},"24",[106,1689,1690],{"class":112},"),",[106,1692,1677],{"class":224},[106,1694,240],{"class":112},[106,1696,1697],{"class":211},"Bold",[106,1699,1700],{"class":112},"())\n",[106,1702,1703,1705,1707,1709,1711,1713,1716,1718],{"class":108,"line":559},[106,1704,540],{"class":224},[106,1706,240],{"class":112},[106,1708,545],{"class":211},[106,1710,246],{"class":112},[106,1712,249],{"class":112},[106,1714,1715],{"class":252},"令和8年4月17日発行",[106,1717,249],{"class":112},[106,1719,197],{"class":112},[106,1721,1722,1724,1726,1728,1730,1732,1735,1737],{"class":108,"line":565},[106,1723,540],{"class":224},[106,1725,240],{"class":112},[106,1727,545],{"class":211},[106,1729,246],{"class":112},[106,1731,249],{"class":112},[106,1733,1734],{"class":252},"金額: ¥100,000 (税込)",[106,1736,249],{"class":112},[106,1738,197],{"class":112},[106,1740,1741],{"class":108,"line":571},[106,1742,562],{"class":112},[106,1744,1745],{"class":108,"line":576},[106,1746,568],{"class":112},[106,1748,1749],{"class":108,"line":597},[106,1750,124],{"emptyLinePlaceholder":123},[106,1752,1753,1755,1757,1759,1761,1763,1765,1767],{"class":108,"line":610},[106,1754,579],{"class":224},[106,1756,228],{"class":112},[106,1758,231],{"class":224},[106,1760,234],{"class":112},[106,1762,455],{"class":224},[106,1764,240],{"class":112},[106,1766,592],{"class":211},[106,1768,463],{"class":112},[106,1770,1771,1773,1775,1777,1779],{"class":108,"line":625},[106,1772,263],{"class":130},[106,1774,231],{"class":224},[106,1776,268],{"class":112},[106,1778,271],{"class":112},[106,1780,218],{"class":112},[106,1782,1783,1785,1787,1789,1791,1793],{"class":108,"line":630},[106,1784,279],{"class":224},[106,1786,240],{"class":112},[106,1788,284],{"class":211},[106,1790,246],{"class":112},[106,1792,289],{"class":224},[106,1794,197],{"class":112},[106,1796,1797],{"class":108,"line":676},[106,1798,297],{"class":112},[106,1800,1801,1803,1805,1807,1809,1811,1813,1815,1817,1820,1822,1824,1826,1828,1830,1832,1834,1836,1838],{"class":108,"line":691},[106,1802,263],{"class":130},[106,1804,231],{"class":224},[106,1806,234],{"class":112},[106,1808,237],{"class":224},[106,1810,240],{"class":112},[106,1812,643],{"class":211},[106,1814,246],{"class":112},[106,1816,249],{"class":112},[106,1818,1819],{"class":252},"invoice.pdf",[106,1821,249],{"class":112},[106,1823,228],{"class":112},[106,1825,657],{"class":224},[106,1827,228],{"class":112},[106,1829,662],{"class":379},[106,1831,665],{"class":112},[106,1833,231],{"class":224},[106,1835,268],{"class":112},[106,1837,271],{"class":112},[106,1839,218],{"class":112},[106,1841,1842,1844,1846,1848,1850,1852],{"class":108,"line":696},[106,1843,279],{"class":224},[106,1845,240],{"class":112},[106,1847,284],{"class":211},[106,1849,246],{"class":112},[106,1851,289],{"class":224},[106,1853,197],{"class":112},[106,1855,1857],{"class":108,"line":1856},40,[106,1858,297],{"class":112},[106,1860,1862],{"class":108,"line":1861},41,[106,1863,699],{"class":112},[19,1865,1866,1870,1871,1874,1875,1315,1877,1880,1881,240],{},[720,1867,1869],{"href":1304,"rel":1868},[724],"moji.or.jp/ipafont","에서 ",[44,1872,1873],{},"IPAex00401.zip","을 받아 ",[44,1876,1314],{},[44,1878,1879],{},"main.go"," 옆에 두고 ",[44,1882,1883],{},"go run main.go",[14,1885,1887],{"id":1886},"어느-ipa-파일이-맞는가","어느 IPA 파일이 맞는가",[19,1889,1890],{},"zip을 열면 TTF 세 개와 라이선스가 들어 있다. 이름이 비슷해 혼동하기 쉽다:",[1892,1893,1894,1907],"table",{},[1895,1896,1897],"thead",{},[1898,1899,1900,1904],"tr",{},[1901,1902,1903],"th",{},"파일",[1901,1905,1906],{},"내용",[1908,1909,1910,1923,1936],"tbody",{},[1898,1911,1912,1917],{},[1913,1914,1915],"td",{},[44,1916,1314],{},[1913,1918,1919,1922],{},[39,1920,1921],{},"IPAex Gothic"," — 산세리프, 서양 문자는 프로포셔널. 일반 문서는 이것.",[1898,1924,1925,1930],{},[1913,1926,1927],{},[44,1928,1929],{},"ipaexm.ttf",[1913,1931,1932,1935],{},[39,1933,1934],{},"IPAex Mincho"," — 세리프(명조), 서양 문자는 프로포셔널. 본문 길거나 고딕과 짝지어 강조할 때.",[1898,1937,1938,1943],{},[1913,1939,1940],{},[44,1941,1942],{},"ipag.ttf",[1913,1944,1945,1948,1949,1952],{},[39,1946,1947],{},"IPA Gothic"," (\"ex\" 없음) — 산세리프, ",[39,1950,1951],{},"서양 문자가 고정폭",". 요즘은 잘 안 쓴다.",[19,1954,1955,1956,1959],{},"IPAex의 \"ex\"는 extended proportional의 약어다. 원본 IPA 폰트는 서양 문자를 CJK 전각 격자에 맞춰 배치하기 때문에 영문·국문 혼용 시 늘어져 보인다. IPAex는 서양 문자를 프로포셔널로 만들면서 CJK는 기본 격자에 유지한다. 영문 단어, URL, 숫자가 들어가는 거의 모든 업무 문서에서 ",[39,1957,1958],{},"IPAex","가 맞다.",[19,1961,1962,1963,1965,1966,1968,1969,1971],{},"기존 프로젝트가 ",[44,1964,1942],{},"를 쓴다면 대개 역사적 이유다(원본 IPA Gothic 2003년, IPAex 2010년 출시). 패밀리명을 ",[44,1967,1548],{},"으로 유지하고 파일만 ",[44,1970,1314],{},"로 바꾸면 코드는 한 줄만 수정하면 된다.",[14,1973,1975],{"id":1974},"bold-파일이-없는-문제","Bold 파일이 없는 문제",[19,1977,1978],{},"IPAex는 패밀리당 Regular 한 굵기만 배포된다. 9단 굵기를 내는 Noto Sans JP에 비하면 눈에 띄는 단점이고, IPAex를 고려했다가 접는 가장 흔한 이유이기도 하다.",[19,1980,1981],{},"gpdf에서 대응법은 둘이다.",[19,1983,1984,42,1987,1989],{},[39,1985,1986],{},"합성 볼드.",[44,1988,1322],{},"가 Regular 글리프 위에 스트로크를 덧입힌다. 타이포그래피 관점에서는 치트다 — 진짜 볼드 굵기는 두꺼운 획으로 새로 그려진 윤곽을 가진다. 하지만 10 pt 이상 인보이스 제목이나 표 라벨이라면 독자 대부분에게는 구분되지 않는다:",[97,1991,1993],{"className":99,"code":1992,"language":101,"meta":102,"style":102},"c.Text(\"合計金額\", template.Bold())\n",[44,1994,1995],{"__ignoreMap":102},[106,1996,1997,1999,2001,2003,2005,2007,2010,2012,2014,2016,2018,2020],{"class":108,"line":109},[106,1998,521],{"class":224},[106,2000,240],{"class":112},[106,2002,545],{"class":211},[106,2004,246],{"class":112},[106,2006,249],{"class":112},[106,2008,2009],{"class":252},"合計金額",[106,2011,249],{"class":112},[106,2013,228],{"class":112},[106,2015,1677],{"class":224},[106,2017,240],{"class":112},[106,2019,1697],{"class":211},[106,2021,1700],{"class":112},[19,2023,2024,2027],{},[39,2025,2026],{},"IPAex 명조와 페어링."," 일본 조판의 고전적인 강조법은 볼드화가 아니라 세리프/산세리프 전환이다. 두 패밀리를 함께 등록한다:",[97,2029,2031],{"className":99,"code":2030,"language":101,"meta":102,"style":102},"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",[44,2032,2033,2061,2088,2092,2106,2129,2153,2175,2179,2183,2207,2237,2285,2304,2308],{"__ignoreMap":102},[106,2034,2035,2038,2040,2043,2045,2047,2049,2051,2053,2055,2057,2059],{"class":108,"line":109},[106,2036,2037],{"class":224},"gothic",[106,2039,228],{"class":112},[106,2041,2042],{"class":224}," _ ",[106,2044,234],{"class":112},[106,2046,237],{"class":224},[106,2048,240],{"class":112},[106,2050,243],{"class":211},[106,2052,246],{"class":112},[106,2054,249],{"class":112},[106,2056,1314],{"class":252},[106,2058,249],{"class":112},[106,2060,197],{"class":112},[106,2062,2063,2066,2068,2070,2072,2074,2076,2078,2080,2082,2084,2086],{"class":108,"line":120},[106,2064,2065],{"class":224},"mincho",[106,2067,228],{"class":112},[106,2069,2042],{"class":224},[106,2071,234],{"class":112},[106,2073,237],{"class":224},[106,2075,240],{"class":112},[106,2077,243],{"class":211},[106,2079,246],{"class":112},[106,2081,249],{"class":112},[106,2083,1929],{"class":252},[106,2085,249],{"class":112},[106,2087,197],{"class":112},[106,2089,2090],{"class":108,"line":127},[106,2091,124],{"emptyLinePlaceholder":123},[106,2093,2094,2096,2098,2100,2102,2104],{"class":108,"line":137},[106,2095,797],{"class":224},[106,2097,234],{"class":112},[106,2099,313],{"class":224},[106,2101,240],{"class":112},[106,2103,318],{"class":211},[106,2105,321],{"class":112},[106,2107,2108,2110,2112,2114,2116,2118,2120,2122,2124,2127],{"class":108,"line":149},[106,2109,812],{"class":224},[106,2111,240],{"class":112},[106,2113,50],{"class":211},[106,2115,246],{"class":112},[106,2117,249],{"class":112},[106,2119,1548],{"class":252},[106,2121,249],{"class":112},[106,2123,228],{"class":112},[106,2125,2126],{"class":224}," gothic",[106,2128,345],{"class":112},[106,2130,2131,2133,2135,2137,2139,2141,2144,2146,2148,2151],{"class":108,"line":159},[106,2132,812],{"class":224},[106,2134,240],{"class":112},[106,2136,50],{"class":211},[106,2138,246],{"class":112},[106,2140,249],{"class":112},[106,2142,2143],{"class":252},"IPAexMincho",[106,2145,249],{"class":112},[106,2147,228],{"class":112},[106,2149,2150],{"class":224}," mincho",[106,2152,345],{"class":112},[106,2154,2155,2157,2159,2161,2163,2165,2167,2169,2171,2173],{"class":108,"line":164},[106,2156,812],{"class":224},[106,2158,240],{"class":112},[106,2160,418],{"class":211},[106,2162,246],{"class":112},[106,2164,249],{"class":112},[106,2166,1548],{"class":252},[106,2168,249],{"class":112},[106,2170,228],{"class":112},[106,2172,1577],{"class":379},[106,2174,345],{"class":112},[106,2176,2177],{"class":108,"line":174},[106,2178,197],{"class":112},[106,2180,2181],{"class":108,"line":184},[106,2182,124],{"emptyLinePlaceholder":123},[106,2184,2185,2187,2189,2191,2193,2195,2197,2199,2201,2203,2205],{"class":108,"line":194},[106,2186,849],{"class":224},[106,2188,240],{"class":112},[106,2190,474],{"class":211},[106,2192,477],{"class":112},[106,2194,481],{"class":480},[106,2196,484],{"class":112},[106,2198,487],{"class":116},[106,2200,240],{"class":112},[106,2202,492],{"class":116},[106,2204,495],{"class":112},[106,2206,218],{"class":112},[106,2208,2209,2211,2213,2215,2217,2219,2221,2223,2225,2227,2229,2231,2233,2235],{"class":108,"line":200},[106,2210,874],{"class":224},[106,2212,240],{"class":112},[106,2214,508],{"class":211},[106,2216,246],{"class":112},[106,2218,513],{"class":379},[106,2220,228],{"class":112},[106,2222,518],{"class":112},[106,2224,521],{"class":480},[106,2226,484],{"class":112},[106,2228,487],{"class":116},[106,2230,240],{"class":112},[106,2232,530],{"class":116},[106,2234,495],{"class":112},[106,2236,218],{"class":112},[106,2238,2239,2241,2243,2245,2247,2249,2251,2253,2255,2257,2259,2262,2264,2266,2268,2270,2272,2274,2276,2278,2280,2282],{"class":108,"line":205},[106,2240,905],{"class":224},[106,2242,240],{"class":112},[106,2244,545],{"class":211},[106,2246,246],{"class":112},[106,2248,249],{"class":112},[106,2250,1670],{"class":252},[106,2252,249],{"class":112},[106,2254,228],{"class":112},[106,2256,1677],{"class":224},[106,2258,240],{"class":112},[106,2260,2261],{"class":211},"FontFamily",[106,2263,246],{"class":112},[106,2265,249],{"class":112},[106,2267,2143],{"class":252},[106,2269,249],{"class":112},[106,2271,1690],{"class":112},[106,2273,1677],{"class":224},[106,2275,240],{"class":112},[106,2277,1682],{"class":211},[106,2279,246],{"class":112},[106,2281,1687],{"class":379},[106,2283,2284],{"class":112},"))\n",[106,2286,2287,2289,2291,2293,2295,2297,2300,2302],{"class":108,"line":221},[106,2288,905],{"class":224},[106,2290,240],{"class":112},[106,2292,545],{"class":211},[106,2294,246],{"class":112},[106,2296,249],{"class":112},[106,2298,2299],{"class":252},"ご請求内容は下記の通りです。",[106,2301,249],{"class":112},[106,2303,197],{"class":112},[106,2305,2306],{"class":108,"line":260},[106,2307,568],{"class":112},[106,2309,2310],{"class":108,"line":276},[106,2311,932],{"class":112},[19,2313,2314],{},"일본의 청첩장, 공식 보고서에서 흔히 보이는 조합이다 — 제목은 명조, 본문은 고딕. 문서가 관공서로 갈 예정이라면 이 조합이 저쪽에서 기대하는 모양새일 가능성이 높다.",[14,2316,2318],{"id":2317},"ipa-font-license-간단히","IPA Font License 간단히",[19,2320,2321,2322,2327],{},"IPAex는 SIL OFL이 아니라 ",[720,2323,2326],{"href":2324,"rel":2325},"https://opensource.org/licenses/IPA",[724],"IPA Font License Agreement v1.0","이다 (OSI 승인). 전반적으로 관대하지만 짚어둘 두 가지:",[33,2329,2330,2343],{},[36,2331,2332,42,2335,2338,2339,2342],{},[39,2333,2334],{},"라이선스 전문을 폰트 바이너리와 함께 유지한다.",[44,2336,2337],{},"//go:embed","로 TTF를 Go 바이너리에 넣는다면 라이선스 파일도 같이 넣는다. 프로젝트 루트에 ",[44,2340,2341],{},"LICENSES/IPA-FONT-1.0.txt","를 두면 대부분의 배포 상황에서 충분하다.",[36,2344,2345,2348,2349,2352],{},[39,2346,2347],{},"폰트 이름을 바꾸지 않는다."," TTF 자체를 수정해 재배포하는 경우, 파생물에는 \"IPA\"나 \"IPAex\"를 포함하지 않는 다른 이름을 붙여야 한다. 단, 이 제약은 렌더링 시의 글리프 서브셋에는 ",[39,2350,2351],{},"적용되지 않는다",". 라이선스 제3조 4항이 폰트로 생성한 \"출력 문서\"를 명명 제약에서 명시적으로 제외한다.",[19,2354,2355,2356,2359],{},"정리하자면, ",[44,2357,2358],{},"doc.Generate()","에서 gpdf가 서브셋을 만드는 것은 라이선스상 안전하다. PDF에 포함되는 서브셋은 다른 이름을 필요로 하지 않고 \"파생 폰트 프로그램\" 조항을 트리거하지도 않는다. 폰트를 재배포하는 것이 아니라 문서를 만드는 것이다.",[19,2361,2362],{},"덧붙이자면, gpdf 코어 OSS 저장소에는 IPAex를 넣지 않는다 (golden 테스트는 Noto 계열 SIL OFL 폰트를 쓴다). 하위 사용자가 자기 프로젝트 최상위 LICENSE와 IPA 라이선스 호환성을 일일이 따지지 않도록 하기 위해서다. 애플리케이션에서 IPAex를 쓰는 것은 그 프로젝트의 판단이지 우리의 결정이 아니다.",[14,2364,2366],{"id":2365},"ipaex-vs-noto-sans-jp-어느-쪽을-고르나","IPAex vs Noto Sans JP, 어느 쪽을 고르나",[1892,2368,2369,2381],{},[1895,2370,2371],{},[1898,2372,2373,2376,2378],{},[1901,2374,2375],{},"축",[1901,2377,1921],{},[1901,2379,2380],{},"Noto Sans JP",[1908,2382,2383,2394,2405,2416,2430,2441],{},[1898,2384,2385,2388,2391],{},[1913,2386,2387],{},"굵기 수",[1913,2389,2390],{},"1 (Regular)",[1913,2392,2393],{},"9 (Thin → Black)",[1898,2395,2396,2399,2402],{},[1913,2397,2398],{},"라이선스",[1913,2400,2401],{},"IPA Font License v1.0",[1913,2403,2404],{},"SIL OFL 1.1",[1898,2406,2407,2410,2413],{},[1913,2408,2409],{},"서양 문자",[1913,2411,2412],{},"프로포셔널(IPAex) 또는 고정폭(IPA)",[1913,2414,2415],{},"프로포셔널",[1898,2417,2418,2421,2427],{},[1913,2419,2420],{},"기본 설치",[1913,2422,2423,2424],{},"일부 일본 Linux 배포판, TeX Live ",[44,2425,2426],{},"ptex-fonts",[1913,2428,2429],{},"Android, ChromeOS",[1898,2431,2432,2435,2438],{},[1913,2433,2434],{},"전형적 용도",[1913,2436,2437],{},"일본 정부, 법률, 학술",[1913,2439,2440],{},"소비자 웹, 국제용",[1898,2442,2443,2446,2449],{},[1913,2444,2445],{},"파일 크기",[1913,2447,2448],{},"7.5 MB (Gothic)",[1913,2450,2451],{},"5 MB (Regular 단독)",[19,2453,2454],{},"출력이 일본의 제도적 경계를 넘는 경우 — e-Tax PDF 첨부, 법원 제출 문서, 일본 저널로의 학술 논문 제출 — 에는 IPAex를 고른다. 그 생태계의 평가자, 심사자, OCR 도구가 IPA에 맞춰져 있기 때문이다. 그 외에는 Noto Sans JP로 충분하다. 렌더링 결과는 실용상 거의 같고, 선택 기준은 미감이 아니라 \"어느 생태계에 출력하느냐\"다.",[14,2456,1168],{"id":1167},[983,2458,2459,2464,2469,2475],{},[36,2460,2461,2463],{},[720,2462,1176],{"href":1175}," — 모든 CJK TTF에 적용되는 일반 레시피",[36,2465,2466,2468],{},[720,2467,1186],{"href":1185}," — SIL OFL에 9굵기가 있는 대안",[36,2470,2471,2474],{},[720,2472,2473],{"href":1280},"gpdf로 만든 PDF에서 일본어가 네모로 나올 때"," — 글리프가 표시되지 않을 때의 트러블슈팅",[36,2476,2477,2482,2483,2485],{},[720,2478,2481],{"href":2479,"rel":2480},"https://gpdf.dev/ko/docs/guide/fonts",[724],"폰트 가이드"," — ",[44,2484,50],{}," 전체 레퍼런스",[14,2487,1202],{"id":1201},[19,2489,1205],{},[97,2491,2492],{"className":1208,"code":1209,"language":1210,"meta":102,"style":102},[44,2493,2494],{"__ignoreMap":102},[106,2495,2496,2498,2500],{"class":108,"line":109},[106,2497,101],{"class":116},[106,2499,1219],{"class":252},[106,2501,1222],{"class":252},[19,2503,2504,1230,2507],{},[720,2505,1229],{"href":1227,"rel":2506},[724],[720,2508,1235],{"href":1233,"rel":2509},[724],[1237,2511,2512],{},"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":102,"searchDepth":120,"depth":120,"links":2514},[2515,2516,2517,2518,2519,2520,2521,2522,2523],{"id":16,"depth":120,"text":17},{"id":24,"depth":120,"text":25},{"id":1326,"depth":120,"text":1327},{"id":1886,"depth":120,"text":1887},{"id":1974,"depth":120,"text":1975},{"id":2317,"depth":120,"text":2318},{"id":2365,"depth":120,"text":2366},{"id":1167,"depth":120,"text":1168},{"id":1201,"depth":120,"text":1202},"ipaexg.ttf를 gpdf.WithFont로 등록한다. IPAex는 Regular 한 가지 굵기만 제공하므로 볼드는 합성하거나 명조와 페어링한다.",{"name":2526,"totalTime":2527,"tools":2528,"steps":2530},"gpdf 문서에서 IPAex Gothic을 기본 폰트로 쓰기","PT10M",[1260,2529],"ipaexg.ttf (moji.or.jp의 IPAex Gothic v4.01)",[2531,2534,2537,2540,2543],{"name":2532,"text":2533},"moji.or.jp에서 IPAex 폰트 묶음을 다운로드한다","moji.or.jp/ipafont에서 IPAex00401.zip을 받아 압축을 풀고 ipaexg.ttf와 함께 들어 있는 IPA Font License Agreement v1.0 텍스트 파일을 함께 보관한다.",{"name":2535,"text":2536},"TTF 바이트를 읽는다","프로그램 시작 시 os.ReadFile(\"ipaexg.ttf\")로 []byte에 로드한다. 컨테이너 배포라면 //go:embed로 Go 바이너리에 포함시키는 편이 배포하기 쉽다.",{"name":2538,"text":2539},"문서 생성 시점에 등록한다","gpdf.WithFont(\"IPAexGothic\", fontBytes)와 gpdf.WithDefaultFont(\"IPAexGothic\", 10.5)를 gpdf.NewDocument에 전달한다. 10.5 pt는 Word의 일본어 문서 기본 포인트와 동일하다.",{"name":2541,"text":2542},"Bold 파일이 없는 문제를 다룬다","IPAex Gothic에는 볼드 변형이 없다. template.Bold()로 합성(gpdf가 0.4 pt 스트로크 덧그림)하거나, IPAex 명조를 별도 패밀리로 등록해 강조용으로 쓴다.",{"name":2544,"text":2545},"배포물에 라이선스 파일을 동봉한다","IPA Font License v1.0은 폰트 바이너리가 배포되는 곳에 라이선스 전문이 함께 있기를 요구한다. //go:embed로 TTF를 넣는다면 LICENSES/IPA-FONT-1.0.txt도 함께 넣고 NOTICE에서 참조한다.",{},"/ko/blog/ipaex-gothic-gpdf",{"title":1290,"description":2524},"ko/blog/009.ipaex-gothic-gpdf",[1284,1286,2551],"tutorial","dE7TE_idJEGimuYycCNJCNfMqSTeI2W1tJNtMSjpqso",{"id":2554,"title":2555,"author":2556,"body":2557,"date":3042,"description":3897,"draft":1254,"extension":1255,"howTo":3898,"image":1278,"meta":3917,"navigation":123,"path":3918,"seo":3919,"stem":3920,"tags":3921,"updated":1278,"__hash__":3923},"blogKo/ko/blog/005.12-column-grid.md","gpdf의 12 컬럼 그리드는 어떻게 동작하나요?",{"name":8,"url":9},{"type":11,"value":2558,"toc":3885},[2559,2561,2572,2576,2590,2594,3539,3544,3548,3554,3558,3577,3591,3595,3598,3612,3626,3630,3636,3642,3797,3801,3807,3818,3821,3827,3831,3854,3858,3861,3873,3882],[14,2560,17],{"id":16},[19,2562,2563,2564,2567,2568,2571],{},"gpdf API를 보면 — 페이지 빌더, 로우 빌더, 컬럼 빌더 — 컬럼 생성자가 숫자를 받습니다: ",[44,2565,2566],{},"r.Col(4, fn)",", ",[44,2569,2570],{},"r.Col(8, fn)",". 이 숫자는 무엇이고, 합이 12가 안 되면 어떻게 되며, CSS에서 익숙한 그리드와는 어떻게 다른가?",[14,2573,2575],{"id":2574},"짧은-답","짧은 답",[19,2577,2578,2581,2582,2585,2586,2589],{},[44,2579,2580],{},"r.Col(span, fn)","은 1부터 12까지의 정수를 받습니다. 이 정수가 행 너비에서 해당 컬럼이 차지하는 비율 — ",[44,2583,2584],{},"span / 12",". 1 미만은 1로, 12 초과는 12로 클램프되고, ",[39,2587,2588],{},"행별 합계를 12로 맞출지 여부를 라이브러리는 강제하지 않습니다",". 그리드는 12 분할로 고정되어 있을 뿐, 나머지는 행을 어떻게 자를지에 대한 선택입니다.",[14,2591,2593],{"id":2592},"동작하는-예제","동작하는 예제",[97,2595,2597],{"className":99,"code":2596,"language":101,"meta":102,"style":102},"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",[44,2598,2599,2605,2609,2615,2623,2631,2635,2643,2651,2659,2663,2667,2677,2691,2709,2740,2744,2748,2762,2766,2771,2795,2825,2865,2869,2873,2877,2882,2906,2937,2956,2975,2979,3009,3028,3047,3051,3055,3059,3064,3088,3119,3139,3144,3175,3195,3200,3231,3251,3256,3261,3266,3272,3297,3329,3349,3354,3385,3405,3410,3415,3420,3439,3452,3467,3472,3514,3529,3534],{"__ignoreMap":102},[106,2600,2601,2603],{"class":108,"line":109},[106,2602,113],{"class":112},[106,2604,117],{"class":116},[106,2606,2607],{"class":108,"line":120},[106,2608,124],{"emptyLinePlaceholder":123},[106,2610,2611,2613],{"class":108,"line":127},[106,2612,131],{"class":130},[106,2614,134],{"class":112},[106,2616,2617,2619,2621],{"class":108,"line":137},[106,2618,140],{"class":112},[106,2620,143],{"class":116},[106,2622,146],{"class":112},[106,2624,2625,2627,2629],{"class":108,"line":149},[106,2626,140],{"class":112},[106,2628,154],{"class":116},[106,2630,146],{"class":112},[106,2632,2633],{"class":108,"line":159},[106,2634,124],{"emptyLinePlaceholder":123},[106,2636,2637,2639,2641],{"class":108,"line":164},[106,2638,140],{"class":112},[106,2640,169],{"class":116},[106,2642,146],{"class":112},[106,2644,2645,2647,2649],{"class":108,"line":174},[106,2646,140],{"class":112},[106,2648,179],{"class":116},[106,2650,146],{"class":112},[106,2652,2653,2655,2657],{"class":108,"line":184},[106,2654,140],{"class":112},[106,2656,189],{"class":116},[106,2658,146],{"class":112},[106,2660,2661],{"class":108,"line":194},[106,2662,197],{"class":112},[106,2664,2665],{"class":108,"line":200},[106,2666,124],{"emptyLinePlaceholder":123},[106,2668,2669,2671,2673,2675],{"class":108,"line":205},[106,2670,208],{"class":112},[106,2672,212],{"class":211},[106,2674,215],{"class":112},[106,2676,218],{"class":112},[106,2678,2679,2681,2683,2685,2687,2689],{"class":108,"line":221},[106,2680,308],{"class":224},[106,2682,234],{"class":112},[106,2684,313],{"class":224},[106,2686,240],{"class":112},[106,2688,318],{"class":211},[106,2690,321],{"class":112},[106,2692,2693,2695,2697,2699,2701,2703,2705,2707],{"class":108,"line":260},[106,2694,327],{"class":224},[106,2696,240],{"class":112},[106,2698,332],{"class":211},[106,2700,246],{"class":112},[106,2702,360],{"class":224},[106,2704,240],{"class":112},[106,2706,342],{"class":224},[106,2708,345],{"class":112},[106,2710,2711,2713,2715,2717,2719,2721,2723,2725,2727,2729,2731,2733,2735,2738],{"class":108,"line":276},[106,2712,327],{"class":224},[106,2714,240],{"class":112},[106,2716,355],{"class":211},[106,2718,246],{"class":112},[106,2720,360],{"class":224},[106,2722,240],{"class":112},[106,2724,365],{"class":211},[106,2726,246],{"class":112},[106,2728,360],{"class":224},[106,2730,240],{"class":112},[106,2732,374],{"class":211},[106,2734,246],{"class":112},[106,2736,2737],{"class":379},"15",[106,2739,383],{"class":112},[106,2741,2742],{"class":108,"line":294},[106,2743,439],{"class":112},[106,2745,2746],{"class":108,"line":300},[106,2747,124],{"emptyLinePlaceholder":123},[106,2749,2750,2752,2754,2756,2758,2760],{"class":108,"line":305},[106,2751,450],{"class":224},[106,2753,234],{"class":112},[106,2755,455],{"class":224},[106,2757,240],{"class":112},[106,2759,460],{"class":211},[106,2761,463],{"class":112},[106,2763,2764],{"class":108,"line":324},[106,2765,124],{"emptyLinePlaceholder":123},[106,2767,2768],{"class":108,"line":348},[106,2769,2770],{"class":835},"    // 전체 너비\n",[106,2772,2773,2775,2777,2779,2781,2783,2785,2787,2789,2791,2793],{"class":108,"line":386},[106,2774,469],{"class":224},[106,2776,240],{"class":112},[106,2778,474],{"class":211},[106,2780,477],{"class":112},[106,2782,481],{"class":480},[106,2784,484],{"class":112},[106,2786,487],{"class":116},[106,2788,240],{"class":112},[106,2790,492],{"class":116},[106,2792,495],{"class":112},[106,2794,218],{"class":112},[106,2796,2797,2799,2801,2803,2805,2807,2809,2811,2813,2815,2817,2819,2821,2823],{"class":108,"line":411},[106,2798,503],{"class":224},[106,2800,240],{"class":112},[106,2802,508],{"class":211},[106,2804,246],{"class":112},[106,2806,513],{"class":379},[106,2808,228],{"class":112},[106,2810,518],{"class":112},[106,2812,521],{"class":480},[106,2814,484],{"class":112},[106,2816,487],{"class":116},[106,2818,240],{"class":112},[106,2820,530],{"class":116},[106,2822,495],{"class":112},[106,2824,218],{"class":112},[106,2826,2827,2829,2831,2833,2835,2837,2840,2842,2844,2846,2848,2850,2852,2855,2857,2859,2861,2863],{"class":108,"line":436},[106,2828,540],{"class":224},[106,2830,240],{"class":112},[106,2832,545],{"class":211},[106,2834,246],{"class":112},[106,2836,249],{"class":112},[106,2838,2839],{"class":252},"세금계산서 #2026-0416",[106,2841,249],{"class":112},[106,2843,228],{"class":112},[106,2845,1677],{"class":224},[106,2847,240],{"class":112},[106,2849,1682],{"class":211},[106,2851,246],{"class":112},[106,2853,2854],{"class":379},"18",[106,2856,1690],{"class":112},[106,2858,1677],{"class":224},[106,2860,240],{"class":112},[106,2862,1697],{"class":211},[106,2864,1700],{"class":112},[106,2866,2867],{"class":108,"line":442},[106,2868,562],{"class":112},[106,2870,2871],{"class":108,"line":447},[106,2872,568],{"class":112},[106,2874,2875],{"class":108,"line":466},[106,2876,124],{"emptyLinePlaceholder":123},[106,2878,2879],{"class":108,"line":500},[106,2880,2881],{"class":835},"    // 2 컬럼 헤더 (6 + 6)\n",[106,2883,2884,2886,2888,2890,2892,2894,2896,2898,2900,2902,2904],{"class":108,"line":537},[106,2885,469],{"class":224},[106,2887,240],{"class":112},[106,2889,474],{"class":211},[106,2891,477],{"class":112},[106,2893,481],{"class":480},[106,2895,484],{"class":112},[106,2897,487],{"class":116},[106,2899,240],{"class":112},[106,2901,492],{"class":116},[106,2903,495],{"class":112},[106,2905,218],{"class":112},[106,2907,2908,2910,2912,2914,2916,2919,2921,2923,2925,2927,2929,2931,2933,2935],{"class":108,"line":559},[106,2909,503],{"class":224},[106,2911,240],{"class":112},[106,2913,508],{"class":211},[106,2915,246],{"class":112},[106,2917,2918],{"class":379},"6",[106,2920,228],{"class":112},[106,2922,518],{"class":112},[106,2924,521],{"class":480},[106,2926,484],{"class":112},[106,2928,487],{"class":116},[106,2930,240],{"class":112},[106,2932,530],{"class":116},[106,2934,495],{"class":112},[106,2936,218],{"class":112},[106,2938,2939,2941,2943,2945,2947,2949,2952,2954],{"class":108,"line":565},[106,2940,540],{"class":224},[106,2942,240],{"class":112},[106,2944,545],{"class":211},[106,2946,246],{"class":112},[106,2948,249],{"class":112},[106,2950,2951],{"class":252},"공급받는자",[106,2953,249],{"class":112},[106,2955,197],{"class":112},[106,2957,2958,2960,2962,2964,2966,2968,2971,2973],{"class":108,"line":571},[106,2959,540],{"class":224},[106,2961,240],{"class":112},[106,2963,545],{"class":211},[106,2965,246],{"class":112},[106,2967,249],{"class":112},[106,2969,2970],{"class":252},"Acme 주식회사",[106,2972,249],{"class":112},[106,2974,197],{"class":112},[106,2976,2977],{"class":108,"line":576},[106,2978,562],{"class":112},[106,2980,2981,2983,2985,2987,2989,2991,2993,2995,2997,2999,3001,3003,3005,3007],{"class":108,"line":597},[106,2982,503],{"class":224},[106,2984,240],{"class":112},[106,2986,508],{"class":211},[106,2988,246],{"class":112},[106,2990,2918],{"class":379},[106,2992,228],{"class":112},[106,2994,518],{"class":112},[106,2996,521],{"class":480},[106,2998,484],{"class":112},[106,3000,487],{"class":116},[106,3002,240],{"class":112},[106,3004,530],{"class":116},[106,3006,495],{"class":112},[106,3008,218],{"class":112},[106,3010,3011,3013,3015,3017,3019,3021,3024,3026],{"class":108,"line":610},[106,3012,540],{"class":224},[106,3014,240],{"class":112},[106,3016,545],{"class":211},[106,3018,246],{"class":112},[106,3020,249],{"class":112},[106,3022,3023],{"class":252},"발행일",[106,3025,249],{"class":112},[106,3027,197],{"class":112},[106,3029,3030,3032,3034,3036,3038,3040,3043,3045],{"class":108,"line":625},[106,3031,540],{"class":224},[106,3033,240],{"class":112},[106,3035,545],{"class":211},[106,3037,246],{"class":112},[106,3039,249],{"class":112},[106,3041,3042],{"class":252},"2026-04-16",[106,3044,249],{"class":112},[106,3046,197],{"class":112},[106,3048,3049],{"class":108,"line":630},[106,3050,562],{"class":112},[106,3052,3053],{"class":108,"line":676},[106,3054,568],{"class":112},[106,3056,3057],{"class":108,"line":691},[106,3058,124],{"emptyLinePlaceholder":123},[106,3060,3061],{"class":108,"line":696},[106,3062,3063],{"class":835},"    // 3 컬럼 요약 (4 + 4 + 4)\n",[106,3065,3066,3068,3070,3072,3074,3076,3078,3080,3082,3084,3086],{"class":108,"line":1856},[106,3067,469],{"class":224},[106,3069,240],{"class":112},[106,3071,474],{"class":211},[106,3073,477],{"class":112},[106,3075,481],{"class":480},[106,3077,484],{"class":112},[106,3079,487],{"class":116},[106,3081,240],{"class":112},[106,3083,492],{"class":116},[106,3085,495],{"class":112},[106,3087,218],{"class":112},[106,3089,3090,3092,3094,3096,3098,3101,3103,3105,3107,3109,3111,3113,3115,3117],{"class":108,"line":1861},[106,3091,503],{"class":224},[106,3093,240],{"class":112},[106,3095,508],{"class":211},[106,3097,246],{"class":112},[106,3099,3100],{"class":379},"4",[106,3102,228],{"class":112},[106,3104,518],{"class":112},[106,3106,521],{"class":480},[106,3108,484],{"class":112},[106,3110,487],{"class":116},[106,3112,240],{"class":112},[106,3114,530],{"class":116},[106,3116,495],{"class":112},[106,3118,218],{"class":112},[106,3120,3122,3124,3126,3128,3130,3132,3135,3137],{"class":108,"line":3121},42,[106,3123,540],{"class":224},[106,3125,240],{"class":112},[106,3127,545],{"class":211},[106,3129,246],{"class":112},[106,3131,249],{"class":112},[106,3133,3134],{"class":252},"소계",[106,3136,249],{"class":112},[106,3138,197],{"class":112},[106,3140,3142],{"class":108,"line":3141},43,[106,3143,562],{"class":112},[106,3145,3147,3149,3151,3153,3155,3157,3159,3161,3163,3165,3167,3169,3171,3173],{"class":108,"line":3146},44,[106,3148,503],{"class":224},[106,3150,240],{"class":112},[106,3152,508],{"class":211},[106,3154,246],{"class":112},[106,3156,3100],{"class":379},[106,3158,228],{"class":112},[106,3160,518],{"class":112},[106,3162,521],{"class":480},[106,3164,484],{"class":112},[106,3166,487],{"class":116},[106,3168,240],{"class":112},[106,3170,530],{"class":116},[106,3172,495],{"class":112},[106,3174,218],{"class":112},[106,3176,3178,3180,3182,3184,3186,3188,3191,3193],{"class":108,"line":3177},45,[106,3179,540],{"class":224},[106,3181,240],{"class":112},[106,3183,545],{"class":211},[106,3185,246],{"class":112},[106,3187,249],{"class":112},[106,3189,3190],{"class":252},"부가세",[106,3192,249],{"class":112},[106,3194,197],{"class":112},[106,3196,3198],{"class":108,"line":3197},46,[106,3199,562],{"class":112},[106,3201,3203,3205,3207,3209,3211,3213,3215,3217,3219,3221,3223,3225,3227,3229],{"class":108,"line":3202},47,[106,3204,503],{"class":224},[106,3206,240],{"class":112},[106,3208,508],{"class":211},[106,3210,246],{"class":112},[106,3212,3100],{"class":379},[106,3214,228],{"class":112},[106,3216,518],{"class":112},[106,3218,521],{"class":480},[106,3220,484],{"class":112},[106,3222,487],{"class":116},[106,3224,240],{"class":112},[106,3226,530],{"class":116},[106,3228,495],{"class":112},[106,3230,218],{"class":112},[106,3232,3234,3236,3238,3240,3242,3244,3247,3249],{"class":108,"line":3233},48,[106,3235,540],{"class":224},[106,3237,240],{"class":112},[106,3239,545],{"class":211},[106,3241,246],{"class":112},[106,3243,249],{"class":112},[106,3245,3246],{"class":252},"합계",[106,3248,249],{"class":112},[106,3250,197],{"class":112},[106,3252,3254],{"class":108,"line":3253},49,[106,3255,562],{"class":112},[106,3257,3259],{"class":108,"line":3258},50,[106,3260,568],{"class":112},[106,3262,3264],{"class":108,"line":3263},51,[106,3265,124],{"emptyLinePlaceholder":123},[106,3267,3269],{"class":108,"line":3268},52,[106,3270,3271],{"class":835},"    // 비대칭 (8 + 4) — 본문 + 사이드 패널\n",[106,3273,3275,3277,3279,3281,3283,3285,3287,3289,3291,3293,3295],{"class":108,"line":3274},53,[106,3276,469],{"class":224},[106,3278,240],{"class":112},[106,3280,474],{"class":211},[106,3282,477],{"class":112},[106,3284,481],{"class":480},[106,3286,484],{"class":112},[106,3288,487],{"class":116},[106,3290,240],{"class":112},[106,3292,492],{"class":116},[106,3294,495],{"class":112},[106,3296,218],{"class":112},[106,3298,3300,3302,3304,3306,3308,3311,3313,3315,3317,3319,3321,3323,3325,3327],{"class":108,"line":3299},54,[106,3301,503],{"class":224},[106,3303,240],{"class":112},[106,3305,508],{"class":211},[106,3307,246],{"class":112},[106,3309,3310],{"class":379},"8",[106,3312,228],{"class":112},[106,3314,518],{"class":112},[106,3316,521],{"class":480},[106,3318,484],{"class":112},[106,3320,487],{"class":116},[106,3322,240],{"class":112},[106,3324,530],{"class":116},[106,3326,495],{"class":112},[106,3328,218],{"class":112},[106,3330,3332,3334,3336,3338,3340,3342,3345,3347],{"class":108,"line":3331},55,[106,3333,540],{"class":224},[106,3335,240],{"class":112},[106,3337,545],{"class":211},[106,3339,246],{"class":112},[106,3341,249],{"class":112},[106,3343,3344],{"class":252},"품목은 여기에 나열됩니다",[106,3346,249],{"class":112},[106,3348,197],{"class":112},[106,3350,3352],{"class":108,"line":3351},56,[106,3353,562],{"class":112},[106,3355,3357,3359,3361,3363,3365,3367,3369,3371,3373,3375,3377,3379,3381,3383],{"class":108,"line":3356},57,[106,3358,503],{"class":224},[106,3360,240],{"class":112},[106,3362,508],{"class":211},[106,3364,246],{"class":112},[106,3366,3100],{"class":379},[106,3368,228],{"class":112},[106,3370,518],{"class":112},[106,3372,521],{"class":480},[106,3374,484],{"class":112},[106,3376,487],{"class":116},[106,3378,240],{"class":112},[106,3380,530],{"class":116},[106,3382,495],{"class":112},[106,3384,218],{"class":112},[106,3386,3388,3390,3392,3394,3396,3398,3401,3403],{"class":108,"line":3387},58,[106,3389,540],{"class":224},[106,3391,240],{"class":112},[106,3393,545],{"class":211},[106,3395,246],{"class":112},[106,3397,249],{"class":112},[106,3399,3400],{"class":252},"비고",[106,3402,249],{"class":112},[106,3404,197],{"class":112},[106,3406,3408],{"class":108,"line":3407},59,[106,3409,562],{"class":112},[106,3411,3413],{"class":108,"line":3412},60,[106,3414,568],{"class":112},[106,3416,3418],{"class":108,"line":3417},61,[106,3419,124],{"emptyLinePlaceholder":123},[106,3421,3423,3425,3427,3429,3431,3433,3435,3437],{"class":108,"line":3422},62,[106,3424,579],{"class":224},[106,3426,228],{"class":112},[106,3428,231],{"class":224},[106,3430,234],{"class":112},[106,3432,455],{"class":224},[106,3434,240],{"class":112},[106,3436,592],{"class":211},[106,3438,463],{"class":112},[106,3440,3442,3444,3446,3448,3450],{"class":108,"line":3441},63,[106,3443,263],{"class":130},[106,3445,231],{"class":224},[106,3447,268],{"class":112},[106,3449,271],{"class":112},[106,3451,218],{"class":112},[106,3453,3455,3457,3459,3461,3463,3465],{"class":108,"line":3454},64,[106,3456,279],{"class":224},[106,3458,240],{"class":112},[106,3460,284],{"class":211},[106,3462,246],{"class":112},[106,3464,289],{"class":224},[106,3466,197],{"class":112},[106,3468,3470],{"class":108,"line":3469},65,[106,3471,297],{"class":112},[106,3473,3475,3477,3479,3481,3483,3485,3487,3489,3491,3494,3496,3498,3500,3502,3504,3506,3508,3510,3512],{"class":108,"line":3474},66,[106,3476,263],{"class":130},[106,3478,231],{"class":224},[106,3480,234],{"class":112},[106,3482,237],{"class":224},[106,3484,240],{"class":112},[106,3486,643],{"class":211},[106,3488,246],{"class":112},[106,3490,249],{"class":112},[106,3492,3493],{"class":252},"layout.pdf",[106,3495,249],{"class":112},[106,3497,228],{"class":112},[106,3499,657],{"class":224},[106,3501,228],{"class":112},[106,3503,662],{"class":379},[106,3505,665],{"class":112},[106,3507,231],{"class":224},[106,3509,268],{"class":112},[106,3511,271],{"class":112},[106,3513,218],{"class":112},[106,3515,3517,3519,3521,3523,3525,3527],{"class":108,"line":3516},67,[106,3518,279],{"class":224},[106,3520,240],{"class":112},[106,3522,284],{"class":211},[106,3524,246],{"class":112},[106,3526,289],{"class":224},[106,3528,197],{"class":112},[106,3530,3532],{"class":108,"line":3531},68,[106,3533,297],{"class":112},[106,3535,3537],{"class":108,"line":3536},69,[106,3538,699],{"class":112},[19,3540,3541,3543],{},[44,3542,1883],{},"를 실행하면 서로 다르게 분할된 네 개의 행이 있는 한 페이지 PDF가 나옵니다.",[14,3545,3547],{"id":3546},"왜-12인가","왜 12인가",[19,3549,3550,3551,240],{},"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는 이 관용 표현을 의도적으로 가져왔습니다 — ",[39,3552,3553],{},"출력이 고정 너비의 종이라고 해서 레이아웃의 사고방식이 웹과 다를 이유는 없습니다",[14,3555,3557],{"id":3556},"계산을-구체적으로","계산을 구체적으로",[19,3559,3560,3561,3564,3565,3568,3569,3572,3573,3576],{},"A4 세로에 사면 15 mm 균일 여백이면 사용 가능한 너비는 180 mm. 행 안의 ",[44,3562,3563],{},"Col(4)","는 그중 4/12, 즉 60 mm. ",[44,3566,3567],{},"Col(8)","은 120 mm. 컬럼 사이 거터는 기본적으로 없습니다. 여백을 두고 싶다면 짧은 쪽 컬럼 안에 ",[44,3570,3571],{},"c.Spacer","를 넣거나, ",[44,3574,3575],{},"Col(1)","을 비워두세요.",[19,3578,3579,3580,3583,3584,3587,3588,240],{},"너비는 빌드 시점에 백분율로 계산되고 (구현은 ",[44,3581,3582],{},"gpdf/template/grid.go","), 레이아웃 엔진이 \"현재 페이지 너비에서 여백을 뺀 값\"을 기준으로 실제 포인트 값으로 해석합니다. 따라서 같은 ",[44,3585,3586],{},"r.Col(6, fn)","이라도 A4와 Letter에서 물리 너비는 다르지만 ",[39,3589,3590],{},"행에 대한 비율은 동일합니다",[14,3592,3594],{"id":3593},"합이-12가-아닐-때","합이 12가 아닐 때",[19,3596,3597],{},"gpdf는 span의 합을 검증하지 않습니다. 의도적인 선택입니다.",[983,3599,3600,3606],{},[36,3601,3602,3605],{},[39,3603,3604],{},"합 \u003C 12",": 행 오른쪽이 빕니다. 왼쪽 끝에만 요소를 고정하고 나머지를 의도적으로 비워두고 싶을 때 유용합니다.",[36,3607,3608,3611],{},[39,3609,3610],{},"합 > 12",": 마지막 컬럼이 오른쪽 여백을 넘어갑니다. 대개 버그입니다. PDF가 어긋나 보이지만 크래시는 나지 않습니다.",[19,3613,3614,3615,3618,3619,3622,3623,3625],{},"대부분의 레이아웃은 행당 정확히 12로 채워집니다. 그게 페이지를 꽉 채우는 방식이기 때문입니다. 다만 \"행 가운데에 폭 6짜리 블록만 두고 싶다\"면 ",[44,3616,3617],{},"Col(3)"," 빔, ",[44,3620,3621],{},"Col(6)"," 내용, ",[44,3624,3617],{}," 빔 — 이런 약식 표기가 가장 자연스럽습니다. 그리드는 이런 표현을 염두에 두고 설계되었습니다.",[14,3627,3629],{"id":3628},"autorow와-row의-차이","AutoRow와 Row의 차이",[19,3631,3632,3635],{},[44,3633,3634],{},"page.AutoRow(fn)","은 가장 키가 큰 컬럼에 맞춰 행 높이가 늘어납니다. 대부분의 행은 이걸 쓰면 됩니다.",[19,3637,3638,3641],{},[44,3639,3640],{},"page.Row(height, fn)","은 높이를 고정합니다. 높이를 넘는 콘텐츠는 잘립니다. 후공정의 스테이플 위치를 맞추기 위해 헤더를 반드시 30 mm로 유지해야 하는 경우처럼 \"시각적 일관성 > 콘텐츠 자유도\"인 상황에서 씁니다.",[97,3643,3645],{"className":99,"code":3644,"language":101,"meta":102,"style":102},"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",[44,3646,3647,3687,3717,3736,3740,3770,3789,3793],{"__ignoreMap":102},[106,3648,3649,3651,3653,3656,3658,3660,3662,3664,3666,3669,3671,3673,3675,3677,3679,3681,3683,3685],{"class":108,"line":109},[106,3650,849],{"class":224},[106,3652,240],{"class":112},[106,3654,3655],{"class":211},"Row",[106,3657,246],{"class":112},[106,3659,360],{"class":224},[106,3661,240],{"class":112},[106,3663,374],{"class":211},[106,3665,246],{"class":112},[106,3667,3668],{"class":379},"30",[106,3670,1690],{"class":112},[106,3672,518],{"class":112},[106,3674,481],{"class":480},[106,3676,484],{"class":112},[106,3678,487],{"class":116},[106,3680,240],{"class":112},[106,3682,492],{"class":116},[106,3684,495],{"class":112},[106,3686,218],{"class":112},[106,3688,3689,3691,3693,3695,3697,3699,3701,3703,3705,3707,3709,3711,3713,3715],{"class":108,"line":120},[106,3690,874],{"class":224},[106,3692,240],{"class":112},[106,3694,508],{"class":211},[106,3696,246],{"class":112},[106,3698,3310],{"class":379},[106,3700,228],{"class":112},[106,3702,518],{"class":112},[106,3704,521],{"class":480},[106,3706,484],{"class":112},[106,3708,487],{"class":116},[106,3710,240],{"class":112},[106,3712,530],{"class":116},[106,3714,495],{"class":112},[106,3716,218],{"class":112},[106,3718,3719,3721,3723,3725,3727,3729,3732,3734],{"class":108,"line":127},[106,3720,905],{"class":224},[106,3722,240],{"class":112},[106,3724,545],{"class":211},[106,3726,246],{"class":112},[106,3728,249],{"class":112},[106,3730,3731],{"class":252},"로고",[106,3733,249],{"class":112},[106,3735,197],{"class":112},[106,3737,3738],{"class":108,"line":137},[106,3739,568],{"class":112},[106,3741,3742,3744,3746,3748,3750,3752,3754,3756,3758,3760,3762,3764,3766,3768],{"class":108,"line":149},[106,3743,874],{"class":224},[106,3745,240],{"class":112},[106,3747,508],{"class":211},[106,3749,246],{"class":112},[106,3751,3100],{"class":379},[106,3753,228],{"class":112},[106,3755,518],{"class":112},[106,3757,521],{"class":480},[106,3759,484],{"class":112},[106,3761,487],{"class":116},[106,3763,240],{"class":112},[106,3765,530],{"class":116},[106,3767,495],{"class":112},[106,3769,218],{"class":112},[106,3771,3772,3774,3776,3778,3780,3782,3785,3787],{"class":108,"line":159},[106,3773,905],{"class":224},[106,3775,240],{"class":112},[106,3777,545],{"class":211},[106,3779,246],{"class":112},[106,3781,249],{"class":112},[106,3783,3784],{"class":252},"세금계산서 번호",[106,3786,249],{"class":112},[106,3788,197],{"class":112},[106,3790,3791],{"class":108,"line":164},[106,3792,568],{"class":112},[106,3794,3795],{"class":108,"line":174},[106,3796,932],{"class":112},[14,3798,3800],{"id":3799},"그리드가-하지-않는-것","그리드가 하지 않는 것",[19,3802,3803,3804,3806],{},"중첩 불가. ",[44,3805,530],{},"는 콘텐츠 요소 (Text / Image / Table / List / Spacer)를 받지만, 안에 또 다른 행을 넣을 수는 없습니다. 중첩이 필요해 보이는 구조는 보통 페이지 레벨에서 두 형제 행으로 나누는 편이 더 깔끔합니다.",[19,3808,3809,3810,3813,3814,3817],{},"오프셋 컬럼 없음. Bootstrap의 ",[44,3811,3812],{},".offset-2","에 대응하는 기능은 없습니다. 오른쪽으로 밀고 싶다면 왼쪽에 빈 ",[44,3815,3816],{},"Col(n)","을 두세요.",[19,3819,3820],{},"브레이크포인트 없음. PDF 페이지는 리사이즈되지 않습니다. 어떤 기기에서 열든 동일한 레이아웃 — 출력이 재배치되는 DOM이 아니라 고정 좌표의 래스터이기 때문입니다.",[19,3822,3823,3826],{},[39,3824,3825],{},"이 \"없음\"들이 설계의 핵심입니다",". 그리드가 갖지 않는 기능 하나당, PDF 결과를 읽을 때 추론해야 할 모호성이 하나씩 줄어듭니다.",[14,3828,3830],{"id":3829},"관련-글","관련 글",[983,3832,3833,3839,3846],{},[36,3834,3835,3838],{},[720,3836,3837],{"href":1175},"gpdf에 일본어 폰트를 임베드하려면?"," — 그리드 컬럼 안에서 CJK 다루기",[36,3840,3841,3845],{},[720,3842,3844],{"href":3843},"/ko/blog/go-pdf-library-showdown-2026","Go PDF 라이브러리 비교 2026"," — Builder API와 gofpdf / gopdf / Maroto 비교",[36,3847,3848,3853],{},[720,3849,3852],{"href":3850,"rel":3851},"https://gpdf.dev/ko/docs/guide/layout",[724],"레이아웃 가이드"," — 행, 컬럼, 간격의 전체 레퍼런스",[14,3855,3857],{"id":3856},"gpdf-써보기","gpdf 써보기",[19,3859,3860],{},"gpdf는 Go PDF 생성 라이브러리입니다. MIT 라이선스, 외부 의존성 0, 네이티브 CJK 지원.",[97,3862,3863],{"className":1208,"code":1209,"language":1210,"meta":102,"style":102},[44,3864,3865],{"__ignoreMap":102},[106,3866,3867,3869,3871],{"class":108,"line":109},[106,3868,101],{"class":116},[106,3870,1219],{"class":252},[106,3872,1222],{"class":252},[19,3874,3875,1230,3879],{},[720,3876,3878],{"href":1227,"rel":3877},[724],"⭐ GitHub에서 별 누르기",[720,3880,1235],{"href":1233,"rel":3881},[724],[1237,3883,3884],{},"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":102,"searchDepth":120,"depth":120,"links":3886},[3887,3888,3889,3890,3891,3892,3893,3894,3895,3896],{"id":16,"depth":120,"text":17},{"id":2574,"depth":120,"text":2575},{"id":2592,"depth":120,"text":2593},{"id":3546,"depth":120,"text":3547},{"id":3556,"depth":120,"text":3557},{"id":3593,"depth":120,"text":3594},{"id":3628,"depth":120,"text":3629},{"id":3799,"depth":120,"text":3800},{"id":3829,"depth":120,"text":3830},{"id":3856,"depth":120,"text":3857},"gpdf의 12 컬럼 그리드는 r.Col(span, fn)에 1–12 정수를 넘깁니다. 컬럼 너비는 span/12 비율, 거터도 브레이크포인트도 없는 PDF 전용 설계.",{"name":3899,"totalTime":2527,"tools":3900,"steps":3901},"gpdf의 12 컬럼 그리드로 페이지 레이아웃 잡기",[1260,169],[3902,3905,3908,3911,3914],{"name":3903,"text":3904},"페이지에 행 열기","가장 키가 큰 컬럼에 맞춰 행 높이가 자동으로 늘어나길 원하면 page.AutoRow(fn)을, 높이를 고정하려면 page.Row(height, fn)을 호출합니다.",{"name":3906,"text":3907},"r.Col(span, fn)으로 컬럼 선언","행 안에서 컬럼 수만큼 r.Col(span, fn)을 호출합니다. span은 1~12 정수로, 해당 컬럼이 행 너비에서 차지하는 비율을 나타냅니다.",{"name":3909,"text":3910},"행당 span 합을 12 이하로 유지","합이 12 미만이면 오른쪽이 비고, 12를 초과하면 마지막 컬럼이 오른쪽 여백을 넘어갑니다 — 보통 버그 신호입니다.",{"name":3912,"text":3913},"컬럼 안에 콘텐츠 채우기","ColBuilder 콜백 안에서 c.Text, c.Image, c.Table, c.Spacer를 호출합니다. 추가한 순서대로 세로로 쌓입니다.",{"name":3915,"text":3916},"다음 행 시작","다음 시각적 행은 page.AutoRow를 다시 호출합니다. 행은 서로 독립적이어서 4+8 행 바로 밑에 3+3+3+3 행을 두어도 됩니다.",{},"/ko/blog/12-column-grid",{"title":2555,"description":3897},"ko/blog/005.12-column-grid",[1284,2551,3922],"templates","jV5hmGH71S2Zm14imSGwJ_DwbGJm7lp3L2qR0JsZpJc",{"id":3925,"title":3926,"author":3927,"body":3928,"date":3042,"description":5700,"draft":1254,"extension":1255,"howTo":1278,"image":1278,"meta":5701,"navigation":123,"path":5702,"seo":5703,"stem":5704,"tags":5705,"updated":1278,"__hash__":5709},"blogKo/ko/blog/006.go-pdf-fpdf-archived.md","go-pdf/fpdf도 아카이브됐다. 2026년의 Go PDF 스택.",{"name":8,"url":9},{"type":11,"value":3929,"toc":5684},[3930,3934,3958,3962,3980,3983,4000,4011,4015,4018,4051,4057,4061,4067,4082,4085,4094,4100,4104,4107,4284,4287,4300,4312,4318,4324,4327,4331,4337,4340,4385,4388,4569,4582,4586,4601,4604,4609,4932,4937,5316,5331,5340,5344,5351,5448,5455,5461,5464,5472,5475,5524,5531,5535,5542,5551,5562,5566,5578,5584,5594,5613,5622,5628,5630,5632,5644,5652,5656,5681],[14,3931,3933],{"id":3932},"tldr","TL;DR",[19,3935,3936,3939,3940,3943,3944,3947,3948,3943,3951,3954,3955,3957],{},[44,3937,3938],{},"fpdf"," 계보에서 유지되던 두 포크가 모두 read-only가 됐다. ",[44,3941,3942],{},"jung-kurt/gofpdf","은 ",[39,3945,3946],{},"2021년 9월",", 커뮤니티 포크 ",[44,3949,3950],{},"go-pdf/fpdf",[39,3952,3953],{},"2025년","에 아카이브. \"다음 유지보수자\"는 오지 않는다. 새 Go 프로젝트의 현대 기본값은 ",[39,3956,337],{}," — 순수 Go, 외부 의존 0, CJK 네이티브, 일반적인 워크로드에서 10–30배 빠름. 이 글은 2026년 지형과 \"gpdf를 언제 선택하고 언제 선택하지 않는가\"에 대한 솔직한 답.",[14,3959,3961],{"id":3960},"지금-상황","지금 상황",[19,3963,3964,3965,3968,3969,3972,3973,3976,3977,3979],{},"지난주 팀원이 ",[44,3966,3967],{},"go get github.com/go-pdf/fpdf","을 치다가 GitHub 배너에서 멈췄다: ",[784,3970,3971],{},"\"This repository has been archived by the owner. It is now read-only.\""," — 이건 ",[784,3974,3975],{},"고쳐진 쪽"," 이어야 했다. 2021년에 아카이브된 ",[44,3978,3942],{},"의 계보를 이어받기로 했던 커뮤니티 포크.",[19,3981,3982],{},"그것도 아카이브됐다. README는 이제 다른 라이브러리를 찾아보라고 권한다.",[19,3984,3985,3986,3989,3990,3992,3993,3995,3996,3999],{},"지난 5년간 Go로 서비스를 돌리며 PDF를 뽑아왔다면(세금계산서, 리포트, 배송 라벨, 전자문서), ",[44,3987,3988],{},"go.mod"," 맨 아래 줄은 거의 확실히 이 둘 중 하나다. Stack Overflow 답변은 ",[44,3991,3942],{},"을, 좀 더 최근 튜토리얼은 ",[44,3994,3950],{},"을 가리킨다. ",[39,3997,3998],{},"둘 다 이제 공급망 부채다"," — CVE 대응, Go 버전 호환 작업, 성능 수정, 스펙 업데이트가 전부 정지 상태.",[19,4001,4002,4003,4007,4008,240],{},"이 글은 한 줄씩 대응시키는 마이그레이션 가이드가 아니다 — ",[720,4004,4006],{"href":4005},"/ko/blog/gofpdf-migration","그건 이미 썼다",". 이 글은 마이그레이션 가이드가 답하지 않는 더 긴 질문에 답한다: ",[39,4009,4010],{},"2026년 Go에서 PDF를 생성할 때 실제로 뭘 쓸 것인가, 그리고 왜 생태계가 여기까지 왔는가",[14,4012,4014],{"id":4013},"아카이브가-실제로-치르는-비용","\"아카이브\"가 실제로 치르는 비용",[19,4016,4017],{},"GitHub의 \"archived\" 라벨은 부드럽게 보인다. import 그래프에 들어 있는 라이브러리 기준으로는, 실제로는 네 가지 구체적인 결과를 뜻한다.",[33,4019,4020,4026,4036,4042],{},[36,4021,4022,4025],{},[39,4023,4024],{},"보안 패치가 없다."," TTF 파서에 메모리 안전 이슈가 생겨도 upstream에 병합되지 않는다. 직접 포크해서 고칠 수는 있지만, 대부분의 팀은 하지 않는다.",[36,4027,4028,4031,4032,4035],{},[39,4029,4030],{},"Go 툴체인 전방 호환이 없다."," Go 1.25의 루프 변수 시맨틱은 지금 gofpdf에서 잘 돈다. 하지만 내일 ",[44,4033,4034],{},"for range"," 주변이나 표준 라이브러리의 deprecation이 뭔가 깨뜨리면, read-only 저장소의 포크를 고치는 건 당신 몫.",[36,4037,4038,4041],{},[39,4039,4040],{},"스펙 업데이트가 없다."," PDF 2.0 (ISO 32000-2)은 2020년에 확정됐다. gofpdf는 대부분 PDF 1.7 수준. 페이지별 연관 파일, 리치 XMP 메타데이터, 현대 디지털 서명(PAdES-B-LT)은 서드파티 접착제에 의존하거나 아예 없다.",[36,4043,4044,4047,4048,4050],{},[39,4045,4046],{},"CJK 진전이 없다."," gofpdf의 Unicode 경로는 단일 바이트 폰트 설계 위에 덧붙인 것이다. 돌긴 하지만 대부분의 실사용 설정에서 서브셋이 아니라 전체 폰트를 임베드한다. 특정 CJK TTF에서 글리프 ID 충돌로 출력이 깨지기도 한다. ",[44,4049,3950],{},"은 같은 아키텍처를 그대로 물려받았다.",[19,4052,4053,4054,240],{},"보안과 전방 호환은 컴플라이언스 미팅에서 아프게 찔린다. \"우리 PDF 라이브러리는 아카이브됐고 CVE 패치가 안 옵니다\"는 감사 담당이 듣고 싶어하는 답이 아니다. ",[39,4055,4056],{},"특히 전자세금계산서나 전자문서 인증 범위 안에 PDF가 들어 있다면, 이 논점은 더 미룰 수 없다",[14,4058,4060],{"id":4059},"왜-두-포크가-다-죽었나","왜 두 포크가 다 죽었나",[19,4062,4063,4064,240],{},"아카이브를 메인테이너 번아웃 한 가지로 설명하고 싶어진다 — PR 리뷰에 지친 한 사람, 버스 팩터 1이 오프라인으로 간다. 그것도 이유지만 전부는 아니다. ",[39,4065,4066],{},"아키텍처가 따라잡는 걸 어렵게 만들었다",[19,4068,4069,4071,4072,2567,4075,2567,4078,4081],{},[44,4070,3942],{},"은 FPDF — 2002년 PHP 라이브러리의 포팅이었다. PHP 원본은 페이지 위에서 커서를 밀면서 절차적으로 콘텐츠를 토해낸다: ",[44,4073,4074],{},"SetXY(x, y)",[44,4076,4077],{},"Cell(w, h, text)",[44,4079,4080],{},"Ln(h)",". 그 모델은 2002년 PHP에서는 합리적 타협이었다 — 당시 대안은 원시 PostScript 아니면 상용 툴킷. Go로 포팅되면서 커서가 남았고, 단일 바이트 폰트 테이블도 남았고, 수동 페이지 브레이크 관리도 남았다.",[19,4083,4084],{},"해가 갈수록 \"사람들이 생성하고 싶은 것\"과 \"커서 모델로 표현 가능한 것\" 사이 격차는 커졌다. 세금계산서는 테이블. 리포트는 반복 헤더/푸터 붙은 그리드. 배송 라벨은 QR 코드 + 현지 언어 텍스트. 커서는 헬퍼로 감싸지고, 그 헬퍼는 튜토리얼로 감싸지고, 2023년 쯤에는 사람들이 \"gofpdf에 대해 쓴 코드\"는 사실 gofpdf가 아니었다 — 팀마다 쌓은 접착제 레이어였고, 그 레이어가 커서를 레이아웃 엔진인 척 위장시키려 했다.",[19,4086,4087,4089,4090,4093],{},[44,4088,3950],{},"은 이걸 그대로 이어받았다. 포크는 내부를 리팩토링하고 오래된 버그를 고쳤지만, ",[39,4091,4092],{},"공개 API의 형태는 바꿀 수 없었다"," — 바꾸는 순간 모든 하위 프로젝트가 깨지기 때문이다. 라이브러리의 모양은 2002년 PHP에 동결됐고, 그 모양을 유지하는 비용이 혜택보다 빠르게 증가했다.",[19,4095,4096,4099],{},[39,4097,4098],{},"즉",": 유지보수자 두 명, 아카이브 두 번, 아키텍처상의 이유 하나. 2026년에 다시 시작한다면, PDF가 실제로 생성되는 방식에 맞는 접근을 골라야 한다 — 오늘의 방식은 플로터를 움직이는 것보다 웹 페이지를 조립하는 쪽에 훨씬 가깝다.",[14,4101,4103],{"id":4102},"_2026년-go-pdf-지형","2026년 Go PDF 지형",[19,4105,4106],{},"뭔가를 추천하기 전에 일단 판부터 깔자. \"유지보수 중\"은 \"최근 6개월 내 커밋이 있고 이슈에 응답이 있다\"는 느슨한 의미로 쓴다.",[1892,4108,4109,4129],{},[1895,4110,4111],{},[1898,4112,4113,4116,4119,4121,4124,4127],{},[1901,4114,4115],{},"라이브러리",[1901,4117,4118],{},"상태 (2026-04)",[1901,4120,2398],{},[1901,4122,4123],{},"CJK 네이티브",[1901,4125,4126],{},"의존성 0",[1901,4128,3400],{},[1908,4130,4131,4154,4174,4194,4218,4239,4262],{},[1898,4132,4133,4137,4142,4145,4148,4151],{},[1913,4134,4135],{},[44,4136,3942],{},[1913,4138,4139],{},[39,4140,4141],{},"2021 아카이브",[1913,4143,4144],{},"MIT",[1913,4146,4147],{},"덧붙임",[1913,4149,4150],{},"예",[1913,4152,4153],{},"원본. 대부분 로케일에서 여전히 검색 1위.",[1898,4155,4156,4160,4165,4167,4169,4171],{},[1913,4157,4158],{},[44,4159,3950],{},[1913,4161,4162],{},[39,4163,4164],{},"2025 아카이브",[1913,4166,4144],{},[1913,4168,4147],{},[1913,4170,4150],{},[1913,4172,4173],{},"위의 커뮤니티 포크. 같은 아키텍처, 같은 천장.",[1898,4175,4176,4181,4184,4186,4189,4191],{},[1913,4177,4178],{},[44,4179,4180],{},"signintech/gopdf",[1913,4182,4183],{},"유지보수 중",[1913,4185,4144],{},[1913,4187,4188],{},"부분",[1913,4190,4150],{},[1913,4192,4193],{},"저수준. 좌표를 직접 쓴다. 폼 오버레이에 적합.",[1898,4195,4196,4202,4204,4206,4209,4212],{},[1913,4197,4198,4201],{},[44,4199,4200],{},"johnfercher/maroto"," v2",[1913,4203,4183],{},[1913,4205,4144],{},[1913,4207,4208],{},"gofpdf 경유",[1913,4210,4211],{},"아니오",[1913,4213,4214,4215,4217],{},"그리드 우선 빌더, 하지만 바닥에 ",[44,4216,3950],{},"가 있음.",[1898,4219,4220,4225,4227,4232,4234,4236],{},[1913,4221,4222],{},[44,4223,4224],{},"unidoc/unipdf",[1913,4226,4183],{},[1913,4228,4229],{},[39,4230,4231],{},"상용",[1913,4233,4150],{},[1913,4235,4211],{},[1913,4237,4238],{},"기능 완비 PDF SDK. 상용 사용에는 유료 라이선스 필수.",[1898,4240,4241,4247,4249,4252,4254,4259],{},[1913,4242,4243,4246],{},[44,4244,4245],{},"chromedp"," + Chromium",[1913,4248,4183],{},[1913,4250,4251],{},"MIT + Chrome",[1913,4253,4150],{},[1913,4255,4256],{},[39,4257,4258],{},"아니오 — 브라우저 동봉",[1913,4260,4261],{},"헤드리스 Chrome으로 HTML→PDF. 런타임 거대.",[1898,4263,4264,4268,4270,4272,4277,4281],{},[1913,4265,4266],{},[44,4267,337],{},[1913,4269,4183],{},[1913,4271,4144],{},[1913,4273,4274],{},[39,4275,4276],{},"네이티브",[1913,4278,4279],{},[39,4280,4150],{},[1913,4282,4283],{},"순수 Go 재구현. 빌더 API, 12 컬럼 그리드.",[19,4285,4286],{},"표만 봐도 몇 가지는 명확하다.",[19,4288,4289,4292,4293,4295,4296,4299],{},[39,4290,4291],{},"유지보수되는 모든 선택지는 상용 라이선스이거나, 거대한 런타임을 끌고 오거나, 곧 낡아질 기반 위에 서 있다",". 예외는 ",[44,4294,4180],{}," — 진짜로 유지되고 의존성도 가볍다. 하지만 좌표 수준 라이브러리다. 패키지 이름만 바꿔 ",[44,4297,4298],{},"SetXY","를 다시 쓰는 셈이다.",[19,4301,4302,4305,4306,4308,4309,4311],{},[39,4303,4304],{},"Maroto v2는 API가 좋은 그리드 우선 빌더",". 문제는 ",[44,4307,3988],{}," 바닥에 ",[44,4310,3950],{},"가 있다는 것. fpdf의 성능 천장과 CJK 한계가 그대로 Maroto의 천장이 된다. v3가 이걸 벗어날 가능성은 있지만, 아직 안 나왔다.",[19,4313,4314,4317],{},[39,4315,4316],{},"unipdf는 풍부하지만 상용에는 MIT 호환이 아니다",". 시트 단위 또는 배포 단위 과금. 매출이 그걸 받쳐준다면 괜찮은 선택, OSS 사이드 프로젝트나 초기 스타트업에는 라이선스 계산이 안 맞는다.",[19,4319,4320,4323],{},[39,4321,4322],{},"chromedp는 돌긴 하지만 브라우저를 출하하는 것",". 100 MB 베이스 이미지가 1 GB+로 불어난다. 서버리스 콜드 스타트가 괴롭다. 폰트도 따로 컨테이너에 넣어야 한다. 장점은 React 템플릿을 재사용할 수 있다는 것, 단점은 세금계산서 한 장을 뽑으려고 Chromium을 계속 돌린다는 것.",[19,4325,4326],{},"빈 자리는 명확하다: 순수 Go, 의존성 0, CJK 네이티브, 그리드 우선, 상용 라이선스도 브라우저 런타임도 필요 없는 라이브러리. 그게 gpdf다.",[14,4328,4330],{"id":4329},"gpdf란-무엇인가","gpdf란 무엇인가",[19,4332,4333,4334,4336],{},"gpdf (",[44,4335,169],{},")는 깨끗한 재구현. 포크가 아니다. PDF 와이어 포맷 writer, 레이아웃 엔진, TrueType 서브세터 — 전부 순수 Go로 처음부터 썼다.",[19,4338,4339],{},"대부분의 팀에 중요한 세 가지 속성:",[983,4341,4342,4356,4373],{},[36,4343,4344,4347,4348,4351,4352,4355],{},[39,4345,4346],{},"순수 Go, CGO 없음",". ",[44,4349,4350],{},"go build","는 정적. ",[44,4353,4354],{},"GOOS=linux GOARCH=arm64 go build","가 MacBook에서 툴체인 세팅 없이 통과한다. Docker 이미지도 작게 유지 — 12 MB distroless 컨테이너가 구동한다.",[36,4357,4358,4347,4361,4364,4365,4368,4369,4372],{},[39,4359,4360],{},"외부 의존성 0",[44,4362,4363],{},"go get github.com/gpdf-dev/gpdf"," 후 ",[44,4366,4367],{},"go mod graph","는 한 줄만 출력: gpdf 자체. 코어는 ",[44,4370,4371],{},"std","만 사용. (HTML→PDF나 디지털 서명 같은 옵션 애드온은 작은 의존성을 가져오지만 모두 opt-in.)",[36,4374,4375,4347,4378,4380,4381,4384],{},[39,4376,4377],{},"네이티브 CJK",[44,4379,50],{},"가 document 구축 시점에 TrueType 폰트를 등록한다. 서브셋 임베딩은 렌더링 시점에 자동. ",[39,4382,4383],{},"200자짜리 한국어/일본어 세금계산서의 임베디드 폰트는 약 30 KB 서브셋",". 5 MB 풀 폰트가 아니다.",[19,4386,4387],{},"API는 선언적. 행/컬럼의 트리를 기술하면 레이아웃 엔진이 배치한다. 그리드는 12 컬럼 — Bootstrap이 2011년부터 출시한 같은 idiom. HTML/CSS를 한 줄이라도 써본 사람이면 gpdf API는 익숙하다:",[97,4389,4391],{"className":99,"code":4390,"language":101,"meta":102,"style":102},"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",[44,4392,4393,4408,4432,4462,4500,4504,4534,4561,4565],{"__ignoreMap":102},[106,4394,4395,4398,4400,4402,4404,4406],{"class":108,"line":109},[106,4396,4397],{"class":224},"page ",[106,4399,234],{"class":112},[106,4401,455],{"class":224},[106,4403,240],{"class":112},[106,4405,460],{"class":211},[106,4407,463],{"class":112},[106,4409,4410,4412,4414,4416,4418,4420,4422,4424,4426,4428,4430],{"class":108,"line":120},[106,4411,849],{"class":224},[106,4413,240],{"class":112},[106,4415,474],{"class":211},[106,4417,477],{"class":112},[106,4419,481],{"class":480},[106,4421,484],{"class":112},[106,4423,487],{"class":116},[106,4425,240],{"class":112},[106,4427,492],{"class":116},[106,4429,495],{"class":112},[106,4431,218],{"class":112},[106,4433,4434,4436,4438,4440,4442,4444,4446,4448,4450,4452,4454,4456,4458,4460],{"class":108,"line":127},[106,4435,874],{"class":224},[106,4437,240],{"class":112},[106,4439,508],{"class":211},[106,4441,246],{"class":112},[106,4443,3310],{"class":379},[106,4445,228],{"class":112},[106,4447,518],{"class":112},[106,4449,521],{"class":480},[106,4451,484],{"class":112},[106,4453,487],{"class":116},[106,4455,240],{"class":112},[106,4457,530],{"class":116},[106,4459,495],{"class":112},[106,4461,218],{"class":112},[106,4463,4464,4466,4468,4470,4472,4474,4476,4478,4480,4482,4484,4486,4488,4490,4492,4494,4496,4498],{"class":108,"line":137},[106,4465,905],{"class":224},[106,4467,240],{"class":112},[106,4469,545],{"class":211},[106,4471,246],{"class":112},[106,4473,249],{"class":112},[106,4475,2839],{"class":252},[106,4477,249],{"class":112},[106,4479,228],{"class":112},[106,4481,1677],{"class":224},[106,4483,240],{"class":112},[106,4485,1682],{"class":211},[106,4487,246],{"class":112},[106,4489,2854],{"class":379},[106,4491,1690],{"class":112},[106,4493,1677],{"class":224},[106,4495,240],{"class":112},[106,4497,1697],{"class":211},[106,4499,1700],{"class":112},[106,4501,4502],{"class":108,"line":149},[106,4503,568],{"class":112},[106,4505,4506,4508,4510,4512,4514,4516,4518,4520,4522,4524,4526,4528,4530,4532],{"class":108,"line":159},[106,4507,874],{"class":224},[106,4509,240],{"class":112},[106,4511,508],{"class":211},[106,4513,246],{"class":112},[106,4515,3100],{"class":379},[106,4517,228],{"class":112},[106,4519,518],{"class":112},[106,4521,521],{"class":480},[106,4523,484],{"class":112},[106,4525,487],{"class":116},[106,4527,240],{"class":112},[106,4529,530],{"class":116},[106,4531,495],{"class":112},[106,4533,218],{"class":112},[106,4535,4536,4538,4540,4542,4544,4546,4548,4550,4552,4554,4556,4559],{"class":108,"line":164},[106,4537,905],{"class":224},[106,4539,240],{"class":112},[106,4541,545],{"class":211},[106,4543,246],{"class":112},[106,4545,249],{"class":112},[106,4547,3042],{"class":252},[106,4549,249],{"class":112},[106,4551,228],{"class":112},[106,4553,1677],{"class":224},[106,4555,240],{"class":112},[106,4557,4558],{"class":211},"AlignRight",[106,4560,1700],{"class":112},[106,4562,4563],{"class":108,"line":174},[106,4564,568],{"class":112},[106,4566,4567],{"class":108,"line":184},[106,4568,932],{"class":112},[19,4570,4571,4572,4574,4575,4578,4579,4581],{},"그리드 상세는 ",[720,4573,2555],{"href":3918},". 한 줄 요약: ",[44,4576,4577],{},"Col(span, fn)","은 1–12 사이의 span을 받고, ",[44,4580,2584],{},"가 그 컬럼이 행 너비에서 차지하는 비율.",[14,4583,4585],{"id":4584},"최소-go-pdffpdf-gpdf-디프","최소 go-pdf/fpdf → gpdf 디프",[19,4587,4588,4590,4591,4593,4594,4596,4597,4600],{},[44,4589,3950],{},"에서 오는 사람에게는 (",[44,4592,3942],{},"이 아니라) 좋은 소식이 있다: API 표면이 거의 같다. ",[44,4595,3950],{},"은 호출 측에서 보면 아무것도 바꾸지 않은 포크다. gpdf로의 마이그레이션은 ",[720,4598,4599],{"href":4005},"gofpdf 가이드","와 동일하고, import 경로를 한 줄 바꾸는 것에서 시작한다.",[19,4602,4603],{},"가장 작은 디프 — \"PDF 반환\" HTTP 핸들러:",[19,4605,4606],{},[39,4607,4608],{},"Before — go-pdf/fpdf:",[97,4610,4612],{"className":99,"code":4611,"language":101,"meta":102,"style":102},"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",[44,4613,4614,4620,4624,4630,4639,4643,4652,4656,4660,4699,4748,4759,4793,4823,4827,4863,4893,4924,4928],{"__ignoreMap":102},[106,4615,4616,4618],{"class":108,"line":109},[106,4617,113],{"class":112},[106,4619,117],{"class":116},[106,4621,4622],{"class":108,"line":120},[106,4623,124],{"emptyLinePlaceholder":123},[106,4625,4626,4628],{"class":108,"line":127},[106,4627,131],{"class":130},[106,4629,134],{"class":112},[106,4631,4632,4634,4637],{"class":108,"line":137},[106,4633,140],{"class":112},[106,4635,4636],{"class":116},"net/http",[106,4638,146],{"class":112},[106,4640,4641],{"class":108,"line":149},[106,4642,124],{"emptyLinePlaceholder":123},[106,4644,4645,4647,4650],{"class":108,"line":159},[106,4646,140],{"class":112},[106,4648,4649],{"class":116},"github.com/go-pdf/fpdf",[106,4651,146],{"class":112},[106,4653,4654],{"class":108,"line":164},[106,4655,197],{"class":112},[106,4657,4658],{"class":108,"line":174},[106,4659,124],{"emptyLinePlaceholder":123},[106,4661,4662,4664,4667,4669,4672,4675,4677,4680,4682,4685,4687,4690,4692,4695,4697],{"class":108,"line":184},[106,4663,208],{"class":112},[106,4665,4666],{"class":211}," handler",[106,4668,246],{"class":112},[106,4670,4671],{"class":480},"w",[106,4673,4674],{"class":116}," http",[106,4676,240],{"class":112},[106,4678,4679],{"class":116},"ResponseWriter",[106,4681,228],{"class":112},[106,4683,4684],{"class":480}," r",[106,4686,484],{"class":112},[106,4688,4689],{"class":116},"http",[106,4691,240],{"class":112},[106,4693,4694],{"class":116},"Request",[106,4696,495],{"class":112},[106,4698,218],{"class":112},[106,4700,4701,4704,4706,4709,4711,4714,4716,4718,4721,4723,4725,4728,4731,4733,4735,4737,4739,4741,4743,4746],{"class":108,"line":194},[106,4702,4703],{"class":224},"    pdf ",[106,4705,234],{"class":112},[106,4707,4708],{"class":224}," fpdf",[106,4710,240],{"class":112},[106,4712,4713],{"class":211},"New",[106,4715,246],{"class":112},[106,4717,249],{"class":112},[106,4719,4720],{"class":252},"P",[106,4722,249],{"class":112},[106,4724,228],{"class":112},[106,4726,4727],{"class":112}," \"",[106,4729,4730],{"class":252},"mm",[106,4732,249],{"class":112},[106,4734,228],{"class":112},[106,4736,4727],{"class":112},[106,4738,342],{"class":252},[106,4740,249],{"class":112},[106,4742,228],{"class":112},[106,4744,4745],{"class":112}," \"\"",[106,4747,197],{"class":112},[106,4749,4750,4753,4755,4757],{"class":108,"line":200},[106,4751,4752],{"class":224},"    pdf",[106,4754,240],{"class":112},[106,4756,460],{"class":211},[106,4758,463],{"class":112},[106,4760,4761,4763,4765,4768,4770,4772,4775,4777,4779,4781,4784,4786,4788,4791],{"class":108,"line":205},[106,4762,4752],{"class":224},[106,4764,240],{"class":112},[106,4766,4767],{"class":211},"SetFont",[106,4769,246],{"class":112},[106,4771,249],{"class":112},[106,4773,4774],{"class":252},"Arial",[106,4776,249],{"class":112},[106,4778,228],{"class":112},[106,4780,4727],{"class":112},[106,4782,4783],{"class":252},"B",[106,4785,249],{"class":112},[106,4787,228],{"class":112},[106,4789,4790],{"class":379}," 16",[106,4792,197],{"class":112},[106,4794,4795,4797,4799,4802,4804,4807,4809,4812,4814,4816,4819,4821],{"class":108,"line":221},[106,4796,4752],{"class":224},[106,4798,240],{"class":112},[106,4800,4801],{"class":211},"Cell",[106,4803,246],{"class":112},[106,4805,4806],{"class":379},"40",[106,4808,228],{"class":112},[106,4810,4811],{"class":379}," 10",[106,4813,228],{"class":112},[106,4815,4727],{"class":112},[106,4817,4818],{"class":252},"Hello, World!",[106,4820,249],{"class":112},[106,4822,197],{"class":112},[106,4824,4825],{"class":108,"line":260},[106,4826,124],{"emptyLinePlaceholder":123},[106,4828,4829,4832,4834,4837,4840,4843,4845,4847,4850,4852,4854,4856,4859,4861],{"class":108,"line":276},[106,4830,4831],{"class":224},"    w",[106,4833,240],{"class":112},[106,4835,4836],{"class":211},"Header",[106,4838,4839],{"class":112},"().",[106,4841,4842],{"class":211},"Set",[106,4844,246],{"class":112},[106,4846,249],{"class":112},[106,4848,4849],{"class":252},"Content-Type",[106,4851,249],{"class":112},[106,4853,228],{"class":112},[106,4855,4727],{"class":112},[106,4857,4858],{"class":252},"application/pdf",[106,4860,249],{"class":112},[106,4862,197],{"class":112},[106,4864,4865,4867,4869,4871,4874,4876,4879,4881,4883,4885,4887,4889,4891],{"class":108,"line":294},[106,4866,263],{"class":130},[106,4868,231],{"class":224},[106,4870,234],{"class":112},[106,4872,4873],{"class":224}," pdf",[106,4875,240],{"class":112},[106,4877,4878],{"class":211},"Output",[106,4880,246],{"class":112},[106,4882,4671],{"class":224},[106,4884,665],{"class":112},[106,4886,231],{"class":224},[106,4888,268],{"class":112},[106,4890,271],{"class":112},[106,4892,218],{"class":112},[106,4894,4895,4898,4900,4903,4905,4907,4909,4912,4914,4916,4919,4922],{"class":108,"line":300},[106,4896,4897],{"class":224},"        http",[106,4899,240],{"class":112},[106,4901,4902],{"class":211},"Error",[106,4904,246],{"class":112},[106,4906,4671],{"class":224},[106,4908,228],{"class":112},[106,4910,4911],{"class":224}," err",[106,4913,240],{"class":112},[106,4915,4902],{"class":211},[106,4917,4918],{"class":112},"(),",[106,4920,4921],{"class":379}," 500",[106,4923,197],{"class":112},[106,4925,4926],{"class":108,"line":305},[106,4927,297],{"class":112},[106,4929,4930],{"class":108,"line":324},[106,4931,699],{"class":112},[19,4933,4934],{},[39,4935,4936],{},"After — gpdf:",[97,4938,4940],{"className":99,"code":4939,"language":101,"meta":102,"style":102},"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",[44,4941,4942,4948,4952,4958,4966,4970,4978,4986,4994,4998,5002,5034,5048,5066,5096,5100,5104,5118,5142,5172,5211,5215,5219,5223,5253,5282,5308,5312],{"__ignoreMap":102},[106,4943,4944,4946],{"class":108,"line":109},[106,4945,113],{"class":112},[106,4947,117],{"class":116},[106,4949,4950],{"class":108,"line":120},[106,4951,124],{"emptyLinePlaceholder":123},[106,4953,4954,4956],{"class":108,"line":127},[106,4955,131],{"class":130},[106,4957,134],{"class":112},[106,4959,4960,4962,4964],{"class":108,"line":137},[106,4961,140],{"class":112},[106,4963,4636],{"class":116},[106,4965,146],{"class":112},[106,4967,4968],{"class":108,"line":149},[106,4969,124],{"emptyLinePlaceholder":123},[106,4971,4972,4974,4976],{"class":108,"line":159},[106,4973,140],{"class":112},[106,4975,169],{"class":116},[106,4977,146],{"class":112},[106,4979,4980,4982,4984],{"class":108,"line":164},[106,4981,140],{"class":112},[106,4983,179],{"class":116},[106,4985,146],{"class":112},[106,4987,4988,4990,4992],{"class":108,"line":174},[106,4989,140],{"class":112},[106,4991,189],{"class":116},[106,4993,146],{"class":112},[106,4995,4996],{"class":108,"line":184},[106,4997,197],{"class":112},[106,4999,5000],{"class":108,"line":194},[106,5001,124],{"emptyLinePlaceholder":123},[106,5003,5004,5006,5008,5010,5012,5014,5016,5018,5020,5022,5024,5026,5028,5030,5032],{"class":108,"line":200},[106,5005,208],{"class":112},[106,5007,4666],{"class":211},[106,5009,246],{"class":112},[106,5011,4671],{"class":480},[106,5013,4674],{"class":116},[106,5015,240],{"class":112},[106,5017,4679],{"class":116},[106,5019,228],{"class":112},[106,5021,4684],{"class":480},[106,5023,484],{"class":112},[106,5025,4689],{"class":116},[106,5027,240],{"class":112},[106,5029,4694],{"class":116},[106,5031,495],{"class":112},[106,5033,218],{"class":112},[106,5035,5036,5038,5040,5042,5044,5046],{"class":108,"line":205},[106,5037,308],{"class":224},[106,5039,234],{"class":112},[106,5041,313],{"class":224},[106,5043,240],{"class":112},[106,5045,318],{"class":211},[106,5047,321],{"class":112},[106,5049,5050,5052,5054,5056,5058,5060,5062,5064],{"class":108,"line":221},[106,5051,327],{"class":224},[106,5053,240],{"class":112},[106,5055,332],{"class":211},[106,5057,246],{"class":112},[106,5059,360],{"class":224},[106,5061,240],{"class":112},[106,5063,342],{"class":224},[106,5065,345],{"class":112},[106,5067,5068,5070,5072,5074,5076,5078,5080,5082,5084,5086,5088,5090,5092,5094],{"class":108,"line":260},[106,5069,327],{"class":224},[106,5071,240],{"class":112},[106,5073,355],{"class":211},[106,5075,246],{"class":112},[106,5077,360],{"class":224},[106,5079,240],{"class":112},[106,5081,365],{"class":211},[106,5083,246],{"class":112},[106,5085,360],{"class":224},[106,5087,240],{"class":112},[106,5089,374],{"class":211},[106,5091,246],{"class":112},[106,5093,380],{"class":379},[106,5095,383],{"class":112},[106,5097,5098],{"class":108,"line":276},[106,5099,439],{"class":112},[106,5101,5102],{"class":108,"line":294},[106,5103,124],{"emptyLinePlaceholder":123},[106,5105,5106,5108,5110,5112,5114,5116],{"class":108,"line":300},[106,5107,450],{"class":224},[106,5109,234],{"class":112},[106,5111,455],{"class":224},[106,5113,240],{"class":112},[106,5115,460],{"class":211},[106,5117,463],{"class":112},[106,5119,5120,5122,5124,5126,5128,5130,5132,5134,5136,5138,5140],{"class":108,"line":305},[106,5121,469],{"class":224},[106,5123,240],{"class":112},[106,5125,474],{"class":211},[106,5127,477],{"class":112},[106,5129,481],{"class":480},[106,5131,484],{"class":112},[106,5133,487],{"class":116},[106,5135,240],{"class":112},[106,5137,492],{"class":116},[106,5139,495],{"class":112},[106,5141,218],{"class":112},[106,5143,5144,5146,5148,5150,5152,5154,5156,5158,5160,5162,5164,5166,5168,5170],{"class":108,"line":324},[106,5145,503],{"class":224},[106,5147,240],{"class":112},[106,5149,508],{"class":211},[106,5151,246],{"class":112},[106,5153,513],{"class":379},[106,5155,228],{"class":112},[106,5157,518],{"class":112},[106,5159,521],{"class":480},[106,5161,484],{"class":112},[106,5163,487],{"class":116},[106,5165,240],{"class":112},[106,5167,530],{"class":116},[106,5169,495],{"class":112},[106,5171,218],{"class":112},[106,5173,5174,5176,5178,5180,5182,5184,5186,5188,5190,5192,5194,5196,5198,5201,5203,5205,5207,5209],{"class":108,"line":348},[106,5175,540],{"class":224},[106,5177,240],{"class":112},[106,5179,545],{"class":211},[106,5181,246],{"class":112},[106,5183,249],{"class":112},[106,5185,4818],{"class":252},[106,5187,249],{"class":112},[106,5189,228],{"class":112},[106,5191,1677],{"class":224},[106,5193,240],{"class":112},[106,5195,1682],{"class":211},[106,5197,246],{"class":112},[106,5199,5200],{"class":379},"16",[106,5202,1690],{"class":112},[106,5204,1677],{"class":224},[106,5206,240],{"class":112},[106,5208,1697],{"class":211},[106,5210,1700],{"class":112},[106,5212,5213],{"class":108,"line":386},[106,5214,562],{"class":112},[106,5216,5217],{"class":108,"line":411},[106,5218,568],{"class":112},[106,5220,5221],{"class":108,"line":436},[106,5222,124],{"emptyLinePlaceholder":123},[106,5224,5225,5227,5229,5231,5233,5235,5237,5239,5241,5243,5245,5247,5249,5251],{"class":108,"line":442},[106,5226,4831],{"class":224},[106,5228,240],{"class":112},[106,5230,4836],{"class":211},[106,5232,4839],{"class":112},[106,5234,4842],{"class":211},[106,5236,246],{"class":112},[106,5238,249],{"class":112},[106,5240,4849],{"class":252},[106,5242,249],{"class":112},[106,5244,228],{"class":112},[106,5246,4727],{"class":112},[106,5248,4858],{"class":252},[106,5250,249],{"class":112},[106,5252,197],{"class":112},[106,5254,5255,5257,5259,5261,5263,5265,5268,5270,5272,5274,5276,5278,5280],{"class":108,"line":447},[106,5256,263],{"class":130},[106,5258,231],{"class":224},[106,5260,234],{"class":112},[106,5262,455],{"class":224},[106,5264,240],{"class":112},[106,5266,5267],{"class":211},"Render",[106,5269,246],{"class":112},[106,5271,4671],{"class":224},[106,5273,665],{"class":112},[106,5275,231],{"class":224},[106,5277,268],{"class":112},[106,5279,271],{"class":112},[106,5281,218],{"class":112},[106,5283,5284,5286,5288,5290,5292,5294,5296,5298,5300,5302,5304,5306],{"class":108,"line":466},[106,5285,4897],{"class":224},[106,5287,240],{"class":112},[106,5289,4902],{"class":211},[106,5291,246],{"class":112},[106,5293,4671],{"class":224},[106,5295,228],{"class":112},[106,5297,4911],{"class":224},[106,5299,240],{"class":112},[106,5301,4902],{"class":211},[106,5303,4918],{"class":112},[106,5305,4921],{"class":379},[106,5307,197],{"class":112},[106,5309,5310],{"class":108,"line":500},[106,5311,297],{"class":112},[106,5313,5314],{"class":108,"line":537},[106,5315,699],{"class":112},[19,5317,5318,5319,5321,5322,5325,5326,5328,5329,240],{},"3줄 커서 코드가 3개의 빌더 호출로 바뀐다. 구조가 ",[44,5320,4801],{}," 호출 순서에 숨지 않고 소스 코드에 그대로 드러난다. CJK는 ",[44,5323,5324],{},"gpdf.WithFont(\"NotoSansJP\", ttfBytes)","만 추가하면 된다 — ",[44,5327,705],{},"도, 파일시스템 경로도, UTF-8 플래그도 필요 없다. 자세한 건 ",[720,5330,3837],{"href":1175},[19,5332,5333,5336,5337,5339],{},[720,5334,5335],{"href":4005},"gofpdf 마이그레이션 가이드","에는 테이블·반복 헤더/푸터·페이지 번호·절대 위치 지정의 before/after 5쌍이 더 있다. 거기 적힌 내용은 ",[44,5338,3950],{}," 사용자에게도 그대로 적용된다 — import 경로만 바꾸면 된다.",[14,5341,5343],{"id":5342},"벤치마크-그림","벤치마크 그림",[19,5345,5346,5347,5350],{},"\"빠르다\"는 쉽게 주장할 수 있고 어렵게 증명된다. 아래 표는 ",[44,5348,5349],{},"gpdf/_benchmark/benchmark_test.go"," 결과 — Apple M1, Go 1.25. 워크로드는 프로덕션 코드가 실제로 하는 일 — 어떤 라이브러리를 좋게 보이게 하려고 고른 마이크로 벤치가 아니다.",[1892,5352,5353,5371],{},[1895,5354,5355],{},[1898,5356,5357,5360,5362,5365,5368],{},[1901,5358,5359],{},"벤치마크",[1901,5361,337],{},[1901,5363,5364],{},"gofpdf",[1901,5366,5367],{},"gopdf",[1901,5369,5370],{},"Maroto v2",[1908,5372,5373,5392,5411,5429],{},[1898,5374,5375,5378,5383,5386,5389],{},[1913,5376,5377],{},"단일 페이지 (hello)",[1913,5379,5380],{},[39,5381,5382],{},"13 µs",[1913,5384,5385],{},"132 µs",[1913,5387,5388],{},"423 µs",[1913,5390,5391],{},"237 µs",[1898,5393,5394,5397,5402,5405,5408],{},[1913,5395,5396],{},"4×10 품목 테이블",[1913,5398,5399],{},[39,5400,5401],{},"108 µs",[1913,5403,5404],{},"241 µs",[1913,5406,5407],{},"835 µs",[1913,5409,5410],{},"8.6 ms",[1898,5412,5413,5416,5421,5424,5426],{},[1913,5414,5415],{},"100페이지 리포트",[1913,5417,5418],{},[39,5419,5420],{},"683 µs",[1913,5422,5423],{},"11.7 ms",[1913,5425,5410],{},[1913,5427,5428],{},"19.8 ms",[1898,5430,5431,5434,5439,5442,5445],{},[1913,5432,5433],{},"복잡한 CJK 세금계산서",[1913,5435,5436],{},[39,5437,5438],{},"133 µs",[1913,5440,5441],{},"254 µs",[1913,5443,5444],{},"997 µs",[1913,5446,5447],{},"10.4 ms",[19,5449,5450,5451,5454],{},"단일 페이지 13 µs면 1코어로 초당 약 75,000장. 품목 테이블 108 µs면 초당 약 9,000장. ",[39,5452,5453],{},"포인트는 벤치 자랑이 아니다"," — PDF 생성을 캐시해야 할지, 비동기 큐로 보내야 할지 고민하지 않아도 된다는 것. 대부분의 워크로드에서는 요청 경로에서 바로 생성해도 충분하다.",[19,5456,5457,5458,5460],{},"테이블 벤치에서 Maroto v2가 느리게 나오는 이유는 바닥에 ",[44,5459,3950],{},"를 놓고 그 위에 자체 레이아웃 패스를 하나 더 얹었기 때문이다. Maroto API에 대한 비판이 아니라 — API는 좋다 — fpdf 기반에 앉는 구조적 비용이다. Maroto v3가 fpdf 의존을 벗으면 이 열의 숫자는 바뀔 것이다.",[19,5462,5463],{},"100페이지 벤치는 조금 더 짚을 만하다. gpdf의 스트리밍 writer는 행을 레이아웃하면서 내용을 흘려보내고, gofpdf는 페이지별로 더 많은 상태를 버퍼링한다. 페이징이 많은 워크로드(월간 리포트, 카탈로그, 컴플라이언스 익스포트)에서는 문서 크기의 상한에서 차이가 \"분 vs 초\"가 된다.",[14,5465,5467,5468,5471],{"id":5466},"gpdf를-선택하지-말아야-할-때","gpdf를 ",[784,5469,5470],{},"선택하지 말아야"," 할 때",[19,5473,5474],{},"마이그레이션 글은 \"언제 안 옮길까\"에 정직하게 답해야 한다:",[983,5476,5477,5490,5499,5511],{},[36,5478,5479,5482,5483,5486,5487,5489],{},[39,5480,5481],{},"AcroForm / 입력 가능한 폼",". Acrobat에서 사용자가 입력하는 PDF를 만들려면 gpdf의 폼 필드 지원은 아직 최소한. ",[44,5484,5485],{},"unidoc","이 이 영역에서 더 완성도 높고, ",[44,5488,4180],{},"은 부분 지원한다. 미래 릴리스에서 채울 예정이지만 오늘은 구멍.",[36,5491,5492,4347,5495,5498],{},[39,5493,5494],{},"임의 벡터 경로와 복잡한 그리기",[44,5496,5497],{},"c.Line()","은 컬럼 안에 가로선 한 줄 긋는다. 베지어·커스텀 패스·그라디언트 채우기로 차트/기술 도면을 그려야 한다면 gpdf는 아직 거기 못 간다. (미리 렌더링한 차트 이미지 임베딩은 문제없음 — 여기서 말하는 건 그리기 프리미티브.)",[36,5500,5501,5506,5507,5510],{},[39,5502,5503,5505],{},[44,5504,4298],{}," 사용이 많은 기존 gofpdf 코드베이스",". 2,000줄의 커서 조작이면 마이그레이션은 치환이 아니라 재작성에 가깝다. 재작성 후 코드는 거의 항상 더 짧지만, 마감일에 \"거의 항상\"은 차가운 위로. ",[720,5508,5509],{"href":4005},"마이그레이션 가이드","에 솔직한 공수 추정을 적어뒀다.",[36,5512,5513,5516,5517,5520,5521,5523],{},[39,5514,5515],{},"지금 당장 풀 CSS 지원의 HTML → PDF가 필요하다",". gpdf의 ",[44,5518,5519],{},"gpdf-pro"," 애드온에 HTML 서브셋이 있지만 Chromium과의 완전한 CSS 패리티는 목표가 아니다. 템플릿이 복잡한 React 컴포넌트라면 ",[44,5522,4245],{},"나 상용 API가 더 직접적이다.",[19,5525,5526,5527,5530],{},"위 중 어느 것도 안 찌른다면 gpdf가 기본값. 하나라도 찌른다면 ",[39,5528,5529],{},"두 라이브러리를 병존","하는 게 보통이다 — 새 PDF는 gpdf, 엣지 케이스는 기존 것에 남겨두고, gpdf가 따라잡으면 그때 옮긴다.",[14,5532,5534],{"id":5533},"컴플라이언스-관점","컴플라이언스 관점",[19,5536,5537,5538,5541],{},"생태계 글에서 잘 다루지 않는 포인트: ",[39,5539,5540],{},"아카이브된 의존성은 SOC 2와 ISO 27001 감사 보고서에 뜬다",". 감사관은 공급망의 서드파티 코드가 적극 유지되는지 알고 싶어 한다. \"2021 아카이브\"는 finding을 띄우고, \"2025 아카이브\"도 띄운다. \"내부 포크\"는 0-day 패치 절차에 대한 추가 질문을 유발한다.",[19,5543,5544,5545,4347,5548,5550],{},"이게 큰 회사의 보안 리뷰를 통과하는 팀들이 조용히 \"gpdf 안정 v1은 언제인가\"를 묻는 주된 이유다. 답은: ",[39,5546,5547],{},"이미 나왔다",[44,5549,169],{},"은 semver 태그가 있고 v1 API 표면이 동결됐다. 프로젝트에는 보안 연락처, 책임 있는 공개 정책, Go 1.22–1.26을 CI에서 돌리는 체계가 있다.",[19,5552,5553,5554,5557,5558,5561],{},"감사 ",[784,5555,5556],{},"때문에"," 옮기는 게 아니다 — ",[39,5559,5560],{},"감사가 요구하기 전에 움직이는 것","이 목적.",[14,5563,5565],{"id":5564},"faq","FAQ",[19,5567,5568,5571,5572,5574,5575,5577],{},[39,5569,5570],{},"\"현대 Go PDF 스택\"은 gpdf 한 라이브러리인가, 여러 라이브러리 조합인가?","\n대부분의 팀에선 gpdf 하나. 단일 라이브러리가 문서 생성·CJK·테이블·그리드·페이지네이션·출력을 커버한다. 폼 필드 요구사항이 있는 팀은 그 종류의 문서에만 ",[44,5573,4180],{},"나 ",[44,5576,5485],{},"을 추가로 쓴다. 차트 위주 팀은 차트를 PNG로 미리 렌더링해서 임베드한다. 여기서 \"스택\"은 층상 아키텍처가 아니라 짧은 리스트의 의미.",[19,5579,5580,5583],{},[39,5581,5582],{},"마이그레이션 중 gpdf와 go-pdf/fpdf를 병존할 수 있나?","\n할 수 있다. import 경로도 타입도 다르다. 새 엔드포인트는 gpdf, 오래된 것은 시간이 될 때까지 go-pdf/fpdf에 남겨두면 된다. 런타임 충돌은 없다.",[19,5585,5586,5589,5590,5593],{},[39,5587,5588],{},"go-pdf/fpdf v3나 새 포크가 나올까?","\n혹시 모른다. gpdf의 베팅은 \"그 포크가 영원히 아카이브로 남는다\"가 아니라 — ",[39,5591,5592],{},"아키텍처가 오늘 만드는 것에 스케일하지 않는다"," 는 쪽. 새 포크가 레이아웃 모델을 안 고치면 같은 제약을 물려받는다. 고치면 그건 fpdf보다 gpdf에 가깝다.",[19,5595,5596,5602,5603,2567,5606,2567,5609,5612],{},[39,5597,5598,5599,5601],{},"현대 대안으로 ",[44,5600,4180],{},"은 어떤가?","\n진짜로 유지되고 진짜로 의존성 0. API는 좌표 수준 — ",[44,5604,5605],{},"SetX",[44,5607,5608],{},"SetY",[44,5610,5611],{},"CellWithOption"," — 폼 오버레이와 고정 템플릿에 잘 맞는다. 테이블과 반복 헤더/푸터가 있는 세금계산서류 문서에서는 결국 위에 레이아웃 헬퍼를 쓰게 되고, gofpdf 사용자가 빠진 같은 함정으로 돌아간다. gpdf와 gopdf는 사실 경쟁 관계가 아니다 — 인접한 문제를 푼다.",[19,5614,5615,5618,5621],{},[39,5616,5617],{},"gpdf에 상용/호스티드 버전이 있나?",[44,5619,5620],{},"gpdf-api"," 준비 중 — JSON 템플릿을 POST하면 PDF를 돌려주는 호스티드 API. 아직 공개하지 않았다. 출시할 때 이 블로그에 글이 올라온다. OSS 라이브러리는 계속 MIT, 의존성 0, 독립적으로 유용한 상태로 유지된다.",[19,5623,5624,5627],{},[39,5625,5626],{},"로드맵 우선순위는?","\n2026-04 시점의 공개 로드맵: (1) AcroForm 폼 필드, (2) 완전한 PDF/A-3 준수, (3) gpdf-pro의 HTML→PDF 커버리지 확장, (4) RTL 텍스트 지원(아랍어, 히브리어). 우선순위 피드백은 GitHub 이슈에서 환영.",[14,5629,3857],{"id":3856},[19,5631,3860],{},[97,5633,5634],{"className":1208,"code":1209,"language":1210,"meta":102,"style":102},[44,5635,5636],{"__ignoreMap":102},[106,5637,5638,5640,5642],{"class":108,"line":109},[106,5639,101],{"class":116},[106,5641,1219],{"class":252},[106,5643,1222],{"class":252},[19,5645,5646,1230,5649],{},[720,5647,3878],{"href":1227,"rel":5648},[724],[720,5650,1235],{"href":1233,"rel":5651},[724],[14,5653,5655],{"id":5654},"다음-읽기","다음 읽기",[983,5657,5658,5664,5669,5674],{},[36,5659,5660,5663],{},[720,5661,5662],{"href":4005},"gofpdf이 아카이브됐다. gpdf로 마이그레이션하는 법."," — 한 API씩 매핑",[36,5665,5666,5668],{},[720,5667,3844],{"href":3843}," — 더 깊은 헤드투헤드 벤치마크와 기능 그리드",[36,5670,5671,5673],{},[720,5672,2555],{"href":3918}," — 커서 조작을 대체하는 빌더 관용구",[36,5675,5676,2482,5678,5680],{},[720,5677,3837],{"href":1175},[44,5679,705],{}," 춤 없는 CJK",[1237,5682,5683],{},"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":102,"searchDepth":120,"depth":120,"links":5685},[5686,5687,5688,5689,5690,5691,5692,5693,5694,5696,5697,5698,5699],{"id":3932,"depth":120,"text":3933},{"id":3960,"depth":120,"text":3961},{"id":4013,"depth":120,"text":4014},{"id":4059,"depth":120,"text":4060},{"id":4102,"depth":120,"text":4103},{"id":4329,"depth":120,"text":4330},{"id":4584,"depth":120,"text":4585},{"id":5342,"depth":120,"text":5343},{"id":5466,"depth":120,"text":5695},"gpdf를 선택하지 말아야 할 때",{"id":5533,"depth":120,"text":5534},{"id":5564,"depth":120,"text":5565},{"id":3856,"depth":120,"text":3857},{"id":5654,"depth":120,"text":5655},"jung-kurt/gofpdf은 2021년, go-pdf/fpdf은 2025년에 아카이브. 2026년에 실제로 쓰는 Go PDF 스택은 gpdf — 이유와 트레이드오프.",{},"/ko/blog/go-pdf-fpdf-archived",{"title":3926,"description":5700},"ko/blog/006.go-pdf-fpdf-archived",[5706,5707,5708],"migration","comparison","benchmark","A_mpHbEbXj57b2z_pnaK3humiJrHb5-Ce-74dLWLNCw",{"id":5711,"title":5712,"author":5713,"body":5714,"date":3042,"description":7393,"draft":1254,"extension":1255,"howTo":7394,"image":1278,"meta":7418,"navigation":123,"path":1196,"seo":7419,"stem":7420,"tags":7421,"updated":1278,"__hash__":7422},"blogKo/ko/blog/007.japanese-pdf-in-go.md","Go로 일본어 PDF 만들기 — 2026 결정판 가이드",{"name":8,"url":9},{"type":11,"value":5715,"toc":7379},[5716,5718,5737,5741,5744,5750,5761,5764,5768,5779,5818,5830,5840,5844,5847,5962,5965,5971,5981,5985,5993,6811,6814,6865,6874,6878,6887,6890,6897,6943,6957,6960,6984,7000,7004,7007,7014,7017,7032,7042,7124,7130,7133,7136,7146,7155,7179,7193,7196,7198,7205,7215,7224,7238,7245,7247,7272,7278,7288,7302,7312,7321,7325,7328,7340,7349,7353,7377],[14,5717,3933],{"id":3932},[19,5719,5720,5721,5723,5724,1315,5727,5729,5730,5733,5734,240],{},"Go PDF에 ",[44,5722,916],{},"라 썼는데 두부 □□□□□ 다섯 개가 나왔다면, 해법은 재작성이 아니라 설정 2줄이다. TTF를 읽고 ",[44,5725,5726],{},"gpdf.WithFont",[44,5728,318],{},"에 넘긴 뒤 일본어를 쓴다. ",[39,5731,5732],{},"gpdf는 글리프 테이블을 자동으로 서브셋한다"," — 출력에는 실제로 쓴 문자의 글리프만 실린다. 5 MB 풀 폰트가 아닌 약 30 KB. 이 글은 그 전체 지도다: Go에서 일본어 PDF가 왜 묘하게 어려웠는지, 2026년의 현실적 선택지 네 개, 동작하는 예제, 폰트 서브셋의 내부, 혼용 조판의 실무, 그리고 ",[39,5735,5736],{},"아직 풀리지 않은 부분",[14,5738,5740],{"id":5739},"이-가이드가-존재하는-이유","이 가이드가 존재하는 이유",[19,5742,5743],{},"Go에서 일본어 PDF를 뱉는 것은 본래 5분짜리 작업이다. 많은 팀에서 하루 반이 걸린다.",[19,5745,5746,5747,5749],{},"익숙한 전개: 누군가 ",[44,5748,705],{},"를 끼워넣는다 → PDF에 빈 사각형이 줄지어 나온다 — 그 유명한 豆腐 — 시니어 한 명이 오후 내내 폰트 경로인지, 서브셋 플래그인지, CMap인지, UTF-8 스위치인지, PDF 리더 문제인지를 좁힌다. 저녁이면 Slack에 \"왜 漢字가 아직 깨졌는가\"라는 스레드가 서고, 다음 날 누구나 후회할 헬퍼 세 개가 추가된 PR이 올라온다.",[19,5751,5752,5753,5756,5757,5760],{},"근본 원인은 그중 어느 것도 아니다. ",[39,5754,5755],{},"Go에서 가장 오래 살아남은 PDF 라이브러리가 2002년의 PHP와 Latin-1을 전제로 설계","된 것과, 그 이후 쓰인 일본어 튜토리얼 대부분이 그 유산과 싸워왔다는 것. 이 글은 2026판이다 — 백지에서 시작할 때 실제로 동작하는 방법과, ",[39,5758,5759],{},"여전히 어려운 지점","을 정직하게.",[19,5762,5763],{},"본문 코드는 gpdf v1.x (2026-04 기준)에서 동작 확인. 벤치 수치는 Apple M1 + Go 1.25.",[14,5765,5767],{"id":5766},"두부-문제를-90초에","두부 문제를 90초에",[19,5769,5770,5771,5774,5775,5778],{},"PDF는 Unicode를 모른다. PDF가 아는 것은 ",[39,5772,5773],{},"글리프 ID"," — 임베디드 글리프 테이블의 정수 인덱스. ",[44,5776,5777],{},"\"こんにちは\"","를 PDF에 쓰려면 누군가 다음을 모두 해야 한다:",[33,5780,5781,5790,5796,5802],{},[36,5782,5783,2482,5786,5789],{},[39,5784,5785],{},"TTF 파싱",[44,5787,5788],{},"cmap"," 서브테이블에서 각 코드포인트에 해당하는 글리프 ID를 찾는다.",[36,5791,5792,5795],{},[39,5793,5794],{},"ToUnicode CMap 작성"," — 사용자가 복사·검색할 때 글리프를 텍스트로 되돌릴 수 있게.",[36,5797,5798,5801],{},[39,5799,5800],{},"서브셋화"," — Noto Sans JP의 2만 글리프를 전부 싣지 않도록.",[36,5803,5804,2482,5807,5810,5811,5810,5814,5817],{},[39,5805,5806],{},"임베딩",[44,5808,5809],{},"name","·",[44,5812,5813],{},"OS/2",[44,5815,5816],{},"head"," 테이블과 인코딩 오브젝트를 올바르게 엮어서.",[19,5819,5820,5821,960,5823,5825,5826,5829],{},"이 중 어느 것이라도 빠지거나 틀리면 리더는 코드포인트에 해당하는 글리프를 못 찾고 두부를 그린다. 아카이브된 ",[44,5822,3942],{},[44,5824,3950],{}," 계열은 이 모든 것을 ",[39,5827,5828],{},"단일 바이트 폰트 전제의 내부 모델에 덧댔다"," — 2002년의 FPDF는 Latin-1만 알았다. 설정이 부서지기 쉬운 이유, 출력이 서브셋이 아닌 풀 폰트를 자주 임베드하는 이유, OS·리더에 따라 부서지는 양상이 다른 이유가 여기에 있다.",[19,5831,5832,5833,5836,5837,5839],{},"gpdf는 CJK를 ",[39,5834,5835],{},"일급 사용례","로 다룬다. TTF 서브셋터가 코어 패키지에 포함된다. ToUnicode CMap은 자동으로 쓰인다. 단일 바이트 폰트 유산이 없으니 ",[44,5838,705],{}," 춤도 없다.",[14,5841,5843],{"id":5842},"_2026년의-현실적-선택지-네-개","2026년의 현실적 선택지 네 개",[19,5845,5846],{},"코드 전에 정직한 판 — \"일본어 지원\"은 \"올바른 TTF가 주어졌을 때 크래시도 두부도 없이 임의의 일본어를 렌더할 수 있다\"의 의미로 쓴다.",[1892,5848,5849,5869],{},[1895,5850,5851],{},[1898,5852,5853,5856,5858,5861,5864,5867],{},[1901,5854,5855],{},"선택지",[1901,5857,2398],{},[1901,5859,5860],{},"의존",[1901,5862,5863],{},"CJK 경로",[1901,5865,5866],{},"300자 문서 크기",[1901,5868,3400],{},[1908,5870,5871,5894,5916,5938],{},[1898,5872,5873,5878,5880,5883,5888,5891],{},[1913,5874,5875,5877],{},[44,5876,3950],{}," (2025 아카이브)",[1913,5879,4144],{},[1913,5881,5882],{},"표준 라이브러리",[1913,5884,5885,5887],{},[44,5886,705],{}," 덧댐",[1913,5889,5890],{},"약 5 MB (풀)",[1913,5892,5893],{},"Latin-1 코어 위에 덧댐. 서브셋은 옵트인이며 불완전.",[1898,5895,5896,5900,5902,5904,5910,5913],{},[1913,5897,5898],{},[44,5899,4180],{},[1913,5901,4144],{},[1913,5903,5882],{},[1913,5905,5906,5909],{},[44,5907,5908],{},"AddTTFFont"," + 수동",[1913,5911,5912],{},"약 3 MB",[1913,5914,5915],{},"저수준. 좌표를 직접 쓴다. 서브셋 기능은 있지만 수동 구동.",[1898,5917,5918,5922,5924,5929,5932,5935],{},[1913,5919,5920,4246],{},[44,5921,4245],{},[1913,5923,4251],{},[1913,5925,5926],{},[39,5927,5928],{},"Chromium 바이너리",[1913,5930,5931],{},"브라우저 네이티브",[1913,5933,5934],{},"가변",[1913,5936,5937],{},"HTML/CSS. 컨테이너에 폰트 설치 필요. 이미지 500 MB+.",[1898,5939,5940,5944,5946,5951,5954,5959],{},[1913,5941,5942],{},[44,5943,337],{},[1913,5945,4144],{},[1913,5947,5948],{},[39,5949,5950],{},"표준만",[1913,5952,5953],{},"네이티브, 자동 서브셋",[1913,5955,5956],{},[39,5957,5958],{},"약 30 KB",[1913,5960,5961],{},"순수 Go. Builder API. ToUnicode CMap 자동 기록.",[19,5963,5964],{},"두 가지 강조:",[19,5966,5967,5970],{},[39,5968,5969],{},"\"풀 임베드\"와 \"자동 서브셋\"의 160배 차이는 오차가 아니다."," 10줄짜리 EC 일본어 청구서 PDF가 사용하는 고유 글리프는 많아야 120개쯤. 매번 풀 Noto Sans JP (5.1 MB)를 임베드하면 연말까지 같은 5 MB 글리프 데이터가 오브젝트 스토리지에 1000만 번 복사된다. 서브셋 임베드는 사용한 글리프만 싣는다.",[19,5972,5973,5976,5977,5980],{},[39,5974,5975],{},"\"chromedp 된다\"는 사실이고, 가장 비싼 답이다."," 스크린샷 용도로 헤드리스 Chrome 함대를 이미 운용 중인 팀이라면 PDF도 거기 얹는 게 괜찮다. 그게 아니라 ",[39,5978,5979],{},"오직 일본어 출력을 위해"," Chromium을 세우는 것은, 40줄의 Go로 풀리는 문제에 과한 인프라다.",[14,5982,5984],{"id":5983},"가장-짧은-동작-경로","가장 짧은 동작 경로",[19,5986,5987,5988,5990,5991,240],{},"먼저 이걸 돌린다. 완전형 — 복사해서 ",[44,5989,1879],{},"로 저장하고 TTF 두 개를 옆에 두고 ",[44,5992,1883],{},[97,5994,5996],{"className":99,"code":5995,"language":101,"meta":102,"style":102},"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",[44,5997,5998,6004,6008,6014,6022,6030,6034,6042,6050,6058,6062,6066,6076,6103,6115,6129,6133,6161,6173,6187,6191,6195,6209,6227,6257,6280,6304,6327,6331,6335,6349,6373,6403,6450,6469,6473,6477,6501,6532,6564,6583,6587,6618,6661,6688,6692,6696,6700,6718,6730,6744,6748,6789,6803,6807],{"__ignoreMap":102},[106,5999,6000,6002],{"class":108,"line":109},[106,6001,113],{"class":112},[106,6003,117],{"class":116},[106,6005,6006],{"class":108,"line":120},[106,6007,124],{"emptyLinePlaceholder":123},[106,6009,6010,6012],{"class":108,"line":127},[106,6011,131],{"class":130},[106,6013,134],{"class":112},[106,6015,6016,6018,6020],{"class":108,"line":137},[106,6017,140],{"class":112},[106,6019,143],{"class":116},[106,6021,146],{"class":112},[106,6023,6024,6026,6028],{"class":108,"line":149},[106,6025,140],{"class":112},[106,6027,154],{"class":116},[106,6029,146],{"class":112},[106,6031,6032],{"class":108,"line":159},[106,6033,124],{"emptyLinePlaceholder":123},[106,6035,6036,6038,6040],{"class":108,"line":164},[106,6037,140],{"class":112},[106,6039,169],{"class":116},[106,6041,146],{"class":112},[106,6043,6044,6046,6048],{"class":108,"line":174},[106,6045,140],{"class":112},[106,6047,179],{"class":116},[106,6049,146],{"class":112},[106,6051,6052,6054,6056],{"class":108,"line":184},[106,6053,140],{"class":112},[106,6055,189],{"class":116},[106,6057,146],{"class":112},[106,6059,6060],{"class":108,"line":194},[106,6061,197],{"class":112},[106,6063,6064],{"class":108,"line":200},[106,6065,124],{"emptyLinePlaceholder":123},[106,6067,6068,6070,6072,6074],{"class":108,"line":205},[106,6069,208],{"class":112},[106,6071,212],{"class":211},[106,6073,215],{"class":112},[106,6075,218],{"class":112},[106,6077,6078,6081,6083,6085,6087,6089,6091,6093,6095,6097,6099,6101],{"class":108,"line":221},[106,6079,6080],{"class":224},"    regular",[106,6082,228],{"class":112},[106,6084,231],{"class":224},[106,6086,234],{"class":112},[106,6088,237],{"class":224},[106,6090,240],{"class":112},[106,6092,243],{"class":211},[106,6094,246],{"class":112},[106,6096,249],{"class":112},[106,6098,253],{"class":252},[106,6100,249],{"class":112},[106,6102,197],{"class":112},[106,6104,6105,6107,6109,6111,6113],{"class":108,"line":260},[106,6106,263],{"class":130},[106,6108,231],{"class":224},[106,6110,268],{"class":112},[106,6112,271],{"class":112},[106,6114,218],{"class":112},[106,6116,6117,6119,6121,6123,6125,6127],{"class":108,"line":276},[106,6118,279],{"class":224},[106,6120,240],{"class":112},[106,6122,284],{"class":211},[106,6124,246],{"class":112},[106,6126,289],{"class":224},[106,6128,197],{"class":112},[106,6130,6131],{"class":108,"line":294},[106,6132,297],{"class":112},[106,6134,6135,6138,6140,6142,6144,6146,6148,6150,6152,6154,6157,6159],{"class":108,"line":300},[106,6136,6137],{"class":224},"    bold",[106,6139,228],{"class":112},[106,6141,231],{"class":224},[106,6143,234],{"class":112},[106,6145,237],{"class":224},[106,6147,240],{"class":112},[106,6149,243],{"class":211},[106,6151,246],{"class":112},[106,6153,249],{"class":112},[106,6155,6156],{"class":252},"NotoSansJP-Bold.ttf",[106,6158,249],{"class":112},[106,6160,197],{"class":112},[106,6162,6163,6165,6167,6169,6171],{"class":108,"line":305},[106,6164,263],{"class":130},[106,6166,231],{"class":224},[106,6168,268],{"class":112},[106,6170,271],{"class":112},[106,6172,218],{"class":112},[106,6174,6175,6177,6179,6181,6183,6185],{"class":108,"line":324},[106,6176,279],{"class":224},[106,6178,240],{"class":112},[106,6180,284],{"class":211},[106,6182,246],{"class":112},[106,6184,289],{"class":224},[106,6186,197],{"class":112},[106,6188,6189],{"class":108,"line":348},[106,6190,297],{"class":112},[106,6192,6193],{"class":108,"line":386},[106,6194,124],{"emptyLinePlaceholder":123},[106,6196,6197,6199,6201,6203,6205,6207],{"class":108,"line":411},[106,6198,308],{"class":224},[106,6200,234],{"class":112},[106,6202,313],{"class":224},[106,6204,240],{"class":112},[106,6206,318],{"class":211},[106,6208,321],{"class":112},[106,6210,6211,6213,6215,6217,6219,6221,6223,6225],{"class":108,"line":436},[106,6212,327],{"class":224},[106,6214,240],{"class":112},[106,6216,332],{"class":211},[106,6218,246],{"class":112},[106,6220,360],{"class":224},[106,6222,240],{"class":112},[106,6224,342],{"class":224},[106,6226,345],{"class":112},[106,6228,6229,6231,6233,6235,6237,6239,6241,6243,6245,6247,6249,6251,6253,6255],{"class":108,"line":442},[106,6230,327],{"class":224},[106,6232,240],{"class":112},[106,6234,355],{"class":211},[106,6236,246],{"class":112},[106,6238,360],{"class":224},[106,6240,240],{"class":112},[106,6242,365],{"class":211},[106,6244,246],{"class":112},[106,6246,360],{"class":224},[106,6248,240],{"class":112},[106,6250,374],{"class":211},[106,6252,246],{"class":112},[106,6254,380],{"class":379},[106,6256,383],{"class":112},[106,6258,6259,6261,6263,6265,6267,6269,6271,6273,6275,6278],{"class":108,"line":447},[106,6260,327],{"class":224},[106,6262,240],{"class":112},[106,6264,50],{"class":211},[106,6266,246],{"class":112},[106,6268,249],{"class":112},[106,6270,399],{"class":252},[106,6272,249],{"class":112},[106,6274,228],{"class":112},[106,6276,6277],{"class":224}," regular",[106,6279,345],{"class":112},[106,6281,6282,6284,6286,6288,6290,6292,6295,6297,6299,6302],{"class":108,"line":466},[106,6283,327],{"class":224},[106,6285,240],{"class":112},[106,6287,50],{"class":211},[106,6289,246],{"class":112},[106,6291,249],{"class":112},[106,6293,6294],{"class":252},"NotoSansJP-Bold",[106,6296,249],{"class":112},[106,6298,228],{"class":112},[106,6300,6301],{"class":224}," bold",[106,6303,345],{"class":112},[106,6305,6306,6308,6310,6312,6314,6316,6318,6320,6322,6325],{"class":108,"line":500},[106,6307,327],{"class":224},[106,6309,240],{"class":112},[106,6311,418],{"class":211},[106,6313,246],{"class":112},[106,6315,249],{"class":112},[106,6317,399],{"class":252},[106,6319,249],{"class":112},[106,6321,228],{"class":112},[106,6323,6324],{"class":379}," 11",[106,6326,345],{"class":112},[106,6328,6329],{"class":108,"line":537},[106,6330,439],{"class":112},[106,6332,6333],{"class":108,"line":559},[106,6334,124],{"emptyLinePlaceholder":123},[106,6336,6337,6339,6341,6343,6345,6347],{"class":108,"line":565},[106,6338,450],{"class":224},[106,6340,234],{"class":112},[106,6342,455],{"class":224},[106,6344,240],{"class":112},[106,6346,460],{"class":211},[106,6348,463],{"class":112},[106,6350,6351,6353,6355,6357,6359,6361,6363,6365,6367,6369,6371],{"class":108,"line":571},[106,6352,469],{"class":224},[106,6354,240],{"class":112},[106,6356,474],{"class":211},[106,6358,477],{"class":112},[106,6360,481],{"class":480},[106,6362,484],{"class":112},[106,6364,487],{"class":116},[106,6366,240],{"class":112},[106,6368,492],{"class":116},[106,6370,495],{"class":112},[106,6372,218],{"class":112},[106,6374,6375,6377,6379,6381,6383,6385,6387,6389,6391,6393,6395,6397,6399,6401],{"class":108,"line":576},[106,6376,503],{"class":224},[106,6378,240],{"class":112},[106,6380,508],{"class":211},[106,6382,246],{"class":112},[106,6384,513],{"class":379},[106,6386,228],{"class":112},[106,6388,518],{"class":112},[106,6390,521],{"class":480},[106,6392,484],{"class":112},[106,6394,487],{"class":116},[106,6396,240],{"class":112},[106,6398,530],{"class":116},[106,6400,495],{"class":112},[106,6402,218],{"class":112},[106,6404,6405,6407,6409,6411,6413,6415,6417,6419,6421,6423,6425,6427,6429,6431,6433,6435,6437,6439,6441,6443,6445,6448],{"class":108,"line":597},[106,6406,540],{"class":224},[106,6408,240],{"class":112},[106,6410,545],{"class":211},[106,6412,246],{"class":112},[106,6414,249],{"class":112},[106,6416,1670],{"class":252},[106,6418,249],{"class":112},[106,6420,228],{"class":112},[106,6422,1677],{"class":224},[106,6424,240],{"class":112},[106,6426,2261],{"class":211},[106,6428,246],{"class":112},[106,6430,249],{"class":112},[106,6432,6294],{"class":252},[106,6434,249],{"class":112},[106,6436,1690],{"class":112},[106,6438,1677],{"class":224},[106,6440,240],{"class":112},[106,6442,1682],{"class":211},[106,6444,246],{"class":112},[106,6446,6447],{"class":379},"22",[106,6449,2284],{"class":112},[106,6451,6452,6454,6456,6458,6460,6462,6465,6467],{"class":108,"line":610},[106,6453,540],{"class":224},[106,6455,240],{"class":112},[106,6457,545],{"class":211},[106,6459,246],{"class":112},[106,6461,249],{"class":112},[106,6463,6464],{"class":252},"2026 年 4 月 16 日",[106,6466,249],{"class":112},[106,6468,197],{"class":112},[106,6470,6471],{"class":108,"line":625},[106,6472,562],{"class":112},[106,6474,6475],{"class":108,"line":630},[106,6476,568],{"class":112},[106,6478,6479,6481,6483,6485,6487,6489,6491,6493,6495,6497,6499],{"class":108,"line":676},[106,6480,469],{"class":224},[106,6482,240],{"class":112},[106,6484,474],{"class":211},[106,6486,477],{"class":112},[106,6488,481],{"class":480},[106,6490,484],{"class":112},[106,6492,487],{"class":116},[106,6494,240],{"class":112},[106,6496,492],{"class":116},[106,6498,495],{"class":112},[106,6500,218],{"class":112},[106,6502,6503,6505,6507,6509,6511,6514,6516,6518,6520,6522,6524,6526,6528,6530],{"class":108,"line":691},[106,6504,503],{"class":224},[106,6506,240],{"class":112},[106,6508,508],{"class":211},[106,6510,246],{"class":112},[106,6512,6513],{"class":379},"7",[106,6515,228],{"class":112},[106,6517,518],{"class":112},[106,6519,521],{"class":480},[106,6521,484],{"class":112},[106,6523,487],{"class":116},[106,6525,240],{"class":112},[106,6527,530],{"class":116},[106,6529,495],{"class":112},[106,6531,218],{"class":112},[106,6533,6534,6536,6538,6540,6542,6544,6547,6549,6551,6553,6555,6557,6559,6562],{"class":108,"line":696},[106,6535,540],{"class":224},[106,6537,240],{"class":112},[106,6539,545],{"class":211},[106,6541,246],{"class":112},[106,6543,249],{"class":112},[106,6545,6546],{"class":252},"株式会社 ABC 御中",[106,6548,249],{"class":112},[106,6550,228],{"class":112},[106,6552,1677],{"class":224},[106,6554,240],{"class":112},[106,6556,1682],{"class":211},[106,6558,246],{"class":112},[106,6560,6561],{"class":379},"13",[106,6563,2284],{"class":112},[106,6565,6566,6568,6570,6572,6574,6576,6579,6581],{"class":108,"line":1856},[106,6567,540],{"class":224},[106,6569,240],{"class":112},[106,6571,545],{"class":211},[106,6573,246],{"class":112},[106,6575,249],{"class":112},[106,6577,6578],{"class":252},"〒 100-0001 東京都千代田区千代田 1-1",[106,6580,249],{"class":112},[106,6582,197],{"class":112},[106,6584,6585],{"class":108,"line":1861},[106,6586,562],{"class":112},[106,6588,6589,6591,6593,6595,6597,6600,6602,6604,6606,6608,6610,6612,6614,6616],{"class":108,"line":3121},[106,6590,503],{"class":224},[106,6592,240],{"class":112},[106,6594,508],{"class":211},[106,6596,246],{"class":112},[106,6598,6599],{"class":379},"5",[106,6601,228],{"class":112},[106,6603,518],{"class":112},[106,6605,521],{"class":480},[106,6607,484],{"class":112},[106,6609,487],{"class":116},[106,6611,240],{"class":112},[106,6613,530],{"class":116},[106,6615,495],{"class":112},[106,6617,218],{"class":112},[106,6619,6620,6622,6624,6626,6628,6630,6633,6635,6637,6639,6641,6643,6645,6647,6649,6651,6653,6655,6657,6659],{"class":108,"line":3141},[106,6621,540],{"class":224},[106,6623,240],{"class":112},[106,6625,545],{"class":211},[106,6627,246],{"class":112},[106,6629,249],{"class":112},[106,6631,6632],{"class":252},"合計 ¥ 128,000",[106,6634,249],{"class":112},[106,6636,228],{"class":112},[106,6638,1677],{"class":224},[106,6640,240],{"class":112},[106,6642,2261],{"class":211},[106,6644,246],{"class":112},[106,6646,249],{"class":112},[106,6648,6294],{"class":252},[106,6650,249],{"class":112},[106,6652,1690],{"class":112},[106,6654,1677],{"class":224},[106,6656,240],{"class":112},[106,6658,4558],{"class":211},[106,6660,1700],{"class":112},[106,6662,6663,6665,6667,6669,6671,6673,6676,6678,6680,6682,6684,6686],{"class":108,"line":3146},[106,6664,540],{"class":224},[106,6666,240],{"class":112},[106,6668,545],{"class":211},[106,6670,246],{"class":112},[106,6672,249],{"class":112},[106,6674,6675],{"class":252},"支払期限: 2026-05-31",[106,6677,249],{"class":112},[106,6679,228],{"class":112},[106,6681,1677],{"class":224},[106,6683,240],{"class":112},[106,6685,4558],{"class":211},[106,6687,1700],{"class":112},[106,6689,6690],{"class":108,"line":3177},[106,6691,562],{"class":112},[106,6693,6694],{"class":108,"line":3197},[106,6695,568],{"class":112},[106,6697,6698],{"class":108,"line":3202},[106,6699,124],{"emptyLinePlaceholder":123},[106,6701,6702,6704,6706,6708,6710,6712,6714,6716],{"class":108,"line":3233},[106,6703,579],{"class":224},[106,6705,228],{"class":112},[106,6707,231],{"class":224},[106,6709,234],{"class":112},[106,6711,455],{"class":224},[106,6713,240],{"class":112},[106,6715,592],{"class":211},[106,6717,463],{"class":112},[106,6719,6720,6722,6724,6726,6728],{"class":108,"line":3253},[106,6721,263],{"class":130},[106,6723,231],{"class":224},[106,6725,268],{"class":112},[106,6727,271],{"class":112},[106,6729,218],{"class":112},[106,6731,6732,6734,6736,6738,6740,6742],{"class":108,"line":3258},[106,6733,279],{"class":224},[106,6735,240],{"class":112},[106,6737,284],{"class":211},[106,6739,246],{"class":112},[106,6741,289],{"class":224},[106,6743,197],{"class":112},[106,6745,6746],{"class":108,"line":3263},[106,6747,297],{"class":112},[106,6749,6750,6752,6754,6756,6758,6760,6762,6764,6766,6769,6771,6773,6775,6777,6779,6781,6783,6785,6787],{"class":108,"line":3268},[106,6751,263],{"class":130},[106,6753,231],{"class":224},[106,6755,234],{"class":112},[106,6757,237],{"class":224},[106,6759,240],{"class":112},[106,6761,643],{"class":211},[106,6763,246],{"class":112},[106,6765,249],{"class":112},[106,6767,6768],{"class":252},"invoice-ja.pdf",[106,6770,249],{"class":112},[106,6772,228],{"class":112},[106,6774,657],{"class":224},[106,6776,228],{"class":112},[106,6778,662],{"class":379},[106,6780,665],{"class":112},[106,6782,231],{"class":224},[106,6784,268],{"class":112},[106,6786,271],{"class":112},[106,6788,218],{"class":112},[106,6790,6791,6793,6795,6797,6799,6801],{"class":108,"line":3274},[106,6792,279],{"class":224},[106,6794,240],{"class":112},[106,6796,284],{"class":211},[106,6798,246],{"class":112},[106,6800,289],{"class":224},[106,6802,197],{"class":112},[106,6804,6805],{"class":108,"line":3299},[106,6806,297],{"class":112},[106,6808,6809],{"class":108,"line":3331},[106,6810,699],{"class":112},[19,6812,6813],{},"눈여겨볼 점:",[983,6815,6816,6832,6841,6856],{},[36,6817,6818,4347,6826,6828,6829,6831],{},[39,6819,6820,6822,6823,6825],{},[44,6821,705],{},"도, UTF-8 플래그도, ",[44,6824,545],{},"의 폰트 경로 인자도 없다",[44,6827,5726],{},"로 family를 등록하고 ",[44,6830,59],{},"는 Unicode를 쓸 뿐. 배선은 전부 내부.",[36,6833,6834,6837,6838,6840],{},[39,6835,6836],{},"굵기는 별도 family이지 플래그가 아니다",". 이것이 TTF의 배포 방식(Noto Sans JP Regular와 Bold는 ",[44,6839,5809],{}," 테이블이 다른 별도 파일)과 부합한다. Gothic/Mincho, Source Han Sans JP Normal/Heavy도 같은 패턴.",[36,6842,6843,4347,6846,960,6849,6852,6853,240],{},[39,6844,6845],{},"레이아웃은 그리드, 커서가 아니다",[44,6847,6848],{},"r.Col(7, ...)",[44,6850,6851],{},"r.Col(5, ...)","는 합 12. 너비는 선언적이며 x 좌표는 계산하지 않는다. 자세한 건 ",[720,6854,6855],{"href":3918},"gpdf의 12칼럼 그리드 동작 방식",[36,6857,6858,6864],{},[39,6859,6860,6863],{},[44,6861,6862],{},"AlignRight()","는 로케일 독립",". 일본어 \"¥ 128,000\"도 \"$1,280.00\"와 같은 방식으로 우측 정렬. 텍스트 내용에 레이아웃 코드가 반응하지 않는다.",[19,6866,6867,6868,6870,6871,6873],{},"생성된 ",[44,6869,6768],{},"를 아무 리더로 연다. \"株式会社 ABC 御中\"을 선택해 에디터에 붙인다. 깨지지 않고 ",[44,6872,6546],{},"이 나온다. 이것이 ToUnicode CMap의 일이며 gpdf가 기본으로 쓴다.",[14,6875,6877],{"id":6876},"폰트-서브셋화-숨어-있는-크기-폭탄","폰트 서브셋화 — 숨어 있는 크기 폭탄",[19,6879,6880,6881,2482,6884,240],{},"튜토리얼이 건너뛰기 쉬운 CJK-in-PDF의 ",[39,6882,6883],{},"가장 중요한 성질",[39,6885,6886],{},"서브셋 임베드",[19,6888,6889],{},"TTF는 글리프 아웃라인과 메타데이터 테이블의 모음이다. Noto Sans JP Regular는 약 17,500 글리프·5.1 MB. 일반 청구서가 사용하는 고유 일본어 문자는 60〜200. 문서마다 풀 임베드는 자릿수 단위의 낭비다.",[19,6891,6892,6893,6896],{},"서브셋 임베드는 ",[39,6894,6895],{},"사용한 글리프만 남긴다",". gpdf는 이를 자동으로 한다. 위 예제로 확인:",[97,6898,6900],{"className":1208,"code":6899,"language":1210,"meta":102,"style":102},"$ ls -l invoice-ja.pdf\n-rw-r--r--  1 dev  staff  34892 Apr 16 10:12 invoice-ja.pdf\n",[44,6901,6902,6916],{"__ignoreMap":102},[106,6903,6904,6907,6910,6913],{"class":108,"line":109},[106,6905,6906],{"class":116},"$",[106,6908,6909],{"class":252}," ls",[106,6911,6912],{"class":252}," -l",[106,6914,6915],{"class":252}," invoice-ja.pdf\n",[106,6917,6918,6921,6924,6927,6930,6933,6936,6938,6941],{"class":108,"line":120},[106,6919,6920],{"class":116},"-rw-r--r--",[106,6922,6923],{"class":379},"  1",[106,6925,6926],{"class":252}," dev",[106,6928,6929],{"class":252},"  staff",[106,6931,6932],{"class":379},"  34892",[106,6934,6935],{"class":252}," Apr",[106,6937,4790],{"class":379},[106,6939,6940],{"class":252}," 10:12",[106,6942,6915],{"class":252},[19,6944,6945,6946,6948,6949,6952,6953,6956],{},"34 KB. 비교: 같은 문서를 ",[44,6947,3950],{}," + ",[44,6950,6951],{},"AddUTF8Font(\"NotoSansJP\", \"NotoSansJP-Regular.ttf\", true)"," (세 번째 인자는 UTF-8 플래그) 로 생성하면 ",[39,6954,6955],{},"4.9 MB",". 입력도 출력 텍스트도 같은데 파일은 143배 크다. 원인은 fpdf 경로가 emit 시점에 서브셋하지 않고 폰트 테이블 전체를 임베드하기 때문.",[19,6958,6959],{},"운영 영향:",[983,6961,6962,6972,6978],{},[36,6963,6964,6967,6968,6971],{},[39,6965,6966],{},"초당 10건 청구서 생성"," (일반적인 SaaS 규모) 에서 서브셋 차이는 ",[39,6969,6970],{},"0.3 MB/s vs 43 MB/s","의 대역폭 차이. 로드밸런서가 여기에 의견이 있다.",[36,6973,6974,6977],{},[39,6975,6976],{},"콜드 스토리지 비용은 PDF 크기에 선형",". 아카이브 500만 건 × 5 MB = 25 TB. × 30 KB = 150 GB. 오브젝트 스토리지 요금은 월간 네 자리 대 두 자리의 차이.",[36,6979,6980,6983],{},[39,6981,6982],{},"이메일 첨부","는 제공사별 10〜25 MB 상한. 5 MB 일본어 청구서 + 다른 첨부 + MIME 인코딩이면 상한에 쉽게 닿는다.",[19,6985,6986,6987,5810,6990,5810,6993,5810,6996,6999],{},"gpdf는 렌더 시점에 서브셋한다. 켜는 플래그는 없다. 어떤 글리프가 출력에 들어갔는지는 로컬 검증 도구로 볼 수 있지만 요점은: ",[44,6988,6989],{},"株",[44,6991,6992],{},"式",[44,6994,6995],{},"会",[44,6997,6998],{},"社","를 썼다면 그 네 글리프가 출력에 실리고 나머지 17,496은 실리지 않는다.",[14,7001,7003],{"id":7002},"혼용-조판-같은-줄의-漢字-かな-ascii","혼용 조판 — 같은 줄의 漢字 + かな + ASCII",[19,7005,7006],{},"일본어 텍스트가 단독으로 나오는 일은 드물다. 실무의 한 줄은 이렇다:",[97,7008,7012],{"className":7009,"code":7011,"language":1087},[7010],"language-text","API の P95 レイテンシは 50 ms 未満です。\n",[44,7013,7011],{"__ignoreMap":102},[19,7015,7016],{},"다섯 스크립트 공존: 로마자 (ASCII Latin), 가타카나, 히라가나, 한자 (Han), 숫자. 소박한 구현은 ASCII 부분에 잘못된 폰트를 얹어, 비례 일본어 옆에 고정폭 \"API\"가 나란히 서서 시각이 깨진다.",[19,7018,7019,7020,7023,7024,7027,7028,7031],{},"gpdf의 기본 동작은 ",[39,7021,7022],{},"등록된 family로 모든 코드포인트를 그린다",". Noto Sans JP가 기본이면 ",[44,7025,7026],{},"API","도 ",[44,7029,7030],{},"50 ms","도 Noto Sans JP의 라틴 글리프로 그려진다 — Noto는 이를 제공한다 (대부분의 일본어 슈퍼패밀리가 그렇다). 결과는 단일 서체처럼 보이고 실제로 단일 서체다.",[19,7033,7034,7035,7038,7039,7041],{},"family를 ",[39,7036,7037],{},"의도적으로 섞고 싶다면"," (ASCII는 condensed sans, 일본어는 Noto Sans JP) 둘 다 등록하고 ",[44,7040,59],{}," 단위로 덮어쓴다:",[97,7043,7045],{"className":99,"code":7044,"language":101,"meta":102,"style":102},"c.Text(\"API の P95 レイテンシは 50 ms 未満です。\",\n    template.FontFamily(\"NotoSansJP\"))\nc.Text(\"API latency (P95) is under 50 ms.\",\n    template.FontFamily(\"InterVariable\"))\n",[44,7046,7047,7067,7086,7105],{"__ignoreMap":102},[106,7048,7049,7051,7053,7055,7057,7059,7062,7064],{"class":108,"line":109},[106,7050,521],{"class":224},[106,7052,240],{"class":112},[106,7054,545],{"class":211},[106,7056,246],{"class":112},[106,7058,249],{"class":112},[106,7060,7061],{"class":252},"API の P95 レイテンシは 50 ms 未満です。",[106,7063,249],{"class":112},[106,7065,7066],{"class":112},",\n",[106,7068,7069,7072,7074,7076,7078,7080,7082,7084],{"class":108,"line":120},[106,7070,7071],{"class":224},"    template",[106,7073,240],{"class":112},[106,7075,2261],{"class":211},[106,7077,246],{"class":112},[106,7079,249],{"class":112},[106,7081,399],{"class":252},[106,7083,249],{"class":112},[106,7085,2284],{"class":112},[106,7087,7088,7090,7092,7094,7096,7098,7101,7103],{"class":108,"line":127},[106,7089,521],{"class":224},[106,7091,240],{"class":112},[106,7093,545],{"class":211},[106,7095,246],{"class":112},[106,7097,249],{"class":112},[106,7099,7100],{"class":252},"API latency (P95) is under 50 ms.",[106,7102,249],{"class":112},[106,7104,7066],{"class":112},[106,7106,7107,7109,7111,7113,7115,7117,7120,7122],{"class":108,"line":137},[106,7108,7071],{"class":224},[106,7110,240],{"class":112},[106,7112,2261],{"class":211},[106,7114,246],{"class":112},[106,7116,249],{"class":112},[106,7118,7119],{"class":252},"InterVariable",[106,7121,249],{"class":112},[106,7123,2284],{"class":112},[19,7125,7126,7127,7129],{},"두 번의 ",[44,7128,59],{},", 두 family, 스크립트 감지 로직은 코드에 없다. 한 줄 안에서 섞고 싶다면 (같은 문장 내 ASCII는 Inter, 일본어는 Noto) gpdf v1.2에서 지원 예정이며, 현재 우회는 스크립트 경계에서 수동 분할 후 가로 열로 배치하는 방식.",[14,7131,5759],{"id":7132},"여전히-어려운-지점",[19,7134,7135],{},"Go의 일본어 PDF 이야기는 95% 풀려 있다. 남은 5%를 정직하게.",[19,7137,7138,7141,7142,7145],{},[39,7139,7140],{},"세로쓰기 (縦書き)는 아직 미지원."," gpdf v1.x는 가로만. 전통적인 일본어 조판 — 우→좌 열, 각 열은 위→아래, 적절한 글리프 회전과 문장부호 재배치 — 는 렌더 미세조정이 아니라 레이아웃 엔진의 깊은 변경이다. 설계안이 달린 오픈 이슈가 있고, 도착할 때 도착한다. 지금 꼭 세로가 필요하다면 (도서·정식 서한), 다른 도구 (Word, InDesign, pandoc + LuaLaTeX 파이프라인) 로 세로 PDF를 만들고 ",[44,7143,7144],{},"gpdf.Merge","로 병합하는 것이 현실해.",[19,7147,7148,42,7151,7154],{},[39,7149,7150],{},"루비 (振り仮名)는 우회만 가능.",[44,7152,7153],{},"c.Ruby(\"漢字\", \"かんじ\")"," 같은 프리미티브는 없다. 어린이 콘텐츠나 언어 교재에서 필요하다면, 위 줄은 작은 가나, 아래 줄은 일반 크기 한자 정렬 — 두 행 구조로. 동작은 하지만 수동이고 가나 경계의 미세 자간 관리가 필요하다.",[19,7156,7157,7160,7161,5810,7164,5810,7167,7170,7171,7173,7174,7178],{},[39,7158,7159],{},"여러 CJK 폰트 간 자동 폴백은 없다."," 사용자 입력이 JP 한자와 CN 전용 자형 (",[44,7162,7163],{},"直",[44,7165,7166],{},"骨",[44,7168,7169],{},"角","은 JP/CN에서 미세하게 다르다) 을 섞는다면, 수동으로 쪼개 두 family를 쓰게 된다. 같은 ",[44,7172,59],{}," 호출 내에서 family를 건너뛰는 자동 폴백은 아직. 실무에서 여기가 직접 박히는 문서는 많지 않지만, 필요하면 ",[720,7175,7177],{"href":7176},"/ko/blog/","JP/CN/KR/EN 혼합 PDF"," (B-070 예정) 참고.",[19,7180,7181,7184,7185,7188,7189,7192],{},[39,7182,7183],{},"엄격 PDF/A-2b + 일본어."," gpdf는 ",[44,7186,7187],{},"gpdf.WithPDFA","로 PDF/A를 낼 수 있지만, 임베디드 글리프 메타데이터, CJK 런의 ",[44,7190,7191],{},"ActualText",", 태그된 구조 트리 등 엄격 요건은 CJK 케이스에서 아직 다듬는 중이다. 장기 보존 (일본의 전자장부보존법, 한국의 전자세금계산서 보관 등) 이라면 커밋 전에 veraPDF (무료) 등 서드파티로 검증하라.",[19,7194,7195],{},"어느 것도 일반 용도 (청구서·보고서·명세서·영수증·증명서) 의 블로커는 아니다. 적어두는 것은 누군가는 그중 하나에 프로덕션에서 부딪힐 것이기 때문이다 — \"로드맵에 있음\"보다 \"여기가 우회로\"가 유용하다.",[14,7197,5534],{"id":5533},[19,7199,7200,7201,7204],{},"자주 언급되지 않는 맥락 하나: ",[39,7202,7203],{},"2026년의 일본어 PDF 생성은 단순한 조판 문제가 아니다",". 두 규제 축이 이를 컴플라이언스 대화로 밀어넣는다.",[19,7206,7207,7210,7211,7214],{},[39,7208,7209],{},"적격청구서 (適格請求書 인보이스) 제도","는 청구서에 특정 필드 (등록 사업자 번호, 적용 세율, 세액 내역) 와 변조 방지 보관을 요구한다. PDF가 사실상의 기본 포맷이고, \"변조 방지\"는 ",[39,7212,7213],{},"PDF 전자서명"," — 엄격 모드에서는 PAdES-B-LT — 에 매핑된다.",[19,7216,7217,7220,7221,240],{},[39,7218,7219],{},"전자장부보존법"," (2024 개정) 은 전자로 수령한 청구서의 보관 의무를 확장했다. 아카이브 PDF는 일정 무결성 요건을 만족해야 한다. 사실상 목표 포맷은 ",[39,7222,7223],{},"PDF/A-2b 또는 PDF/A-3b",[19,7225,7226,7227,7230,7231,7234,7235,7237],{},"둘 다 ",[39,7228,7229],{},"PDF 네이티브 기능"," — 서명, 장기 검증, PDF/A 임베디드 메타데이터 — 에 기댄다. 헤드리스 브라우저 경유 HTML→PDF는 어느 쪽도 깔끔히 만족하지 못한다. Chromium PDF 출력은 PDF/A가 아니고 단일 단계로 전자서명을 임베드하지도 못한다. 네이티브 Go 스택 (gpdf + ",[44,7232,7233],{},"gpdf/signature","의 PAdES + ",[44,7236,7187],{},") 은 이 체인을 프로세스 이탈 없이 한 파이프라인으로 통과한다.",[19,7239,7240,7241,7244],{},"본문에서는 ",[39,7242,7243],{},"예고","에 그친다 — 서명과 PDF/A는 각자 히어로 한 편 분량이다 (백로그 B-067, B-068). 다만 오늘 일본어 PDF 스택을 고르는데 컴플라이언스가 눈에 들어온다면, 네이티브로 서명과 PDF/A를 낼 수 있는 스택을 고르라. \"지금 동작\"에서 \"감사 통과\"로의 이주세는 실재하며 뒤로 미룰수록 비싸다.",[14,7246,5565],{"id":5564},[19,7248,7249,7252,7253,5574,7256,7259,7260,7263,7264,7267,7268,7271],{},[39,7250,7251],{},"서버나 컨테이너에 폰트를 설치해야 하나?","\n아니. gpdf는 TTF 바이트를 읽는다 — 시스템 폰트 캐시를 보지 않는다. ",[44,7254,7255],{},"os.ReadFile(\"NotoSansJP-Regular.ttf\")",[44,7257,7258],{},"//go:embed NotoSansJP-Regular.ttf","는 macOS / Linux / Windows, distroless 컨테이너, AWS Lambda에서 동일하게 동작한다. ",[44,7261,7262],{},"fontconfig"," 불필요, ",[44,7265,7266],{},"fc-cache -fv"," 불필요. gpdf가 ",[44,7269,7270],{},"FROM scratch"," 이미지에서 돌아가는 이유 중 하나.",[19,7273,7274,7277],{},[39,7275,7276],{},"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,7279,7280,7283,7284,7287],{},[39,7281,7282],{},"游ゴシック (Yu Gothic) 이나 히라기노는?","\nOS 번들 상용 폰트. 배포 대상이 라이선스를 가졌다면 사용 가능 (Windows Server는 Yu Gothic 번들, macOS는 히라기노 번들) 이지만, TTF 파일의 확보와 컨테이너 빌드에서의 재배포 조건은 각자 확인 필요. 개방형 배포에는 ",[39,7285,7286],{},"Noto Sans JP 또는 IPAex Gothic"," (양쪽 모두 자유 재배포 가능) 권장.",[19,7289,7290,7297,7298,7301],{},[39,7291,7292,7293,7296],{},"PDF는 나오는데 ",[44,7294,7295],{},"Ctrl+F"," 검색이 안 된다","\n거의 확실히 ToUnicode CMap 이슈. gpdf는 기본으로 쓴다. gpdf에서 이 현상이라면 리더 이름을 붙여 이슈를 열어달라. gofpdf라면 UTF-8 플래그 활성화 ",[39,7299,7300],{},"+"," 리더가 CID 폰트를 지원하는지 확인 (구 버전 macOS Preview.app에 알려진 문제 있음). 대조군으로 Adobe Reader 또는 Chrome 사용.",[19,7303,7304,7307,7308,7311],{},[39,7305,7306],{},"폰트에 없는 JIS X 0213 문자는?","\n그릴 글리프가 없으니 나오지 않는다. 실용 답은 \"JIS X 0213을 커버하는 폰트 사용\". Noto Sans JP는 BMP 전역 + JIS X 0213 1수준을 커버한다. 희귀 이체자는 Hanazono Mincho (화원명조) 같은 말단 폴백. 어떤 폰트에도 없는 코드포인트는 gpdf가 Unicode 치환 문자 (U+FFFD) 를 낸다 — 무음의 두부가 아니라 ",[44,7309,7310],{},"�","가 보이므로 조사의 단서가 된다.",[19,7313,7314,7317,7318,7320],{},[39,7315,7316],{},"CJK는 ASCII보다 느린가?","\n살짝. gpdf의 \"complex CJK invoice\" 벤치는 Apple M1에서 133 µs, ASCII 4×10 테이블이 108 µs. 약 23% 오버헤드로 대부분 글리프 룩업과 서브셋 비용이다. 참고: 같은 CJK 벤치에서 ",[44,7319,3950],{},"는 254 µs, Maroto v2는 10.4 ms. 일본어 렌더가 서비스 병목이 되는 일은 거의 없다.",[14,7322,7324],{"id":7323},"gpdf-사용해-보기","gpdf 사용해 보기",[19,7326,7327],{},"gpdf는 Go용 PDF 생성 라이브러리. MIT, 외부 의존 없음, 네이티브 CJK.",[97,7329,7330],{"className":1208,"code":1209,"language":1210,"meta":102,"style":102},[44,7331,7332],{"__ignoreMap":102},[106,7333,7334,7336,7338],{"class":108,"line":109},[106,7335,101],{"class":116},[106,7337,1219],{"class":252},[106,7339,1222],{"class":252},[19,7341,7342,1230,7346],{},[720,7343,7345],{"href":1227,"rel":7344},[724],"⭐ Star on GitHub",[720,7347,1235],{"href":1233,"rel":7348},[724],[14,7350,7352],{"id":7351},"이어-읽을-글","이어 읽을 글",[983,7354,7355,7360,7366,7371],{},[36,7356,7357,7359],{},[720,7358,3837],{"href":1175}," — 배경 없는 3행 레시피",[36,7361,7362,7365],{},[720,7363,7364],{"href":1185},"gpdf에서 Noto Sans JP 쓰기"," — Regular / Bold / Medium 웨이트 설정",[36,7367,7368,7370],{},[720,7369,6855],{"href":3918}," — 커서 계산을 대체하는 레이아웃 관용구",[36,7372,7373,7376],{},[720,7374,7375],{"href":5702},"go-pdf/fpdf도 아카이브. 2026년의 Go PDF 스택"," — 2026 판도 전체",[1237,7378,2512],{},{"title":102,"searchDepth":120,"depth":120,"links":7380},[7381,7382,7383,7384,7385,7386,7387,7388,7389,7390,7391,7392],{"id":3932,"depth":120,"text":3933},{"id":5739,"depth":120,"text":5740},{"id":5766,"depth":120,"text":5767},{"id":5842,"depth":120,"text":5843},{"id":5983,"depth":120,"text":5984},{"id":6876,"depth":120,"text":6877},{"id":7002,"depth":120,"text":7003},{"id":7132,"depth":120,"text":5759},{"id":5533,"depth":120,"text":5534},{"id":5564,"depth":120,"text":5565},{"id":7323,"depth":120,"text":7324},{"id":7351,"depth":120,"text":7352},"Go에서 일본어 PDF를 생성하는 완전한 순서. CGO 없이, Chromium 없이, 두부 글자 없이. 폰트·서브셋·혼용 조판까지.",{"name":7395,"totalTime":7396,"tools":7397,"steps":7399},"Go에서 TrueType 서브셋 임베딩된 일본어 PDF 생성하기","PT20M",[1260,7398],"NotoSansJP-Regular.ttf 및 NotoSansJP-Bold.ttf (또는 임의의 일본어 지원 TTF 쌍)",[7400,7403,7406,7409,7412,7415],{"name":7401,"text":7402},"gpdf 설치 및 폰트 준비","go get github.com/gpdf-dev/gpdf를 실행. Google Fonts에서 Noto Sans JP Regular와 Bold를 내려받아 main.go 옆에 둔다. CGO도, OS 폰트 설정도 필요 없다.",{"name":7404,"text":7405},"기동 시 TTF 바이트를 읽어들이기","os.ReadFile로 두 TTF 파일을 []byte로 읽는다. 바이너리에 묻고 싶다면 //go:embed도 가능.",{"name":7407,"text":7408},"문서 구성 시 폰트 등록","gpdf.WithFont(\"NotoSansJP\", regular)과 gpdf.WithFont(\"NotoSansJP-Bold\", bold)을 gpdf.NewDocument에 넘긴다. family 이름은 임의 식별자 — 이후 참조할 핸들에 불과하다.",{"name":7410,"text":7411},"일본어 폰트를 기본으로 설정","gpdf.WithDefaultFont(\"NotoSansJP\", 11)을 추가. 이후의 c.Text는 FontFamily 옵션 없이 일본어 폰트를 사용한다.",{"name":7413,"text":7414},"c.Text로 문서 트리를 조립","page.AutoRow 블록 안에서 r.Col(span, fn)을 호출하고 c.Text(\"こんにちは、世界。\")를 쓴다. 굵기와 크기는 메서드가 아니라 template 옵션이다.",{"name":7416,"text":7417},"생성하고 동작 확인","doc.Generate()로 []byte를 받아 os.WriteFile로 저장. PDF를 열어 텍스트를 선택해 에디터에 붙여넣으면 — ToUnicode CMap 덕분에 복사·붙여넣기가 정확히 동작한다.",{},{"title":5712,"description":7393},"ko/blog/007.japanese-pdf-in-go",[2551,1286,1285],"uW2Khc9HlItLbXpkwE4500UyNrvr0zIo6xJx8VjxID0",{"id":7424,"title":7425,"author":7426,"body":7427,"date":9041,"description":9042,"draft":1254,"extension":1255,"howTo":1278,"image":1278,"meta":9043,"navigation":123,"path":3843,"seo":9044,"stem":9045,"tags":9046,"updated":1278,"__hash__":9047},"blogKo/ko/blog/002.go-pdf-library-showdown-2026.md","2026 년 Go PDF 라이브러리 비교 쇼다운",{"name":8,"url":9},{"type":11,"value":7428,"toc":9023},[7429,7431,7437,7457,7460,7467,7471,7474,7505,7508,7511,7514,7690,7697,7699,7713,7716,7742,7748,7752,7846,7855,7859,7866,7875,7881,7887,7894,7897,7907,7984,7987,7989,8043,8046,8052,8056,8094,8098,8101,8141,8144,8148,8151,8873,8882,8886,8889,8927,8929,8935,8945,8951,8957,8963,8967,8970,8982,8991,8995,9020],[14,7430,3933],{"id":3932},[19,7432,7433,7434,7436],{},"5 년 전에 \"Go PDF\"를 검색하면 거의 틀림없이 ",[39,7435,3942],{}," 로 연결됐다. 지금 그 저장소는 아카이브 상태. 커뮤니티 포크 go-pdf/fpdf 도 마찬가지. 검색 결과가 암시하는 것보다 현재 선택지는 훨씬 좁다.",[983,7438,7439,7445,7451],{},[36,7440,7441,7444],{},[39,7442,7443],{},"활발히 유지보수 중",": gpdf (본 팀), signintech/gopdf, johnfercher/maroto v2 — 단 Maroto 는 아카이브된 gofpdf 에 의존한다.",[36,7446,7447,7450],{},[39,7448,7449],{},"아카이브",": jung-kurt/gofpdf (2021), go-pdf/fpdf (2025).",[36,7452,7453,7456],{},[39,7454,7455],{},"상용 / AGPL",": unidoc/unipdf.",[19,7458,7459],{},"이 글에서는 현역 라이브러리 4 개를 4 가지 워크로드로 벤치마크하고, 라이선스와 의존성 그래프, 유지보수 상태를 표로 정리한다. 용례별 선택 가이드도 끝에 둔다. 내년에 다시 돌린다.",[19,7461,7462,7463,7466],{},"편향 공개: 우리는 gpdf 팀. 벤치마크 코드는 공개 (",[44,7464,7465],{},"_benchmark/benchmark_test.go","). 직접 클론해서 다시 측정해 주고, 숫자가 다르면 알려 달라.",[14,7468,7470],{"id":7469},"go-pdf-라이브러리는-세-종류다","\"Go PDF 라이브러리\"는 세 종류다",[19,7472,7473],{},"\"Go PDF 라이브러리\" 라는 말이 사실은 세 가지 서로 다른 도구를 한 선반에 올려놓고 있다:",[33,7475,7476,7486,7497],{},[36,7477,7478,7481,7482,2567,7484,240],{},[39,7479,7480],{},"저수준 PDF 라이터"," — 바이트를 밀고 프리미티브로 그린다. ",[44,7483,3942],{},[44,7485,4180],{},[36,7487,7488,7491,7492,2567,7495,240],{},[39,7489,7490],{},"라이터를 감싼 레이아웃 라이브러리"," — 선언적 행/열. ",[44,7493,7494],{},"johnfercher/maroto v2",[44,7496,337],{},[36,7498,7499,7502,7503,240],{},[39,7500,7501],{},"전체 문서 스위트"," — 파싱, 서명, PDF/A, OCR, 블랙아웃. ",[44,7504,4224],{},[19,7506,7507],{},"\"가장 좋은 Go PDF 라이브러리는 뭔가요?\" 같은 질문이 Reddit/인프런 댓글에서 엇나가는 이유가 여기다. 아래 비교에서는 계속 이 구분을 살려 둔다.",[19,7509,7510],{},"라인업에서 빠진 것: headless Chromium 을 띄우는 쪽 (go-rod, chromedp). 그건 PDF 라이브러리가 아니고 \"인쇄 기능을 가진 브라우저\". CSS 가 무거운 디자인 재현엔 좋지만 콜드스타트/메모리/distroless 배포 어디든 무겁다. \"디자이너가 준 HTML+CSS 를 픽셀 단위로 재현\" 이 목표라면 그 도구들이 답이고, 여기서는 그쪽과 경쟁하지 않는다.",[14,7512,7513],{"id":7513},"스코어보드",[1892,7515,7516,7541],{},[1895,7517,7518],{},[1898,7519,7520,7522,7525,7527,7529,7532,7535,7538],{},[1901,7521,4115],{},[1901,7523,7524],{},"마지막 릴리스",[1901,7526,7449],{},[1901,7528,2398],{},[1901,7530,7531],{},"코어 의존",[1901,7533,7534],{},"CJK",[1901,7536,7537],{},"레이아웃 그리드",[1901,7539,7540],{},"2026 상태",[1908,7542,7543,7572,7592,7615,7641,7667],{},[1898,7544,7545,7550,7553,7556,7558,7563,7565,7568],{},[1913,7546,7547,7549],{},[39,7548,337],{}," (본 팀)",[1913,7551,7552],{},"활발",[1913,7554,7555],{},"—",[1913,7557,4144],{},[1913,7559,7560],{},[39,7561,7562],{},"0",[1913,7564,4276],{},[1913,7566,7567],{},"12 컬럼",[1913,7569,7570],{},[39,7571,4183],{},[1898,7573,7574,7576,7578,7580,7582,7584,7587,7590],{},[1913,7575,4180],{},[1913,7577,7552],{},[1913,7579,7555],{},[1913,7581,4144],{},[1913,7583,7562],{},[1913,7585,7586],{},"TTF 수동",[1913,7588,7589],{},"없음",[1913,7591,4183],{},[1898,7593,7594,7596,7598,7600,7602,7607,7609,7612],{},[1913,7595,7494],{},[1913,7597,7552],{},[1913,7599,7555],{},[1913,7601,4144],{},[1913,7603,7604],{},[39,7605,7606],{},"gofpdf (아카이브)",[1913,7608,4208],{},[1913,7610,7611],{},"행/열",[1913,7613,7614],{},"기반이 죽은 상태",[1898,7616,7617,7619,7622,7627,7629,7631,7635,7637],{},[1913,7618,3942],{},[1913,7620,7621],{},"2021",[1913,7623,7624],{},[39,7625,7626],{},"2021-09-08",[1913,7628,4144],{},[1913,7630,7562],{},[1913,7632,7633],{},[44,7634,705],{},[1913,7636,7589],{},[1913,7638,7639],{},[39,7640,7449],{},[1898,7642,7643,7645,7648,7653,7655,7657,7661,7663],{},[1913,7644,3950],{},[1913,7646,7647],{},"2023",[1913,7649,7650],{},[39,7651,7652],{},"2025",[1913,7654,4144],{},[1913,7656,7562],{},[1913,7658,7659],{},[44,7660,705],{},[1913,7662,7589],{},[1913,7664,7665],{},[39,7666,7449],{},[1898,7668,7669,7671,7673,7675,7680,7683,7686,7688],{},[1913,7670,4224],{},[1913,7672,7552],{},[1913,7674,7555],{},[1913,7676,7677],{},[39,7678,7679],{},"AGPL-3.0 / 상용",[1913,7681,7682],{},"다수",[1913,7684,7685],{},"있음",[1913,7687,7589],{},[1913,7689,4231],{},[19,7691,7692,7693,7696],{},"주목할 점 셋. 절반이 아카이브됐다. Maroto 는 본체는 활발하지만 기반이 죽어 — 오늘 빌드가 통과해도 공급망 리스크가 있다. AGPL 을 수용 못 하는 조직에서 unidoc 선택은 기술 문제가 아니라 ",[39,7694,7695],{},"상용 라이선스 조달 문제","다.",[14,7698,5359],{"id":5359},[19,7700,7701,7702,7708,7709,7712],{},"코드: gpdf 저장소의 ",[720,7703,7706],{"href":7704,"rel":7705},"https://github.com/gpdf-dev/gpdf/tree/main/_benchmark",[724],[44,7707,7465],{},". 환경은 Apple M1 (Max, 32 GB, macOS 14.5), Go 1.25, CGO 없음. 각 케이스는 최소 5 초 실시간으로 돌렸고, ",[44,7710,7711],{},"-benchmem"," 을 켜서 ns/op 와 할당 횟수를 기록했다.",[19,7714,7715],{},"4 가지 케이스를 고른 이유는 실제 프로덕션에서 생성되는 모양에 가깝기 때문:",[33,7717,7718,7724,7730,7736],{},[36,7719,7720,7723],{},[39,7721,7722],{},"단일 페이지 hello world",". 1 페이지 / 1 줄 / 1 폰트. 문서당 고정 오버헤드의 하한.",[36,7725,7726,7729],{},[39,7727,7728],{},"4×10 인보이스 테이블",". 헤더 1 행 + 바디 10 행 + 컬럼 정렬 + 얇은 테두리. \"인보이스 생성\" 형.",[36,7731,7732,7735],{},[39,7733,7734],{},"100 페이지 페이지 구분 리포트",". 반복 헤더, 푸터, 페이지 번호, 본문. 페이지 구분 비용 측정.",[36,7737,7738,7741],{},[39,7739,7740],{},"복잡한 CJK 인보이스",". 일본어 (히라가나·가타카나·한자) 혼합, 4×15 명세 표, 헤더, 페이지 번호 포함 푸터, NotoSansJP TrueType 서브셋 임베드.",[19,7743,7744,7745,7747],{},"포함 안 함: ",[44,7746,4224],{},". 바이너리가 라이선스로 게이트돼 있어서, 그들의 공개 벤치 방법론을 공개 벤치 저장소에서 재현하는 건 오해 소지를 만든다. unidoc 를 검토 중이라면 자체 공식 벤치를 돌려 보라.",[7749,7750,7751],"h3",{"id":7751},"결과",[1892,7753,7754,7771],{},[1895,7755,7756],{},[1898,7757,7758,7761,7763,7765,7767,7769],{},[1901,7759,7760],{},"워크로드",[1901,7762,337],{},[1901,7764,4180],{},[1901,7766,5370],{},[1901,7768,5364],{},[1901,7770,3950],{},[1908,7772,7773,7790,7808,7828],{},[1898,7774,7775,7777,7781,7783,7785,7787],{},[1913,7776,7722],{},[1913,7778,7779],{},[39,7780,5382],{},[1913,7782,5388],{},[1913,7784,5391],{},[1913,7786,5385],{},[1913,7788,7789],{},"135 µs",[1898,7791,7792,7794,7798,7800,7803,7805],{},[1913,7793,7728],{},[1913,7795,7796],{},[39,7797,5401],{},[1913,7799,5407],{},[1913,7801,7802],{},"8,600 µs",[1913,7804,5404],{},[1913,7806,7807],{},"243 µs",[1898,7809,7810,7813,7817,7819,7822,7825],{},[1913,7811,7812],{},"100 페이지 리포트",[1913,7814,7815],{},[39,7816,5420],{},[1913,7818,7802],{},[1913,7820,7821],{},"19,800 µs",[1913,7823,7824],{},"11,700 µs",[1913,7826,7827],{},"11,900 µs",[1898,7829,7830,7832,7836,7838,7841,7843],{},[1913,7831,7740],{},[1913,7833,7834],{},[39,7835,5438],{},[1913,7837,5444],{},[1913,7839,7840],{},"10,400 µs",[1913,7842,5441],{},[1913,7844,7845],{},"n/a",[19,7847,7848,7849,7851,7852,7854],{},"go-pdf/fpdf 의 CJK 칸이 ",[44,7850,7845],{}," 인 이유: 테스트한 버전에서 ",[44,7853,705],{}," 경로가 NotoSansJP 의 cmap format 12 테이블을 읽을 때 panic. 패치로 고칠 수 있지만 포크 자체가 아카이브 — 아무도 수정을 릴리스하지 않는다.",[7749,7856,7858],{"id":7857},"숫자-읽는-법","숫자 읽는 법",[19,7860,7861,7862,7865],{},"순서는 워크로드를 넘나들어도 안정적이다. 모든 케이스에서 gpdf 는 2 위보다 ",[39,7863,7864],{},"10–30 배 빠르다",". 기묘한 기술이 아니라, 3 가지 설계가 누적된 결과:",[19,7867,7868,7871,7872,240],{},[39,7869,7870],{},"단일 패스 레이아웃",". gpdf 는 중간 AST 를 만들었다가 직렬화하지 않는다. 빌더가 해석된 시점에 PDF content stream 에 직접 쓰기 때문에 다른 라이브러리 대비 할당이 대략 절반으로 준다. 100 페이지 벤치에서 683 µs 대 19,800 µs 차이가 나는 건 튜닝 차이가 아니라 ",[39,7873,7874],{},"아키텍처가 다르다",[19,7876,7877,7880],{},[39,7878,7879],{},"핫패스에 리플렉션 없음",". 레이아웃 엔진이 닿는 타입은 전부 구체 타입. 개별로는 미세 최적화이지만 100 페이지 리포트 프로파일에선 인터페이스 디스패치가 보이기 시작. 우리는 피했다.",[19,7882,7883,7886],{},[39,7884,7885],{},"cmap 을 캐시하는 TrueType 서브세터",". gofpdf 는 글리프 조회마다 cmap 테이블을 다시 읽는다; gpdf 는 한 번 해석 후 캐시. Latin 전용이면 거의 차이 없지만 CJK 는 한 문단에 한자 + 가나 + 구두점으로 150 글리프를 건드릴 수 있고, \"동기 생성 가능\" 과 \"큐에 넣어야 함\" 의 경계가 된다.",[19,7888,7889,7890,7893],{},"벤치 표에는 안 나오는 주의 하나: ",[39,7891,7892],{},"대부분의 PDF 워크로드에서 절대 속도는 생각만큼 중요하지 않다",". 의미 있는 경계는 \"요청 경로에서 동기 생성해도 괜찮은가\" 다. hello world 한 페이지라면 모든 라이브러리가 통과. 반복 chrome 이 있는 100 페이지 리포트에선 gpdf 만 통과. 최대 문서가 영수증 한 장이라면 현역 4 개 모두 괜찮고, API 와 라이선스로 고르면 된다.",[14,7895,7896],{"id":7896},"의존성",[19,7898,7899,7900,7903,7904,7906],{},"각각 방금 ",[44,7901,7902],{},"go get"," 한 후 ",[44,7905,4367],{}," 결과:",[1892,7908,7909,7921],{},[1895,7910,7911],{},[1898,7912,7913,7915,7918],{},[1901,7914,4115],{},[1901,7916,7917],{},"외부 모듈",[1901,7919,7920],{},"전이적 아카이브 의존",[1908,7922,7923,7936,7944,7954,7962,7974],{},[1898,7924,7925,7930,7934],{},[1913,7926,7927,7929],{},[39,7928,337],{}," (코어)",[1913,7931,7932],{},[39,7933,7562],{},[1913,7935,7555],{},[1898,7937,7938,7940,7942],{},[1913,7939,4180],{},[1913,7941,7562],{},[1913,7943,7555],{},[1898,7945,7946,7948,7951],{},[1913,7947,5364],{},[1913,7949,7950],{},"0 (자신이 아카이브)",[1913,7952,7953],{},"자신",[1898,7955,7956,7958,7960],{},[1913,7957,3950],{},[1913,7959,7950],{},[1913,7961,7953],{},[1898,7963,7964,7966,7971],{},[1913,7965,7494],{},[1913,7967,7968],{},[39,7969,7970],{},"gofpdf (2021 아카이브)",[1913,7972,7973],{},"있음 — gofpdf",[1898,7975,7976,7978,7981],{},[1913,7977,4224],{},[1913,7979,7980],{},"다수 (이미지, 암호, 압축)",[1913,7982,7983],{},"아카이브 없음",[19,7985,7986],{},"\"프로덕션 의존에 아카이브 레포 금지\" 린트 룰이 있는 팀이라면 오늘의 Maroto v2 는 이 규칙에 걸린다. Maroto 메인테이너들은 1 년 이상 gofpdf 제거 작업을 해왔고, 완료되면 이 행은 바뀐다. 판단 전에 Maroto 레포 현황을 확인하는 편이 좋다.",[14,7988,2398],{"id":2398},[1892,7990,7991,7999],{},[1895,7992,7993],{},[1898,7994,7995,7997],{},[1901,7996,4115],{},[1901,7998,2398],{},[1908,8000,8001,8008,8014,8020,8026,8032],{},[1898,8002,8003,8006],{},[1913,8004,8005],{},"gpdf (코어)",[1913,8007,4144],{},[1898,8009,8010,8012],{},[1913,8011,4180],{},[1913,8013,4144],{},[1898,8015,8016,8018],{},[1913,8017,7494],{},[1913,8019,4144],{},[1898,8021,8022,8024],{},[1913,8023,5364],{},[1913,8025,4144],{},[1898,8027,8028,8030],{},[1913,8029,3950],{},[1913,8031,4144],{},[1898,8033,8034,8038],{},[1913,8035,8036],{},[39,8037,4224],{},[1913,8039,8040],{},[39,8041,8042],{},"AGPL-3.0 또는 상용 라이선스",[19,8044,8045],{},"unidoc 의 AGPL 은 꽤 강한 편. 사용자가 네트워크로 접속하는 서버에서 사용한다면 서버 쪽 코드도 AGPL 로 공개해야 한다 — 대부분의 클로즈드 SaaS 에는 성립하지 않는다. 결국 상용 라이선스가 유일한 현실적 선택이 되고, 가격은 공개되지 않았다. 영업 상담이 전제.",[19,8047,8048,8049,8051],{},"GitHub 스타 수 비교에서 가장 자주 놓치는 지점이 여기. unidoc 는 기능도 가장 많고 스타도 가장 많지만, 상용 용례 대부분에 문을 닫는 라이선스를 가지고 있다 (구매 전제). unidoc 를 깎아내리는 게 아니다 — 비즈니스 모델은 정당하고 제품도 훌륭하다. 다만 ",[44,8050,7902],{}," 전에 알고 있어야 한다.",[14,8053,8055],{"id":8054},"유지보수-상태","유지보수 상태",[983,8057,8058,8063,8068,8073,8081,8089],{},[36,8059,8060,8062],{},[39,8061,337],{}," — 주 메인테이너는 본 팀 (gpdf-dev). 2–4 주마다 릴리스, 로드맵은 레포 내, CI 는 Go 1.22–1.26 에서 돌고, 메인 레포 이슈는 며칠 안에 응답. 진지하게 투자하고 있다.",[36,8064,8065,8067],{},[39,8066,4180],{}," — 활발하지만 커밋 템포는 낮다. 이슈는 읽히고 PR 은 수 주 내 병합. 주 용도는 여전히 저수준 생성.",[36,8069,8070,8072],{},[39,8071,5370],{}," — 활발. 2023 년 v2 재작성 이후 안정. gofpdf 의존은 알려져 있고 교체 작업 중. 결정 전에 레포 확인.",[36,8074,8075,8077,8078,8080],{},[39,8076,5364],{}," — 2021-09-08 아카이브. 레포 배너: ",[784,8079,3971],{}," 보안 패치도 버그 수정도 없음.",[36,8082,8083,8085,8086,240],{},[39,8084,3950],{}," — 2025 년 아카이브. README 가 다른 라이브러리 사용을 권장. 별도 마이그레이션 가이드를 썼다: ",[720,8087,8088],{"href":4005},"gofpdf 에서 gpdf 로 마이그레이션",[36,8090,8091,8093],{},[39,8092,4224],{}," — 활발, 상용 팀, 리소스 풍부, 엔터프라이즈 지원 제공.",[14,8095,8097],{"id":8096},"고르는-법","고르는 법",[19,8099,8100],{},"기능 매트릭스 대신 의사결정 트리로 쓴다. \"기능 많음 = 정답\" 은 대개 맞는 질문이 아니다.",[983,8102,8103,8109,8115,8121,8127,8135],{},[36,8104,8105,8108],{},[39,8106,8107],{},"\"Go 코드베이스에서 인보이스·리포트·문서를 생성한다. MIT 선호, 의존 0 선호, CJK 가 섞이기도 한다.\""," → gpdf.",[36,8110,8111,8114],{},[39,8112,8113],{},"\"맞춤 지오메트리까지 저수준으로 생성한다. 작고 안정적이며 수동 컨트롤이 강한 라이브러리가 좋다.\""," → signintech/gopdf.",[36,8116,8117,8120],{},[39,8118,8119],{},"\"이미 Maroto 스타일 레이아웃 코드가 돌고 있다.\""," → gofpdf 제거 완료까지 Maroto v2 유지 후 재평가. API 자체가 문제는 아니다.",[36,8122,8123,8126],{},[39,8124,8125],{},"\"PDF/A, OCR, 블랙아웃, 전자서명이 필요하고 회사가 상용 라이선스를 낼 수 있다.\""," → unidoc/unipdf. 라이선스 협의부터.",[36,8128,8129,8132,8133,240],{},[39,8130,8131],{},"\"여전히 gofpdf, 잘 돌고 있다.\""," → 오늘은 괜찮다. 다음 관련 의존의 CVE 가 터지기 전에 마이그레이션 계획. ",[720,8134,5509],{"href":4005},[36,8136,8137,8140],{},[39,8138,8139],{},"\"픽셀 단위 HTML/CSS→PDF 가 필요하다.\""," → 위 어느 것도 아님. go-rod / chromedp + headless Chromium, 콜드스타트 감수.",[19,8142,8143],{},"우리는 gpdf 팀이라 1 번과 5 번 다수 케이스에서 gpdf 가 합리적 기본값이라 생각한다 — 당연한 편향. 벤치 코드를 읽고 로컬에서 돌리고, 이 표를 곧이곧대로 믿지 말기를.",[14,8145,8147],{"id":8146},"_30-줄짜리-gpdf-예제","30 줄짜리 gpdf 예제",[19,8149,8150],{},"\"가장 빠름\" 과 \"의존 그래프 최소\" 는 코드가 읽을 만할 때 의미가 있다. 완전히 실행되는 인보이스 한 페이지, 가짜 코드/생략 import 없음:",[97,8152,8154],{"className":99,"code":8153,"language":101,"meta":102,"style":102},"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",[44,8155,8156,8162,8166,8172,8180,8188,8192,8200,8208,8217,8225,8229,8233,8243,8257,8275,8305,8309,8313,8327,8351,8381,8420,8443,8454,8503,8513,8554,8594,8635,8640,8671,8682,8694,8715,8741,8746,8751,8755,8759,8763,8781,8793,8807,8811,8851,8865,8869],{"__ignoreMap":102},[106,8157,8158,8160],{"class":108,"line":109},[106,8159,113],{"class":112},[106,8161,117],{"class":116},[106,8163,8164],{"class":108,"line":120},[106,8165,124],{"emptyLinePlaceholder":123},[106,8167,8168,8170],{"class":108,"line":127},[106,8169,131],{"class":130},[106,8171,134],{"class":112},[106,8173,8174,8176,8178],{"class":108,"line":137},[106,8175,140],{"class":112},[106,8177,143],{"class":116},[106,8179,146],{"class":112},[106,8181,8182,8184,8186],{"class":108,"line":149},[106,8183,140],{"class":112},[106,8185,154],{"class":116},[106,8187,146],{"class":112},[106,8189,8190],{"class":108,"line":159},[106,8191,124],{"emptyLinePlaceholder":123},[106,8193,8194,8196,8198],{"class":108,"line":164},[106,8195,140],{"class":112},[106,8197,169],{"class":116},[106,8199,146],{"class":112},[106,8201,8202,8204,8206],{"class":108,"line":174},[106,8203,140],{"class":112},[106,8205,179],{"class":116},[106,8207,146],{"class":112},[106,8209,8210,8212,8215],{"class":108,"line":184},[106,8211,140],{"class":112},[106,8213,8214],{"class":116},"github.com/gpdf-dev/gpdf/pdf",[106,8216,146],{"class":112},[106,8218,8219,8221,8223],{"class":108,"line":194},[106,8220,140],{"class":112},[106,8222,189],{"class":116},[106,8224,146],{"class":112},[106,8226,8227],{"class":108,"line":200},[106,8228,197],{"class":112},[106,8230,8231],{"class":108,"line":205},[106,8232,124],{"emptyLinePlaceholder":123},[106,8234,8235,8237,8239,8241],{"class":108,"line":221},[106,8236,208],{"class":112},[106,8238,212],{"class":211},[106,8240,215],{"class":112},[106,8242,218],{"class":112},[106,8244,8245,8247,8249,8251,8253,8255],{"class":108,"line":260},[106,8246,308],{"class":224},[106,8248,234],{"class":112},[106,8250,313],{"class":224},[106,8252,240],{"class":112},[106,8254,318],{"class":211},[106,8256,321],{"class":112},[106,8258,8259,8261,8263,8265,8267,8269,8271,8273],{"class":108,"line":276},[106,8260,327],{"class":224},[106,8262,240],{"class":112},[106,8264,332],{"class":211},[106,8266,246],{"class":112},[106,8268,360],{"class":224},[106,8270,240],{"class":112},[106,8272,342],{"class":224},[106,8274,345],{"class":112},[106,8276,8277,8279,8281,8283,8285,8287,8289,8291,8293,8295,8297,8299,8301,8303],{"class":108,"line":294},[106,8278,327],{"class":224},[106,8280,240],{"class":112},[106,8282,355],{"class":211},[106,8284,246],{"class":112},[106,8286,360],{"class":224},[106,8288,240],{"class":112},[106,8290,365],{"class":211},[106,8292,246],{"class":112},[106,8294,360],{"class":224},[106,8296,240],{"class":112},[106,8298,374],{"class":211},[106,8300,246],{"class":112},[106,8302,380],{"class":379},[106,8304,383],{"class":112},[106,8306,8307],{"class":108,"line":300},[106,8308,439],{"class":112},[106,8310,8311],{"class":108,"line":305},[106,8312,124],{"emptyLinePlaceholder":123},[106,8314,8315,8317,8319,8321,8323,8325],{"class":108,"line":324},[106,8316,450],{"class":224},[106,8318,234],{"class":112},[106,8320,455],{"class":224},[106,8322,240],{"class":112},[106,8324,460],{"class":211},[106,8326,463],{"class":112},[106,8328,8329,8331,8333,8335,8337,8339,8341,8343,8345,8347,8349],{"class":108,"line":348},[106,8330,469],{"class":224},[106,8332,240],{"class":112},[106,8334,474],{"class":211},[106,8336,477],{"class":112},[106,8338,481],{"class":480},[106,8340,484],{"class":112},[106,8342,487],{"class":116},[106,8344,240],{"class":112},[106,8346,492],{"class":116},[106,8348,495],{"class":112},[106,8350,218],{"class":112},[106,8352,8353,8355,8357,8359,8361,8363,8365,8367,8369,8371,8373,8375,8377,8379],{"class":108,"line":386},[106,8354,503],{"class":224},[106,8356,240],{"class":112},[106,8358,508],{"class":211},[106,8360,246],{"class":112},[106,8362,513],{"class":379},[106,8364,228],{"class":112},[106,8366,518],{"class":112},[106,8368,521],{"class":480},[106,8370,484],{"class":112},[106,8372,487],{"class":116},[106,8374,240],{"class":112},[106,8376,530],{"class":116},[106,8378,495],{"class":112},[106,8380,218],{"class":112},[106,8382,8383,8385,8387,8389,8391,8393,8396,8398,8400,8402,8404,8406,8408,8410,8412,8414,8416,8418],{"class":108,"line":411},[106,8384,540],{"class":224},[106,8386,240],{"class":112},[106,8388,545],{"class":211},[106,8390,246],{"class":112},[106,8392,249],{"class":112},[106,8394,8395],{"class":252},"청구서 #2026-0042",[106,8397,249],{"class":112},[106,8399,228],{"class":112},[106,8401,1677],{"class":224},[106,8403,240],{"class":112},[106,8405,1697],{"class":211},[106,8407,4918],{"class":112},[106,8409,1677],{"class":224},[106,8411,240],{"class":112},[106,8413,1682],{"class":211},[106,8415,246],{"class":112},[106,8417,380],{"class":379},[106,8419,2284],{"class":112},[106,8421,8422,8424,8426,8429,8431,8433,8435,8437,8439,8441],{"class":108,"line":436},[106,8423,540],{"class":224},[106,8425,240],{"class":112},[106,8427,8428],{"class":211},"Spacer",[106,8430,246],{"class":112},[106,8432,360],{"class":224},[106,8434,240],{"class":112},[106,8436,374],{"class":211},[106,8438,246],{"class":112},[106,8440,2918],{"class":379},[106,8442,2284],{"class":112},[106,8444,8445,8447,8449,8452],{"class":108,"line":442},[106,8446,540],{"class":224},[106,8448,240],{"class":112},[106,8450,8451],{"class":211},"Table",[106,8453,321],{"class":112},[106,8455,8456,8459,8463,8466,8468,8471,8473,8475,8477,8480,8482,8484,8486,8489,8491,8493,8495,8498,8500],{"class":108,"line":447},[106,8457,8458],{"class":112},"                []",[106,8460,8462],{"class":8461},"spNyl","string",[106,8464,8465],{"class":112},"{",[106,8467,249],{"class":112},[106,8469,8470],{"class":252},"항목",[106,8472,249],{"class":112},[106,8474,228],{"class":112},[106,8476,4727],{"class":112},[106,8478,8479],{"class":252},"수량",[106,8481,249],{"class":112},[106,8483,228],{"class":112},[106,8485,4727],{"class":112},[106,8487,8488],{"class":252},"단가",[106,8490,249],{"class":112},[106,8492,228],{"class":112},[106,8494,4727],{"class":112},[106,8496,8497],{"class":252},"금액",[106,8499,249],{"class":112},[106,8501,8502],{"class":112},"},\n",[106,8504,8505,8508,8510],{"class":108,"line":466},[106,8506,8507],{"class":112},"                [][]",[106,8509,8462],{"class":8461},[106,8511,8512],{"class":112},"{\n",[106,8514,8515,8518,8520,8523,8525,8527,8529,8532,8534,8536,8538,8541,8543,8545,8547,8550,8552],{"class":108,"line":500},[106,8516,8517],{"class":112},"                    {",[106,8519,249],{"class":112},[106,8521,8522],{"class":252},"프론트엔드 개발",[106,8524,249],{"class":112},[106,8526,228],{"class":112},[106,8528,4727],{"class":112},[106,8530,8531],{"class":252},"40 시간",[106,8533,249],{"class":112},[106,8535,228],{"class":112},[106,8537,4727],{"class":112},[106,8539,8540],{"class":252},"₩200,000",[106,8542,249],{"class":112},[106,8544,228],{"class":112},[106,8546,4727],{"class":112},[106,8548,8549],{"class":252},"₩8,000,000",[106,8551,249],{"class":112},[106,8553,8502],{"class":112},[106,8555,8556,8558,8560,8563,8565,8567,8570,8573,8575,8577,8579,8581,8583,8585,8587,8590,8592],{"class":108,"line":537},[106,8557,8517],{"class":112},[106,8559,249],{"class":112},[106,8561,8562],{"class":252},"백엔드 개발",[106,8564,249],{"class":112},[106,8566,228],{"class":112},[106,8568,8569],{"class":112},"     \"",[106,8571,8572],{"class":252},"60 시간",[106,8574,249],{"class":112},[106,8576,228],{"class":112},[106,8578,4727],{"class":112},[106,8580,8540],{"class":252},[106,8582,249],{"class":112},[106,8584,228],{"class":112},[106,8586,4727],{"class":112},[106,8588,8589],{"class":252},"₩12,000,000",[106,8591,249],{"class":112},[106,8593,8502],{"class":112},[106,8595,8596,8598,8600,8603,8605,8607,8610,8613,8615,8617,8619,8622,8624,8626,8628,8631,8633],{"class":108,"line":559},[106,8597,8517],{"class":112},[106,8599,249],{"class":112},[106,8601,8602],{"class":252},"UI 디자인",[106,8604,249],{"class":112},[106,8606,228],{"class":112},[106,8608,8609],{"class":112},"       \"",[106,8611,8612],{"class":252},"20 시간",[106,8614,249],{"class":112},[106,8616,228],{"class":112},[106,8618,4727],{"class":112},[106,8620,8621],{"class":252},"₩160,000",[106,8623,249],{"class":112},[106,8625,228],{"class":112},[106,8627,4727],{"class":112},[106,8629,8630],{"class":252},"₩3,200,000",[106,8632,249],{"class":112},[106,8634,8502],{"class":112},[106,8636,8637],{"class":108,"line":565},[106,8638,8639],{"class":112},"                },\n",[106,8641,8642,8645,8647,8650,8652,8655,8657,8660,8662,8664,8666,8669],{"class":108,"line":571},[106,8643,8644],{"class":224},"                template",[106,8646,240],{"class":112},[106,8648,8649],{"class":211},"ColumnWidths",[106,8651,246],{"class":112},[106,8653,8654],{"class":379},"50",[106,8656,228],{"class":112},[106,8658,8659],{"class":379}," 15",[106,8661,228],{"class":112},[106,8663,8659],{"class":379},[106,8665,228],{"class":112},[106,8667,8668],{"class":379}," 20",[106,8670,345],{"class":112},[106,8672,8673,8675,8677,8680],{"class":108,"line":576},[106,8674,8644],{"class":224},[106,8676,240],{"class":112},[106,8678,8679],{"class":211},"TableHeaderStyle",[106,8681,321],{"class":112},[106,8683,8684,8687,8689,8691],{"class":108,"line":597},[106,8685,8686],{"class":224},"                    template",[106,8688,240],{"class":112},[106,8690,1697],{"class":211},[106,8692,8693],{"class":112},"(),\n",[106,8695,8696,8698,8700,8703,8705,8708,8710,8713],{"class":108,"line":610},[106,8697,8686],{"class":224},[106,8699,240],{"class":112},[106,8701,8702],{"class":211},"TextColor",[106,8704,246],{"class":112},[106,8706,8707],{"class":224},"pdf",[106,8709,240],{"class":112},[106,8711,8712],{"class":224},"White",[106,8714,345],{"class":112},[106,8716,8717,8719,8721,8724,8726,8728,8730,8733,8735,8738],{"class":108,"line":625},[106,8718,8686],{"class":224},[106,8720,240],{"class":112},[106,8722,8723],{"class":211},"BgColor",[106,8725,246],{"class":112},[106,8727,8707],{"class":224},[106,8729,240],{"class":112},[106,8731,8732],{"class":211},"RGBHex",[106,8734,246],{"class":112},[106,8736,8737],{"class":379},"0x1A237E",[106,8739,8740],{"class":112},")),\n",[106,8742,8743],{"class":108,"line":630},[106,8744,8745],{"class":112},"                ),\n",[106,8747,8748],{"class":108,"line":676},[106,8749,8750],{"class":112},"            )\n",[106,8752,8753],{"class":108,"line":691},[106,8754,562],{"class":112},[106,8756,8757],{"class":108,"line":696},[106,8758,568],{"class":112},[106,8760,8761],{"class":108,"line":1856},[106,8762,124],{"emptyLinePlaceholder":123},[106,8764,8765,8767,8769,8771,8773,8775,8777,8779],{"class":108,"line":1861},[106,8766,579],{"class":224},[106,8768,228],{"class":112},[106,8770,231],{"class":224},[106,8772,234],{"class":112},[106,8774,455],{"class":224},[106,8776,240],{"class":112},[106,8778,592],{"class":211},[106,8780,463],{"class":112},[106,8782,8783,8785,8787,8789,8791],{"class":108,"line":3121},[106,8784,263],{"class":130},[106,8786,231],{"class":224},[106,8788,268],{"class":112},[106,8790,271],{"class":112},[106,8792,218],{"class":112},[106,8794,8795,8797,8799,8801,8803,8805],{"class":108,"line":3141},[106,8796,279],{"class":224},[106,8798,240],{"class":112},[106,8800,284],{"class":211},[106,8802,246],{"class":112},[106,8804,289],{"class":224},[106,8806,197],{"class":112},[106,8808,8809],{"class":108,"line":3146},[106,8810,297],{"class":112},[106,8812,8813,8815,8817,8819,8821,8823,8825,8827,8829,8831,8833,8835,8837,8839,8841,8843,8845,8847,8849],{"class":108,"line":3177},[106,8814,263],{"class":130},[106,8816,231],{"class":224},[106,8818,234],{"class":112},[106,8820,237],{"class":224},[106,8822,240],{"class":112},[106,8824,643],{"class":211},[106,8826,246],{"class":112},[106,8828,249],{"class":112},[106,8830,1819],{"class":252},[106,8832,249],{"class":112},[106,8834,228],{"class":112},[106,8836,657],{"class":224},[106,8838,228],{"class":112},[106,8840,662],{"class":379},[106,8842,665],{"class":112},[106,8844,231],{"class":224},[106,8846,268],{"class":112},[106,8848,271],{"class":112},[106,8850,218],{"class":112},[106,8852,8853,8855,8857,8859,8861,8863],{"class":108,"line":3197},[106,8854,279],{"class":224},[106,8856,240],{"class":112},[106,8858,284],{"class":211},[106,8860,246],{"class":112},[106,8862,289],{"class":224},[106,8864,197],{"class":112},[106,8866,8867],{"class":108,"line":3202},[106,8868,297],{"class":112},[106,8870,8871],{"class":108,"line":3233},[106,8872,699],{"class":112},[19,8874,8875,8877,8878,8881],{},[44,8876,4298],{}," 0 개. 수동 컬럼 폭 계산 0 개. 문서 옵션에 ",[44,8879,8880],{},"gpdf.WithFont(\"NotoSansKR\", ttfBytes)"," 를 더하면 위 한국어가 그대로 렌더링된다. 두부 현상 없음.",[14,8883,8885],{"id":8884},"싣지-않은-것","싣지 않은 것",[19,8887,8888],{},"모든 비교 글에는 \"X 때문에 제외\" 섹션이 있다. 우리 쪽:",[983,8890,8891,8897,8909,8918],{},[36,8892,8893,8896],{},[39,8894,8895],{},"사내 gofpdf 포크",". 프로덕션에서 돌고 있는 비공개 포크가 있다. 볼 수 없는 코드는 벤치할 수 없다.",[36,8898,8899,8904,8905,8908],{},[39,8900,8901],{},[44,8902,8903],{},"pdfcpu",". \"Go PDF 라이브러리\" 목록에 늘 보이지만 주 용도는 ",[39,8906,8907],{},"PDF 프로세서"," (병합·분할·암호화·스탬프) 로 생성이 아니다. 이 글 범위 밖; 프로세싱 지향 별도 글을 계획.",[36,8910,8911,8917],{},[39,8912,8913,8916],{},[44,8914,8915],{},"gotenberg"," 이나 headless 브라우저 서비스 래퍼",". 라이브러리가 아니고 공평한 비교도 아니다.",[36,8919,8920,8926],{},[39,8921,8922,8923,8925],{},"자체 ",[44,8924,337],{}," 벤치",". 비교의 초점은 코어 수치.",[14,8928,5565],{"id":5564},[19,8930,8931,8934],{},[39,8932,8933],{},"gpdf 는 왜 gofpdf 보다 10× 빠른가? 어떤 트릭?","\n단일 트릭은 없다. 3 가지 설계가 누적: 단일 패스 레이아웃 (빌더-라이터 간 AST 없음), 핫패스의 구체 타입, cmap 을 캐시하는 TrueType 서브세터. 하나당 2× 가량 기여. 누적해서 자릿수가 바뀐다.",[19,8936,8937,8940,8941,8944],{},[39,8938,8939],{},"이 벤치 직접 재현 가능한가?","\n가능. ",[44,8942,8943],{},"git clone https://github.com/gpdf-dev/gpdf && cd gpdf/_benchmark && go test -bench=. -benchmem",". 같은 CPU 아키, 같은 Go 버전에서 숫자가 안 맞으면 이슈를 열어 달라. 벤치 드리프트는 발생하고 알고 싶다.",[19,8946,8947,8950],{},[39,8948,8949],{},"gofpdf 는 돌아오나?","\n현실적으로는 아니다. 마지막 커밋은 2021 년. 이슈 트래커는 닫혔다. 누가 다시 열어도 커서 + 싱글바이트 폰트 + 그리드 없음 의 아키텍처는 2026 년 출발점으로 맞지 않는다. 유물 취급 후 마이그레이션이 실용적.",[19,8952,8953,8956],{},[39,8954,8955],{},"Java iText / Python ReportLab / Node pdfkit 는?","\n언어 교차 벤치는 별도 글. 짧게: Go 는 처리량과 콜드스타트에서 대체로 이기고, 기능 폭 (특히 HTML→PDF 충실도) 에서 진다. 이미 Go 팀이면 gpdf 가 더 빠르고 작음. Python / Node 팀이 Go 로 옮길 땐 마이그레이션 비용이 커서 대용량일 때만 투자 회수.",[19,8958,8959,8962],{},[39,8960,8961],{},"경쟁사가 개선되면 이 비교는 공평성을 유지하나?","\n유지한다. 매년 돌린다. signintech/gopdf 가 테이블 API 를 내서 시간을 절반으로 줄이면 2027 판에 반영한다. Maroto v2 가 gofpdf 제거를 끝내면 그 행은 바뀐다. 벤치 코드를 공개한 건 어떤 누구도 우리 말을 믿을 필요 없게 하기 위해서다.",[14,8964,8966],{"id":8965},"gpdf-써-보기","gpdf 써 보기",[19,8968,8969],{},"gpdf 는 Go 의 PDF 생성 라이브러리. MIT, 의존 0, 네이티브 CJK.",[97,8971,8972],{"className":1208,"code":1209,"language":1210,"meta":102,"style":102},[44,8973,8974],{"__ignoreMap":102},[106,8975,8976,8978,8980],{"class":108,"line":109},[106,8977,101],{"class":116},[106,8979,1219],{"class":252},[106,8981,1222],{"class":252},[19,8983,8984,1230,8988],{},[720,8985,8987],{"href":1227,"rel":8986},[724],"⭐ GitHub 에서 스타",[720,8989,1235],{"href":1233,"rel":8990},[724],[14,8992,8994],{"id":8993},"이어서-읽기","이어서 읽기",[983,8996,8997,9003,9012],{},[36,8998,8999,9002],{},[720,9000,9001],{"href":4005},"gofpdf 가 아카이브됐다. gpdf 로의 마이그레이션 가이드"," — 5 쌍의 Before/After 로 전체 API 매핑.",[36,9004,9005,2482,9009,9011],{},[720,9006,9008],{"href":1233,"rel":9007},[724],"Quickstart",[44,9010,3988],{}," 포함 5 분 세팅.",[36,9013,9014,9015,240],{},"벤치마크 코드 그 자체: ",[720,9016,9018],{"href":7704,"rel":9017},[724],[44,9019,7465],{},[1237,9021,9022],{},"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":102,"searchDepth":120,"depth":120,"links":9024},[9025,9026,9027,9028,9032,9033,9034,9035,9036,9037,9038,9039,9040],{"id":3932,"depth":120,"text":3933},{"id":7469,"depth":120,"text":7470},{"id":7513,"depth":120,"text":7513},{"id":5359,"depth":120,"text":5359,"children":9029},[9030,9031],{"id":7751,"depth":127,"text":7751},{"id":7857,"depth":127,"text":7858},{"id":7896,"depth":120,"text":7896},{"id":2398,"depth":120,"text":2398},{"id":8054,"depth":120,"text":8055},{"id":8096,"depth":120,"text":8097},{"id":8146,"depth":120,"text":8147},{"id":8884,"depth":120,"text":8885},{"id":5564,"depth":120,"text":5565},{"id":8965,"depth":120,"text":8966},{"id":8993,"depth":120,"text":8994},"2026-04-15","2026 년에도 살아있는 Go PDF 라이브러리를 4 가지 워크로드에서 벤치마크. 라이선스·의존성·유지보수 상태를 정리.",{},{"title":7425,"description":9042},"ko/blog/002.go-pdf-library-showdown-2026",[5707,5708],"teHAfEI1uOobKueYaAVVayAsI2HPDNPhHDNAT5xQYo4",{"id":9049,"title":1176,"author":9050,"body":9051,"date":9041,"description":10356,"draft":1254,"extension":1255,"howTo":10357,"image":1278,"meta":10373,"navigation":123,"path":1175,"seo":10374,"stem":10375,"tags":10376,"updated":1278,"__hash__":10377},"blogKo/ko/blog/003.embed-japanese-font.md",{"name":8,"url":9},{"type":11,"value":9052,"toc":10345},[9053,9055,9064,9067,9080,9084,9588,9601,9605,9608,9614,9623,9636,9640,9651,9799,9816,9820,9827,10253,10260,10264,10270,10276,10291,10293,10318,10320,10323,10335,10343],[14,9054,17],{"id":16},[19,9056,9057,9059,9060,9063],{},[44,9058,705],{}," 절차, CGO 의존, 문서마다 5 MB짜리 폰트 전부 임베딩 — 이런 것들 없이 ",[720,9061,337],{"href":1227,"rel":9062},[724],"로 일본어(또는 CJK) PDF를 만드는 가장 짧은 방법은 무엇인가.",[14,9065,9066],{"id":9066},"요점",[19,9068,9069,9070,1315,9073,9075,9076,9079],{},"TTF 바이트를 읽고, ",[44,9071,9072],{},"gpdf.WithFont(\"NotoSansJP\", fontBytes)",[44,9074,318],{},"에 전달하고, 필요하면 기본 폰트로 지정한다. ",[39,9077,9078],{},"설정 3줄이면 gpdf가 실제로 사용된 글리프만 자동 서브셋 임베딩","한다 — 5 MB 통째로 끌어안지 않는다.",[14,9081,9083],{"id":9082},"완전한-예제","완전한 예제",[97,9085,9087],{"className":99,"code":9086,"language":101,"meta":102,"style":102},"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",[44,9088,9089,9095,9099,9105,9113,9121,9125,9133,9141,9149,9153,9157,9167,9193,9205,9219,9223,9227,9241,9259,9289,9311,9333,9337,9341,9355,9379,9409,9447,9466,9470,9474,9478,9496,9508,9522,9526,9566,9580,9584],{"__ignoreMap":102},[106,9090,9091,9093],{"class":108,"line":109},[106,9092,113],{"class":112},[106,9094,117],{"class":116},[106,9096,9097],{"class":108,"line":120},[106,9098,124],{"emptyLinePlaceholder":123},[106,9100,9101,9103],{"class":108,"line":127},[106,9102,131],{"class":130},[106,9104,134],{"class":112},[106,9106,9107,9109,9111],{"class":108,"line":137},[106,9108,140],{"class":112},[106,9110,143],{"class":116},[106,9112,146],{"class":112},[106,9114,9115,9117,9119],{"class":108,"line":149},[106,9116,140],{"class":112},[106,9118,154],{"class":116},[106,9120,146],{"class":112},[106,9122,9123],{"class":108,"line":159},[106,9124,124],{"emptyLinePlaceholder":123},[106,9126,9127,9129,9131],{"class":108,"line":164},[106,9128,140],{"class":112},[106,9130,169],{"class":116},[106,9132,146],{"class":112},[106,9134,9135,9137,9139],{"class":108,"line":174},[106,9136,140],{"class":112},[106,9138,179],{"class":116},[106,9140,146],{"class":112},[106,9142,9143,9145,9147],{"class":108,"line":184},[106,9144,140],{"class":112},[106,9146,189],{"class":116},[106,9148,146],{"class":112},[106,9150,9151],{"class":108,"line":194},[106,9152,197],{"class":112},[106,9154,9155],{"class":108,"line":200},[106,9156,124],{"emptyLinePlaceholder":123},[106,9158,9159,9161,9163,9165],{"class":108,"line":205},[106,9160,208],{"class":112},[106,9162,212],{"class":211},[106,9164,215],{"class":112},[106,9166,218],{"class":112},[106,9168,9169,9171,9173,9175,9177,9179,9181,9183,9185,9187,9189,9191],{"class":108,"line":221},[106,9170,225],{"class":224},[106,9172,228],{"class":112},[106,9174,231],{"class":224},[106,9176,234],{"class":112},[106,9178,237],{"class":224},[106,9180,240],{"class":112},[106,9182,243],{"class":211},[106,9184,246],{"class":112},[106,9186,249],{"class":112},[106,9188,253],{"class":252},[106,9190,249],{"class":112},[106,9192,197],{"class":112},[106,9194,9195,9197,9199,9201,9203],{"class":108,"line":260},[106,9196,263],{"class":130},[106,9198,231],{"class":224},[106,9200,268],{"class":112},[106,9202,271],{"class":112},[106,9204,218],{"class":112},[106,9206,9207,9209,9211,9213,9215,9217],{"class":108,"line":276},[106,9208,279],{"class":224},[106,9210,240],{"class":112},[106,9212,284],{"class":211},[106,9214,246],{"class":112},[106,9216,289],{"class":224},[106,9218,197],{"class":112},[106,9220,9221],{"class":108,"line":294},[106,9222,297],{"class":112},[106,9224,9225],{"class":108,"line":300},[106,9226,124],{"emptyLinePlaceholder":123},[106,9228,9229,9231,9233,9235,9237,9239],{"class":108,"line":305},[106,9230,308],{"class":224},[106,9232,234],{"class":112},[106,9234,313],{"class":224},[106,9236,240],{"class":112},[106,9238,318],{"class":211},[106,9240,321],{"class":112},[106,9242,9243,9245,9247,9249,9251,9253,9255,9257],{"class":108,"line":324},[106,9244,327],{"class":224},[106,9246,240],{"class":112},[106,9248,332],{"class":211},[106,9250,246],{"class":112},[106,9252,337],{"class":224},[106,9254,240],{"class":112},[106,9256,342],{"class":224},[106,9258,345],{"class":112},[106,9260,9261,9263,9265,9267,9269,9271,9273,9275,9277,9279,9281,9283,9285,9287],{"class":108,"line":348},[106,9262,327],{"class":224},[106,9264,240],{"class":112},[106,9266,355],{"class":211},[106,9268,246],{"class":112},[106,9270,360],{"class":224},[106,9272,240],{"class":112},[106,9274,365],{"class":211},[106,9276,246],{"class":112},[106,9278,360],{"class":224},[106,9280,240],{"class":112},[106,9282,374],{"class":211},[106,9284,246],{"class":112},[106,9286,380],{"class":379},[106,9288,383],{"class":112},[106,9290,9291,9293,9295,9297,9299,9301,9303,9305,9307,9309],{"class":108,"line":386},[106,9292,327],{"class":224},[106,9294,240],{"class":112},[106,9296,50],{"class":211},[106,9298,246],{"class":112},[106,9300,249],{"class":112},[106,9302,399],{"class":252},[106,9304,249],{"class":112},[106,9306,228],{"class":112},[106,9308,406],{"class":224},[106,9310,345],{"class":112},[106,9312,9313,9315,9317,9319,9321,9323,9325,9327,9329,9331],{"class":108,"line":411},[106,9314,327],{"class":224},[106,9316,240],{"class":112},[106,9318,418],{"class":211},[106,9320,246],{"class":112},[106,9322,249],{"class":112},[106,9324,399],{"class":252},[106,9326,249],{"class":112},[106,9328,228],{"class":112},[106,9330,431],{"class":379},[106,9332,345],{"class":112},[106,9334,9335],{"class":108,"line":436},[106,9336,439],{"class":112},[106,9338,9339],{"class":108,"line":442},[106,9340,124],{"emptyLinePlaceholder":123},[106,9342,9343,9345,9347,9349,9351,9353],{"class":108,"line":447},[106,9344,450],{"class":224},[106,9346,234],{"class":112},[106,9348,455],{"class":224},[106,9350,240],{"class":112},[106,9352,460],{"class":211},[106,9354,463],{"class":112},[106,9356,9357,9359,9361,9363,9365,9367,9369,9371,9373,9375,9377],{"class":108,"line":466},[106,9358,469],{"class":224},[106,9360,240],{"class":112},[106,9362,474],{"class":211},[106,9364,477],{"class":112},[106,9366,481],{"class":480},[106,9368,484],{"class":112},[106,9370,487],{"class":116},[106,9372,240],{"class":112},[106,9374,492],{"class":116},[106,9376,495],{"class":112},[106,9378,218],{"class":112},[106,9380,9381,9383,9385,9387,9389,9391,9393,9395,9397,9399,9401,9403,9405,9407],{"class":108,"line":500},[106,9382,503],{"class":224},[106,9384,240],{"class":112},[106,9386,508],{"class":211},[106,9388,246],{"class":112},[106,9390,513],{"class":379},[106,9392,228],{"class":112},[106,9394,518],{"class":112},[106,9396,521],{"class":480},[106,9398,484],{"class":112},[106,9400,487],{"class":116},[106,9402,240],{"class":112},[106,9404,530],{"class":116},[106,9406,495],{"class":112},[106,9408,218],{"class":112},[106,9410,9411,9413,9415,9417,9419,9421,9423,9425,9427,9429,9431,9433,9435,9437,9439,9441,9443,9445],{"class":108,"line":537},[106,9412,540],{"class":224},[106,9414,240],{"class":112},[106,9416,545],{"class":211},[106,9418,246],{"class":112},[106,9420,249],{"class":112},[106,9422,552],{"class":252},[106,9424,249],{"class":112},[106,9426,228],{"class":112},[106,9428,1677],{"class":224},[106,9430,240],{"class":112},[106,9432,1682],{"class":211},[106,9434,246],{"class":112},[106,9436,1687],{"class":379},[106,9438,1690],{"class":112},[106,9440,1677],{"class":224},[106,9442,240],{"class":112},[106,9444,1697],{"class":211},[106,9446,1700],{"class":112},[106,9448,9449,9451,9453,9455,9457,9459,9462,9464],{"class":108,"line":559},[106,9450,540],{"class":224},[106,9452,240],{"class":112},[106,9454,545],{"class":211},[106,9456,246],{"class":112},[106,9458,249],{"class":112},[106,9460,9461],{"class":252},"日本語 PDF、これだけ。",[106,9463,249],{"class":112},[106,9465,197],{"class":112},[106,9467,9468],{"class":108,"line":565},[106,9469,562],{"class":112},[106,9471,9472],{"class":108,"line":571},[106,9473,568],{"class":112},[106,9475,9476],{"class":108,"line":576},[106,9477,124],{"emptyLinePlaceholder":123},[106,9479,9480,9482,9484,9486,9488,9490,9492,9494],{"class":108,"line":597},[106,9481,579],{"class":224},[106,9483,228],{"class":112},[106,9485,231],{"class":224},[106,9487,234],{"class":112},[106,9489,455],{"class":224},[106,9491,240],{"class":112},[106,9493,592],{"class":211},[106,9495,463],{"class":112},[106,9497,9498,9500,9502,9504,9506],{"class":108,"line":610},[106,9499,263],{"class":130},[106,9501,231],{"class":224},[106,9503,268],{"class":112},[106,9505,271],{"class":112},[106,9507,218],{"class":112},[106,9509,9510,9512,9514,9516,9518,9520],{"class":108,"line":625},[106,9511,279],{"class":224},[106,9513,240],{"class":112},[106,9515,284],{"class":211},[106,9517,246],{"class":112},[106,9519,289],{"class":224},[106,9521,197],{"class":112},[106,9523,9524],{"class":108,"line":630},[106,9525,297],{"class":112},[106,9527,9528,9530,9532,9534,9536,9538,9540,9542,9544,9546,9548,9550,9552,9554,9556,9558,9560,9562,9564],{"class":108,"line":676},[106,9529,263],{"class":130},[106,9531,231],{"class":224},[106,9533,234],{"class":112},[106,9535,237],{"class":224},[106,9537,240],{"class":112},[106,9539,643],{"class":211},[106,9541,246],{"class":112},[106,9543,249],{"class":112},[106,9545,650],{"class":252},[106,9547,249],{"class":112},[106,9549,228],{"class":112},[106,9551,657],{"class":224},[106,9553,228],{"class":112},[106,9555,662],{"class":379},[106,9557,665],{"class":112},[106,9559,231],{"class":224},[106,9561,268],{"class":112},[106,9563,271],{"class":112},[106,9565,218],{"class":112},[106,9567,9568,9570,9572,9574,9576,9578],{"class":108,"line":691},[106,9569,279],{"class":224},[106,9571,240],{"class":112},[106,9573,284],{"class":211},[106,9575,246],{"class":112},[106,9577,289],{"class":224},[106,9579,197],{"class":112},[106,9581,9582],{"class":108,"line":696},[106,9583,297],{"class":112},[106,9585,9586],{"class":108,"line":1856},[106,9587,699],{"class":112},[19,9589,9590,1870,9593,9595,9596,1880,9598,9600],{},[720,9591,725],{"href":722,"rel":9592},[724],[44,9594,253],{},"를 다운로드해 ",[44,9597,1879],{},[44,9599,1883],{},"를 실행하면 일본어가 찍힌 한 장짜리 PDF가 나온다.",[14,9602,9604],{"id":9603},"세-줄-뒤에서-벌어지는-일","세 줄 뒤에서 벌어지는 일",[19,9606,9607],{},"내부에서는 두 가지 일이 일어나며, 둘 다 사용자가 신경 쓸 필요가 없다.",[19,9609,9610,9613],{},[39,9611,9612],{},"서브셋 임베딩."," Noto Sans JP은 약 17,000개의 글리프를 담고 있고 Regular 단독으로 약 5 MB다. 폰트 전체를 그대로 임베드한다면 네 줄짜리 영수증 PDF도 5 MB를 넘어버린다. gpdf는 렌더링한 텍스트를 훑어 실제 사용된 글리프 ID만 뽑아 PDF에 기록한다. 짧은 청구서 한 장이라면 폰트 데이터는 보통 20–40 KB 수준이다.",[19,9615,9616,9617,9619,9620,9622],{},"gofpdf도 서브셋화는 가능했지만, ",[44,9618,705],{},"가 파일 경로와 UTF-8 플래그를 받아 커서 이동 중에 로드하는 구조라 문서 중간에 폰트를 바꾸는 것이 번거로웠다. gpdf는 문서 생성 시 한 번만 등록하고, 이후 모든 ",[44,9621,59],{},"는 패밀리 이름만 참조한다. 호출마다 준비 작업은 없다.",[19,9624,9625,9628,9629,9632,9633,9635],{},[39,9626,9627],{},"CGO를 쓰지 않는다."," 이 점은 생각보다 크다. 다른 생태계에서는 폰트 처리가 FreeType이나 HarfBuzz를 거치는 일이 많은데, 그러면 C 의존이 생기고, 빌드 캐시 동작이 달라지며, Docker 이미지 레이어가 늘고, macOS에서 ",[44,9630,9631],{},"linux/arm64","로 크로스 컴파일할 때 추가 설정이 필요해진다. gpdf는 TrueType 테이블을 순수 Go로 파싱한다. ",[44,9634,4350],{},"는 여전히 정적 바이너리를 만들고, distroless 컨테이너에 Go 바이너리와 TTF만 넣어 배포할 수 있다.",[14,9637,9639],{"id":9638},"bold-italic-변형","Bold / Italic 변형",[19,9641,9642,9643,9646,9647,9650],{},"일본어 Noto 패밀리는 굵기별로 파일이 나뉘어 있다. ",[39,9644,9645],{},"굵은 글씨","를 쓰려면 Bold TTF를 ",[44,9648,9649],{},"-Bold"," 접미사로 따로 등록한다:",[97,9652,9654],{"className":99,"code":9653,"language":101,"meta":102,"style":102},"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",[44,9655,9656,9683,9710,9714,9728,9751,9773,9795],{"__ignoreMap":102},[106,9657,9658,9661,9663,9665,9667,9669,9671,9673,9675,9677,9679,9681],{"class":108,"line":109},[106,9659,9660],{"class":224},"reg",[106,9662,228],{"class":112},[106,9664,2042],{"class":224},[106,9666,234],{"class":112},[106,9668,237],{"class":224},[106,9670,240],{"class":112},[106,9672,243],{"class":211},[106,9674,246],{"class":112},[106,9676,249],{"class":112},[106,9678,253],{"class":252},[106,9680,249],{"class":112},[106,9682,197],{"class":112},[106,9684,9685,9688,9690,9692,9694,9696,9698,9700,9702,9704,9706,9708],{"class":108,"line":120},[106,9686,9687],{"class":224},"bold",[106,9689,228],{"class":112},[106,9691,2042],{"class":224},[106,9693,234],{"class":112},[106,9695,237],{"class":224},[106,9697,240],{"class":112},[106,9699,243],{"class":211},[106,9701,246],{"class":112},[106,9703,249],{"class":112},[106,9705,6156],{"class":252},[106,9707,249],{"class":112},[106,9709,197],{"class":112},[106,9711,9712],{"class":108,"line":127},[106,9713,124],{"emptyLinePlaceholder":123},[106,9715,9716,9718,9720,9722,9724,9726],{"class":108,"line":137},[106,9717,797],{"class":224},[106,9719,234],{"class":112},[106,9721,313],{"class":224},[106,9723,240],{"class":112},[106,9725,318],{"class":211},[106,9727,321],{"class":112},[106,9729,9730,9732,9734,9736,9738,9740,9742,9744,9746,9749],{"class":108,"line":149},[106,9731,812],{"class":224},[106,9733,240],{"class":112},[106,9735,50],{"class":211},[106,9737,246],{"class":112},[106,9739,249],{"class":112},[106,9741,399],{"class":252},[106,9743,249],{"class":112},[106,9745,228],{"class":112},[106,9747,9748],{"class":224}," reg",[106,9750,345],{"class":112},[106,9752,9753,9755,9757,9759,9761,9763,9765,9767,9769,9771],{"class":108,"line":159},[106,9754,812],{"class":224},[106,9756,240],{"class":112},[106,9758,50],{"class":211},[106,9760,246],{"class":112},[106,9762,249],{"class":112},[106,9764,6294],{"class":252},[106,9766,249],{"class":112},[106,9768,228],{"class":112},[106,9770,6301],{"class":224},[106,9772,345],{"class":112},[106,9774,9775,9777,9779,9781,9783,9785,9787,9789,9791,9793],{"class":108,"line":164},[106,9776,812],{"class":224},[106,9778,240],{"class":112},[106,9780,418],{"class":211},[106,9782,246],{"class":112},[106,9784,249],{"class":112},[106,9786,399],{"class":252},[106,9788,249],{"class":112},[106,9790,228],{"class":112},[106,9792,431],{"class":379},[106,9794,345],{"class":112},[106,9796,9797],{"class":108,"line":174},[106,9798,197],{"class":112},[19,9800,9801,9802,9804,9805,9807,9808,9811,9812,9815],{},"이제 ",[44,9803,1322],{},"가 ",[44,9806,9649],{}," 변형을 집어든다. ",[44,9809,9810],{},"-Italic","과 ",[44,9813,9814],{},"-BoldItalic","도 같은 규약이다. 변형을 등록하지 않으면 합성 굵기로 폴백된다 — 화면상 읽히지만 타이포그래피적으로는 정확하지 않다. 실전 청구서라면 실제 굵기를 등록하자.",[14,9817,9819],{"id":9818},"같은-문서에-한중일-섞기","같은 문서에 한·중·일 섞기",[19,9821,9822,9823,9826],{},"패밀리는 몇 개든 등록 가능하다. gpdf는 이들을 독립적으로 관리한다. 텍스트 단위로 ",[44,9824,9825],{},"template.FontFamily(...)","를 이용해 전환하면 된다:",[97,9828,9830],{"className":99,"code":9829,"language":101,"meta":102,"style":102},"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",[44,9831,9832,9859,9887,9915,9919,9933,9956,9980,10004,10026,10030,10034,10058,10088,10107,10111,10141,10176,10180,10210,10245,10249],{"__ignoreMap":102},[106,9833,9834,9837,9839,9841,9843,9845,9847,9849,9851,9853,9855,9857],{"class":108,"line":109},[106,9835,9836],{"class":224},"jp",[106,9838,228],{"class":112},[106,9840,2042],{"class":224},[106,9842,234],{"class":112},[106,9844,237],{"class":224},[106,9846,240],{"class":112},[106,9848,243],{"class":211},[106,9850,246],{"class":112},[106,9852,249],{"class":112},[106,9854,253],{"class":252},[106,9856,249],{"class":112},[106,9858,197],{"class":112},[106,9860,9861,9864,9866,9868,9870,9872,9874,9876,9878,9880,9883,9885],{"class":108,"line":120},[106,9862,9863],{"class":224},"sc",[106,9865,228],{"class":112},[106,9867,2042],{"class":224},[106,9869,234],{"class":112},[106,9871,237],{"class":224},[106,9873,240],{"class":112},[106,9875,243],{"class":211},[106,9877,246],{"class":112},[106,9879,249],{"class":112},[106,9881,9882],{"class":252},"NotoSansSC-Regular.ttf",[106,9884,249],{"class":112},[106,9886,197],{"class":112},[106,9888,9889,9892,9894,9896,9898,9900,9902,9904,9906,9908,9911,9913],{"class":108,"line":127},[106,9890,9891],{"class":224},"kr",[106,9893,228],{"class":112},[106,9895,2042],{"class":224},[106,9897,234],{"class":112},[106,9899,237],{"class":224},[106,9901,240],{"class":112},[106,9903,243],{"class":211},[106,9905,246],{"class":112},[106,9907,249],{"class":112},[106,9909,9910],{"class":252},"NotoSansKR-Regular.ttf",[106,9912,249],{"class":112},[106,9914,197],{"class":112},[106,9916,9917],{"class":108,"line":137},[106,9918,124],{"emptyLinePlaceholder":123},[106,9920,9921,9923,9925,9927,9929,9931],{"class":108,"line":149},[106,9922,797],{"class":224},[106,9924,234],{"class":112},[106,9926,313],{"class":224},[106,9928,240],{"class":112},[106,9930,318],{"class":211},[106,9932,321],{"class":112},[106,9934,9935,9937,9939,9941,9943,9945,9947,9949,9951,9954],{"class":108,"line":159},[106,9936,812],{"class":224},[106,9938,240],{"class":112},[106,9940,50],{"class":211},[106,9942,246],{"class":112},[106,9944,249],{"class":112},[106,9946,399],{"class":252},[106,9948,249],{"class":112},[106,9950,228],{"class":112},[106,9952,9953],{"class":224}," jp",[106,9955,345],{"class":112},[106,9957,9958,9960,9962,9964,9966,9968,9971,9973,9975,9978],{"class":108,"line":164},[106,9959,812],{"class":224},[106,9961,240],{"class":112},[106,9963,50],{"class":211},[106,9965,246],{"class":112},[106,9967,249],{"class":112},[106,9969,9970],{"class":252},"NotoSansSC",[106,9972,249],{"class":112},[106,9974,228],{"class":112},[106,9976,9977],{"class":224}," sc",[106,9979,345],{"class":112},[106,9981,9982,9984,9986,9988,9990,9992,9995,9997,9999,10002],{"class":108,"line":174},[106,9983,812],{"class":224},[106,9985,240],{"class":112},[106,9987,50],{"class":211},[106,9989,246],{"class":112},[106,9991,249],{"class":112},[106,9993,9994],{"class":252},"NotoSansKR",[106,9996,249],{"class":112},[106,9998,228],{"class":112},[106,10000,10001],{"class":224}," kr",[106,10003,345],{"class":112},[106,10005,10006,10008,10010,10012,10014,10016,10018,10020,10022,10024],{"class":108,"line":184},[106,10007,812],{"class":224},[106,10009,240],{"class":112},[106,10011,418],{"class":211},[106,10013,246],{"class":112},[106,10015,249],{"class":112},[106,10017,399],{"class":252},[106,10019,249],{"class":112},[106,10021,228],{"class":112},[106,10023,431],{"class":379},[106,10025,345],{"class":112},[106,10027,10028],{"class":108,"line":194},[106,10029,197],{"class":112},[106,10031,10032],{"class":108,"line":200},[106,10033,124],{"emptyLinePlaceholder":123},[106,10035,10036,10038,10040,10042,10044,10046,10048,10050,10052,10054,10056],{"class":108,"line":205},[106,10037,849],{"class":224},[106,10039,240],{"class":112},[106,10041,474],{"class":211},[106,10043,477],{"class":112},[106,10045,481],{"class":480},[106,10047,484],{"class":112},[106,10049,487],{"class":116},[106,10051,240],{"class":112},[106,10053,492],{"class":116},[106,10055,495],{"class":112},[106,10057,218],{"class":112},[106,10059,10060,10062,10064,10066,10068,10070,10072,10074,10076,10078,10080,10082,10084,10086],{"class":108,"line":221},[106,10061,874],{"class":224},[106,10063,240],{"class":112},[106,10065,508],{"class":211},[106,10067,246],{"class":112},[106,10069,3100],{"class":379},[106,10071,228],{"class":112},[106,10073,518],{"class":112},[106,10075,521],{"class":480},[106,10077,484],{"class":112},[106,10079,487],{"class":116},[106,10081,240],{"class":112},[106,10083,530],{"class":116},[106,10085,495],{"class":112},[106,10087,218],{"class":112},[106,10089,10090,10092,10094,10096,10098,10100,10103,10105],{"class":108,"line":260},[106,10091,905],{"class":224},[106,10093,240],{"class":112},[106,10095,545],{"class":211},[106,10097,246],{"class":112},[106,10099,249],{"class":112},[106,10101,10102],{"class":252},"日本語",[106,10104,249],{"class":112},[106,10106,197],{"class":112},[106,10108,10109],{"class":108,"line":276},[106,10110,568],{"class":112},[106,10112,10113,10115,10117,10119,10121,10123,10125,10127,10129,10131,10133,10135,10137,10139],{"class":108,"line":294},[106,10114,874],{"class":224},[106,10116,240],{"class":112},[106,10118,508],{"class":211},[106,10120,246],{"class":112},[106,10122,3100],{"class":379},[106,10124,228],{"class":112},[106,10126,518],{"class":112},[106,10128,521],{"class":480},[106,10130,484],{"class":112},[106,10132,487],{"class":116},[106,10134,240],{"class":112},[106,10136,530],{"class":116},[106,10138,495],{"class":112},[106,10140,218],{"class":112},[106,10142,10143,10145,10147,10149,10151,10153,10156,10158,10160,10162,10164,10166,10168,10170,10172,10174],{"class":108,"line":300},[106,10144,905],{"class":224},[106,10146,240],{"class":112},[106,10148,545],{"class":211},[106,10150,246],{"class":112},[106,10152,249],{"class":112},[106,10154,10155],{"class":252},"中文",[106,10157,249],{"class":112},[106,10159,228],{"class":112},[106,10161,1677],{"class":224},[106,10163,240],{"class":112},[106,10165,2261],{"class":211},[106,10167,246],{"class":112},[106,10169,249],{"class":112},[106,10171,9970],{"class":252},[106,10173,249],{"class":112},[106,10175,2284],{"class":112},[106,10177,10178],{"class":108,"line":305},[106,10179,568],{"class":112},[106,10181,10182,10184,10186,10188,10190,10192,10194,10196,10198,10200,10202,10204,10206,10208],{"class":108,"line":324},[106,10183,874],{"class":224},[106,10185,240],{"class":112},[106,10187,508],{"class":211},[106,10189,246],{"class":112},[106,10191,3100],{"class":379},[106,10193,228],{"class":112},[106,10195,518],{"class":112},[106,10197,521],{"class":480},[106,10199,484],{"class":112},[106,10201,487],{"class":116},[106,10203,240],{"class":112},[106,10205,530],{"class":116},[106,10207,495],{"class":112},[106,10209,218],{"class":112},[106,10211,10212,10214,10216,10218,10220,10222,10225,10227,10229,10231,10233,10235,10237,10239,10241,10243],{"class":108,"line":348},[106,10213,905],{"class":224},[106,10215,240],{"class":112},[106,10217,545],{"class":211},[106,10219,246],{"class":112},[106,10221,249],{"class":112},[106,10223,10224],{"class":252},"한국어",[106,10226,249],{"class":112},[106,10228,228],{"class":112},[106,10230,1677],{"class":224},[106,10232,240],{"class":112},[106,10234,2261],{"class":211},[106,10236,246],{"class":112},[106,10238,249],{"class":112},[106,10240,9994],{"class":252},[106,10242,249],{"class":112},[106,10244,2284],{"class":112},[106,10246,10247],{"class":108,"line":386},[106,10248,568],{"class":112},[106,10250,10251],{"class":108,"line":411},[106,10252,932],{"class":112},[19,10254,10255,10256,10259],{},"한자 통합(Han unification) 때문에 일본어와 중국어 간체는 유니코드 코드포인트를 공유하지만 실제 자형은 다르다. ",[39,10257,10258],{},"같은 코드포인트라도 어떤 폰트를 쓰느냐에 따라 글자 모양이 달라진다"," — 폰트 선택은 미학 문제가 아니라 정확성 문제다. 국가별 세금계산서나 운송장을 생성한다면 두 폰트 모두 등록해야 한다.",[14,10261,10263],{"id":10262},"두부-문자-함정","두부 문자 함정",[19,10265,10266,10267,10269],{},"일본어를 썼는데 ",[44,10268,50],{},"를 빠뜨리면 gpdf는 Base-14 표준 PDF 폰트로 폴백한다. 거기엔 CJK 글리프가 없으므로 문자가 빈 사각형으로 렌더링된다. 유니코드 쪽에서 흔히 말하는 \"두부 문자\"다:",[97,10271,10274],{"className":10272,"code":10273,"language":1087},[7010],"□□□□□、□□。\n",[44,10275,10273],{"__ignoreMap":102},[19,10277,10278,10279,10281,10282,10284,10285,47,10287,10290],{},"이 출력을 보면 원인은 하나다: CJK 폰트를 등록하지 않았거나, 해당 글리프가 없는 패밀리로 쓰고 있다. 해결책도 하나다: ",[44,10280,50],{},"를 추가하고 ",[44,10283,418],{},"로 기본값을 잡거나 ",[44,10286,59],{},[44,10288,10289],{},"template.FontFamily","를 명시하자.",[14,10292,3830],{"id":3829},[983,10294,10295,10304,10310],{},[36,10296,10297,2482,10300,10303],{},[720,10298,10299],{"href":4005},"gofpdf가 아카이브됐다. gpdf로 이관하기.",[44,10301,10302],{},"pdf.AddUTF8Font","에서 옮겨가는 경우의 전체 매핑",[36,10305,10306,10309],{},[720,10307,10308],{"href":3843},"Go PDF 라이브러리 쇼다운 2026"," — gofpdf / gopdf / Maroto / unipdf와 gpdf의 CJK 비교",[36,10311,10312,2482,10315,10317],{},[720,10313,2481],{"href":2479,"rel":10314},[724],[44,10316,50],{}," 전체 레퍼런스 및 변형 명명 규약",[14,10319,3857],{"id":3856},[19,10321,10322],{},"gpdf는 Go용 PDF 생성 라이브러리다. MIT, 외부 의존 없음, CJK 기본 지원.",[97,10324,10325],{"className":1208,"code":1209,"language":1210,"meta":102,"style":102},[44,10326,10327],{"__ignoreMap":102},[106,10328,10329,10331,10333],{"class":108,"line":109},[106,10330,101],{"class":116},[106,10332,1219],{"class":252},[106,10334,1222],{"class":252},[19,10336,10337,1230,10340],{},[720,10338,1229],{"href":1227,"rel":10339},[724],[720,10341,1235],{"href":1233,"rel":10342},[724],[1237,10344,2512],{},{"title":102,"searchDepth":120,"depth":120,"links":10346},[10347,10348,10349,10350,10351,10352,10353,10354,10355],{"id":16,"depth":120,"text":17},{"id":9066,"depth":120,"text":9066},{"id":9082,"depth":120,"text":9083},{"id":9603,"depth":120,"text":9604},{"id":9638,"depth":120,"text":9639},{"id":9818,"depth":120,"text":9819},{"id":10262,"depth":120,"text":10263},{"id":3829,"depth":120,"text":3830},{"id":3856,"depth":120,"text":3857},"TTF 바이트를 gpdf.WithFont에 전달하면 끝. 서브셋 임베딩은 자동, CGO도 필요 없음. Go에서 일본어 PDF를 만드는 최단 경로.",{"name":10358,"totalTime":2527,"tools":10359,"steps":10361},"gpdf 문서에 일본어 TrueType 폰트를 임베드한다",[1260,10360],"NotoSansJP-Regular.ttf (또는 임의의 CJK 지원 TTF)",[10362,10364,10367,10370],{"name":2535,"text":10363},"프로그램 시작 시 os.ReadFile로 NotoSansJP-Regular.ttf를 []byte에 읽어들인다. 바이너리에 포함하고 싶다면 //go:embed를 사용해도 된다.",{"name":10365,"text":10366},"문서 생성 시 WithFont로 등록한다","gpdf.WithFont(\"NotoSansJP\", fontBytes)를 gpdf.NewDocument에 전달한다. 패밀리 이름은 자유롭게 지정 가능 — 이후 참조할 이름과 일치하기만 하면 된다. 서브셋화는 렌더링 시점에 자동으로 수행된다.",{"name":10368,"text":10369},"기본 폰트로 설정한다","gpdf.WithDefaultFont(\"NotoSansJP\", 12)를 추가하면 c.Text 호출 시마다 FontFamily를 넘기지 않아도 일본어 폰트가 적용된다.",{"name":10371,"text":10372},"일본어를 쓰고 PDF를 생성한다","컬럼 안에서 c.Text(\"こんにちは、世界。\")를 호출한다. doc.Generate()가 []byte를 돌려주므로 os.WriteFile로 디스크에 기록한다.",{},{"title":1176,"description":10356},"ko/blog/003.embed-japanese-font",[1284,1286,2551],"q6pAYkicKsHJcq0nPA-lF7_4cBBih3fQI3_pvoipxDs",{"id":10379,"title":1186,"author":10380,"body":10381,"date":9041,"description":11730,"draft":1254,"extension":1255,"howTo":11731,"image":1278,"meta":11748,"navigation":123,"path":1185,"seo":11749,"stem":11750,"tags":11751,"updated":1278,"__hash__":11752},"blogKo/ko/blog/004.noto-sans-jp-with-gpdf.md",{"name":8,"url":9},{"type":11,"value":10382,"toc":11717},[10383,10387,10396,10400,10413,10415,10920,10933,10937,10944,10971,10978,10998,11001,11005,11008,11075,11095,11106,11110,11113,11259,11272,11275,11344,11354,11358,11361,11364,11425,11428,11431,11435,11438,11443,11457,11466,11480,11484,11487,11645,11652,11656,11685,11689,11692,11704,11714],[14,10384,10386],{"id":10385},"질문을-다시-정리하면","질문을 다시 정리하면",[19,10388,10389,10392,10393,240],{},[720,10390,337],{"href":1227,"rel":10391},[724]," 문서에 일본어를 렌더링하려 하고, 폰트는 Noto Sans JP — Google이 배포하는 SIL OFL 라이선스의 고딕체, JIS 영역을 완전히 커버하는 그 폰트 — 를 쓰기로 정했다. Google Fonts의 zip은 이미 받았다. 여기서부터 알고 싶은 세 가지: ",[39,10394,10395],{},"어느 파일을 고를지, 어느 굵기를 등록할지, zip 안에 숨어 있는 한 가지 함정은 무엇인지",[14,10397,10399],{"id":10398},"결론-tldr","결론 (TL;DR)",[19,10401,10402,10403,10408,10409,10412],{},"zip을 풀면 ",[39,10404,10405],{},[44,10406,10407],{},"static/NotoSansJP-Regular.ttf"," 를 씁니다 — zip 루트의 variable 폰트가 아닙니다. 이 파일을 ",[44,10410,10411],{},"gpdf.WithFont(\"NotoSansJP\", bytes)","에 넘기고 기본 폰트로 지정하면 끝. gpdf는 약 17,000개 글리프 중 실제로 렌더링된 글리프만 서브셋해서 PDF에 포함합니다 — 일반적인 청구서 한 장은 최종 PDF에 20–40 KB 정도의 폰트 데이터를 담게 됩니다.",[14,10414,9083],{"id":9082},[97,10416,10418],{"className":99,"code":10417,"language":101,"meta":102,"style":102},"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",[44,10419,10420,10426,10430,10436,10444,10452,10456,10464,10472,10480,10484,10488,10498,10524,10536,10550,10554,10558,10572,10590,10620,10642,10664,10668,10672,10686,10710,10740,10779,10798,10802,10806,10810,10828,10840,10854,10858,10898,10912,10916],{"__ignoreMap":102},[106,10421,10422,10424],{"class":108,"line":109},[106,10423,113],{"class":112},[106,10425,117],{"class":116},[106,10427,10428],{"class":108,"line":120},[106,10429,124],{"emptyLinePlaceholder":123},[106,10431,10432,10434],{"class":108,"line":127},[106,10433,131],{"class":130},[106,10435,134],{"class":112},[106,10437,10438,10440,10442],{"class":108,"line":137},[106,10439,140],{"class":112},[106,10441,143],{"class":116},[106,10443,146],{"class":112},[106,10445,10446,10448,10450],{"class":108,"line":149},[106,10447,140],{"class":112},[106,10449,154],{"class":116},[106,10451,146],{"class":112},[106,10453,10454],{"class":108,"line":159},[106,10455,124],{"emptyLinePlaceholder":123},[106,10457,10458,10460,10462],{"class":108,"line":164},[106,10459,140],{"class":112},[106,10461,169],{"class":116},[106,10463,146],{"class":112},[106,10465,10466,10468,10470],{"class":108,"line":174},[106,10467,140],{"class":112},[106,10469,179],{"class":116},[106,10471,146],{"class":112},[106,10473,10474,10476,10478],{"class":108,"line":184},[106,10475,140],{"class":112},[106,10477,189],{"class":116},[106,10479,146],{"class":112},[106,10481,10482],{"class":108,"line":194},[106,10483,197],{"class":112},[106,10485,10486],{"class":108,"line":200},[106,10487,124],{"emptyLinePlaceholder":123},[106,10489,10490,10492,10494,10496],{"class":108,"line":205},[106,10491,208],{"class":112},[106,10493,212],{"class":211},[106,10495,215],{"class":112},[106,10497,218],{"class":112},[106,10499,10500,10502,10504,10506,10508,10510,10512,10514,10516,10518,10520,10522],{"class":108,"line":221},[106,10501,225],{"class":224},[106,10503,228],{"class":112},[106,10505,231],{"class":224},[106,10507,234],{"class":112},[106,10509,237],{"class":224},[106,10511,240],{"class":112},[106,10513,243],{"class":211},[106,10515,246],{"class":112},[106,10517,249],{"class":112},[106,10519,253],{"class":252},[106,10521,249],{"class":112},[106,10523,197],{"class":112},[106,10525,10526,10528,10530,10532,10534],{"class":108,"line":260},[106,10527,263],{"class":130},[106,10529,231],{"class":224},[106,10531,268],{"class":112},[106,10533,271],{"class":112},[106,10535,218],{"class":112},[106,10537,10538,10540,10542,10544,10546,10548],{"class":108,"line":276},[106,10539,279],{"class":224},[106,10541,240],{"class":112},[106,10543,284],{"class":211},[106,10545,246],{"class":112},[106,10547,289],{"class":224},[106,10549,197],{"class":112},[106,10551,10552],{"class":108,"line":294},[106,10553,297],{"class":112},[106,10555,10556],{"class":108,"line":300},[106,10557,124],{"emptyLinePlaceholder":123},[106,10559,10560,10562,10564,10566,10568,10570],{"class":108,"line":305},[106,10561,308],{"class":224},[106,10563,234],{"class":112},[106,10565,313],{"class":224},[106,10567,240],{"class":112},[106,10569,318],{"class":211},[106,10571,321],{"class":112},[106,10573,10574,10576,10578,10580,10582,10584,10586,10588],{"class":108,"line":324},[106,10575,327],{"class":224},[106,10577,240],{"class":112},[106,10579,332],{"class":211},[106,10581,246],{"class":112},[106,10583,337],{"class":224},[106,10585,240],{"class":112},[106,10587,342],{"class":224},[106,10589,345],{"class":112},[106,10591,10592,10594,10596,10598,10600,10602,10604,10606,10608,10610,10612,10614,10616,10618],{"class":108,"line":348},[106,10593,327],{"class":224},[106,10595,240],{"class":112},[106,10597,355],{"class":211},[106,10599,246],{"class":112},[106,10601,360],{"class":224},[106,10603,240],{"class":112},[106,10605,365],{"class":211},[106,10607,246],{"class":112},[106,10609,360],{"class":224},[106,10611,240],{"class":112},[106,10613,374],{"class":211},[106,10615,246],{"class":112},[106,10617,380],{"class":379},[106,10619,383],{"class":112},[106,10621,10622,10624,10626,10628,10630,10632,10634,10636,10638,10640],{"class":108,"line":386},[106,10623,327],{"class":224},[106,10625,240],{"class":112},[106,10627,50],{"class":211},[106,10629,246],{"class":112},[106,10631,249],{"class":112},[106,10633,399],{"class":252},[106,10635,249],{"class":112},[106,10637,228],{"class":112},[106,10639,406],{"class":224},[106,10641,345],{"class":112},[106,10643,10644,10646,10648,10650,10652,10654,10656,10658,10660,10662],{"class":108,"line":411},[106,10645,327],{"class":224},[106,10647,240],{"class":112},[106,10649,418],{"class":211},[106,10651,246],{"class":112},[106,10653,249],{"class":112},[106,10655,399],{"class":252},[106,10657,249],{"class":112},[106,10659,228],{"class":112},[106,10661,6324],{"class":379},[106,10663,345],{"class":112},[106,10665,10666],{"class":108,"line":436},[106,10667,439],{"class":112},[106,10669,10670],{"class":108,"line":442},[106,10671,124],{"emptyLinePlaceholder":123},[106,10673,10674,10676,10678,10680,10682,10684],{"class":108,"line":447},[106,10675,450],{"class":224},[106,10677,234],{"class":112},[106,10679,455],{"class":224},[106,10681,240],{"class":112},[106,10683,460],{"class":211},[106,10685,463],{"class":112},[106,10687,10688,10690,10692,10694,10696,10698,10700,10702,10704,10706,10708],{"class":108,"line":466},[106,10689,469],{"class":224},[106,10691,240],{"class":112},[106,10693,474],{"class":211},[106,10695,477],{"class":112},[106,10697,481],{"class":480},[106,10699,484],{"class":112},[106,10701,487],{"class":116},[106,10703,240],{"class":112},[106,10705,492],{"class":116},[106,10707,495],{"class":112},[106,10709,218],{"class":112},[106,10711,10712,10714,10716,10718,10720,10722,10724,10726,10728,10730,10732,10734,10736,10738],{"class":108,"line":500},[106,10713,503],{"class":224},[106,10715,240],{"class":112},[106,10717,508],{"class":211},[106,10719,246],{"class":112},[106,10721,513],{"class":379},[106,10723,228],{"class":112},[106,10725,518],{"class":112},[106,10727,521],{"class":480},[106,10729,484],{"class":112},[106,10731,487],{"class":116},[106,10733,240],{"class":112},[106,10735,530],{"class":116},[106,10737,495],{"class":112},[106,10739,218],{"class":112},[106,10741,10742,10744,10746,10748,10750,10752,10754,10756,10758,10760,10762,10764,10766,10769,10771,10773,10775,10777],{"class":108,"line":537},[106,10743,540],{"class":224},[106,10745,240],{"class":112},[106,10747,545],{"class":211},[106,10749,246],{"class":112},[106,10751,249],{"class":112},[106,10753,1670],{"class":252},[106,10755,249],{"class":112},[106,10757,228],{"class":112},[106,10759,1677],{"class":224},[106,10761,240],{"class":112},[106,10763,1682],{"class":211},[106,10765,246],{"class":112},[106,10767,10768],{"class":379},"28",[106,10770,1690],{"class":112},[106,10772,1677],{"class":224},[106,10774,240],{"class":112},[106,10776,1697],{"class":211},[106,10778,1700],{"class":112},[106,10780,10781,10783,10785,10787,10789,10791,10794,10796],{"class":108,"line":559},[106,10782,540],{"class":224},[106,10784,240],{"class":112},[106,10786,545],{"class":211},[106,10788,246],{"class":112},[106,10790,249],{"class":112},[106,10792,10793],{"class":252},"Noto Sans JP、これで十分。",[106,10795,249],{"class":112},[106,10797,197],{"class":112},[106,10799,10800],{"class":108,"line":565},[106,10801,562],{"class":112},[106,10803,10804],{"class":108,"line":571},[106,10805,568],{"class":112},[106,10807,10808],{"class":108,"line":576},[106,10809,124],{"emptyLinePlaceholder":123},[106,10811,10812,10814,10816,10818,10820,10822,10824,10826],{"class":108,"line":597},[106,10813,579],{"class":224},[106,10815,228],{"class":112},[106,10817,231],{"class":224},[106,10819,234],{"class":112},[106,10821,455],{"class":224},[106,10823,240],{"class":112},[106,10825,592],{"class":211},[106,10827,463],{"class":112},[106,10829,10830,10832,10834,10836,10838],{"class":108,"line":610},[106,10831,263],{"class":130},[106,10833,231],{"class":224},[106,10835,268],{"class":112},[106,10837,271],{"class":112},[106,10839,218],{"class":112},[106,10841,10842,10844,10846,10848,10850,10852],{"class":108,"line":625},[106,10843,279],{"class":224},[106,10845,240],{"class":112},[106,10847,284],{"class":211},[106,10849,246],{"class":112},[106,10851,289],{"class":224},[106,10853,197],{"class":112},[106,10855,10856],{"class":108,"line":630},[106,10857,297],{"class":112},[106,10859,10860,10862,10864,10866,10868,10870,10872,10874,10876,10878,10880,10882,10884,10886,10888,10890,10892,10894,10896],{"class":108,"line":676},[106,10861,263],{"class":130},[106,10863,231],{"class":224},[106,10865,234],{"class":112},[106,10867,237],{"class":224},[106,10869,240],{"class":112},[106,10871,643],{"class":211},[106,10873,246],{"class":112},[106,10875,249],{"class":112},[106,10877,1819],{"class":252},[106,10879,249],{"class":112},[106,10881,228],{"class":112},[106,10883,657],{"class":224},[106,10885,228],{"class":112},[106,10887,662],{"class":379},[106,10889,665],{"class":112},[106,10891,231],{"class":224},[106,10893,268],{"class":112},[106,10895,271],{"class":112},[106,10897,218],{"class":112},[106,10899,10900,10902,10904,10906,10908,10910],{"class":108,"line":691},[106,10901,279],{"class":224},[106,10903,240],{"class":112},[106,10905,284],{"class":211},[106,10907,246],{"class":112},[106,10909,289],{"class":224},[106,10911,197],{"class":112},[106,10913,10914],{"class":108,"line":696},[106,10915,297],{"class":112},[106,10917,10918],{"class":108,"line":1856},[106,10919,699],{"class":112},[19,10921,10922,10925,10926,1315,10928,1880,10930,10932],{},[720,10923,725],{"href":722,"rel":10924},[724],"에서 zip을 받아 풀고, ",[44,10927,10407],{},[44,10929,1879],{},[44,10931,1883],{},"를 실행하면 한 페이지짜리 PDF가 나옵니다.",[14,10934,10936],{"id":10935},"variable-폰트가-아닌-static-ttf를-고르기","variable 폰트가 아닌 static TTF를 고르기",[19,10938,10939,10940,10943],{},"Google Fonts에서 ",[39,10941,10942],{},"Get font → Download all",", zip 압축을 풀면 보기에 비슷하지만 성격이 전혀 다른 두 그룹이 보입니다:",[983,10945,10946,10957],{},[36,10947,10948,10949,10952,10953,10956],{},"zip 루트의 ",[44,10950,10951],{},"NotoSansJP-VariableFont_wght.ttf"," — weight 100–900을 한 파일에 담은 ",[39,10954,10955],{},"variable 폰트",", 약 7 MB",[36,10958,10959,10962,10963,10966,10967,10970],{},[44,10960,10961],{},"static/"," 디렉토리 — ",[44,10964,10965],{},"NotoSansJP-Thin.ttf","부터 ",[44,10968,10969],{},"NotoSansJP-Black.ttf","까지 굵기별로 분리된 9개의 TTF, 각 약 5 MB",[19,10972,10973,240],{},[39,10974,10975,10977],{},[44,10976,10961],{}," 쪽을 쓰세요",[19,10979,10980,10981,2567,10983,10986,10987,10990,10991,10990,10994,10997],{},"gpdf의 TrueType 파서는 일부러 범위를 좁게 잡아 뒀습니다. 글리프 아웃라인, 복합 글리프, ",[44,10982,5788],{},[44,10984,10985],{},"hmtx"," — 고정 굵기 텍스트를 렌더링하는 데 필요한 테이블은 다 다룹니다. 하지만 variable 폰트를 진짜 가변적으로 만드는 ",[44,10988,10989],{},"fvar"," / ",[44,10992,10993],{},"gvar",[44,10995,10996],{},"HVAR"," 테이블은 읽지 않습니다. VariableFont_wght.ttf를 넘기면 파서가 깔끔히 에러를 내거나, 운이 나쁘면 기본 인스턴스의 글리프만 뽑고 당신이 지정했다고 생각한 weight 축을 조용히 무시합니다.",[19,10999,11000],{},"파일 크기 측면에서도 static 쪽이 유리합니다. variable 폰트는 weight 축 위의 모든 인스턴스 아웃라인을 한 파일에 담는 — 그게 설계 의도입니다. Regular만 쓴다면 나머지 8개 weight 만큼의 데이터를 그냥 실어 나르게 됩니다. static Regular가 5 MB, variable이 7 MB. 서브셋으로 둘 다 줄어들긴 하지만 입력은 static이 깔끔합니다.",[14,11002,11004],{"id":11003},"핵심은-이-네-줄","핵심은 이 네 줄",[19,11006,11007],{},"의미 있는 건 생성자 옵션뿐입니다:",[97,11009,11011],{"className":99,"code":11010,"language":101,"meta":102,"style":102},"doc := gpdf.NewDocument(\n    gpdf.WithFont(\"NotoSansJP\", font),\n    gpdf.WithDefaultFont(\"NotoSansJP\", 11),\n)\n",[44,11012,11013,11027,11049,11071],{"__ignoreMap":102},[106,11014,11015,11017,11019,11021,11023,11025],{"class":108,"line":109},[106,11016,797],{"class":224},[106,11018,234],{"class":112},[106,11020,313],{"class":224},[106,11022,240],{"class":112},[106,11024,318],{"class":211},[106,11026,321],{"class":112},[106,11028,11029,11031,11033,11035,11037,11039,11041,11043,11045,11047],{"class":108,"line":120},[106,11030,812],{"class":224},[106,11032,240],{"class":112},[106,11034,50],{"class":211},[106,11036,246],{"class":112},[106,11038,249],{"class":112},[106,11040,399],{"class":252},[106,11042,249],{"class":112},[106,11044,228],{"class":112},[106,11046,406],{"class":224},[106,11048,345],{"class":112},[106,11050,11051,11053,11055,11057,11059,11061,11063,11065,11067,11069],{"class":108,"line":127},[106,11052,812],{"class":224},[106,11054,240],{"class":112},[106,11056,418],{"class":211},[106,11058,246],{"class":112},[106,11060,249],{"class":112},[106,11062,399],{"class":252},[106,11064,249],{"class":112},[106,11066,228],{"class":112},[106,11068,6324],{"class":379},[106,11070,345],{"class":112},[106,11072,11073],{"class":108,"line":137},[106,11074,197],{"class":112},[19,11076,11077,11078,11081,11082,5574,11085,2567,11088,11091,11092,11094],{},"폰트 패밀리명 (",[44,11079,11080],{},"\"NotoSansJP\"",") 은 임의로 정해도 됩니다. gpdf는 이를 조회 키로만 씁니다 — 파일 경로도 아니고, 폰트 메타데이터에서 읽는 이름도 아닙니다. 팀에서 ",[44,11083,11084],{},"\"body\"",[44,11086,11087],{},"\"jp\"",[44,11089,11090],{},"\"Noto\"","가 더 읽기 좋다면 그걸 쓰세요. 나중에 ",[44,11093,9825],{},"에 같은 이름을 넘기기만 하면 됩니다.",[19,11096,11097,11099,11100,11102,11103,11105],{},[44,11098,418],{},"는 매번 ",[44,11101,59],{}," 호출에 ",[44,11104,946],{},"를 쓰지 않게 해 줍니다. 이걸 빼면 gpdf는 Helvetica로 폴백하는데, Helvetica는 CJK 코드포인트를 하나도 커버하지 않아서 — 제목만 제대로 나오고 본문 전체가 두부 네모 (□□□)가 된 PDF가 나옵니다. 왜 제목만 멀쩡한지 한 시간쯤 헤매게 됩니다.",[14,11107,11109],{"id":11108},"어느-굵기를-등록해야-할까","어느 굵기를 등록해야 할까",[19,11111,11112],{},"청구서·영수증·업무용 리포트라면 Regular와 Bold 두 개면 충분합니다:",[97,11114,11116],{"className":99,"code":11115,"language":101,"meta":102,"style":102},"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",[44,11117,11118,11145,11171,11175,11189,11211,11233,11255],{"__ignoreMap":102},[106,11119,11120,11122,11124,11127,11129,11131,11133,11135,11137,11139,11141,11143],{"class":108,"line":109},[106,11121,9660],{"class":224},[106,11123,228],{"class":112},[106,11125,11126],{"class":224},"  _ ",[106,11128,234],{"class":112},[106,11130,237],{"class":224},[106,11132,240],{"class":112},[106,11134,243],{"class":211},[106,11136,246],{"class":112},[106,11138,249],{"class":112},[106,11140,253],{"class":252},[106,11142,249],{"class":112},[106,11144,197],{"class":112},[106,11146,11147,11149,11151,11153,11155,11157,11159,11161,11163,11165,11167,11169],{"class":108,"line":120},[106,11148,9687],{"class":224},[106,11150,228],{"class":112},[106,11152,2042],{"class":224},[106,11154,234],{"class":112},[106,11156,237],{"class":224},[106,11158,240],{"class":112},[106,11160,243],{"class":211},[106,11162,246],{"class":112},[106,11164,249],{"class":112},[106,11166,6156],{"class":252},[106,11168,249],{"class":112},[106,11170,197],{"class":112},[106,11172,11173],{"class":108,"line":127},[106,11174,124],{"emptyLinePlaceholder":123},[106,11176,11177,11179,11181,11183,11185,11187],{"class":108,"line":137},[106,11178,797],{"class":224},[106,11180,234],{"class":112},[106,11182,313],{"class":224},[106,11184,240],{"class":112},[106,11186,318],{"class":211},[106,11188,321],{"class":112},[106,11190,11191,11193,11195,11197,11199,11201,11203,11205,11207,11209],{"class":108,"line":149},[106,11192,812],{"class":224},[106,11194,240],{"class":112},[106,11196,50],{"class":211},[106,11198,246],{"class":112},[106,11200,249],{"class":112},[106,11202,399],{"class":252},[106,11204,249],{"class":112},[106,11206,228],{"class":112},[106,11208,9748],{"class":224},[106,11210,345],{"class":112},[106,11212,11213,11215,11217,11219,11221,11223,11225,11227,11229,11231],{"class":108,"line":159},[106,11214,812],{"class":224},[106,11216,240],{"class":112},[106,11218,50],{"class":211},[106,11220,246],{"class":112},[106,11222,249],{"class":112},[106,11224,6294],{"class":252},[106,11226,249],{"class":112},[106,11228,228],{"class":112},[106,11230,6301],{"class":224},[106,11232,345],{"class":112},[106,11234,11235,11237,11239,11241,11243,11245,11247,11249,11251,11253],{"class":108,"line":164},[106,11236,812],{"class":224},[106,11238,240],{"class":112},[106,11240,418],{"class":211},[106,11242,246],{"class":112},[106,11244,249],{"class":112},[106,11246,399],{"class":252},[106,11248,249],{"class":112},[106,11250,228],{"class":112},[106,11252,6324],{"class":379},[106,11254,345],{"class":112},[106,11256,11257],{"class":108,"line":174},[106,11258,197],{"class":112},[19,11260,11261,11263,11264,11266,11267,2567,11269,11271],{},[44,11262,9649],{}," 서픽스로 등록하면 ",[44,11265,1322],{},"가 자동으로 잡습니다. ",[44,11268,9810],{},[44,11270,9814],{},"도 같은 규약. 다만 Noto Sans JP에는 이탤릭이 없습니다 — CJK 폰트는 자연스러운 기울임 형태가 없어서 Noto 계열에도 이탤릭이 존재하지 않습니다. 일본어에서 강조가 필요하면 색상·크기·굵기로 대체하세요.",[19,11273,11274],{},"브로슈어나 헤드라인에 Medium이나 SemiBold가 필요하면 원하는 서픽스로 등록하고 패밀리명으로 직접 참조하면 됩니다:",[97,11276,11278],{"className":99,"code":11277,"language":101,"meta":102,"style":102},"gpdf.WithFont(\"NotoSansJP-Medium\", medium)\n// ...\nc.Text(\"見出し\", template.FontFamily(\"NotoSansJP-Medium\"))\n",[44,11279,11280,11304,11309],{"__ignoreMap":102},[106,11281,11282,11284,11286,11288,11290,11292,11295,11297,11299,11302],{"class":108,"line":109},[106,11283,337],{"class":224},[106,11285,240],{"class":112},[106,11287,50],{"class":211},[106,11289,246],{"class":112},[106,11291,249],{"class":112},[106,11293,11294],{"class":252},"NotoSansJP-Medium",[106,11296,249],{"class":112},[106,11298,228],{"class":112},[106,11300,11301],{"class":224}," medium",[106,11303,197],{"class":112},[106,11305,11306],{"class":108,"line":120},[106,11307,11308],{"class":835},"// ...\n",[106,11310,11311,11313,11315,11317,11319,11321,11324,11326,11328,11330,11332,11334,11336,11338,11340,11342],{"class":108,"line":127},[106,11312,521],{"class":224},[106,11314,240],{"class":112},[106,11316,545],{"class":211},[106,11318,246],{"class":112},[106,11320,249],{"class":112},[106,11322,11323],{"class":252},"見出し",[106,11325,249],{"class":112},[106,11327,228],{"class":112},[106,11329,1677],{"class":224},[106,11331,240],{"class":112},[106,11333,2261],{"class":211},[106,11335,246],{"class":112},[106,11337,249],{"class":112},[106,11339,11294],{"class":252},[106,11341,249],{"class":112},[106,11343,2284],{"class":112},[19,11345,11346,11347,10990,11349,10990,11351,11353],{},"서픽스 기반 Bold/Italic 숏컷은 ",[44,11348,9649],{},[44,11350,9810],{},[44,11352,9814],{}," 이 세 개에만 자동 연결됩니다. 나머지는 패밀리명으로 명시적으로 부릅니다.",[14,11355,11357],{"id":11356},"서브셋-후의-실제-크기","서브셋 후의 실제 크기",[19,11359,11360],{},"Noto Sans JP Regular는 디스크에서 약 5 MB. 이 숫자를 보고 별도 폰트 CDN을 꾸리거나 PDF 후처리로 폰트를 빼내는 팀이 가끔 있는데, gpdf에서는 둘 다 필요 없습니다.",[19,11362,11363],{},"실제로 PDF에 들어가는 양은 이 정도:",[1892,11365,11366,11379],{},[1895,11367,11368],{},[1898,11369,11370,11373,11376],{},[1901,11371,11372],{},"문서",[1901,11374,11375],{},"사용 글리프",[1901,11377,11378],{},"PDF 내 폰트 데이터",[1908,11380,11381,11392,11403,11414],{},[1898,11382,11383,11386,11389],{},[1913,11384,11385],{},"한 줄 영수증 (~15자)",[1913,11387,11388],{},"~14",[1913,11390,11391],{},"~11 KB",[1898,11393,11394,11397,11400],{},[1913,11395,11396],{},"일반 청구서 (~200자)",[1913,11398,11399],{},"~80",[1913,11401,11402],{},"~28 KB",[1898,11404,11405,11408,11411],{},[1913,11406,11407],{},"10페이지 리포트 (~8,000자)",[1913,11409,11410],{},"~900",[1913,11412,11413],{},"~180 KB",[1898,11415,11416,11419,11422],{},[1913,11417,11418],{},"사전 수준 풀셋 (JIS Level 1)",[1913,11420,11421],{},"~6,800",[1913,11423,11424],{},"~2.1 MB",[19,11426,11427],{},"(gpdf v1.0, 정적 서브셋 활성화. 글리프 ID가 CFF와 hmtx 어디에 떨어지느냐에 따라 몇 KB 편차)",[19,11429,11430],{},"최종 50 KB짜리 청구서 PDF라면 그중 절반 이상이 폰트 데이터입니다. 그래도 서브셋 없이 5 MB를 통째로 넣는 것에 비하면 오차 수준이고, 뷰어는 즉시 엽니다.",[14,11432,11434],{"id":11433},"noto-sans-jp와-noto-sans-cjk-jp-혼동-금지","Noto Sans JP와 Noto Sans CJK JP — 혼동 금지",[19,11436,11437],{},"일본어를 처리할 수 있다고 주장하는 Noto 패밀리가 두 개 있고, 이름이 비슷해서 서로 호환된다고 착각하기 쉽습니다. 실제로는 완전히 다릅니다.",[19,11439,11440,11442],{},[39,11441,2380],{},"가 쓰려는 쪽입니다. TTF 배포, 단일 언어, 굵기별로 파일이 분리. Google Fonts에서 받는 것이 이것입니다.",[19,11444,11445,11448,11449,11452,11453,11456],{},[39,11446,11447],{},"Noto Sans CJK JP","는 CJK 전체를 아우르는 슈퍼 패밀리. OpenType Collection (",[44,11450,11451],{},".ttc",") 형식으로, 일본어·간체 중국어·번체 중국어·한국어 글리프를 한자 통합 (Han unification) 방식으로 한 파일에 담아 배포합니다. 초기 Noto 릴리스와 ",[44,11454,11455],{},"notofonts.github.io/noto-cjk","에 있는 것은 이쪽입니다.",[19,11458,11459,11460,11462,11463,11465],{},"gpdf는 TTF를 바로 지원합니다. TTC는 컨테이너 포맷이라 ",[44,11461,50],{},"에 바이트를 넘기기 전에 face 인덱스를 골라야 하고, 각 face 안의 ",[44,11464,5788],{},"은 특정 CJK 로케일에 맞춰져 있어 한자 통합에 관한 선택을 암묵적으로 하는 꼴이 됩니다. JP 전용 TTF를 고르면 이런 선택이 명시적이 됩니다.",[19,11467,11468,11469,11472,11473,757,11476,11479],{},"새 프로젝트면 Noto Sans JP를 씁니다. 레거시 프로젝트에 ",[44,11470,11471],{},"NotoSansCJK-Regular.ttc","가 이미 있다면 ",[44,11474,11475],{},"pyftsubset",[44,11477,11478],{},"fonttools","로 JP face만 추출해 TTF로 저장소에 커밋하는 편이 안전합니다.",[14,11481,11483],{"id":11482},"바이너리에-폰트-임베드하기","바이너리에 폰트 임베드하기",[19,11485,11486],{},"PDF 생성기는 대개 컨테이너에서 돕니다. 폰트를 함께 배포하는 가장 깔끔한 방법은 바이너리에 컴파일해 넣는 것:",[97,11488,11490],{"className":99,"code":11489,"language":101,"meta":102,"style":102},"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",[44,11491,11492,11498,11502,11508,11520,11524,11532,11536,11540,11545,11559,11563,11573,11587,11610,11632,11636,11641],{"__ignoreMap":102},[106,11493,11494,11496],{"class":108,"line":109},[106,11495,113],{"class":112},[106,11497,117],{"class":116},[106,11499,11500],{"class":108,"line":120},[106,11501,124],{"emptyLinePlaceholder":123},[106,11503,11504,11506],{"class":108,"line":127},[106,11505,131],{"class":130},[106,11507,134],{"class":112},[106,11509,11510,11513,11515,11518],{"class":108,"line":137},[106,11511,11512],{"class":224},"    _ ",[106,11514,249],{"class":112},[106,11516,11517],{"class":116},"embed",[106,11519,146],{"class":112},[106,11521,11522],{"class":108,"line":149},[106,11523,124],{"emptyLinePlaceholder":123},[106,11525,11526,11528,11530],{"class":108,"line":159},[106,11527,140],{"class":112},[106,11529,169],{"class":116},[106,11531,146],{"class":112},[106,11533,11534],{"class":108,"line":164},[106,11535,197],{"class":112},[106,11537,11538],{"class":108,"line":174},[106,11539,124],{"emptyLinePlaceholder":123},[106,11541,11542],{"class":108,"line":184},[106,11543,11544],{"class":835},"//go:embed NotoSansJP-Regular.ttf\n",[106,11546,11547,11550,11553,11556],{"class":108,"line":194},[106,11548,11549],{"class":112},"var",[106,11551,11552],{"class":224}," notoJP ",[106,11554,11555],{"class":112},"[]",[106,11557,11558],{"class":8461},"byte\n",[106,11560,11561],{"class":108,"line":200},[106,11562,124],{"emptyLinePlaceholder":123},[106,11564,11565,11567,11569,11571],{"class":108,"line":205},[106,11566,208],{"class":112},[106,11568,212],{"class":211},[106,11570,215],{"class":112},[106,11572,218],{"class":112},[106,11574,11575,11577,11579,11581,11583,11585],{"class":108,"line":221},[106,11576,308],{"class":224},[106,11578,234],{"class":112},[106,11580,313],{"class":224},[106,11582,240],{"class":112},[106,11584,318],{"class":211},[106,11586,321],{"class":112},[106,11588,11589,11591,11593,11595,11597,11599,11601,11603,11605,11608],{"class":108,"line":260},[106,11590,327],{"class":224},[106,11592,240],{"class":112},[106,11594,50],{"class":211},[106,11596,246],{"class":112},[106,11598,249],{"class":112},[106,11600,399],{"class":252},[106,11602,249],{"class":112},[106,11604,228],{"class":112},[106,11606,11607],{"class":224}," notoJP",[106,11609,345],{"class":112},[106,11611,11612,11614,11616,11618,11620,11622,11624,11626,11628,11630],{"class":108,"line":276},[106,11613,327],{"class":224},[106,11615,240],{"class":112},[106,11617,418],{"class":211},[106,11619,246],{"class":112},[106,11621,249],{"class":112},[106,11623,399],{"class":252},[106,11625,249],{"class":112},[106,11627,228],{"class":112},[106,11629,6324],{"class":379},[106,11631,345],{"class":112},[106,11633,11634],{"class":108,"line":294},[106,11635,439],{"class":112},[106,11637,11638],{"class":108,"line":300},[106,11639,11640],{"class":835},"    // ...\n",[106,11642,11643],{"class":108,"line":305},[106,11644,699],{"class":112},[19,11646,11647,11648,11651],{},"바이너리는 약 8 MB에서 약 13 MB로 늘어납니다. 대신 Docker 이미지 산출물이 두 개가 아니라 하나가 되고, ",[44,11649,11650],{},"COPY --from=builder /app /app"," 만으로 충분하며, 폰트 파일 누락으로 망가진 컨테이너를 누군가 올릴 일도 없습니다. 하루에 수천 개 PDF를 생성하는 배치 작업이라면 이 쪽이 올바른 기본값입니다.",[14,11653,11655],{"id":11654},"관련-읽을거리","관련 읽을거리",[983,11657,11658,11663,11671,11676],{},[36,11659,11660,11662],{},[720,11661,1176],{"href":1175}," — CJK TTF 전반에 적용되는 일반 레시피",[36,11664,11665,2482,11668,11670],{},[720,11666,11667],{"href":4005},"gofpdf가 아카이브되었다. gpdf 마이그레이션 가이드",[44,11669,705],{},"에서 옮겨 오는 매핑",[36,11672,11673,11675],{},[720,11674,10308],{"href":3843}," — CJK 처리 관점의 비교",[36,11677,11678,2482,11682,11684],{},[720,11679,2481],{"href":11680,"rel":11681},"https://gpdf.dev/docs/guide/fonts",[724],[44,11683,50],{}," 완전 레퍼런스",[14,11686,11688],{"id":11687},"gpdf를-써-보기","gpdf를 써 보기",[19,11690,11691],{},"gpdf는 Go용 PDF 생성 라이브러리입니다. MIT, 외부 의존성 제로, 네이티브 CJK 지원.",[97,11693,11694],{"className":1208,"code":1209,"language":1210,"meta":102,"style":102},[44,11695,11696],{"__ignoreMap":102},[106,11697,11698,11700,11702],{"class":108,"line":109},[106,11699,101],{"class":116},[106,11701,1219],{"class":252},[106,11703,1222],{"class":252},[19,11705,11706,1230,11709],{},[720,11707,1229],{"href":1227,"rel":11708},[724],[720,11710,11713],{"href":11711,"rel":11712},"https://gpdf.dev/docs/quickstart",[724],"문서 보기",[1237,11715,11716],{},"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":102,"searchDepth":120,"depth":120,"links":11718},[11719,11720,11721,11722,11723,11724,11725,11726,11727,11728,11729],{"id":10385,"depth":120,"text":10386},{"id":10398,"depth":120,"text":10399},{"id":9082,"depth":120,"text":9083},{"id":10935,"depth":120,"text":10936},{"id":11003,"depth":120,"text":11004},{"id":11108,"depth":120,"text":11109},{"id":11356,"depth":120,"text":11357},{"id":11433,"depth":120,"text":11434},{"id":11482,"depth":120,"text":11483},{"id":11654,"depth":120,"text":11655},{"id":11687,"depth":120,"text":11688},"gpdf.WithFont에 static 버전 NotoSansJP-Regular.ttf를 등록합니다. variable 폰트를 쓰지 않는 이유와 17,000개 글리프가 PDF에서 40 KB 미만까지 줄어드는 서브셋 구조를 설명합니다.",{"name":11732,"totalTime":2527,"tools":11733,"steps":11735},"gpdf 문서에서 Noto Sans JP를 기본 폰트로 사용하기",[1260,11734],"NotoSansJP-Regular.ttf (Google Fonts의 static TTF)",[11736,11739,11742,11745],{"name":11737,"text":11738},"Google Fonts에서 static TTF 다운로드","fonts.google.com에서 Noto Sans JP zip을 받아 압축을 풀고 static/NotoSansJP-Regular.ttf를 선택합니다. zip 루트의 NotoSansJP-VariableFont_wght.ttf는 쓰지 않습니다.",{"name":11740,"text":11741},"시작 시점에 바이트로 읽기","os.ReadFile로 NotoSansJP-Regular.ttf를 읽습니다. 바이너리에 포함하고 싶다면 //go:embed를 씁니다.",{"name":11743,"text":11744},"문서 생성 시 폰트 등록","gpdf.NewDocument에 gpdf.WithFont(\"NotoSansJP\", fontBytes)와 gpdf.WithDefaultFont(\"NotoSansJP\", 11)을 전달합니다. AddUTF8Font도 파일 경로도 필요 없습니다.",{"name":11746,"text":11747},"일본어 텍스트 작성 후 PDF 생성","컬럼 안에서 c.Text(\"請求書\")를 호출합니다. doc.Generate()가 []byte를 반환하고, gpdf는 실제로 사용한 글리프만 서브셋으로 최종 PDF에 임베드합니다.",{},{"title":1186,"description":11730},"ko/blog/004.noto-sans-jp-with-gpdf",[1284,1286,2551],"hNM0iCUbRC1dUgryk9bj5q9wPeXGxWoSj3L4d8uNQ7c",{"id":11754,"title":11755,"author":11756,"body":11757,"date":16503,"description":16504,"draft":1254,"extension":1255,"howTo":16505,"image":1278,"meta":16528,"navigation":123,"path":4005,"seo":16529,"stem":16530,"tags":16531,"updated":1278,"__hash__":16532},"blogKo/ko/blog/001.gofpdf-migration.md","gofpdf이 보관됨. gpdf로 마이그레이션하는 완전 가이드",{"name":8,"url":9},{"type":11,"value":11758,"toc":16487},[11759,11761,11776,11786,11789,11800,11803,11807,11813,11816,11823,11830,11833,11837,11840,11866,11872,11876,11879,12144,12158,12185,12189,12192,12197,12370,12374,12755,12765,12778,12782,12792,12796,13532,13535,13539,13901,13918,13921,13925,13931,13935,14097,14110,14114,14558,14561,14571,14574,14577,14581,14600,14604,14919,14925,14929,15589,15604,15608,15621,15625,15887,15891,16210,16227,16231,16240,16317,16320,16323,16327,16330,16367,16370,16372,16378,16397,16406,16417,16426,16432,16434,16437,16449,16458,16462,16484],[14,11760,3933],{"id":3932},[19,11762,11763,11765,11766,11768,11769,11771,11772,11775],{},[39,11764,337],{},"는 순수 Go·외부 의존성 제로·네이티브 CJK 지원 PDF 라이브러리. ",[44,11767,705],{}," 같은 번거로운 절차도, ",[44,11770,4298],{},"로 좌표를 직접 다루는 일도 없다. Bootstrap 식 12-컬럼 그리드로 선언적으로 작성할 수 있고, 벤치마크에선 ",[39,11773,11774],{},"gofpdf보다 약 10배 빠름",". 마이그레이션은 대체로 \"명령형 커서 조작\"을 \"선언형 빌더\"로 바꾸는 작업이며, Before/After 다섯 쌍으로 전체 매핑을 살펴본다.",[19,11777,11778,11779,11782,11783,11785],{},"지난주 동료가 새 Go 프로젝트를 열고 ",[44,11780,11781],{},"go get github.com/jung-kurt/gofpdf","를 실행했다. 10분 뒤 GitHub 배너 스크린샷이 날아왔다: ",[39,11784,3971],{}," 그리고 한마디, \"잠깐, 포크도 아카이브된 거야?\"",[19,11787,11788],{},"맞다. 둘 다 그렇다.",[19,11790,11791,3943,11793,11796,11797,11799],{},[44,11792,3942],{},[39,11794,11795],{},"2021년 9월 8일"," 보관되었다. 커뮤니티 포크 ",[44,11798,3950],{},"의 마지막 릴리스는 2023년이고 2025년에 정식으로 보관되었다. Stack Overflow와 한국어 블로그의 Go PDF 관련 답변 대부분이 아직도 gofpdf를 가리키지만, 그건 4년 넘게 read-only 상태이고 후계자로 여겨지던 포크마저 사라졌다.",[19,11801,11802],{},"운영 중인 gofpdf 코드가 있다면 이 글은 마이그레이션 지도. 신규 프로젝트에서 검색 결과에 끌려 반사적으로 gofpdf를 집으려 했다면, 이 글이 그대로 대안이다.",[14,11804,11806],{"id":11805},"왜-gofpdf은-정말로-돌아오지-않는가","왜 gofpdf은 정말로 돌아오지 않는가",[19,11808,11809,11810,11812],{},"오픈소스가 반드시 죽는 건 아니다. 원래 메인테이너가 손을 뗐어도 다른 누가 이어받는 경우가 있다. gofpdf도 그렇게 될 줄 알았고, 한동안은 실제로 그랬다. ",[44,11811,3950],{},"는 코드를 정리하고, 오래된 버그를 고치고, PR을 받으며 \"진정한 후계\"처럼 보였다.",[19,11814,11815],{},"그러다 2025년 초 포크도 보관되었다. README에는 이렇게 쓰여 있다: \"이 프로젝트는 더 이상 적극적으로 유지되지 않습니다. 다른 라이브러리 사용을 고려해 주세요.\"",[19,11817,11818,11819,11822],{},"이유보다 결과가 중요하다. ",[39,11820,11821],{},"gofpdf에 의존하는 모든 Go 프로젝트는 지금 두 겹의 미유지 코드 위에 앉아 있다",". 보안 이슈는 패치되지 않는다. PDF 2.0 스펙은 2020년에 나왔지만 gofpdf는 대부분 따라잡지 못했다. Go 1.25의 루프 변수 동작은 지금은 gofpdf와 문제없이 작동하지만 내일 깨진다면 포크해서 고치는 건 당신 몫이다.",[19,11824,11825,11826,11829],{},"\"라이브러리에 버그가 있다\"는 문제가 아니라 ",[39,11827,11828],{},"공급망"," 문제다.",[19,11831,11832],{},"한국 팀엔 특히 민감한 지점이 있다. 전자세금계산서, 전자문서 원본성 인증, 공공기관 납품용 PDF/A 요건 등에서 미유지 라이브러리로 기술 스택을 정당화하기 어렵다.",[14,11834,11836],{"id":11835},"한국-팀이-gofpdf로-실제-하던-일","한국 팀이 gofpdf로 실제 하던 일",[19,11838,11839],{},"GitHub Issues와 한국 기술 블로그 글을 살피면 gofpdf의 주요 용도는 다음 네 가지다:",[33,11841,11842,11848,11854,11860],{},[36,11843,11844,11847],{},[39,11845,11846],{},"세금계산서·영수증·납품서"," — 헤더, 거래처, 명세표, 합계, 푸터",[36,11849,11850,11853],{},[39,11851,11852],{},"리포트"," — 헤더와 페이지 번호가 반복되는 다중 페이지 문서",[36,11855,11856,11859],{},[39,11857,11858],{},"증명서·양식"," — 템플릿 이미지 위에 고정 위치로 텍스트를 얹음",[36,11861,11862,11865],{},[39,11863,11864],{},"CJK 문서"," — 한국어·일본어·중국어 혼용 송장, 배송 라벨",[19,11867,11868,11869,11871],{},"앞의 세 가지는 gpdf 빌더 API로 바로 덮인다. 네 번째 CJK가 gpdf가 gofpdf 대비 가장 격차를 크게 두는 영역. gofpdf은 ",[44,11870,705],{},"를 호출하고 TTF 경로를 관리하며 기본 문자면 밖으로 글자가 나가지 않기를 기도해야 했다. gpdf는 CJK를 처음부터 일급 시민으로 다룬다 — TrueType 폰트를 등록하고 한국어를 쓰고, PDF가 나온다. 끝.",[14,11873,11875],{"id":11874},"api-매핑표","API 매핑표",[19,11877,11878],{},"아래 표가 치트시트. 이후 섹션에서 다섯 개의 구체적 Before/After를 본다.",[1892,11880,11881,11892],{},[1895,11882,11883],{},[1898,11884,11885,11888,11890],{},[1901,11886,11887],{},"하고 싶은 것",[1901,11889,5364],{},[1901,11891,337],{},[1908,11893,11894,11909,11930,11949,11966,11981,11999,12017,12032,12047,12065,12080,12095,12114,12129],{},[1898,11895,11896,11899,11904],{},[1913,11897,11898],{},"문서 생성",[1913,11900,11901],{},[44,11902,11903],{},"gofpdf.New(\"P\", \"mm\", \"A4\", \"\")",[1913,11905,11906],{},[44,11907,11908],{},"gpdf.NewDocument(gpdf.WithPageSize(document.A4))",[1898,11910,11911,11914,11919],{},[1913,11912,11913],{},"페이지 추가",[1913,11915,11916],{},[44,11917,11918],{},"pdf.AddPage()",[1913,11920,11921,42,11924],{},[44,11922,11923],{},"doc.AddPage()",[784,11925,246,11926,11929],{},[44,11927,11928],{},"*PageBuilder"," 반환)",[1898,11931,11932,11935,11940],{},[1913,11933,11934],{},"폰트 지정",[1913,11936,11937],{},[44,11938,11939],{},"pdf.SetFont(\"Arial\", \"B\", 16)",[1913,11941,11942,10990,11944,10990,11946],{},[44,11943,9825],{},[44,11945,1322],{},[44,11947,11948],{},"template.FontSize(16)",[1898,11950,11951,11954,11959],{},[1913,11952,11953],{},"TTF 등록 (CJK)",[1913,11955,11956],{},[44,11957,11958],{},"pdf.AddUTF8Font(\"noto\", \"\", \"NotoSansKR-Regular.ttf\")",[1913,11960,11961,42,11963],{},[44,11962,8880],{},[784,11964,11965],{},"(생성 시 전달)",[1898,11967,11968,11971,11976],{},[1913,11969,11970],{},"한 줄 텍스트",[1913,11972,11973],{},[44,11974,11975],{},"pdf.Cell(40, 10, \"hi\")",[1913,11977,11978],{},[44,11979,11980],{},"c.Text(\"hi\")",[1898,11982,11983,11986,11991],{},[1913,11984,11985],{},"자동 줄바꿈 텍스트",[1913,11987,11988],{},[44,11989,11990],{},"pdf.MultiCell(0, 10, body, \"\", \"L\", false)",[1913,11992,11993,42,11996],{},[44,11994,11995],{},"c.Text(body)",[784,11997,11998],{},"(자동 줄바꿈)",[1898,12000,12001,12004,12009],{},[1913,12002,12003],{},"텍스트 색상",[1913,12005,12006],{},[44,12007,12008],{},"pdf.SetTextColor(255, 0, 0)",[1913,12010,12011,42,12014],{},[44,12012,12013],{},"template.TextColor(pdf.Red)",[784,12015,12016],{},"(텍스트별 옵션)",[1898,12018,12019,12022,12027],{},[1913,12020,12021],{},"가로선",[1913,12023,12024],{},[44,12025,12026],{},"pdf.Line(x1, y1, x2, y2)",[1913,12028,12029],{},[44,12030,12031],{},"c.Line(template.LineThickness(document.Pt(1)))",[1898,12033,12034,12037,12042],{},[1913,12035,12036],{},"이미지 삽입",[1913,12038,12039],{},[44,12040,12041],{},"pdf.ImageOptions(\"logo.png\", x, y, w, h, ...)",[1913,12043,12044],{},[44,12045,12046],{},"c.Image(imgBytes, template.FitWidth(document.Mm(50)))",[1898,12048,12049,12052,12057],{},[1913,12050,12051],{},"커서 위치",[1913,12053,12054],{},[44,12055,12056],{},"pdf.SetXY(x, y)",[1913,12058,12059],{},[784,12060,12061,12062,495],{},"(없음 — 행/열로 작성, 또는 ",[44,12063,12064],{},"page.Absolute(x, y, fn)",[1898,12066,12067,12070,12075],{},[1913,12068,12069],{},"모든 페이지 헤더",[1913,12071,12072],{},[44,12073,12074],{},"pdf.SetHeaderFunc(fn)",[1913,12076,12077],{},[44,12078,12079],{},"doc.Header(fn)",[1898,12081,12082,12085,12090],{},[1913,12083,12084],{},"모든 페이지 푸터",[1913,12086,12087],{},[44,12088,12089],{},"pdf.SetFooterFunc(fn)",[1913,12091,12092],{},[44,12093,12094],{},"doc.Footer(fn)",[1898,12096,12097,12100,12106],{},[1913,12098,12099],{},"페이지 번호",[1913,12101,12102,12105],{},[44,12103,12104],{},"pdf.PageNo()","(수동)",[1913,12107,12108,10990,12111],{},[44,12109,12110],{},"c.PageNumber()",[44,12112,12113],{},"c.TotalPages()",[1898,12115,12116,12119,12124],{},[1913,12117,12118],{},"파일로 출력",[1913,12120,12121],{},[44,12122,12123],{},"pdf.OutputFileAndClose(\"out.pdf\")",[1913,12125,12126],{},[44,12127,12128],{},"data, _ := doc.Generate(); os.WriteFile(\"out.pdf\", data, 0o644)",[1898,12130,12131,12134,12139],{},[1913,12132,12133],{},"io.Writer로 출력",[1913,12135,12136],{},[44,12137,12138],{},"pdf.Output(w)",[1913,12140,12141],{},[44,12142,12143],{},"doc.Render(w)",[19,12145,12146,12147,12150,12151,12154,12155,12157],{},"가장 큰 차이는 API 형태다. gofpdf는 ",[39,12148,12149],{},"명령형",", gpdf는 ",[39,12152,12153],{},"선언형",". gofpdf는 커서를 옮기며 쓴다. gpdf는 행과 열의 트리를 기술하고 레이아웃 엔진이 배치한다. 초반 몇 개는 gpdf 쪽이 더 길게 느껴진다. 세 번째쯤 되면 ",[44,12156,4298],{},"가 그리워지지 않는다.",[19,12159,12160,12161,10990,12164,10990,12167,12170,12171,2567,12174,2567,12177,12180,12181,12184],{},"단위 얘기. gofpdf는 생성 시 기본 단위(",[44,12162,12163],{},"\"mm\"",[44,12165,12166],{},"\"pt\"",[44,12168,12169],{},"\"in\"",")를 고른다. gpdf는 내부적으로 전부 pt로 고정하고, 호출부에서 ",[44,12172,12173],{},"document.Mm(20)",[44,12175,12176],{},"document.Pt(12)",[44,12178,12179],{},"document.Cm(1)"," 같은 헬퍼를 쓴다. CSS 감각에 가까운데, 헤더 여백을 ",[44,12182,12183],{},"document.Mm(15)","로 잡고 난 뒤로는 단위를 의식하지 않게 된다.",[14,12186,12188],{"id":12187},"before-after-1-가장-단순한-pdf","Before / After 1: 가장 단순한 PDF",[19,12190,12191],{},"\"hello world\" 쌍. gofpdf의 간결함이 인기의 이유였다. gpdf 버전은 몇 줄 더 길다 — 커서를 움직이는 게 아니라 트리를 만들기 때문.",[19,12193,12194],{},[39,12195,12196],{},"Before — gofpdf:",[97,12198,12200],{"className":99,"code":12199,"language":101,"meta":102,"style":102},"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",[44,12201,12202,12208,12212,12223,12227,12237,12280,12290,12321,12347,12366],{"__ignoreMap":102},[106,12203,12204,12206],{"class":108,"line":109},[106,12205,113],{"class":112},[106,12207,117],{"class":116},[106,12209,12210],{"class":108,"line":120},[106,12211,124],{"emptyLinePlaceholder":123},[106,12213,12214,12216,12218,12221],{"class":108,"line":127},[106,12215,131],{"class":130},[106,12217,4727],{"class":112},[106,12219,12220],{"class":116},"github.com/jung-kurt/gofpdf",[106,12222,146],{"class":112},[106,12224,12225],{"class":108,"line":137},[106,12226,124],{"emptyLinePlaceholder":123},[106,12228,12229,12231,12233,12235],{"class":108,"line":149},[106,12230,208],{"class":112},[106,12232,212],{"class":211},[106,12234,215],{"class":112},[106,12236,218],{"class":112},[106,12238,12239,12241,12243,12246,12248,12250,12252,12254,12256,12258,12260,12262,12264,12266,12268,12270,12272,12274,12276,12278],{"class":108,"line":159},[106,12240,4703],{"class":224},[106,12242,234],{"class":112},[106,12244,12245],{"class":224}," gofpdf",[106,12247,240],{"class":112},[106,12249,4713],{"class":211},[106,12251,246],{"class":112},[106,12253,249],{"class":112},[106,12255,4720],{"class":252},[106,12257,249],{"class":112},[106,12259,228],{"class":112},[106,12261,4727],{"class":112},[106,12263,4730],{"class":252},[106,12265,249],{"class":112},[106,12267,228],{"class":112},[106,12269,4727],{"class":112},[106,12271,342],{"class":252},[106,12273,249],{"class":112},[106,12275,228],{"class":112},[106,12277,4745],{"class":112},[106,12279,197],{"class":112},[106,12281,12282,12284,12286,12288],{"class":108,"line":164},[106,12283,4752],{"class":224},[106,12285,240],{"class":112},[106,12287,460],{"class":211},[106,12289,463],{"class":112},[106,12291,12292,12294,12296,12298,12300,12302,12304,12306,12308,12310,12312,12314,12316,12319],{"class":108,"line":174},[106,12293,4752],{"class":224},[106,12295,240],{"class":112},[106,12297,4767],{"class":211},[106,12299,246],{"class":112},[106,12301,249],{"class":112},[106,12303,4774],{"class":252},[106,12305,249],{"class":112},[106,12307,228],{"class":112},[106,12309,4727],{"class":112},[106,12311,4783],{"class":252},[106,12313,249],{"class":112},[106,12315,228],{"class":112},[106,12317,12318],{"class":379}," 24",[106,12320,197],{"class":112},[106,12322,12323,12325,12327,12329,12331,12333,12335,12337,12339,12341,12343,12345],{"class":108,"line":184},[106,12324,4752],{"class":224},[106,12326,240],{"class":112},[106,12328,4801],{"class":211},[106,12330,246],{"class":112},[106,12332,4806],{"class":379},[106,12334,228],{"class":112},[106,12336,4811],{"class":379},[106,12338,228],{"class":112},[106,12340,4727],{"class":112},[106,12342,4818],{"class":252},[106,12344,249],{"class":112},[106,12346,197],{"class":112},[106,12348,12349,12351,12353,12356,12358,12360,12362,12364],{"class":108,"line":194},[106,12350,4752],{"class":224},[106,12352,240],{"class":112},[106,12354,12355],{"class":211},"OutputFileAndClose",[106,12357,246],{"class":112},[106,12359,249],{"class":112},[106,12361,650],{"class":252},[106,12363,249],{"class":112},[106,12365,197],{"class":112},[106,12367,12368],{"class":108,"line":200},[106,12369,699],{"class":112},[19,12371,12372],{},[39,12373,4936],{},[97,12375,12377],{"className":99,"code":12376,"language":101,"meta":102,"style":102},"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",[44,12378,12379,12385,12389,12395,12403,12411,12415,12423,12431,12439,12443,12447,12457,12471,12489,12519,12523,12527,12541,12565,12595,12633,12637,12641,12645,12663,12675,12689,12693,12733,12747,12751],{"__ignoreMap":102},[106,12380,12381,12383],{"class":108,"line":109},[106,12382,113],{"class":112},[106,12384,117],{"class":116},[106,12386,12387],{"class":108,"line":120},[106,12388,124],{"emptyLinePlaceholder":123},[106,12390,12391,12393],{"class":108,"line":127},[106,12392,131],{"class":130},[106,12394,134],{"class":112},[106,12396,12397,12399,12401],{"class":108,"line":137},[106,12398,140],{"class":112},[106,12400,143],{"class":116},[106,12402,146],{"class":112},[106,12404,12405,12407,12409],{"class":108,"line":149},[106,12406,140],{"class":112},[106,12408,154],{"class":116},[106,12410,146],{"class":112},[106,12412,12413],{"class":108,"line":159},[106,12414,124],{"emptyLinePlaceholder":123},[106,12416,12417,12419,12421],{"class":108,"line":164},[106,12418,140],{"class":112},[106,12420,169],{"class":116},[106,12422,146],{"class":112},[106,12424,12425,12427,12429],{"class":108,"line":174},[106,12426,140],{"class":112},[106,12428,179],{"class":116},[106,12430,146],{"class":112},[106,12432,12433,12435,12437],{"class":108,"line":184},[106,12434,140],{"class":112},[106,12436,189],{"class":116},[106,12438,146],{"class":112},[106,12440,12441],{"class":108,"line":194},[106,12442,197],{"class":112},[106,12444,12445],{"class":108,"line":200},[106,12446,124],{"emptyLinePlaceholder":123},[106,12448,12449,12451,12453,12455],{"class":108,"line":205},[106,12450,208],{"class":112},[106,12452,212],{"class":211},[106,12454,215],{"class":112},[106,12456,218],{"class":112},[106,12458,12459,12461,12463,12465,12467,12469],{"class":108,"line":221},[106,12460,308],{"class":224},[106,12462,234],{"class":112},[106,12464,313],{"class":224},[106,12466,240],{"class":112},[106,12468,318],{"class":211},[106,12470,321],{"class":112},[106,12472,12473,12475,12477,12479,12481,12483,12485,12487],{"class":108,"line":260},[106,12474,327],{"class":224},[106,12476,240],{"class":112},[106,12478,332],{"class":211},[106,12480,246],{"class":112},[106,12482,360],{"class":224},[106,12484,240],{"class":112},[106,12486,342],{"class":224},[106,12488,345],{"class":112},[106,12490,12491,12493,12495,12497,12499,12501,12503,12505,12507,12509,12511,12513,12515,12517],{"class":108,"line":276},[106,12492,327],{"class":224},[106,12494,240],{"class":112},[106,12496,355],{"class":211},[106,12498,246],{"class":112},[106,12500,360],{"class":224},[106,12502,240],{"class":112},[106,12504,365],{"class":211},[106,12506,246],{"class":112},[106,12508,360],{"class":224},[106,12510,240],{"class":112},[106,12512,374],{"class":211},[106,12514,246],{"class":112},[106,12516,380],{"class":379},[106,12518,383],{"class":112},[106,12520,12521],{"class":108,"line":294},[106,12522,439],{"class":112},[106,12524,12525],{"class":108,"line":300},[106,12526,124],{"emptyLinePlaceholder":123},[106,12528,12529,12531,12533,12535,12537,12539],{"class":108,"line":305},[106,12530,450],{"class":224},[106,12532,234],{"class":112},[106,12534,455],{"class":224},[106,12536,240],{"class":112},[106,12538,460],{"class":211},[106,12540,463],{"class":112},[106,12542,12543,12545,12547,12549,12551,12553,12555,12557,12559,12561,12563],{"class":108,"line":324},[106,12544,469],{"class":224},[106,12546,240],{"class":112},[106,12548,474],{"class":211},[106,12550,477],{"class":112},[106,12552,481],{"class":480},[106,12554,484],{"class":112},[106,12556,487],{"class":116},[106,12558,240],{"class":112},[106,12560,492],{"class":116},[106,12562,495],{"class":112},[106,12564,218],{"class":112},[106,12566,12567,12569,12571,12573,12575,12577,12579,12581,12583,12585,12587,12589,12591,12593],{"class":108,"line":348},[106,12568,503],{"class":224},[106,12570,240],{"class":112},[106,12572,508],{"class":211},[106,12574,246],{"class":112},[106,12576,513],{"class":379},[106,12578,228],{"class":112},[106,12580,518],{"class":112},[106,12582,521],{"class":480},[106,12584,484],{"class":112},[106,12586,487],{"class":116},[106,12588,240],{"class":112},[106,12590,530],{"class":116},[106,12592,495],{"class":112},[106,12594,218],{"class":112},[106,12596,12597,12599,12601,12603,12605,12607,12609,12611,12613,12615,12617,12619,12621,12623,12625,12627,12629,12631],{"class":108,"line":386},[106,12598,540],{"class":224},[106,12600,240],{"class":112},[106,12602,545],{"class":211},[106,12604,246],{"class":112},[106,12606,249],{"class":112},[106,12608,4818],{"class":252},[106,12610,249],{"class":112},[106,12612,228],{"class":112},[106,12614,1677],{"class":224},[106,12616,240],{"class":112},[106,12618,1682],{"class":211},[106,12620,246],{"class":112},[106,12622,1687],{"class":379},[106,12624,1690],{"class":112},[106,12626,1677],{"class":224},[106,12628,240],{"class":112},[106,12630,1697],{"class":211},[106,12632,1700],{"class":112},[106,12634,12635],{"class":108,"line":411},[106,12636,562],{"class":112},[106,12638,12639],{"class":108,"line":436},[106,12640,568],{"class":112},[106,12642,12643],{"class":108,"line":442},[106,12644,124],{"emptyLinePlaceholder":123},[106,12646,12647,12649,12651,12653,12655,12657,12659,12661],{"class":108,"line":447},[106,12648,579],{"class":224},[106,12650,228],{"class":112},[106,12652,231],{"class":224},[106,12654,234],{"class":112},[106,12656,455],{"class":224},[106,12658,240],{"class":112},[106,12660,592],{"class":211},[106,12662,463],{"class":112},[106,12664,12665,12667,12669,12671,12673],{"class":108,"line":466},[106,12666,263],{"class":130},[106,12668,231],{"class":224},[106,12670,268],{"class":112},[106,12672,271],{"class":112},[106,12674,218],{"class":112},[106,12676,12677,12679,12681,12683,12685,12687],{"class":108,"line":500},[106,12678,279],{"class":224},[106,12680,240],{"class":112},[106,12682,284],{"class":211},[106,12684,246],{"class":112},[106,12686,289],{"class":224},[106,12688,197],{"class":112},[106,12690,12691],{"class":108,"line":537},[106,12692,297],{"class":112},[106,12694,12695,12697,12699,12701,12703,12705,12707,12709,12711,12713,12715,12717,12719,12721,12723,12725,12727,12729,12731],{"class":108,"line":559},[106,12696,263],{"class":130},[106,12698,231],{"class":224},[106,12700,234],{"class":112},[106,12702,237],{"class":224},[106,12704,240],{"class":112},[106,12706,643],{"class":211},[106,12708,246],{"class":112},[106,12710,249],{"class":112},[106,12712,650],{"class":252},[106,12714,249],{"class":112},[106,12716,228],{"class":112},[106,12718,657],{"class":224},[106,12720,228],{"class":112},[106,12722,662],{"class":379},[106,12724,665],{"class":112},[106,12726,231],{"class":224},[106,12728,268],{"class":112},[106,12730,271],{"class":112},[106,12732,218],{"class":112},[106,12734,12735,12737,12739,12741,12743,12745],{"class":108,"line":565},[106,12736,279],{"class":224},[106,12738,240],{"class":112},[106,12740,284],{"class":211},[106,12742,246],{"class":112},[106,12744,289],{"class":224},[106,12746,197],{"class":112},[106,12748,12749],{"class":108,"line":571},[106,12750,297],{"class":112},[106,12752,12753],{"class":108,"line":576},[106,12754,699],{"class":112},[19,12756,12757,12758,12760,12761,12764],{},"그리드가 일을 한다. ",[44,12759,474],{},"는 내용 높이로 결정되는 행을 추가하고 ",[44,12762,12763],{},"r.Col(12, ...)","는 \"12 그리드 전체를 차지하는 컬럼\"을 뜻한다. Bootstrap과 같은 발상을 PDF 페이지에 적용한 것뿐.",[19,12766,12767,12769,12770,12773,12774,12777],{},[44,12768,1146],{},"는 바이트 슬라이스를 돌려준다. ",[44,12771,12772],{},"io.Writer","로 흘려 보내고 싶다면 ",[44,12775,12776],{},"Render(w)",". \"파일을 닫는\" 단계가 없는 건 gpdf가 파일 핸들을 소유하지 않기 때문.",[14,12779,12781],{"id":12780},"before-after-2-명세표","Before / After 2: 명세표",[19,12783,12784,12785,12787,12788,12791],{},"세금계산서 명세표는 gofpdf가 가장 수다스러워지는 지점. 내장 테이블이 없어서 ",[44,12786,4801],{},"을 중첩 루프로 호출하고, 열 너비는 직접 계산하고, ",[44,12789,12790],{},"Ln(-1)","로 개행한다. gofpdf 세금계산서 튜토리얼 글의 절반은 테이블 보일러플레이트로 채워져 있다.",[19,12793,12794],{},[39,12795,12196],{},[97,12797,12799],{"className":99,"code":12798,"language":101,"meta":102,"style":102},"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",[44,12800,12801,12831,12856,12924,12983,13042,13101,13105,13131,13145,13185,13223,13263,13267,13290,13352,13410,13469,13528],{"__ignoreMap":102},[106,12802,12803,12805,12807,12809,12811,12813,12815,12817,12819,12821,12823,12825,12827,12829],{"class":108,"line":109},[106,12804,8707],{"class":224},[106,12806,240],{"class":112},[106,12808,4767],{"class":211},[106,12810,246],{"class":112},[106,12812,249],{"class":112},[106,12814,4774],{"class":252},[106,12816,249],{"class":112},[106,12818,228],{"class":112},[106,12820,4727],{"class":112},[106,12822,4783],{"class":252},[106,12824,249],{"class":112},[106,12826,228],{"class":112},[106,12828,6324],{"class":379},[106,12830,197],{"class":112},[106,12832,12833,12835,12837,12840,12842,12845,12847,12850,12852,12854],{"class":108,"line":120},[106,12834,8707],{"class":224},[106,12836,240],{"class":112},[106,12838,12839],{"class":211},"SetFillColor",[106,12841,246],{"class":112},[106,12843,12844],{"class":379},"220",[106,12846,228],{"class":112},[106,12848,12849],{"class":379}," 220",[106,12851,228],{"class":112},[106,12853,12849],{"class":379},[106,12855,197],{"class":112},[106,12857,12858,12860,12862,12865,12867,12870,12872,12875,12877,12879,12882,12884,12886,12889,12892,12894,12896,12899,12901,12903,12906,12908,12910,12914,12916,12918,12920,12922],{"class":108,"line":127},[106,12859,8707],{"class":224},[106,12861,240],{"class":112},[106,12863,12864],{"class":211},"CellFormat",[106,12866,246],{"class":112},[106,12868,12869],{"class":379},"80",[106,12871,228],{"class":112},[106,12873,12874],{"class":379}," 8",[106,12876,228],{"class":112},[106,12878,4727],{"class":112},[106,12880,12881],{"class":252},"품목",[106,12883,249],{"class":112},[106,12885,228],{"class":112},[106,12887,12888],{"class":112},"   \"",[106,12890,12891],{"class":252},"1",[106,12893,249],{"class":112},[106,12895,228],{"class":112},[106,12897,12898],{"class":379}," 0",[106,12900,228],{"class":112},[106,12902,4727],{"class":112},[106,12904,12905],{"class":252},"L",[106,12907,249],{"class":112},[106,12909,228],{"class":112},[106,12911,12913],{"class":12912},"sfNiH"," true",[106,12915,228],{"class":112},[106,12917,12898],{"class":379},[106,12919,228],{"class":112},[106,12921,4745],{"class":112},[106,12923,197],{"class":112},[106,12925,12926,12928,12930,12932,12934,12936,12938,12940,12942,12944,12946,12948,12950,12952,12954,12956,12958,12960,12962,12964,12967,12969,12971,12973,12975,12977,12979,12981],{"class":108,"line":137},[106,12927,8707],{"class":224},[106,12929,240],{"class":112},[106,12931,12864],{"class":211},[106,12933,246],{"class":112},[106,12935,380],{"class":379},[106,12937,228],{"class":112},[106,12939,12874],{"class":379},[106,12941,228],{"class":112},[106,12943,4727],{"class":112},[106,12945,8479],{"class":252},[106,12947,249],{"class":112},[106,12949,228],{"class":112},[106,12951,12888],{"class":112},[106,12953,12891],{"class":252},[106,12955,249],{"class":112},[106,12957,228],{"class":112},[106,12959,12898],{"class":379},[106,12961,228],{"class":112},[106,12963,4727],{"class":112},[106,12965,12966],{"class":252},"C",[106,12968,249],{"class":112},[106,12970,228],{"class":112},[106,12972,12913],{"class":12912},[106,12974,228],{"class":112},[106,12976,12898],{"class":379},[106,12978,228],{"class":112},[106,12980,4745],{"class":112},[106,12982,197],{"class":112},[106,12984,12985,12987,12989,12991,12993,12995,12997,12999,13001,13003,13005,13007,13009,13011,13013,13015,13017,13019,13021,13023,13026,13028,13030,13032,13034,13036,13038,13040],{"class":108,"line":149},[106,12986,8707],{"class":224},[106,12988,240],{"class":112},[106,12990,12864],{"class":211},[106,12992,246],{"class":112},[106,12994,3668],{"class":379},[106,12996,228],{"class":112},[106,12998,12874],{"class":379},[106,13000,228],{"class":112},[106,13002,4727],{"class":112},[106,13004,8488],{"class":252},[106,13006,249],{"class":112},[106,13008,228],{"class":112},[106,13010,12888],{"class":112},[106,13012,12891],{"class":252},[106,13014,249],{"class":112},[106,13016,228],{"class":112},[106,13018,12898],{"class":379},[106,13020,228],{"class":112},[106,13022,4727],{"class":112},[106,13024,13025],{"class":252},"R",[106,13027,249],{"class":112},[106,13029,228],{"class":112},[106,13031,12913],{"class":12912},[106,13033,228],{"class":112},[106,13035,12898],{"class":379},[106,13037,228],{"class":112},[106,13039,4745],{"class":112},[106,13041,197],{"class":112},[106,13043,13044,13046,13048,13050,13052,13054,13056,13058,13060,13062,13064,13066,13068,13070,13072,13074,13076,13079,13081,13083,13085,13087,13089,13091,13093,13095,13097,13099],{"class":108,"line":159},[106,13045,8707],{"class":224},[106,13047,240],{"class":112},[106,13049,12864],{"class":211},[106,13051,246],{"class":112},[106,13053,3668],{"class":379},[106,13055,228],{"class":112},[106,13057,12874],{"class":379},[106,13059,228],{"class":112},[106,13061,4727],{"class":112},[106,13063,8497],{"class":252},[106,13065,249],{"class":112},[106,13067,228],{"class":112},[106,13069,12888],{"class":112},[106,13071,12891],{"class":252},[106,13073,249],{"class":112},[106,13075,228],{"class":112},[106,13077,13078],{"class":379}," 1",[106,13080,228],{"class":112},[106,13082,4727],{"class":112},[106,13084,13025],{"class":252},[106,13086,249],{"class":112},[106,13088,228],{"class":112},[106,13090,12913],{"class":12912},[106,13092,228],{"class":112},[106,13094,12898],{"class":379},[106,13096,228],{"class":112},[106,13098,4745],{"class":112},[106,13100,197],{"class":112},[106,13102,13103],{"class":108,"line":164},[106,13104,124],{"emptyLinePlaceholder":123},[106,13106,13107,13109,13111,13113,13115,13117,13119,13121,13123,13125,13127,13129],{"class":108,"line":174},[106,13108,8707],{"class":224},[106,13110,240],{"class":112},[106,13112,4767],{"class":211},[106,13114,246],{"class":112},[106,13116,249],{"class":112},[106,13118,4774],{"class":252},[106,13120,249],{"class":112},[106,13122,228],{"class":112},[106,13124,4745],{"class":112},[106,13126,228],{"class":112},[106,13128,6324],{"class":379},[106,13130,197],{"class":112},[106,13132,13133,13136,13138,13141,13143],{"class":108,"line":184},[106,13134,13135],{"class":224},"items ",[106,13137,234],{"class":112},[106,13139,13140],{"class":112}," [][]",[106,13142,8462],{"class":8461},[106,13144,8512],{"class":112},[106,13146,13147,13150,13152,13154,13156,13158,13160,13163,13165,13167,13169,13172,13174,13176,13178,13181,13183],{"class":108,"line":194},[106,13148,13149],{"class":112},"    {",[106,13151,249],{"class":112},[106,13153,8522],{"class":252},[106,13155,249],{"class":112},[106,13157,228],{"class":112},[106,13159,4727],{"class":112},[106,13161,13162],{"class":252},"40h",[106,13164,249],{"class":112},[106,13166,228],{"class":112},[106,13168,4727],{"class":112},[106,13170,13171],{"class":252},"₩180,000",[106,13173,249],{"class":112},[106,13175,228],{"class":112},[106,13177,4727],{"class":112},[106,13179,13180],{"class":252},"₩7,200,000",[106,13182,249],{"class":112},[106,13184,8502],{"class":112},[106,13186,13187,13189,13191,13193,13195,13197,13199,13202,13204,13206,13208,13210,13212,13214,13216,13219,13221],{"class":108,"line":200},[106,13188,13149],{"class":112},[106,13190,249],{"class":112},[106,13192,8562],{"class":252},[106,13194,249],{"class":112},[106,13196,228],{"class":112},[106,13198,140],{"class":112},[106,13200,13201],{"class":252},"60h",[106,13203,249],{"class":112},[106,13205,228],{"class":112},[106,13207,4727],{"class":112},[106,13209,13171],{"class":252},[106,13211,249],{"class":112},[106,13213,228],{"class":112},[106,13215,4727],{"class":112},[106,13217,13218],{"class":252},"₩10,800,000",[106,13220,249],{"class":112},[106,13222,8502],{"class":112},[106,13224,13225,13227,13229,13231,13233,13235,13238,13241,13243,13245,13247,13250,13252,13254,13256,13259,13261],{"class":108,"line":205},[106,13226,13149],{"class":112},[106,13228,249],{"class":112},[106,13230,8602],{"class":252},[106,13232,249],{"class":112},[106,13234,228],{"class":112},[106,13236,13237],{"class":112},"      \"",[106,13239,13240],{"class":252},"20h",[106,13242,249],{"class":112},[106,13244,228],{"class":112},[106,13246,4727],{"class":112},[106,13248,13249],{"class":252},"₩150,000",[106,13251,249],{"class":112},[106,13253,228],{"class":112},[106,13255,4727],{"class":112},[106,13257,13258],{"class":252},"₩3,000,000",[106,13260,249],{"class":112},[106,13262,8502],{"class":112},[106,13264,13265],{"class":108,"line":221},[106,13266,699],{"class":112},[106,13268,13269,13272,13275,13277,13280,13282,13285,13288],{"class":108,"line":260},[106,13270,13271],{"class":130},"for",[106,13273,13274],{"class":224}," _",[106,13276,228],{"class":112},[106,13278,13279],{"class":224}," row ",[106,13281,234],{"class":112},[106,13283,13284],{"class":130}," range",[106,13286,13287],{"class":224}," items ",[106,13289,8512],{"class":112},[106,13291,13292,13294,13296,13298,13300,13302,13304,13306,13308,13311,13314,13316,13319,13321,13323,13325,13327,13329,13331,13333,13335,13337,13339,13342,13344,13346,13348,13350],{"class":108,"line":276},[106,13293,4752],{"class":224},[106,13295,240],{"class":112},[106,13297,12864],{"class":211},[106,13299,246],{"class":112},[106,13301,12869],{"class":379},[106,13303,228],{"class":112},[106,13305,12874],{"class":379},[106,13307,228],{"class":112},[106,13309,13310],{"class":224}," row",[106,13312,13313],{"class":112},"[",[106,13315,7562],{"class":379},[106,13317,13318],{"class":112},"],",[106,13320,4727],{"class":112},[106,13322,12891],{"class":252},[106,13324,249],{"class":112},[106,13326,228],{"class":112},[106,13328,12898],{"class":379},[106,13330,228],{"class":112},[106,13332,4727],{"class":112},[106,13334,12905],{"class":252},[106,13336,249],{"class":112},[106,13338,228],{"class":112},[106,13340,13341],{"class":12912}," false",[106,13343,228],{"class":112},[106,13345,12898],{"class":379},[106,13347,228],{"class":112},[106,13349,4745],{"class":112},[106,13351,197],{"class":112},[106,13353,13354,13356,13358,13360,13362,13364,13366,13368,13370,13372,13374,13376,13378,13380,13382,13384,13386,13388,13390,13392,13394,13396,13398,13400,13402,13404,13406,13408],{"class":108,"line":294},[106,13355,4752],{"class":224},[106,13357,240],{"class":112},[106,13359,12864],{"class":211},[106,13361,246],{"class":112},[106,13363,380],{"class":379},[106,13365,228],{"class":112},[106,13367,12874],{"class":379},[106,13369,228],{"class":112},[106,13371,13310],{"class":224},[106,13373,13313],{"class":112},[106,13375,12891],{"class":379},[106,13377,13318],{"class":112},[106,13379,4727],{"class":112},[106,13381,12891],{"class":252},[106,13383,249],{"class":112},[106,13385,228],{"class":112},[106,13387,12898],{"class":379},[106,13389,228],{"class":112},[106,13391,4727],{"class":112},[106,13393,12966],{"class":252},[106,13395,249],{"class":112},[106,13397,228],{"class":112},[106,13399,13341],{"class":12912},[106,13401,228],{"class":112},[106,13403,12898],{"class":379},[106,13405,228],{"class":112},[106,13407,4745],{"class":112},[106,13409,197],{"class":112},[106,13411,13412,13414,13416,13418,13420,13422,13424,13426,13428,13430,13432,13435,13437,13439,13441,13443,13445,13447,13449,13451,13453,13455,13457,13459,13461,13463,13465,13467],{"class":108,"line":300},[106,13413,4752],{"class":224},[106,13415,240],{"class":112},[106,13417,12864],{"class":211},[106,13419,246],{"class":112},[106,13421,3668],{"class":379},[106,13423,228],{"class":112},[106,13425,12874],{"class":379},[106,13427,228],{"class":112},[106,13429,13310],{"class":224},[106,13431,13313],{"class":112},[106,13433,13434],{"class":379},"2",[106,13436,13318],{"class":112},[106,13438,4727],{"class":112},[106,13440,12891],{"class":252},[106,13442,249],{"class":112},[106,13444,228],{"class":112},[106,13446,12898],{"class":379},[106,13448,228],{"class":112},[106,13450,4727],{"class":112},[106,13452,13025],{"class":252},[106,13454,249],{"class":112},[106,13456,228],{"class":112},[106,13458,13341],{"class":12912},[106,13460,228],{"class":112},[106,13462,12898],{"class":379},[106,13464,228],{"class":112},[106,13466,4745],{"class":112},[106,13468,197],{"class":112},[106,13470,13471,13473,13475,13477,13479,13481,13483,13485,13487,13489,13491,13494,13496,13498,13500,13502,13504,13506,13508,13510,13512,13514,13516,13518,13520,13522,13524,13526],{"class":108,"line":305},[106,13472,4752],{"class":224},[106,13474,240],{"class":112},[106,13476,12864],{"class":211},[106,13478,246],{"class":112},[106,13480,3668],{"class":379},[106,13482,228],{"class":112},[106,13484,12874],{"class":379},[106,13486,228],{"class":112},[106,13488,13310],{"class":224},[106,13490,13313],{"class":112},[106,13492,13493],{"class":379},"3",[106,13495,13318],{"class":112},[106,13497,4727],{"class":112},[106,13499,12891],{"class":252},[106,13501,249],{"class":112},[106,13503,228],{"class":112},[106,13505,13078],{"class":379},[106,13507,228],{"class":112},[106,13509,4727],{"class":112},[106,13511,13025],{"class":252},[106,13513,249],{"class":112},[106,13515,228],{"class":112},[106,13517,13341],{"class":12912},[106,13519,228],{"class":112},[106,13521,12898],{"class":379},[106,13523,228],{"class":112},[106,13525,4745],{"class":112},[106,13527,197],{"class":112},[106,13529,13530],{"class":108,"line":324},[106,13531,699],{"class":112},[19,13533,13534],{},"머릿속에서 열 너비를 계산해 가며 써야 한다. 품목명이 줄바꿈되면 망가진다.",[19,13536,13537],{},[39,13538,4936],{},[97,13540,13542],{"className":99,"code":13541,"language":101,"meta":102,"style":102},"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",[44,13543,13544,13568,13598,13608,13649,13658,13695,13731,13767,13772,13799,13809,13819,13837,13859,13864,13888,13893,13897],{"__ignoreMap":102},[106,13545,13546,13548,13550,13552,13554,13556,13558,13560,13562,13564,13566],{"class":108,"line":109},[106,13547,849],{"class":224},[106,13549,240],{"class":112},[106,13551,474],{"class":211},[106,13553,477],{"class":112},[106,13555,481],{"class":480},[106,13557,484],{"class":112},[106,13559,487],{"class":116},[106,13561,240],{"class":112},[106,13563,492],{"class":116},[106,13565,495],{"class":112},[106,13567,218],{"class":112},[106,13569,13570,13572,13574,13576,13578,13580,13582,13584,13586,13588,13590,13592,13594,13596],{"class":108,"line":120},[106,13571,874],{"class":224},[106,13573,240],{"class":112},[106,13575,508],{"class":211},[106,13577,246],{"class":112},[106,13579,513],{"class":379},[106,13581,228],{"class":112},[106,13583,518],{"class":112},[106,13585,521],{"class":480},[106,13587,484],{"class":112},[106,13589,487],{"class":116},[106,13591,240],{"class":112},[106,13593,530],{"class":116},[106,13595,495],{"class":112},[106,13597,218],{"class":112},[106,13599,13600,13602,13604,13606],{"class":108,"line":127},[106,13601,905],{"class":224},[106,13603,240],{"class":112},[106,13605,8451],{"class":211},[106,13607,321],{"class":112},[106,13609,13610,13613,13615,13617,13619,13621,13623,13625,13627,13629,13631,13633,13635,13637,13639,13641,13643,13645,13647],{"class":108,"line":137},[106,13611,13612],{"class":112},"            []",[106,13614,8462],{"class":8461},[106,13616,8465],{"class":112},[106,13618,249],{"class":112},[106,13620,12881],{"class":252},[106,13622,249],{"class":112},[106,13624,228],{"class":112},[106,13626,4727],{"class":112},[106,13628,8479],{"class":252},[106,13630,249],{"class":112},[106,13632,228],{"class":112},[106,13634,4727],{"class":112},[106,13636,8488],{"class":252},[106,13638,249],{"class":112},[106,13640,228],{"class":112},[106,13642,4727],{"class":112},[106,13644,8497],{"class":252},[106,13646,249],{"class":112},[106,13648,8502],{"class":112},[106,13650,13651,13654,13656],{"class":108,"line":149},[106,13652,13653],{"class":112},"            [][]",[106,13655,8462],{"class":8461},[106,13657,8512],{"class":112},[106,13659,13660,13663,13665,13667,13669,13671,13673,13675,13677,13679,13681,13683,13685,13687,13689,13691,13693],{"class":108,"line":159},[106,13661,13662],{"class":112},"                {",[106,13664,249],{"class":112},[106,13666,8522],{"class":252},[106,13668,249],{"class":112},[106,13670,228],{"class":112},[106,13672,4727],{"class":112},[106,13674,13162],{"class":252},[106,13676,249],{"class":112},[106,13678,228],{"class":112},[106,13680,4727],{"class":112},[106,13682,13171],{"class":252},[106,13684,249],{"class":112},[106,13686,228],{"class":112},[106,13688,4727],{"class":112},[106,13690,13180],{"class":252},[106,13692,249],{"class":112},[106,13694,8502],{"class":112},[106,13696,13697,13699,13701,13703,13705,13707,13709,13711,13713,13715,13717,13719,13721,13723,13725,13727,13729],{"class":108,"line":164},[106,13698,13662],{"class":112},[106,13700,249],{"class":112},[106,13702,8562],{"class":252},[106,13704,249],{"class":112},[106,13706,228],{"class":112},[106,13708,140],{"class":112},[106,13710,13201],{"class":252},[106,13712,249],{"class":112},[106,13714,228],{"class":112},[106,13716,4727],{"class":112},[106,13718,13171],{"class":252},[106,13720,249],{"class":112},[106,13722,228],{"class":112},[106,13724,4727],{"class":112},[106,13726,13218],{"class":252},[106,13728,249],{"class":112},[106,13730,8502],{"class":112},[106,13732,13733,13735,13737,13739,13741,13743,13745,13747,13749,13751,13753,13755,13757,13759,13761,13763,13765],{"class":108,"line":174},[106,13734,13662],{"class":112},[106,13736,249],{"class":112},[106,13738,8602],{"class":252},[106,13740,249],{"class":112},[106,13742,228],{"class":112},[106,13744,13237],{"class":112},[106,13746,13240],{"class":252},[106,13748,249],{"class":112},[106,13750,228],{"class":112},[106,13752,4727],{"class":112},[106,13754,13249],{"class":252},[106,13756,249],{"class":112},[106,13758,228],{"class":112},[106,13760,4727],{"class":112},[106,13762,13258],{"class":252},[106,13764,249],{"class":112},[106,13766,8502],{"class":112},[106,13768,13769],{"class":108,"line":184},[106,13770,13771],{"class":112},"            },\n",[106,13773,13774,13777,13779,13781,13783,13785,13787,13789,13791,13793,13795,13797],{"class":108,"line":194},[106,13775,13776],{"class":224},"            template",[106,13778,240],{"class":112},[106,13780,8649],{"class":211},[106,13782,246],{"class":112},[106,13784,8654],{"class":379},[106,13786,228],{"class":112},[106,13788,8659],{"class":379},[106,13790,228],{"class":112},[106,13792,8659],{"class":379},[106,13794,228],{"class":112},[106,13796,8668],{"class":379},[106,13798,345],{"class":112},[106,13800,13801,13803,13805,13807],{"class":108,"line":200},[106,13802,13776],{"class":224},[106,13804,240],{"class":112},[106,13806,8679],{"class":211},[106,13808,321],{"class":112},[106,13810,13811,13813,13815,13817],{"class":108,"line":205},[106,13812,8644],{"class":224},[106,13814,240],{"class":112},[106,13816,1697],{"class":211},[106,13818,8693],{"class":112},[106,13820,13821,13823,13825,13827,13829,13831,13833,13835],{"class":108,"line":221},[106,13822,8644],{"class":224},[106,13824,240],{"class":112},[106,13826,8702],{"class":211},[106,13828,246],{"class":112},[106,13830,8707],{"class":224},[106,13832,240],{"class":112},[106,13834,8712],{"class":224},[106,13836,345],{"class":112},[106,13838,13839,13841,13843,13845,13847,13849,13851,13853,13855,13857],{"class":108,"line":260},[106,13840,8644],{"class":224},[106,13842,240],{"class":112},[106,13844,8723],{"class":211},[106,13846,246],{"class":112},[106,13848,8707],{"class":224},[106,13850,240],{"class":112},[106,13852,8732],{"class":211},[106,13854,246],{"class":112},[106,13856,8737],{"class":379},[106,13858,8740],{"class":112},[106,13860,13861],{"class":108,"line":276},[106,13862,13863],{"class":112},"            ),\n",[106,13865,13866,13868,13870,13873,13875,13877,13879,13881,13883,13886],{"class":108,"line":294},[106,13867,13776],{"class":224},[106,13869,240],{"class":112},[106,13871,13872],{"class":211},"TableStripe",[106,13874,246],{"class":112},[106,13876,8707],{"class":224},[106,13878,240],{"class":112},[106,13880,8732],{"class":211},[106,13882,246],{"class":112},[106,13884,13885],{"class":379},"0xF5F5F5",[106,13887,8740],{"class":112},[106,13889,13890],{"class":108,"line":300},[106,13891,13892],{"class":112},"        )\n",[106,13894,13895],{"class":108,"line":305},[106,13896,568],{"class":112},[106,13898,13899],{"class":108,"line":324},[106,13900,932],{"class":112},[19,13902,13903,13906,13907,13910,13911,13914,13915,13917],{},[44,13904,13905],{},"ColumnWidths(50, 15, 15, 20)","의 숫자는 ",[39,13908,13909],{},"절대 mm가 아니라, 표가 올라가는 컬럼 내의 비율",". 같은 표를 ",[44,13912,13913],{},"r.Col(6, ...)"," 안에 넣어도 이 비율이 그대로 잘 맞는다. ",[44,13916,12864],{},"을 래핑하지 않고는 닿을 수 없던 추상.",[19,13919,13920],{},"줄바꿈은 자동. 페이지 분리도 자동 — 표가 하단 여백을 넘으면 다음 페이지에 헤더 행이 다시 그려진다.",[14,13922,13924],{"id":13923},"before-after-3-거추장스러운-절차-없이-한국어-쓰기","Before / After 3: 거추장스러운 절차 없이 한국어 쓰기",[19,13926,13927,13928,13930],{},"gofpdf을 포기한 결정적 이유가 여기. gofpdf에서 한국어를 출력하려면 ",[44,13929,705],{},"를 호출하고, 디스크의 TTF를 가리키고, 폰트를 설정하고, 기도한다. 서브셋팅은 대부분 동작한다. 일부 TTF는 글리프 ID 충돌을 일으켜 깨진 문자를 낸다. 에러 메시지는 도움이 안 된다.",[19,13932,13933],{},[39,13934,12196],{},[97,13936,13938],{"className":99,"code":13937,"language":101,"meta":102,"style":102},"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",[44,13939,13940,13983,14014,14024,14051,14078],{"__ignoreMap":102},[106,13941,13942,13945,13947,13949,13951,13953,13955,13957,13959,13961,13963,13965,13967,13969,13971,13973,13975,13977,13979,13981],{"class":108,"line":109},[106,13943,13944],{"class":224},"pdf ",[106,13946,234],{"class":112},[106,13948,12245],{"class":224},[106,13950,240],{"class":112},[106,13952,4713],{"class":211},[106,13954,246],{"class":112},[106,13956,249],{"class":112},[106,13958,4720],{"class":252},[106,13960,249],{"class":112},[106,13962,228],{"class":112},[106,13964,4727],{"class":112},[106,13966,4730],{"class":252},[106,13968,249],{"class":112},[106,13970,228],{"class":112},[106,13972,4727],{"class":112},[106,13974,342],{"class":252},[106,13976,249],{"class":112},[106,13978,228],{"class":112},[106,13980,4745],{"class":112},[106,13982,197],{"class":112},[106,13984,13985,13987,13989,13991,13993,13995,13998,14000,14002,14004,14006,14008,14010,14012],{"class":108,"line":120},[106,13986,8707],{"class":224},[106,13988,240],{"class":112},[106,13990,705],{"class":211},[106,13992,246],{"class":112},[106,13994,249],{"class":112},[106,13996,13997],{"class":252},"notosanskr",[106,13999,249],{"class":112},[106,14001,228],{"class":112},[106,14003,4745],{"class":112},[106,14005,228],{"class":112},[106,14007,4727],{"class":112},[106,14009,9910],{"class":252},[106,14011,249],{"class":112},[106,14013,197],{"class":112},[106,14015,14016,14018,14020,14022],{"class":108,"line":127},[106,14017,8707],{"class":224},[106,14019,240],{"class":112},[106,14021,460],{"class":211},[106,14023,463],{"class":112},[106,14025,14026,14028,14030,14032,14034,14036,14038,14040,14042,14044,14046,14049],{"class":108,"line":137},[106,14027,8707],{"class":224},[106,14029,240],{"class":112},[106,14031,4767],{"class":211},[106,14033,246],{"class":112},[106,14035,249],{"class":112},[106,14037,13997],{"class":252},[106,14039,249],{"class":112},[106,14041,228],{"class":112},[106,14043,4745],{"class":112},[106,14045,228],{"class":112},[106,14047,14048],{"class":379}," 14",[106,14050,197],{"class":112},[106,14052,14053,14055,14057,14059,14061,14063,14065,14067,14069,14071,14074,14076],{"class":108,"line":149},[106,14054,8707],{"class":224},[106,14056,240],{"class":112},[106,14058,4801],{"class":211},[106,14060,246],{"class":112},[106,14062,7562],{"class":379},[106,14064,228],{"class":112},[106,14066,4811],{"class":379},[106,14068,228],{"class":112},[106,14070,4727],{"class":112},[106,14072,14073],{"class":252},"안녕하세요, 세계.",[106,14075,249],{"class":112},[106,14077,197],{"class":112},[106,14079,14080,14082,14084,14086,14088,14090,14093,14095],{"class":108,"line":159},[106,14081,8707],{"class":224},[106,14083,240],{"class":112},[106,14085,12355],{"class":211},[106,14087,246],{"class":112},[106,14089,249],{"class":112},[106,14091,14092],{"class":252},"ko.pdf",[106,14094,249],{"class":112},[106,14096,197],{"class":112},[19,14098,14099,14100,14103,14104,14106,14107,14109],{},"지뢰 두 개. TTF가 ",[39,14101,14102],{},"런타임에"," 지정 경로에 존재해야 하고, 그래서 Docker 이미지에 폰트를 함께 담아야 한다. ",[44,14105,4801],{}," 너비를 ",[44,14108,7562],{},"으로 주면 \"오른쪽 여백까지\"를 뜻하는데, CJK에서는 폭 추정이 전각 문자를 제대로 계산하지 못해 잘리는 일이 잦다.",[19,14111,14112],{},[39,14113,4936],{},[97,14115,14117],{"className":99,"code":14116,"language":101,"meta":102,"style":102},"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",[44,14118,14119,14125,14129,14135,14143,14151,14155,14163,14171,14179,14183,14187,14197,14224,14236,14250,14254,14258,14272,14290,14320,14343,14365,14369,14373,14387,14411,14441,14459,14478,14497,14501,14505,14509,14527,14554],{"__ignoreMap":102},[106,14120,14121,14123],{"class":108,"line":109},[106,14122,113],{"class":112},[106,14124,117],{"class":116},[106,14126,14127],{"class":108,"line":120},[106,14128,124],{"emptyLinePlaceholder":123},[106,14130,14131,14133],{"class":108,"line":127},[106,14132,131],{"class":130},[106,14134,134],{"class":112},[106,14136,14137,14139,14141],{"class":108,"line":137},[106,14138,140],{"class":112},[106,14140,143],{"class":116},[106,14142,146],{"class":112},[106,14144,14145,14147,14149],{"class":108,"line":149},[106,14146,140],{"class":112},[106,14148,154],{"class":116},[106,14150,146],{"class":112},[106,14152,14153],{"class":108,"line":159},[106,14154,124],{"emptyLinePlaceholder":123},[106,14156,14157,14159,14161],{"class":108,"line":164},[106,14158,140],{"class":112},[106,14160,169],{"class":116},[106,14162,146],{"class":112},[106,14164,14165,14167,14169],{"class":108,"line":174},[106,14166,140],{"class":112},[106,14168,179],{"class":116},[106,14170,146],{"class":112},[106,14172,14173,14175,14177],{"class":108,"line":184},[106,14174,140],{"class":112},[106,14176,189],{"class":116},[106,14178,146],{"class":112},[106,14180,14181],{"class":108,"line":194},[106,14182,197],{"class":112},[106,14184,14185],{"class":108,"line":200},[106,14186,124],{"emptyLinePlaceholder":123},[106,14188,14189,14191,14193,14195],{"class":108,"line":205},[106,14190,208],{"class":112},[106,14192,212],{"class":211},[106,14194,215],{"class":112},[106,14196,218],{"class":112},[106,14198,14199,14202,14204,14206,14208,14210,14212,14214,14216,14218,14220,14222],{"class":108,"line":221},[106,14200,14201],{"class":224},"    fontData",[106,14203,228],{"class":112},[106,14205,231],{"class":224},[106,14207,234],{"class":112},[106,14209,237],{"class":224},[106,14211,240],{"class":112},[106,14213,243],{"class":211},[106,14215,246],{"class":112},[106,14217,249],{"class":112},[106,14219,9910],{"class":252},[106,14221,249],{"class":112},[106,14223,197],{"class":112},[106,14225,14226,14228,14230,14232,14234],{"class":108,"line":260},[106,14227,263],{"class":130},[106,14229,231],{"class":224},[106,14231,268],{"class":112},[106,14233,271],{"class":112},[106,14235,218],{"class":112},[106,14237,14238,14240,14242,14244,14246,14248],{"class":108,"line":276},[106,14239,279],{"class":224},[106,14241,240],{"class":112},[106,14243,284],{"class":211},[106,14245,246],{"class":112},[106,14247,289],{"class":224},[106,14249,197],{"class":112},[106,14251,14252],{"class":108,"line":294},[106,14253,297],{"class":112},[106,14255,14256],{"class":108,"line":300},[106,14257,124],{"emptyLinePlaceholder":123},[106,14259,14260,14262,14264,14266,14268,14270],{"class":108,"line":305},[106,14261,308],{"class":224},[106,14263,234],{"class":112},[106,14265,313],{"class":224},[106,14267,240],{"class":112},[106,14269,318],{"class":211},[106,14271,321],{"class":112},[106,14273,14274,14276,14278,14280,14282,14284,14286,14288],{"class":108,"line":324},[106,14275,327],{"class":224},[106,14277,240],{"class":112},[106,14279,332],{"class":211},[106,14281,246],{"class":112},[106,14283,360],{"class":224},[106,14285,240],{"class":112},[106,14287,342],{"class":224},[106,14289,345],{"class":112},[106,14291,14292,14294,14296,14298,14300,14302,14304,14306,14308,14310,14312,14314,14316,14318],{"class":108,"line":348},[106,14293,327],{"class":224},[106,14295,240],{"class":112},[106,14297,355],{"class":211},[106,14299,246],{"class":112},[106,14301,360],{"class":224},[106,14303,240],{"class":112},[106,14305,365],{"class":211},[106,14307,246],{"class":112},[106,14309,360],{"class":224},[106,14311,240],{"class":112},[106,14313,374],{"class":211},[106,14315,246],{"class":112},[106,14317,380],{"class":379},[106,14319,383],{"class":112},[106,14321,14322,14324,14326,14328,14330,14332,14334,14336,14338,14341],{"class":108,"line":386},[106,14323,327],{"class":224},[106,14325,240],{"class":112},[106,14327,50],{"class":211},[106,14329,246],{"class":112},[106,14331,249],{"class":112},[106,14333,9994],{"class":252},[106,14335,249],{"class":112},[106,14337,228],{"class":112},[106,14339,14340],{"class":224}," fontData",[106,14342,345],{"class":112},[106,14344,14345,14347,14349,14351,14353,14355,14357,14359,14361,14363],{"class":108,"line":411},[106,14346,327],{"class":224},[106,14348,240],{"class":112},[106,14350,418],{"class":211},[106,14352,246],{"class":112},[106,14354,249],{"class":112},[106,14356,9994],{"class":252},[106,14358,249],{"class":112},[106,14360,228],{"class":112},[106,14362,14048],{"class":379},[106,14364,345],{"class":112},[106,14366,14367],{"class":108,"line":436},[106,14368,439],{"class":112},[106,14370,14371],{"class":108,"line":442},[106,14372,124],{"emptyLinePlaceholder":123},[106,14374,14375,14377,14379,14381,14383,14385],{"class":108,"line":447},[106,14376,450],{"class":224},[106,14378,234],{"class":112},[106,14380,455],{"class":224},[106,14382,240],{"class":112},[106,14384,460],{"class":211},[106,14386,463],{"class":112},[106,14388,14389,14391,14393,14395,14397,14399,14401,14403,14405,14407,14409],{"class":108,"line":466},[106,14390,469],{"class":224},[106,14392,240],{"class":112},[106,14394,474],{"class":211},[106,14396,477],{"class":112},[106,14398,481],{"class":480},[106,14400,484],{"class":112},[106,14402,487],{"class":116},[106,14404,240],{"class":112},[106,14406,492],{"class":116},[106,14408,495],{"class":112},[106,14410,218],{"class":112},[106,14412,14413,14415,14417,14419,14421,14423,14425,14427,14429,14431,14433,14435,14437,14439],{"class":108,"line":500},[106,14414,503],{"class":224},[106,14416,240],{"class":112},[106,14418,508],{"class":211},[106,14420,246],{"class":112},[106,14422,513],{"class":379},[106,14424,228],{"class":112},[106,14426,518],{"class":112},[106,14428,521],{"class":480},[106,14430,484],{"class":112},[106,14432,487],{"class":116},[106,14434,240],{"class":112},[106,14436,530],{"class":116},[106,14438,495],{"class":112},[106,14440,218],{"class":112},[106,14442,14443,14445,14447,14449,14451,14453,14455,14457],{"class":108,"line":537},[106,14444,540],{"class":224},[106,14446,240],{"class":112},[106,14448,545],{"class":211},[106,14450,246],{"class":112},[106,14452,249],{"class":112},[106,14454,14073],{"class":252},[106,14456,249],{"class":112},[106,14458,197],{"class":112},[106,14460,14461,14463,14465,14467,14469,14471,14474,14476],{"class":108,"line":559},[106,14462,540],{"class":224},[106,14464,240],{"class":112},[106,14466,545],{"class":211},[106,14468,246],{"class":112},[106,14470,249],{"class":112},[106,14472,14473],{"class":252},"대한민국 서울특별시 강남구 테헤란로 427",[106,14475,249],{"class":112},[106,14477,197],{"class":112},[106,14479,14480,14482,14484,14486,14488,14490,14493,14495],{"class":108,"line":565},[106,14481,540],{"class":224},[106,14483,240],{"class":112},[106,14485,545],{"class":211},[106,14487,246],{"class":112},[106,14489,249],{"class":112},[106,14491,14492],{"class":252},"천 리 길도 한 걸음부터.",[106,14494,249],{"class":112},[106,14496,197],{"class":112},[106,14498,14499],{"class":108,"line":571},[106,14500,562],{"class":112},[106,14502,14503],{"class":108,"line":576},[106,14504,568],{"class":112},[106,14506,14507],{"class":108,"line":597},[106,14508,124],{"emptyLinePlaceholder":123},[106,14510,14511,14513,14515,14517,14519,14521,14523,14525],{"class":108,"line":610},[106,14512,579],{"class":224},[106,14514,228],{"class":112},[106,14516,2042],{"class":224},[106,14518,234],{"class":112},[106,14520,455],{"class":224},[106,14522,240],{"class":112},[106,14524,592],{"class":211},[106,14526,463],{"class":112},[106,14528,14529,14532,14534,14536,14538,14540,14542,14544,14546,14548,14550,14552],{"class":108,"line":625},[106,14530,14531],{"class":224},"    os",[106,14533,240],{"class":112},[106,14535,643],{"class":211},[106,14537,246],{"class":112},[106,14539,249],{"class":112},[106,14541,14092],{"class":252},[106,14543,249],{"class":112},[106,14545,228],{"class":112},[106,14547,657],{"class":224},[106,14549,228],{"class":112},[106,14551,662],{"class":379},[106,14553,197],{"class":112},[106,14555,14556],{"class":108,"line":630},[106,14557,699],{"class":112},[19,14559,14560],{},"다른 점이 둘.",[19,14562,14563,14564,4347,14567,14570],{},"첫째, ",[39,14565,14566],{},"경로가 아니라 바이트를 넘긴다",[44,14568,14569],{},"//go:embed NotoSansKR-Regular.ttf","로 TTF를 바이너리에 포함시키면 배포가 자기완결적이 된다. 프로덕션에서 \"폰트를 찾을 수 없음\"이 발생하지 않는다.",[19,14572,14573],{},"둘째, gpdf의 TrueType 서브셋팅은 CJK cmap 형식(4, 6, 12)과 Identity-H 인코딩을 이해한다. 출력 PDF에는 실제로 사용한 글리프만 들어간다 — NotoSansKR을 200자짜리 세금계산서에 넣으면 약 30 KB 서브셋이 된다. 4 MB 풀 임베드가 아니다. gofpdf로 한국어 한 페이지 PDF가 5 MB가 되는 걸 본 적이 있다면 가장 먼저 느끼는 차이가 이것.",[19,14575,14576],{},"나눔스퀘어, Pretendard, 본고딕 등 한국어 폰트별 심화 주제는 별도 글에서 다룰 예정.",[14,14578,14580],{"id":14579},"before-after-4-모든-페이지-헤더-푸터-페이지-번호","Before / After 4: 모든 페이지 헤더 + 푸터 페이지 번호",[19,14582,14583,14584,10990,14587,14590,14591,14594,14595,6948,14597,240],{},"gofpdf의 반복 크롬 패턴은 ",[44,14585,14586],{},"SetHeaderFunc",[44,14588,14589],{},"SetFooterFunc",". 각각 현재 커서에 대해 실행되는 ",[44,14592,14593],{},"func()","를 받는다. 페이지 번호는 ",[44,14596,12104],{},[44,14598,14599],{},"pdf.AliasNbPages()",[19,14601,14602],{},[39,14603,12196],{},[97,14605,14607],{"className":99,"code":14606,"language":101,"meta":102,"style":102},"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",[44,14608,14609,14651,14664,14694,14721,14736,14740,14752,14767,14798,14816,14853,14884,14888,14904,14914],{"__ignoreMap":102},[106,14610,14611,14613,14615,14617,14619,14621,14623,14625,14627,14629,14631,14633,14635,14637,14639,14641,14643,14645,14647,14649],{"class":108,"line":109},[106,14612,13944],{"class":224},[106,14614,234],{"class":112},[106,14616,12245],{"class":224},[106,14618,240],{"class":112},[106,14620,4713],{"class":211},[106,14622,246],{"class":112},[106,14624,249],{"class":112},[106,14626,4720],{"class":252},[106,14628,249],{"class":112},[106,14630,228],{"class":112},[106,14632,4727],{"class":112},[106,14634,4730],{"class":252},[106,14636,249],{"class":112},[106,14638,228],{"class":112},[106,14640,4727],{"class":112},[106,14642,342],{"class":252},[106,14644,249],{"class":112},[106,14646,228],{"class":112},[106,14648,4745],{"class":112},[106,14650,197],{"class":112},[106,14652,14653,14655,14657,14659,14662],{"class":108,"line":120},[106,14654,8707],{"class":224},[106,14656,240],{"class":112},[106,14658,14586],{"class":211},[106,14660,14661],{"class":112},"(func()",[106,14663,218],{"class":112},[106,14665,14666,14668,14670,14672,14674,14676,14678,14680,14682,14684,14686,14688,14690,14692],{"class":108,"line":127},[106,14667,4752],{"class":224},[106,14669,240],{"class":112},[106,14671,4767],{"class":211},[106,14673,246],{"class":112},[106,14675,249],{"class":112},[106,14677,4774],{"class":252},[106,14679,249],{"class":112},[106,14681,228],{"class":112},[106,14683,4727],{"class":112},[106,14685,4783],{"class":252},[106,14687,249],{"class":112},[106,14689,228],{"class":112},[106,14691,431],{"class":379},[106,14693,197],{"class":112},[106,14695,14696,14698,14700,14702,14704,14706,14708,14710,14712,14714,14717,14719],{"class":108,"line":137},[106,14697,4752],{"class":224},[106,14699,240],{"class":112},[106,14701,4801],{"class":211},[106,14703,246],{"class":112},[106,14705,7562],{"class":379},[106,14707,228],{"class":112},[106,14709,4811],{"class":379},[106,14711,228],{"class":112},[106,14713,4727],{"class":112},[106,14715,14716],{"class":252},"ACME 주식회사",[106,14718,249],{"class":112},[106,14720,197],{"class":112},[106,14722,14723,14725,14727,14730,14732,14734],{"class":108,"line":149},[106,14724,4752],{"class":224},[106,14726,240],{"class":112},[106,14728,14729],{"class":211},"Ln",[106,14731,246],{"class":112},[106,14733,2737],{"class":379},[106,14735,197],{"class":112},[106,14737,14738],{"class":108,"line":159},[106,14739,932],{"class":112},[106,14741,14742,14744,14746,14748,14750],{"class":108,"line":164},[106,14743,8707],{"class":224},[106,14745,240],{"class":112},[106,14747,14589],{"class":211},[106,14749,14661],{"class":112},[106,14751,218],{"class":112},[106,14753,14754,14756,14758,14760,14763,14765],{"class":108,"line":174},[106,14755,4752],{"class":224},[106,14757,240],{"class":112},[106,14759,5608],{"class":211},[106,14761,14762],{"class":112},"(-",[106,14764,2737],{"class":379},[106,14766,197],{"class":112},[106,14768,14769,14771,14773,14775,14777,14779,14781,14783,14785,14787,14790,14792,14794,14796],{"class":108,"line":184},[106,14770,4752],{"class":224},[106,14772,240],{"class":112},[106,14774,4767],{"class":211},[106,14776,246],{"class":112},[106,14778,249],{"class":112},[106,14780,4774],{"class":252},[106,14782,249],{"class":112},[106,14784,228],{"class":112},[106,14786,4727],{"class":112},[106,14788,14789],{"class":252},"I",[106,14791,249],{"class":112},[106,14793,228],{"class":112},[106,14795,12874],{"class":379},[106,14797,197],{"class":112},[106,14799,14800,14802,14804,14806,14808,14810,14812,14814],{"class":108,"line":194},[106,14801,4752],{"class":224},[106,14803,240],{"class":112},[106,14805,12864],{"class":211},[106,14807,246],{"class":112},[106,14809,7562],{"class":379},[106,14811,228],{"class":112},[106,14813,4811],{"class":379},[106,14815,7066],{"class":112},[106,14817,14818,14821,14823,14826,14828,14830,14833,14836,14839,14841,14843,14845,14847,14850],{"class":108,"line":200},[106,14819,14820],{"class":224},"        fmt",[106,14822,240],{"class":112},[106,14824,14825],{"class":211},"Sprintf",[106,14827,246],{"class":112},[106,14829,249],{"class":112},[106,14831,14832],{"class":252},"Page ",[106,14834,14835],{"class":1058},"%d",[106,14837,14838],{"class":252},"/{nb}",[106,14840,249],{"class":112},[106,14842,228],{"class":112},[106,14844,4873],{"class":224},[106,14846,240],{"class":112},[106,14848,14849],{"class":211},"PageNo",[106,14851,14852],{"class":112},"()),\n",[106,14854,14855,14858,14860,14862,14864,14866,14868,14870,14872,14874,14876,14878,14880,14882],{"class":108,"line":205},[106,14856,14857],{"class":112},"        \"\"",[106,14859,228],{"class":112},[106,14861,12898],{"class":379},[106,14863,228],{"class":112},[106,14865,4727],{"class":112},[106,14867,12966],{"class":252},[106,14869,249],{"class":112},[106,14871,228],{"class":112},[106,14873,13341],{"class":12912},[106,14875,228],{"class":112},[106,14877,12898],{"class":379},[106,14879,228],{"class":112},[106,14881,4745],{"class":112},[106,14883,197],{"class":112},[106,14885,14886],{"class":108,"line":221},[106,14887,932],{"class":112},[106,14889,14890,14892,14894,14897,14899,14902],{"class":108,"line":260},[106,14891,8707],{"class":224},[106,14893,240],{"class":112},[106,14895,14896],{"class":211},"AliasNbPages",[106,14898,246],{"class":112},[106,14900,14901],{"class":112},"\"\"",[106,14903,197],{"class":112},[106,14905,14906,14908,14910,14912],{"class":108,"line":276},[106,14907,8707],{"class":224},[106,14909,240],{"class":112},[106,14911,460],{"class":211},[106,14913,463],{"class":112},[106,14915,14916],{"class":108,"line":294},[106,14917,14918],{"class":835},"// ... body ...\n",[19,14920,14921,14924],{},[44,14922,14923],{},"{nb}","는 gofpdf가 출력 시 전체 페이지 수로 치환하는 센티넬. 동작은 하지만 \"알고 있어야만 쓴다\"는 부류.",[19,14926,14927],{},[39,14928,4936],{},[97,14930,14932],{"className":99,"code":14931,"language":101,"meta":102,"style":102},"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",[44,14933,14934,14948,14966,14996,15000,15004,15030,15055,15085,15123,15158,15180,15184,15188,15192,15196,15221,15245,15275,15293,15328,15332,15362,15367,15372,15391,15425,15429,15433,15437,15441,15472,15486,15510,15540,15577,15581,15585],{"__ignoreMap":102},[106,14935,14936,14938,14940,14942,14944,14946],{"class":108,"line":109},[106,14937,797],{"class":224},[106,14939,234],{"class":112},[106,14941,313],{"class":224},[106,14943,240],{"class":112},[106,14945,318],{"class":211},[106,14947,321],{"class":112},[106,14949,14950,14952,14954,14956,14958,14960,14962,14964],{"class":108,"line":120},[106,14951,812],{"class":224},[106,14953,240],{"class":112},[106,14955,332],{"class":211},[106,14957,246],{"class":112},[106,14959,360],{"class":224},[106,14961,240],{"class":112},[106,14963,342],{"class":224},[106,14965,345],{"class":112},[106,14967,14968,14970,14972,14974,14976,14978,14980,14982,14984,14986,14988,14990,14992,14994],{"class":108,"line":127},[106,14969,812],{"class":224},[106,14971,240],{"class":112},[106,14973,355],{"class":211},[106,14975,246],{"class":112},[106,14977,360],{"class":224},[106,14979,240],{"class":112},[106,14981,365],{"class":211},[106,14983,246],{"class":112},[106,14985,360],{"class":224},[106,14987,240],{"class":112},[106,14989,374],{"class":211},[106,14991,246],{"class":112},[106,14993,380],{"class":379},[106,14995,383],{"class":112},[106,14997,14998],{"class":108,"line":137},[106,14999,197],{"class":112},[106,15001,15002],{"class":108,"line":149},[106,15003,124],{"emptyLinePlaceholder":123},[106,15005,15006,15009,15011,15013,15015,15017,15019,15021,15023,15026,15028],{"class":108,"line":159},[106,15007,15008],{"class":224},"doc",[106,15010,240],{"class":112},[106,15012,4836],{"class":211},[106,15014,477],{"class":112},[106,15016,19],{"class":480},[106,15018,484],{"class":112},[106,15020,487],{"class":116},[106,15022,240],{"class":112},[106,15024,15025],{"class":116},"PageBuilder",[106,15027,495],{"class":112},[106,15029,218],{"class":112},[106,15031,15032,15035,15037,15039,15041,15043,15045,15047,15049,15051,15053],{"class":108,"line":164},[106,15033,15034],{"class":224},"    p",[106,15036,240],{"class":112},[106,15038,474],{"class":211},[106,15040,477],{"class":112},[106,15042,481],{"class":480},[106,15044,484],{"class":112},[106,15046,487],{"class":116},[106,15048,240],{"class":112},[106,15050,492],{"class":116},[106,15052,495],{"class":112},[106,15054,218],{"class":112},[106,15056,15057,15059,15061,15063,15065,15067,15069,15071,15073,15075,15077,15079,15081,15083],{"class":108,"line":174},[106,15058,503],{"class":224},[106,15060,240],{"class":112},[106,15062,508],{"class":211},[106,15064,246],{"class":112},[106,15066,513],{"class":379},[106,15068,228],{"class":112},[106,15070,518],{"class":112},[106,15072,521],{"class":480},[106,15074,484],{"class":112},[106,15076,487],{"class":116},[106,15078,240],{"class":112},[106,15080,530],{"class":116},[106,15082,495],{"class":112},[106,15084,218],{"class":112},[106,15086,15087,15089,15091,15093,15095,15097,15099,15101,15103,15105,15107,15109,15111,15113,15115,15117,15119,15121],{"class":108,"line":184},[106,15088,540],{"class":224},[106,15090,240],{"class":112},[106,15092,545],{"class":211},[106,15094,246],{"class":112},[106,15096,249],{"class":112},[106,15098,14716],{"class":252},[106,15100,249],{"class":112},[106,15102,228],{"class":112},[106,15104,1677],{"class":224},[106,15106,240],{"class":112},[106,15108,1697],{"class":211},[106,15110,4918],{"class":112},[106,15112,1677],{"class":224},[106,15114,240],{"class":112},[106,15116,1682],{"class":211},[106,15118,246],{"class":112},[106,15120,513],{"class":379},[106,15122,2284],{"class":112},[106,15124,15125,15127,15129,15132,15134,15136,15138,15141,15143,15145,15147,15150,15152,15155],{"class":108,"line":194},[106,15126,540],{"class":224},[106,15128,240],{"class":112},[106,15130,15131],{"class":211},"Line",[106,15133,246],{"class":112},[106,15135,487],{"class":224},[106,15137,240],{"class":112},[106,15139,15140],{"class":211},"LineColor",[106,15142,246],{"class":112},[106,15144,8707],{"class":224},[106,15146,240],{"class":112},[106,15148,15149],{"class":211},"Gray",[106,15151,246],{"class":112},[106,15153,15154],{"class":379},"0.7",[106,15156,15157],{"class":112},")))\n",[106,15159,15160,15162,15164,15166,15168,15170,15172,15174,15176,15178],{"class":108,"line":200},[106,15161,540],{"class":224},[106,15163,240],{"class":112},[106,15165,8428],{"class":211},[106,15167,246],{"class":112},[106,15169,360],{"class":224},[106,15171,240],{"class":112},[106,15173,374],{"class":211},[106,15175,246],{"class":112},[106,15177,3100],{"class":379},[106,15179,2284],{"class":112},[106,15181,15182],{"class":108,"line":205},[106,15183,562],{"class":112},[106,15185,15186],{"class":108,"line":221},[106,15187,568],{"class":112},[106,15189,15190],{"class":108,"line":260},[106,15191,932],{"class":112},[106,15193,15194],{"class":108,"line":276},[106,15195,124],{"emptyLinePlaceholder":123},[106,15197,15198,15200,15202,15205,15207,15209,15211,15213,15215,15217,15219],{"class":108,"line":294},[106,15199,15008],{"class":224},[106,15201,240],{"class":112},[106,15203,15204],{"class":211},"Footer",[106,15206,477],{"class":112},[106,15208,19],{"class":480},[106,15210,484],{"class":112},[106,15212,487],{"class":116},[106,15214,240],{"class":112},[106,15216,15025],{"class":116},[106,15218,495],{"class":112},[106,15220,218],{"class":112},[106,15222,15223,15225,15227,15229,15231,15233,15235,15237,15239,15241,15243],{"class":108,"line":300},[106,15224,15034],{"class":224},[106,15226,240],{"class":112},[106,15228,474],{"class":211},[106,15230,477],{"class":112},[106,15232,481],{"class":480},[106,15234,484],{"class":112},[106,15236,487],{"class":116},[106,15238,240],{"class":112},[106,15240,492],{"class":116},[106,15242,495],{"class":112},[106,15244,218],{"class":112},[106,15246,15247,15249,15251,15253,15255,15257,15259,15261,15263,15265,15267,15269,15271,15273],{"class":108,"line":305},[106,15248,503],{"class":224},[106,15250,240],{"class":112},[106,15252,508],{"class":211},[106,15254,246],{"class":112},[106,15256,2918],{"class":379},[106,15258,228],{"class":112},[106,15260,518],{"class":112},[106,15262,521],{"class":480},[106,15264,484],{"class":112},[106,15266,487],{"class":116},[106,15268,240],{"class":112},[106,15270,530],{"class":116},[106,15272,495],{"class":112},[106,15274,218],{"class":112},[106,15276,15277,15279,15281,15283,15285,15287,15289,15291],{"class":108,"line":324},[106,15278,540],{"class":224},[106,15280,240],{"class":112},[106,15282,545],{"class":211},[106,15284,246],{"class":112},[106,15286,249],{"class":112},[106,15288,14716],{"class":252},[106,15290,249],{"class":112},[106,15292,7066],{"class":112},[106,15294,15295,15297,15299,15301,15303,15305,15307,15309,15311,15313,15315,15317,15319,15321,15323,15326],{"class":108,"line":348},[106,15296,8644],{"class":224},[106,15298,240],{"class":112},[106,15300,1682],{"class":211},[106,15302,246],{"class":112},[106,15304,3310],{"class":379},[106,15306,1690],{"class":112},[106,15308,1677],{"class":224},[106,15310,240],{"class":112},[106,15312,8702],{"class":211},[106,15314,246],{"class":112},[106,15316,8707],{"class":224},[106,15318,240],{"class":112},[106,15320,15149],{"class":211},[106,15322,246],{"class":112},[106,15324,15325],{"class":379},"0.5",[106,15327,15157],{"class":112},[106,15329,15330],{"class":108,"line":386},[106,15331,562],{"class":112},[106,15333,15334,15336,15338,15340,15342,15344,15346,15348,15350,15352,15354,15356,15358,15360],{"class":108,"line":411},[106,15335,503],{"class":224},[106,15337,240],{"class":112},[106,15339,508],{"class":211},[106,15341,246],{"class":112},[106,15343,2918],{"class":379},[106,15345,228],{"class":112},[106,15347,518],{"class":112},[106,15349,521],{"class":480},[106,15351,484],{"class":112},[106,15353,487],{"class":116},[106,15355,240],{"class":112},[106,15357,530],{"class":116},[106,15359,495],{"class":112},[106,15361,218],{"class":112},[106,15363,15364],{"class":108,"line":436},[106,15365,15366],{"class":835},"            // \"페이지 X / Y\" — 둘 다 플레이스홀더로,\n",[106,15368,15369],{"class":108,"line":442},[106,15370,15371],{"class":835},"            // 페이지네이션 완료 후 레이아웃 엔진이 해결한다.\n",[106,15373,15374,15376,15378,15381,15383,15385,15387,15389],{"class":108,"line":447},[106,15375,540],{"class":224},[106,15377,240],{"class":112},[106,15379,15380],{"class":211},"PageNumber",[106,15382,246],{"class":112},[106,15384,487],{"class":224},[106,15386,240],{"class":112},[106,15388,4558],{"class":211},[106,15390,8693],{"class":112},[106,15392,15393,15395,15397,15399,15401,15403,15405,15407,15409,15411,15413,15415,15417,15419,15421,15423],{"class":108,"line":466},[106,15394,8644],{"class":224},[106,15396,240],{"class":112},[106,15398,1682],{"class":211},[106,15400,246],{"class":112},[106,15402,3310],{"class":379},[106,15404,1690],{"class":112},[106,15406,1677],{"class":224},[106,15408,240],{"class":112},[106,15410,8702],{"class":211},[106,15412,246],{"class":112},[106,15414,8707],{"class":224},[106,15416,240],{"class":112},[106,15418,15149],{"class":211},[106,15420,246],{"class":112},[106,15422,15325],{"class":379},[106,15424,15157],{"class":112},[106,15426,15427],{"class":108,"line":500},[106,15428,562],{"class":112},[106,15430,15431],{"class":108,"line":537},[106,15432,568],{"class":112},[106,15434,15435],{"class":108,"line":559},[106,15436,932],{"class":112},[106,15438,15439],{"class":108,"line":565},[106,15440,124],{"emptyLinePlaceholder":123},[106,15442,15443,15445,15448,15450,15452,15455,15457,15460,15462,15464,15467,15470],{"class":108,"line":571},[106,15444,13271],{"class":130},[106,15446,15447],{"class":224}," i ",[106,15449,234],{"class":112},[106,15451,12898],{"class":379},[106,15453,15454],{"class":112},";",[106,15456,15447],{"class":224},[106,15458,15459],{"class":112},"\u003C",[106,15461,4811],{"class":379},[106,15463,15454],{"class":112},[106,15465,15466],{"class":224}," i",[106,15468,15469],{"class":112},"++",[106,15471,218],{"class":112},[106,15473,15474,15476,15478,15480,15482,15484],{"class":108,"line":576},[106,15475,450],{"class":224},[106,15477,234],{"class":112},[106,15479,455],{"class":224},[106,15481,240],{"class":112},[106,15483,460],{"class":211},[106,15485,463],{"class":112},[106,15487,15488,15490,15492,15494,15496,15498,15500,15502,15504,15506,15508],{"class":108,"line":597},[106,15489,469],{"class":224},[106,15491,240],{"class":112},[106,15493,474],{"class":211},[106,15495,477],{"class":112},[106,15497,481],{"class":480},[106,15499,484],{"class":112},[106,15501,487],{"class":116},[106,15503,240],{"class":112},[106,15505,492],{"class":116},[106,15507,495],{"class":112},[106,15509,218],{"class":112},[106,15511,15512,15514,15516,15518,15520,15522,15524,15526,15528,15530,15532,15534,15536,15538],{"class":108,"line":610},[106,15513,503],{"class":224},[106,15515,240],{"class":112},[106,15517,508],{"class":211},[106,15519,246],{"class":112},[106,15521,513],{"class":379},[106,15523,228],{"class":112},[106,15525,518],{"class":112},[106,15527,521],{"class":480},[106,15529,484],{"class":112},[106,15531,487],{"class":116},[106,15533,240],{"class":112},[106,15535,530],{"class":116},[106,15537,495],{"class":112},[106,15539,218],{"class":112},[106,15541,15542,15544,15546,15548,15550,15552,15554,15556,15558,15560,15562,15565,15567,15569,15571,15573,15575],{"class":108,"line":625},[106,15543,540],{"class":224},[106,15545,240],{"class":112},[106,15547,545],{"class":211},[106,15549,246],{"class":112},[106,15551,1046],{"class":224},[106,15553,240],{"class":112},[106,15555,14825],{"class":211},[106,15557,246],{"class":112},[106,15559,249],{"class":112},[106,15561,14835],{"class":1058},[106,15563,15564],{"class":252}," 페이지 본문.",[106,15566,249],{"class":112},[106,15568,228],{"class":112},[106,15570,15466],{"class":224},[106,15572,7300],{"class":112},[106,15574,12891],{"class":379},[106,15576,2284],{"class":112},[106,15578,15579],{"class":108,"line":630},[106,15580,562],{"class":112},[106,15582,15583],{"class":108,"line":676},[106,15584,568],{"class":112},[106,15586,15587],{"class":108,"line":691},[106,15588,699],{"class":112},[19,15590,15591,960,15593,15596,15597,15599,15600,15603],{},[44,15592,15380],{},[44,15594,15595],{},"TotalPages","는 플레이스홀더. 레이아웃 엔진이 페이지 수를 확정한 뒤 확장된다. ",[44,15598,14923],{}," 센티넬도, ",[44,15601,15602],{},"SetY(-15)","로 푸터를 바닥에 박아두는 작업도 필요 없다 — 푸터는 그냥 트리일 뿐이고, 엔진이 매 페이지 자동으로 공간을 확보한다.",[14,15605,15607],{"id":15606},"before-after-5-http-핸들러에-바이트-반환","Before / After 5: HTTP 핸들러에 바이트 반환",[19,15609,15610,15611,15613,15614,15616,15617,15620],{},"실제 운영 중인 gofpdf 코드 대부분은 파일이 아니라 ",[44,15612,12772],{},"에 쓴다. 주로 ",[44,15615,4858],{},"를 반환하는 ",[44,15618,15619],{},"http.ResponseWriter",". 이 쌍에서 gpdf API가 gofpdf에 가장 가깝다.",[19,15622,15623],{},[39,15624,12196],{},[97,15626,15628],{"className":99,"code":15627,"language":101,"meta":102,"style":102},"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",[44,15629,15630,15662,15704,15714,15740,15791,15795,15825,15853,15879,15883],{"__ignoreMap":102},[106,15631,15632,15634,15636,15638,15640,15642,15644,15646,15648,15650,15652,15654,15656,15658,15660],{"class":108,"line":109},[106,15633,208],{"class":112},[106,15635,4666],{"class":211},[106,15637,246],{"class":112},[106,15639,4671],{"class":480},[106,15641,4674],{"class":116},[106,15643,240],{"class":112},[106,15645,4679],{"class":116},[106,15647,228],{"class":112},[106,15649,4684],{"class":480},[106,15651,484],{"class":112},[106,15653,4689],{"class":116},[106,15655,240],{"class":112},[106,15657,4694],{"class":116},[106,15659,495],{"class":112},[106,15661,218],{"class":112},[106,15663,15664,15666,15668,15670,15672,15674,15676,15678,15680,15682,15684,15686,15688,15690,15692,15694,15696,15698,15700,15702],{"class":108,"line":120},[106,15665,4703],{"class":224},[106,15667,234],{"class":112},[106,15669,12245],{"class":224},[106,15671,240],{"class":112},[106,15673,4713],{"class":211},[106,15675,246],{"class":112},[106,15677,249],{"class":112},[106,15679,4720],{"class":252},[106,15681,249],{"class":112},[106,15683,228],{"class":112},[106,15685,4727],{"class":112},[106,15687,4730],{"class":252},[106,15689,249],{"class":112},[106,15691,228],{"class":112},[106,15693,4727],{"class":112},[106,15695,342],{"class":252},[106,15697,249],{"class":112},[106,15699,228],{"class":112},[106,15701,4745],{"class":112},[106,15703,197],{"class":112},[106,15705,15706,15708,15710,15712],{"class":108,"line":127},[106,15707,4752],{"class":224},[106,15709,240],{"class":112},[106,15711,460],{"class":211},[106,15713,463],{"class":112},[106,15715,15716,15718,15720,15722,15724,15726,15728,15730,15732,15734,15736,15738],{"class":108,"line":137},[106,15717,4752],{"class":224},[106,15719,240],{"class":112},[106,15721,4767],{"class":211},[106,15723,246],{"class":112},[106,15725,249],{"class":112},[106,15727,4774],{"class":252},[106,15729,249],{"class":112},[106,15731,228],{"class":112},[106,15733,4745],{"class":112},[106,15735,228],{"class":112},[106,15737,431],{"class":379},[106,15739,197],{"class":112},[106,15741,15742,15744,15746,15748,15750,15752,15754,15756,15758,15760,15763,15765,15767,15770,15772,15775,15777,15780,15782,15784,15786,15789],{"class":108,"line":149},[106,15743,4752],{"class":224},[106,15745,240],{"class":112},[106,15747,4801],{"class":211},[106,15749,246],{"class":112},[106,15751,7562],{"class":379},[106,15753,228],{"class":112},[106,15755,4811],{"class":379},[106,15757,228],{"class":112},[106,15759,4727],{"class":112},[106,15761,15762],{"class":252},"생성 시간: ",[106,15764,249],{"class":112},[106,15766,7300],{"class":112},[106,15768,15769],{"class":224},"time",[106,15771,240],{"class":112},[106,15773,15774],{"class":211},"Now",[106,15776,4839],{"class":112},[106,15778,15779],{"class":211},"Format",[106,15781,246],{"class":112},[106,15783,15769],{"class":224},[106,15785,240],{"class":112},[106,15787,15788],{"class":224},"RFC3339",[106,15790,2284],{"class":112},[106,15792,15793],{"class":108,"line":159},[106,15794,124],{"emptyLinePlaceholder":123},[106,15796,15797,15799,15801,15803,15805,15807,15809,15811,15813,15815,15817,15819,15821,15823],{"class":108,"line":164},[106,15798,4831],{"class":224},[106,15800,240],{"class":112},[106,15802,4836],{"class":211},[106,15804,4839],{"class":112},[106,15806,4842],{"class":211},[106,15808,246],{"class":112},[106,15810,249],{"class":112},[106,15812,4849],{"class":252},[106,15814,249],{"class":112},[106,15816,228],{"class":112},[106,15818,4727],{"class":112},[106,15820,4858],{"class":252},[106,15822,249],{"class":112},[106,15824,197],{"class":112},[106,15826,15827,15829,15831,15833,15835,15837,15839,15841,15843,15845,15847,15849,15851],{"class":108,"line":174},[106,15828,263],{"class":130},[106,15830,231],{"class":224},[106,15832,234],{"class":112},[106,15834,4873],{"class":224},[106,15836,240],{"class":112},[106,15838,4878],{"class":211},[106,15840,246],{"class":112},[106,15842,4671],{"class":224},[106,15844,665],{"class":112},[106,15846,231],{"class":224},[106,15848,268],{"class":112},[106,15850,271],{"class":112},[106,15852,218],{"class":112},[106,15854,15855,15857,15859,15861,15863,15865,15867,15869,15871,15873,15875,15877],{"class":108,"line":184},[106,15856,4897],{"class":224},[106,15858,240],{"class":112},[106,15860,4902],{"class":211},[106,15862,246],{"class":112},[106,15864,4671],{"class":224},[106,15866,228],{"class":112},[106,15868,4911],{"class":224},[106,15870,240],{"class":112},[106,15872,4902],{"class":211},[106,15874,4918],{"class":112},[106,15876,4921],{"class":379},[106,15878,197],{"class":112},[106,15880,15881],{"class":108,"line":194},[106,15882,297],{"class":112},[106,15884,15885],{"class":108,"line":200},[106,15886,699],{"class":112},[19,15888,15889],{},[39,15890,4936],{},[97,15892,15894],{"className":99,"code":15893,"language":101,"meta":102,"style":102},"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",[44,15895,15896,15928,15942,15960,15990,15994,15998,16012,16036,16066,16106,16110,16114,16118,16148,16176,16202,16206],{"__ignoreMap":102},[106,15897,15898,15900,15902,15904,15906,15908,15910,15912,15914,15916,15918,15920,15922,15924,15926],{"class":108,"line":109},[106,15899,208],{"class":112},[106,15901,4666],{"class":211},[106,15903,246],{"class":112},[106,15905,4671],{"class":480},[106,15907,4674],{"class":116},[106,15909,240],{"class":112},[106,15911,4679],{"class":116},[106,15913,228],{"class":112},[106,15915,4684],{"class":480},[106,15917,484],{"class":112},[106,15919,4689],{"class":116},[106,15921,240],{"class":112},[106,15923,4694],{"class":116},[106,15925,495],{"class":112},[106,15927,218],{"class":112},[106,15929,15930,15932,15934,15936,15938,15940],{"class":108,"line":120},[106,15931,308],{"class":224},[106,15933,234],{"class":112},[106,15935,313],{"class":224},[106,15937,240],{"class":112},[106,15939,318],{"class":211},[106,15941,321],{"class":112},[106,15943,15944,15946,15948,15950,15952,15954,15956,15958],{"class":108,"line":127},[106,15945,327],{"class":224},[106,15947,240],{"class":112},[106,15949,332],{"class":211},[106,15951,246],{"class":112},[106,15953,360],{"class":224},[106,15955,240],{"class":112},[106,15957,342],{"class":224},[106,15959,345],{"class":112},[106,15961,15962,15964,15966,15968,15970,15972,15974,15976,15978,15980,15982,15984,15986,15988],{"class":108,"line":137},[106,15963,327],{"class":224},[106,15965,240],{"class":112},[106,15967,355],{"class":211},[106,15969,246],{"class":112},[106,15971,360],{"class":224},[106,15973,240],{"class":112},[106,15975,365],{"class":211},[106,15977,246],{"class":112},[106,15979,360],{"class":224},[106,15981,240],{"class":112},[106,15983,374],{"class":211},[106,15985,246],{"class":112},[106,15987,380],{"class":379},[106,15989,383],{"class":112},[106,15991,15992],{"class":108,"line":149},[106,15993,439],{"class":112},[106,15995,15996],{"class":108,"line":159},[106,15997,124],{"emptyLinePlaceholder":123},[106,15999,16000,16002,16004,16006,16008,16010],{"class":108,"line":164},[106,16001,450],{"class":224},[106,16003,234],{"class":112},[106,16005,455],{"class":224},[106,16007,240],{"class":112},[106,16009,460],{"class":211},[106,16011,463],{"class":112},[106,16013,16014,16016,16018,16020,16022,16024,16026,16028,16030,16032,16034],{"class":108,"line":174},[106,16015,469],{"class":224},[106,16017,240],{"class":112},[106,16019,474],{"class":211},[106,16021,477],{"class":112},[106,16023,481],{"class":480},[106,16025,484],{"class":112},[106,16027,487],{"class":116},[106,16029,240],{"class":112},[106,16031,492],{"class":116},[106,16033,495],{"class":112},[106,16035,218],{"class":112},[106,16037,16038,16040,16042,16044,16046,16048,16050,16052,16054,16056,16058,16060,16062,16064],{"class":108,"line":184},[106,16039,503],{"class":224},[106,16041,240],{"class":112},[106,16043,508],{"class":211},[106,16045,246],{"class":112},[106,16047,513],{"class":379},[106,16049,228],{"class":112},[106,16051,518],{"class":112},[106,16053,521],{"class":480},[106,16055,484],{"class":112},[106,16057,487],{"class":116},[106,16059,240],{"class":112},[106,16061,530],{"class":116},[106,16063,495],{"class":112},[106,16065,218],{"class":112},[106,16067,16068,16070,16072,16074,16076,16078,16080,16082,16085,16088,16090,16092,16094,16096,16098,16100,16102,16104],{"class":108,"line":194},[106,16069,540],{"class":224},[106,16071,240],{"class":112},[106,16073,545],{"class":211},[106,16075,246],{"class":112},[106,16077,249],{"class":112},[106,16079,15762],{"class":252},[106,16081,249],{"class":112},[106,16083,16084],{"class":112}," +",[106,16086,16087],{"class":224}," time",[106,16089,240],{"class":112},[106,16091,15774],{"class":211},[106,16093,4839],{"class":112},[106,16095,15779],{"class":211},[106,16097,246],{"class":112},[106,16099,15769],{"class":224},[106,16101,240],{"class":112},[106,16103,15788],{"class":224},[106,16105,2284],{"class":112},[106,16107,16108],{"class":108,"line":200},[106,16109,562],{"class":112},[106,16111,16112],{"class":108,"line":205},[106,16113,568],{"class":112},[106,16115,16116],{"class":108,"line":221},[106,16117,124],{"emptyLinePlaceholder":123},[106,16119,16120,16122,16124,16126,16128,16130,16132,16134,16136,16138,16140,16142,16144,16146],{"class":108,"line":260},[106,16121,4831],{"class":224},[106,16123,240],{"class":112},[106,16125,4836],{"class":211},[106,16127,4839],{"class":112},[106,16129,4842],{"class":211},[106,16131,246],{"class":112},[106,16133,249],{"class":112},[106,16135,4849],{"class":252},[106,16137,249],{"class":112},[106,16139,228],{"class":112},[106,16141,4727],{"class":112},[106,16143,4858],{"class":252},[106,16145,249],{"class":112},[106,16147,197],{"class":112},[106,16149,16150,16152,16154,16156,16158,16160,16162,16164,16166,16168,16170,16172,16174],{"class":108,"line":276},[106,16151,263],{"class":130},[106,16153,231],{"class":224},[106,16155,234],{"class":112},[106,16157,455],{"class":224},[106,16159,240],{"class":112},[106,16161,5267],{"class":211},[106,16163,246],{"class":112},[106,16165,4671],{"class":224},[106,16167,665],{"class":112},[106,16169,231],{"class":224},[106,16171,268],{"class":112},[106,16173,271],{"class":112},[106,16175,218],{"class":112},[106,16177,16178,16180,16182,16184,16186,16188,16190,16192,16194,16196,16198,16200],{"class":108,"line":294},[106,16179,4897],{"class":224},[106,16181,240],{"class":112},[106,16183,4902],{"class":211},[106,16185,246],{"class":112},[106,16187,4671],{"class":224},[106,16189,228],{"class":112},[106,16191,4911],{"class":224},[106,16193,240],{"class":112},[106,16195,4902],{"class":211},[106,16197,4918],{"class":112},[106,16199,4921],{"class":379},[106,16201,197],{"class":112},[106,16203,16204],{"class":108,"line":300},[106,16205,297],{"class":112},[106,16207,16208],{"class":108,"line":305},[106,16209,699],{"class":112},[19,16211,16212,16213,16215,16216,16219,16220,16222,16223,16226],{},"형태는 같다. ",[44,16214,12143],{},"가 PDF를 응답으로 바로 흘려보낸다. ",[44,16217,16218],{},"Content-Length","를 세우고 싶다면 먼저 ",[44,16221,1146],{},"로 바이트를 받아 ",[44,16224,16225],{},"len()","을 찍는다.",[14,16228,16230],{"id":16229},"충분히-빠름은-얼마나-빠른가","\"충분히 빠름\"은 얼마나 빠른가",[19,16232,16233,16234,16236,16237,16239],{},"gpdf는 실전 워크로드에서 ",[39,16235,11774],{},". 아래 수치는 ",[44,16238,7465],{},"를 Apple M1, Go 1.25에서 돌린 결과다.",[1892,16241,16242,16256],{},[1895,16243,16244],{},[1898,16245,16246,16248,16250,16252,16254],{},[1901,16247,5359],{},[1901,16249,337],{},[1901,16251,5364],{},[1901,16253,5367],{},[1901,16255,5370],{},[1908,16257,16258,16273,16288,16303],{},[1898,16259,16260,16263,16267,16269,16271],{},[1913,16261,16262],{},"단일 페이지",[1913,16264,16265],{},[39,16266,5382],{},[1913,16268,5385],{},[1913,16270,5388],{},[1913,16272,5391],{},[1898,16274,16275,16278,16282,16284,16286],{},[1913,16276,16277],{},"4×10 테이블",[1913,16279,16280],{},[39,16281,5401],{},[1913,16283,5404],{},[1913,16285,5407],{},[1913,16287,5410],{},[1898,16289,16290,16293,16297,16299,16301],{},[1913,16291,16292],{},"100 페이지",[1913,16294,16295],{},[39,16296,5420],{},[1913,16298,5423],{},[1913,16300,5410],{},[1913,16302,5428],{},[1898,16304,16305,16307,16311,16313,16315],{},[1913,16306,5433],{},[1913,16308,16309],{},[39,16310,5438],{},[1913,16312,5441],{},[1913,16314,5444],{},[1913,16316,5447],{},[19,16318,16319],{},"합성 벤치마크가 아니다. 테이블 벤치는 4열 10행 세금계산서 명세, 100페이지 벤치는 헤더와 페이지 번호가 반복되는 리포트 — 실제 프로덕션 코드 모양에 맞춰져 있다.",[19,16321,16322],{},"의미 얘기도 가볍게. 13 µs/단일 페이지 = 싱글 코어로 초당 7만 5천 장의 hello-world PDF, 108 µs/테이블 포함 = 초당 약 9,000장의 세금계산서. 핵심은 자랑이 아니라, \"PDF 생성을 캐시해야 할까? 비동기 큐로 빼야 할까?\" 같은 고민이 사라진다는 것. 대부분의 워크로드는 요청 경로에서 동기 생성으로 충분하다.",[14,16324,16326],{"id":16325},"마이그레이션에서-잃는-것","마이그레이션에서 잃는 것",[19,16328,16329],{},"가이드가 현실의 간극을 가리면 의미가 없다. gpdf가 아직 gofpdf 대비 약한 지점을 솔직히 적는다:",[983,16331,16332,16340,16355,16361],{},[36,16333,16334,4347,16337,16339],{},[39,16335,16336],{},"임의 각도 선, 베지어, 복잡한 패스",[44,16338,5497],{},"은 컬럼을 가로지르는 수평선을 그린다. CAD 도면이나 자체 차트 지오메트리는 아직 못 담는다. (차트를 이미지로 사전 렌더링해서 삽입하는 건 문제없이 된다.)",[36,16341,16342,4347,16347,16349,16350,6948,16352,16354],{},[39,16343,16344,16346],{},[44,16345,4298],{}," 기반 절대 좌표 코드",[44,16348,12064],{},"로 비슷한 것은 할 수 있지만, 기존 코드가 2,000줄 ",[44,16351,4298],{},[44,16353,4801],{},"이라면 이전은 사실상 재작성. 다만 재작성본이 보통 원본의 절반 길이가 되는 게 위안.",[36,16356,16357,16360],{},[39,16358,16359],{},"AcroForm (입력 가능 폼)",". gpdf는 아직 생성하지 않는다. PDF가 뷰어에서 사용자가 채우는 템플릿이라면 당분간 AcroForm 지원 라이브러리에 남는 선택지.",[36,16362,16363,16366],{},[39,16364,16365],{},"주석·북마크",". 기본 아웃라인은 되지만 리치 주석은 미지원.",[19,16368,16369],{},"이 중 어느 것도 당신에게 걸리지 않는다면 마이그레이션은 매끄럽게 끝난다. 걸린다면 Issue를 열어 달라 — 로드맵은 요청 기반으로 돌아간다.",[14,16371,5565],{"id":5564},[19,16373,16374,16377],{},[39,16375,16376],{},"gpdf는 gofpdf의 포크인가?","\n아니다. gpdf는 순수 Go로 처음부터 다시 구현했다. PDF 와이어 포맷, 레이아웃 엔진, TrueType 서브셋팅 — 전부 새로 작성. gofpdf나 그 포크와 공유하는 코드는 없다. 왜 포크가 아닌 재구현이냐면, gofpdf 아키텍처는 \"단일 변경 가능 커서\"를 전제로 만들어져 있어서 선언형 그리드를 뒤에 얹으면 기존 호출이 전부 깨지기 때문.",[19,16379,16380,16383,16384,4364,16386,16389,16390,16392,16393,16396],{},[39,16381,16382],{},"외부 의존성은?","\n코어는 제로. ",[44,16385,4363],{},[44,16387,16388],{},"go mod graph | grep gpdf","를 치면 한 줄만 나온다. ",[44,16391,5519],{}," 확장(HTML→PDF, AES 암호화, 서명, PDF/A)은 HTML 파싱을 위해 ",[44,16394,16395],{},"golang.org/x/net","을 끌어오지만, 이건 옵트인이고 마이그레이션에 필수가 아니다.",[19,16398,16399,16402,16403,16405],{},[39,16400,16401],{},"CGO는? gofpdf은 CGO-free였는데 gpdf는?","\n똑같이 순수 Go, CGO 없음. ",[44,16404,4354],{},"로 크로스 컴파일해 정적 바이너리를 배포할 수 있다. Distroless나 Alpine에서는 CGO 툴체인이 없는 것만으로 이미지 크기가 절반이 되기 때문에 중요한 포인트.",[19,16407,16408,16414,16416],{},[39,16409,16410,16411,16413],{},"기존 gofpdf 코드가 ",[44,16412,4298],{}," 투성이. 재작성 없이 이전할 수 있나?",[44,16415,12064],{},"를 래핑하면 비슷한 감각은 낸다. 다만 코드 전체가 커서 조작 중심이라면, 레이아웃 엔진 모델로의 이전은 문법이 아니라 사고방식 전환이다. 많은 팀이 \"재작성한 쪽이 원본보다 짧다\"고 말한다.",[19,16418,16419,16422,16423,16425],{},[39,16420,16421],{},"전자세금계산서, 전자문서 인증은?","\n타임스탬프와 전자서명은 ",[44,16424,5519],{},"에서 구현 중. 구체적 요구가 있다면 Issue로 우선순위를 올려 달라.",[19,16427,16428,16431],{},[39,16429,16430],{},"go-pdf/fpdf가 아카이브 해제되면?","\n선택지가 하나 더 생길 뿐. gpdf의 베팅은 \"gofpdf이 영원히 아카이브\"가 아니라 \"커서 기반 + 싱글바이트 폰트 + CJK 미지원이라는 아키텍처 자체가 누가 유지해도 막다른 길\"이라는 쪽. 2026년의 PDF 생성은 플로터를 조작하는 것보다 웹 페이지를 짜는 것에 가깝고, API도 그걸 반영해야 한다.",[14,16433,7324],{"id":7323},[19,16435,16436],{},"gpdf는 Go의 PDF 생성 라이브러리. MIT, 의존성 제로, CJK 지원.",[97,16438,16439],{"className":1208,"code":1209,"language":1210,"meta":102,"style":102},[44,16440,16441],{"__ignoreMap":102},[106,16442,16443,16445,16447],{"class":108,"line":109},[106,16444,101],{"class":116},[106,16446,1219],{"class":252},[106,16448,1222],{"class":252},[19,16450,16451,1230,16455],{},[720,16452,16454],{"href":1227,"rel":16453},[724],"⭐ GitHub에서 스타 누르기",[720,16456,11372],{"href":1233,"rel":16457},[724],[14,16459,16461],{"id":16460},"다음으로-읽을-것","다음으로 읽을 것",[983,16463,16464,16470,16475],{},[36,16465,16466,16467],{},"12-컬럼 그리드는 gpdf에서 어떻게 동작하나 ",[784,16468,16469],{},"(곧 공개)",[36,16471,16472,16473],{},"gpdf에 한국어 폰트 넣기 ",[784,16474,16469],{},[36,16476,16477,16480,16481,16483],{},[720,16478,9008],{"href":1233,"rel":16479},[724]," — 5분 세팅, ",[44,16482,3988],{}," 포함",[1237,16485,16486],{},"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":102,"searchDepth":120,"depth":120,"links":16488},[16489,16490,16491,16492,16493,16494,16495,16496,16497,16498,16499,16500,16501,16502],{"id":3932,"depth":120,"text":3933},{"id":11805,"depth":120,"text":11806},{"id":11835,"depth":120,"text":11836},{"id":11874,"depth":120,"text":11875},{"id":12187,"depth":120,"text":12188},{"id":12780,"depth":120,"text":12781},{"id":13923,"depth":120,"text":13924},{"id":14579,"depth":120,"text":14580},{"id":15606,"depth":120,"text":15607},{"id":16229,"depth":120,"text":16230},{"id":16325,"depth":120,"text":16326},{"id":5564,"depth":120,"text":5565},{"id":7323,"depth":120,"text":7324},{"id":16460,"depth":120,"text":16461},"2026-04-14","gofpdf은 2021년 보관, 후속 go-pdf/fpdf도 2025년 중단. CJK 네이티브·의존성 제로의 순수 Go 라이브러리 gpdf로 옮기는 법.",{"name":16506,"totalTime":16507,"tools":16508,"steps":16509},"Go 프로젝트를 gofpdf에서 gpdf로 마이그레이션하기","PT30M",[1260],[16510,16513,16516,16519,16522,16525],{"name":16511,"text":16512},"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":16514,"text":16515},"커서 대신 빌더로 문서 구성","gpdf.NewDocument에 WithPageSize / WithMargins / WithFont를 넘겨 생성한다. SetXY로 커서를 움직이는 대신 doc.AddPage()로 페이지를 추가하고 RowBuilder와 ColBuilder로 내용을 선언한다.",{"name":16517,"text":16518},"Cell과 MultiCell을 선언형 Text로 변경","pdf.Cell과 pdf.MultiCell을 컬럼 안의 c.Text(...)로 교체한다. 텍스트는 컬럼 경계에서 자동으로 줄바꿈되므로 MultiCell의 마지막 플래그는 필요 없다. 폰트 크기, 굵기, 색상은 per-text 옵션으로 전달한다.",{"name":16520,"text":16521},"CJK 폰트를 WithFont로 등록","한국어·일본어·중국어 텍스트는 pdf.AddUTF8Font 대신 문서 생성 시 gpdf.WithFont(name, ttfBytes)를 넘긴다. TTF 경로 관리도 UTF-8 플래그도 필요 없고, 서브셋 임베딩은 자동으로 처리된다.",{"name":16523,"text":16524},"테이블을 행과 컬럼으로 다시 작성","컬럼 너비를 수동 관리하는 중첩 Cell 루프 대신 AutoRow 안에서 row.Col(n, fn)으로 테이블 행을 만든다. 12컬럼 그리드가 너비 계산과 페이지 분할을 모두 처리한다.",{"name":16526,"text":16527},"출력 호출 전환","pdf.OutputFileAndClose(path) 대신 doc.Generate()로 []byte를 얻고 os.WriteFile(path, data, 0o644)로 저장한다. io.Writer에 바로 쓰려면 doc.Render(w)를 사용한다.",{},{"title":11755,"description":16504},"ko/blog/001.gofpdf-migration",[5706,5707,2551,1286],"8Hxw_yxpwY_bwhDJ7JXtYIFESEHZKv26vsJNo1b8bJM",1776526787752]