[{"data":1,"prerenderedAt":1287},["ShallowReactive",2],{"blog-ko-tofu-boxes-japanese":3},{"id":4,"title":5,"author":6,"body":9,"date":1251,"description":1252,"draft":1253,"extension":1254,"howTo":1255,"image":1277,"meta":1278,"navigation":122,"path":1279,"seo":1280,"stem":1281,"tags":1282,"updated":1277,"__hash__":1286},"blogKo/ko/blog/008.tofu-boxes-japanese.md","gpdf로 만든 PDF에서 일본어가 네모 (두부 문자) 로 나오는 이유와 해결법",{"name":7,"url":8},"gpdf team","https://gpdf.dev",{"type":10,"value":11,"toc":1239},"minimark",[12,17,21,25,28,31,88,92,95,699,713,726,730,733,749,769,775,779,787,932,964,968,975,978,981,1011,1014,1018,1024,1089,1100,1103,1136,1140,1164,1168,1198,1202,1205,1222,1235],[13,14,16],"h2",{"id":15},"질문을-다시-말하면","질문을 다시 말하면",[18,19,20],"p",{},"gpdf로 일본어를 썼는데 출력된 PDF에서 그 문자들이 전부 빈 네모로 나온다. 이게 뭐고, 어떻게 해야 실제 일본어 글리프가 파일 안에 들어가는가.",[13,22,24],{"id":23},"빠른-답","빠른 답",[18,26,27],{},"이것이 두부 문자(tofu)다. PDF 뷰어가 임베드된 폰트에서 해당 유니코드 코드포인트의 글리프를 찾지 못해 자리 표시용 사각형을 그리는 것. 원인은 네 가지이고, 그중 하나가 압도적으로 많다.",[18,29,30],{},"빈도 순:",[32,33,34,51,68,78],"ol",{},[35,36,37,41,42,46,47,50],"li",{},[38,39,40],"strong",{},"CJK 폰트를 등록하지 않았다."," ",[43,44,45],"code",{},"gpdf.NewDocument","에 ",[43,48,49],{},"WithFont"," 호출이 없어서 문서가 PDF Base-14 폰트(Helvetica / Times / Courier)로 폴백한 상태. 이들 중 어느 것도 U+3040–U+9FFF를 커버하지 않는다.",[35,52,53,41,60,63,64,67],{},[38,54,55,56,59],{},"CJK 폰트는 등록했는데 ",[43,57,58],{},"c.Text","의 패밀리명이 다르다.",[43,61,62],{},"WithFont(\"NotoSansJP\", ...)","는 설정했지만 텍스트에 ",[43,65,66],{},"template.FontFamily(\"Arial\")","가 지정되어 있어서, gpdf가 Latin 폰트에서 일본어를 조회한다.",[35,69,70,73,74,77],{},[38,71,72],{},"폰트 파일 자체에 CJK 글리프가 없다."," 디스크의 TTF가 Latin 서브셋(",[43,75,76],{},"NotoSans-Regular.ttf",")이다. 이름은 맞아 보이지만 커버리지가 비어 있다.",[35,79,80,83,84,87],{},[38,81,82],{},"gpdf에 도달하기 전에 바이트가 깨졌다."," 문자열이 상류에서 Shift-JIS나 Latin-1로 디코딩돼, 렌더링하려는 것이 이미 일본어 코드포인트가 아니다. 네모가 아니라 ",[43,85,86],{},"縺ゅ→縺","처럼 나오면 이 경우다.",[13,89,91],{"id":90},"원인-1의-표준-수정","원인 #1의 표준 수정",[18,93,94],{},"열에 아홉은 이것이다:",[96,97,102],"pre",{"className":98,"code":99,"language":100,"meta":101,"style":101},"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","",[43,103,104,117,124,134,146,156,161,171,181,191,197,202,218,257,273,291,297,302,321,345,383,408,433,439,444,463,497,534,556,562,568,573,594,607,622,627,673,688,693],{"__ignoreMap":101},[105,106,109,113],"span",{"class":107,"line":108},"line",1,[105,110,112],{"class":111},"sMK4o","package",[105,114,116],{"class":115},"sBMFI"," main\n",[105,118,120],{"class":107,"line":119},2,[105,121,123],{"emptyLinePlaceholder":122},true,"\n",[105,125,127,131],{"class":107,"line":126},3,[105,128,130],{"class":129},"s7zQu","import",[105,132,133],{"class":111}," (\n",[105,135,137,140,143],{"class":107,"line":136},4,[105,138,139],{"class":111},"    \"",[105,141,142],{"class":115},"log",[105,144,145],{"class":111},"\"\n",[105,147,149,151,154],{"class":107,"line":148},5,[105,150,139],{"class":111},[105,152,153],{"class":115},"os",[105,155,145],{"class":111},[105,157,159],{"class":107,"line":158},6,[105,160,123],{"emptyLinePlaceholder":122},[105,162,164,166,169],{"class":107,"line":163},7,[105,165,139],{"class":111},[105,167,168],{"class":115},"github.com/gpdf-dev/gpdf",[105,170,145],{"class":111},[105,172,174,176,179],{"class":107,"line":173},8,[105,175,139],{"class":111},[105,177,178],{"class":115},"github.com/gpdf-dev/gpdf/document",[105,180,145],{"class":111},[105,182,184,186,189],{"class":107,"line":183},9,[105,185,139],{"class":111},[105,187,188],{"class":115},"github.com/gpdf-dev/gpdf/template",[105,190,145],{"class":111},[105,192,194],{"class":107,"line":193},10,[105,195,196],{"class":111},")\n",[105,198,200],{"class":107,"line":199},11,[105,201,123],{"emptyLinePlaceholder":122},[105,203,205,208,212,215],{"class":107,"line":204},12,[105,206,207],{"class":111},"func",[105,209,211],{"class":210},"s2Zo4"," main",[105,213,214],{"class":111},"()",[105,216,217],{"class":111}," {\n",[105,219,221,225,228,231,234,237,240,243,246,249,253,255],{"class":107,"line":220},13,[105,222,224],{"class":223},"sTEyZ","    font",[105,226,227],{"class":111},",",[105,229,230],{"class":223}," err ",[105,232,233],{"class":111},":=",[105,235,236],{"class":223}," os",[105,238,239],{"class":111},".",[105,241,242],{"class":210},"ReadFile",[105,244,245],{"class":111},"(",[105,247,248],{"class":111},"\"",[105,250,252],{"class":251},"sfazB","NotoSansJP-Regular.ttf",[105,254,248],{"class":111},[105,256,196],{"class":111},[105,258,260,263,265,268,271],{"class":107,"line":259},14,[105,261,262],{"class":129},"    if",[105,264,230],{"class":223},[105,266,267],{"class":111},"!=",[105,269,270],{"class":111}," nil",[105,272,217],{"class":111},[105,274,276,279,281,284,286,289],{"class":107,"line":275},15,[105,277,278],{"class":223},"        log",[105,280,239],{"class":111},[105,282,283],{"class":210},"Fatal",[105,285,245],{"class":111},[105,287,288],{"class":223},"err",[105,290,196],{"class":111},[105,292,294],{"class":107,"line":293},16,[105,295,296],{"class":111},"    }\n",[105,298,300],{"class":107,"line":299},17,[105,301,123],{"emptyLinePlaceholder":122},[105,303,305,308,310,313,315,318],{"class":107,"line":304},18,[105,306,307],{"class":223},"    doc ",[105,309,233],{"class":111},[105,311,312],{"class":223}," gpdf",[105,314,239],{"class":111},[105,316,317],{"class":210},"NewDocument",[105,319,320],{"class":111},"(\n",[105,322,324,327,329,332,334,337,339,342],{"class":107,"line":323},19,[105,325,326],{"class":223},"        gpdf",[105,328,239],{"class":111},[105,330,331],{"class":210},"WithPageSize",[105,333,245],{"class":111},[105,335,336],{"class":223},"gpdf",[105,338,239],{"class":111},[105,340,341],{"class":223},"A4",[105,343,344],{"class":111},"),\n",[105,346,348,350,352,355,357,360,362,365,367,369,371,374,376,380],{"class":107,"line":347},20,[105,349,326],{"class":223},[105,351,239],{"class":111},[105,353,354],{"class":210},"WithMargins",[105,356,245],{"class":111},[105,358,359],{"class":223},"document",[105,361,239],{"class":111},[105,363,364],{"class":210},"UniformEdges",[105,366,245],{"class":111},[105,368,359],{"class":223},[105,370,239],{"class":111},[105,372,373],{"class":210},"Mm",[105,375,245],{"class":111},[105,377,379],{"class":378},"sbssI","20",[105,381,382],{"class":111},"))),\n",[105,384,386,388,390,392,394,396,399,401,403,406],{"class":107,"line":385},21,[105,387,326],{"class":223},[105,389,239],{"class":111},[105,391,49],{"class":210},[105,393,245],{"class":111},[105,395,248],{"class":111},[105,397,398],{"class":251},"NotoSansJP",[105,400,248],{"class":111},[105,402,227],{"class":111},[105,404,405],{"class":223}," font",[105,407,344],{"class":111},[105,409,411,413,415,418,420,422,424,426,428,431],{"class":107,"line":410},22,[105,412,326],{"class":223},[105,414,239],{"class":111},[105,416,417],{"class":210},"WithDefaultFont",[105,419,245],{"class":111},[105,421,248],{"class":111},[105,423,398],{"class":251},[105,425,248],{"class":111},[105,427,227],{"class":111},[105,429,430],{"class":378}," 12",[105,432,344],{"class":111},[105,434,436],{"class":107,"line":435},23,[105,437,438],{"class":111},"    )\n",[105,440,442],{"class":107,"line":441},24,[105,443,123],{"emptyLinePlaceholder":122},[105,445,447,450,452,455,457,460],{"class":107,"line":446},25,[105,448,449],{"class":223},"    page ",[105,451,233],{"class":111},[105,453,454],{"class":223}," doc",[105,456,239],{"class":111},[105,458,459],{"class":210},"AddPage",[105,461,462],{"class":111},"()\n",[105,464,466,469,471,474,477,481,484,487,489,492,495],{"class":107,"line":465},26,[105,467,468],{"class":223},"    page",[105,470,239],{"class":111},[105,472,473],{"class":210},"AutoRow",[105,475,476],{"class":111},"(func(",[105,478,480],{"class":479},"sHdIc","r",[105,482,483],{"class":111}," *",[105,485,486],{"class":115},"template",[105,488,239],{"class":111},[105,490,491],{"class":115},"RowBuilder",[105,493,494],{"class":111},")",[105,496,217],{"class":111},[105,498,500,503,505,508,510,513,515,518,521,523,525,527,530,532],{"class":107,"line":499},27,[105,501,502],{"class":223},"        r",[105,504,239],{"class":111},[105,506,507],{"class":210},"Col",[105,509,245],{"class":111},[105,511,512],{"class":378},"12",[105,514,227],{"class":111},[105,516,517],{"class":111}," func(",[105,519,520],{"class":479},"c",[105,522,483],{"class":111},[105,524,486],{"class":115},[105,526,239],{"class":111},[105,528,529],{"class":115},"ColBuilder",[105,531,494],{"class":111},[105,533,217],{"class":111},[105,535,537,540,542,545,547,549,552,554],{"class":107,"line":536},28,[105,538,539],{"class":223},"            c",[105,541,239],{"class":111},[105,543,544],{"class":210},"Text",[105,546,245],{"class":111},[105,548,248],{"class":111},[105,550,551],{"class":251},"こんにちは、世界。",[105,553,248],{"class":111},[105,555,196],{"class":111},[105,557,559],{"class":107,"line":558},29,[105,560,561],{"class":111},"        })\n",[105,563,565],{"class":107,"line":564},30,[105,566,567],{"class":111},"    })\n",[105,569,571],{"class":107,"line":570},31,[105,572,123],{"emptyLinePlaceholder":122},[105,574,576,579,581,583,585,587,589,592],{"class":107,"line":575},32,[105,577,578],{"class":223},"    data",[105,580,227],{"class":111},[105,582,230],{"class":223},[105,584,233],{"class":111},[105,586,454],{"class":223},[105,588,239],{"class":111},[105,590,591],{"class":210},"Generate",[105,593,462],{"class":111},[105,595,597,599,601,603,605],{"class":107,"line":596},33,[105,598,262],{"class":129},[105,600,230],{"class":223},[105,602,267],{"class":111},[105,604,270],{"class":111},[105,606,217],{"class":111},[105,608,610,612,614,616,618,620],{"class":107,"line":609},34,[105,611,278],{"class":223},[105,613,239],{"class":111},[105,615,283],{"class":210},[105,617,245],{"class":111},[105,619,288],{"class":223},[105,621,196],{"class":111},[105,623,625],{"class":107,"line":624},35,[105,626,296],{"class":111},[105,628,630,632,634,636,638,640,643,645,647,650,652,654,657,659,662,665,667,669,671],{"class":107,"line":629},36,[105,631,262],{"class":129},[105,633,230],{"class":223},[105,635,233],{"class":111},[105,637,236],{"class":223},[105,639,239],{"class":111},[105,641,642],{"class":210},"WriteFile",[105,644,245],{"class":111},[105,646,248],{"class":111},[105,648,649],{"class":251},"hello.pdf",[105,651,248],{"class":111},[105,653,227],{"class":111},[105,655,656],{"class":223}," data",[105,658,227],{"class":111},[105,660,661],{"class":378}," 0o644",[105,663,664],{"class":111},");",[105,666,230],{"class":223},[105,668,267],{"class":111},[105,670,270],{"class":111},[105,672,217],{"class":111},[105,674,676,678,680,682,684,686],{"class":107,"line":675},37,[105,677,278],{"class":223},[105,679,239],{"class":111},[105,681,283],{"class":210},[105,683,245],{"class":111},[105,685,288],{"class":223},[105,687,196],{"class":111},[105,689,691],{"class":107,"line":690},38,[105,692,296],{"class":111},[105,694,696],{"class":107,"line":695},39,[105,697,698],{"class":111},"}\n",[18,700,701,702,705,706,709,710,712],{},"두 줄로 폰트 등록과 기본 설정이 끝난다. CGO도, ",[43,703,704],{},"AddUTF8Font"," 설정 단계도 필요 없다. ",[43,707,708],{},"□□□□□、□□。","로 나오던 것이 이 코드와 실제 ",[43,711,252],{},"를 나란히 두고 실행하면 제대로 된 글리프로 나온다.",[18,714,715,717,718,725],{},[43,716,252],{},"는 ",[719,720,724],"a",{"href":721,"rel":722},"https://fonts.google.com/noto/specimen/Noto+Sans+JP",[723],"nofollow","Google Fonts","에서 받는다.",[13,727,729],{"id":728},"어떤-원인인지-판별하기","어떤 원인인지 판별하기",[18,731,732],{},"봐야 할 곳은 세 군데다. 문서를 만드는 부분, 텍스트를 쓰는 부분, TTF 파일 그 자체.",[18,734,735,741,742,745,746,748],{},[38,736,737,738],{},"출력이 일관된 ",[43,739,740],{},"□□□"," (모든 네모가 같은 모양)이라면 원인 1, 2, 3 중 하나. PDF에는 폰트가 임베드됐지만 그 폰트에 글리프가 없는 상태다. Acrobat에서 PDF를 열고 ",[43,743,744],{},"파일 → 속성 → 폰트","에서 실제로 임베드된 폰트를 본다. Helvetica / Times / Courier뿐이면 원인 1. ",[43,747,398],{},"가 나열돼 있는데도 네모면 원인 2 또는 3.",[18,750,751,760,761,764,765,768],{},[38,752,753,754,756,757],{},"출력이 ",[43,755,86],{},"이나 ",[43,758,759],{},"ã\"ã‚\"ã«ã¡ã¯"," 같은 Latin 잡음이면 원인 4. 일본어 문자열이 gpdf에 도달하기 전에 다시 인코딩됐다. 가장 흔한 범인은 Excel이 Shift-JIS로 저장한 CSV를 ",[43,762,763],{},"os.ReadFile","로 읽어 UTF-8로 그대로 쓰는 경우, 또는 ",[43,766,767],{},"charset=utf-8","을 선언하지 않은 HTTP 엔드포인트. 고쳐야 할 건 디코더지 PDF가 아니다.",[18,770,771,774],{},[38,772,773],{},"섞여서 나옴"," — 일부는 정상, 일부는 네모 — 라면 폰트 커버리지가 부분적이라는 뜻. \"일본어 지원\"이라고 표기된 폰트가 히라가나·가타카나는 포함해도 鬱, 龠 같은 드문 한자를 빠뜨리는 경우가 있다. JIS X 0213을 커버하는 Noto Sans JP나 Source Han Sans JP로 바꾸면 해결된다.",[13,776,778],{"id":777},"원인-2-상세-폰트는-맞는데-패밀리명이-틀림","원인 2 상세: 폰트는 맞는데 패밀리명이 틀림",[18,780,781,782,786],{},"이게 까다로운 이유는 폰트가 ",[783,784,785],"em",{},"실제로"," 임베드됐기 때문이다 — 그냥 안 쓰일 뿐. 최소 재현:",[96,788,790],{"className":98,"code":789,"language":100,"meta":101,"style":101},"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",[43,791,792,807,830,836,840,844,869,900,923,927],{"__ignoreMap":101},[105,793,794,797,799,801,803,805],{"class":107,"line":108},[105,795,796],{"class":223},"doc ",[105,798,233],{"class":111},[105,800,312],{"class":223},[105,802,239],{"class":111},[105,804,317],{"class":210},[105,806,320],{"class":111},[105,808,809,812,814,816,818,820,822,824,826,828],{"class":107,"line":119},[105,810,811],{"class":223},"    gpdf",[105,813,239],{"class":111},[105,815,49],{"class":210},[105,817,245],{"class":111},[105,819,248],{"class":111},[105,821,398],{"class":251},[105,823,248],{"class":111},[105,825,227],{"class":111},[105,827,405],{"class":223},[105,829,344],{"class":111},[105,831,832],{"class":107,"line":126},[105,833,835],{"class":834},"sHwdD","    // WithDefaultFont 누락\n",[105,837,838],{"class":107,"line":136},[105,839,196],{"class":111},[105,841,842],{"class":107,"line":148},[105,843,123],{"emptyLinePlaceholder":122},[105,845,846,849,851,853,855,857,859,861,863,865,867],{"class":107,"line":158},[105,847,848],{"class":223},"page",[105,850,239],{"class":111},[105,852,473],{"class":210},[105,854,476],{"class":111},[105,856,480],{"class":479},[105,858,483],{"class":111},[105,860,486],{"class":115},[105,862,239],{"class":111},[105,864,491],{"class":115},[105,866,494],{"class":111},[105,868,217],{"class":111},[105,870,871,874,876,878,880,882,884,886,888,890,892,894,896,898],{"class":107,"line":163},[105,872,873],{"class":223},"    r",[105,875,239],{"class":111},[105,877,507],{"class":210},[105,879,245],{"class":111},[105,881,512],{"class":378},[105,883,227],{"class":111},[105,885,517],{"class":111},[105,887,520],{"class":479},[105,889,483],{"class":111},[105,891,486],{"class":115},[105,893,239],{"class":111},[105,895,529],{"class":115},[105,897,494],{"class":111},[105,899,217],{"class":111},[105,901,902,905,907,909,911,913,916,918,920],{"class":107,"line":173},[105,903,904],{"class":223},"        c",[105,906,239],{"class":111},[105,908,544],{"class":210},[105,910,245],{"class":111},[105,912,248],{"class":111},[105,914,915],{"class":251},"こんにちは",[105,917,248],{"class":111},[105,919,494],{"class":111},[105,921,922],{"class":834}," // 기본 폰트 = Helvetica로 그려짐\n",[105,924,925],{"class":107,"line":183},[105,926,567],{"class":111},[105,928,929],{"class":107,"line":193},[105,930,931],{"class":111},"})\n",[18,933,934,935,46,937,940,941,46,943,946,947,949,950,952,953,956,957,959,960,963],{},"수정: ",[43,936,317],{},[43,938,939],{},"gpdf.WithDefaultFont(\"NotoSansJP\", 12)","를 추가하거나, 일본어를 쓰는 모든 ",[43,942,58],{},[43,944,945],{},"template.FontFamily(\"NotoSansJP\")","를 넘긴다. ",[43,948,49],{},"의 패밀리명과 ",[43,951,58],{},"의 패밀리명은 ",[38,954,955],{},"대소문자까지 완전히 일치","해야 한다. ",[43,958,398],{},"와 ",[43,961,962],{},"notosansjp","는 gpdf 입장에서 다른 폰트다.",[13,965,967],{"id":966},"원인-3-상세-잘못된-ttf-파일","원인 3 상세: 잘못된 TTF 파일",[18,969,970,959,972,974],{},[43,971,76],{},[43,973,252],{},"는 다른 파일이다. 전자는 CJK 커버리지가 0인 Latin 폰트. 후자는 약 17,000자의 일본어 컷. 디렉토리 리스트에서 거의 똑같이 보이고, 자동완성이 틀린 쪽을 집어주기 쉽다.",[18,976,977],{},"gpdf는 등록 시점에 글리프 커버리지를 검증하지 않는다. 바이트를 주면 믿는다. 실패는 렌더링 시점의 두부 문자로만 드러난다.",[18,979,980],{},"빠른 확인:",[982,983,984,991,998],"ul",{},[35,985,986,987,990],{},"macOS: ",[43,988,989],{},"Font Book","에서 파일을 더블 클릭하면 글리프 그리드가 나온다",[35,992,993,994,997],{},"Linux: ",[43,995,996],{},"otfinfo -u NotoSans-Regular.ttf","가 유니코드 커버리지를 덤프한다",[35,999,1000,1001,1006,1007,1010],{},"크로스플랫폼: ",[719,1002,1005],{"href":1003,"rel":1004},"https://github.com/fonttools/fonttools",[723],"fontTools","의 ",[43,1008,1009],{},"ttx -t cmap NotoSans-Regular.ttf","가 cmap 테이블을 XML로 내보낸다",[18,1012,1013],{},"목록에 U+3042 (あ)가 없으면 Latin 서브셋을 쥐고 있는 것이다.",[13,1015,1017],{"id":1016},"원인-4-상세-인코딩-손상","원인 4 상세: 인코딩 손상",[18,1019,1020,1021,1023],{},"이건 사실 gpdf와 무관하다. ",[43,1022,58],{},"에 넘긴 시점에 문자열이 이미 깨져 있다. 렌더 전에 출력해본다:",[96,1025,1027],{"className":98,"code":1026,"language":100,"meta":101,"style":101},"text := loadLabelFromSomewhere()\nfmt.Printf(\"%q\\n\", text) // 실제 rune 출력\nc.Text(text)\n",[43,1028,1029,1041,1074],{"__ignoreMap":101},[105,1030,1031,1034,1036,1039],{"class":107,"line":108},[105,1032,1033],{"class":223},"text ",[105,1035,233],{"class":111},[105,1037,1038],{"class":210}," loadLabelFromSomewhere",[105,1040,462],{"class":111},[105,1042,1043,1046,1048,1051,1053,1055,1059,1062,1064,1066,1069,1071],{"class":107,"line":119},[105,1044,1045],{"class":223},"fmt",[105,1047,239],{"class":111},[105,1049,1050],{"class":210},"Printf",[105,1052,245],{"class":111},[105,1054,248],{"class":111},[105,1056,1058],{"class":1057},"swJcz","%q",[105,1060,1061],{"class":223},"\\n",[105,1063,248],{"class":111},[105,1065,227],{"class":111},[105,1067,1068],{"class":223}," text",[105,1070,494],{"class":111},[105,1072,1073],{"class":834}," // 실제 rune 출력\n",[105,1075,1076,1078,1080,1082,1084,1087],{"class":107,"line":126},[105,1077,520],{"class":223},[105,1079,239],{"class":111},[105,1081,544],{"class":210},[105,1083,245],{"class":111},[105,1085,1086],{"class":223},"text",[105,1088,196],{"class":111},[18,1090,1091,1092,1095,1096,1099],{},"여기서 ",[43,1093,1094],{},"\"あいうえ\""," 대신 ",[43,1097,1098],{},"\"縺ゅ→縺\"","가 나오면 손상은 상류에서 일어났다. gpdf는 못 고친다 — UTF-8이 잘못 디코딩된 지점을 찾아 수정한다.",[18,1101,1102],{},"흔한 상류 범인:",[982,1104,1105,1115,1130],{},[35,1106,1107,1108,1110,1111,1114],{},"Excel이 Shift-JIS(CP949와 혼동 주의, 일본은 CP932)로 저장한 CSV를 ",[43,1109,763],{}," 후 바로 ",[43,1112,1113],{},"string()"," 캐스팅",[35,1116,1117,1118,1121,1122,1125,1126,1129],{},"이미 mojibake를 저장하고 있는 ",[43,1119,1120],{},"latin1"," 또는 ",[43,1123,1124],{},"utf8mb3"," 컬럼(",[43,1127,1128],{},"utf8mb4"," 아님)",[35,1131,1132,1135],{},[43,1133,1134],{},"Content-Type: application/json; charset=utf-8"," 선언이 없어서 Latin-1로 추측한 HTTP 응답",[13,1137,1139],{"id":1138},"한-가지-잊기-쉬운-엣지-케이스","한 가지 잊기 쉬운 엣지 케이스",[18,1141,1142,1143,1146,1147,1149,1150,1153,1154,1157,1158,1160,1161,1163],{},"gpdf의 서브셋 고정은 ",[43,1144,1145],{},"Generate()"," 시점에 일어난다. 문서 구성 중에 ",[43,1148,915],{},"를 그린 뒤 나중에 ",[43,1151,1152],{},"鬱陶しい","를 그려도, 두 번째도 서브셋에 제대로 추가된다. 하지만 ",[38,1155,1156],{},"이미 생성된 PDF를 Acrobat에서 열어 원본에 없던 한자를 타이핑","하면 그 자리는 두부가 된다. 서브셋은 ",[43,1159,1145],{}," 순간에 얼어붙기 때문이다. PDF를 후편집하지 말고 Go에서 다시 ",[43,1162,1145],{}," 한다.",[13,1165,1167],{"id":1166},"관련-레시피","관련 레시피",[982,1169,1170,1180,1191],{},[35,1171,1172,1176,1177,1179],{},[719,1173,1175],{"href":1174},"/ko/blog/embed-japanese-font","gpdf에서 일본어 폰트를 임베드하려면?"," — 볼드/이탤릭 변형과 다중 CJK 문서를 포함한 ",[43,1178,49],{}," 전체 안내",[35,1181,1182,1186,1187,1190],{},[719,1183,1185],{"href":1184},"/ko/blog/noto-sans-jp-with-gpdf","gpdf에서 Noto Sans JP를 사용하려면?"," — 어떤 Noto 파일을 고를지, ",[43,1188,1189],{},"go:embed","로 배포를 어떻게 단순화할지",[35,1192,1193,1197],{},[719,1194,1196],{"href":1195},"/ko/blog/japanese-pdf-in-go","Go에서 일본어 PDF 만들기 결정판 가이드(2026)"," — 폰트, 세로쓰기, ruby, 일본어 특유 레이아웃을 다루는 장편 가이드",[13,1199,1201],{"id":1200},"gpdf를-써보자","gpdf를 써보자",[18,1203,1204],{},"gpdf는 Go용 PDF 생성 라이브러리다. MIT 라이선스, 외부 의존성 0, 네이티브 CJK 지원.",[96,1206,1210],{"className":1207,"code":1208,"language":1209,"meta":101,"style":101},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go get github.com/gpdf-dev/gpdf\n","bash",[43,1211,1212],{"__ignoreMap":101},[105,1213,1214,1216,1219],{"class":107,"line":108},[105,1215,100],{"class":115},[105,1217,1218],{"class":251}," get",[105,1220,1221],{"class":251}," github.com/gpdf-dev/gpdf\n",[18,1223,1224,1229,1230],{},[719,1225,1228],{"href":1226,"rel":1227},"https://github.com/gpdf-dev/gpdf",[723],"⭐ GitHub에서 Star"," · ",[719,1231,1234],{"href":1232,"rel":1233},"https://gpdf.dev/ko/docs/quickstart",[723],"문서 읽기",[1236,1237,1238],"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":101,"searchDepth":119,"depth":119,"links":1240},[1241,1242,1243,1244,1245,1246,1247,1248,1249,1250],{"id":15,"depth":119,"text":16},{"id":23,"depth":119,"text":24},{"id":90,"depth":119,"text":91},{"id":728,"depth":119,"text":729},{"id":777,"depth":119,"text":778},{"id":966,"depth":119,"text":967},{"id":1016,"depth":119,"text":1017},{"id":1138,"depth":119,"text":1139},{"id":1166,"depth":119,"text":1167},{"id":1200,"depth":119,"text":1201},"2026-04-17","일본어가 □ 로 나오는 건 대부분 폰트 미등록. 흔한 4가지 원인과 최단 수정법을 정리한다.",false,"md",{"name":1256,"totalTime":1257,"tools":1258,"steps":1261},"gpdf 문서의 두부 문자를 진단하고 고친다","PT15M",[1259,1260],"Go 1.22+","CJK를 지원하는 TTF (예: NotoSansJP-Regular.ttf)",[1262,1265,1268,1271,1274],{"name":1263,"text":1264},"증상이 두부 문자인지 mojibake인지 구분한다","PDF를 연다. 일본어가 빈 사각형(□)이면 폰트 조회 실패. 縺ゅ→縺 같은 형태면 gpdf에 도달하기 전에 UTF-8이 잘못 디코딩된 것이다. 해결 경로가 완전히 다르다.",{"name":1266,"text":1267},"CJK 폰트가 등록되어 있는지 확인한다","문서 생성 부분에서 gpdf.WithFont를 검색한다. CJK TTF가 등록되어 있지 않으면 gpdf는 PDF Base-14 폰트로 폴백하고, 이들은 CJK 코드포인트를 전혀 다루지 않는다.",{"name":1269,"text":1270},"각 c.Text의 폰트 패밀리명을 검증한다","WithDefaultFont가 없다면 일본어를 그리는 모든 c.Text에 template.FontFamily(\"NotoSansJP\")를 명시해야 한다. 이름이 맞지 않으면 조용히 기본 폰트로 폴백한다.",{"name":1272,"text":1273},"TTF 파일에 실제로 CJK 글리프가 있는지 확인한다","NotoSans-Regular.ttf (Latin 서브셋) 와 NotoSansJP-Regular.ttf는 다른 파일이다. gpdf는 등록 시점에 커버리지를 검증하지 않는다.",{"name":1275,"text":1276},"두 개의 뷰어에서 재확인한다","생성한 PDF를 Adobe Acrobat과 Chrome 양쪽에서 연다. 둘 다 정상 렌더링되어야 OK. 한쪽만 정상이면 글리프는 임베드됐지만 서브셋 등록이 어긋난 경우다.",null,{},"/ko/blog/tofu-boxes-japanese",{"title":5,"description":1252},"ko/blog/008.tofu-boxes-japanese",[1283,1284,1285],"recipe","troubleshooting","cjk","GIOTuFQGRC84xnT5i01kWQ0hpkKcc8JRyxTc4jsIS3Y",1776529266451]