[{"data":1,"prerenderedAt":1309},["ShallowReactive",2],{"blog-ja-tofu-boxes-japanese":3},{"id":4,"title":5,"author":6,"body":9,"date":1273,"description":1274,"draft":1275,"extension":1276,"howTo":1277,"image":1299,"meta":1300,"navigation":121,"path":1301,"seo":1302,"stem":1303,"tags":1304,"updated":1299,"__hash__":1308},"blogJa/ja/blog/008.tofu-boxes-japanese.md","gpdf で日本語が豆腐文字 (□□□) になる原因と直し方",{"name":7,"url":8},"gpdf team","https://gpdf.dev",{"type":10,"value":11,"toc":1261},"minimark",[12,16,20,23,26,29,90,94,698,712,725,728,731,747,778,784,788,795,940,972,976,987,990,993,1023,1026,1030,1036,1101,1108,1111,1162,1166,1187,1190,1220,1224,1227,1244,1257],[13,14,15],"h2",{"id":15},"質問を言い換えると",[17,18,19],"p",{},"gpdf で日本語を書いたら、PDF にしたときに文字が □ で出てしまう。これは何で、どう直すのか。",[13,21,22],{"id":22},"速答",[17,24,25],{},"これは豆腐文字 (tofu)。PDF ビューアが、埋め込まれているフォントに該当コードポイントのグリフがないときに代替で描く矩形。原因は 4 つあり、そのうち 1 つが圧倒的に多い。",[17,27,28],{},"頻度順:",[30,31,32,49,66,76],"ol",{},[33,34,35,39,40,44,45,48],"li",{},[36,37,38],"strong",{},"CJK フォントを登録していない。"," ",[41,42,43],"code",{},"gpdf.NewDocument"," に ",[41,46,47],{},"WithFont"," がなく、PDF 標準 14 フォント (Helvetica / Times / Courier) にフォールバックしている。どれも U+3040〜U+9FFF をカバーしない。",[33,50,51,39,58,61,62,65],{},[36,52,53,54,57],{},"CJK フォントは登録したが ",[41,55,56],{},"c.Text"," のファミリ名が違う。",[41,59,60],{},"WithFont(\"NotoSansJP\", ...)"," はあるのに ",[41,63,64],{},"template.FontFamily(\"Arial\")"," を指定していて、Latin フォントで日本語を引きにいっている。",[33,67,68,71,72,75],{},[36,69,70],{},"TTF ファイル自体に CJK グリフが入っていない。"," ディスク上の TTF が Latin サブセット (",[41,73,74],{},"NotoSans-Regular.ttf",") で、見た目のファイル名は正しそうだが中身に日本語グリフがない。",[33,77,78,81,82,89],{},[36,79,80],{},"gpdf に渡す前に文字列が化けている。"," Shift-JIS や Latin-1 として読んでしまった文字列を UTF-8 として扱っており、そもそも日本語の Unicode コードポイントではなくなっている。",[36,83,84,85,88],{},"□ ではなく ",[41,86,87],{},"縺ゅ→縺"," のような化け方"," ならこれ。",[13,91,93],{"id":92},"原因-1-の直し方-これで-9-割片付く","原因 #1 の直し方 (これで 9 割片付く)",[95,96,101],"pre",{"className":97,"code":98,"language":99,"meta":100,"style":100},"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","",[41,102,103,116,123,133,145,155,160,170,180,190,196,201,217,256,272,290,296,301,320,344,382,407,432,438,443,462,496,533,555,561,567,572,593,606,621,626,672,687,692],{"__ignoreMap":100},[104,105,108,112],"span",{"class":106,"line":107},"line",1,[104,109,111],{"class":110},"sMK4o","package",[104,113,115],{"class":114},"sBMFI"," main\n",[104,117,119],{"class":106,"line":118},2,[104,120,122],{"emptyLinePlaceholder":121},true,"\n",[104,124,126,130],{"class":106,"line":125},3,[104,127,129],{"class":128},"s7zQu","import",[104,131,132],{"class":110}," (\n",[104,134,136,139,142],{"class":106,"line":135},4,[104,137,138],{"class":110},"    \"",[104,140,141],{"class":114},"log",[104,143,144],{"class":110},"\"\n",[104,146,148,150,153],{"class":106,"line":147},5,[104,149,138],{"class":110},[104,151,152],{"class":114},"os",[104,154,144],{"class":110},[104,156,158],{"class":106,"line":157},6,[104,159,122],{"emptyLinePlaceholder":121},[104,161,163,165,168],{"class":106,"line":162},7,[104,164,138],{"class":110},[104,166,167],{"class":114},"github.com/gpdf-dev/gpdf",[104,169,144],{"class":110},[104,171,173,175,178],{"class":106,"line":172},8,[104,174,138],{"class":110},[104,176,177],{"class":114},"github.com/gpdf-dev/gpdf/document",[104,179,144],{"class":110},[104,181,183,185,188],{"class":106,"line":182},9,[104,184,138],{"class":110},[104,186,187],{"class":114},"github.com/gpdf-dev/gpdf/template",[104,189,144],{"class":110},[104,191,193],{"class":106,"line":192},10,[104,194,195],{"class":110},")\n",[104,197,199],{"class":106,"line":198},11,[104,200,122],{"emptyLinePlaceholder":121},[104,202,204,207,211,214],{"class":106,"line":203},12,[104,205,206],{"class":110},"func",[104,208,210],{"class":209},"s2Zo4"," main",[104,212,213],{"class":110},"()",[104,215,216],{"class":110}," {\n",[104,218,220,224,227,230,233,236,239,242,245,248,252,254],{"class":106,"line":219},13,[104,221,223],{"class":222},"sTEyZ","    font",[104,225,226],{"class":110},",",[104,228,229],{"class":222}," err ",[104,231,232],{"class":110},":=",[104,234,235],{"class":222}," os",[104,237,238],{"class":110},".",[104,240,241],{"class":209},"ReadFile",[104,243,244],{"class":110},"(",[104,246,247],{"class":110},"\"",[104,249,251],{"class":250},"sfazB","NotoSansJP-Regular.ttf",[104,253,247],{"class":110},[104,255,195],{"class":110},[104,257,259,262,264,267,270],{"class":106,"line":258},14,[104,260,261],{"class":128},"    if",[104,263,229],{"class":222},[104,265,266],{"class":110},"!=",[104,268,269],{"class":110}," nil",[104,271,216],{"class":110},[104,273,275,278,280,283,285,288],{"class":106,"line":274},15,[104,276,277],{"class":222},"        log",[104,279,238],{"class":110},[104,281,282],{"class":209},"Fatal",[104,284,244],{"class":110},[104,286,287],{"class":222},"err",[104,289,195],{"class":110},[104,291,293],{"class":106,"line":292},16,[104,294,295],{"class":110},"    }\n",[104,297,299],{"class":106,"line":298},17,[104,300,122],{"emptyLinePlaceholder":121},[104,302,304,307,309,312,314,317],{"class":106,"line":303},18,[104,305,306],{"class":222},"    doc ",[104,308,232],{"class":110},[104,310,311],{"class":222}," gpdf",[104,313,238],{"class":110},[104,315,316],{"class":209},"NewDocument",[104,318,319],{"class":110},"(\n",[104,321,323,326,328,331,333,336,338,341],{"class":106,"line":322},19,[104,324,325],{"class":222},"        gpdf",[104,327,238],{"class":110},[104,329,330],{"class":209},"WithPageSize",[104,332,244],{"class":110},[104,334,335],{"class":222},"gpdf",[104,337,238],{"class":110},[104,339,340],{"class":222},"A4",[104,342,343],{"class":110},"),\n",[104,345,347,349,351,354,356,359,361,364,366,368,370,373,375,379],{"class":106,"line":346},20,[104,348,325],{"class":222},[104,350,238],{"class":110},[104,352,353],{"class":209},"WithMargins",[104,355,244],{"class":110},[104,357,358],{"class":222},"document",[104,360,238],{"class":110},[104,362,363],{"class":209},"UniformEdges",[104,365,244],{"class":110},[104,367,358],{"class":222},[104,369,238],{"class":110},[104,371,372],{"class":209},"Mm",[104,374,244],{"class":110},[104,376,378],{"class":377},"sbssI","20",[104,380,381],{"class":110},"))),\n",[104,383,385,387,389,391,393,395,398,400,402,405],{"class":106,"line":384},21,[104,386,325],{"class":222},[104,388,238],{"class":110},[104,390,47],{"class":209},[104,392,244],{"class":110},[104,394,247],{"class":110},[104,396,397],{"class":250},"NotoSansJP",[104,399,247],{"class":110},[104,401,226],{"class":110},[104,403,404],{"class":222}," font",[104,406,343],{"class":110},[104,408,410,412,414,417,419,421,423,425,427,430],{"class":106,"line":409},22,[104,411,325],{"class":222},[104,413,238],{"class":110},[104,415,416],{"class":209},"WithDefaultFont",[104,418,244],{"class":110},[104,420,247],{"class":110},[104,422,397],{"class":250},[104,424,247],{"class":110},[104,426,226],{"class":110},[104,428,429],{"class":377}," 12",[104,431,343],{"class":110},[104,433,435],{"class":106,"line":434},23,[104,436,437],{"class":110},"    )\n",[104,439,441],{"class":106,"line":440},24,[104,442,122],{"emptyLinePlaceholder":121},[104,444,446,449,451,454,456,459],{"class":106,"line":445},25,[104,447,448],{"class":222},"    page ",[104,450,232],{"class":110},[104,452,453],{"class":222}," doc",[104,455,238],{"class":110},[104,457,458],{"class":209},"AddPage",[104,460,461],{"class":110},"()\n",[104,463,465,468,470,473,476,480,483,486,488,491,494],{"class":106,"line":464},26,[104,466,467],{"class":222},"    page",[104,469,238],{"class":110},[104,471,472],{"class":209},"AutoRow",[104,474,475],{"class":110},"(func(",[104,477,479],{"class":478},"sHdIc","r",[104,481,482],{"class":110}," *",[104,484,485],{"class":114},"template",[104,487,238],{"class":110},[104,489,490],{"class":114},"RowBuilder",[104,492,493],{"class":110},")",[104,495,216],{"class":110},[104,497,499,502,504,507,509,512,514,517,520,522,524,526,529,531],{"class":106,"line":498},27,[104,500,501],{"class":222},"        r",[104,503,238],{"class":110},[104,505,506],{"class":209},"Col",[104,508,244],{"class":110},[104,510,511],{"class":377},"12",[104,513,226],{"class":110},[104,515,516],{"class":110}," func(",[104,518,519],{"class":478},"c",[104,521,482],{"class":110},[104,523,485],{"class":114},[104,525,238],{"class":110},[104,527,528],{"class":114},"ColBuilder",[104,530,493],{"class":110},[104,532,216],{"class":110},[104,534,536,539,541,544,546,548,551,553],{"class":106,"line":535},28,[104,537,538],{"class":222},"            c",[104,540,238],{"class":110},[104,542,543],{"class":209},"Text",[104,545,244],{"class":110},[104,547,247],{"class":110},[104,549,550],{"class":250},"こんにちは、世界。",[104,552,247],{"class":110},[104,554,195],{"class":110},[104,556,558],{"class":106,"line":557},29,[104,559,560],{"class":110},"        })\n",[104,562,564],{"class":106,"line":563},30,[104,565,566],{"class":110},"    })\n",[104,568,570],{"class":106,"line":569},31,[104,571,122],{"emptyLinePlaceholder":121},[104,573,575,578,580,582,584,586,588,591],{"class":106,"line":574},32,[104,576,577],{"class":222},"    data",[104,579,226],{"class":110},[104,581,229],{"class":222},[104,583,232],{"class":110},[104,585,453],{"class":222},[104,587,238],{"class":110},[104,589,590],{"class":209},"Generate",[104,592,461],{"class":110},[104,594,596,598,600,602,604],{"class":106,"line":595},33,[104,597,261],{"class":128},[104,599,229],{"class":222},[104,601,266],{"class":110},[104,603,269],{"class":110},[104,605,216],{"class":110},[104,607,609,611,613,615,617,619],{"class":106,"line":608},34,[104,610,277],{"class":222},[104,612,238],{"class":110},[104,614,282],{"class":209},[104,616,244],{"class":110},[104,618,287],{"class":222},[104,620,195],{"class":110},[104,622,624],{"class":106,"line":623},35,[104,625,295],{"class":110},[104,627,629,631,633,635,637,639,642,644,646,649,651,653,656,658,661,664,666,668,670],{"class":106,"line":628},36,[104,630,261],{"class":128},[104,632,229],{"class":222},[104,634,232],{"class":110},[104,636,235],{"class":222},[104,638,238],{"class":110},[104,640,641],{"class":209},"WriteFile",[104,643,244],{"class":110},[104,645,247],{"class":110},[104,647,648],{"class":250},"hello.pdf",[104,650,247],{"class":110},[104,652,226],{"class":110},[104,654,655],{"class":222}," data",[104,657,226],{"class":110},[104,659,660],{"class":377}," 0o644",[104,662,663],{"class":110},");",[104,665,229],{"class":222},[104,667,266],{"class":110},[104,669,269],{"class":110},[104,671,216],{"class":110},[104,673,675,677,679,681,683,685],{"class":106,"line":674},37,[104,676,277],{"class":222},[104,678,238],{"class":110},[104,680,282],{"class":209},[104,682,244],{"class":110},[104,684,287],{"class":222},[104,686,195],{"class":110},[104,688,690],{"class":106,"line":689},38,[104,691,295],{"class":110},[104,693,695],{"class":106,"line":694},39,[104,696,697],{"class":110},"}\n",[17,699,700,701,704,705,708,709,711],{},"2 行で登録と既定設定が済む。CGO なし、",[41,702,703],{},"AddUTF8Font"," の段取りもなし。",[41,706,707],{},"□□□□□、□□。"," になっていたものが、このコードと ",[41,710,251],{}," を同じディレクトリに置いて実行すれば正しい日本語で出る。",[17,713,714,716,717,724],{},[41,715,251],{}," は ",[718,719,723],"a",{"href":720,"rel":721},"https://fonts.google.com/noto/specimen/Noto+Sans+JP",[722],"nofollow","Google Fonts"," から。",[13,726,727],{"id":727},"どの原因か見分ける",[17,729,730],{},"見るべきは 3 箇所 — ドキュメントを組み立てているところ、テキストを書いているところ、TTF ファイルそのもの。",[17,732,733,739,740,743,744,746],{},[36,734,735,736],{},"出力が一様な ",[41,737,738],{},"□□□"," (全部同じ矩形) なら原因 1・2・3 のいずれか。PDF にはフォントが埋め込まれているが、そのフォントに必要なグリフがない状態。PDF を Acrobat で開き ",[41,741,742],{},"ファイル → プロパティ → フォント"," でどれが埋め込まれているか見る。Helvetica / Times / Courier だけなら原因 1。",[41,745,397],{}," が載っているのに豆腐なら原因 2 か 3。",[17,748,749,758,759,762,763,766,767,770,771,754,774,777],{},[36,750,751,752,754,755],{},"出力が ",[41,753,87],{}," や ",[41,756,757],{},"ã\"ã‚\"ã«ã¡ã¯"," のような Latin 交じりの化け方なら原因 4。日本語文字列が gpdf に渡る前に再エンコードされている。日本で一番多いのが ",[36,760,761],{},"Excel で保存した Shift-JIS CSV"," を ",[41,764,765],{},"os.ReadFile"," でそのまま ",[41,768,769],{},"string"," にしているケース。次点は MySQL のカラム定義が ",[41,772,773],{},"utf8mb3",[41,775,776],{},"latin1"," のままになっているケース。gpdf 側では直せない — 読み込み側を修正する。",[17,779,780,783],{},[36,781,782],{},"一部だけ化ける"," (ひらがな・カタカナは出るが特定の漢字だけ □) ならフォントが部分的にしかカバーしていない。「日本語対応」を名乗るフォントでも 鬱・龠・曻 のような JIS X 0213 第 3・第 4 水準の漢字を落としていることがある。Noto Sans JP は JIS X 0213 を網羅しているので切り替えれば直る。",[13,785,787],{"id":786},"原因-2-の詳細-フォントは入っているのに使われない","原因 2 の詳細: フォントは入っているのに使われない",[17,789,790,791,794],{},"やっかいなのはこれ。フォント",[36,792,793],{},"自体","は PDF に埋め込まれているのに、テキスト側で引いていない。最小再現:",[95,796,798],{"className":97,"code":797,"language":99,"meta":100,"style":100},"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",[41,799,800,815,838,844,848,852,877,908,931,935],{"__ignoreMap":100},[104,801,802,805,807,809,811,813],{"class":106,"line":107},[104,803,804],{"class":222},"doc ",[104,806,232],{"class":110},[104,808,311],{"class":222},[104,810,238],{"class":110},[104,812,316],{"class":209},[104,814,319],{"class":110},[104,816,817,820,822,824,826,828,830,832,834,836],{"class":106,"line":118},[104,818,819],{"class":222},"    gpdf",[104,821,238],{"class":110},[104,823,47],{"class":209},[104,825,244],{"class":110},[104,827,247],{"class":110},[104,829,397],{"class":250},[104,831,247],{"class":110},[104,833,226],{"class":110},[104,835,404],{"class":222},[104,837,343],{"class":110},[104,839,840],{"class":106,"line":125},[104,841,843],{"class":842},"sHwdD","    // WithDefaultFont を書き忘れている\n",[104,845,846],{"class":106,"line":135},[104,847,195],{"class":110},[104,849,850],{"class":106,"line":147},[104,851,122],{"emptyLinePlaceholder":121},[104,853,854,857,859,861,863,865,867,869,871,873,875],{"class":106,"line":157},[104,855,856],{"class":222},"page",[104,858,238],{"class":110},[104,860,472],{"class":209},[104,862,475],{"class":110},[104,864,479],{"class":478},[104,866,482],{"class":110},[104,868,485],{"class":114},[104,870,238],{"class":110},[104,872,490],{"class":114},[104,874,493],{"class":110},[104,876,216],{"class":110},[104,878,879,882,884,886,888,890,892,894,896,898,900,902,904,906],{"class":106,"line":162},[104,880,881],{"class":222},"    r",[104,883,238],{"class":110},[104,885,506],{"class":209},[104,887,244],{"class":110},[104,889,511],{"class":377},[104,891,226],{"class":110},[104,893,516],{"class":110},[104,895,519],{"class":478},[104,897,482],{"class":110},[104,899,485],{"class":114},[104,901,238],{"class":110},[104,903,528],{"class":114},[104,905,493],{"class":110},[104,907,216],{"class":110},[104,909,910,913,915,917,919,921,924,926,928],{"class":106,"line":172},[104,911,912],{"class":222},"        c",[104,914,238],{"class":110},[104,916,543],{"class":209},[104,918,244],{"class":110},[104,920,247],{"class":110},[104,922,923],{"class":250},"こんにちは",[104,925,247],{"class":110},[104,927,493],{"class":110},[104,929,930],{"class":842}," // デフォルト = Helvetica で描画される\n",[104,932,933],{"class":106,"line":182},[104,934,566],{"class":110},[104,936,937],{"class":106,"line":192},[104,938,939],{"class":110},"})\n",[17,941,942,943,762,946,948,949,44,951,954,955,957,958,960,961,964,965,967,968,971],{},"直し方は 2 つ: ",[41,944,945],{},"gpdf.WithDefaultFont(\"NotoSansJP\", 12)",[41,947,316],{}," に足すか、日本語を書く全ての ",[41,950,56],{},[41,952,953],{},"template.FontFamily(\"NotoSansJP\")"," を渡す。",[41,956,47],{}," に渡したファミリ名と ",[41,959,56],{}," 側のファミリ名は",[36,962,963],{},"大文字小文字まで完全一致","が必要。",[41,966,397],{}," と ",[41,969,970],{},"notosansjp"," は別フォント扱いになる。",[13,973,975],{"id":974},"原因-3-の詳細-ttf-ファイルが違う","原因 3 の詳細: TTF ファイルが違う",[17,977,978,967,980,982,983,986],{},[41,979,74],{},[41,981,251],{}," は別ファイル。前者は Latin 専用で CJK グリフはゼロ、後者が日本語版 (約 17,000 グリフ)。",[41,984,985],{},"ls"," で並べると見分けにくく、補完で間違って掴むことがある。",[17,988,989],{},"gpdf は登録時にグリフの網羅を検証しない — バイト列を渡したら信用する設計。失敗はレンダリング時の豆腐で初めて現れる。",[17,991,992],{},"確認方法:",[994,995,996,1003,1010],"ul",{},[33,997,998,999,1002],{},"macOS: ",[41,1000,1001],{},"Font Book"," でファイルをダブルクリックするとグリフ一覧が見える",[33,1004,1005,1006,1009],{},"Linux: ",[41,1007,1008],{},"otfinfo -u NotoSans-Regular.ttf"," で Unicode カバレッジが出る",[33,1011,1012,1013,1018,1019,1022],{},"全 OS: ",[718,1014,1017],{"href":1015,"rel":1016},"https://github.com/fonttools/fonttools",[722],"fontTools"," の ",[41,1020,1021],{},"ttx -t cmap NotoSans-Regular.ttf"," で cmap テーブルが XML で出る",[17,1024,1025],{},"U+3042 (あ) が含まれていなければ Latin サブセットを掴んでいる。",[13,1027,1029],{"id":1028},"原因-4-の詳細-文字コードの化け","原因 4 の詳細: 文字コードの化け",[17,1031,1032,1033,1035],{},"これは gpdf 以前の問題。",[41,1034,56],{}," に渡した時点で文字列が既に壊れている。描画前に print で確認する:",[95,1037,1039],{"className":97,"code":1038,"language":99,"meta":100,"style":100},"text := loadLabelFromSomewhere()\nfmt.Printf(\"%q\\n\", text) // 実際の runes が出る\nc.Text(text)\n",[41,1040,1041,1053,1086],{"__ignoreMap":100},[104,1042,1043,1046,1048,1051],{"class":106,"line":107},[104,1044,1045],{"class":222},"text ",[104,1047,232],{"class":110},[104,1049,1050],{"class":209}," loadLabelFromSomewhere",[104,1052,461],{"class":110},[104,1054,1055,1058,1060,1063,1065,1067,1071,1074,1076,1078,1081,1083],{"class":106,"line":118},[104,1056,1057],{"class":222},"fmt",[104,1059,238],{"class":110},[104,1061,1062],{"class":209},"Printf",[104,1064,244],{"class":110},[104,1066,247],{"class":110},[104,1068,1070],{"class":1069},"swJcz","%q",[104,1072,1073],{"class":222},"\\n",[104,1075,247],{"class":110},[104,1077,226],{"class":110},[104,1079,1080],{"class":222}," text",[104,1082,493],{"class":110},[104,1084,1085],{"class":842}," // 実際の runes が出る\n",[104,1087,1088,1090,1092,1094,1096,1099],{"class":106,"line":125},[104,1089,519],{"class":222},[104,1091,238],{"class":110},[104,1093,543],{"class":209},[104,1095,244],{"class":110},[104,1097,1098],{"class":222},"text",[104,1100,195],{"class":110},[17,1102,1103,1104,1107],{},"ここで ",[41,1105,1106],{},"\"縺ゅ→縺\""," と出たら、もっと前の段階で UTF-8 が壊れている。gpdf では直せない — 文字列を作っている側で直す。",[17,1109,1110],{},"日本の業務系で頻出するパターン:",[994,1112,1113,1132,1147,1156],{},[33,1114,1115,762,1118,1120,1121,1124,1125,1018,1128,1131],{},[36,1116,1117],{},"Excel から書き出した Shift-JIS CSV",[41,1119,765],{}," → ",[41,1122,1123],{},"string()"," でそのまま読んでいる。Excel はデフォルトで CP932 (ほぼ Shift-JIS) で保存する。",[41,1126,1127],{},"golang.org/x/text/encoding/japanese",[41,1129,1130],{},"ShiftJIS.NewDecoder()"," を挟む",[33,1133,1134,1137,1138,754,1140,1142,1143,1146],{},[36,1135,1136],{},"レガシー DB"," のカラムが ",[41,1139,773],{},[41,1141,776],{}," のままで、保存時点で既に化けている。カラム定義を ",[41,1144,1145],{},"utf8mb4"," に変更し、既存データはマイグレーションで変換",[33,1148,1149,44,1152,1155],{},[36,1150,1151],{},"HTTP レスポンス",[41,1153,1154],{},"Content-Type: application/json; charset=utf-8"," が付いておらず、クライアントが Latin-1 推定した",[33,1157,1158,1161],{},[36,1159,1160],{},"EUC-JP の古いシステム"," からデータを引っ張ってきたケース。滅多に見ないが、官公庁系の CSV では今も現役",[13,1163,1165],{"id":1164},"_1-つ見落としやすい罠","1 つ見落としやすい罠",[17,1167,1168,1169,1172,1173,1175,1176,1179,1180,1183,1184,1186],{},"gpdf のサブセット化は ",[41,1170,1171],{},"Generate()"," の瞬間に確定する。つまりドキュメント構築中に ",[41,1174,923],{}," を描画し、その後 ",[41,1177,1178],{},"鬱陶しい"," を描画したら、2 回目もちゃんとサブセットに追加される。",[36,1181,1182],{},"ただし、生成済みの PDF を Acrobat で後から開いて漢字をタイプし直す","とそのグリフはサブセットに入っていないので豆腐になる。PDF を後編集するのではなく、Go 側でもう一度 ",[41,1185,1171],{}," する。",[13,1188,1189],{"id":1189},"関連記事",[994,1191,1192,1202,1213],{},[33,1193,1194,1198,1199,1201],{},[718,1195,1197],{"href":1196},"/ja/blog/embed-japanese-font","gpdf で日本語フォントを埋め込むには?"," — ",[41,1200,47],{}," のフルリファレンス、bold / italic バリアント、多言語 CJK の扱い",[33,1203,1204,1208,1209,1212],{},[718,1205,1207],{"href":1206},"/ja/blog/noto-sans-jp-with-gpdf","Noto Sans JP を gpdf で使うには?"," — Noto のどのファイルを選ぶべきかと ",[41,1210,1211],{},"go:embed"," でバイナリに焼き付ける方法",[33,1214,1215,1219],{},[718,1216,1218],{"href":1217},"/ja/blog/japanese-pdf-in-go","Go で日本語 PDF を作る決定版ガイド (2026)"," — フォント・縦書き・ルビ・日本特有のレイアウトまで一気通貫",[13,1221,1223],{"id":1222},"gpdf-を使ってみる","gpdf を使ってみる",[17,1225,1226],{},"gpdf は Go の PDF 生成ライブラリ。MIT、外部依存ゼロ、ネイティブ CJK 対応。",[95,1228,1232],{"className":1229,"code":1230,"language":1231,"meta":100,"style":100},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go get github.com/gpdf-dev/gpdf\n","bash",[41,1233,1234],{"__ignoreMap":100},[104,1235,1236,1238,1241],{"class":106,"line":107},[104,1237,99],{"class":114},[104,1239,1240],{"class":250}," get",[104,1242,1243],{"class":250}," github.com/gpdf-dev/gpdf\n",[17,1245,1246,1251,1252],{},[718,1247,1250],{"href":1248,"rel":1249},"https://github.com/gpdf-dev/gpdf",[722],"⭐ GitHub でスター"," · ",[718,1253,1256],{"href":1254,"rel":1255},"https://gpdf.dev/ja/docs/quickstart",[722],"ドキュメントを読む",[1258,1259,1260],"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":100,"searchDepth":118,"depth":118,"links":1262},[1263,1264,1265,1266,1267,1268,1269,1270,1271,1272],{"id":15,"depth":118,"text":15},{"id":22,"depth":118,"text":22},{"id":92,"depth":118,"text":93},{"id":727,"depth":118,"text":727},{"id":786,"depth":118,"text":787},{"id":974,"depth":118,"text":975},{"id":1028,"depth":118,"text":1029},{"id":1164,"depth":118,"text":1165},{"id":1189,"depth":118,"text":1189},{"id":1222,"depth":118,"text":1223},"2026-04-17","PDF 出力で日本語が □ になるのはフォント未登録が最多。よくある 4 原因と直し方を最短で整理する。",false,"md",{"name":1278,"totalTime":1279,"tools":1280,"steps":1283},"gpdf の豆腐文字を切り分けて直す","PT15M",[1281,1282],"Go 1.22+","CJK 対応 TTF (NotoSansJP-Regular.ttf など)",[1284,1287,1290,1293,1296],{"name":1285,"text":1286},"症状が豆腐か文字化けかを見分ける","PDF を開き、日本語が □ (矩形) で出ているならフォント探索の失敗。縺ゅ→縺 のような化け方なら gpdf に渡す前に UTF-8 が壊れている。別の問題なので切り分けが先。",{"name":1288,"text":1289},"CJK フォントが登録されているか確認する","gpdf.NewDocument の周りを grep して gpdf.WithFont がないなら原因はこれ。WithFont がないと標準 14 フォントにフォールバックし、CJK コードポイントは全て豆腐になる。",{"name":1291,"text":1292},"c.Text のファミリ名を突合する","WithDefaultFont がないなら、日本語を描画する c.Text 全てに template.FontFamily(\"NotoSansJP\") を明示する。名前がずれていると静かにデフォルトに戻る。",{"name":1294,"text":1295},"TTF ファイル自体に CJK グリフが入っているか検証する","NotoSans-Regular.ttf (Latin サブセット) と NotoSansJP-Regular.ttf は別物。gpdf は登録時にグリフ網羅を検証しないので、描画時に初めて豆腐が出る。",{"name":1297,"text":1298},"2 つのビューアで再確認する","生成した PDF を Adobe Acrobat と Chrome の両方で開く。両方で正しく出れば OK。片方だけ出るならサブセットと埋め込みの整合で追加調査が要る。",null,{},"/ja/blog/tofu-boxes-japanese",{"title":5,"description":1274},"ja/blog/008.tofu-boxes-japanese",[1305,1306,1307],"recipe","troubleshooting","cjk","wZYb4vdbHHSvST5tikp7NlRthvjtxxdX_Qk4ri1dJTk",1776529263897]