[{"data":1,"prerenderedAt":19171},["ShallowReactive",2],{"blog-tag-ja-internals":3},[4,1326,2951,4070,5315,6672,8413,10112,11717,13037,14399],{"id":5,"title":6,"author":7,"body":10,"date":1290,"description":1291,"draft":1292,"extension":1293,"howTo":1294,"image":1316,"meta":1317,"navigation":83,"path":1318,"seo":1319,"stem":1320,"tags":1321,"updated":1316,"__hash__":1325},"blogJa/ja/blog/010.source-han-sans-jp-with-gpdf.md","gpdf で Source Han Sans JP (源ノ角ゴシック JP) を使うには?",{"name":8,"url":9},"gpdf team","https://gpdf.dev",{"type":11,"value":12,"toc":1278},"minimark",[13,17,33,36,53,56,705,719,723,734,737,822,833,837,871,878,902,912,916,919,927,930,1085,1098,1101,1105,1108,1150,1164,1168,1171,1185,1188,1203,1206,1209,1238,1242,1245,1262,1274],[14,15,16],"h2",{"id":16},"質問を言い換えると",[18,19,20,27,28,32],"p",{},[21,22,26],"a",{"href":23,"rel":24},"https://github.com/gpdf-dev/gpdf",[25],"nofollow","gpdf"," のドキュメントで ",[29,30,31],"strong",{},"Source Han Sans JP (源ノ角ゴシック JP)"," — Adobe と Google の提携で 2014 年に公開された汎 CJK サンセリフの Adobe 側ブランド — を使いたい。GitHub のリリースタグで再現性を確保したい、Source Han を標準化してきたデザインシステムを受け継いでいる、あるいは Adobe の配布サイクルが好み。理由はどれでも構わない。ダウンロードする前に押さえておきたいのは 3 点だけ — どのファイルを取るか、Noto Sans JP との関係、そして gpdf が読める形式はどれか。",[14,34,35],{"id":35},"速答",[18,37,38,43,44,48,49,52],{},[21,39,42],{"href":40,"rel":41},"https://github.com/adobe-fonts/source-han-sans/releases",[25],"adobe-fonts/source-han-sans"," のリリースページから ",[45,46,47],"code",{},"SourceHanSansJP-Regular.ttf"," (TTF バンドル、OTF ではない) を取得し、",[45,50,51],{},"gpdf.WithFont(\"SourceHanSansJP\", bytes)"," で登録してデフォルトに設定する。Source Han Sans JP と Noto Sans JP はグリフが完全に同一なので、Adobe の運用要件に特にこだわりがなければ Noto Sans JP のほうが入手は楽。",[14,54,55],{"id":55},"動くコード",[57,58,63],"pre",{"className":59,"code":60,"language":61,"meta":62,"style":62},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    font, err := os.ReadFile(\"SourceHanSansJP-Regular.ttf\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(gpdf.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n        gpdf.WithFont(\"SourceHanSansJP\", font),\n        gpdf.WithDefaultFont(\"SourceHanSansJP\", 11),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"報告書\", template.FontSize(24), template.Bold())\n            c.Text(\"Source Han Sans JP — Adobe 配布の無料 CJK フォント。\")\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"report.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n","go","",[45,64,65,78,85,95,107,117,122,132,142,152,158,163,179,217,233,251,257,262,281,304,342,368,393,399,404,423,457,494,542,562,568,574,579,600,613,628,633,679,694,699],{"__ignoreMap":62},[66,67,70,74],"span",{"class":68,"line":69},"line",1,[66,71,73],{"class":72},"sMK4o","package",[66,75,77],{"class":76},"sBMFI"," main\n",[66,79,81],{"class":68,"line":80},2,[66,82,84],{"emptyLinePlaceholder":83},true,"\n",[66,86,88,92],{"class":68,"line":87},3,[66,89,91],{"class":90},"s7zQu","import",[66,93,94],{"class":72}," (\n",[66,96,98,101,104],{"class":68,"line":97},4,[66,99,100],{"class":72},"    \"",[66,102,103],{"class":76},"log",[66,105,106],{"class":72},"\"\n",[66,108,110,112,115],{"class":68,"line":109},5,[66,111,100],{"class":72},[66,113,114],{"class":76},"os",[66,116,106],{"class":72},[66,118,120],{"class":68,"line":119},6,[66,121,84],{"emptyLinePlaceholder":83},[66,123,125,127,130],{"class":68,"line":124},7,[66,126,100],{"class":72},[66,128,129],{"class":76},"github.com/gpdf-dev/gpdf",[66,131,106],{"class":72},[66,133,135,137,140],{"class":68,"line":134},8,[66,136,100],{"class":72},[66,138,139],{"class":76},"github.com/gpdf-dev/gpdf/document",[66,141,106],{"class":72},[66,143,145,147,150],{"class":68,"line":144},9,[66,146,100],{"class":72},[66,148,149],{"class":76},"github.com/gpdf-dev/gpdf/template",[66,151,106],{"class":72},[66,153,155],{"class":68,"line":154},10,[66,156,157],{"class":72},")\n",[66,159,161],{"class":68,"line":160},11,[66,162,84],{"emptyLinePlaceholder":83},[66,164,166,169,173,176],{"class":68,"line":165},12,[66,167,168],{"class":72},"func",[66,170,172],{"class":171},"s2Zo4"," main",[66,174,175],{"class":72},"()",[66,177,178],{"class":72}," {\n",[66,180,182,186,189,192,195,198,201,204,207,210,213,215],{"class":68,"line":181},13,[66,183,185],{"class":184},"sTEyZ","    font",[66,187,188],{"class":72},",",[66,190,191],{"class":184}," err ",[66,193,194],{"class":72},":=",[66,196,197],{"class":184}," os",[66,199,200],{"class":72},".",[66,202,203],{"class":171},"ReadFile",[66,205,206],{"class":72},"(",[66,208,209],{"class":72},"\"",[66,211,47],{"class":212},"sfazB",[66,214,209],{"class":72},[66,216,157],{"class":72},[66,218,220,223,225,228,231],{"class":68,"line":219},14,[66,221,222],{"class":90},"    if",[66,224,191],{"class":184},[66,226,227],{"class":72},"!=",[66,229,230],{"class":72}," nil",[66,232,178],{"class":72},[66,234,236,239,241,244,246,249],{"class":68,"line":235},15,[66,237,238],{"class":184},"        log",[66,240,200],{"class":72},[66,242,243],{"class":171},"Fatal",[66,245,206],{"class":72},[66,247,248],{"class":184},"err",[66,250,157],{"class":72},[66,252,254],{"class":68,"line":253},16,[66,255,256],{"class":72},"    }\n",[66,258,260],{"class":68,"line":259},17,[66,261,84],{"emptyLinePlaceholder":83},[66,263,265,268,270,273,275,278],{"class":68,"line":264},18,[66,266,267],{"class":184},"    doc ",[66,269,194],{"class":72},[66,271,272],{"class":184}," gpdf",[66,274,200],{"class":72},[66,276,277],{"class":171},"NewDocument",[66,279,280],{"class":72},"(\n",[66,282,284,287,289,292,294,296,298,301],{"class":68,"line":283},19,[66,285,286],{"class":184},"        gpdf",[66,288,200],{"class":72},[66,290,291],{"class":171},"WithPageSize",[66,293,206],{"class":72},[66,295,26],{"class":184},[66,297,200],{"class":72},[66,299,300],{"class":184},"A4",[66,302,303],{"class":72},"),\n",[66,305,307,309,311,314,316,319,321,324,326,328,330,333,335,339],{"class":68,"line":306},20,[66,308,286],{"class":184},[66,310,200],{"class":72},[66,312,313],{"class":171},"WithMargins",[66,315,206],{"class":72},[66,317,318],{"class":184},"document",[66,320,200],{"class":72},[66,322,323],{"class":171},"UniformEdges",[66,325,206],{"class":72},[66,327,318],{"class":184},[66,329,200],{"class":72},[66,331,332],{"class":171},"Mm",[66,334,206],{"class":72},[66,336,338],{"class":337},"sbssI","20",[66,340,341],{"class":72},"))),\n",[66,343,345,347,349,352,354,356,359,361,363,366],{"class":68,"line":344},21,[66,346,286],{"class":184},[66,348,200],{"class":72},[66,350,351],{"class":171},"WithFont",[66,353,206],{"class":72},[66,355,209],{"class":72},[66,357,358],{"class":212},"SourceHanSansJP",[66,360,209],{"class":72},[66,362,188],{"class":72},[66,364,365],{"class":184}," font",[66,367,303],{"class":72},[66,369,371,373,375,378,380,382,384,386,388,391],{"class":68,"line":370},22,[66,372,286],{"class":184},[66,374,200],{"class":72},[66,376,377],{"class":171},"WithDefaultFont",[66,379,206],{"class":72},[66,381,209],{"class":72},[66,383,358],{"class":212},[66,385,209],{"class":72},[66,387,188],{"class":72},[66,389,390],{"class":337}," 11",[66,392,303],{"class":72},[66,394,396],{"class":68,"line":395},23,[66,397,398],{"class":72},"    )\n",[66,400,402],{"class":68,"line":401},24,[66,403,84],{"emptyLinePlaceholder":83},[66,405,407,410,412,415,417,420],{"class":68,"line":406},25,[66,408,409],{"class":184},"    page ",[66,411,194],{"class":72},[66,413,414],{"class":184}," doc",[66,416,200],{"class":72},[66,418,419],{"class":171},"AddPage",[66,421,422],{"class":72},"()\n",[66,424,426,429,431,434,437,441,444,447,449,452,455],{"class":68,"line":425},26,[66,427,428],{"class":184},"    page",[66,430,200],{"class":72},[66,432,433],{"class":171},"AutoRow",[66,435,436],{"class":72},"(func(",[66,438,440],{"class":439},"sHdIc","r",[66,442,443],{"class":72}," *",[66,445,446],{"class":76},"template",[66,448,200],{"class":72},[66,450,451],{"class":76},"RowBuilder",[66,453,454],{"class":72},")",[66,456,178],{"class":72},[66,458,460,463,465,468,470,473,475,478,481,483,485,487,490,492],{"class":68,"line":459},27,[66,461,462],{"class":184},"        r",[66,464,200],{"class":72},[66,466,467],{"class":171},"Col",[66,469,206],{"class":72},[66,471,472],{"class":337},"12",[66,474,188],{"class":72},[66,476,477],{"class":72}," func(",[66,479,480],{"class":439},"c",[66,482,443],{"class":72},[66,484,446],{"class":76},[66,486,200],{"class":72},[66,488,489],{"class":76},"ColBuilder",[66,491,454],{"class":72},[66,493,178],{"class":72},[66,495,497,500,502,505,507,509,512,514,516,519,521,524,526,529,532,534,536,539],{"class":68,"line":496},28,[66,498,499],{"class":184},"            c",[66,501,200],{"class":72},[66,503,504],{"class":171},"Text",[66,506,206],{"class":72},[66,508,209],{"class":72},[66,510,511],{"class":212},"報告書",[66,513,209],{"class":72},[66,515,188],{"class":72},[66,517,518],{"class":184}," template",[66,520,200],{"class":72},[66,522,523],{"class":171},"FontSize",[66,525,206],{"class":72},[66,527,528],{"class":337},"24",[66,530,531],{"class":72},"),",[66,533,518],{"class":184},[66,535,200],{"class":72},[66,537,538],{"class":171},"Bold",[66,540,541],{"class":72},"())\n",[66,543,545,547,549,551,553,555,558,560],{"class":68,"line":544},29,[66,546,499],{"class":184},[66,548,200],{"class":72},[66,550,504],{"class":171},[66,552,206],{"class":72},[66,554,209],{"class":72},[66,556,557],{"class":212},"Source Han Sans JP — Adobe 配布の無料 CJK フォント。",[66,559,209],{"class":72},[66,561,157],{"class":72},[66,563,565],{"class":68,"line":564},30,[66,566,567],{"class":72},"        })\n",[66,569,571],{"class":68,"line":570},31,[66,572,573],{"class":72},"    })\n",[66,575,577],{"class":68,"line":576},32,[66,578,84],{"emptyLinePlaceholder":83},[66,580,582,585,587,589,591,593,595,598],{"class":68,"line":581},33,[66,583,584],{"class":184},"    data",[66,586,188],{"class":72},[66,588,191],{"class":184},[66,590,194],{"class":72},[66,592,414],{"class":184},[66,594,200],{"class":72},[66,596,597],{"class":171},"Generate",[66,599,422],{"class":72},[66,601,603,605,607,609,611],{"class":68,"line":602},34,[66,604,222],{"class":90},[66,606,191],{"class":184},[66,608,227],{"class":72},[66,610,230],{"class":72},[66,612,178],{"class":72},[66,614,616,618,620,622,624,626],{"class":68,"line":615},35,[66,617,238],{"class":184},[66,619,200],{"class":72},[66,621,243],{"class":171},[66,623,206],{"class":72},[66,625,248],{"class":184},[66,627,157],{"class":72},[66,629,631],{"class":68,"line":630},36,[66,632,256],{"class":72},[66,634,636,638,640,642,644,646,649,651,653,656,658,660,663,665,668,671,673,675,677],{"class":68,"line":635},37,[66,637,222],{"class":90},[66,639,191],{"class":184},[66,641,194],{"class":72},[66,643,197],{"class":184},[66,645,200],{"class":72},[66,647,648],{"class":171},"WriteFile",[66,650,206],{"class":72},[66,652,209],{"class":72},[66,654,655],{"class":212},"report.pdf",[66,657,209],{"class":72},[66,659,188],{"class":72},[66,661,662],{"class":184}," data",[66,664,188],{"class":72},[66,666,667],{"class":337}," 0o644",[66,669,670],{"class":72},");",[66,672,191],{"class":184},[66,674,227],{"class":72},[66,676,230],{"class":72},[66,678,178],{"class":72},[66,680,682,684,686,688,690,692],{"class":68,"line":681},38,[66,683,238],{"class":184},[66,685,200],{"class":72},[66,687,243],{"class":171},[66,689,206],{"class":72},[66,691,248],{"class":184},[66,693,157],{"class":72},[66,695,697],{"class":68,"line":696},39,[66,698,256],{"class":72},[66,700,702],{"class":68,"line":701},40,[66,703,704],{"class":72},"}\n",[18,706,707,708,711,712,715,716,718],{},"TTF を ",[45,709,710],{},"main.go"," の隣に置いて ",[45,713,714],{},"go run main.go","。日本語入り 1 ページの PDF が ",[45,717,655],{}," として出力される。",[14,720,722],{"id":721},"source-han-sans-jp-noto-sans-cjk-jp","Source Han Sans JP = Noto Sans CJK JP",[18,724,725,726,729,730,733],{},"最初に押さえておきたい事実は 1 つ。",[29,727,728],{},"Source Han Sans と Noto Sans CJK は同じフォント","。グリフ設計・メトリクス・文字セットは Adobe が担当し、Google が Noto ブランドで並行配布した。リリース日は両者とも 2014-07-15。アウトライン、",[45,731,732],{},"hmtx"," テーブル、JIS X 0213 / Adobe-Japan1-6 のカバレッジは 1 bit 単位で同一で、Adobe がバージョンを上げると数週間以内に Noto 側にも反映される。",[18,735,736],{},"違いはすべて配布経路とパッケージング:",[738,739,740,755],"table",{},[741,742,743],"thead",{},[744,745,746,749,752],"tr",{},[747,748],"th",{},[747,750,751],{},"Source Han Sans JP",[747,753,754],{},"Noto Sans JP",[756,757,758,770,789,800,811],"tbody",{},[744,759,760,764,767],{},[761,762,763],"td",{},"配布元",[761,765,766],{},"Adobe",[761,768,769],{},"Google",[744,771,772,775,781],{},[761,773,774],{},"一次ソース",[761,776,777],{},[21,778,42],{"href":779,"rel":780},"https://github.com/adobe-fonts/source-han-sans",[25],[761,782,783,788],{},[21,784,787],{"href":785,"rel":786},"https://notofonts.github.io",[25],"notofonts.github.io"," + Google Fonts",[744,790,791,794,797],{},[761,792,793],{},"既定フォーマット",[761,795,796],{},"OTF (CFF アウトライン)",[761,798,799],{},"TTF (static) + variable",[744,801,802,805,808],{},[761,803,804],{},"リリースモデル",[761,806,807],{},"GitHub リリースタグで手動バージョニング",[761,809,810],{},"Google Fonts CDN + git リポジトリ",[744,812,813,816,819],{},[761,814,815],{},"言語バンドル",[761,817,818],{},"言語別 TTF + 汎 CJK OTC",[761,820,821],{},"JP のみ",[18,823,824,825,828,829,832],{},"Adobe の GitHub タグを固定する運用、社内で ",[45,826,827],{},"github.com/adobe-fonts"," をミラーリング済み、あるいは別パイプラインで汎 CJK OTC も使う — こういう事情があれば Source Han Sans JP。そうでなければ TTF が直接落とせる ",[21,830,754],{"href":831},"/ja/blog/noto-sans-jp-with-gpdf"," のほうが手間が少ない。",[14,834,836],{"id":835},"なぜ-otf-ではなく-ttf-なのか","なぜ OTF ではなく TTF なのか",[18,838,839,840,843,844,847,848,851,852,851,855,851,858,860,861,851,864,867,868,870],{},"Adobe の既定配布は ",[45,841,842],{},".otf","、正確には CFF ベースの OpenType。一方 gpdf のフォントパーサは ",[45,845,846],{},"pdf/font/truetype.go"," 1 ファイルにまとまっており、扱うのは ",[45,849,850],{},"glyf"," / ",[45,853,854],{},"loca",[45,856,857],{},"cmap",[45,859,732],{}," と複合グリフ。",[45,862,863],{},"CFF ",[45,865,866],{},"CFF2"," アウトラインは読まない。CFF 系 ",[45,869,842],{}," を渡すと、レンダリング時ではなくドキュメント構築時点でパースエラーになる。",[18,872,873,874,877],{},"Adobe のリリースページは OTF と TTF の両方を公開しているので、",[29,875,876],{},"TTF バンドル"," を選ぶ。ポイントリリースで TTF が落ちていないケース用に、きれいな代替は 2 つ:",[879,880,881,888],"ol",{},[882,883,884,887],"li",{},[29,885,886],{},"Noto Sans JP に切り替える。"," Google Fonts が static TTF を直接配信しており、グリフデータは完全に同じ。変換不要、出力も同じ。",[882,889,890,893,894,897,898,901],{},[29,891,892],{},"一度変換してコミットする。"," ",[45,895,896],{},"fonttools"," の ",[45,899,900],{},"otf2ttf"," で 1 分。結果をリポジトリか社内のアーティファクトサーバに置いて、ビルドパイプラインから変換処理を追い出す。",[18,903,904,905,907,908,911],{},"ビルド時にその場で変換するのは避ける。フォント変換ツールはバージョンをまたぐと挙動が微妙に変わり、",[45,906,732],{}," テーブルがわずかに違うだけで改行位置が ",[45,909,910],{},"pip install -U"," のタイミングで動く。",[14,913,915],{"id":914},"_7-ウェイト","7 ウェイト",[18,917,918],{},"Source Han Sans JP は ExtraLight から Heavy まで 7 ウェイトを個別 TTF で配布:",[57,920,925],{"className":921,"code":923,"language":924},[922],"language-text","SourceHanSansJP-ExtraLight.ttf\nSourceHanSansJP-Light.ttf\nSourceHanSansJP-Normal.ttf\nSourceHanSansJP-Regular.ttf\nSourceHanSansJP-Medium.ttf\nSourceHanSansJP-Bold.ttf\nSourceHanSansJP-Heavy.ttf\n","text",[45,926,923],{"__ignoreMap":62},[18,928,929],{},"ほとんどの業務文書では Regular と Bold の 2 本で足りる:",[57,931,933],{"className":59,"code":932,"language":61,"meta":62,"style":62},"reg,  _ := os.ReadFile(\"SourceHanSansJP-Regular.ttf\")\nbold, _ := os.ReadFile(\"SourceHanSansJP-Bold.ttf\")\n\ndoc := gpdf.NewDocument(\n    gpdf.WithFont(\"SourceHanSansJP\", reg),\n    gpdf.WithFont(\"SourceHanSansJP-Bold\", bold),\n    gpdf.WithDefaultFont(\"SourceHanSansJP\", 11),\n)\n",[45,934,935,963,992,996,1011,1035,1059,1081],{"__ignoreMap":62},[66,936,937,940,942,945,947,949,951,953,955,957,959,961],{"class":68,"line":69},[66,938,939],{"class":184},"reg",[66,941,188],{"class":72},[66,943,944],{"class":184},"  _ ",[66,946,194],{"class":72},[66,948,197],{"class":184},[66,950,200],{"class":72},[66,952,203],{"class":171},[66,954,206],{"class":72},[66,956,209],{"class":72},[66,958,47],{"class":212},[66,960,209],{"class":72},[66,962,157],{"class":72},[66,964,965,968,970,973,975,977,979,981,983,985,988,990],{"class":68,"line":80},[66,966,967],{"class":184},"bold",[66,969,188],{"class":72},[66,971,972],{"class":184}," _ ",[66,974,194],{"class":72},[66,976,197],{"class":184},[66,978,200],{"class":72},[66,980,203],{"class":171},[66,982,206],{"class":72},[66,984,209],{"class":72},[66,986,987],{"class":212},"SourceHanSansJP-Bold.ttf",[66,989,209],{"class":72},[66,991,157],{"class":72},[66,993,994],{"class":68,"line":87},[66,995,84],{"emptyLinePlaceholder":83},[66,997,998,1001,1003,1005,1007,1009],{"class":68,"line":97},[66,999,1000],{"class":184},"doc ",[66,1002,194],{"class":72},[66,1004,272],{"class":184},[66,1006,200],{"class":72},[66,1008,277],{"class":171},[66,1010,280],{"class":72},[66,1012,1013,1016,1018,1020,1022,1024,1026,1028,1030,1033],{"class":68,"line":109},[66,1014,1015],{"class":184},"    gpdf",[66,1017,200],{"class":72},[66,1019,351],{"class":171},[66,1021,206],{"class":72},[66,1023,209],{"class":72},[66,1025,358],{"class":212},[66,1027,209],{"class":72},[66,1029,188],{"class":72},[66,1031,1032],{"class":184}," reg",[66,1034,303],{"class":72},[66,1036,1037,1039,1041,1043,1045,1047,1050,1052,1054,1057],{"class":68,"line":119},[66,1038,1015],{"class":184},[66,1040,200],{"class":72},[66,1042,351],{"class":171},[66,1044,206],{"class":72},[66,1046,209],{"class":72},[66,1048,1049],{"class":212},"SourceHanSansJP-Bold",[66,1051,209],{"class":72},[66,1053,188],{"class":72},[66,1055,1056],{"class":184}," bold",[66,1058,303],{"class":72},[66,1060,1061,1063,1065,1067,1069,1071,1073,1075,1077,1079],{"class":68,"line":124},[66,1062,1015],{"class":184},[66,1064,200],{"class":72},[66,1066,377],{"class":171},[66,1068,206],{"class":72},[66,1070,209],{"class":72},[66,1072,358],{"class":212},[66,1074,209],{"class":72},[66,1076,188],{"class":72},[66,1078,390],{"class":337},[66,1080,303],{"class":72},[66,1082,1083],{"class":68,"line":134},[66,1084,157],{"class":72},[18,1086,1087,1090,1091,1094,1095,1097],{},[45,1088,1089],{},"-Bold"," サフィックスが ",[45,1092,1093],{},"template.Bold()"," とボールド TTF を結びつける規約。登録しないまま ",[45,1096,1093],{}," を使うと、Regular グリフに 0.4 pt のストロークを重ねた合成ボールドにフォールバックする。見出し程度なら通用するが、大きな級数では本物のボールドアウトラインに比べて明らかに痩せて見える。",[18,1099,1100],{},"CJK フォントはイタリック版を出さないのが定石で、Source Han Sans JP も例外ではない。日本語の強調にイタリックが欲しい場面は、ウェイトか色に振り替えるほうが自然。漢字に斜体変形をかけると強調ではなく破綻して見える。",[14,1102,1104],{"id":1103},"pan-cjk-か-jp-単体か-superotc-か","Pan-CJK か JP 単体か SuperOTC か",[18,1106,1107],{},"Adobe は Source Han Sans を複数の粒度で配布している。Go の PDF 生成では使い分けが重要:",[1109,1110,1111,1124,1134],"ul",{},[882,1112,1113,1116,1117,1120,1121,1123],{},[29,1114,1115],{},"SourceHanSans.ttc"," (Super OTC) — CJK 全言語を 1 つの 20 MB 超の TrueType Collection に収めたファイル。gpdf は ",[45,1118,1119],{},".ttc"," の内部フェースインデックスを解決しないため、先に ",[45,1122,896],{}," で JP フェースを切り出して TTF として渡す必要がある。通常は選ばない。",[882,1125,1126,1129,1130,1133],{},[29,1127,1128],{},"地域別 OTF"," (例: ",[45,1131,1132],{},"SourceHanSans-Regular.otf",") — CJK 言語統合、CFF アウトライン。gpdf では読めない。",[882,1135,1136,1139,1140,1142,1143,1145,1146,1149],{},[29,1137,1138],{},"言語別 TTF"," (",[45,1141,47],{},") — JP 単体、",[45,1144,850],{}," アウトライン。",[29,1147,1148],{},"これを選ぶ","。",[18,1151,1152,1153,1155,1156,1159,1160,1163],{},"1 ページに日本語と韓国語 / 中国語が混在する場合は、汎 CJK OTF に頼らず言語別ファミリを並べて登録する: ",[45,1154,358],{}," と ",[45,1157,1158],{},"SourceHanSansKR"," を登録し、スクリプトが切り替わる箇所で ",[45,1161,1162],{},"template.FontFamily"," を明示する。汎 CJK OTF は漢字を Han unification で 1 つの形に寄せるため、日本語本文の中で「これは日本語の漢字のはず」と読者が感じる文字が中国語の字形で出てしまう事故が起きる。",[14,1165,1167],{"id":1166},"noto-ではなく-source-han-を選ぶべき場面","Noto ではなく Source Han を選ぶべき場面",[18,1169,1170],{},"アウトラインは同じ、違うのは配布経路。Source Han Sans JP を選ぶと自然なのは:",[1109,1172,1173,1176,1182],{},[882,1174,1175],{},"Adobe の GitHub リリースタグにフォントを固定する運用を取りたい (再現性・監査性)",[882,1177,1178,1179,1181],{},"社内で ",[45,1180,827],{}," をすでにミラーしている (厳しいアーティファクトポリシーのある企業で多い)",[882,1183,1184],{},"汎 CJK OTC バンドルがパイプラインの別工程でも役立つ (DTP 連携、ブランドシステムが Adobe 名で統一されている等)",[18,1186,1187],{},"逆に Noto Sans JP が正解なのは:",[1109,1189,1190,1197,1200],{},[882,1191,1192,1193,1196],{},"TTF を最短経路で入手したい (",[45,1194,1195],{},"fonts.google.com/noto/specimen/Noto+Sans+JP"," → zip → 完了)",[882,1198,1199],{},"OTF→TTF 変換をビルドに持ち込みたくない",[882,1201,1202],{},"すでに他の Google Fonts を既存ワークフローで取得している",[18,1204,1205],{},"レンダリング結果はどちらでも同じ。判断軸は運用上のもの — ファイルをどこから取るか、どうバージョニングするか、運用チームがどちらに慣れているか — で、美的なものではない。",[14,1207,1208],{"id":1208},"関連記事",[1109,1210,1211,1217,1224,1231],{},[882,1212,1213,1216],{},[21,1214,1215],{"href":831},"Noto Sans JP を gpdf で使うには?"," — 同じグリフ、TTF でそのまま配布されている選択肢",[882,1218,1219,1223],{},[21,1220,1222],{"href":1221},"/ja/blog/embed-japanese-font","gpdf で日本語フォントを埋め込むには?"," — CJK 全般の一般的な埋め込みレシピ",[882,1225,1226,1230],{},[21,1227,1229],{"href":1228},"/ja/blog/ipaex-gothic-gpdf","gpdf で IPAex ゴシックを使うには?"," — IPA ライセンスの選択肢 (官公庁提出向け)",[882,1232,1233,1237],{},[21,1234,1236],{"href":1235},"/ja/blog/tofu-boxes-japanese","gpdf で日本語が豆腐文字になる原因と直し方"," — グリフが出ないときのトラブルシュート",[14,1239,1241],{"id":1240},"gpdf-を使ってみる","gpdf を使ってみる",[18,1243,1244],{},"gpdf は Go の PDF 生成ライブラリ。MIT、外部依存ゼロ、ネイティブ CJK 対応。",[57,1246,1250],{"className":1247,"code":1248,"language":1249,"meta":62,"style":62},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go get github.com/gpdf-dev/gpdf\n","bash",[45,1251,1252],{"__ignoreMap":62},[66,1253,1254,1256,1259],{"class":68,"line":69},[66,1255,61],{"class":76},[66,1257,1258],{"class":212}," get",[66,1260,1261],{"class":212}," github.com/gpdf-dev/gpdf\n",[18,1263,1264,1268,1269],{},[21,1265,1267],{"href":23,"rel":1266},[25],"⭐ GitHub でスター"," · ",[21,1270,1273],{"href":1271,"rel":1272},"https://gpdf.dev/ja/docs/quickstart",[25],"ドキュメントを読む",[1275,1276,1277],"style",{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":62,"searchDepth":80,"depth":80,"links":1279},[1280,1281,1282,1283,1284,1285,1286,1287,1288,1289],{"id":16,"depth":80,"text":16},{"id":35,"depth":80,"text":35},{"id":55,"depth":80,"text":55},{"id":721,"depth":80,"text":722},{"id":835,"depth":80,"text":836},{"id":914,"depth":80,"text":915},{"id":1103,"depth":80,"text":1104},{"id":1166,"depth":80,"text":1167},{"id":1208,"depth":80,"text":1208},{"id":1240,"depth":80,"text":1241},"2026-04-19","Adobe の GitHub リリースから TTF 版 Source Han Sans JP を取得して gpdf.WithFont で登録する。Noto Sans JP と同じグリフ、7 ウェイト、SIL OFL。",false,"md",{"name":1295,"totalTime":1296,"tools":1297,"steps":1300},"gpdf ドキュメントで Source Han Sans JP (源ノ角ゴシック JP) をデフォルトフォントに設定する","PT15M",[1298,1299],"Go 1.22+","SourceHanSansJP-Regular.ttf (adobe-fonts/source-han-sans 配布)",[1301,1304,1307,1310,1313],{"name":1302,"text":1303},"Adobe の GitHub リリースから TTF 版を入手する","github.com/adobe-fonts/source-han-sans/releases を開き、最新リリースから TTF バンドル (OTF / SuperOTC ではなく) をダウンロードして SourceHanSansJP-Regular.ttf を取り出す。gpdf は TrueType パーサのみで CFF 系 OpenType は読めない。",{"name":1305,"text":1306},"起動時にバイト列を読み込む","os.ReadFile(\"SourceHanSansJP-Regular.ttf\") か //go:embed を使う。CI 再現性のため、ビルド時にダウンロードせず Adobe のリリースタグを固定してリポジトリに含める運用が安全。",{"name":1308,"text":1309},"ドキュメント生成時に登録する","gpdf.WithFont(\"SourceHanSansJP\", fontBytes) と gpdf.WithDefaultFont(\"SourceHanSansJP\", 11) を gpdf.NewDocument に渡す。AddUTF8Font もファイルパスも不要。",{"name":1311,"text":1312},"必要に応じて他ウェイトも登録する","Source Han Sans JP は ExtraLight 〜 Heavy の 7 ウェイトを個別 TTF として配布。-Bold サフィックス付きで登録しておくと template.Bold() が本物のボールドアウトラインを使うようになる。",{"name":1314,"text":1315},"OFL.txt を配布物に同梱する","SIL OFL 1.1 はフォントバイナリの配布先にライセンス全文の同梱を要求する。//go:embed する場合は OFL.txt も LICENSES/ に含めて NOTICE から参照する。",null,{},"/ja/blog/source-han-sans-jp-with-gpdf",{"title":6,"description":1291},"ja/blog/010.source-han-sans-jp-with-gpdf",[1322,1323,1324],"recipe","cjk","tutorial","ARN91OiCQazALeR25_a6EjgTC3En5Sc7vh7sZKr3LFg",{"id":1327,"title":1328,"author":1329,"body":1330,"date":1290,"description":2941,"draft":1292,"extension":1293,"howTo":1316,"image":1316,"meta":2942,"navigation":83,"path":2943,"seo":2944,"stem":2945,"tags":2946,"updated":1316,"__hash__":2950},"blogJa/ja/blog/011.why-gpdf-is-faster.md","gpdf が他の Go PDF ライブラリより 10〜30 倍速い理由",{"name":8,"url":9},{"type":11,"value":1331,"toc":2920},[1332,1336,1359,1383,1386,1397,1404,1408,1415,1524,1534,1541,1548,1552,1555,1561,1576,1582,1589,1886,1921,1944,1947,1951,1954,1973,1984,1993,1996,2000,2003,2010,2107,2134,2137,2140,2286,2295,2305,2308,2315,2321,2331,2334,2340,2343,2346,2360,2363,2367,2370,2377,2380,2406,2417,2420,2427,2556,2559,2563,2576,2579,2582,2596,2603,2606,2609,2687,2694,2697,2701,2704,2714,2723,2737,2745,2748,2751,2754,2794,2803,2806,2817,2821,2832,2838,2851,2857,2863,2865,2868,2880,2890,2893,2917],[14,1333,1335],{"id":1334},"tldr","TL;DR",[18,1337,1338,1339,1342,1343,1346,1347,1350,1351,1354,1355,1358],{},"gpdf は 1 ページ ",[29,1340,1341],{},"13 µs","、4×10 の請求書テーブル ",[29,1344,1345],{},"108 µs","、100 ページのレポートを ",[29,1348,1349],{},"683 µs"," で生成する。次に速い ",[45,1352,1353],{},"jung-kurt/gofpdf"," は同じ 100 ページを ",[29,1356,1357],{},"11.7 ms"," — 約 17 倍遅い。これはチューニングの差ではない。3 つの設計判断が積み重なった結果だ。",[879,1360,1361,1367,1377],{},[882,1362,1363,1366],{},[29,1364,1365],{},"単一パスレイアウト。"," Builder API と PDF コンテンツストリームの間に中間 AST を持たない。",[882,1368,1369,1372,1373,1376],{},[29,1370,1371],{},"ホットパスの具象型。"," レイアウトループ内に reflection も ",[45,1374,1375],{},"interface{}"," も仮想ディスパッチもない。",[882,1378,1379,1382],{},[29,1380,1381],{},"cmap を一度だけ解決する TrueType サブセッタ。"," グリフごとでも、ページごとでもない。一度だけ。",[18,1384,1385],{},"1 つだけでも 2〜3 倍。3 つ重ねて一桁の差になる。",[18,1387,1388,1389,1396],{},"この記事ではその数字を生むコードパスをそのまま追う。ベンチマークは ",[21,1390,1393],{"href":1391,"rel":1392},"https://github.com/gpdf-dev/gpdf/tree/main/_benchmark",[25],[45,1394,1395],{},"_benchmark/benchmark_test.go"," に公開してある。クローンして、自分のマシンで回して、数字が食い違ったら issue で教えてほしい。",[18,1398,1399,1400,1403],{},"最初にバイアスを開示する。私たちは gpdf のチームだ。「私たちの方が速い」を正直に言い換えると「私たちは違うトレードオフを取った」であって、面白い問いは ",[29,1401,1402],{},"その速さのために何を捨てたか"," のほう。後半はその話をする。",[14,1405,1407],{"id":1406},"そもそも速いとは","そもそも「速い」とは",[18,1409,1410,1411,1414],{},"アーキテクチャの前に、説明しようとしているスコアボードを (Apple M1, Go 1.25, CGO 無効, ",[45,1412,1413],{},"-benchmem"," 有効):",[738,1416,1417,1438],{},[741,1418,1419],{},[744,1420,1421,1424,1426,1429,1432,1435],{},[747,1422,1423],{},"ワークロード",[747,1425,26],{},[747,1427,1428],{},"gofpdf",[747,1430,1431],{},"go-pdf/fpdf",[747,1433,1434],{},"signintech/gopdf",[747,1436,1437],{},"Maroto v2",[756,1439,1440,1461,1482,1502],{},[744,1441,1442,1445,1449,1452,1455,1458],{},[761,1443,1444],{},"単一ページ (Hello World)",[761,1446,1447],{},[29,1448,1341],{},[761,1450,1451],{},"132 µs",[761,1453,1454],{},"135 µs",[761,1456,1457],{},"423 µs",[761,1459,1460],{},"237 µs",[744,1462,1463,1466,1470,1473,1476,1479],{},[761,1464,1465],{},"4×10 の請求書テーブル",[761,1467,1468],{},[29,1469,1345],{},[761,1471,1472],{},"241 µs",[761,1474,1475],{},"243 µs",[761,1477,1478],{},"835 µs",[761,1480,1481],{},"8,600 µs",[744,1483,1484,1487,1491,1494,1497,1499],{},[761,1485,1486],{},"100 ページレポート",[761,1488,1489],{},[29,1490,1349],{},[761,1492,1493],{},"11,700 µs",[761,1495,1496],{},"11,900 µs",[761,1498,1481],{},[761,1500,1501],{},"19,800 µs",[744,1503,1504,1507,1512,1515,1518,1521],{},[761,1505,1506],{},"CJK の複雑な請求書",[761,1508,1509],{},[29,1510,1511],{},"133 µs",[761,1513,1514],{},"254 µs",[761,1516,1517],{},"n/a",[761,1519,1520],{},"997 µs",[761,1522,1523],{},"10,400 µs",[18,1525,1526,1527,1530,1531,1533],{},"説明する前から見える 2 つの形がある。ページ数が増えるほど ",[29,1528,1529],{},"差が広がる"," (Hello World で 10 倍、100 ページで 17 倍)。複雑度が増すほど ",[29,1532,1529],{}," (テーブル単独で gpdf 108 µs、Maroto の gofpdf バックエンド経由で 8.6 ms)。",[18,1535,1536,1537,1540],{},"どちらの形も原因は同じ。",[29,1538,1539],{},"gpdf のレイアウトループは共通パスで割り当てを行わない"," から、要素 1 つあたりのコストがほぼ一定。なぜそうなっているのかを次から説明する。",[18,1542,1543,1544,1547],{},"念のための但し書き。",[29,1545,1546],{},"多くの PDF ワークロードでは絶対速度の意味は思うほど大きくない","。1 ページのレシートだけなら、このテーブルの現役ライブラリはどれもリクエストパス上で生成できる。差が効いてくる閾値は「100 通の請求書をキューに積まずに同期生成できるか」のあたりから。",[14,1549,1551],{"id":1550},"決定-1-中間-ast-を作らない","決定 1: 中間 AST を作らない",[18,1553,1554],{},"一般的な PDF Builder ライブラリはこう動く:",[57,1556,1559],{"className":1557,"code":1558,"language":924},[922],"builder API → ドキュメントツリー (AST) → レイアウトパス → シリアライザ → バイト列\n",[45,1560,1558],{"__ignoreMap":62},[18,1562,1563,1564,1567,1568,1571,1572,1575],{},"真ん中の ",[29,1565,1566],{},"ドキュメントツリー"," の段階が問題だ。",[45,1569,1570],{},".Text()"," ごとにノードを割り当て、",[45,1573,1574],{},".Row()"," ごとにコンテナを割り当てる。レイアウトパスがツリーを歩いて位置を計算し、シリアライザがもう一度ツリーを歩いてバイトを書く。3 パス・3 回の割り当て・3 回の CPU キャッシュ往復。",[18,1577,1578,1579,1149],{},"gpdf にはこの段階 2 がない。Builder はレイアウトコンテキストに直接書き、レイアウトコンテキストはコンテンツストリームに直接書く。",[29,1580,1581],{},"1 パス",[18,1583,1584,1585,1588],{},"テキスト要素の実際のコードパスがこれ (",[45,1586,1587],{},"template/col_builder.go"," から長さ調整):",[57,1590,1592],{"className":59,"code":1591,"language":61,"meta":62,"style":62},"func (c *ColBuilder) Text(s string, opts ...TextOption) {\n    opt := c.resolveOptions(opts)\n    box := c.currentBox()\n    w := c.measureText(s, opt)\n    h := opt.FontSize.Pt() * opt.LineHeight\n    c.writer.BeginText()\n    c.writer.SetFont(opt.Font, opt.FontSize)\n    c.writer.MoveTo(box.X, box.Y-opt.FontSize.Pt())\n    c.writer.ShowString(s)\n    c.writer.EndText()\n    c.advance(w, h)\n}\n",[45,1593,1594,1637,1659,1675,1700,1729,1746,1779,1827,1846,1861,1882],{"__ignoreMap":62},[66,1595,1596,1598,1600,1603,1606,1608,1610,1613,1615,1618,1622,1624,1627,1630,1633,1635],{"class":68,"line":69},[66,1597,168],{"class":72},[66,1599,1139],{"class":72},[66,1601,1602],{"class":439},"c ",[66,1604,1605],{"class":72},"*",[66,1607,489],{"class":76},[66,1609,454],{"class":72},[66,1611,1612],{"class":171}," Text",[66,1614,206],{"class":72},[66,1616,1617],{"class":439},"s",[66,1619,1621],{"class":1620},"spNyl"," string",[66,1623,188],{"class":72},[66,1625,1626],{"class":439}," opts",[66,1628,1629],{"class":72}," ...",[66,1631,1632],{"class":76},"TextOption",[66,1634,454],{"class":72},[66,1636,178],{"class":72},[66,1638,1639,1642,1644,1647,1649,1652,1654,1657],{"class":68,"line":80},[66,1640,1641],{"class":184},"    opt ",[66,1643,194],{"class":72},[66,1645,1646],{"class":184}," c",[66,1648,200],{"class":72},[66,1650,1651],{"class":171},"resolveOptions",[66,1653,206],{"class":72},[66,1655,1656],{"class":184},"opts",[66,1658,157],{"class":72},[66,1660,1661,1664,1666,1668,1670,1673],{"class":68,"line":87},[66,1662,1663],{"class":184},"    box ",[66,1665,194],{"class":72},[66,1667,1646],{"class":184},[66,1669,200],{"class":72},[66,1671,1672],{"class":171},"currentBox",[66,1674,422],{"class":72},[66,1676,1677,1680,1682,1684,1686,1689,1691,1693,1695,1698],{"class":68,"line":97},[66,1678,1679],{"class":184},"    w ",[66,1681,194],{"class":72},[66,1683,1646],{"class":184},[66,1685,200],{"class":72},[66,1687,1688],{"class":171},"measureText",[66,1690,206],{"class":72},[66,1692,1617],{"class":184},[66,1694,188],{"class":72},[66,1696,1697],{"class":184}," opt",[66,1699,157],{"class":72},[66,1701,1702,1705,1707,1709,1711,1713,1715,1718,1720,1722,1724,1726],{"class":68,"line":109},[66,1703,1704],{"class":184},"    h ",[66,1706,194],{"class":72},[66,1708,1697],{"class":184},[66,1710,200],{"class":72},[66,1712,523],{"class":184},[66,1714,200],{"class":72},[66,1716,1717],{"class":171},"Pt",[66,1719,175],{"class":72},[66,1721,443],{"class":72},[66,1723,1697],{"class":184},[66,1725,200],{"class":72},[66,1727,1728],{"class":184},"LineHeight\n",[66,1730,1731,1734,1736,1739,1741,1744],{"class":68,"line":119},[66,1732,1733],{"class":184},"    c",[66,1735,200],{"class":72},[66,1737,1738],{"class":184},"writer",[66,1740,200],{"class":72},[66,1742,1743],{"class":171},"BeginText",[66,1745,422],{"class":72},[66,1747,1748,1750,1752,1754,1756,1759,1761,1764,1766,1769,1771,1773,1775,1777],{"class":68,"line":124},[66,1749,1733],{"class":184},[66,1751,200],{"class":72},[66,1753,1738],{"class":184},[66,1755,200],{"class":72},[66,1757,1758],{"class":171},"SetFont",[66,1760,206],{"class":72},[66,1762,1763],{"class":184},"opt",[66,1765,200],{"class":72},[66,1767,1768],{"class":184},"Font",[66,1770,188],{"class":72},[66,1772,1697],{"class":184},[66,1774,200],{"class":72},[66,1776,523],{"class":184},[66,1778,157],{"class":72},[66,1780,1781,1783,1785,1787,1789,1792,1794,1797,1799,1802,1804,1807,1809,1812,1815,1817,1819,1821,1823,1825],{"class":68,"line":134},[66,1782,1733],{"class":184},[66,1784,200],{"class":72},[66,1786,1738],{"class":184},[66,1788,200],{"class":72},[66,1790,1791],{"class":171},"MoveTo",[66,1793,206],{"class":72},[66,1795,1796],{"class":184},"box",[66,1798,200],{"class":72},[66,1800,1801],{"class":184},"X",[66,1803,188],{"class":72},[66,1805,1806],{"class":184}," box",[66,1808,200],{"class":72},[66,1810,1811],{"class":184},"Y",[66,1813,1814],{"class":72},"-",[66,1816,1763],{"class":184},[66,1818,200],{"class":72},[66,1820,523],{"class":184},[66,1822,200],{"class":72},[66,1824,1717],{"class":171},[66,1826,541],{"class":72},[66,1828,1829,1831,1833,1835,1837,1840,1842,1844],{"class":68,"line":144},[66,1830,1733],{"class":184},[66,1832,200],{"class":72},[66,1834,1738],{"class":184},[66,1836,200],{"class":72},[66,1838,1839],{"class":171},"ShowString",[66,1841,206],{"class":72},[66,1843,1617],{"class":184},[66,1845,157],{"class":72},[66,1847,1848,1850,1852,1854,1856,1859],{"class":68,"line":154},[66,1849,1733],{"class":184},[66,1851,200],{"class":72},[66,1853,1738],{"class":184},[66,1855,200],{"class":72},[66,1857,1858],{"class":171},"EndText",[66,1860,422],{"class":72},[66,1862,1863,1865,1867,1870,1872,1875,1877,1880],{"class":68,"line":160},[66,1864,1733],{"class":184},[66,1866,200],{"class":72},[66,1868,1869],{"class":171},"advance",[66,1871,206],{"class":72},[66,1873,1874],{"class":184},"w",[66,1876,188],{"class":72},[66,1878,1879],{"class":184}," h",[66,1881,157],{"class":72},[66,1883,1884],{"class":68,"line":165},[66,1885,704],{"class":72},[18,1887,1888,1889,1892,1893,1896,1897,1900,1901,851,1903,851,1905,1907,1908,893,1911,893,1914,893,1917,1920],{},"ノードはツリーに積まれない。位置は遅延されない。writer は ",[45,1890,1891],{},"*pdf.Writer"," で、その中の ",[45,1894,1895],{},"io.Writer"," (通常は ",[45,1898,1899],{},"bytes.Buffer",") に対して ",[45,1902,1743],{},[45,1904,1791],{},[45,1906,1839],{}," が即座に ",[45,1909,1910],{},"BT",[45,1912,1913],{},"Td",[45,1915,1916],{},"Tj",[45,1918,1919],{},"ET"," の PDF 演算子を書き出す。",[18,1922,1923,1924,1927,1928,1931,1932,1935,1936,1939,1940,1943],{},"gofpdf の同じ論理操作と比較する。gofpdf は ",[45,1925,1926],{},"page"," オブジェクトに操作のスライスを持つ。",[45,1929,1930],{},"SetXY"," + ",[45,1933,1934],{},"Cell"," を呼ぶたびにそのスライスに追記される。最終的に ",[45,1937,1938],{},"Output"," (か ",[45,1941,1942],{},"OutputFileAndClose",") がスライスを歩いてバイトを吐く。セルあたり 2 割り当て (operation 構造体と文字列コピー) + 追加の 1 パス。",[18,1945,1946],{},"1 ページ 40 行 × 100 ページで、gpdf が行わない割り当てが 4,000 個発生する。",[1948,1949,1950],"h3",{"id":1950},"単一パスの痛いところ",[18,1952,1953],{},"当然の疑問: バイト出力を始めるより前に最終レイアウトを知る必要がある要素はどうするのか。ページ番号入りヘッダ。ページをまたぐテーブル。本文最終行の下にアンカーするフッタ。",[18,1955,1956,1957,1960,1961,1964,1965,1968,1969,1972],{},"答えは 2 つ。1 つ目、バッファリングの単位は ",[29,1958,1959],{},"ドキュメントではなくページ","。ページは数十 KB 程度の有界ユニット。次の ",[45,1962,1963],{},"AddPage()"," が走ると、現ページのコンテンツストリームが確定 (",[45,1966,1967],{},"Length",", ",[45,1970,1971],{},"Filter",", オフセット) され、xref エントリが書かれ、ページバッファはリセットされる。メモリの最大消費は O(1 ページ分)。",[18,1974,1975,1976,1979,1980,1983],{},"2 つ目、本当にグローバルな要素 (\"Page 3 of 27\" のような) については、その ",[29,1977,1978],{},"範囲だけ"," を fix-up パスに遅延させる。残りの内容はすでにストリームに書かれている。fix-up は短い \"deferred-reference\" マーカーのリストを歩いてパッチする。ここはコードベースで AST に近いコストを払う唯一の場所で、",[29,1981,1982],{},"実際に必要な範囲にしか"," 払わない。",[18,1985,1986,1987,897,1990,1992],{},"代わりに捨てたもの: ノードツリーに対する任意の後処理ができない。「",[45,1988,1989],{},"bold: true",[45,1991,504],{}," ノードだけ全部並び替える」ようなプラグインは書けない。ノードツリーがないからだ。この形の API が必要なら Maroto v2 を選ぶべきだ。",[18,1994,1995],{},"gpdf が対象とする用途にはこのトレードオフが正しいと考えている。PDF の大半は左から右・上から下に、構築時点で決まるレイアウトで生成される。少数派のために AST を抱え続けるコストを、多数派が全ページで払っている。その比率を入れ替えた。",[14,1997,1999],{"id":1998},"決定-2-ホットパスに-reflection-も-interface-も置かない","決定 2: ホットパスに reflection も interface も置かない",[18,2001,2002],{},"書いて面白い話ではないが、プロファイルで見ると残り半分の速度差はここから来る。",[18,2004,2005,2006,2009],{},"gofpdf の ",[45,2007,2008],{},"CellFormat"," シグネチャ:",[57,2011,2013],{"className":59,"code":2012,"language":61,"meta":62,"style":62},"func (f *Fpdf) CellFormat(w, h float64, txtStr, borderStr string,\n    ln int, alignStr string, fill bool, link int, linkStr string) { ... }\n",[45,2014,2015,2060],{"__ignoreMap":62},[66,2016,2017,2019,2021,2024,2026,2029,2031,2034,2036,2038,2040,2042,2045,2047,2050,2052,2055,2057],{"class":68,"line":69},[66,2018,168],{"class":72},[66,2020,1139],{"class":72},[66,2022,2023],{"class":439},"f ",[66,2025,1605],{"class":72},[66,2027,2028],{"class":76},"Fpdf",[66,2030,454],{"class":72},[66,2032,2033],{"class":171}," CellFormat",[66,2035,206],{"class":72},[66,2037,1874],{"class":439},[66,2039,188],{"class":72},[66,2041,1879],{"class":439},[66,2043,2044],{"class":1620}," float64",[66,2046,188],{"class":72},[66,2048,2049],{"class":439}," txtStr",[66,2051,188],{"class":72},[66,2053,2054],{"class":439}," borderStr",[66,2056,1621],{"class":1620},[66,2058,2059],{"class":72},",\n",[66,2061,2062,2065,2068,2070,2073,2075,2077,2080,2083,2085,2088,2090,2092,2095,2097,2099,2102,2104],{"class":68,"line":80},[66,2063,2064],{"class":439},"    ln",[66,2066,2067],{"class":1620}," int",[66,2069,188],{"class":72},[66,2071,2072],{"class":439}," alignStr",[66,2074,1621],{"class":1620},[66,2076,188],{"class":72},[66,2078,2079],{"class":439}," fill",[66,2081,2082],{"class":1620}," bool",[66,2084,188],{"class":72},[66,2086,2087],{"class":439}," link",[66,2089,2067],{"class":1620},[66,2091,188],{"class":72},[66,2093,2094],{"class":439}," linkStr",[66,2096,1621],{"class":1620},[66,2098,454],{"class":72},[66,2100,2101],{"class":72}," {",[66,2103,1629],{"class":72},[66,2105,2106],{"class":72}," }\n",[18,2108,2109,2110,2113,2114,2117,2118,2121,2122,1149,2125,1155,2127,2130,2131,2133],{},"これは問題ない。Maroto のコンポーネントツリーを見る。",[45,2111,2112],{},"Row"," は ",[45,2115,2116],{},"[]Component"," を持ち、",[45,2119,2120],{},"Component"," は interface。レイアウト操作のたびに仮想ディスパッチ: ",[45,2123,2124],{},"component.Render(ctx)",[45,2126,504],{},[45,2128,2129],{},"Spacer"," が入った 1 つの ",[45,2132,467],{}," で 3 回のディスパッチ。100 ページ × 30 行 × 3 コンポーネントで 9,000 回。",[18,2135,2136],{},"1 回あたり Go の interface ディスパッチは 2〜3 ns で、単独では罪ではない。ただし interface を通すことで、コンパイラはボックス化された値をヒープに置く必要がある — Go のコンパイラが常に行ってくれるわけではない devirtualization がない限り、interface 越しにスタック割り当てはできない。コストはディスパッチ自体ではなく、それを食わせるための割り当て。",[18,2138,2139],{},"gpdf のレイアウトエンジンは具象構造体を使う:",[57,2141,2143],{"className":59,"code":2142,"language":61,"meta":62,"style":62},"type RowBuilder struct {\n    doc    *Document\n    parent *pageState\n    spans  [12]int\n    cols   [12]ColBuilder  // 値配列。ポインタでも interface でもない\n    n      uint8\n}\n\ntype ColBuilder struct {\n    row    *RowBuilder\n    span   int\n    cursor document.Point\n    writer *pdf.Writer\n}\n",[45,2144,2145,2158,2168,2178,2194,2211,2219,2223,2227,2238,2248,2255,2267,2282],{"__ignoreMap":62},[66,2146,2147,2150,2153,2156],{"class":68,"line":69},[66,2148,2149],{"class":72},"type",[66,2151,2152],{"class":76}," RowBuilder",[66,2154,2155],{"class":72}," struct",[66,2157,178],{"class":72},[66,2159,2160,2163,2165],{"class":68,"line":80},[66,2161,2162],{"class":184},"    doc    ",[66,2164,1605],{"class":72},[66,2166,2167],{"class":76},"Document\n",[66,2169,2170,2173,2175],{"class":68,"line":87},[66,2171,2172],{"class":184},"    parent ",[66,2174,1605],{"class":72},[66,2176,2177],{"class":76},"pageState\n",[66,2179,2180,2183,2186,2188,2191],{"class":68,"line":97},[66,2181,2182],{"class":184},"    spans  ",[66,2184,2185],{"class":72},"[",[66,2187,472],{"class":337},[66,2189,2190],{"class":72},"]",[66,2192,2193],{"class":1620},"int\n",[66,2195,2196,2199,2201,2203,2205,2207],{"class":68,"line":109},[66,2197,2198],{"class":184},"    cols   ",[66,2200,2185],{"class":72},[66,2202,472],{"class":337},[66,2204,2190],{"class":72},[66,2206,489],{"class":76},[66,2208,2210],{"class":2209},"sHwdD","  // 値配列。ポインタでも interface でもない\n",[66,2212,2213,2216],{"class":68,"line":119},[66,2214,2215],{"class":184},"    n      ",[66,2217,2218],{"class":1620},"uint8\n",[66,2220,2221],{"class":68,"line":124},[66,2222,704],{"class":72},[66,2224,2225],{"class":68,"line":134},[66,2226,84],{"emptyLinePlaceholder":83},[66,2228,2229,2231,2234,2236],{"class":68,"line":144},[66,2230,2149],{"class":72},[66,2232,2233],{"class":76}," ColBuilder",[66,2235,2155],{"class":72},[66,2237,178],{"class":72},[66,2239,2240,2243,2245],{"class":68,"line":154},[66,2241,2242],{"class":184},"    row    ",[66,2244,1605],{"class":72},[66,2246,2247],{"class":76},"RowBuilder\n",[66,2249,2250,2253],{"class":68,"line":160},[66,2251,2252],{"class":184},"    span   ",[66,2254,2193],{"class":1620},[66,2256,2257,2260,2262,2264],{"class":68,"line":165},[66,2258,2259],{"class":184},"    cursor ",[66,2261,318],{"class":76},[66,2263,200],{"class":72},[66,2265,2266],{"class":76},"Point\n",[66,2268,2269,2272,2274,2277,2279],{"class":68,"line":181},[66,2270,2271],{"class":184},"    writer ",[66,2273,1605],{"class":72},[66,2275,2276],{"class":76},"pdf",[66,2278,200],{"class":72},[66,2280,2281],{"class":76},"Writer\n",[66,2283,2284],{"class":68,"line":219},[66,2285,704],{"class":72},[18,2287,2288,2291,2292,1149],{},[45,2289,2290],{},"cols"," はグリッドの最大 (12) にサイズされた値配列。ヒープ割り当てなし。行がカラムを反復するときに interface ディスパッチもなし。writer のポインタを Builder が持つ構造で、",[29,2293,2294],{},"writer は Builder ツリーを知らない",[18,2296,2297,2298,2301,2302,2304],{},"コールバックパターン (",[45,2299,2300],{},"r.Col(4, func(c *ColBuilder) { ... })",") は偶然ではない。プロトタイプした他の形 (チェーン可能な struct 返し API、Component interface のボックス化ツリー) は全部遅かった。このクロージャのゼロ割り当ては ",[45,2303,489],{}," が呼び出し側のポインタ引数経由で取られる値で、クロージャ自体が大半の場合 escape analysis でスタックに載ることによる。",[1948,2306,2307],{"id":2307},"これが効いたとわかるところ",[18,2309,2310,2311,2314],{},"gpdf で ",[45,2312,2313],{},"go test -run=XXX -bench=BenchmarkSinglePage -memprofile=mem.out"," を回す:",[57,2316,2319],{"className":2317,"code":2318,"language":924},[922],"BenchmarkSinglePage-8   91270   13120 ns/op   8321 B/op   52 allocs/op\n",[45,2320,2318],{"__ignoreMap":62},[18,2322,2323,2324,2327,2328,2330],{},"1 つの PDF ページ全体で ",[29,2325,2326],{},"52 割り当て","。そのほぼ全部が初期ページバッファ、フォントメトリクス参照 (フォントごと 1 回、グリフごとではない)、最後の ",[45,2329,1899],{}," 成長。レイアウトループはゼロ割り当て — プロファイルを見ればわかる。",[18,2332,2333],{},"gofpdf で同じページ:",[57,2335,2338],{"className":2336,"code":2337,"language":924},[922],"BenchmarkGofpdfSinglePage-8   7500   132400 ns/op   71200 B/op   430 allocs/op\n",[45,2339,2337],{"__ignoreMap":62},[18,2341,2342],{},"430 割り当て。大半が operation スライスとそれを埋める文字列コピー。割り当てが約 8 倍あれば、GC を含めた実行時間の差が約 10 倍になるのは自然な話になる。",[1948,2344,2345],{"id":2345},"代わりに失ったもの",[18,2347,2348,2349,2352,2353,2355,2356,2359],{},"ホットパスのエルゴノミクスがゼロということは、",[29,2350,2351],{},"拡張ポイントが少ない"," ということ。gpdf のレイアウトに組み込める独自要素型 — Maroto の ",[45,2354,2120],{}," を実装するのと同等のこと — は書けない。実装すべき interface がない。代わりに用意しているのは ",[45,2357,2358],{},"template.WithWriterSetup()","。これは PDF writer へのフックで、カスタムアノテーション、PDF/A メタデータ、暗号化などを注入できる。レイアウトレベルの拡張は、ユーザーが呼ぶのと同じ Builder メソッドを呼ぶヘルパーとして書くことになる。",[18,2361,2362],{},"拡張ポイントが少ないのは本物のコストだ。今の判断としては釣り合っていると考えている。プロジェクトの方向性が変わってその判断が成り立たなくなったら、見直す。",[14,2364,2366],{"id":2365},"決定-3-再走査しない-truetype-サブセッタ","決定 3: 再走査しない TrueType サブセッタ",[18,2368,2369],{},"CJK ベンチマーク (gpdf 133 µs 対 gofpdf 254 µs) の差はここから大半が出てくる。",[18,2371,2372,2373,2376],{},"TrueType サブセッタの役割をざっくり: PDF に日本語フォントを埋め込むとき、20,000 グリフ全部を埋め込みたくはない — 100 KB のドキュメントに 15 MB のフォントデータが乗ってしまう。",[29,2374,2375],{},"ドキュメントで実際に使われるグリフだけ","、PDF リーダーがデコードできる有効な subset TTF としてパッケージしたい。",[18,2378,2379],{},"そのためには:",[879,2381,2382,2397,2400,2403],{},[882,2383,2384,2385,2387,2388,2390,2391,2393,2394,2396],{},"完全な TTF テーブルをパース: ",[45,2386,857],{}," (文字→グリフ対応)、",[45,2389,850],{}," (アウトライン)、",[45,2392,854],{}," (glyf へのオフセット)、",[45,2395,732],{}," (水平メトリクス) など。",[882,2398,2399],{},"ドキュメント内の各文字について、cmap 経由でグリフ ID を引く。",[882,2401,2402],{},"合成グリフが参照する副次グリフも推移的に収集。",[882,2404,2405],{},"使うグリフのみ番号を振り直した新しい TTF を吐く。",[18,2407,2408,2409,2412,2413,2416],{},"ホットパスはステップ 2 — ",[29,2410,2411],{},"cmap ルックアップ","。gofpdf の実装はグリフルックアップのたびに cmap テーブルを ",[29,2414,2415],{},"先頭から歩く","。Latin だけのページなら問題ない。cmap は小さくキャッシュも行儀がいい。CJK のページで 150 個のユニークグリフがあれば、テーブルを 150 回全走査することになる。",[18,2418,2419],{},"cmap format 12 (現代の CJK フォントで使われる) は (start, end, startGlyphID) の三つ組をソートして並べた配列。1 回の走査は範囲数に対して O(n) で、NotoSansJP なら 200〜500 範囲。150 ルックアップ × 範囲ごとの比較 × 400 範囲 = 必要量よりはるかに多い仕事。",[18,2421,2422,2423,2426],{},"gpdf はフォントの初回ロード時点で cmap 全体を ",[45,2424,2425],{},"map[rune]uint16"," に展開する。その後のルックアップは全部 O(1)。NotoSansJP なら初回コストが約 150 µs、以降は 1 文字 10 ns。",[57,2428,2430],{"className":59,"code":2429,"language":61,"meta":62,"style":62},"// pdf/font/ttf.go より単純化\ntype Font struct {\n    runeToGID map[rune]uint16  // ロード時に一度だけ解決\n    glyphs    []glyph          // GID でインデックス\n    metrics   []glyphMetric\n}\n\nfunc (f *Font) GlyphFor(r rune) uint16 {\n    return f.runeToGID[r]  // O(1)、キャッシュフレンドリ、テーブル走査なし\n}\n",[45,2431,2432,2437,2448,2467,2481,2491,2495,2499,2530,2552],{"__ignoreMap":62},[66,2433,2434],{"class":68,"line":69},[66,2435,2436],{"class":2209},"// pdf/font/ttf.go より単純化\n",[66,2438,2439,2441,2444,2446],{"class":68,"line":80},[66,2440,2149],{"class":72},[66,2442,2443],{"class":76}," Font",[66,2445,2155],{"class":72},[66,2447,178],{"class":72},[66,2449,2450,2453,2456,2459,2461,2464],{"class":68,"line":87},[66,2451,2452],{"class":184},"    runeToGID ",[66,2454,2455],{"class":72},"map[",[66,2457,2458],{"class":1620},"rune",[66,2460,2190],{"class":72},[66,2462,2463],{"class":1620},"uint16",[66,2465,2466],{"class":2209},"  // ロード時に一度だけ解決\n",[66,2468,2469,2472,2475,2478],{"class":68,"line":97},[66,2470,2471],{"class":184},"    glyphs    ",[66,2473,2474],{"class":72},"[]",[66,2476,2477],{"class":76},"glyph",[66,2479,2480],{"class":2209},"          // GID でインデックス\n",[66,2482,2483,2486,2488],{"class":68,"line":109},[66,2484,2485],{"class":184},"    metrics   ",[66,2487,2474],{"class":72},[66,2489,2490],{"class":76},"glyphMetric\n",[66,2492,2493],{"class":68,"line":119},[66,2494,704],{"class":72},[66,2496,2497],{"class":68,"line":124},[66,2498,84],{"emptyLinePlaceholder":83},[66,2500,2501,2503,2505,2507,2509,2511,2513,2516,2518,2520,2523,2525,2528],{"class":68,"line":134},[66,2502,168],{"class":72},[66,2504,1139],{"class":72},[66,2506,2023],{"class":439},[66,2508,1605],{"class":72},[66,2510,1768],{"class":76},[66,2512,454],{"class":72},[66,2514,2515],{"class":171}," GlyphFor",[66,2517,206],{"class":72},[66,2519,440],{"class":439},[66,2521,2522],{"class":1620}," rune",[66,2524,454],{"class":72},[66,2526,2527],{"class":1620}," uint16",[66,2529,178],{"class":72},[66,2531,2532,2535,2538,2540,2543,2545,2547,2549],{"class":68,"line":144},[66,2533,2534],{"class":90},"    return",[66,2536,2537],{"class":184}," f",[66,2539,200],{"class":72},[66,2541,2542],{"class":184},"runeToGID",[66,2544,2185],{"class":72},[66,2546,440],{"class":184},[66,2548,2190],{"class":72},[66,2550,2551],{"class":2209},"  // O(1)、キャッシュフレンドリ、テーブル走査なし\n",[66,2553,2554],{"class":68,"line":154},[66,2555,704],{"class":72},[18,2557,2558],{},"rune でインデックスされたマップ 1 つ、cmap テーブルの線形スキャン 1 回で構築。同じフォントを複数ページ (=普通はすべてのページ) で使うドキュメントでは、グリフルックアップが「ページ数 × グリフ数のほぼ二次」から「総グリフ数 + 定数」に変わる。",[1948,2560,2562],{"id":2561},"format-12が要点","「format 12」が要点",[18,2564,2565,2566,897,2568,2571,2572,2575],{},"古い Go PDF ライブラリの多くは、Latin しか誰も気にしなかった頃に書かれた。実装されている cmap は format 4 — Basic Multilingual Plane (U+0000〜U+FFFF) のセグメント範囲。BMP 外の日本語 (一部の異体字 Kanji) には format 12 が必要。",[45,2567,1431],{},[45,2569,2570],{},"AddUTF8Font"," は NotoSansJP-Regular.ttf で ",[29,2573,2574],{},"panic する","。format 12 のパーサが最後まで書かれていないからだ。",[18,2577,2578],{},"これは揶揄ではない。遺物だ。gofpdf は 2015 年頃の Latin 中心の Web アプリに必要なものとして優秀だった。フォークはそのスコープを引き継いだ。時代が変わり、CJK は「誰かの問題」から「日本語と中国語 Go エコシステムの多数派の問題」になった。gpdf は cmap 仕様を全実装した。しなければ「品目」のところに豆腐が並ぶ請求書が出力される — 公開 1 週目に実際に届いたバグ報告だ。",[1948,2580,2581],{"id":2581},"ドキュメント数ではなくフォント数でスケールするキャッシュ",[18,2583,2584,2585,2588,2589,2591,2592,2595],{},"フォントキャッシュは ",[45,2586,2587],{},"Document"," ごとで、グローバルではない。同じフォントで 10,000 PDF を生成すると、150 µs の解決コストを 10,000 回払う — ただし ",[45,2590,1768],{}," インスタンスをドキュメント間で共有すれば別で、",[45,2593,2594],{},"gpdf.WithSharedFont(preloadedFont)"," で API が用意されている。",[18,2597,2598,2599,2602],{},"高スループットのバッチ生成 (SaaS の ",[45,2600,2601],{},"gpdf-api"," はこれ) では、この共有フォントパターンが P95 レイテンシを予測可能にする。docs で紹介している。OSS ユーザーの大半は必要ない。",[14,2604,2605],{"id":2605},"組み合わさった効果",[18,2607,2608],{},"3 つの決定を 100 ページベンチマーク (gpdf 683 µs, gofpdf 11.7 ms) に当てはめると:",[738,2610,2611,2624],{},[741,2612,2613],{},[744,2614,2615,2618,2621],{},[747,2616,2617],{},"時間の出処",[747,2619,2620],{},"gofpdf (ページあたり概算)",[747,2622,2623],{},"gpdf (ページあたり概算)",[756,2625,2626,2637,2648,2659,2670],{},[744,2627,2628,2631,2634],{},[761,2629,2630],{},"operation スライス構築",[761,2632,2633],{},"約 60 µs",[761,2635,2636],{},"0 (直接ストリーム)",[744,2638,2639,2642,2645],{},[761,2640,2641],{},"operation シリアライズ",[761,2643,2644],{},"約 35 µs",[761,2646,2647],{},"0 (既に書かれた)",[744,2649,2650,2653,2656],{},[761,2651,2652],{},"グリフルックアップ (40 字)",[761,2654,2655],{},"約 6 µs",[761,2657,2658],{},"約 0.4 µs",[744,2660,2661,2664,2667],{},[761,2662,2663],{},"割り当て / GC 圧",[761,2665,2666],{},"約 20 µs",[761,2668,2669],{},"約 2 µs",[744,2671,2672,2677,2682],{},[761,2673,2674],{},[29,2675,2676],{},"合計",[761,2678,2679],{},[29,2680,2681],{},"約 120 µs",[761,2683,2684],{},[29,2685,2686],{},"約 7 µs",[18,2688,2689,2690,2693],{},"数字はプロファイルからの推定で、内容によって実際の内訳は変わる。ただし形はこの通り。",[29,2691,2692],{},"3 つのうちどれ 1 つも単独では 10 倍の勝ちにならない","。積み重なって初めて 10 倍になる。",[18,2695,2696],{},"系として: 既存ライブラリに 1 つだけコピーすれば 2〜3 倍の改善は得られる。10 倍が欲しいなら 3 つ必要で、最初の 1 つ (単一パス) を AST ベースのライブラリに後付けするには書き直すしかない。",[14,2698,2700],{"id":2699},"捨てたもの-正直の節","捨てたもの (正直の節)",[18,2702,2703],{},"ここまで言い方を工夫してきたが、全部列挙する:",[18,2705,2706,2709,2710,2713],{},[29,2707,2708],{},"AST ベースの後処理。"," プラグインアーキテクチャなし。「ノードツリーを歩いて変換を適用」なし。ドキュメント全体のテキストスタイルをレンダリング前に一括編集したければ、Builder を呼ぶ ",[29,2711,2712],{},"前"," にやる。",[18,2715,2716,893,2719,2722],{},[29,2717,2718],{},"イントロスペクション。",[45,2720,2721],{},"doc.Components()"," で入れたもの全部を返すようなメソッドはない。意味のあるメソッドが走る時点で、ドキュメントはもう演算子のストリーム。大半のユーザーには関係ない。ドキュメント操作ツールを書く少数派には関係する。",[18,2724,2725,2728,2729,2732,2733,2736],{},[29,2726,2727],{},"reflection ベースのシリアライゼーション。"," 任意の構造体を PDF に変換する ",[45,2730,2731],{},"json.Unmarshal"," 風 API はない。JSON Schema 入口 (",[45,2734,2735],{},"template.FromJSON",") はサポートする形を明示している。意図的だ。汎用の Go 構造体を食わせて PDF を返す API が欲しければ unidoc の領域。",[18,2738,2739,893,2742,2744],{},[29,2740,2741],{},"interface の拡張性。",[45,2743,2120],{}," を実装してカスタム要素を登録することはできない。Builder 呼び出しをラップするヘルパー関数は書ける。実用上はそれで 95% カバーできるが、モデルは違う。",[18,2746,2747],{},"全部意図した結果だ。1 つでも採用すれば速度は死ぬ。「速くて意見の強いモノ」が嬉しいバケットのユーザーを優先し、「柔軟でプラグイン豊富」が必要なバケットのユーザーは Maroto v2 か unidoc の方が合う。",[14,2749,2750],{"id":2750},"ベンチマークの再現",[18,2752,2753],{},"できる。コードを公開している目的はそこ。",[57,2755,2757],{"className":1247,"code":2756,"language":1249,"meta":62,"style":62},"git clone https://github.com/gpdf-dev/gpdf\ncd gpdf/_benchmark\ngo test -bench=. -benchmem -benchtime=5s\n",[45,2758,2759,2770,2778],{"__ignoreMap":62},[66,2760,2761,2764,2767],{"class":68,"line":69},[66,2762,2763],{"class":76},"git",[66,2765,2766],{"class":212}," clone",[66,2768,2769],{"class":212}," https://github.com/gpdf-dev/gpdf\n",[66,2771,2772,2775],{"class":68,"line":80},[66,2773,2774],{"class":171},"cd",[66,2776,2777],{"class":212}," gpdf/_benchmark\n",[66,2779,2780,2782,2785,2788,2791],{"class":68,"line":87},[66,2781,61],{"class":76},[66,2783,2784],{"class":212}," test",[66,2786,2787],{"class":212}," -bench=.",[66,2789,2790],{"class":212}," -benchmem",[66,2792,2793],{"class":212}," -benchtime=5s\n",[18,2795,2796,2797,2802],{},"そのディレクトリの README に 4 つのワークロードと計測内容が書いてある。同じ CPU アーキテクチャ、同じ Go バージョンで 20% 以上ズレたら ",[21,2798,2801],{"href":2799,"rel":2800},"https://github.com/gpdf-dev/gpdf/issues",[25],"issue を立ててほしい"," — ドリフトは実在する。",[18,2804,2805],{},"2 つ補足:",[1109,2807,2808,2814],{},[882,2809,2810,2811,2813],{},"ベンチは ",[45,2812,1413],{}," 有効で走らせている。無効にすれば全体で約 5% 改善するが、実コードの走らせ方ではないので公開数字には入れない。",[882,2815,2816],{},"CGO 無効。FreeType バックエンドを CGO で組んだ方がフォント操作が速いのではという質問を受けて実験した。FFI 境界のマーシャリングコストが得より大きかった。PDF ジェネレータのアクセスパターンに対しては純 Go のサブセッタが勝つ。",[14,2818,2820],{"id":2819},"faq","FAQ",[18,2822,2823,2826,2827,2831],{},[29,2824,2825],{},"なぜアーカイブ済みの gofpdf と比較する?","\nいまだに GitHub で「go pdf」検索のトップ結果で、gpdf に着地するチームの大半はそこから移行してくる。ベンチマークはこの層に「移行する価値があるか」を答える必要がある。答え: ある。",[21,2828,2830],{"href":2829},"/blog/gofpdf-migration","移行ガイド"," も書いた。",[18,2833,2834,2837],{},[29,2835,2836],{},"PDF 生成において 10 倍速は実質意味があるのか?","\nワークロード次第。1 リクエスト 1 ドキュメントならほぼ関係ない — どちらも「リクエストパス上で生成」の閾値は越える。バッチ処理 (夜間明細、大量請求書、DB クエリからのレポート生成) では差がそのまま台数減になる。バッチパイプラインを最初に移行したチームから「ワーカー数が 10 分の 1」と聞いた。彼らの計算は監査していないが、ベンチの形と整合している。",[18,2839,2840,2843,2844,2847,2848,2850],{},[29,2841,2842],{},"CJK の数字の落とし穴は?","\nフォントファイルは自分で同梱する必要がある。gpdf がサブセット化するが、3 MB の NotoSansJP TTF は Go バイナリに埋め込むか起動時に ",[45,2845,2846],{},"os.ReadFile"," するかのどちらか。distroless イメージでは効く。SaaS の ",[45,2849,2601],{}," はイメージに代表的フォントを同梱して解決している。OSS ユーザーは自分で扱う。",[18,2852,2853,2856],{},[29,2854,2855],{},"機能が増えたら遅くなるのか?","\n一番気にしている質問。答え: リリースごとに前バージョンとベンチマークを取り、4 ワークロードのいずれかで 5% 以上の悪化が出たらリリースを止める。ベンチがライブラリと同じリポジトリにある理由はまさにこれだ。",[18,2858,2859,2862],{},[29,2860,2861],{},"名前の由来は?","\ngpdf = Go + PDF。捻りはない。狙ってシンプルにしてある。",[14,2864,1241],{"id":1240},[18,2866,2867],{},"gpdf は Go の PDF 生成ライブラリ。MIT、ゼロ依存、CJK ネイティブ対応。",[57,2869,2870],{"className":1247,"code":1248,"language":1249,"meta":62,"style":62},[45,2871,2872],{"__ignoreMap":62},[66,2873,2874,2876,2878],{"class":68,"line":69},[66,2875,61],{"class":76},[66,2877,1258],{"class":212},[66,2879,1261],{"class":212},[18,2881,2882,1268,2886],{},[21,2883,2885],{"href":23,"rel":2884},[25],"⭐ Star on GitHub",[21,2887,2889],{"href":1271,"rel":2888},[25],"ドキュメント",[14,2891,2892],{"id":2892},"次に読む",[1109,2894,2895,2902,2909],{},[882,2896,2897,2901],{},[21,2898,2900],{"href":2899},"/ja/blog/go-pdf-library-showdown-2026","2026 年 Go PDF ライブラリ徹底比較"," — ライセンスと依存を含む全体比較。",[882,2903,2904,2908],{},[21,2905,2907],{"href":2906},"/ja/blog/gofpdf-migration","gofpdf はアーカイブされた。gpdf への移行方法"," — Before/After の API 対応 5 組、全部動く。",[882,2910,2911,2912,1149],{},"ベンチマークコードそのもの: ",[21,2913,2915],{"href":1391,"rel":2914},[25],[45,2916,1395],{},[1275,2918,2919],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}",{"title":62,"searchDepth":80,"depth":80,"links":2921},[2922,2923,2924,2927,2931,2935,2936,2937,2938,2939,2940],{"id":1334,"depth":80,"text":1335},{"id":1406,"depth":80,"text":1407},{"id":1550,"depth":80,"text":1551,"children":2925},[2926],{"id":1950,"depth":87,"text":1950},{"id":1998,"depth":80,"text":1999,"children":2928},[2929,2930],{"id":2307,"depth":87,"text":2307},{"id":2345,"depth":87,"text":2345},{"id":2365,"depth":80,"text":2366,"children":2932},[2933,2934],{"id":2561,"depth":87,"text":2562},{"id":2581,"depth":87,"text":2581},{"id":2605,"depth":80,"text":2605},{"id":2699,"depth":80,"text":2700},{"id":2750,"depth":80,"text":2750},{"id":2819,"depth":80,"text":2820},{"id":1240,"depth":80,"text":1241},{"id":2892,"depth":80,"text":2892},"1 ページ 13 µs、100 ページレポート 683 µs。チューニングではなく 3 つの設計判断の積。実際のコードパスで説明する。",{},"/ja/blog/why-gpdf-is-faster",{"title":1328,"description":2941},"ja/blog/011.why-gpdf-is-faster",[2947,2948,2949],"benchmark","internals","comparison","6EQvoM8kQ4JKeH5HvtDZ_JT__2jK_3LfDnQJjj5TCRY",{"id":2952,"title":2953,"author":2954,"body":2955,"date":4042,"description":4043,"draft":1292,"extension":1293,"howTo":4044,"image":1316,"meta":4064,"navigation":83,"path":1235,"seo":4065,"stem":4066,"tags":4067,"updated":1316,"__hash__":4069},"blogJa/ja/blog/008.tofu-boxes-japanese.md","gpdf で日本語が豆腐文字 (□□□) になる原因と直し方",{"name":8,"url":9},{"type":11,"value":2956,"toc":4030},[2957,2959,2962,2964,2967,2970,3025,3029,3499,3512,3522,3525,3528,3544,3574,3580,3584,3591,3732,3763,3767,3778,3781,3784,3812,3815,3819,3825,3889,3896,3899,3950,3954,3975,3977,4003,4005,4007,4019,4027],[14,2958,16],{"id":16},[18,2960,2961],{},"gpdf で日本語を書いたら、PDF にしたときに文字が □ で出てしまう。これは何で、どう直すのか。",[14,2963,35],{"id":35},[18,2965,2966],{},"これは豆腐文字 (tofu)。PDF ビューアが、埋め込まれているフォントに該当コードポイントのグリフがないときに代替で描く矩形。原因は 4 つあり、そのうち 1 つが圧倒的に多い。",[18,2968,2969],{},"頻度順:",[879,2971,2972,2984,3001,3011],{},[882,2973,2974,893,2977,2980,2981,2983],{},[29,2975,2976],{},"CJK フォントを登録していない。",[45,2978,2979],{},"gpdf.NewDocument"," に ",[45,2982,351],{}," がなく、PDF 標準 14 フォント (Helvetica / Times / Courier) にフォールバックしている。どれも U+3040〜U+9FFF をカバーしない。",[882,2985,2986,893,2993,2996,2997,3000],{},[29,2987,2988,2989,2992],{},"CJK フォントは登録したが ",[45,2990,2991],{},"c.Text"," のファミリ名が違う。",[45,2994,2995],{},"WithFont(\"NotoSansJP\", ...)"," はあるのに ",[45,2998,2999],{},"template.FontFamily(\"Arial\")"," を指定していて、Latin フォントで日本語を引きにいっている。",[882,3002,3003,3006,3007,3010],{},[29,3004,3005],{},"TTF ファイル自体に CJK グリフが入っていない。"," ディスク上の TTF が Latin サブセット (",[45,3008,3009],{},"NotoSans-Regular.ttf",") で、見た目のファイル名は正しそうだが中身に日本語グリフがない。",[882,3012,3013,3016,3017,3024],{},[29,3014,3015],{},"gpdf に渡す前に文字列が化けている。"," Shift-JIS や Latin-1 として読んでしまった文字列を UTF-8 として扱っており、そもそも日本語の Unicode コードポイントではなくなっている。",[29,3018,3019,3020,3023],{},"□ ではなく ",[45,3021,3022],{},"縺ゅ→縺"," のような化け方"," ならこれ。",[14,3026,3028],{"id":3027},"原因-1-の直し方-これで-9-割片付く","原因 #1 の直し方 (これで 9 割片付く)",[57,3030,3032],{"className":59,"code":3031,"language":61,"meta":62,"style":62},"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",[45,3033,3034,3040,3044,3050,3058,3066,3070,3078,3086,3094,3098,3102,3112,3139,3151,3165,3169,3173,3187,3205,3235,3258,3281,3285,3289,3303,3327,3357,3376,3380,3384,3388,3406,3418,3432,3436,3477,3491,3495],{"__ignoreMap":62},[66,3035,3036,3038],{"class":68,"line":69},[66,3037,73],{"class":72},[66,3039,77],{"class":76},[66,3041,3042],{"class":68,"line":80},[66,3043,84],{"emptyLinePlaceholder":83},[66,3045,3046,3048],{"class":68,"line":87},[66,3047,91],{"class":90},[66,3049,94],{"class":72},[66,3051,3052,3054,3056],{"class":68,"line":97},[66,3053,100],{"class":72},[66,3055,103],{"class":76},[66,3057,106],{"class":72},[66,3059,3060,3062,3064],{"class":68,"line":109},[66,3061,100],{"class":72},[66,3063,114],{"class":76},[66,3065,106],{"class":72},[66,3067,3068],{"class":68,"line":119},[66,3069,84],{"emptyLinePlaceholder":83},[66,3071,3072,3074,3076],{"class":68,"line":124},[66,3073,100],{"class":72},[66,3075,129],{"class":76},[66,3077,106],{"class":72},[66,3079,3080,3082,3084],{"class":68,"line":134},[66,3081,100],{"class":72},[66,3083,139],{"class":76},[66,3085,106],{"class":72},[66,3087,3088,3090,3092],{"class":68,"line":144},[66,3089,100],{"class":72},[66,3091,149],{"class":76},[66,3093,106],{"class":72},[66,3095,3096],{"class":68,"line":154},[66,3097,157],{"class":72},[66,3099,3100],{"class":68,"line":160},[66,3101,84],{"emptyLinePlaceholder":83},[66,3103,3104,3106,3108,3110],{"class":68,"line":165},[66,3105,168],{"class":72},[66,3107,172],{"class":171},[66,3109,175],{"class":72},[66,3111,178],{"class":72},[66,3113,3114,3116,3118,3120,3122,3124,3126,3128,3130,3132,3135,3137],{"class":68,"line":181},[66,3115,185],{"class":184},[66,3117,188],{"class":72},[66,3119,191],{"class":184},[66,3121,194],{"class":72},[66,3123,197],{"class":184},[66,3125,200],{"class":72},[66,3127,203],{"class":171},[66,3129,206],{"class":72},[66,3131,209],{"class":72},[66,3133,3134],{"class":212},"NotoSansJP-Regular.ttf",[66,3136,209],{"class":72},[66,3138,157],{"class":72},[66,3140,3141,3143,3145,3147,3149],{"class":68,"line":219},[66,3142,222],{"class":90},[66,3144,191],{"class":184},[66,3146,227],{"class":72},[66,3148,230],{"class":72},[66,3150,178],{"class":72},[66,3152,3153,3155,3157,3159,3161,3163],{"class":68,"line":235},[66,3154,238],{"class":184},[66,3156,200],{"class":72},[66,3158,243],{"class":171},[66,3160,206],{"class":72},[66,3162,248],{"class":184},[66,3164,157],{"class":72},[66,3166,3167],{"class":68,"line":253},[66,3168,256],{"class":72},[66,3170,3171],{"class":68,"line":259},[66,3172,84],{"emptyLinePlaceholder":83},[66,3174,3175,3177,3179,3181,3183,3185],{"class":68,"line":264},[66,3176,267],{"class":184},[66,3178,194],{"class":72},[66,3180,272],{"class":184},[66,3182,200],{"class":72},[66,3184,277],{"class":171},[66,3186,280],{"class":72},[66,3188,3189,3191,3193,3195,3197,3199,3201,3203],{"class":68,"line":283},[66,3190,286],{"class":184},[66,3192,200],{"class":72},[66,3194,291],{"class":171},[66,3196,206],{"class":72},[66,3198,26],{"class":184},[66,3200,200],{"class":72},[66,3202,300],{"class":184},[66,3204,303],{"class":72},[66,3206,3207,3209,3211,3213,3215,3217,3219,3221,3223,3225,3227,3229,3231,3233],{"class":68,"line":306},[66,3208,286],{"class":184},[66,3210,200],{"class":72},[66,3212,313],{"class":171},[66,3214,206],{"class":72},[66,3216,318],{"class":184},[66,3218,200],{"class":72},[66,3220,323],{"class":171},[66,3222,206],{"class":72},[66,3224,318],{"class":184},[66,3226,200],{"class":72},[66,3228,332],{"class":171},[66,3230,206],{"class":72},[66,3232,338],{"class":337},[66,3234,341],{"class":72},[66,3236,3237,3239,3241,3243,3245,3247,3250,3252,3254,3256],{"class":68,"line":344},[66,3238,286],{"class":184},[66,3240,200],{"class":72},[66,3242,351],{"class":171},[66,3244,206],{"class":72},[66,3246,209],{"class":72},[66,3248,3249],{"class":212},"NotoSansJP",[66,3251,209],{"class":72},[66,3253,188],{"class":72},[66,3255,365],{"class":184},[66,3257,303],{"class":72},[66,3259,3260,3262,3264,3266,3268,3270,3272,3274,3276,3279],{"class":68,"line":370},[66,3261,286],{"class":184},[66,3263,200],{"class":72},[66,3265,377],{"class":171},[66,3267,206],{"class":72},[66,3269,209],{"class":72},[66,3271,3249],{"class":212},[66,3273,209],{"class":72},[66,3275,188],{"class":72},[66,3277,3278],{"class":337}," 12",[66,3280,303],{"class":72},[66,3282,3283],{"class":68,"line":395},[66,3284,398],{"class":72},[66,3286,3287],{"class":68,"line":401},[66,3288,84],{"emptyLinePlaceholder":83},[66,3290,3291,3293,3295,3297,3299,3301],{"class":68,"line":406},[66,3292,409],{"class":184},[66,3294,194],{"class":72},[66,3296,414],{"class":184},[66,3298,200],{"class":72},[66,3300,419],{"class":171},[66,3302,422],{"class":72},[66,3304,3305,3307,3309,3311,3313,3315,3317,3319,3321,3323,3325],{"class":68,"line":425},[66,3306,428],{"class":184},[66,3308,200],{"class":72},[66,3310,433],{"class":171},[66,3312,436],{"class":72},[66,3314,440],{"class":439},[66,3316,443],{"class":72},[66,3318,446],{"class":76},[66,3320,200],{"class":72},[66,3322,451],{"class":76},[66,3324,454],{"class":72},[66,3326,178],{"class":72},[66,3328,3329,3331,3333,3335,3337,3339,3341,3343,3345,3347,3349,3351,3353,3355],{"class":68,"line":459},[66,3330,462],{"class":184},[66,3332,200],{"class":72},[66,3334,467],{"class":171},[66,3336,206],{"class":72},[66,3338,472],{"class":337},[66,3340,188],{"class":72},[66,3342,477],{"class":72},[66,3344,480],{"class":439},[66,3346,443],{"class":72},[66,3348,446],{"class":76},[66,3350,200],{"class":72},[66,3352,489],{"class":76},[66,3354,454],{"class":72},[66,3356,178],{"class":72},[66,3358,3359,3361,3363,3365,3367,3369,3372,3374],{"class":68,"line":496},[66,3360,499],{"class":184},[66,3362,200],{"class":72},[66,3364,504],{"class":171},[66,3366,206],{"class":72},[66,3368,209],{"class":72},[66,3370,3371],{"class":212},"こんにちは、世界。",[66,3373,209],{"class":72},[66,3375,157],{"class":72},[66,3377,3378],{"class":68,"line":544},[66,3379,567],{"class":72},[66,3381,3382],{"class":68,"line":564},[66,3383,573],{"class":72},[66,3385,3386],{"class":68,"line":570},[66,3387,84],{"emptyLinePlaceholder":83},[66,3389,3390,3392,3394,3396,3398,3400,3402,3404],{"class":68,"line":576},[66,3391,584],{"class":184},[66,3393,188],{"class":72},[66,3395,191],{"class":184},[66,3397,194],{"class":72},[66,3399,414],{"class":184},[66,3401,200],{"class":72},[66,3403,597],{"class":171},[66,3405,422],{"class":72},[66,3407,3408,3410,3412,3414,3416],{"class":68,"line":581},[66,3409,222],{"class":90},[66,3411,191],{"class":184},[66,3413,227],{"class":72},[66,3415,230],{"class":72},[66,3417,178],{"class":72},[66,3419,3420,3422,3424,3426,3428,3430],{"class":68,"line":602},[66,3421,238],{"class":184},[66,3423,200],{"class":72},[66,3425,243],{"class":171},[66,3427,206],{"class":72},[66,3429,248],{"class":184},[66,3431,157],{"class":72},[66,3433,3434],{"class":68,"line":615},[66,3435,256],{"class":72},[66,3437,3438,3440,3442,3444,3446,3448,3450,3452,3454,3457,3459,3461,3463,3465,3467,3469,3471,3473,3475],{"class":68,"line":630},[66,3439,222],{"class":90},[66,3441,191],{"class":184},[66,3443,194],{"class":72},[66,3445,197],{"class":184},[66,3447,200],{"class":72},[66,3449,648],{"class":171},[66,3451,206],{"class":72},[66,3453,209],{"class":72},[66,3455,3456],{"class":212},"hello.pdf",[66,3458,209],{"class":72},[66,3460,188],{"class":72},[66,3462,662],{"class":184},[66,3464,188],{"class":72},[66,3466,667],{"class":337},[66,3468,670],{"class":72},[66,3470,191],{"class":184},[66,3472,227],{"class":72},[66,3474,230],{"class":72},[66,3476,178],{"class":72},[66,3478,3479,3481,3483,3485,3487,3489],{"class":68,"line":635},[66,3480,238],{"class":184},[66,3482,200],{"class":72},[66,3484,243],{"class":171},[66,3486,206],{"class":72},[66,3488,248],{"class":184},[66,3490,157],{"class":72},[66,3492,3493],{"class":68,"line":681},[66,3494,256],{"class":72},[66,3496,3497],{"class":68,"line":696},[66,3498,704],{"class":72},[18,3500,3501,3502,3504,3505,3508,3509,3511],{},"2 行で登録と既定設定が済む。CGO なし、",[45,3503,2570],{}," の段取りもなし。",[45,3506,3507],{},"□□□□□、□□。"," になっていたものが、このコードと ",[45,3510,3134],{}," を同じディレクトリに置いて実行すれば正しい日本語で出る。",[18,3513,3514,2113,3516,3521],{},[45,3515,3134],{},[21,3517,3520],{"href":3518,"rel":3519},"https://fonts.google.com/noto/specimen/Noto+Sans+JP",[25],"Google Fonts"," から。",[14,3523,3524],{"id":3524},"どの原因か見分ける",[18,3526,3527],{},"見るべきは 3 箇所 — ドキュメントを組み立てているところ、テキストを書いているところ、TTF ファイルそのもの。",[18,3529,3530,3536,3537,3540,3541,3543],{},[29,3531,3532,3533],{},"出力が一様な ",[45,3534,3535],{},"□□□"," (全部同じ矩形) なら原因 1・2・3 のいずれか。PDF にはフォントが埋め込まれているが、そのフォントに必要なグリフがない状態。PDF を Acrobat で開き ",[45,3538,3539],{},"ファイル → プロパティ → フォント"," でどれが埋め込まれているか見る。Helvetica / Times / Courier だけなら原因 1。",[45,3542,3249],{}," が載っているのに豆腐なら原因 2 か 3。",[18,3545,3546,3555,3556,3559,3560,3562,3563,3566,3567,3551,3570,3573],{},[29,3547,3548,3549,3551,3552],{},"出力が ",[45,3550,3022],{}," や ",[45,3553,3554],{},"ã\"ã‚\"ã«ã¡ã¯"," のような Latin 交じりの化け方なら原因 4。日本語文字列が gpdf に渡る前に再エンコードされている。日本で一番多いのが ",[29,3557,3558],{},"Excel で保存した Shift-JIS CSV"," を ",[45,3561,2846],{}," でそのまま ",[45,3564,3565],{},"string"," にしているケース。次点は MySQL のカラム定義が ",[45,3568,3569],{},"utf8mb3",[45,3571,3572],{},"latin1"," のままになっているケース。gpdf 側では直せない — 読み込み側を修正する。",[18,3575,3576,3579],{},[29,3577,3578],{},"一部だけ化ける"," (ひらがな・カタカナは出るが特定の漢字だけ □) ならフォントが部分的にしかカバーしていない。「日本語対応」を名乗るフォントでも 鬱・龠・曻 のような JIS X 0213 第 3・第 4 水準の漢字を落としていることがある。Noto Sans JP は JIS X 0213 を網羅しているので切り替えれば直る。",[14,3581,3583],{"id":3582},"原因-2-の詳細-フォントは入っているのに使われない","原因 2 の詳細: フォントは入っているのに使われない",[18,3585,3586,3587,3590],{},"やっかいなのはこれ。フォント",[29,3588,3589],{},"自体","は PDF に埋め込まれているのに、テキスト側で引いていない。最小再現:",[57,3592,3594],{"className":59,"code":3593,"language":61,"meta":62,"style":62},"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",[45,3595,3596,3610,3632,3637,3641,3645,3669,3700,3723,3727],{"__ignoreMap":62},[66,3597,3598,3600,3602,3604,3606,3608],{"class":68,"line":69},[66,3599,1000],{"class":184},[66,3601,194],{"class":72},[66,3603,272],{"class":184},[66,3605,200],{"class":72},[66,3607,277],{"class":171},[66,3609,280],{"class":72},[66,3611,3612,3614,3616,3618,3620,3622,3624,3626,3628,3630],{"class":68,"line":80},[66,3613,1015],{"class":184},[66,3615,200],{"class":72},[66,3617,351],{"class":171},[66,3619,206],{"class":72},[66,3621,209],{"class":72},[66,3623,3249],{"class":212},[66,3625,209],{"class":72},[66,3627,188],{"class":72},[66,3629,365],{"class":184},[66,3631,303],{"class":72},[66,3633,3634],{"class":68,"line":87},[66,3635,3636],{"class":2209},"    // WithDefaultFont を書き忘れている\n",[66,3638,3639],{"class":68,"line":97},[66,3640,157],{"class":72},[66,3642,3643],{"class":68,"line":109},[66,3644,84],{"emptyLinePlaceholder":83},[66,3646,3647,3649,3651,3653,3655,3657,3659,3661,3663,3665,3667],{"class":68,"line":119},[66,3648,1926],{"class":184},[66,3650,200],{"class":72},[66,3652,433],{"class":171},[66,3654,436],{"class":72},[66,3656,440],{"class":439},[66,3658,443],{"class":72},[66,3660,446],{"class":76},[66,3662,200],{"class":72},[66,3664,451],{"class":76},[66,3666,454],{"class":72},[66,3668,178],{"class":72},[66,3670,3671,3674,3676,3678,3680,3682,3684,3686,3688,3690,3692,3694,3696,3698],{"class":68,"line":124},[66,3672,3673],{"class":184},"    r",[66,3675,200],{"class":72},[66,3677,467],{"class":171},[66,3679,206],{"class":72},[66,3681,472],{"class":337},[66,3683,188],{"class":72},[66,3685,477],{"class":72},[66,3687,480],{"class":439},[66,3689,443],{"class":72},[66,3691,446],{"class":76},[66,3693,200],{"class":72},[66,3695,489],{"class":76},[66,3697,454],{"class":72},[66,3699,178],{"class":72},[66,3701,3702,3705,3707,3709,3711,3713,3716,3718,3720],{"class":68,"line":134},[66,3703,3704],{"class":184},"        c",[66,3706,200],{"class":72},[66,3708,504],{"class":171},[66,3710,206],{"class":72},[66,3712,209],{"class":72},[66,3714,3715],{"class":212},"こんにちは",[66,3717,209],{"class":72},[66,3719,454],{"class":72},[66,3721,3722],{"class":2209}," // デフォルト = Helvetica で描画される\n",[66,3724,3725],{"class":68,"line":144},[66,3726,573],{"class":72},[66,3728,3729],{"class":68,"line":154},[66,3730,3731],{"class":72},"})\n",[18,3733,3734,3735,3559,3738,3740,3741,2980,3743,3746,3747,3749,3750,3752,3753,3756,3757,1155,3759,3762],{},"直し方は 2 つ: ",[45,3736,3737],{},"gpdf.WithDefaultFont(\"NotoSansJP\", 12)",[45,3739,277],{}," に足すか、日本語を書く全ての ",[45,3742,2991],{},[45,3744,3745],{},"template.FontFamily(\"NotoSansJP\")"," を渡す。",[45,3748,351],{}," に渡したファミリ名と ",[45,3751,2991],{}," 側のファミリ名は",[29,3754,3755],{},"大文字小文字まで完全一致","が必要。",[45,3758,3249],{},[45,3760,3761],{},"notosansjp"," は別フォント扱いになる。",[14,3764,3766],{"id":3765},"原因-3-の詳細-ttf-ファイルが違う","原因 3 の詳細: TTF ファイルが違う",[18,3768,3769,1155,3771,3773,3774,3777],{},[45,3770,3009],{},[45,3772,3134],{}," は別ファイル。前者は Latin 専用で CJK グリフはゼロ、後者が日本語版 (約 17,000 グリフ)。",[45,3775,3776],{},"ls"," で並べると見分けにくく、補完で間違って掴むことがある。",[18,3779,3780],{},"gpdf は登録時にグリフの網羅を検証しない — バイト列を渡したら信用する設計。失敗はレンダリング時の豆腐で初めて現れる。",[18,3782,3783],{},"確認方法:",[1109,3785,3786,3793,3800],{},[882,3787,3788,3789,3792],{},"macOS: ",[45,3790,3791],{},"Font Book"," でファイルをダブルクリックするとグリフ一覧が見える",[882,3794,3795,3796,3799],{},"Linux: ",[45,3797,3798],{},"otfinfo -u NotoSans-Regular.ttf"," で Unicode カバレッジが出る",[882,3801,3802,3803,897,3808,3811],{},"全 OS: ",[21,3804,3807],{"href":3805,"rel":3806},"https://github.com/fonttools/fonttools",[25],"fontTools",[45,3809,3810],{},"ttx -t cmap NotoSans-Regular.ttf"," で cmap テーブルが XML で出る",[18,3813,3814],{},"U+3042 (あ) が含まれていなければ Latin サブセットを掴んでいる。",[14,3816,3818],{"id":3817},"原因-4-の詳細-文字コードの化け","原因 4 の詳細: 文字コードの化け",[18,3820,3821,3822,3824],{},"これは gpdf 以前の問題。",[45,3823,2991],{}," に渡した時点で文字列が既に壊れている。描画前に print で確認する:",[57,3826,3828],{"className":59,"code":3827,"language":61,"meta":62,"style":62},"text := loadLabelFromSomewhere()\nfmt.Printf(\"%q\\n\", text) // 実際の runes が出る\nc.Text(text)\n",[45,3829,3830,3842,3875],{"__ignoreMap":62},[66,3831,3832,3835,3837,3840],{"class":68,"line":69},[66,3833,3834],{"class":184},"text ",[66,3836,194],{"class":72},[66,3838,3839],{"class":171}," loadLabelFromSomewhere",[66,3841,422],{"class":72},[66,3843,3844,3847,3849,3852,3854,3856,3860,3863,3865,3867,3870,3872],{"class":68,"line":80},[66,3845,3846],{"class":184},"fmt",[66,3848,200],{"class":72},[66,3850,3851],{"class":171},"Printf",[66,3853,206],{"class":72},[66,3855,209],{"class":72},[66,3857,3859],{"class":3858},"swJcz","%q",[66,3861,3862],{"class":184},"\\n",[66,3864,209],{"class":72},[66,3866,188],{"class":72},[66,3868,3869],{"class":184}," text",[66,3871,454],{"class":72},[66,3873,3874],{"class":2209}," // 実際の runes が出る\n",[66,3876,3877,3879,3881,3883,3885,3887],{"class":68,"line":87},[66,3878,480],{"class":184},[66,3880,200],{"class":72},[66,3882,504],{"class":171},[66,3884,206],{"class":72},[66,3886,924],{"class":184},[66,3888,157],{"class":72},[18,3890,3891,3892,3895],{},"ここで ",[45,3893,3894],{},"\"縺ゅ→縺\""," と出たら、もっと前の段階で UTF-8 が壊れている。gpdf では直せない — 文字列を作っている側で直す。",[18,3897,3898],{},"日本の業務系で頻出するパターン:",[1109,3900,3901,3920,3935,3944],{},[882,3902,3903,3559,3906,3908,3909,3912,3913,897,3916,3919],{},[29,3904,3905],{},"Excel から書き出した Shift-JIS CSV",[45,3907,2846],{}," → ",[45,3910,3911],{},"string()"," でそのまま読んでいる。Excel はデフォルトで CP932 (ほぼ Shift-JIS) で保存する。",[45,3914,3915],{},"golang.org/x/text/encoding/japanese",[45,3917,3918],{},"ShiftJIS.NewDecoder()"," を挟む",[882,3921,3922,3925,3926,3551,3928,3930,3931,3934],{},[29,3923,3924],{},"レガシー DB"," のカラムが ",[45,3927,3569],{},[45,3929,3572],{}," のままで、保存時点で既に化けている。カラム定義を ",[45,3932,3933],{},"utf8mb4"," に変更し、既存データはマイグレーションで変換",[882,3936,3937,2980,3940,3943],{},[29,3938,3939],{},"HTTP レスポンス",[45,3941,3942],{},"Content-Type: application/json; charset=utf-8"," が付いておらず、クライアントが Latin-1 推定した",[882,3945,3946,3949],{},[29,3947,3948],{},"EUC-JP の古いシステム"," からデータを引っ張ってきたケース。滅多に見ないが、官公庁系の CSV では今も現役",[14,3951,3953],{"id":3952},"_1-つ見落としやすい罠","1 つ見落としやすい罠",[18,3955,3956,3957,3960,3961,3963,3964,3967,3968,3971,3972,3974],{},"gpdf のサブセット化は ",[45,3958,3959],{},"Generate()"," の瞬間に確定する。つまりドキュメント構築中に ",[45,3962,3715],{}," を描画し、その後 ",[45,3965,3966],{},"鬱陶しい"," を描画したら、2 回目もちゃんとサブセットに追加される。",[29,3969,3970],{},"ただし、生成済みの PDF を Acrobat で後から開いて漢字をタイプし直す","とそのグリフはサブセットに入っていないので豆腐になる。PDF を後編集するのではなく、Go 側でもう一度 ",[45,3973,3959],{}," する。",[14,3976,1208],{"id":1208},[1109,3978,3979,3987,3996],{},[882,3980,3981,3983,3984,3986],{},[21,3982,1222],{"href":1221}," — ",[45,3985,351],{}," のフルリファレンス、bold / italic バリアント、多言語 CJK の扱い",[882,3988,3989,3991,3992,3995],{},[21,3990,1215],{"href":831}," — Noto のどのファイルを選ぶべきかと ",[45,3993,3994],{},"go:embed"," でバイナリに焼き付ける方法",[882,3997,3998,4002],{},[21,3999,4001],{"href":4000},"/ja/blog/japanese-pdf-in-go","Go で日本語 PDF を作る決定版ガイド (2026)"," — フォント・縦書き・ルビ・日本特有のレイアウトまで一気通貫",[14,4004,1241],{"id":1240},[18,4006,1244],{},[57,4008,4009],{"className":1247,"code":1248,"language":1249,"meta":62,"style":62},[45,4010,4011],{"__ignoreMap":62},[66,4012,4013,4015,4017],{"class":68,"line":69},[66,4014,61],{"class":76},[66,4016,1258],{"class":212},[66,4018,1261],{"class":212},[18,4020,4021,1268,4024],{},[21,4022,1267],{"href":23,"rel":4023},[25],[21,4025,1273],{"href":1271,"rel":4026},[25],[1275,4028,4029],{},"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":62,"searchDepth":80,"depth":80,"links":4031},[4032,4033,4034,4035,4036,4037,4038,4039,4040,4041],{"id":16,"depth":80,"text":16},{"id":35,"depth":80,"text":35},{"id":3027,"depth":80,"text":3028},{"id":3524,"depth":80,"text":3524},{"id":3582,"depth":80,"text":3583},{"id":3765,"depth":80,"text":3766},{"id":3817,"depth":80,"text":3818},{"id":3952,"depth":80,"text":3953},{"id":1208,"depth":80,"text":1208},{"id":1240,"depth":80,"text":1241},"2026-04-17","PDF 出力で日本語が □ になるのはフォント未登録が最多。よくある 4 原因と直し方を最短で整理する。",{"name":4045,"totalTime":1296,"tools":4046,"steps":4048},"gpdf の豆腐文字を切り分けて直す",[1298,4047],"CJK 対応 TTF (NotoSansJP-Regular.ttf など)",[4049,4052,4055,4058,4061],{"name":4050,"text":4051},"症状が豆腐か文字化けかを見分ける","PDF を開き、日本語が □ (矩形) で出ているならフォント探索の失敗。縺ゅ→縺 のような化け方なら gpdf に渡す前に UTF-8 が壊れている。別の問題なので切り分けが先。",{"name":4053,"text":4054},"CJK フォントが登録されているか確認する","gpdf.NewDocument の周りを grep して gpdf.WithFont がないなら原因はこれ。WithFont がないと標準 14 フォントにフォールバックし、CJK コードポイントは全て豆腐になる。",{"name":4056,"text":4057},"c.Text のファミリ名を突合する","WithDefaultFont がないなら、日本語を描画する c.Text 全てに template.FontFamily(\"NotoSansJP\") を明示する。名前がずれていると静かにデフォルトに戻る。",{"name":4059,"text":4060},"TTF ファイル自体に CJK グリフが入っているか検証する","NotoSans-Regular.ttf (Latin サブセット) と NotoSansJP-Regular.ttf は別物。gpdf は登録時にグリフ網羅を検証しないので、描画時に初めて豆腐が出る。",{"name":4062,"text":4063},"2 つのビューアで再確認する","生成した PDF を Adobe Acrobat と Chrome の両方で開く。両方で正しく出れば OK。片方だけ出るならサブセットと埋め込みの整合で追加調査が要る。",{},{"title":2953,"description":4043},"ja/blog/008.tofu-boxes-japanese",[1322,4068,1323],"troubleshooting","wZYb4vdbHHSvST5tikp7NlRthvjtxxdX_Qk4ri1dJTk",{"id":4071,"title":1229,"author":4072,"body":4073,"date":4042,"description":5288,"draft":1292,"extension":1293,"howTo":5289,"image":1316,"meta":5310,"navigation":83,"path":1228,"seo":5311,"stem":5312,"tags":5313,"updated":1316,"__hash__":5314},"blogJa/ja/blog/009.ipaex-gothic-gpdf.md",{"name":8,"url":9},{"type":11,"value":4074,"toc":5277},[4075,4077,4087,4089,4101,4103,4632,4655,4659,4662,4717,4727,4739,4743,4746,4749,4757,4789,4795,5078,5081,5085,5094,5120,5127,5130,5134,5217,5223,5225,5251,5253,5255,5267,5275],[14,4076,16],{"id":16},[18,4078,4079,4082,4083,4086],{},[21,4080,26],{"href":23,"rel":4081},[25]," のドキュメントで、IPA (情報処理推進機構) が配布している ",[29,4084,4085],{},"IPAex ゴシック"," を使いたい。用途はだいたい決まっていて、e-Tax の PDF 添付、官公庁提出書類、あるいは 2010 年前後から IPAex に統一してきたハウススタイルの踏襲。詰まるのはいつも 3 点 — どのファイルを拾うか、Bold がない問題、そして IPA フォントライセンス v1.0 の条件。",[14,4088,35],{"id":35},[18,4090,4091,3559,4094,4097,4098,4100],{},[45,4092,4093],{},"ipaexg.ttf",[45,4095,4096],{},"gpdf.WithFont(\"IPAexGothic\", bytes)"," で登録し、デフォルトに設定する。Bold は ",[45,4099,1093],{}," での合成か、IPAex 明朝をペア登録して明朝で強調する古典的な方法で対処。バイナリ配布時はライセンス全文を同梱する。",[14,4102,55],{"id":55},[57,4104,4106],{"className":59,"code":4105,"language":61,"meta":62,"style":62},"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",[45,4107,4108,4114,4118,4124,4132,4140,4144,4152,4160,4168,4172,4176,4186,4212,4224,4238,4242,4246,4260,4278,4309,4332,4355,4359,4363,4377,4401,4431,4470,4489,4508,4512,4516,4520,4538,4550,4564,4568,4609,4623,4627],{"__ignoreMap":62},[66,4109,4110,4112],{"class":68,"line":69},[66,4111,73],{"class":72},[66,4113,77],{"class":76},[66,4115,4116],{"class":68,"line":80},[66,4117,84],{"emptyLinePlaceholder":83},[66,4119,4120,4122],{"class":68,"line":87},[66,4121,91],{"class":90},[66,4123,94],{"class":72},[66,4125,4126,4128,4130],{"class":68,"line":97},[66,4127,100],{"class":72},[66,4129,103],{"class":76},[66,4131,106],{"class":72},[66,4133,4134,4136,4138],{"class":68,"line":109},[66,4135,100],{"class":72},[66,4137,114],{"class":76},[66,4139,106],{"class":72},[66,4141,4142],{"class":68,"line":119},[66,4143,84],{"emptyLinePlaceholder":83},[66,4145,4146,4148,4150],{"class":68,"line":124},[66,4147,100],{"class":72},[66,4149,129],{"class":76},[66,4151,106],{"class":72},[66,4153,4154,4156,4158],{"class":68,"line":134},[66,4155,100],{"class":72},[66,4157,139],{"class":76},[66,4159,106],{"class":72},[66,4161,4162,4164,4166],{"class":68,"line":144},[66,4163,100],{"class":72},[66,4165,149],{"class":76},[66,4167,106],{"class":72},[66,4169,4170],{"class":68,"line":154},[66,4171,157],{"class":72},[66,4173,4174],{"class":68,"line":160},[66,4175,84],{"emptyLinePlaceholder":83},[66,4177,4178,4180,4182,4184],{"class":68,"line":165},[66,4179,168],{"class":72},[66,4181,172],{"class":171},[66,4183,175],{"class":72},[66,4185,178],{"class":72},[66,4187,4188,4190,4192,4194,4196,4198,4200,4202,4204,4206,4208,4210],{"class":68,"line":181},[66,4189,185],{"class":184},[66,4191,188],{"class":72},[66,4193,191],{"class":184},[66,4195,194],{"class":72},[66,4197,197],{"class":184},[66,4199,200],{"class":72},[66,4201,203],{"class":171},[66,4203,206],{"class":72},[66,4205,209],{"class":72},[66,4207,4093],{"class":212},[66,4209,209],{"class":72},[66,4211,157],{"class":72},[66,4213,4214,4216,4218,4220,4222],{"class":68,"line":219},[66,4215,222],{"class":90},[66,4217,191],{"class":184},[66,4219,227],{"class":72},[66,4221,230],{"class":72},[66,4223,178],{"class":72},[66,4225,4226,4228,4230,4232,4234,4236],{"class":68,"line":235},[66,4227,238],{"class":184},[66,4229,200],{"class":72},[66,4231,243],{"class":171},[66,4233,206],{"class":72},[66,4235,248],{"class":184},[66,4237,157],{"class":72},[66,4239,4240],{"class":68,"line":253},[66,4241,256],{"class":72},[66,4243,4244],{"class":68,"line":259},[66,4245,84],{"emptyLinePlaceholder":83},[66,4247,4248,4250,4252,4254,4256,4258],{"class":68,"line":264},[66,4249,267],{"class":184},[66,4251,194],{"class":72},[66,4253,272],{"class":184},[66,4255,200],{"class":72},[66,4257,277],{"class":171},[66,4259,280],{"class":72},[66,4261,4262,4264,4266,4268,4270,4272,4274,4276],{"class":68,"line":283},[66,4263,286],{"class":184},[66,4265,200],{"class":72},[66,4267,291],{"class":171},[66,4269,206],{"class":72},[66,4271,26],{"class":184},[66,4273,200],{"class":72},[66,4275,300],{"class":184},[66,4277,303],{"class":72},[66,4279,4280,4282,4284,4286,4288,4290,4292,4294,4296,4298,4300,4302,4304,4307],{"class":68,"line":306},[66,4281,286],{"class":184},[66,4283,200],{"class":72},[66,4285,313],{"class":171},[66,4287,206],{"class":72},[66,4289,318],{"class":184},[66,4291,200],{"class":72},[66,4293,323],{"class":171},[66,4295,206],{"class":72},[66,4297,318],{"class":184},[66,4299,200],{"class":72},[66,4301,332],{"class":171},[66,4303,206],{"class":72},[66,4305,4306],{"class":337},"25",[66,4308,341],{"class":72},[66,4310,4311,4313,4315,4317,4319,4321,4324,4326,4328,4330],{"class":68,"line":344},[66,4312,286],{"class":184},[66,4314,200],{"class":72},[66,4316,351],{"class":171},[66,4318,206],{"class":72},[66,4320,209],{"class":72},[66,4322,4323],{"class":212},"IPAexGothic",[66,4325,209],{"class":72},[66,4327,188],{"class":72},[66,4329,365],{"class":184},[66,4331,303],{"class":72},[66,4333,4334,4336,4338,4340,4342,4344,4346,4348,4350,4353],{"class":68,"line":370},[66,4335,286],{"class":184},[66,4337,200],{"class":72},[66,4339,377],{"class":171},[66,4341,206],{"class":72},[66,4343,209],{"class":72},[66,4345,4323],{"class":212},[66,4347,209],{"class":72},[66,4349,188],{"class":72},[66,4351,4352],{"class":337}," 10.5",[66,4354,303],{"class":72},[66,4356,4357],{"class":68,"line":395},[66,4358,398],{"class":72},[66,4360,4361],{"class":68,"line":401},[66,4362,84],{"emptyLinePlaceholder":83},[66,4364,4365,4367,4369,4371,4373,4375],{"class":68,"line":406},[66,4366,409],{"class":184},[66,4368,194],{"class":72},[66,4370,414],{"class":184},[66,4372,200],{"class":72},[66,4374,419],{"class":171},[66,4376,422],{"class":72},[66,4378,4379,4381,4383,4385,4387,4389,4391,4393,4395,4397,4399],{"class":68,"line":425},[66,4380,428],{"class":184},[66,4382,200],{"class":72},[66,4384,433],{"class":171},[66,4386,436],{"class":72},[66,4388,440],{"class":439},[66,4390,443],{"class":72},[66,4392,446],{"class":76},[66,4394,200],{"class":72},[66,4396,451],{"class":76},[66,4398,454],{"class":72},[66,4400,178],{"class":72},[66,4402,4403,4405,4407,4409,4411,4413,4415,4417,4419,4421,4423,4425,4427,4429],{"class":68,"line":459},[66,4404,462],{"class":184},[66,4406,200],{"class":72},[66,4408,467],{"class":171},[66,4410,206],{"class":72},[66,4412,472],{"class":337},[66,4414,188],{"class":72},[66,4416,477],{"class":72},[66,4418,480],{"class":439},[66,4420,443],{"class":72},[66,4422,446],{"class":76},[66,4424,200],{"class":72},[66,4426,489],{"class":76},[66,4428,454],{"class":72},[66,4430,178],{"class":72},[66,4432,4433,4435,4437,4439,4441,4443,4446,4448,4450,4452,4454,4456,4458,4460,4462,4464,4466,4468],{"class":68,"line":496},[66,4434,499],{"class":184},[66,4436,200],{"class":72},[66,4438,504],{"class":171},[66,4440,206],{"class":72},[66,4442,209],{"class":72},[66,4444,4445],{"class":212},"請求書",[66,4447,209],{"class":72},[66,4449,188],{"class":72},[66,4451,518],{"class":184},[66,4453,200],{"class":72},[66,4455,523],{"class":171},[66,4457,206],{"class":72},[66,4459,528],{"class":337},[66,4461,531],{"class":72},[66,4463,518],{"class":184},[66,4465,200],{"class":72},[66,4467,538],{"class":171},[66,4469,541],{"class":72},[66,4471,4472,4474,4476,4478,4480,4482,4485,4487],{"class":68,"line":544},[66,4473,499],{"class":184},[66,4475,200],{"class":72},[66,4477,504],{"class":171},[66,4479,206],{"class":72},[66,4481,209],{"class":72},[66,4483,4484],{"class":212},"令和8年4月17日発行",[66,4486,209],{"class":72},[66,4488,157],{"class":72},[66,4490,4491,4493,4495,4497,4499,4501,4504,4506],{"class":68,"line":564},[66,4492,499],{"class":184},[66,4494,200],{"class":72},[66,4496,504],{"class":171},[66,4498,206],{"class":72},[66,4500,209],{"class":72},[66,4502,4503],{"class":212},"金額: ¥100,000 (税込)",[66,4505,209],{"class":72},[66,4507,157],{"class":72},[66,4509,4510],{"class":68,"line":570},[66,4511,567],{"class":72},[66,4513,4514],{"class":68,"line":576},[66,4515,573],{"class":72},[66,4517,4518],{"class":68,"line":581},[66,4519,84],{"emptyLinePlaceholder":83},[66,4521,4522,4524,4526,4528,4530,4532,4534,4536],{"class":68,"line":602},[66,4523,584],{"class":184},[66,4525,188],{"class":72},[66,4527,191],{"class":184},[66,4529,194],{"class":72},[66,4531,414],{"class":184},[66,4533,200],{"class":72},[66,4535,597],{"class":171},[66,4537,422],{"class":72},[66,4539,4540,4542,4544,4546,4548],{"class":68,"line":615},[66,4541,222],{"class":90},[66,4543,191],{"class":184},[66,4545,227],{"class":72},[66,4547,230],{"class":72},[66,4549,178],{"class":72},[66,4551,4552,4554,4556,4558,4560,4562],{"class":68,"line":630},[66,4553,238],{"class":184},[66,4555,200],{"class":72},[66,4557,243],{"class":171},[66,4559,206],{"class":72},[66,4561,248],{"class":184},[66,4563,157],{"class":72},[66,4565,4566],{"class":68,"line":635},[66,4567,256],{"class":72},[66,4569,4570,4572,4574,4576,4578,4580,4582,4584,4586,4589,4591,4593,4595,4597,4599,4601,4603,4605,4607],{"class":68,"line":681},[66,4571,222],{"class":90},[66,4573,191],{"class":184},[66,4575,194],{"class":72},[66,4577,197],{"class":184},[66,4579,200],{"class":72},[66,4581,648],{"class":171},[66,4583,206],{"class":72},[66,4585,209],{"class":72},[66,4587,4588],{"class":212},"invoice.pdf",[66,4590,209],{"class":72},[66,4592,188],{"class":72},[66,4594,662],{"class":184},[66,4596,188],{"class":72},[66,4598,667],{"class":337},[66,4600,670],{"class":72},[66,4602,191],{"class":184},[66,4604,227],{"class":72},[66,4606,230],{"class":72},[66,4608,178],{"class":72},[66,4610,4611,4613,4615,4617,4619,4621],{"class":68,"line":696},[66,4612,238],{"class":184},[66,4614,200],{"class":72},[66,4616,243],{"class":171},[66,4618,206],{"class":72},[66,4620,248],{"class":184},[66,4622,157],{"class":72},[66,4624,4625],{"class":68,"line":701},[66,4626,256],{"class":72},[66,4628,4630],{"class":68,"line":4629},41,[66,4631,704],{"class":72},[18,4633,4634,4639,4640,4643,4644,3559,4646,4648,4649,1149,4651,4654],{},[21,4635,4638],{"href":4636,"rel":4637},"https://moji.or.jp/ipafont/",[25],"moji.or.jp/ipafont"," から ",[45,4641,4642],{},"IPAex00401.zip"," を落として ",[45,4645,4093],{},[45,4647,710],{}," の隣に置き、",[45,4650,714],{},[45,4652,4653],{},"10.5 pt"," は Word 互換で違和感のない標準サイズ、25 mm マージンは日本の A4 ビジネス文書の慣習。",[14,4656,4658],{"id":4657},"ipa-ipaex-のどのファイルを使うか","IPA / IPAex のどのファイルを使うか",[18,4660,4661],{},"zip を開くと TTF が 3 つとライセンス本文が入っている。名前がそっくりで間違えやすい:",[738,4663,4664,4674],{},[741,4665,4666],{},[744,4667,4668,4671],{},[747,4669,4670],{},"ファイル",[747,4672,4673],{},"中身",[756,4675,4676,4687,4700],{},[744,4677,4678,4682],{},[761,4679,4680],{},[45,4681,4093],{},[761,4683,4684,4686],{},[29,4685,4085],{}," — サンセリフ、Latin はプロポーショナル。一般的な書類はこれ。",[744,4688,4689,4694],{},[761,4690,4691],{},[45,4692,4693],{},"ipaexm.ttf",[761,4695,4696,4699],{},[29,4697,4698],{},"IPAex 明朝"," — セリフ、Latin はプロポーショナル。長文本文やゴシックと組んでの強調用。",[744,4701,4702,4707],{},[761,4703,4704],{},[45,4705,4706],{},"ipag.ttf",[761,4708,4709,4712,4713,4716],{},[29,4710,4711],{},"IPA ゴシック"," (ex なし) — サンセリフ、",[29,4714,4715],{},"Latin が等幅","。今はあまり使わない。",[18,4718,4719,4720,4722,4723,4726],{},"\"ex\" は extended proportional の略で、Latin を CJK 全角の格子から外してプロポーショナルにした版。旧 IPA ゴシック (",[45,4721,4706],{},") は英数字まで全角幅で並ぶため、和英混在文書では間延びして見える。URL や金額や商品コードが必ず入る業務文書なら、ほぼ間違いなく ",[29,4724,4725],{},"IPAex"," (ipaexg/ipaexm) を選ぶ。",[18,4728,4729,4730,4732,4733,4735,4736,4738],{},"既存プロジェクトで ",[45,4731,4706],{}," を使っているなら、理由はだいたい歴史的経緯 (IPA ゴシック初版が 2003 年、IPAex は 2010 年リリース)。ファミリ名を ",[45,4734,4323],{}," に揃えて ",[45,4737,4093],{}," に差し替えれば、コード側は 1 行変更で済む。",[14,4740,4742],{"id":4741},"bold-がない問題への対処","Bold がない問題への対処",[18,4744,4745],{},"IPAex は Regular のみ 1 ウェイト。Noto Sans JP が Thin から Black まで 9 ウェイト出しているのと比較すると見劣りするポイントで、IPAex を採用検討して断念する最大の理由はこれ。",[18,4747,4748],{},"gpdf での対処は 2 通り。",[18,4750,4751,893,4754,4756],{},[29,4752,4753],{},"合成ボールド。",[45,4755,1093],{}," が Regular のグリフにストロークを重ねて太く見せる。タイポグラフィ的には「本当のボールド」(専用デザインされた太字アウトライン) とは別物だが、10 pt 以上の見出し・表ラベル用途なら肉眼ではほぼ見分けがつかない:",[57,4758,4760],{"className":59,"code":4759,"language":61,"meta":62,"style":62},"c.Text(\"合計金額\", template.Bold())\n",[45,4761,4762],{"__ignoreMap":62},[66,4763,4764,4766,4768,4770,4772,4774,4777,4779,4781,4783,4785,4787],{"class":68,"line":69},[66,4765,480],{"class":184},[66,4767,200],{"class":72},[66,4769,504],{"class":171},[66,4771,206],{"class":72},[66,4773,209],{"class":72},[66,4775,4776],{"class":212},"合計金額",[66,4778,209],{"class":72},[66,4780,188],{"class":72},[66,4782,518],{"class":184},[66,4784,200],{"class":72},[66,4786,538],{"class":171},[66,4788,541],{"class":72},[18,4790,4791,4794],{},[29,4792,4793],{},"明朝ペアで強調する。"," 日本語組版の古典的な強調手法は「太字にする」ではなく「明朝とゴシックを切り替える」。両ファミリを登録して場面で使い分ける:",[57,4796,4798],{"className":59,"code":4797,"language":61,"meta":62,"style":62},"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",[45,4799,4800,4827,4854,4858,4872,4895,4919,4941,4945,4949,4973,5003,5051,5070,5074],{"__ignoreMap":62},[66,4801,4802,4805,4807,4809,4811,4813,4815,4817,4819,4821,4823,4825],{"class":68,"line":69},[66,4803,4804],{"class":184},"gothic",[66,4806,188],{"class":72},[66,4808,972],{"class":184},[66,4810,194],{"class":72},[66,4812,197],{"class":184},[66,4814,200],{"class":72},[66,4816,203],{"class":171},[66,4818,206],{"class":72},[66,4820,209],{"class":72},[66,4822,4093],{"class":212},[66,4824,209],{"class":72},[66,4826,157],{"class":72},[66,4828,4829,4832,4834,4836,4838,4840,4842,4844,4846,4848,4850,4852],{"class":68,"line":80},[66,4830,4831],{"class":184},"mincho",[66,4833,188],{"class":72},[66,4835,972],{"class":184},[66,4837,194],{"class":72},[66,4839,197],{"class":184},[66,4841,200],{"class":72},[66,4843,203],{"class":171},[66,4845,206],{"class":72},[66,4847,209],{"class":72},[66,4849,4693],{"class":212},[66,4851,209],{"class":72},[66,4853,157],{"class":72},[66,4855,4856],{"class":68,"line":87},[66,4857,84],{"emptyLinePlaceholder":83},[66,4859,4860,4862,4864,4866,4868,4870],{"class":68,"line":97},[66,4861,1000],{"class":184},[66,4863,194],{"class":72},[66,4865,272],{"class":184},[66,4867,200],{"class":72},[66,4869,277],{"class":171},[66,4871,280],{"class":72},[66,4873,4874,4876,4878,4880,4882,4884,4886,4888,4890,4893],{"class":68,"line":109},[66,4875,1015],{"class":184},[66,4877,200],{"class":72},[66,4879,351],{"class":171},[66,4881,206],{"class":72},[66,4883,209],{"class":72},[66,4885,4323],{"class":212},[66,4887,209],{"class":72},[66,4889,188],{"class":72},[66,4891,4892],{"class":184}," gothic",[66,4894,303],{"class":72},[66,4896,4897,4899,4901,4903,4905,4907,4910,4912,4914,4917],{"class":68,"line":119},[66,4898,1015],{"class":184},[66,4900,200],{"class":72},[66,4902,351],{"class":171},[66,4904,206],{"class":72},[66,4906,209],{"class":72},[66,4908,4909],{"class":212},"IPAexMincho",[66,4911,209],{"class":72},[66,4913,188],{"class":72},[66,4915,4916],{"class":184}," mincho",[66,4918,303],{"class":72},[66,4920,4921,4923,4925,4927,4929,4931,4933,4935,4937,4939],{"class":68,"line":124},[66,4922,1015],{"class":184},[66,4924,200],{"class":72},[66,4926,377],{"class":171},[66,4928,206],{"class":72},[66,4930,209],{"class":72},[66,4932,4323],{"class":212},[66,4934,209],{"class":72},[66,4936,188],{"class":72},[66,4938,4352],{"class":337},[66,4940,303],{"class":72},[66,4942,4943],{"class":68,"line":134},[66,4944,157],{"class":72},[66,4946,4947],{"class":68,"line":144},[66,4948,84],{"emptyLinePlaceholder":83},[66,4950,4951,4953,4955,4957,4959,4961,4963,4965,4967,4969,4971],{"class":68,"line":154},[66,4952,1926],{"class":184},[66,4954,200],{"class":72},[66,4956,433],{"class":171},[66,4958,436],{"class":72},[66,4960,440],{"class":439},[66,4962,443],{"class":72},[66,4964,446],{"class":76},[66,4966,200],{"class":72},[66,4968,451],{"class":76},[66,4970,454],{"class":72},[66,4972,178],{"class":72},[66,4974,4975,4977,4979,4981,4983,4985,4987,4989,4991,4993,4995,4997,4999,5001],{"class":68,"line":160},[66,4976,3673],{"class":184},[66,4978,200],{"class":72},[66,4980,467],{"class":171},[66,4982,206],{"class":72},[66,4984,472],{"class":337},[66,4986,188],{"class":72},[66,4988,477],{"class":72},[66,4990,480],{"class":439},[66,4992,443],{"class":72},[66,4994,446],{"class":76},[66,4996,200],{"class":72},[66,4998,489],{"class":76},[66,5000,454],{"class":72},[66,5002,178],{"class":72},[66,5004,5005,5007,5009,5011,5013,5015,5017,5019,5021,5023,5025,5028,5030,5032,5034,5036,5038,5040,5042,5044,5046,5048],{"class":68,"line":165},[66,5006,3704],{"class":184},[66,5008,200],{"class":72},[66,5010,504],{"class":171},[66,5012,206],{"class":72},[66,5014,209],{"class":72},[66,5016,4445],{"class":212},[66,5018,209],{"class":72},[66,5020,188],{"class":72},[66,5022,518],{"class":184},[66,5024,200],{"class":72},[66,5026,5027],{"class":171},"FontFamily",[66,5029,206],{"class":72},[66,5031,209],{"class":72},[66,5033,4909],{"class":212},[66,5035,209],{"class":72},[66,5037,531],{"class":72},[66,5039,518],{"class":184},[66,5041,200],{"class":72},[66,5043,523],{"class":171},[66,5045,206],{"class":72},[66,5047,528],{"class":337},[66,5049,5050],{"class":72},"))\n",[66,5052,5053,5055,5057,5059,5061,5063,5066,5068],{"class":68,"line":181},[66,5054,3704],{"class":184},[66,5056,200],{"class":72},[66,5058,504],{"class":171},[66,5060,206],{"class":72},[66,5062,209],{"class":72},[66,5064,5065],{"class":212},"ご請求内容は下記の通りです。",[66,5067,209],{"class":72},[66,5069,157],{"class":72},[66,5071,5072],{"class":68,"line":219},[66,5073,573],{"class":72},[66,5075,5076],{"class":68,"line":235},[66,5077,3731],{"class":72},[18,5079,5080],{},"招待状や式典用の案内状、それから官公庁向けの公式文書でよく見る組み合わせ。見出しを明朝にし、本文をゴシックにするとフォーマル寄りの紙面になる。e-Tax 添付書類や税務署提出用の帳票なら、この組み合わせのほうが違和感なく収まる。",[14,5082,5084],{"id":5083},"ipa-フォントライセンスの扱い","IPA フォントライセンスの扱い",[18,5086,5087,5088,5093],{},"IPAex は SIL OFL ではなく ",[21,5089,5092],{"href":5090,"rel":5091},"https://www.ipa.go.jp/security/fonts/license.html",[25],"IPA フォントライセンス v1.0"," (OSI 承認済み)。基本は寛容だが、2 点だけ確認しておく:",[879,5095,5096,5110],{},[882,5097,5098,5101,5102,5105,5106,5109],{},[29,5099,5100],{},"ライセンス本文の同梱が必要。"," フォントバイナリを配布する先にライセンス全文が付いている必要がある。",[45,5103,5104],{},"//go:embed"," で TTF を Go バイナリに焼く場合、同時に ",[45,5107,5108],{},"LICENSES/IPA-FONT-1.0.txt"," を置いて NOTICE から参照しておけば問題ない。",[882,5111,5112,5115,5116,5119],{},[29,5113,5114],{},"フォントのリネーム禁止。"," TTF 自体を改変して再配布する場合、\"IPA\" や \"IPAex\" を含まない別名にする必要がある。ただし",[29,5117,5118],{},"サブセット化はこれに該当しない"," — ライセンス第 3 条第 4 項が明示的に、フォントを使って作成した「出力文書」(Derivative Document) にはこの制約が及ばないと書いている。",[18,5121,5122,5123,5126],{},"つまり、gpdf が ",[45,5124,5125],{},"doc.Generate()"," 時にグリフをサブセット化して PDF に埋め込む行為は license 上セーフ。PDF ファイル自体はフォントプログラムの再配布ではなく「フォントを使って作った文書」扱いになる。",[18,5128,5129],{},"補足として、gpdf のコア OSS リポジトリには IPAex を置いていない (golden file テスト用は Noto 系の SIL OFL フォントを使っている)。これは下流のユーザが、自プロジェクトの LICENSE との組み合わせで IPA ライセンスの整合性をわざわざ確認せずに済むようにするため。アプリケーション側で IPAex を使うのは各プロジェクトの判断で問題ない。",[14,5131,5133],{"id":5132},"ipaex-ゴシック-vs-noto-sans-jpどっちを選ぶか","IPAex ゴシック vs Noto Sans JP、どっちを選ぶか",[738,5135,5136,5147],{},[741,5137,5138],{},[744,5139,5140,5143,5145],{},[747,5141,5142],{},"比較軸",[747,5144,4085],{},[747,5146,754],{},[756,5148,5149,5160,5170,5181,5195,5206],{},[744,5150,5151,5154,5157],{},[761,5152,5153],{},"ウェイト数",[761,5155,5156],{},"1 (Regular のみ)",[761,5158,5159],{},"9 (Thin 〜 Black)",[744,5161,5162,5165,5167],{},[761,5163,5164],{},"ライセンス",[761,5166,5092],{},[761,5168,5169],{},"SIL OFL 1.1",[744,5171,5172,5175,5178],{},[761,5173,5174],{},"Latin 扱い",[761,5176,5177],{},"IPAex はプロポーショナル / IPA は等幅",[761,5179,5180],{},"プロポーショナル",[744,5182,5183,5186,5192],{},[761,5184,5185],{},"初期インストール",[761,5187,5188,5189],{},"一部の日本語 Linux ディストロ、TeX Live ",[45,5190,5191],{},"ptex-fonts",[761,5193,5194],{},"Android、ChromeOS",[744,5196,5197,5200,5203],{},[761,5198,5199],{},"よく使われる場面",[761,5201,5202],{},"官公庁・税務・学術提出",[761,5204,5205],{},"商用 Web、国際向け",[744,5207,5208,5211,5214],{},[761,5209,5210],{},"ファイルサイズ",[761,5212,5213],{},"7.5 MB (ゴシック単体)",[761,5215,5216],{},"5 MB (Regular 単体)",[18,5218,5219,5222],{},[29,5220,5221],{},"e-Tax の PDF 添付、裁判所提出書類、学会への論文提出"," — つまり出力が日本の制度的な窓口を通るときは IPAex を選ぶ。採点者・レビュア・OCR ツールが IPA に合わせて調整されている。それ以外は Noto Sans JP で十分。レンダリング結果はどちらも実用上そっくりで、選ぶ基準は見た目ではなく「出力先のエコシステムがどちらに馴染んでいるか」。",[14,5224,1208],{"id":1208},[1109,5226,5227,5232,5237,5241],{},[882,5228,5229,5231],{},[21,5230,1222],{"href":1221}," — CJK TTF 全般に効く一般的なレシピ",[882,5233,5234,5236],{},[21,5235,1215],{"href":831}," — SIL OFL で 9 ウェイト揃う選択肢",[882,5238,5239,1237],{},[21,5240,2953],{"href":1235},[882,5242,5243,3983,5248,5250],{},[21,5244,5247],{"href":5245,"rel":5246},"https://gpdf.dev/ja/docs/guide/fonts",[25],"フォントガイド",[45,5249,351],{}," のフルリファレンス",[14,5252,1241],{"id":1240},[18,5254,1244],{},[57,5256,5257],{"className":1247,"code":1248,"language":1249,"meta":62,"style":62},[45,5258,5259],{"__ignoreMap":62},[66,5260,5261,5263,5265],{"class":68,"line":69},[66,5262,61],{"class":76},[66,5264,1258],{"class":212},[66,5266,1261],{"class":212},[18,5268,5269,1268,5272],{},[21,5270,1267],{"href":23,"rel":5271},[25],[21,5273,1273],{"href":1271,"rel":5274},[25],[1275,5276,1277],{},{"title":62,"searchDepth":80,"depth":80,"links":5278},[5279,5280,5281,5282,5283,5284,5285,5286,5287],{"id":16,"depth":80,"text":16},{"id":35,"depth":80,"text":35},{"id":55,"depth":80,"text":55},{"id":4657,"depth":80,"text":4658},{"id":4741,"depth":80,"text":4742},{"id":5083,"depth":80,"text":5084},{"id":5132,"depth":80,"text":5133},{"id":1208,"depth":80,"text":1208},{"id":1240,"depth":80,"text":1241},"ipaexg.ttf を gpdf.WithFont で登録する。IPAex は Regular 1 ウェイトのみなので、Bold は合成か明朝ペアで対応する。",{"name":5290,"totalTime":5291,"tools":5292,"steps":5294},"gpdf ドキュメントで IPAex ゴシックをデフォルトフォントに設定する","PT10M",[1298,5293],"ipaexg.ttf (moji.or.jp 配布の IPAex ゴシック v4.01)",[5295,5298,5301,5304,5307],{"name":5296,"text":5297},"moji.or.jp から IPAex フォント一式をダウンロードする","moji.or.jp/ipafont から IPAex00401.zip を取得し、解凍後に ipaexg.ttf と同梱の IPA フォントライセンス v1.0 のテキストファイルを残しておく。",{"name":5299,"text":5300},"TTF のバイト列を読み込む","プログラム起動時に os.ReadFile(\"ipaexg.ttf\") で []byte に読み込む。コンテナ配布なら //go:embed で Go バイナリに焼き付けると取り回しが楽。",{"name":5302,"text":5303},"ドキュメント構築時に登録する","gpdf.WithFont(\"IPAexGothic\", fontBytes) と gpdf.WithDefaultFont(\"IPAexGothic\", 10.5) を gpdf.NewDocument に渡す。10.5 pt は Word の日本語既定サイズと同じ。",{"name":5305,"text":5306},"Bold がない問題に対処する","IPAex にはボールド版がない。template.Bold() で合成 (0.4 pt のストローク重ね) するか、別ファミリとして IPAex 明朝を登録して強調用に使い分ける。",{"name":5308,"text":5309},"ライセンステキストを同梱する","IPA フォントライセンス v1.0 はフォントバイナリの配布先にライセンス全文の同梱を求める。//go:embed する場合は同時に LICENSES/IPA-FONT-1.0.txt を置いて NOTICE から参照する。",{},{"title":1229,"description":5288},"ja/blog/009.ipaex-gothic-gpdf",[1322,1323,1324],"xl0gLtrWe__11ibyPs-oyON7YjMJTtF-j4V0t6aCDW8",{"id":5316,"title":5317,"author":5318,"body":5319,"date":5803,"description":6645,"draft":1292,"extension":1293,"howTo":6646,"image":1316,"meta":6665,"navigation":83,"path":6666,"seo":6667,"stem":6668,"tags":6669,"updated":1316,"__hash__":6671},"blogJa/ja/blog/005.12-column-grid.md","gpdf の 12 列グリッドはどう動くのか？",{"name":8,"url":9},{"type":11,"value":5320,"toc":6633},[5321,5324,5335,5338,5352,5355,6299,6304,6308,6314,6317,6336,6350,6354,6357,6371,6385,6389,6395,6401,6555,6558,6564,6575,6578,6581,6583,6605,6607,6610,6622,6630],[14,5322,5323],{"id":5323},"質問の言い換え",[18,5325,5326,5327,5330,5331,5334],{},"gpdf の API を触っていると、列のコンストラクタが数字を受け取る: ",[45,5328,5329],{},"r.Col(4, fn)","、",[45,5332,5333],{},"r.Col(8, fn)","。この数字は何で、合計が 12 にならなかったらどうなり、CSS で見慣れたグリッドと何が違うのか?",[14,5336,5337],{"id":5337},"結論",[18,5339,5340,5343,5344,5347,5348,5351],{},[45,5341,5342],{},"r.Col(span, fn)"," は 1〜12 の整数を取る。その整数は行幅のうち ",[45,5345,5346],{},"span / 12"," の比率を占める。1 未満は 1 に、12 超は 12 にクランプされ、",[29,5349,5350],{},"行ごとの合計を 12 にするかどうかはライブラリ側では強制しない","。グリッドの分割数が 12 で固定されているだけで、残りは行をどう切り分けるかの選択の話。",[14,5353,5354],{"id":5354},"動くサンプル",[57,5356,5358],{"className":59,"code":5357,"language":61,"meta":62,"style":62},"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(\"株式会社アクメ\")\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",[45,5359,5360,5366,5370,5376,5384,5392,5396,5404,5412,5420,5424,5428,5438,5452,5470,5501,5505,5509,5523,5527,5532,5556,5586,5626,5630,5634,5638,5643,5667,5698,5717,5736,5740,5770,5789,5808,5812,5816,5820,5825,5849,5880,5900,5905,5936,5956,5961,5992,6011,6016,6021,6026,6032,6057,6089,6109,6114,6145,6165,6170,6175,6180,6199,6212,6227,6232,6274,6289,6294],{"__ignoreMap":62},[66,5361,5362,5364],{"class":68,"line":69},[66,5363,73],{"class":72},[66,5365,77],{"class":76},[66,5367,5368],{"class":68,"line":80},[66,5369,84],{"emptyLinePlaceholder":83},[66,5371,5372,5374],{"class":68,"line":87},[66,5373,91],{"class":90},[66,5375,94],{"class":72},[66,5377,5378,5380,5382],{"class":68,"line":97},[66,5379,100],{"class":72},[66,5381,103],{"class":76},[66,5383,106],{"class":72},[66,5385,5386,5388,5390],{"class":68,"line":109},[66,5387,100],{"class":72},[66,5389,114],{"class":76},[66,5391,106],{"class":72},[66,5393,5394],{"class":68,"line":119},[66,5395,84],{"emptyLinePlaceholder":83},[66,5397,5398,5400,5402],{"class":68,"line":124},[66,5399,100],{"class":72},[66,5401,129],{"class":76},[66,5403,106],{"class":72},[66,5405,5406,5408,5410],{"class":68,"line":134},[66,5407,100],{"class":72},[66,5409,139],{"class":76},[66,5411,106],{"class":72},[66,5413,5414,5416,5418],{"class":68,"line":144},[66,5415,100],{"class":72},[66,5417,149],{"class":76},[66,5419,106],{"class":72},[66,5421,5422],{"class":68,"line":154},[66,5423,157],{"class":72},[66,5425,5426],{"class":68,"line":160},[66,5427,84],{"emptyLinePlaceholder":83},[66,5429,5430,5432,5434,5436],{"class":68,"line":165},[66,5431,168],{"class":72},[66,5433,172],{"class":171},[66,5435,175],{"class":72},[66,5437,178],{"class":72},[66,5439,5440,5442,5444,5446,5448,5450],{"class":68,"line":181},[66,5441,267],{"class":184},[66,5443,194],{"class":72},[66,5445,272],{"class":184},[66,5447,200],{"class":72},[66,5449,277],{"class":171},[66,5451,280],{"class":72},[66,5453,5454,5456,5458,5460,5462,5464,5466,5468],{"class":68,"line":219},[66,5455,286],{"class":184},[66,5457,200],{"class":72},[66,5459,291],{"class":171},[66,5461,206],{"class":72},[66,5463,318],{"class":184},[66,5465,200],{"class":72},[66,5467,300],{"class":184},[66,5469,303],{"class":72},[66,5471,5472,5474,5476,5478,5480,5482,5484,5486,5488,5490,5492,5494,5496,5499],{"class":68,"line":235},[66,5473,286],{"class":184},[66,5475,200],{"class":72},[66,5477,313],{"class":171},[66,5479,206],{"class":72},[66,5481,318],{"class":184},[66,5483,200],{"class":72},[66,5485,323],{"class":171},[66,5487,206],{"class":72},[66,5489,318],{"class":184},[66,5491,200],{"class":72},[66,5493,332],{"class":171},[66,5495,206],{"class":72},[66,5497,5498],{"class":337},"15",[66,5500,341],{"class":72},[66,5502,5503],{"class":68,"line":253},[66,5504,398],{"class":72},[66,5506,5507],{"class":68,"line":259},[66,5508,84],{"emptyLinePlaceholder":83},[66,5510,5511,5513,5515,5517,5519,5521],{"class":68,"line":264},[66,5512,409],{"class":184},[66,5514,194],{"class":72},[66,5516,414],{"class":184},[66,5518,200],{"class":72},[66,5520,419],{"class":171},[66,5522,422],{"class":72},[66,5524,5525],{"class":68,"line":283},[66,5526,84],{"emptyLinePlaceholder":83},[66,5528,5529],{"class":68,"line":306},[66,5530,5531],{"class":2209},"    // 全幅\n",[66,5533,5534,5536,5538,5540,5542,5544,5546,5548,5550,5552,5554],{"class":68,"line":344},[66,5535,428],{"class":184},[66,5537,200],{"class":72},[66,5539,433],{"class":171},[66,5541,436],{"class":72},[66,5543,440],{"class":439},[66,5545,443],{"class":72},[66,5547,446],{"class":76},[66,5549,200],{"class":72},[66,5551,451],{"class":76},[66,5553,454],{"class":72},[66,5555,178],{"class":72},[66,5557,5558,5560,5562,5564,5566,5568,5570,5572,5574,5576,5578,5580,5582,5584],{"class":68,"line":370},[66,5559,462],{"class":184},[66,5561,200],{"class":72},[66,5563,467],{"class":171},[66,5565,206],{"class":72},[66,5567,472],{"class":337},[66,5569,188],{"class":72},[66,5571,477],{"class":72},[66,5573,480],{"class":439},[66,5575,443],{"class":72},[66,5577,446],{"class":76},[66,5579,200],{"class":72},[66,5581,489],{"class":76},[66,5583,454],{"class":72},[66,5585,178],{"class":72},[66,5587,5588,5590,5592,5594,5596,5598,5601,5603,5605,5607,5609,5611,5613,5616,5618,5620,5622,5624],{"class":68,"line":395},[66,5589,499],{"class":184},[66,5591,200],{"class":72},[66,5593,504],{"class":171},[66,5595,206],{"class":72},[66,5597,209],{"class":72},[66,5599,5600],{"class":212},"請求書 #2026-0416",[66,5602,209],{"class":72},[66,5604,188],{"class":72},[66,5606,518],{"class":184},[66,5608,200],{"class":72},[66,5610,523],{"class":171},[66,5612,206],{"class":72},[66,5614,5615],{"class":337},"18",[66,5617,531],{"class":72},[66,5619,518],{"class":184},[66,5621,200],{"class":72},[66,5623,538],{"class":171},[66,5625,541],{"class":72},[66,5627,5628],{"class":68,"line":401},[66,5629,567],{"class":72},[66,5631,5632],{"class":68,"line":406},[66,5633,573],{"class":72},[66,5635,5636],{"class":68,"line":425},[66,5637,84],{"emptyLinePlaceholder":83},[66,5639,5640],{"class":68,"line":459},[66,5641,5642],{"class":2209},"    // 2 列ヘッダー (6 + 6)\n",[66,5644,5645,5647,5649,5651,5653,5655,5657,5659,5661,5663,5665],{"class":68,"line":496},[66,5646,428],{"class":184},[66,5648,200],{"class":72},[66,5650,433],{"class":171},[66,5652,436],{"class":72},[66,5654,440],{"class":439},[66,5656,443],{"class":72},[66,5658,446],{"class":76},[66,5660,200],{"class":72},[66,5662,451],{"class":76},[66,5664,454],{"class":72},[66,5666,178],{"class":72},[66,5668,5669,5671,5673,5675,5677,5680,5682,5684,5686,5688,5690,5692,5694,5696],{"class":68,"line":544},[66,5670,462],{"class":184},[66,5672,200],{"class":72},[66,5674,467],{"class":171},[66,5676,206],{"class":72},[66,5678,5679],{"class":337},"6",[66,5681,188],{"class":72},[66,5683,477],{"class":72},[66,5685,480],{"class":439},[66,5687,443],{"class":72},[66,5689,446],{"class":76},[66,5691,200],{"class":72},[66,5693,489],{"class":76},[66,5695,454],{"class":72},[66,5697,178],{"class":72},[66,5699,5700,5702,5704,5706,5708,5710,5713,5715],{"class":68,"line":564},[66,5701,499],{"class":184},[66,5703,200],{"class":72},[66,5705,504],{"class":171},[66,5707,206],{"class":72},[66,5709,209],{"class":72},[66,5711,5712],{"class":212},"請求先",[66,5714,209],{"class":72},[66,5716,157],{"class":72},[66,5718,5719,5721,5723,5725,5727,5729,5732,5734],{"class":68,"line":570},[66,5720,499],{"class":184},[66,5722,200],{"class":72},[66,5724,504],{"class":171},[66,5726,206],{"class":72},[66,5728,209],{"class":72},[66,5730,5731],{"class":212},"株式会社アクメ",[66,5733,209],{"class":72},[66,5735,157],{"class":72},[66,5737,5738],{"class":68,"line":576},[66,5739,567],{"class":72},[66,5741,5742,5744,5746,5748,5750,5752,5754,5756,5758,5760,5762,5764,5766,5768],{"class":68,"line":581},[66,5743,462],{"class":184},[66,5745,200],{"class":72},[66,5747,467],{"class":171},[66,5749,206],{"class":72},[66,5751,5679],{"class":337},[66,5753,188],{"class":72},[66,5755,477],{"class":72},[66,5757,480],{"class":439},[66,5759,443],{"class":72},[66,5761,446],{"class":76},[66,5763,200],{"class":72},[66,5765,489],{"class":76},[66,5767,454],{"class":72},[66,5769,178],{"class":72},[66,5771,5772,5774,5776,5778,5780,5782,5785,5787],{"class":68,"line":602},[66,5773,499],{"class":184},[66,5775,200],{"class":72},[66,5777,504],{"class":171},[66,5779,206],{"class":72},[66,5781,209],{"class":72},[66,5783,5784],{"class":212},"発行日",[66,5786,209],{"class":72},[66,5788,157],{"class":72},[66,5790,5791,5793,5795,5797,5799,5801,5804,5806],{"class":68,"line":615},[66,5792,499],{"class":184},[66,5794,200],{"class":72},[66,5796,504],{"class":171},[66,5798,206],{"class":72},[66,5800,209],{"class":72},[66,5802,5803],{"class":212},"2026-04-16",[66,5805,209],{"class":72},[66,5807,157],{"class":72},[66,5809,5810],{"class":68,"line":630},[66,5811,567],{"class":72},[66,5813,5814],{"class":68,"line":635},[66,5815,573],{"class":72},[66,5817,5818],{"class":68,"line":681},[66,5819,84],{"emptyLinePlaceholder":83},[66,5821,5822],{"class":68,"line":696},[66,5823,5824],{"class":2209},"    // 3 列サマリー (4 + 4 + 4)\n",[66,5826,5827,5829,5831,5833,5835,5837,5839,5841,5843,5845,5847],{"class":68,"line":701},[66,5828,428],{"class":184},[66,5830,200],{"class":72},[66,5832,433],{"class":171},[66,5834,436],{"class":72},[66,5836,440],{"class":439},[66,5838,443],{"class":72},[66,5840,446],{"class":76},[66,5842,200],{"class":72},[66,5844,451],{"class":76},[66,5846,454],{"class":72},[66,5848,178],{"class":72},[66,5850,5851,5853,5855,5857,5859,5862,5864,5866,5868,5870,5872,5874,5876,5878],{"class":68,"line":4629},[66,5852,462],{"class":184},[66,5854,200],{"class":72},[66,5856,467],{"class":171},[66,5858,206],{"class":72},[66,5860,5861],{"class":337},"4",[66,5863,188],{"class":72},[66,5865,477],{"class":72},[66,5867,480],{"class":439},[66,5869,443],{"class":72},[66,5871,446],{"class":76},[66,5873,200],{"class":72},[66,5875,489],{"class":76},[66,5877,454],{"class":72},[66,5879,178],{"class":72},[66,5881,5883,5885,5887,5889,5891,5893,5896,5898],{"class":68,"line":5882},42,[66,5884,499],{"class":184},[66,5886,200],{"class":72},[66,5888,504],{"class":171},[66,5890,206],{"class":72},[66,5892,209],{"class":72},[66,5894,5895],{"class":212},"小計",[66,5897,209],{"class":72},[66,5899,157],{"class":72},[66,5901,5903],{"class":68,"line":5902},43,[66,5904,567],{"class":72},[66,5906,5908,5910,5912,5914,5916,5918,5920,5922,5924,5926,5928,5930,5932,5934],{"class":68,"line":5907},44,[66,5909,462],{"class":184},[66,5911,200],{"class":72},[66,5913,467],{"class":171},[66,5915,206],{"class":72},[66,5917,5861],{"class":337},[66,5919,188],{"class":72},[66,5921,477],{"class":72},[66,5923,480],{"class":439},[66,5925,443],{"class":72},[66,5927,446],{"class":76},[66,5929,200],{"class":72},[66,5931,489],{"class":76},[66,5933,454],{"class":72},[66,5935,178],{"class":72},[66,5937,5939,5941,5943,5945,5947,5949,5952,5954],{"class":68,"line":5938},45,[66,5940,499],{"class":184},[66,5942,200],{"class":72},[66,5944,504],{"class":171},[66,5946,206],{"class":72},[66,5948,209],{"class":72},[66,5950,5951],{"class":212},"消費税",[66,5953,209],{"class":72},[66,5955,157],{"class":72},[66,5957,5959],{"class":68,"line":5958},46,[66,5960,567],{"class":72},[66,5962,5964,5966,5968,5970,5972,5974,5976,5978,5980,5982,5984,5986,5988,5990],{"class":68,"line":5963},47,[66,5965,462],{"class":184},[66,5967,200],{"class":72},[66,5969,467],{"class":171},[66,5971,206],{"class":72},[66,5973,5861],{"class":337},[66,5975,188],{"class":72},[66,5977,477],{"class":72},[66,5979,480],{"class":439},[66,5981,443],{"class":72},[66,5983,446],{"class":76},[66,5985,200],{"class":72},[66,5987,489],{"class":76},[66,5989,454],{"class":72},[66,5991,178],{"class":72},[66,5993,5995,5997,5999,6001,6003,6005,6007,6009],{"class":68,"line":5994},48,[66,5996,499],{"class":184},[66,5998,200],{"class":72},[66,6000,504],{"class":171},[66,6002,206],{"class":72},[66,6004,209],{"class":72},[66,6006,2676],{"class":212},[66,6008,209],{"class":72},[66,6010,157],{"class":72},[66,6012,6014],{"class":68,"line":6013},49,[66,6015,567],{"class":72},[66,6017,6019],{"class":68,"line":6018},50,[66,6020,573],{"class":72},[66,6022,6024],{"class":68,"line":6023},51,[66,6025,84],{"emptyLinePlaceholder":83},[66,6027,6029],{"class":68,"line":6028},52,[66,6030,6031],{"class":2209},"    // 非対称 (8 + 4) — 本文 + サイドパネル\n",[66,6033,6035,6037,6039,6041,6043,6045,6047,6049,6051,6053,6055],{"class":68,"line":6034},53,[66,6036,428],{"class":184},[66,6038,200],{"class":72},[66,6040,433],{"class":171},[66,6042,436],{"class":72},[66,6044,440],{"class":439},[66,6046,443],{"class":72},[66,6048,446],{"class":76},[66,6050,200],{"class":72},[66,6052,451],{"class":76},[66,6054,454],{"class":72},[66,6056,178],{"class":72},[66,6058,6060,6062,6064,6066,6068,6071,6073,6075,6077,6079,6081,6083,6085,6087],{"class":68,"line":6059},54,[66,6061,462],{"class":184},[66,6063,200],{"class":72},[66,6065,467],{"class":171},[66,6067,206],{"class":72},[66,6069,6070],{"class":337},"8",[66,6072,188],{"class":72},[66,6074,477],{"class":72},[66,6076,480],{"class":439},[66,6078,443],{"class":72},[66,6080,446],{"class":76},[66,6082,200],{"class":72},[66,6084,489],{"class":76},[66,6086,454],{"class":72},[66,6088,178],{"class":72},[66,6090,6092,6094,6096,6098,6100,6102,6105,6107],{"class":68,"line":6091},55,[66,6093,499],{"class":184},[66,6095,200],{"class":72},[66,6097,504],{"class":171},[66,6099,206],{"class":72},[66,6101,209],{"class":72},[66,6103,6104],{"class":212},"明細はここに並ぶ",[66,6106,209],{"class":72},[66,6108,157],{"class":72},[66,6110,6112],{"class":68,"line":6111},56,[66,6113,567],{"class":72},[66,6115,6117,6119,6121,6123,6125,6127,6129,6131,6133,6135,6137,6139,6141,6143],{"class":68,"line":6116},57,[66,6118,462],{"class":184},[66,6120,200],{"class":72},[66,6122,467],{"class":171},[66,6124,206],{"class":72},[66,6126,5861],{"class":337},[66,6128,188],{"class":72},[66,6130,477],{"class":72},[66,6132,480],{"class":439},[66,6134,443],{"class":72},[66,6136,446],{"class":76},[66,6138,200],{"class":72},[66,6140,489],{"class":76},[66,6142,454],{"class":72},[66,6144,178],{"class":72},[66,6146,6148,6150,6152,6154,6156,6158,6161,6163],{"class":68,"line":6147},58,[66,6149,499],{"class":184},[66,6151,200],{"class":72},[66,6153,504],{"class":171},[66,6155,206],{"class":72},[66,6157,209],{"class":72},[66,6159,6160],{"class":212},"備考",[66,6162,209],{"class":72},[66,6164,157],{"class":72},[66,6166,6168],{"class":68,"line":6167},59,[66,6169,567],{"class":72},[66,6171,6173],{"class":68,"line":6172},60,[66,6174,573],{"class":72},[66,6176,6178],{"class":68,"line":6177},61,[66,6179,84],{"emptyLinePlaceholder":83},[66,6181,6183,6185,6187,6189,6191,6193,6195,6197],{"class":68,"line":6182},62,[66,6184,584],{"class":184},[66,6186,188],{"class":72},[66,6188,191],{"class":184},[66,6190,194],{"class":72},[66,6192,414],{"class":184},[66,6194,200],{"class":72},[66,6196,597],{"class":171},[66,6198,422],{"class":72},[66,6200,6202,6204,6206,6208,6210],{"class":68,"line":6201},63,[66,6203,222],{"class":90},[66,6205,191],{"class":184},[66,6207,227],{"class":72},[66,6209,230],{"class":72},[66,6211,178],{"class":72},[66,6213,6215,6217,6219,6221,6223,6225],{"class":68,"line":6214},64,[66,6216,238],{"class":184},[66,6218,200],{"class":72},[66,6220,243],{"class":171},[66,6222,206],{"class":72},[66,6224,248],{"class":184},[66,6226,157],{"class":72},[66,6228,6230],{"class":68,"line":6229},65,[66,6231,256],{"class":72},[66,6233,6235,6237,6239,6241,6243,6245,6247,6249,6251,6254,6256,6258,6260,6262,6264,6266,6268,6270,6272],{"class":68,"line":6234},66,[66,6236,222],{"class":90},[66,6238,191],{"class":184},[66,6240,194],{"class":72},[66,6242,197],{"class":184},[66,6244,200],{"class":72},[66,6246,648],{"class":171},[66,6248,206],{"class":72},[66,6250,209],{"class":72},[66,6252,6253],{"class":212},"layout.pdf",[66,6255,209],{"class":72},[66,6257,188],{"class":72},[66,6259,662],{"class":184},[66,6261,188],{"class":72},[66,6263,667],{"class":337},[66,6265,670],{"class":72},[66,6267,191],{"class":184},[66,6269,227],{"class":72},[66,6271,230],{"class":72},[66,6273,178],{"class":72},[66,6275,6277,6279,6281,6283,6285,6287],{"class":68,"line":6276},67,[66,6278,238],{"class":184},[66,6280,200],{"class":72},[66,6282,243],{"class":171},[66,6284,206],{"class":72},[66,6286,248],{"class":184},[66,6288,157],{"class":72},[66,6290,6292],{"class":68,"line":6291},68,[66,6293,256],{"class":72},[66,6295,6297],{"class":68,"line":6296},69,[66,6298,704],{"class":72},[18,6300,6301,6303],{},[45,6302,714],{}," で 1 ページの PDF が出る。4 つの行がそれぞれ違う割り方をしている。",[14,6305,6307],{"id":6306},"なぜ-12-なのか","なぜ 12 なのか",[18,6309,6310,6311,1149],{},"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 はこのイディオムを意図的に持ち込んでいる。",[29,6312,6313],{},"出力が固定幅の紙だろうと、レイアウトの考え方は Web と別物ではない",[14,6315,6316],{"id":6316},"計算を具体的に",[18,6318,6319,6320,6323,6324,6327,6328,6331,6332,6335],{},"A4 縦で 15 mm のマージンを四辺に置くと、使える幅は 180 mm。行の中の ",[45,6321,6322],{},"Col(4)"," はそのうち 4/12 つまり 60 mm。",[45,6325,6326],{},"Col(8)"," は 120 mm。列と列の間にガターは入らない (デフォルト 0)。隙間が欲しければ短い方の列の中に ",[45,6329,6330],{},"c.Spacer"," を追加するか、",[45,6333,6334],{},"Col(1)"," を空のまま挟む。",[18,6337,6338,6339,6342,6343,6346,6347,1149],{},"幅の計算はビルド時に百分率で行われ (該当箇所は ",[45,6340,6341],{},"gpdf/template/grid.go",")、レイアウトエンジンが現在のページ幅からマージンを引いた値に対して実際のポイントに解決する。つまり同じ ",[45,6344,6345],{},"r.Col(6, fn)"," でも A4 と Letter では物理的な幅は変わるが、",[29,6348,6349],{},"行に対する比率は変わらない",[14,6351,6353],{"id":6352},"合計が-12-に届かない-超える","合計が 12 に届かない / 超える",[18,6355,6356],{},"gpdf は span の合計を検証しない。これは意図的な設計。",[1109,6358,6359,6365],{},[882,6360,6361,6364],{},[29,6362,6363],{},"合計 \u003C 12",": 行の右側が空く。左端にだけ要素を置いて残りを意図的に空けたい場合に便利。",[882,6366,6367,6370],{},[29,6368,6369],{},"合計 > 12",": 最後の列が右マージンからはみ出す。たいていバグで、PDF の見た目はおかしくなるがクラッシュはしない。",[18,6372,6373,6374,6377,6378,6381,6382,6384],{},"ほとんどのレイアウトは行あたり 12 でぴったり埋まる。ただし「中央に 6 幅の塊だけ置きたい」なら ",[45,6375,6376],{},"Col(3)"," 空、",[45,6379,6380],{},"Col(6)"," 本体、",[45,6383,6376],{}," 空、というショートハンドが自然に書ける — このグリッドはそういう表現を狙って作られている。",[14,6386,6388],{"id":6387},"autorow-と-row-の違い","AutoRow と Row の違い",[18,6390,6391,6394],{},[45,6392,6393],{},"page.AutoRow(fn)"," は一番高い列に合わせて行の高さが伸びる。ほとんどの行はこれでいい。",[18,6396,6397,6400],{},[45,6398,6399],{},"page.Row(height, fn)"," は高さを固定する。高さを超えたコンテンツはクリップされる。後工程のステープル位置を合わせるためにヘッダーを必ず 30 mm にしたい、といった「見た目の一貫性 > コンテンツの自由度」の場面で使う。",[57,6402,6404],{"className":59,"code":6403,"language":61,"meta":62,"style":62},"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",[45,6405,6406,6445,6475,6494,6498,6528,6547,6551],{"__ignoreMap":62},[66,6407,6408,6410,6412,6414,6416,6418,6420,6422,6424,6427,6429,6431,6433,6435,6437,6439,6441,6443],{"class":68,"line":69},[66,6409,1926],{"class":184},[66,6411,200],{"class":72},[66,6413,2112],{"class":171},[66,6415,206],{"class":72},[66,6417,318],{"class":184},[66,6419,200],{"class":72},[66,6421,332],{"class":171},[66,6423,206],{"class":72},[66,6425,6426],{"class":337},"30",[66,6428,531],{"class":72},[66,6430,477],{"class":72},[66,6432,440],{"class":439},[66,6434,443],{"class":72},[66,6436,446],{"class":76},[66,6438,200],{"class":72},[66,6440,451],{"class":76},[66,6442,454],{"class":72},[66,6444,178],{"class":72},[66,6446,6447,6449,6451,6453,6455,6457,6459,6461,6463,6465,6467,6469,6471,6473],{"class":68,"line":80},[66,6448,3673],{"class":184},[66,6450,200],{"class":72},[66,6452,467],{"class":171},[66,6454,206],{"class":72},[66,6456,6070],{"class":337},[66,6458,188],{"class":72},[66,6460,477],{"class":72},[66,6462,480],{"class":439},[66,6464,443],{"class":72},[66,6466,446],{"class":76},[66,6468,200],{"class":72},[66,6470,489],{"class":76},[66,6472,454],{"class":72},[66,6474,178],{"class":72},[66,6476,6477,6479,6481,6483,6485,6487,6490,6492],{"class":68,"line":87},[66,6478,3704],{"class":184},[66,6480,200],{"class":72},[66,6482,504],{"class":171},[66,6484,206],{"class":72},[66,6486,209],{"class":72},[66,6488,6489],{"class":212},"ロゴ",[66,6491,209],{"class":72},[66,6493,157],{"class":72},[66,6495,6496],{"class":68,"line":97},[66,6497,573],{"class":72},[66,6499,6500,6502,6504,6506,6508,6510,6512,6514,6516,6518,6520,6522,6524,6526],{"class":68,"line":109},[66,6501,3673],{"class":184},[66,6503,200],{"class":72},[66,6505,467],{"class":171},[66,6507,206],{"class":72},[66,6509,5861],{"class":337},[66,6511,188],{"class":72},[66,6513,477],{"class":72},[66,6515,480],{"class":439},[66,6517,443],{"class":72},[66,6519,446],{"class":76},[66,6521,200],{"class":72},[66,6523,489],{"class":76},[66,6525,454],{"class":72},[66,6527,178],{"class":72},[66,6529,6530,6532,6534,6536,6538,6540,6543,6545],{"class":68,"line":119},[66,6531,3704],{"class":184},[66,6533,200],{"class":72},[66,6535,504],{"class":171},[66,6537,206],{"class":72},[66,6539,209],{"class":72},[66,6541,6542],{"class":212},"請求番号",[66,6544,209],{"class":72},[66,6546,157],{"class":72},[66,6548,6549],{"class":68,"line":124},[66,6550,573],{"class":72},[66,6552,6553],{"class":68,"line":134},[66,6554,3731],{"class":72},[14,6556,6557],{"id":6557},"グリッドにないもの",[18,6559,6560,6561,6563],{},"ネストはできない。",[45,6562,489],{}," はコンテンツ要素 (Text / Image / Table / List / Spacer) を受け取るが、中に別の行を入れることはできない。ネストが必要に見える構造は、ページ直下の兄弟行として 2 行に分けた方がすっきりすることが多い。",[18,6565,6566,6567,6570,6571,6574],{},"オフセット列もない。Bootstrap の ",[45,6568,6569],{},".offset-2"," 相当は提供していない。右に寄せたければ空の ",[45,6572,6573],{},"Col(n)"," を左に挟む。",[18,6576,6577],{},"ブレークポイントもない。PDF はリサイズしない。どの端末で開いても同じレイアウトになる — 出力が DOM ではなく固定座標のラスタなので、再レイアウトという概念が存在しない。",[18,6579,6580],{},"この「ないもの」が設計上の利点になっている。グリッドが持たない機能ごとに、PDF の出力結果を読むときに考えなくていい曖昧さが 1 つずつ減っていく。",[14,6582,1208],{"id":1208},[1109,6584,6585,6591,6597],{},[882,6586,6587,6590],{},[21,6588,6589],{"href":1221},"gpdf で日本語フォントを埋め込むには？"," — グリッド列の中で CJK を扱う",[882,6592,6593,6596],{},[21,6594,6595],{"href":2899},"Go PDF ライブラリ比較 2026"," — Builder API が gofpdf / gopdf / Maroto とどう違うか",[882,6598,6599,6604],{},[21,6600,6603],{"href":6601,"rel":6602},"https://gpdf.dev/ja/docs/guide/layout",[25],"レイアウトガイド"," — 行・列・スペーシングの完全リファレンス",[14,6606,1241],{"id":1240},[18,6608,6609],{},"gpdf は Go の PDF 生成ライブラリ。MIT、ゼロ依存、CJK 対応。",[57,6611,6612],{"className":1247,"code":1248,"language":1249,"meta":62,"style":62},[45,6613,6614],{"__ignoreMap":62},[66,6615,6616,6618,6620],{"class":68,"line":69},[66,6617,61],{"class":76},[66,6619,1258],{"class":212},[66,6621,1261],{"class":212},[18,6623,6624,1268,6627],{},[21,6625,2885],{"href":23,"rel":6626},[25],[21,6628,1273],{"href":1271,"rel":6629},[25],[1275,6631,6632],{},"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":62,"searchDepth":80,"depth":80,"links":6634},[6635,6636,6637,6638,6639,6640,6641,6642,6643,6644],{"id":5323,"depth":80,"text":5323},{"id":5337,"depth":80,"text":5337},{"id":5354,"depth":80,"text":5354},{"id":6306,"depth":80,"text":6307},{"id":6316,"depth":80,"text":6316},{"id":6352,"depth":80,"text":6353},{"id":6387,"depth":80,"text":6388},{"id":6557,"depth":80,"text":6557},{"id":1208,"depth":80,"text":1208},{"id":1240,"depth":80,"text":1241},"gpdf の 12 列グリッドは r.Col(span, fn) に 1–12 の整数を渡すだけ。幅は span/12、ガターもブレークポイントもない PDF 向けの割り切った設計。",{"name":6647,"totalTime":5291,"tools":6648,"steps":6649},"gpdf の 12 列グリッドでページをレイアウトする",[1298,129],[6650,6653,6656,6659,6662],{"name":6651,"text":6652},"ページに行を開く","一番高い列に合わせて行の高さを可変にしたいなら page.AutoRow(fn)、固定したいなら page.Row(height, fn) を呼ぶ。",{"name":6654,"text":6655},"r.Col(span, fn) で列を宣言する","行の中で r.Col(span, fn) を列の数だけ呼ぶ。span は 1〜12 の整数で、行幅に対するその列の取り分を表す。",{"name":6657,"text":6658},"行ごとの span 合計を 12 以下に収める","合計が 12 未満なら右側が空く。12 を超えると最後の列が行からはみ出す — たいていバグの兆候。",{"name":6660,"text":6661},"列にコンテンツを詰める","ColBuilder のコールバック内で c.Text / c.Image / c.Table / c.Spacer を呼ぶ。追加した順に縦積みされる。",{"name":6663,"text":6664},"次の行を開く","次の視覚行は page.AutoRow をもう一度呼ぶ。行は独立していて、4+8 の行のすぐ下に 3+3+3+3 の行を置いて問題ない。",{},"/ja/blog/12-column-grid",{"title":5317,"description":6645},"ja/blog/005.12-column-grid",[1322,1324,6670],"templates","-lx6YfUHIibbueChcrBeJmAI6Vbt1sGp5RzBMpkaoGc",{"id":6673,"title":6674,"author":6675,"body":6676,"date":5803,"description":8405,"draft":1292,"extension":1293,"howTo":1316,"image":1316,"meta":8406,"navigation":83,"path":8407,"seo":8408,"stem":8409,"tags":8410,"updated":1316,"__hash__":8412},"blogJa/ja/blog/006.go-pdf-fpdf-archived.md","go-pdf/fpdf もアーカイブ。Go の PDF は gpdf に移る。",{"name":8,"url":9},{"type":11,"value":6677,"toc":8389},[6678,6680,6701,6704,6723,6726,6743,6753,6757,6760,6793,6799,6802,6808,6823,6826,6835,6841,6845,6848,7024,7027,7039,7051,7057,7063,7066,7070,7076,7079,7121,7124,7305,7318,7322,7337,7340,7345,7664,7669,8048,8064,8073,8076,8083,8165,8172,8178,8181,8189,8192,8240,8243,8246,8253,8262,8272,8274,8286,8292,8302,8321,8329,8335,8337,8339,8351,8359,8361,8386],[14,6679,1335],{"id":1334},[18,6681,6682,6685,6686,2113,6688,6691,6692,2113,6694,6697,6698,6700],{},[45,6683,6684],{},"fpdf"," 系統の保守されていたフォークは 2 つとも read-only になった。",[45,6687,1353],{},[29,6689,6690],{},"2021 年 9 月","、コミュニティフォークの ",[45,6693,1431],{},[29,6695,6696],{},"2025 年"," にアーカイブ。\"次のメンテナ\" は現れない。新規プロジェクトで選ぶべきは ",[29,6699,26],{}," — 純 Go、ゼロ依存、CJK ネイティブ、一般的なワークロードで 10〜30 倍高速。この記事は 2026 年時点の Go PDF 勢力図と、gpdf を採用すべき場合・そうでない場合を正直に書いたもの。",[14,6702,6703],{"id":6703},"状況",[18,6705,6706,6707,6710,6711,6715,6716,6719,6720,6722],{},"先週、同僚が ",[45,6708,6709],{},"go get github.com/go-pdf/fpdf"," を叩いて GitHub のバナーで手が止まった: ",[6712,6713,6714],"em",{},"\"This repository has been archived by the owner. It is now read-only.\""," — これは ",[6712,6717,6718],{},"直された方"," のはずだった。2021 年にアーカイブされた ",[45,6721,1353],{}," を引き継ぐはずのコミュニティフォークだ。",[18,6724,6725],{},"それもアーカイブされている。README は別のライブラリを勧めている。",[18,6727,6728,6729,6732,6733,6735,6736,6738,6739,6742],{},"この 5 年間、請求書・レポート・配送ラベル・適格請求書 PDF を Go で吐くサービスを運用してきたなら、",[45,6730,6731],{},"go.mod"," の底に入っているのはほぼ間違いなくこの 2 つのどちらか。Stack Overflow の回答は ",[45,6734,1353],{}," を指し、新しめのチュートリアルは ",[45,6737,1431],{}," を指す。",[29,6740,6741],{},"両方とも、もはやサプライチェーン上の負債","だ。CVE のトリアージも、Go のバージョン追従も、パフォーマンス修正も、仕様更新も止まっている。",[18,6744,6745,6746,6749,6750,1149],{},"この記事は「API をこう置き換えましょう」の移行ガイドではない。それは",[21,6747,6748],{"href":2906},"別の記事","で書いた。ここで答えたいのは、移行ガイドが答えていない方の問い — ",[29,6751,6752],{},"2026 年の Go PDF 生成は何で組むのが正解で、なぜ業界はここに着地したのか",[14,6754,6756],{"id":6755},"アーカイブ-が実運用に意味すること","\"アーカイブ\" が実運用に意味すること",[18,6758,6759],{},"GitHub の \"archived\" ラベルはやわらかく見える。import グラフに入っているライブラリとしては、具体的には 4 つの結果を意味する。",[879,6761,6762,6768,6778,6784],{},[882,6763,6764,6767],{},[29,6765,6766],{},"セキュリティパッチが来ない","。TTF パーサにメモリ安全性の問題が見つかっても、上流にはマージされない。自分でフォークして直すしかないが、ほとんどのチームはやらない。",[882,6769,6770,6773,6774,6777],{},[29,6771,6772],{},"Go ツールチェインへの追従がない","。Go 1.25 のループ変数セマンティクスは gofpdf でも今のところ動く。ただ、明日 ",[45,6775,6776],{},"for range"," 周りや std の非推奨で何かが壊れたら、read-only リポジトリのフォークで直すのは自分。",[882,6779,6780,6783],{},[29,6781,6782],{},"仕様更新がない","。PDF 2.0 (ISO 32000-2) は 2020 年に出た。gofpdf は PDF 1.7 までしか実装していない。ページ単位の関連ファイル、リッチな XMP メタデータ、モダンな電子署名 (PAdES-B-LT) などは、サードパーティの糊付けが必須か、そもそもない。",[882,6785,6786,6789,6790,6792],{},[29,6787,6788],{},"CJK の前進がない","。gofpdf の Unicode 経路は、単一バイトフォント前提の設計に後付けされたもの。動くには動くが、実設定ではフルフォント埋め込みになりがちで、特定の CJK TTF でグリフ ID 衝突が起きて出力が壊れることがある。",[45,6791,1431],{}," は同じ構造を引き継いでいる。",[18,6794,6795,6796,1149],{},"セキュリティと前方互換の方は、コンプライアンス会議で刺さる。「PDF ライブラリはアーカイブされていて CVE パッチは来ません」は、監査担当が聞きたい答えではない。",[29,6797,6798],{},"電子帳簿保存法のスコープ内で PDF を保管しているなら特に、この論点は先送りできない",[14,6800,6801],{"id":6801},"なぜ両方のフォークが死んだのか",[18,6803,6804,6805,1149],{},"単なるメンテナ燃え尽きで説明したくなる — PR レビューに疲れた一人のメンテナ、バス係数 1 がオフラインに。それも要因ではあるが、全体像ではない。",[29,6806,6807],{},"アーキテクチャ側の問題が、追いつくのを難しくした",[18,6809,6810,6812,6813,5330,6816,5330,6819,6822],{},[45,6811,1353],{}," は FPDF、2002 年の PHP ライブラリの移植だった。PHP オリジナルはカーソルをページ上で動かして手続き的にコンテンツを吐く: ",[45,6814,6815],{},"SetXY(x, y)",[45,6817,6818],{},"Cell(w, h, text)",[45,6820,6821],{},"Ln(h)","。このモデルは 2002 年の PHP では妥当な妥協だった — 当時の代案は生の PostScript か商用ツールキット。Go に移植されるときも、カーソルを残し、単一バイトのフォントテーブルを残し、手動のページブレーク管理を残した。",[18,6824,6825],{},"年を追うごとに、人が生成したいものとカーソルモデルで表現できるもののギャップは広がった。請求書はテーブル。レポートは繰り返しのクロームを持ったグリッド。配送ラベルは QR コード + 現地語テキスト。カーソルはヘルパーで包まれ、そのヘルパーはチュートリアルで包まれ、2023 年頃には「人が gofpdf に対して書いたコード」は実質 gofpdf ではなかった — チームごとの糊コードで、カーソルをレイアウトエンジンのフリをさせようとする層だった。",[18,6827,6828,6830,6831,6834],{},[45,6829,1431],{}," はそれを継承した。フォークは内部をリファクタし、長年のバグを修正したが、",[29,6832,6833],{},"公開 API の形は変えられなかった","。全ての下流プロジェクトを壊さずには直せなかったからだ。ライブラリの形は 2002 年の PHP で凍結されていて、その形を維持する費用は、得られる便益より速く増えていった。",[18,6836,6837,6840],{},[29,6838,6839],{},"つまり",": メンテナ 2 人、アーカイブ 2 つ、アーキテクチャの 1 つの理由。2026 年にやり直すなら、今の PDF 生成の仕方に合ったアプローチを選ぶ必要がある — 今のそれは、プロッタを動かすよりも、Web ページを組み立てる方に近い。",[14,6842,6844],{"id":6843},"_2026-年の-go-pdf-勢力図","2026 年の Go PDF 勢力図",[18,6846,6847],{},"何かを推す前に、まず勢力図を並べる。「メンテナンスされている」は「直近 6 ヶ月にコミットがあり、issue に反応がある」の意味で使う。",[738,6849,6850,6870],{},[741,6851,6852],{},[744,6853,6854,6857,6860,6862,6865,6868],{},[747,6855,6856],{},"ライブラリ",[747,6858,6859],{},"ステータス (2026-04)",[747,6861,5164],{},[747,6863,6864],{},"CJK ネイティブ",[747,6866,6867],{},"外部依存ゼロ",[747,6869,6160],{},[756,6871,6872,6895,6915,6934,6958,6979,7002],{},[744,6873,6874,6878,6883,6886,6889,6892],{},[761,6875,6876],{},[45,6877,1353],{},[761,6879,6880],{},[29,6881,6882],{},"2021 アーカイブ",[761,6884,6885],{},"MIT",[761,6887,6888],{},"後付け",[761,6890,6891],{},"はい",[761,6893,6894],{},"オリジナル。ほとんどの言語の検索結果で今も 1 位。",[744,6896,6897,6901,6906,6908,6910,6912],{},[761,6898,6899],{},[45,6900,1431],{},[761,6902,6903],{},[29,6904,6905],{},"2025 アーカイブ",[761,6907,6885],{},[761,6909,6888],{},[761,6911,6891],{},[761,6913,6914],{},"上のコミュニティフォーク。同じアーキテクチャ、同じ天井。",[744,6916,6917,6921,6924,6926,6929,6931],{},[761,6918,6919],{},[45,6920,1434],{},[761,6922,6923],{},"メンテナンス中",[761,6925,6885],{},[761,6927,6928],{},"部分的",[761,6930,6891],{},[761,6932,6933],{},"低レベル。座標を自分で書く。帳票オーバーレイには向く。",[744,6935,6936,6942,6944,6946,6949,6952],{},[761,6937,6938,6941],{},[45,6939,6940],{},"johnfercher/maroto"," v2",[761,6943,6923],{},[761,6945,6885],{},[761,6947,6948],{},"gofpdf 経由",[761,6950,6951],{},"いいえ",[761,6953,6954,6955,6957],{},"グリッド優先のビルダー。ただし下に ",[45,6956,1431],{}," を抱える。",[744,6959,6960,6965,6967,6972,6974,6976],{},[761,6961,6962],{},[45,6963,6964],{},"unidoc/unipdf",[761,6966,6923],{},[761,6968,6969],{},[29,6970,6971],{},"商用",[761,6973,6891],{},[761,6975,6951],{},[761,6977,6978],{},"フル機能の PDF SDK。商用利用には有料ライセンスが必須。",[744,6980,6981,6987,6989,6992,6994,6999],{},[761,6982,6983,6986],{},[45,6984,6985],{},"chromedp"," + Chromium",[761,6988,6923],{},[761,6990,6991],{},"MIT + Chrome",[761,6993,6891],{},[761,6995,6996],{},[29,6997,6998],{},"いいえ — ブラウザを同梱",[761,7000,7001],{},"HTML→PDF をヘッドレス Chrome で。ランタイムが巨大。",[744,7003,7004,7008,7010,7012,7017,7021],{},[761,7005,7006],{},[45,7007,26],{},[761,7009,6923],{},[761,7011,6885],{},[761,7013,7014],{},[29,7015,7016],{},"ネイティブ",[761,7018,7019],{},[29,7020,6891],{},[761,7022,7023],{},"純 Go の書き直し。ビルダー API、12 列グリッド。",[18,7025,7026],{},"表を眺めるだけで見えてくることがいくつかある。",[18,7028,7029,7032,7033,7035,7036,7038],{},[29,7030,7031],{},"メンテナンスされている選択肢は、商用ライセンスを要するか、巨大なランタイムを抱えるか、もうすぐ陳腐化する基盤に乗っている","。例外は ",[45,7034,1434],{}," — 本当に保守されていて、依存も軽い。ただしこれは座標レベルのライブラリで、結局 ",[45,7037,1930],{}," をパッケージ名を変えてやり直すことになる。",[18,7040,7041,7044,7045,7047,7048,7050],{},[29,7042,7043],{},"Maroto v2 は良い API のグリッド優先ビルダー","。問題は ",[45,7046,6731],{}," の底に ",[45,7049,1431],{}," があることだ。fpdf のパフォーマンス天井も CJK の制約も、そのまま Maroto の天井になる。v3 でそれを脱する可能性はあるが、まだ出ていない。",[18,7052,7053,7056],{},[29,7054,7055],{},"unipdf はリッチだが商用利用には MIT 互換ではない","。シート単位 or デプロイ単位で課金される。収益がそれを支えるなら問題ない選択だが、OSS サイドプロジェクトや初期段階のスタートアップではライセンス計算が合わない。",[18,7058,7059,7062],{},[29,7060,7061],{},"chromedp は動くが、ブラウザを同梱することになる","。100 MB のベースイメージが 1 GB 超になる。サーバレスのコールドスタートは痛い。フォントは別途コンテナに入れる必要がある。利点は React テンプレートを再利用できること、欠点は請求書を出すために Chromium を回し続けること。",[18,7064,7065],{},"空いている席は明らか: 純 Go、ゼロ依存、CJK ネイティブ、グリッド優先、商用ライセンス不要、ブラウザランタイム不要のライブラリ。それが gpdf。",[14,7067,7069],{"id":7068},"gpdf-とは何か","gpdf とは何か",[18,7071,7072,7073,7075],{},"gpdf (",[45,7074,129],{},") はクリーンな書き直し。フォークではない。PDF のワイヤーフォーマット書き込み、レイアウトエンジン、TrueType サブセッター — すべて純 Go で一から書いた。",[18,7077,7078],{},"ほとんどのチームにとって重要なのは 3 つの性質:",[1109,7080,7081,7094,7110],{},[882,7082,7083,1149,7086,7089,7090,7093],{},[29,7084,7085],{},"純 Go、CGO なし",[45,7087,7088],{},"go build"," は静的。",[45,7091,7092],{},"GOOS=linux GOARCH=arm64 go build"," が MacBook からツールチェイン設定なしで通る。Docker イメージも小さいまま — 12 MB の distroless コンテナで動く。",[882,7095,7096,1149,7098,7101,7102,7105,7106,7109],{},[29,7097,6867],{},[45,7099,7100],{},"go get github.com/gpdf-dev/gpdf"," した後に ",[45,7103,7104],{},"go mod graph"," を叩くと 1 行しか出ない: gpdf 自身。コアは ",[45,7107,7108],{},"std"," のみ。(HTML→PDF や電子署名の追加モジュールは小さな依存を持つが、これらはオプトイン。)",[882,7111,7112,1149,7114,7116,7117,7120],{},[29,7113,6864],{},[45,7115,351],{}," が TrueType フォントをドキュメント構築時に登録する。サブセット埋め込みは描画時に自動。",[29,7118,7119],{},"日本語 200 文字の請求書なら、埋め込みフォントは ~30 KB のサブセット","。フルフォントの 5 MB ではない。",[18,7122,7123],{},"API は宣言的。行と列のツリーを記述するとレイアウトエンジンが配置する。グリッドは 12 列 — Bootstrap が 2011 年から出しているのと同じ idiom。HTML/CSS を一行でも書いたことがあれば、gpdf の API は馴染みがある:",[57,7125,7127],{"className":59,"code":7126,"language":61,"meta":62,"style":62},"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",[45,7128,7129,7144,7168,7198,7236,7240,7270,7297,7301],{"__ignoreMap":62},[66,7130,7131,7134,7136,7138,7140,7142],{"class":68,"line":69},[66,7132,7133],{"class":184},"page ",[66,7135,194],{"class":72},[66,7137,414],{"class":184},[66,7139,200],{"class":72},[66,7141,419],{"class":171},[66,7143,422],{"class":72},[66,7145,7146,7148,7150,7152,7154,7156,7158,7160,7162,7164,7166],{"class":68,"line":80},[66,7147,1926],{"class":184},[66,7149,200],{"class":72},[66,7151,433],{"class":171},[66,7153,436],{"class":72},[66,7155,440],{"class":439},[66,7157,443],{"class":72},[66,7159,446],{"class":76},[66,7161,200],{"class":72},[66,7163,451],{"class":76},[66,7165,454],{"class":72},[66,7167,178],{"class":72},[66,7169,7170,7172,7174,7176,7178,7180,7182,7184,7186,7188,7190,7192,7194,7196],{"class":68,"line":87},[66,7171,3673],{"class":184},[66,7173,200],{"class":72},[66,7175,467],{"class":171},[66,7177,206],{"class":72},[66,7179,6070],{"class":337},[66,7181,188],{"class":72},[66,7183,477],{"class":72},[66,7185,480],{"class":439},[66,7187,443],{"class":72},[66,7189,446],{"class":76},[66,7191,200],{"class":72},[66,7193,489],{"class":76},[66,7195,454],{"class":72},[66,7197,178],{"class":72},[66,7199,7200,7202,7204,7206,7208,7210,7212,7214,7216,7218,7220,7222,7224,7226,7228,7230,7232,7234],{"class":68,"line":97},[66,7201,3704],{"class":184},[66,7203,200],{"class":72},[66,7205,504],{"class":171},[66,7207,206],{"class":72},[66,7209,209],{"class":72},[66,7211,5600],{"class":212},[66,7213,209],{"class":72},[66,7215,188],{"class":72},[66,7217,518],{"class":184},[66,7219,200],{"class":72},[66,7221,523],{"class":171},[66,7223,206],{"class":72},[66,7225,5615],{"class":337},[66,7227,531],{"class":72},[66,7229,518],{"class":184},[66,7231,200],{"class":72},[66,7233,538],{"class":171},[66,7235,541],{"class":72},[66,7237,7238],{"class":68,"line":109},[66,7239,573],{"class":72},[66,7241,7242,7244,7246,7248,7250,7252,7254,7256,7258,7260,7262,7264,7266,7268],{"class":68,"line":119},[66,7243,3673],{"class":184},[66,7245,200],{"class":72},[66,7247,467],{"class":171},[66,7249,206],{"class":72},[66,7251,5861],{"class":337},[66,7253,188],{"class":72},[66,7255,477],{"class":72},[66,7257,480],{"class":439},[66,7259,443],{"class":72},[66,7261,446],{"class":76},[66,7263,200],{"class":72},[66,7265,489],{"class":76},[66,7267,454],{"class":72},[66,7269,178],{"class":72},[66,7271,7272,7274,7276,7278,7280,7282,7284,7286,7288,7290,7292,7295],{"class":68,"line":124},[66,7273,3704],{"class":184},[66,7275,200],{"class":72},[66,7277,504],{"class":171},[66,7279,206],{"class":72},[66,7281,209],{"class":72},[66,7283,5803],{"class":212},[66,7285,209],{"class":72},[66,7287,188],{"class":72},[66,7289,518],{"class":184},[66,7291,200],{"class":72},[66,7293,7294],{"class":171},"AlignRight",[66,7296,541],{"class":72},[66,7298,7299],{"class":68,"line":134},[66,7300,573],{"class":72},[66,7302,7303],{"class":68,"line":144},[66,7304,3731],{"class":72},[18,7306,7307,7308,7310,7311,7314,7315,7317],{},"グリッドの詳細は ",[21,7309,5317],{"href":6666}," に書いた。1 文で言えば、",[45,7312,7313],{},"Col(span, fn)"," は 1〜12 の span を取り、",[45,7316,5346],{}," がその列の行幅に対する比率。",[14,7319,7321],{"id":7320},"最小の-go-pdffpdf-gpdf-差分","最小の go-pdf/fpdf → gpdf 差分",[18,7323,7324,7326,7327,7329,7330,7332,7333,7336],{},[45,7325,1431],{}," から来る人 (",[45,7328,1353],{}," ではなく) には良い知らせ: API の形はほぼ同じだ。",[45,7331,1431],{}," は呼び出し側のコードに対しては何も変えていないフォーク。gpdf への移行は ",[21,7334,7335],{"href":2906},"gofpdf ガイド","と同じで、import パスを 1 行書き換えるところから始まる。",[18,7338,7339],{},"最小の差分 — 「PDF を返す」HTTP ハンドラ:",[18,7341,7342],{},[29,7343,7344],{},"Before — go-pdf/fpdf:",[57,7346,7348],{"className":59,"code":7347,"language":61,"meta":62,"style":62},"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",[45,7349,7350,7356,7360,7366,7375,7379,7388,7392,7396,7434,7483,7494,7527,7556,7560,7596,7625,7656,7660],{"__ignoreMap":62},[66,7351,7352,7354],{"class":68,"line":69},[66,7353,73],{"class":72},[66,7355,77],{"class":76},[66,7357,7358],{"class":68,"line":80},[66,7359,84],{"emptyLinePlaceholder":83},[66,7361,7362,7364],{"class":68,"line":87},[66,7363,91],{"class":90},[66,7365,94],{"class":72},[66,7367,7368,7370,7373],{"class":68,"line":97},[66,7369,100],{"class":72},[66,7371,7372],{"class":76},"net/http",[66,7374,106],{"class":72},[66,7376,7377],{"class":68,"line":109},[66,7378,84],{"emptyLinePlaceholder":83},[66,7380,7381,7383,7386],{"class":68,"line":119},[66,7382,100],{"class":72},[66,7384,7385],{"class":76},"github.com/go-pdf/fpdf",[66,7387,106],{"class":72},[66,7389,7390],{"class":68,"line":124},[66,7391,157],{"class":72},[66,7393,7394],{"class":68,"line":134},[66,7395,84],{"emptyLinePlaceholder":83},[66,7397,7398,7400,7403,7405,7407,7410,7412,7415,7417,7420,7422,7425,7427,7430,7432],{"class":68,"line":144},[66,7399,168],{"class":72},[66,7401,7402],{"class":171}," handler",[66,7404,206],{"class":72},[66,7406,1874],{"class":439},[66,7408,7409],{"class":76}," http",[66,7411,200],{"class":72},[66,7413,7414],{"class":76},"ResponseWriter",[66,7416,188],{"class":72},[66,7418,7419],{"class":439}," r",[66,7421,443],{"class":72},[66,7423,7424],{"class":76},"http",[66,7426,200],{"class":72},[66,7428,7429],{"class":76},"Request",[66,7431,454],{"class":72},[66,7433,178],{"class":72},[66,7435,7436,7439,7441,7444,7446,7449,7451,7453,7456,7458,7460,7463,7466,7468,7470,7472,7474,7476,7478,7481],{"class":68,"line":154},[66,7437,7438],{"class":184},"    pdf ",[66,7440,194],{"class":72},[66,7442,7443],{"class":184}," fpdf",[66,7445,200],{"class":72},[66,7447,7448],{"class":171},"New",[66,7450,206],{"class":72},[66,7452,209],{"class":72},[66,7454,7455],{"class":212},"P",[66,7457,209],{"class":72},[66,7459,188],{"class":72},[66,7461,7462],{"class":72}," \"",[66,7464,7465],{"class":212},"mm",[66,7467,209],{"class":72},[66,7469,188],{"class":72},[66,7471,7462],{"class":72},[66,7473,300],{"class":212},[66,7475,209],{"class":72},[66,7477,188],{"class":72},[66,7479,7480],{"class":72}," \"\"",[66,7482,157],{"class":72},[66,7484,7485,7488,7490,7492],{"class":68,"line":160},[66,7486,7487],{"class":184},"    pdf",[66,7489,200],{"class":72},[66,7491,419],{"class":171},[66,7493,422],{"class":72},[66,7495,7496,7498,7500,7502,7504,7506,7509,7511,7513,7515,7518,7520,7522,7525],{"class":68,"line":165},[66,7497,7487],{"class":184},[66,7499,200],{"class":72},[66,7501,1758],{"class":171},[66,7503,206],{"class":72},[66,7505,209],{"class":72},[66,7507,7508],{"class":212},"Arial",[66,7510,209],{"class":72},[66,7512,188],{"class":72},[66,7514,7462],{"class":72},[66,7516,7517],{"class":212},"B",[66,7519,209],{"class":72},[66,7521,188],{"class":72},[66,7523,7524],{"class":337}," 16",[66,7526,157],{"class":72},[66,7528,7529,7531,7533,7535,7537,7540,7542,7545,7547,7549,7552,7554],{"class":68,"line":181},[66,7530,7487],{"class":184},[66,7532,200],{"class":72},[66,7534,1934],{"class":171},[66,7536,206],{"class":72},[66,7538,7539],{"class":337},"40",[66,7541,188],{"class":72},[66,7543,7544],{"class":337}," 10",[66,7546,188],{"class":72},[66,7548,7462],{"class":72},[66,7550,7551],{"class":212},"Hello, World!",[66,7553,209],{"class":72},[66,7555,157],{"class":72},[66,7557,7558],{"class":68,"line":219},[66,7559,84],{"emptyLinePlaceholder":83},[66,7561,7562,7565,7567,7570,7573,7576,7578,7580,7583,7585,7587,7589,7592,7594],{"class":68,"line":235},[66,7563,7564],{"class":184},"    w",[66,7566,200],{"class":72},[66,7568,7569],{"class":171},"Header",[66,7571,7572],{"class":72},"().",[66,7574,7575],{"class":171},"Set",[66,7577,206],{"class":72},[66,7579,209],{"class":72},[66,7581,7582],{"class":212},"Content-Type",[66,7584,209],{"class":72},[66,7586,188],{"class":72},[66,7588,7462],{"class":72},[66,7590,7591],{"class":212},"application/pdf",[66,7593,209],{"class":72},[66,7595,157],{"class":72},[66,7597,7598,7600,7602,7604,7607,7609,7611,7613,7615,7617,7619,7621,7623],{"class":68,"line":253},[66,7599,222],{"class":90},[66,7601,191],{"class":184},[66,7603,194],{"class":72},[66,7605,7606],{"class":184}," pdf",[66,7608,200],{"class":72},[66,7610,1938],{"class":171},[66,7612,206],{"class":72},[66,7614,1874],{"class":184},[66,7616,670],{"class":72},[66,7618,191],{"class":184},[66,7620,227],{"class":72},[66,7622,230],{"class":72},[66,7624,178],{"class":72},[66,7626,7627,7630,7632,7635,7637,7639,7641,7644,7646,7648,7651,7654],{"class":68,"line":259},[66,7628,7629],{"class":184},"        http",[66,7631,200],{"class":72},[66,7633,7634],{"class":171},"Error",[66,7636,206],{"class":72},[66,7638,1874],{"class":184},[66,7640,188],{"class":72},[66,7642,7643],{"class":184}," err",[66,7645,200],{"class":72},[66,7647,7634],{"class":171},[66,7649,7650],{"class":72},"(),",[66,7652,7653],{"class":337}," 500",[66,7655,157],{"class":72},[66,7657,7658],{"class":68,"line":264},[66,7659,256],{"class":72},[66,7661,7662],{"class":68,"line":283},[66,7663,704],{"class":72},[18,7665,7666],{},[29,7667,7668],{},"After — gpdf:",[57,7670,7672],{"className":59,"code":7671,"language":61,"meta":62,"style":62},"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",[45,7673,7674,7680,7684,7690,7698,7702,7710,7718,7726,7730,7734,7766,7780,7798,7828,7832,7836,7850,7874,7904,7943,7947,7951,7955,7985,8014,8040,8044],{"__ignoreMap":62},[66,7675,7676,7678],{"class":68,"line":69},[66,7677,73],{"class":72},[66,7679,77],{"class":76},[66,7681,7682],{"class":68,"line":80},[66,7683,84],{"emptyLinePlaceholder":83},[66,7685,7686,7688],{"class":68,"line":87},[66,7687,91],{"class":90},[66,7689,94],{"class":72},[66,7691,7692,7694,7696],{"class":68,"line":97},[66,7693,100],{"class":72},[66,7695,7372],{"class":76},[66,7697,106],{"class":72},[66,7699,7700],{"class":68,"line":109},[66,7701,84],{"emptyLinePlaceholder":83},[66,7703,7704,7706,7708],{"class":68,"line":119},[66,7705,100],{"class":72},[66,7707,129],{"class":76},[66,7709,106],{"class":72},[66,7711,7712,7714,7716],{"class":68,"line":124},[66,7713,100],{"class":72},[66,7715,139],{"class":76},[66,7717,106],{"class":72},[66,7719,7720,7722,7724],{"class":68,"line":134},[66,7721,100],{"class":72},[66,7723,149],{"class":76},[66,7725,106],{"class":72},[66,7727,7728],{"class":68,"line":144},[66,7729,157],{"class":72},[66,7731,7732],{"class":68,"line":154},[66,7733,84],{"emptyLinePlaceholder":83},[66,7735,7736,7738,7740,7742,7744,7746,7748,7750,7752,7754,7756,7758,7760,7762,7764],{"class":68,"line":160},[66,7737,168],{"class":72},[66,7739,7402],{"class":171},[66,7741,206],{"class":72},[66,7743,1874],{"class":439},[66,7745,7409],{"class":76},[66,7747,200],{"class":72},[66,7749,7414],{"class":76},[66,7751,188],{"class":72},[66,7753,7419],{"class":439},[66,7755,443],{"class":72},[66,7757,7424],{"class":76},[66,7759,200],{"class":72},[66,7761,7429],{"class":76},[66,7763,454],{"class":72},[66,7765,178],{"class":72},[66,7767,7768,7770,7772,7774,7776,7778],{"class":68,"line":165},[66,7769,267],{"class":184},[66,7771,194],{"class":72},[66,7773,272],{"class":184},[66,7775,200],{"class":72},[66,7777,277],{"class":171},[66,7779,280],{"class":72},[66,7781,7782,7784,7786,7788,7790,7792,7794,7796],{"class":68,"line":181},[66,7783,286],{"class":184},[66,7785,200],{"class":72},[66,7787,291],{"class":171},[66,7789,206],{"class":72},[66,7791,318],{"class":184},[66,7793,200],{"class":72},[66,7795,300],{"class":184},[66,7797,303],{"class":72},[66,7799,7800,7802,7804,7806,7808,7810,7812,7814,7816,7818,7820,7822,7824,7826],{"class":68,"line":219},[66,7801,286],{"class":184},[66,7803,200],{"class":72},[66,7805,313],{"class":171},[66,7807,206],{"class":72},[66,7809,318],{"class":184},[66,7811,200],{"class":72},[66,7813,323],{"class":171},[66,7815,206],{"class":72},[66,7817,318],{"class":184},[66,7819,200],{"class":72},[66,7821,332],{"class":171},[66,7823,206],{"class":72},[66,7825,338],{"class":337},[66,7827,341],{"class":72},[66,7829,7830],{"class":68,"line":235},[66,7831,398],{"class":72},[66,7833,7834],{"class":68,"line":253},[66,7835,84],{"emptyLinePlaceholder":83},[66,7837,7838,7840,7842,7844,7846,7848],{"class":68,"line":259},[66,7839,409],{"class":184},[66,7841,194],{"class":72},[66,7843,414],{"class":184},[66,7845,200],{"class":72},[66,7847,419],{"class":171},[66,7849,422],{"class":72},[66,7851,7852,7854,7856,7858,7860,7862,7864,7866,7868,7870,7872],{"class":68,"line":264},[66,7853,428],{"class":184},[66,7855,200],{"class":72},[66,7857,433],{"class":171},[66,7859,436],{"class":72},[66,7861,440],{"class":439},[66,7863,443],{"class":72},[66,7865,446],{"class":76},[66,7867,200],{"class":72},[66,7869,451],{"class":76},[66,7871,454],{"class":72},[66,7873,178],{"class":72},[66,7875,7876,7878,7880,7882,7884,7886,7888,7890,7892,7894,7896,7898,7900,7902],{"class":68,"line":283},[66,7877,462],{"class":184},[66,7879,200],{"class":72},[66,7881,467],{"class":171},[66,7883,206],{"class":72},[66,7885,472],{"class":337},[66,7887,188],{"class":72},[66,7889,477],{"class":72},[66,7891,480],{"class":439},[66,7893,443],{"class":72},[66,7895,446],{"class":76},[66,7897,200],{"class":72},[66,7899,489],{"class":76},[66,7901,454],{"class":72},[66,7903,178],{"class":72},[66,7905,7906,7908,7910,7912,7914,7916,7918,7920,7922,7924,7926,7928,7930,7933,7935,7937,7939,7941],{"class":68,"line":306},[66,7907,499],{"class":184},[66,7909,200],{"class":72},[66,7911,504],{"class":171},[66,7913,206],{"class":72},[66,7915,209],{"class":72},[66,7917,7551],{"class":212},[66,7919,209],{"class":72},[66,7921,188],{"class":72},[66,7923,518],{"class":184},[66,7925,200],{"class":72},[66,7927,523],{"class":171},[66,7929,206],{"class":72},[66,7931,7932],{"class":337},"16",[66,7934,531],{"class":72},[66,7936,518],{"class":184},[66,7938,200],{"class":72},[66,7940,538],{"class":171},[66,7942,541],{"class":72},[66,7944,7945],{"class":68,"line":344},[66,7946,567],{"class":72},[66,7948,7949],{"class":68,"line":370},[66,7950,573],{"class":72},[66,7952,7953],{"class":68,"line":395},[66,7954,84],{"emptyLinePlaceholder":83},[66,7956,7957,7959,7961,7963,7965,7967,7969,7971,7973,7975,7977,7979,7981,7983],{"class":68,"line":401},[66,7958,7564],{"class":184},[66,7960,200],{"class":72},[66,7962,7569],{"class":171},[66,7964,7572],{"class":72},[66,7966,7575],{"class":171},[66,7968,206],{"class":72},[66,7970,209],{"class":72},[66,7972,7582],{"class":212},[66,7974,209],{"class":72},[66,7976,188],{"class":72},[66,7978,7462],{"class":72},[66,7980,7591],{"class":212},[66,7982,209],{"class":72},[66,7984,157],{"class":72},[66,7986,7987,7989,7991,7993,7995,7997,8000,8002,8004,8006,8008,8010,8012],{"class":68,"line":406},[66,7988,222],{"class":90},[66,7990,191],{"class":184},[66,7992,194],{"class":72},[66,7994,414],{"class":184},[66,7996,200],{"class":72},[66,7998,7999],{"class":171},"Render",[66,8001,206],{"class":72},[66,8003,1874],{"class":184},[66,8005,670],{"class":72},[66,8007,191],{"class":184},[66,8009,227],{"class":72},[66,8011,230],{"class":72},[66,8013,178],{"class":72},[66,8015,8016,8018,8020,8022,8024,8026,8028,8030,8032,8034,8036,8038],{"class":68,"line":425},[66,8017,7629],{"class":184},[66,8019,200],{"class":72},[66,8021,7634],{"class":171},[66,8023,206],{"class":72},[66,8025,1874],{"class":184},[66,8027,188],{"class":72},[66,8029,7643],{"class":184},[66,8031,200],{"class":72},[66,8033,7634],{"class":171},[66,8035,7650],{"class":72},[66,8037,7653],{"class":337},[66,8039,157],{"class":72},[66,8041,8042],{"class":68,"line":459},[66,8043,256],{"class":72},[66,8045,8046],{"class":68,"line":496},[66,8047,704],{"class":72},[18,8049,8050,8051,8053,8054,8057,8058,8060,8061,8063],{},"3 行のカーソルコードが 3 コールのビルダーコードに置き換わる。構造はソースコードに現れる — ",[45,8052,1934],{}," の呼び出し順の中に隠れない。CJK なら ",[45,8055,8056],{},"gpdf.WithFont(\"NotoSansJP\", ttfBytes)"," を追加するだけ。",[45,8059,2570],{}," も、ファイルシステムのパスも、UTF-8 フラグもいらない。詳細は ",[21,8062,6589],{"href":1221}," を参照。",[18,8065,8066,8069,8070,8072],{},[21,8067,8068],{"href":2906},"gofpdf 移行ガイド"," にはテーブル・繰り返しヘッダ/フッタ・ページ番号・絶対位置指定の before/after がさらに 5 つある。そこに書いてあることはすべて ",[45,8071,1431],{}," ユーザーにそのまま当てはまる — import パスを変えるだけ。",[14,8074,8075],{"id":8075},"ベンチマークの実像",[18,8077,8078,8079,8082],{},"「速い」は主張するのが簡単で、裏付けるのは難しい。下の表は ",[45,8080,8081],{},"gpdf/_benchmark/benchmark_test.go"," の結果 — Apple M1、Go 1.25。ワークロードは実運用のコードが実際にやっていること — どれかのライブラリを良く見せるために選んだマイクロベンチではない。",[738,8084,8085,8101],{},[741,8086,8087],{},[744,8088,8089,8092,8094,8096,8099],{},[747,8090,8091],{},"ベンチマーク",[747,8093,26],{},[747,8095,1428],{},[747,8097,8098],{},"gopdf",[747,8100,1437],{},[756,8102,8103,8118,8134,8149],{},[744,8104,8105,8108,8112,8114,8116],{},[761,8106,8107],{},"単一ページ (hello)",[761,8109,8110],{},[29,8111,1341],{},[761,8113,1451],{},[761,8115,1457],{},[761,8117,1460],{},[744,8119,8120,8123,8127,8129,8131],{},[761,8121,8122],{},"4×10 明細テーブル",[761,8124,8125],{},[29,8126,1345],{},[761,8128,1472],{},[761,8130,1478],{},[761,8132,8133],{},"8.6 ms",[744,8135,8136,8138,8142,8144,8146],{},[761,8137,1486],{},[761,8139,8140],{},[29,8141,1349],{},[761,8143,1357],{},[761,8145,8133],{},[761,8147,8148],{},"19.8 ms",[744,8150,8151,8154,8158,8160,8162],{},[761,8152,8153],{},"複雑 CJK 請求書",[761,8155,8156],{},[29,8157,1511],{},[761,8159,1514],{},[761,8161,1520],{},[761,8163,8164],{},"10.4 ms",[18,8166,8167,8168,8171],{},"単一ページ 13 µs なら 1 コアで毎秒 ~75,000 枚。明細付き請求書 108 µs なら毎秒 ~9,000 枚。",[29,8169,8170],{},"ここでの論点はベンチマーク自慢ではない"," — PDF 生成をキャッシュすべきか非同期キューに逃がすべきかを悩まなくていい、というだけ。ほとんどのワークロードではリクエストパスで生成しても間に合う。",[18,8173,8174,8175,8177],{},"テーブルベンチで Maroto v2 が遅く出るのは、下に ",[45,8176,1431],{}," を持ってその上に独自レイアウトパスを足しているから。Maroto の API 批判ではない — API は良い — fpdf 基盤に乗る構造的なコストがここに出ているだけ。Maroto v3 で fpdf 依存が外れれば、この列の数字は変わるはず。",[18,8179,8180],{},"100 ページベンチは少し掘り下げる価値がある。gpdf のストリーミング書き込みは、行をレイアウトしながら内容を出力していく。gofpdf はページごとにより多くの状態をバッファする。ページング主体のワークロード (月次レポート、カタログ、コンプライアンス書類) では、書類サイズが大きくなると差は「分 vs 秒」の領域になる。",[14,8182,8184,8185,8188],{"id":8183},"gpdf-を-選ぶべきでない-場合","gpdf を ",[6712,8186,8187],{},"選ぶべきでない"," 場合",[18,8190,8191],{},"移行記事は「移らない方が良いケース」に必ず答えるべき。正直に書く:",[1109,8193,8194,8207,8216,8227],{},[882,8195,8196,8199,8200,8203,8204,8206],{},[29,8197,8198],{},"AcroForm / 入力可能フォーム","。生成した PDF を Acrobat で開いてユーザーに入力させる用途なら、gpdf のフォームフィールド対応はまだ最小限。",[45,8201,8202],{},"unidoc"," の方が完成度が高く、",[45,8205,1434],{}," には部分対応がある。将来リリースで埋めるつもりだが、今日時点では穴。",[882,8208,8209,1149,8212,8215],{},[29,8210,8211],{},"任意のベクターパスと複雑な描画",[45,8213,8214],{},"c.Line()"," は列の中の水平線を引く。ベジェ、カスタムパス、グラデーション塗りがチャートや技術図で必要なら、gpdf は今届かない。(事前レンダリングしたチャート画像の埋め込みは問題ない — ここで言っているのは描画プリミティブの話。)",[882,8217,8218,8223,8224,8226],{},[29,8219,8220,8222],{},[45,8221,1930],{}," を多用した既存の gofpdf コードベース","。2,000 行のカーソル操作なら、移行は置換ではなく書き直しに近い。書き直した後のコードはほぼ必ず短くなるが、「ほぼ必ず」は締切が迫った日には冷たい慰め。",[21,8225,2830],{"href":2906}," には工数の見積もりを正直に書いた。",[882,8228,8229,8232,8233,8236,8237,8239],{},[29,8230,8231],{},"フル CSS 対応の HTML → PDF を今すぐ必要としている","。gpdf には ",[45,8234,8235],{},"gpdf-pro"," アドオンで HTML サブセットがあるが、Chromium との完全な CSS パリティは目標ではない。テンプレートが複雑な React コンポーネントなら、",[45,8238,6985],{}," や商用 API の方が直接的。",[18,8241,8242],{},"上のどれも刺さらないなら gpdf がデフォルト。どれかが刺さるなら、両方のライブラリを併存させる — 新しい PDF は gpdf、エッジケースは既存のものに残して、gpdf が追いついたら移す。",[14,8244,8245],{"id":8245},"コンプライアンスの角度",[18,8247,8248,8249,8252],{},"エコシステム記事でもう少し語られてよい論点: ",[29,8250,8251],{},"アーカイブされた依存は SOC 2 や ISO 27001 の監査で指摘対象になる","。監査担当は、サプライチェーンのサードパーティコードが積極的にメンテされているかを知りたい。「2021 年にアーカイブ」は指摘、「2025 年にアーカイブ」も指摘、「社内でフォーク」はゼロデイにどう対応するかのフォローアップ質問を誘発する。",[18,8254,8255,8256,1149,8259,8261],{},"大きめの会社のセキュリティレビューを通したチームから、「gpdf の安定 v1 はいつか」と静かに聞かれるのは主にこれが理由。答えは: ",[29,8257,8258],{},"すでに来ている",[45,8260,129],{}," はタグ付きの semver で、v1 の API 表面は凍結済み。プロジェクトにはセキュリティ窓口、責任ある開示ポリシー、Go 1.22〜1.26 を CI で回す体制がある。",[18,8263,8264,8265,8268,8269,1149],{},"監査の ",[6712,8266,8267],{},"ため"," に移行するのではない。",[29,8270,8271],{},"監査が移行を要求してくる前に動くのが目的",[14,8273,2820],{"id":2819},[18,8275,8276,8279,8280,8282,8283,8285],{},[29,8277,8278],{},"\"モダンな Go PDF スタック\" は gpdf 単独か、複数ライブラリの組み合わせか?","\nほとんどのチームでは gpdf 単独。ドキュメント生成、CJK、テーブル、グリッド、ページング、出力まで 1 つでカバーする。入力可能フォームが要件のチームは、その種類のドキュメントにだけ ",[45,8281,1434],{}," または ",[45,8284,8202],{}," を併用する。チャート重めのエクスポートが多いチームは、チャートを PNG に事前レンダリングして埋め込む。ここでの「スタック」は層状アーキテクチャではなく、短いリストの意味。",[18,8287,8288,8291],{},[29,8289,8290],{},"移行期間中、gpdf と go-pdf/fpdf を並行運用できるか?","\nできる。import パスも型も別物。新しいエンドポイントを gpdf に向けて、古いのは時間ができるまで go-pdf/fpdf に残せばいい。ランタイム上の衝突はない。",[18,8293,8294,8297,8298,8301],{},[29,8295,8296],{},"go-pdf/fpdf v3 や新しいフォークが出る可能性は?","\nあるかもしれない。gpdf の賭けは「フォークが永遠にアーカイブのままになる」ではなく、",[29,8299,8300],{},"アーキテクチャが今日作るものにスケールしない"," という方。新しいフォークがレイアウトモデルを書き直すなら、それは fpdf より gpdf に近い。",[18,8303,8304,8310,8311,5330,8314,5330,8317,8320],{},[29,8305,8306,8307,8309],{},"モダンな代替として ",[45,8308,1434],{}," は?","\n本当にメンテされていてゼロ依存。API は座標レベル — ",[45,8312,8313],{},"SetX",[45,8315,8316],{},"SetY",[45,8318,8319],{},"CellWithOption"," — なので、フォームオーバーレイや固定テンプレートには向く。テーブルや繰り返しクロームを持った請求書類のドキュメントでは、結局上にレイアウトヘルパーを書くことになり、gofpdf ユーザーが落ちた同じ穴に戻る。gpdf と gopdf は本当は競合していない — 隣接する問題を解いている。",[18,8322,8323,8326,8328],{},[29,8324,8325],{},"gpdf に商用/ホスト版はあるか?",[45,8327,2601],{}," を準備中 — JSON テンプレートを POST して PDF が返ってくるホスト型 API。まだ公開していない。ローンチ時にはこのブログで記事を出す。OSS ライブラリは引き続き MIT・ゼロ依存・単独で有用なままにする。",[18,8330,8331,8334],{},[29,8332,8333],{},"ロードマップの優先順位は?","\n2026-04 時点の公開ロードマップ: (1) AcroForm フォームフィールド、(2) フル PDF/A-3 コンプライアンス、(3) gpdf-pro の HTML→PDF カバレッジ拡大、(4) RTL テキスト対応 (アラビア語・ヘブライ語)。優先順位へのフィードバックは GitHub issue に。",[14,8336,1241],{"id":1240},[18,8338,6609],{},[57,8340,8341],{"className":1247,"code":1248,"language":1249,"meta":62,"style":62},[45,8342,8343],{"__ignoreMap":62},[66,8344,8345,8347,8349],{"class":68,"line":69},[66,8346,61],{"class":76},[66,8348,1258],{"class":212},[66,8350,1261],{"class":212},[18,8352,8353,1268,8356],{},[21,8354,2885],{"href":23,"rel":8355},[25],[21,8357,1273],{"href":1271,"rel":8358},[25],[14,8360,2892],{"id":2892},[1109,8362,8363,8369,8374,8379],{},[882,8364,8365,8368],{},[21,8366,8367],{"href":2906},"gofpdf がアーカイブされた。gpdf への移行方法。"," — API の逐一マッピング",[882,8370,8371,8373],{},[21,8372,6595],{"href":2899}," — より深いヘッドトゥヘッドのベンチとフィーチャーグリッド",[882,8375,8376,8378],{},[21,8377,5317],{"href":6666}," — カーソル操作を置き換えるビルダー idiom",[882,8380,8381,3983,8383,8385],{},[21,8382,6589],{"href":1221},[45,8384,2570],{}," ダンスなしの CJK",[1275,8387,8388],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}",{"title":62,"searchDepth":80,"depth":80,"links":8390},[8391,8392,8393,8394,8395,8396,8397,8398,8399,8401,8402,8403,8404],{"id":1334,"depth":80,"text":1335},{"id":6703,"depth":80,"text":6703},{"id":6755,"depth":80,"text":6756},{"id":6801,"depth":80,"text":6801},{"id":6843,"depth":80,"text":6844},{"id":7068,"depth":80,"text":7069},{"id":7320,"depth":80,"text":7321},{"id":8075,"depth":80,"text":8075},{"id":8183,"depth":80,"text":8400},"gpdf を 選ぶべきでない 場合",{"id":8245,"depth":80,"text":8245},{"id":2819,"depth":80,"text":2820},{"id":1240,"depth":80,"text":1241},{"id":2892,"depth":80,"text":2892},"jung-kurt/gofpdf は 2021 年、go-pdf/fpdf は 2025 年にアーカイブ。2026 年に選ぶべき Go PDF ライブラリは gpdf — 理由とトレードオフ。",{},"/ja/blog/go-pdf-fpdf-archived",{"title":6674,"description":8405},"ja/blog/006.go-pdf-fpdf-archived",[8411,2949,2947],"migration","FlKmSMaPEvZC9LbkygHmc7wZXe6qRJq7VRG2QMefEjM",{"id":8414,"title":4001,"author":8415,"body":8416,"date":5803,"description":10082,"draft":1292,"extension":1293,"howTo":10083,"image":1316,"meta":10107,"navigation":83,"path":4000,"seo":10108,"stem":10109,"tags":10110,"updated":1316,"__hash__":10111},"blogJa/ja/blog/007.japanese-pdf-in-go.md",{"name":8,"url":9},{"type":11,"value":8417,"toc":10068},[8418,8420,8439,8442,8445,8451,8462,8465,8469,8480,8518,8530,8540,8544,8547,8662,8665,8671,8681,8684,8692,9508,9511,9562,9571,9575,9584,9587,9594,9640,9653,9656,9680,9697,9701,9704,9710,9713,9727,9737,9818,9824,9826,9829,9839,9848,9872,9886,9889,9893,9900,9910,9919,9933,9940,9942,9967,9973,9983,9997,10007,10016,10018,10021,10033,10041,10043,10066],[14,8419,1335],{"id":1334},[18,8421,8422,8423,8425,8426,3559,8429,8431,8432,8435,8436,1149],{},"Go の PDF に ",[45,8424,3715],{}," と書いたら豆腐 □□□□□ が 5 つ並んだ、という症状の直し方はリライトではなくセットアップ 2 行。TTF を読んで ",[45,8427,8428],{},"gpdf.WithFont",[45,8430,277],{}," に渡し、あとは日本語を書く。",[29,8433,8434],{},"gpdf はグリフテーブルを自動的にサブセット化する","ので、出力には実際に使った文字のグリフだけが載る — 5 MB のフルフォントではなく 30 KB 前後。この記事はその全景図: なぜ Go で日本語 PDF は妙に難しかったのか、2026 年の現実的な選択肢 4 つ、動くコード、フォントサブセット化の内部、混植の実務、そして",[29,8437,8438],{},"まだ解けていない部分",[14,8440,8441],{"id":8441},"なぜこの記事を書くのか",[18,8443,8444],{},"Go で日本語 PDF を吐くのは本来 5 分の仕事だ。多くのチームではそれが 1 日半かかっている。",[18,8446,8447,8448,8450],{},"よくあるのはこういう流れ: ",[45,8449,2570],{}," を呼び出してみる → 出力 PDF には空の四角 (豆腐) が並ぶ → シニアエンジニアが半日使って、フォントパスなのかサブセットフラグなのか CMap なのか UTF-8 フラグなのか PDF ビューアなのかを絞り込む。夕方には Slack に「なぜ漢字がまだ壊れてるのか」というスレッドが立ち、翌日には誰もが後悔するヘルパー関数が 3 つ追加された PR が出ている。",[18,8452,8453,8454,8457,8458,8461],{},"根本原因はそのどれでもない。",[29,8455,8456],{},"Go で一番長く生きている PDF ライブラリが、2002 年の PHP と Latin-1 前提で設計された","ことと、それ以降に書かれた日本語チュートリアルのほぼ全てがその負債と戦ってきたこと。この記事は 2026 年版、ゼロからやる場合に本当に動くやり方と、",[29,8459,8460],{},"今なお難しい部分","を正直に書く。",[18,8463,8464],{},"本記事のコードは gpdf v1.x (2026-04 時点) で動作確認済み。ベンチ数値は Apple M1 + Go 1.25。",[14,8466,8468],{"id":8467},"豆腐文字問題を-90-秒で","豆腐文字問題を 90 秒で",[18,8470,8471,8472,8475,8476,8479],{},"PDF は Unicode を知らない。PDF が知っているのは ",[29,8473,8474],{},"グリフ ID"," — フォントに埋め込まれたグリフテーブルへの整数インデックス。",[45,8477,8478],{},"\"こんにちは\""," を PDF に書くには、誰かが以下を全部やる必要がある:",[879,8481,8482,8491,8497,8503],{},[882,8483,8484,8487,8488,8490],{},[29,8485,8486],{},"TTF を解析"," して、各コードポイントに対応するグリフ ID を ",[45,8489,857],{}," サブテーブルから引く。",[882,8492,8493,8496],{},[29,8494,8495],{},"ToUnicode CMap を書き出す"," — ユーザーがコピー・検索したときに、グリフからテキストへ戻せるように。",[882,8498,8499,8502],{},[29,8500,8501],{},"サブセット化","。Noto Sans JP の 2 万グリフを全部埋め込まない。",[882,8504,8505,3983,8508,851,8511,851,8514,8517],{},[29,8506,8507],{},"埋め込み",[45,8509,8510],{},"name",[45,8512,8513],{},"OS/2",[45,8515,8516],{},"head"," テーブルとエンコーディングオブジェクトを正しく接続した形で。",[18,8519,8520,8521,1155,8523,8525,8526,8529],{},"このどれかが抜けるか間違うと、PDF ビューアはコードポイントに対応するグリフを見つけられず豆腐を描く。アーカイブ済みの ",[45,8522,1353],{},[45,8524,1431],{}," 系統は、これら全てを ",[29,8527,8528],{},"単一バイトフォント前提の内部モデル"," に後付けしてきた — 2002 年のオリジナル FPDF は Latin-1 しか知らなかったからだ。セットアップが脆いのも、出力がサブセットではなくフルフォントを埋め込みがちなのも、OS や PDF ビューアによって壊れ方が変わるのもそのせい。",[18,8531,8532,8533,8536,8537,8539],{},"gpdf は CJK を",[29,8534,8535],{},"ファーストクラスの用例","として扱う。TTF サブセッタはコアパッケージに同梱。ToUnicode CMap は自動で書かれる。単一バイトフォントの過去互換層がないので、",[45,8538,2570],{}," ダンスもない。",[14,8541,8543],{"id":8542},"_2026-年の現実的な選択肢-4-つ","2026 年の現実的な選択肢 4 つ",[18,8545,8546],{},"コードを書く前に、正直な勢力図。「日本語対応」は「正しい TTF を渡せば豆腐もクラッシュもなしに任意の日本語を描ける」の意味で使う。",[738,8548,8549,8569],{},[741,8550,8551],{},[744,8552,8553,8556,8558,8561,8564,8567],{},[747,8554,8555],{},"選択肢",[747,8557,5164],{},[747,8559,8560],{},"依存",[747,8562,8563],{},"CJK 経路",[747,8565,8566],{},"300 字 PDF のサイズ",[747,8568,6160],{},[756,8570,8571,8594,8616,8638],{},[744,8572,8573,8578,8580,8583,8588,8591],{},[761,8574,8575,8577],{},[45,8576,1431],{}," (2025 archived)",[761,8579,6885],{},[761,8581,8582],{},"stdlib",[761,8584,8585,8587],{},[45,8586,2570],{}," 後付け",[761,8589,8590],{},"約 5 MB (フル埋込)",[761,8592,8593],{},"Latin-1 コアに後付け。サブセットはオプトインかつ不完全。",[744,8595,8596,8600,8602,8604,8610,8613],{},[761,8597,8598],{},[45,8599,1434],{},[761,8601,6885],{},[761,8603,8582],{},[761,8605,8606,8609],{},[45,8607,8608],{},"AddTTFFont"," + 手動",[761,8611,8612],{},"約 3 MB",[761,8614,8615],{},"低レベル。座標を自分で書く。サブセットはあるが自分で叩く。",[744,8617,8618,8622,8624,8629,8632,8635],{},[761,8619,8620,6986],{},[45,8621,6985],{},[761,8623,6991],{},[761,8625,8626],{},[29,8627,8628],{},"Chromium バイナリ",[761,8630,8631],{},"ブラウザ経由 (ネイティブ)",[761,8633,8634],{},"可変",[761,8636,8637],{},"HTML/CSS。コンテナにフォントを入れる必要あり。イメージ 500 MB+。",[744,8639,8640,8644,8646,8651,8654,8659],{},[761,8641,8642],{},[45,8643,26],{},[761,8645,6885],{},[761,8647,8648],{},[29,8649,8650],{},"stdlib のみ",[761,8652,8653],{},"ネイティブ、自動サブセット",[761,8655,8656],{},[29,8657,8658],{},"約 30 KB",[761,8660,8661],{},"純 Go。ビルダー API。ToUnicode CMap を自動出力。",[18,8663,8664],{},"2 点強調したい。",[18,8666,8667,8670],{},[29,8668,8669],{},"「フル埋込」と「自動サブセット」の 160 倍の差は誤差ではない。"," 10 明細の EC 請求書 PDF で使う日本語グリフは、ユニークで多くても 120 字程度。毎回の請求書にフル Noto Sans JP (5.1 MB) を埋め込むなら、年末までに同じ 5 MB のグリフデータがオブジェクトストレージに 1,000 万回コピーされる。サブセット埋込なら、使ったグリフだけが乗る。",[18,8672,8673,8676,8677,8680],{},[29,8674,8675],{},"「chromedp で動く」は事実だが、最も高価な答え","。すでにスクリーンショット用にヘッドレス Chrome の艦隊を運用しているチームなら、それに PDF を相乗りさせるのはアリ。そうでないチームが ",[29,8678,8679],{},"日本語を印字するためだけに"," Chromium を立てるのは、40 行の Go で解ける問題に対して過剰なインフラだ。",[14,8682,8683],{"id":8683},"動く最短経路",[18,8685,8686,8687,8689,8690,1149],{},"まずこれを試す。完全形 — コピーして ",[45,8688,710],{}," で保存、TTF 2 本を隣に置いて ",[45,8691,714],{},[57,8693,8695],{"className":59,"code":8694,"language":61,"meta":62,"style":62},"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",[45,8696,8697,8703,8707,8713,8721,8729,8733,8741,8749,8757,8761,8765,8775,8802,8814,8828,8832,8860,8872,8886,8890,8894,8908,8926,8956,8979,9002,9024,9028,9032,9046,9070,9100,9147,9166,9170,9174,9198,9229,9261,9280,9284,9315,9358,9385,9389,9393,9397,9415,9427,9441,9445,9486,9500,9504],{"__ignoreMap":62},[66,8698,8699,8701],{"class":68,"line":69},[66,8700,73],{"class":72},[66,8702,77],{"class":76},[66,8704,8705],{"class":68,"line":80},[66,8706,84],{"emptyLinePlaceholder":83},[66,8708,8709,8711],{"class":68,"line":87},[66,8710,91],{"class":90},[66,8712,94],{"class":72},[66,8714,8715,8717,8719],{"class":68,"line":97},[66,8716,100],{"class":72},[66,8718,103],{"class":76},[66,8720,106],{"class":72},[66,8722,8723,8725,8727],{"class":68,"line":109},[66,8724,100],{"class":72},[66,8726,114],{"class":76},[66,8728,106],{"class":72},[66,8730,8731],{"class":68,"line":119},[66,8732,84],{"emptyLinePlaceholder":83},[66,8734,8735,8737,8739],{"class":68,"line":124},[66,8736,100],{"class":72},[66,8738,129],{"class":76},[66,8740,106],{"class":72},[66,8742,8743,8745,8747],{"class":68,"line":134},[66,8744,100],{"class":72},[66,8746,139],{"class":76},[66,8748,106],{"class":72},[66,8750,8751,8753,8755],{"class":68,"line":144},[66,8752,100],{"class":72},[66,8754,149],{"class":76},[66,8756,106],{"class":72},[66,8758,8759],{"class":68,"line":154},[66,8760,157],{"class":72},[66,8762,8763],{"class":68,"line":160},[66,8764,84],{"emptyLinePlaceholder":83},[66,8766,8767,8769,8771,8773],{"class":68,"line":165},[66,8768,168],{"class":72},[66,8770,172],{"class":171},[66,8772,175],{"class":72},[66,8774,178],{"class":72},[66,8776,8777,8780,8782,8784,8786,8788,8790,8792,8794,8796,8798,8800],{"class":68,"line":181},[66,8778,8779],{"class":184},"    regular",[66,8781,188],{"class":72},[66,8783,191],{"class":184},[66,8785,194],{"class":72},[66,8787,197],{"class":184},[66,8789,200],{"class":72},[66,8791,203],{"class":171},[66,8793,206],{"class":72},[66,8795,209],{"class":72},[66,8797,3134],{"class":212},[66,8799,209],{"class":72},[66,8801,157],{"class":72},[66,8803,8804,8806,8808,8810,8812],{"class":68,"line":219},[66,8805,222],{"class":90},[66,8807,191],{"class":184},[66,8809,227],{"class":72},[66,8811,230],{"class":72},[66,8813,178],{"class":72},[66,8815,8816,8818,8820,8822,8824,8826],{"class":68,"line":235},[66,8817,238],{"class":184},[66,8819,200],{"class":72},[66,8821,243],{"class":171},[66,8823,206],{"class":72},[66,8825,248],{"class":184},[66,8827,157],{"class":72},[66,8829,8830],{"class":68,"line":253},[66,8831,256],{"class":72},[66,8833,8834,8837,8839,8841,8843,8845,8847,8849,8851,8853,8856,8858],{"class":68,"line":259},[66,8835,8836],{"class":184},"    bold",[66,8838,188],{"class":72},[66,8840,191],{"class":184},[66,8842,194],{"class":72},[66,8844,197],{"class":184},[66,8846,200],{"class":72},[66,8848,203],{"class":171},[66,8850,206],{"class":72},[66,8852,209],{"class":72},[66,8854,8855],{"class":212},"NotoSansJP-Bold.ttf",[66,8857,209],{"class":72},[66,8859,157],{"class":72},[66,8861,8862,8864,8866,8868,8870],{"class":68,"line":264},[66,8863,222],{"class":90},[66,8865,191],{"class":184},[66,8867,227],{"class":72},[66,8869,230],{"class":72},[66,8871,178],{"class":72},[66,8873,8874,8876,8878,8880,8882,8884],{"class":68,"line":283},[66,8875,238],{"class":184},[66,8877,200],{"class":72},[66,8879,243],{"class":171},[66,8881,206],{"class":72},[66,8883,248],{"class":184},[66,8885,157],{"class":72},[66,8887,8888],{"class":68,"line":306},[66,8889,256],{"class":72},[66,8891,8892],{"class":68,"line":344},[66,8893,84],{"emptyLinePlaceholder":83},[66,8895,8896,8898,8900,8902,8904,8906],{"class":68,"line":370},[66,8897,267],{"class":184},[66,8899,194],{"class":72},[66,8901,272],{"class":184},[66,8903,200],{"class":72},[66,8905,277],{"class":171},[66,8907,280],{"class":72},[66,8909,8910,8912,8914,8916,8918,8920,8922,8924],{"class":68,"line":395},[66,8911,286],{"class":184},[66,8913,200],{"class":72},[66,8915,291],{"class":171},[66,8917,206],{"class":72},[66,8919,318],{"class":184},[66,8921,200],{"class":72},[66,8923,300],{"class":184},[66,8925,303],{"class":72},[66,8927,8928,8930,8932,8934,8936,8938,8940,8942,8944,8946,8948,8950,8952,8954],{"class":68,"line":401},[66,8929,286],{"class":184},[66,8931,200],{"class":72},[66,8933,313],{"class":171},[66,8935,206],{"class":72},[66,8937,318],{"class":184},[66,8939,200],{"class":72},[66,8941,323],{"class":171},[66,8943,206],{"class":72},[66,8945,318],{"class":184},[66,8947,200],{"class":72},[66,8949,332],{"class":171},[66,8951,206],{"class":72},[66,8953,338],{"class":337},[66,8955,341],{"class":72},[66,8957,8958,8960,8962,8964,8966,8968,8970,8972,8974,8977],{"class":68,"line":406},[66,8959,286],{"class":184},[66,8961,200],{"class":72},[66,8963,351],{"class":171},[66,8965,206],{"class":72},[66,8967,209],{"class":72},[66,8969,3249],{"class":212},[66,8971,209],{"class":72},[66,8973,188],{"class":72},[66,8975,8976],{"class":184}," regular",[66,8978,303],{"class":72},[66,8980,8981,8983,8985,8987,8989,8991,8994,8996,8998,9000],{"class":68,"line":425},[66,8982,286],{"class":184},[66,8984,200],{"class":72},[66,8986,351],{"class":171},[66,8988,206],{"class":72},[66,8990,209],{"class":72},[66,8992,8993],{"class":212},"NotoSansJP-Bold",[66,8995,209],{"class":72},[66,8997,188],{"class":72},[66,8999,1056],{"class":184},[66,9001,303],{"class":72},[66,9003,9004,9006,9008,9010,9012,9014,9016,9018,9020,9022],{"class":68,"line":459},[66,9005,286],{"class":184},[66,9007,200],{"class":72},[66,9009,377],{"class":171},[66,9011,206],{"class":72},[66,9013,209],{"class":72},[66,9015,3249],{"class":212},[66,9017,209],{"class":72},[66,9019,188],{"class":72},[66,9021,390],{"class":337},[66,9023,303],{"class":72},[66,9025,9026],{"class":68,"line":496},[66,9027,398],{"class":72},[66,9029,9030],{"class":68,"line":544},[66,9031,84],{"emptyLinePlaceholder":83},[66,9033,9034,9036,9038,9040,9042,9044],{"class":68,"line":564},[66,9035,409],{"class":184},[66,9037,194],{"class":72},[66,9039,414],{"class":184},[66,9041,200],{"class":72},[66,9043,419],{"class":171},[66,9045,422],{"class":72},[66,9047,9048,9050,9052,9054,9056,9058,9060,9062,9064,9066,9068],{"class":68,"line":570},[66,9049,428],{"class":184},[66,9051,200],{"class":72},[66,9053,433],{"class":171},[66,9055,436],{"class":72},[66,9057,440],{"class":439},[66,9059,443],{"class":72},[66,9061,446],{"class":76},[66,9063,200],{"class":72},[66,9065,451],{"class":76},[66,9067,454],{"class":72},[66,9069,178],{"class":72},[66,9071,9072,9074,9076,9078,9080,9082,9084,9086,9088,9090,9092,9094,9096,9098],{"class":68,"line":576},[66,9073,462],{"class":184},[66,9075,200],{"class":72},[66,9077,467],{"class":171},[66,9079,206],{"class":72},[66,9081,472],{"class":337},[66,9083,188],{"class":72},[66,9085,477],{"class":72},[66,9087,480],{"class":439},[66,9089,443],{"class":72},[66,9091,446],{"class":76},[66,9093,200],{"class":72},[66,9095,489],{"class":76},[66,9097,454],{"class":72},[66,9099,178],{"class":72},[66,9101,9102,9104,9106,9108,9110,9112,9114,9116,9118,9120,9122,9124,9126,9128,9130,9132,9134,9136,9138,9140,9142,9145],{"class":68,"line":581},[66,9103,499],{"class":184},[66,9105,200],{"class":72},[66,9107,504],{"class":171},[66,9109,206],{"class":72},[66,9111,209],{"class":72},[66,9113,4445],{"class":212},[66,9115,209],{"class":72},[66,9117,188],{"class":72},[66,9119,518],{"class":184},[66,9121,200],{"class":72},[66,9123,5027],{"class":171},[66,9125,206],{"class":72},[66,9127,209],{"class":72},[66,9129,8993],{"class":212},[66,9131,209],{"class":72},[66,9133,531],{"class":72},[66,9135,518],{"class":184},[66,9137,200],{"class":72},[66,9139,523],{"class":171},[66,9141,206],{"class":72},[66,9143,9144],{"class":337},"22",[66,9146,5050],{"class":72},[66,9148,9149,9151,9153,9155,9157,9159,9162,9164],{"class":68,"line":602},[66,9150,499],{"class":184},[66,9152,200],{"class":72},[66,9154,504],{"class":171},[66,9156,206],{"class":72},[66,9158,209],{"class":72},[66,9160,9161],{"class":212},"2026 年 4 月 16 日",[66,9163,209],{"class":72},[66,9165,157],{"class":72},[66,9167,9168],{"class":68,"line":615},[66,9169,567],{"class":72},[66,9171,9172],{"class":68,"line":630},[66,9173,573],{"class":72},[66,9175,9176,9178,9180,9182,9184,9186,9188,9190,9192,9194,9196],{"class":68,"line":635},[66,9177,428],{"class":184},[66,9179,200],{"class":72},[66,9181,433],{"class":171},[66,9183,436],{"class":72},[66,9185,440],{"class":439},[66,9187,443],{"class":72},[66,9189,446],{"class":76},[66,9191,200],{"class":72},[66,9193,451],{"class":76},[66,9195,454],{"class":72},[66,9197,178],{"class":72},[66,9199,9200,9202,9204,9206,9208,9211,9213,9215,9217,9219,9221,9223,9225,9227],{"class":68,"line":681},[66,9201,462],{"class":184},[66,9203,200],{"class":72},[66,9205,467],{"class":171},[66,9207,206],{"class":72},[66,9209,9210],{"class":337},"7",[66,9212,188],{"class":72},[66,9214,477],{"class":72},[66,9216,480],{"class":439},[66,9218,443],{"class":72},[66,9220,446],{"class":76},[66,9222,200],{"class":72},[66,9224,489],{"class":76},[66,9226,454],{"class":72},[66,9228,178],{"class":72},[66,9230,9231,9233,9235,9237,9239,9241,9244,9246,9248,9250,9252,9254,9256,9259],{"class":68,"line":696},[66,9232,499],{"class":184},[66,9234,200],{"class":72},[66,9236,504],{"class":171},[66,9238,206],{"class":72},[66,9240,209],{"class":72},[66,9242,9243],{"class":212},"株式会社 ABC 御中",[66,9245,209],{"class":72},[66,9247,188],{"class":72},[66,9249,518],{"class":184},[66,9251,200],{"class":72},[66,9253,523],{"class":171},[66,9255,206],{"class":72},[66,9257,9258],{"class":337},"13",[66,9260,5050],{"class":72},[66,9262,9263,9265,9267,9269,9271,9273,9276,9278],{"class":68,"line":701},[66,9264,499],{"class":184},[66,9266,200],{"class":72},[66,9268,504],{"class":171},[66,9270,206],{"class":72},[66,9272,209],{"class":72},[66,9274,9275],{"class":212},"〒 100-0001 東京都千代田区千代田 1-1",[66,9277,209],{"class":72},[66,9279,157],{"class":72},[66,9281,9282],{"class":68,"line":4629},[66,9283,567],{"class":72},[66,9285,9286,9288,9290,9292,9294,9297,9299,9301,9303,9305,9307,9309,9311,9313],{"class":68,"line":5882},[66,9287,462],{"class":184},[66,9289,200],{"class":72},[66,9291,467],{"class":171},[66,9293,206],{"class":72},[66,9295,9296],{"class":337},"5",[66,9298,188],{"class":72},[66,9300,477],{"class":72},[66,9302,480],{"class":439},[66,9304,443],{"class":72},[66,9306,446],{"class":76},[66,9308,200],{"class":72},[66,9310,489],{"class":76},[66,9312,454],{"class":72},[66,9314,178],{"class":72},[66,9316,9317,9319,9321,9323,9325,9327,9330,9332,9334,9336,9338,9340,9342,9344,9346,9348,9350,9352,9354,9356],{"class":68,"line":5902},[66,9318,499],{"class":184},[66,9320,200],{"class":72},[66,9322,504],{"class":171},[66,9324,206],{"class":72},[66,9326,209],{"class":72},[66,9328,9329],{"class":212},"合計 ¥ 128,000",[66,9331,209],{"class":72},[66,9333,188],{"class":72},[66,9335,518],{"class":184},[66,9337,200],{"class":72},[66,9339,5027],{"class":171},[66,9341,206],{"class":72},[66,9343,209],{"class":72},[66,9345,8993],{"class":212},[66,9347,209],{"class":72},[66,9349,531],{"class":72},[66,9351,518],{"class":184},[66,9353,200],{"class":72},[66,9355,7294],{"class":171},[66,9357,541],{"class":72},[66,9359,9360,9362,9364,9366,9368,9370,9373,9375,9377,9379,9381,9383],{"class":68,"line":5907},[66,9361,499],{"class":184},[66,9363,200],{"class":72},[66,9365,504],{"class":171},[66,9367,206],{"class":72},[66,9369,209],{"class":72},[66,9371,9372],{"class":212},"支払期限: 2026-05-31",[66,9374,209],{"class":72},[66,9376,188],{"class":72},[66,9378,518],{"class":184},[66,9380,200],{"class":72},[66,9382,7294],{"class":171},[66,9384,541],{"class":72},[66,9386,9387],{"class":68,"line":5938},[66,9388,567],{"class":72},[66,9390,9391],{"class":68,"line":5958},[66,9392,573],{"class":72},[66,9394,9395],{"class":68,"line":5963},[66,9396,84],{"emptyLinePlaceholder":83},[66,9398,9399,9401,9403,9405,9407,9409,9411,9413],{"class":68,"line":5994},[66,9400,584],{"class":184},[66,9402,188],{"class":72},[66,9404,191],{"class":184},[66,9406,194],{"class":72},[66,9408,414],{"class":184},[66,9410,200],{"class":72},[66,9412,597],{"class":171},[66,9414,422],{"class":72},[66,9416,9417,9419,9421,9423,9425],{"class":68,"line":6013},[66,9418,222],{"class":90},[66,9420,191],{"class":184},[66,9422,227],{"class":72},[66,9424,230],{"class":72},[66,9426,178],{"class":72},[66,9428,9429,9431,9433,9435,9437,9439],{"class":68,"line":6018},[66,9430,238],{"class":184},[66,9432,200],{"class":72},[66,9434,243],{"class":171},[66,9436,206],{"class":72},[66,9438,248],{"class":184},[66,9440,157],{"class":72},[66,9442,9443],{"class":68,"line":6023},[66,9444,256],{"class":72},[66,9446,9447,9449,9451,9453,9455,9457,9459,9461,9463,9466,9468,9470,9472,9474,9476,9478,9480,9482,9484],{"class":68,"line":6028},[66,9448,222],{"class":90},[66,9450,191],{"class":184},[66,9452,194],{"class":72},[66,9454,197],{"class":184},[66,9456,200],{"class":72},[66,9458,648],{"class":171},[66,9460,206],{"class":72},[66,9462,209],{"class":72},[66,9464,9465],{"class":212},"invoice-ja.pdf",[66,9467,209],{"class":72},[66,9469,188],{"class":72},[66,9471,662],{"class":184},[66,9473,188],{"class":72},[66,9475,667],{"class":337},[66,9477,670],{"class":72},[66,9479,191],{"class":184},[66,9481,227],{"class":72},[66,9483,230],{"class":72},[66,9485,178],{"class":72},[66,9487,9488,9490,9492,9494,9496,9498],{"class":68,"line":6034},[66,9489,238],{"class":184},[66,9491,200],{"class":72},[66,9493,243],{"class":171},[66,9495,206],{"class":72},[66,9497,248],{"class":184},[66,9499,157],{"class":72},[66,9501,9502],{"class":68,"line":6059},[66,9503,256],{"class":72},[66,9505,9506],{"class":68,"line":6091},[66,9507,704],{"class":72},[18,9509,9510],{},"全部説明するより、目に留まってほしい点だけ:",[1109,9512,9513,9529,9538,9553],{},[882,9514,9515,1149,9523,9525,9526,9528],{},[29,9516,9517,9519,9520,9522],{},[45,9518,2570],{}," もなし、UTF-8 フラグもなし、",[45,9521,504],{}," にフォントパス引数もなし",[45,9524,8428],{}," で family を登録、",[45,9527,2991],{}," は Unicode を書くだけ。配線は内部で完結。",[882,9530,9531,9534,9535,9537],{},[29,9532,9533],{},"太字は別 family、フラグではない","。これは TTF の流通形態 (Noto Sans JP Regular と Noto Sans JP Bold は ",[45,9536,8510],{}," テーブルが別の独立ファイル) に合っている。ゴシックと明朝、Source Han Sans JP Normal と Heavy なども同じパターン。",[882,9539,9540,1149,9543,1155,9546,9549,9550,1149],{},[29,9541,9542],{},"レイアウトはグリッド、カーソルではない",[45,9544,9545],{},"r.Col(7, ...)",[45,9547,9548],{},"r.Col(5, ...)"," は合計 12。幅は宣言的、x 座標は書かない。詳細は ",[21,9551,9552],{"href":6666},"gpdf の 12 カラムグリッドの仕組み",[882,9554,9555,9561],{},[29,9556,9557,9560],{},[45,9558,9559],{},"AlignRight()"," はロケール非依存","。「¥ 128,000」は「$1,280.00」と同じ書き方で右寄せできる。テキストの中身でレイアウトコードが変わらない。",[18,9563,9564,9565,9567,9568,9570],{},"できた ",[45,9566,9465],{}," を任意のビューアで開き、「株式会社 ABC 御中」を選択してテキストエディタに貼る。",[45,9569,9243],{}," と出る — 文字化けしない。これが ToUnicode CMap の仕事で、gpdf は既定で書き出す。",[14,9572,9574],{"id":9573},"フォントサブセット化-隠れたサイズ爆弾","フォントサブセット化 — 隠れたサイズ爆弾",[18,9576,9577,9578,3983,9581,1149],{},"チュートリアルが飛ばしがちな、CJK in PDF の",[29,9579,9580],{},"最重要性質",[29,9582,9583],{},"サブセット埋込",[18,9585,9586],{},"TTF はグリフアウトラインとメタデータテーブルの集合。Noto Sans JP Regular は約 17,500 グリフを含み 5.1 MB。典型的な請求書が使う日本語ユニーク文字は 60〜200 字。毎文書にフル埋込は桁単位の浪費だ。",[18,9588,9589,9590,9593],{},"サブセット埋込は",[29,9591,9592],{},"使ったグリフだけを残す","。gpdf はこれを自動でやる。上のコード例を動かして確認:",[57,9595,9597],{"className":1247,"code":9596,"language":1249,"meta":62,"style":62},"$ ls -l invoice-ja.pdf\n-rw-r--r--  1 dev  staff  34892 Apr 16 10:12 invoice-ja.pdf\n",[45,9598,9599,9613],{"__ignoreMap":62},[66,9600,9601,9604,9607,9610],{"class":68,"line":69},[66,9602,9603],{"class":76},"$",[66,9605,9606],{"class":212}," ls",[66,9608,9609],{"class":212}," -l",[66,9611,9612],{"class":212}," invoice-ja.pdf\n",[66,9614,9615,9618,9621,9624,9627,9630,9633,9635,9638],{"class":68,"line":80},[66,9616,9617],{"class":76},"-rw-r--r--",[66,9619,9620],{"class":337},"  1",[66,9622,9623],{"class":212}," dev",[66,9625,9626],{"class":212},"  staff",[66,9628,9629],{"class":337},"  34892",[66,9631,9632],{"class":212}," Apr",[66,9634,7524],{"class":337},[66,9636,9637],{"class":212}," 10:12",[66,9639,9612],{"class":212},[18,9641,9642,9643,1931,9645,9648,9649,9652],{},"34 KB。比較: 同じ文書を ",[45,9644,1431],{},[45,9646,9647],{},"AddUTF8Font(\"NotoSansJP\", \"NotoSansJP-Regular.ttf\", true)"," (第 3 引数は UTF-8 フラグ) で吐くと ",[29,9650,9651],{},"4.9 MB","。入力も出力文字も同じ、ファイルサイズは 143 倍。原因は fpdf の経路が emit 時点でサブセット化せずフォントテーブル全体を埋め込むため。",[18,9654,9655],{},"実運用への影響を具体的に:",[1109,9657,9658,9668,9674],{},[882,9659,9660,9663,9664,9667],{},[29,9661,9662],{},"秒 10 件の請求書生成"," (SaaS でよくある規模) で、サブセット差は ",[29,9665,9666],{},"0.3 MB/s と 43 MB/s"," の帯域差。ロードバランサはこれに意見を持っている。",[882,9669,9670,9673],{},[29,9671,9672],{},"コールドストレージ費用は PDF サイズに線形","。アーカイブ 500 万件 × 5 MB = 25 TB。× 30 KB = 150 GB。オブジェクトストレージ料金は月額で 4 桁 vs 2 桁のレベルで変わる。",[882,9675,9676,9679],{},[29,9677,9678],{},"メール添付"," は各社 10〜25 MB 上限。5 MB の日本語請求書 + 他の添付 + MIME エンコーディングで、普通にその天井にぶつかる。",[18,9681,9682,9683,9686,9687,9686,9690,9686,9693,9696],{},"gpdf はレンダリング時にサブセット化する。オンにするフラグはない。どのグリフが埋め込まれたかは gpdf の検証ツールで見られるが、要点はこう — ",[45,9684,9685],{},"株","・",[45,9688,9689],{},"式",[45,9691,9692],{},"会",[45,9694,9695],{},"社"," を使ったなら、その 4 グリフだけが出力に乗り、残り 17,496 は乗らない。",[14,9698,9700],{"id":9699},"混植-漢字-かな-ascii-を-1-行に","混植 — 漢字 + かな + ASCII を 1 行に",[18,9702,9703],{},"日本語テキストは単独で現れることは稀だ。実運用の 1 行はこういう形をしている:",[57,9705,9708],{"className":9706,"code":9707,"language":924},[922],"API の P95 レイテンシは 50 ms 未満です。\n",[45,9709,9707],{"__ignoreMap":62},[18,9711,9712],{},"5 スクリプトが混在: ローマ字 (ASCII Latin)、カタカナ、ひらがな、漢字 (Han)、数字。素朴な実装は ASCII 部分に間違ったフォントを当てて、プロポーショナルな日本語の横に等幅の「API」が並んで見た目が崩壊する。",[18,9714,9715,9716,9719,9720,3551,9723,9726],{},"gpdf の既定挙動は ",[29,9717,9718],{},"登録した family で全コードポイントを描く","。Noto Sans JP が既定なら、",[45,9721,9722],{},"API",[45,9724,9725],{},"50 ms"," も Noto Sans JP の Latin グリフで描かれる — Noto はこれを提供している (日本語スーパーファミリーはだいたい持っている)。結果は単一書体に見える、実際単一書体だから。",[18,9728,9729,9730,9733,9734,9736],{},"family を",[29,9731,9732],{},"意図的に混ぜたい","場合 (ASCII は condensed サンセリフ、日本語は Noto Sans JP、など) は、両方登録して ",[45,9735,2991],{}," 単位で上書き:",[57,9738,9740],{"className":59,"code":9739,"language":61,"meta":62,"style":62},"c.Text(\"API の P95 レイテンシは 50 ms 未満です。\",\n    template.FontFamily(\"NotoSansJP\"))\nc.Text(\"API latency (P95) is under 50 ms.\",\n    template.FontFamily(\"InterVariable\"))\n",[45,9741,9742,9761,9780,9799],{"__ignoreMap":62},[66,9743,9744,9746,9748,9750,9752,9754,9757,9759],{"class":68,"line":69},[66,9745,480],{"class":184},[66,9747,200],{"class":72},[66,9749,504],{"class":171},[66,9751,206],{"class":72},[66,9753,209],{"class":72},[66,9755,9756],{"class":212},"API の P95 レイテンシは 50 ms 未満です。",[66,9758,209],{"class":72},[66,9760,2059],{"class":72},[66,9762,9763,9766,9768,9770,9772,9774,9776,9778],{"class":68,"line":80},[66,9764,9765],{"class":184},"    template",[66,9767,200],{"class":72},[66,9769,5027],{"class":171},[66,9771,206],{"class":72},[66,9773,209],{"class":72},[66,9775,3249],{"class":212},[66,9777,209],{"class":72},[66,9779,5050],{"class":72},[66,9781,9782,9784,9786,9788,9790,9792,9795,9797],{"class":68,"line":87},[66,9783,480],{"class":184},[66,9785,200],{"class":72},[66,9787,504],{"class":171},[66,9789,206],{"class":72},[66,9791,209],{"class":72},[66,9793,9794],{"class":212},"API latency (P95) is under 50 ms.",[66,9796,209],{"class":72},[66,9798,2059],{"class":72},[66,9800,9801,9803,9805,9807,9809,9811,9814,9816],{"class":68,"line":97},[66,9802,9765],{"class":184},[66,9804,200],{"class":72},[66,9806,5027],{"class":171},[66,9808,206],{"class":72},[66,9810,209],{"class":72},[66,9812,9813],{"class":212},"InterVariable",[66,9815,209],{"class":72},[66,9817,5050],{"class":72},[18,9819,9820,9821,9823],{},"2 回の ",[45,9822,2991],{},"、2 family、スクリプト判定ロジックは自分で書かない。1 行内で両方を混ぜたい (同じ文中で ASCII は Inter、日本語は Noto) なら、それは gpdf v1.2 で対応予定。今の回避策はスクリプト境界で手動分割し、横並びのカラム行でレイアウトする方法。",[14,9825,8438],{"id":8438},[18,9827,9828],{},"Go で日本語 PDF の話は 95% 解けている。残り 5% を正直に書く。",[18,9830,9831,9834,9835,9838],{},[29,9832,9833],{},"縦書きはまだ未対応","。gpdf v1.x は横書きのみ。伝統的な日本語組版 — 右から左へ列、列は上から下、グリフ回転と句読点配置の特殊処理 — は描画の微調整ではなくレイアウトエンジンの深い変更で、設計案のあるオープン issue になっている。着地したら着地する。今どうしても 縦書き が必要なら (書籍や公式往来)、別ツール (Word、InDesign、pandoc + LuaLaTeX パイプライン) で縦書き PDF を作ってから ",[45,9836,9837],{},"gpdf.Merge"," で連結するのが現実解。",[18,9840,9841,1149,9844,9847],{},[29,9842,9843],{},"ルビ (振り仮名) は回避策のみ",[45,9845,9846],{},"c.Ruby(\"漢字\", \"かんじ\")"," のような基本機能はない。児童向けコンテンツや教材で必要なら、上段に小さなかな、下段に通常サイズの漢字を並べた 2 行構造で組む。動きはするが手作業で、カーニングも慎重にやる必要がある。",[18,9849,9850,9853,9854,9686,9857,9686,9860,9863,9864,9866,9867,9871],{},[29,9851,9852],{},"CJK フォント間のフォールバックは自動ではない","。ユーザー入力が JP の漢字と CN 専用字形 (",[45,9855,9856],{},"直",[45,9858,9859],{},"骨",[45,9861,9862],{},"角"," などは JP/CN で字形が微妙に違う) を混ぜるなら、手動で分割して 2 family を使う必要がある。同じ ",[45,9865,2991],{}," 内での自動フォールバックはまだ。実務でここが刺さる文書はかなり少ないが、必要なら ",[21,9868,9870],{"href":9869},"/ja/blog/","JP/CN/KR/EN 混在 PDF"," (B-070 予定) を参照。",[18,9873,9874,9877,9878,9881,9882,9885],{},[29,9875,9876],{},"PDF/A-2b 厳格モードで日本語","。gpdf は ",[45,9879,9880],{},"gpdf.WithPDFA"," で PDF/A を吐けるが、埋込グリフメタデータ・CJK ラン単位の ",[45,9883,9884],{},"ActualText","・タグ付き構造ツリーなど、厳密適合の要件は CJK ケースでまだ詰めている最中。電子帳簿保存法で長期保存する PDF なら、コミット前に veraPDF (無料) などサードパーティで検証する。",[18,9887,9888],{},"どれも一般的な用途 (請求書・レポート・明細書・領収書・証明書) のブロッカーではない。ただ本番で刺さる人がいずれいるから書いておく — 「ロードマップにあります」より「これが回避策です」のほうが誠実なので。",[14,9890,9892],{"id":9891},"コンプライアンス-日本市場の文脈","コンプライアンス: 日本市場の文脈",[18,9894,9895,9896,9899],{},"もう一つ、普段あまり書かれない話。",[29,9897,9898],{},"2026 年の日本における PDF 生成は、もはや単なるタイポグラフィ問題ではない","。2 つの制度がこれをコンプライアンス会話の中に押し込んでいる。",[18,9901,9902,9905,9906,9909],{},[29,9903,9904],{},"適格請求書 (インボイス制度)"," は、請求書に所定の項目 (登録番号・適用税率・税額内訳) と改ざん耐性のある保存を要求する。PDF が事実上のデフォルト形式であり、改ざん耐性は ",[29,9907,9908],{},"PDF デジタル署名"," — 厳密には PAdES-B-LT — に写像される。",[18,9911,9912,9915,9916,1149],{},[29,9913,9914],{},"電子帳簿保存法"," (2024 改正) は、電子で受領した請求書の保存義務を拡張した。アーカイブ PDF は所定の完全性要件を満たす必要がある。デファクト目標形式は ",[29,9917,9918],{},"PDF/A-2b または PDF/A-3b",[18,9920,9921,9922,9925,9926,9929,9930,9932],{},"両方とも ",[29,9923,9924],{},"PDF ネイティブ機能"," に寄っている — 署名、長期検証、PDF/A 埋込メタデータ。ヘッドレスブラウザ経由の HTML→PDF はどちらの要件もきれいには満たさない: Chromium の PDF 出力は PDF/A 適合ではないし、単一ステップでのデジタル署名埋込もできない。ネイティブ Go スタック (gpdf + ",[45,9927,9928],{},"gpdf/signature"," による PAdES + ",[45,9931,9880],{},") はこのチェーン全体をプロセスから出ずに 1 本のパイプラインで通せる。",[18,9934,9935,9936,9939],{},"これは本稿では ",[29,9937,9938],{},"予告にとどめる"," — 署名と PDF/A はそれぞれヒーロー記事 1 本に値する (バックログの B-067 と B-068)。ただ、日本市場で日本語 PDF スタックを今選ぶなら、署名と PDF/A をネイティブで吐けるスタックを選んでおけ。「とりあえず動く」から「監査を通る」への移行税は本物で、後から払うと高い。",[14,9941,2820],{"id":2819},[18,9943,9944,9947,9948,3551,9951,9954,9955,9958,9959,9962,9963,9966],{},[29,9945,9946],{},"サーバーやコンテナにフォントをインストールする必要は?","\nない。gpdf は TTF バイトを読む — システムフォントキャッシュを見ない。",[45,9949,9950],{},"os.ReadFile(\"NotoSansJP-Regular.ttf\")",[45,9952,9953],{},"//go:embed NotoSansJP-Regular.ttf"," は macOS / Linux / Windows、distroless コンテナ、AWS Lambda で等価に動く。",[45,9956,9957],{},"fontconfig"," も ",[45,9960,9961],{},"fc-cache -fv"," も不要。",[45,9964,9965],{},"FROM scratch"," イメージで動くのはこの性質のおかげ。",[18,9968,9969,9972],{},[29,9970,9971],{},"Noto Sans JP と Source Han Sans JP はどっちがいい?","\n同じフォント、2 ブランド。Adobe が Source Han Sans JP として公開したものを Google が Noto Sans JP として再配布している。グリフカバレッジは同一。両方 SIL Open Font License — 法務レビュー結果でどちらの経路が通しやすいかで選ぶ。gpdf のサンプルは覚えやすいという理由で Noto Sans JP を既定にしている。",[18,9974,9975,9978,9979,9982],{},[29,9976,9977],{},"游ゴシック (Yu Gothic) やヒラギノは?","\nOS 同梱の商用フォント。デプロイ先がライセンスを持つ環境なら使える (Windows Server は Yu Gothic 同梱、macOS はヒラギノ同梱) が、TTF ファイル自体の入手と、コンテナビルドでの再配布条件は各自確認が必要。オープンなデプロイには ",[29,9980,9981],{},"Noto Sans JP または IPAex ゴシック"," (どちらも自由再配布可) を勧める。",[18,9984,9985,9992,9993,9996],{},[29,9986,9987,9988,9991],{},"PDF は出るが ",[45,9989,9990],{},"Ctrl+F"," で検索が効かない","\nほぼ確実に ToUnicode CMap 問題。gpdf は既定で書き出すので、gpdf でこれが起きているならビューア名付きで issue を立ててほしい。gofpdf でこれが起きているなら、UTF-8 フラグの有効化 ",[29,9994,9995],{},"+"," ビューアが CID フォントをサポートしている確認が必要 (旧バージョンの macOS Preview.app で既知の問題あり)。Adobe Reader か Chrome を対照実験にする。",[18,9998,9999,10002,10003,10006],{},[29,10000,10001],{},"フォントにない JIS X 0213 の文字を出したい","\n出ない — 描くグリフがない。実用解は「JIS X 0213 をカバーするフォントを使う」。Noto Sans JP は BMP 全域 + JIS X 0213 第 1 水準をカバーしている。稀な異体字には最終フォールバックとして花園明朝 (Hanazono Mincho) がある。どのフォントにも無いコードポイントは、gpdf は Unicode 置換文字 (U+FFFD) を出す — 無言の豆腐ではなく ",[45,10004,10005],{},"�"," が出るので、調べるきっかけになる。",[18,10008,10009,10012,10013,10015],{},[29,10010,10011],{},"CJK は ASCII より遅い?","\nわずかに。gpdf の「complex CJK invoice」ベンチは Apple M1 で 133 µs、ASCII 4×10 テーブルが 108 µs。約 23% の上乗せで、主にグリフ検索とサブセット化のコスト。参考まで、同じ CJK ベンチで ",[45,10014,1431],{}," は 254 µs、Maroto v2 は 10.4 ms。日本語描画がサービスのボトルネックになることはまずない。",[14,10017,1241],{"id":1240},[18,10019,10020],{},"gpdf は Go の PDF 生成ライブラリ。MIT、ゼロ依存、CJK ネイティブ。",[57,10022,10023],{"className":1247,"code":1248,"language":1249,"meta":62,"style":62},[45,10024,10025],{"__ignoreMap":62},[66,10026,10027,10029,10031],{"class":68,"line":69},[66,10028,61],{"class":76},[66,10030,1258],{"class":212},[66,10032,1261],{"class":212},[18,10034,10035,1268,10038],{},[21,10036,2885],{"href":23,"rel":10037},[25],[21,10039,2889],{"href":1271,"rel":10040},[25],[14,10042,2892],{"id":2892},[1109,10044,10045,10050,10055,10060],{},[882,10046,10047,10049],{},[21,10048,1222],{"href":1221}," — 背景抜きの 3 行レシピ",[882,10051,10052,10054],{},[21,10053,1215],{"href":831}," — Regular / Bold / Medium ウェイト設定",[882,10056,10057,10059],{},[21,10058,9552],{"href":6666}," — カーソル計算を置き換えるレイアウト idiom",[882,10061,10062,10065],{},[21,10063,10064],{"href":8407},"go-pdf/fpdf もアーカイブ。Go の PDF は gpdf に移る"," — 2026 年の勢力図全体",[1275,10067,1277],{},{"title":62,"searchDepth":80,"depth":80,"links":10069},[10070,10071,10072,10073,10074,10075,10076,10077,10078,10079,10080,10081],{"id":1334,"depth":80,"text":1335},{"id":8441,"depth":80,"text":8441},{"id":8467,"depth":80,"text":8468},{"id":8542,"depth":80,"text":8543},{"id":8683,"depth":80,"text":8683},{"id":9573,"depth":80,"text":9574},{"id":9699,"depth":80,"text":9700},{"id":8438,"depth":80,"text":8438},{"id":9891,"depth":80,"text":9892},{"id":2819,"depth":80,"text":2820},{"id":1240,"depth":80,"text":1241},{"id":2892,"depth":80,"text":2892},"Go で日本語 PDF を吐く完全手順。CGO なし、Chromium なし、豆腐文字なし。フォント・サブセット・混植・縦書きの実務まで。",{"name":10084,"totalTime":10085,"tools":10086,"steps":10088},"Go で TrueType サブセット埋め込みされた日本語 PDF を生成する","PT20M",[1298,10087],"NotoSansJP-Regular.ttf と NotoSansJP-Bold.ttf (または任意の日本語対応 TTF ペア)",[10089,10092,10095,10098,10101,10104],{"name":10090,"text":10091},"gpdf を導入してフォントを用意する","go get github.com/gpdf-dev/gpdf を実行。Google Fonts から Noto Sans JP の Regular と Bold をダウンロードして main.go の隣に置く。CGO 不要、OS のフォント設定も不要。",{"name":10093,"text":10094},"起動時に TTF バイトを読み込む","両方の TTF ファイルを os.ReadFile で []byte に読み込む。バイナリに埋め込みたいなら //go:embed でも可。",{"name":10096,"text":10097},"ドキュメント構築時にフォントを登録する","gpdf.WithFont(\"NotoSansJP\", regular) と gpdf.WithFont(\"NotoSansJP-Bold\", bold) を gpdf.NewDocument に渡す。family 名は任意の識別子 — 後で参照するハンドルにすぎない。",{"name":10099,"text":10100},"日本語フォントを既定に設定する","gpdf.WithDefaultFont(\"NotoSansJP\", 11) を追加。以降の c.Text は明示的な FontFamily オプションなしで日本語フォントを使う。",{"name":10102,"text":10103},"c.Text でドキュメント木を組み立てる","page.AutoRow ブロックの中で r.Col(span, fn) を呼び、c.Text(\"こんにちは、世界。\") と書く。太字やサイズはメソッドではなく template オプション。",{"name":10105,"text":10106},"生成して動作確認する","doc.Generate() で []byte を受け取り os.WriteFile で保存。PDF を開いて文字列を選択しテキストエディタに貼り付ける — ToUnicode CMap が書き出されているのでコピー & ペーストが正しく動く。",{},{"title":4001,"description":10082},"ja/blog/007.japanese-pdf-in-go",[1324,1323,4068],"FGDEPOzv0AU64mFP2rOIx9Us40iil_t5uRy0Qdm_Bo0",{"id":10113,"title":2900,"author":10114,"body":10115,"date":11710,"description":11711,"draft":1292,"extension":1293,"howTo":1316,"image":1316,"meta":11712,"navigation":83,"path":2899,"seo":11713,"stem":11714,"tags":11715,"updated":1316,"__hash__":11716},"blogJa/ja/blog/002.go-pdf-library-showdown-2026.md",{"name":8,"url":9},{"type":11,"value":10116,"toc":11692},[10117,10119,10125,10145,10148,10154,10158,10161,10192,10195,10198,10201,10378,10389,10391,10403,10406,10432,10438,10441,10528,10537,10540,10547,10557,10563,10569,10576,10578,10588,10665,10668,10670,10724,10727,10733,10737,10775,10778,10781,10822,10825,10829,10832,11550,11558,11561,11564,11602,11604,11610,11620,11626,11632,11638,11640,11643,11655,11663,11665,11689],[14,10118,1335],{"id":1334},[18,10120,10121,10122,10124],{},"5 年前、「Go で PDF」で検索するとほぼ確実に ",[29,10123,1353],{}," にたどり着いた。今、それはアーカイブされている。コミュニティフォークの go-pdf/fpdf も同じ。実質的に選択肢は、検索結果が示唆するよりずっと少ない。",[1109,10126,10127,10133,10139],{},[882,10128,10129,10132],{},[29,10130,10131],{},"現役でメンテされている",": gpdf (このチーム), signintech/gopdf, johnfercher/maroto v2 — ただし Maroto はアーカイブ済みの gofpdf に依存している。",[882,10134,10135,10138],{},[29,10136,10137],{},"アーカイブ",": jung-kurt/gofpdf (2021), go-pdf/fpdf (2025)。",[882,10140,10141,10144],{},[29,10142,10143],{},"商用 / AGPL",": unidoc/unipdf。",[18,10146,10147],{},"この記事では現役ライブラリを 4 つのワークロードでベンチし、ライセンスと依存グラフと保守状況を横並びで並べる。用途別の選び方も最後に置く。来年も同じ構成で更新する予定。",[18,10149,10150,10151,10153],{},"バイアスの開示: 私たちは gpdf のチーム。ベンチマークコードは公開 (",[45,10152,1395],{},") している。手元でクローンして再計測してほしい。数字が違えば教えてほしい。",[14,10155,10157],{"id":10156},"go-pdf-ライブラリは-3-種類ある","「Go PDF ライブラリ」は 3 種類ある",[18,10159,10160],{},"ひとくちに「Go PDF ライブラリ」と言っても、実は違う 3 種類が同じ棚に並んでいる:",[879,10162,10163,10173,10184],{},[882,10164,10165,10168,10169,1968,10171,1149],{},[29,10166,10167],{},"低レベル PDF ライター"," — バイトを書いてプリミティブで描画する。",[45,10170,1353],{},[45,10172,1434],{},[882,10174,10175,10178,10179,1968,10182,1149],{},[29,10176,10177],{},"ライターをラップするレイアウトライブラリ"," — 宣言的に行と列を組む。",[45,10180,10181],{},"johnfercher/maroto v2",[45,10183,26],{},[882,10185,10186,10189,10190,1149],{},[29,10187,10188],{},"フルドキュメントスイート"," — 解析・署名・PDF/A・OCR・赤塗りまで含む。",[45,10191,6964],{},[18,10193,10194],{},"「一番良い Go PDF ライブラリはどれ?」の質問が Qiita や Reddit で噛み合わないのは、この 3 つが混ざったまま議論するから。以下の比較では毎節でこの区別を意識する。",[18,10196,10197],{},"ラインナップから外したもの: ヘッドレス Chromium を立ち上げる系 (go-rod, chromedp)。あれは PDF ライブラリではなく「印刷する機能を持ったブラウザ」。デザイナーから上がってくる HTML/CSS をピクセル精度で再現したいならあれが正解だが、コールドスタートもメモリも distroless 運用も全部重い。この記事の比較対象には含めない。",[14,10199,10200],{"id":10200},"スコアボード",[738,10202,10203,10228],{},[741,10204,10205],{},[744,10206,10207,10209,10212,10214,10216,10219,10222,10225],{},[747,10208,6856],{},[747,10210,10211],{},"最終リリース",[747,10213,10137],{},[747,10215,5164],{},[747,10217,10218],{},"コア依存",[747,10220,10221],{},"CJK",[747,10223,10224],{},"レイアウトグリッド",[747,10226,10227],{},"2026 年時点",[756,10229,10230,10260,10280,10303,10329,10355],{},[744,10231,10232,10237,10240,10243,10245,10250,10252,10255],{},[761,10233,10234,10236],{},[29,10235,26],{}," (このチーム)",[761,10238,10239],{},"稼働中",[761,10241,10242],{},"—",[761,10244,6885],{},[761,10246,10247],{},[29,10248,10249],{},"0",[761,10251,7016],{},[761,10253,10254],{},"12 カラム",[761,10256,10257],{},[29,10258,10259],{},"メンテ中",[744,10261,10262,10264,10266,10268,10270,10272,10275,10278],{},[761,10263,1434],{},[761,10265,10239],{},[761,10267,10242],{},[761,10269,6885],{},[761,10271,10249],{},[761,10273,10274],{},"TTF 手動",[761,10276,10277],{},"なし",[761,10279,10259],{},[744,10281,10282,10284,10286,10288,10290,10295,10297,10300],{},[761,10283,10181],{},[761,10285,10239],{},[761,10287,10242],{},[761,10289,6885],{},[761,10291,10292],{},[29,10293,10294],{},"gofpdf (アーカイブ)",[761,10296,6948],{},[761,10298,10299],{},"行/列",[761,10301,10302],{},"土台が死んだ状態",[744,10304,10305,10307,10310,10315,10317,10319,10323,10325],{},[761,10306,1353],{},[761,10308,10309],{},"2021",[761,10311,10312],{},[29,10313,10314],{},"2021-09-08",[761,10316,6885],{},[761,10318,10249],{},[761,10320,10321],{},[45,10322,2570],{},[761,10324,10277],{},[761,10326,10327],{},[29,10328,10137],{},[744,10330,10331,10333,10336,10341,10343,10345,10349,10351],{},[761,10332,1431],{},[761,10334,10335],{},"2023",[761,10337,10338],{},[29,10339,10340],{},"2025",[761,10342,6885],{},[761,10344,10249],{},[761,10346,10347],{},[45,10348,2570],{},[761,10350,10277],{},[761,10352,10353],{},[29,10354,10137],{},[744,10356,10357,10359,10361,10363,10368,10371,10374,10376],{},[761,10358,6964],{},[761,10360,10239],{},[761,10362,10242],{},[761,10364,10365],{},[29,10366,10367],{},"AGPL-3.0 / 商用ライセンス",[761,10369,10370],{},"多数",[761,10372,10373],{},"あり",[761,10375,10277],{},[761,10377,6971],{},[18,10379,10380,10381,10384,10385,10388],{},"押さえるべき点が 3 つ。",[29,10382,10383],{},"半数がアーカイブ済み","。Maroto は自身は現役だが土台が死んでいる — ビルドは通っても供給網の問題がある。そして AGPL を受け入れられない組織にとって unidoc の選定は技術の問題ではなく ",[29,10386,10387],{},"ライセンス調達の問題"," になる。",[14,10390,8091],{"id":8091},[18,10392,10393,10394,10399,10400,10402],{},"コード: gpdf リポジトリの ",[21,10395,10397],{"href":1391,"rel":10396},[25],[45,10398,1395],{},"。環境は Apple M1 (Max, 32 GB, macOS 14.5)、Go 1.25、CGO なし。各ケースは最低 5 秒の実時間回している。",[45,10401,1413],{}," を有効化し、ns/op とアロケーション数を計測。",[18,10404,10405],{},"4 つのケースを選んだのは、現場で実際に生成されるものの形に近いから:",[879,10407,10408,10414,10420,10426],{},[882,10409,10410,10413],{},[29,10411,10412],{},"ページ 1 枚の hello world"," — 1 ページ、1 行テキスト、1 フォント。生成時固定オーバーヘッドの床。",[882,10415,10416,10419],{},[29,10417,10418],{},"4 列 10 行の請求書テーブル"," — ヘッダ行 + ボディ 10 行 + カラム整列 + 細枠。「請求書を作る」ときの形。",[882,10421,10422,10425],{},[29,10423,10424],{},"100 ページのページ付きレポート"," — ヘッダ・フッタ・ページ番号・本文の反復。ページ分割コストを測る。",[882,10427,10428,10431],{},[29,10429,10430],{},"複雑な CJK 請求書"," — 平仮名・片仮名・漢字混在、4 列 15 行の明細表、ヘッダ・フッタ・ページ番号、NotoSansJP の TrueType サブセット埋め込み。",[18,10433,10434,10435,10437],{},"含めなかったもの: ",[45,10436,6964],{},"。バイナリがライセンスゲートされており、彼らの公開ベンチの方法論を一般公開リポジトリに移植すると誤解を招く。unidoc を検討中なら、公式が自社ベンチを公開しているのでそちらを参照してほしい。",[1948,10439,10440],{"id":10440},"結果",[738,10442,10443,10459],{},[741,10444,10445],{},[744,10446,10447,10449,10451,10453,10455,10457],{},[747,10448,1423],{},[747,10450,26],{},[747,10452,1434],{},[747,10454,1437],{},[747,10456,1428],{},[747,10458,1431],{},[756,10460,10461,10478,10495,10512],{},[744,10462,10463,10466,10470,10472,10474,10476],{},[761,10464,10465],{},"1 ページ hello world",[761,10467,10468],{},[29,10469,1341],{},[761,10471,1457],{},[761,10473,1460],{},[761,10475,1451],{},[761,10477,1454],{},[744,10479,10480,10483,10487,10489,10491,10493],{},[761,10481,10482],{},"4×10 請求書テーブル",[761,10484,10485],{},[29,10486,1345],{},[761,10488,1478],{},[761,10490,1481],{},[761,10492,1472],{},[761,10494,1475],{},[744,10496,10497,10500,10504,10506,10508,10510],{},[761,10498,10499],{},"100 ページのレポート",[761,10501,10502],{},[29,10503,1349],{},[761,10505,1481],{},[761,10507,1501],{},[761,10509,1493],{},[761,10511,1496],{},[744,10513,10514,10516,10520,10522,10524,10526],{},[761,10515,10430],{},[761,10517,10518],{},[29,10519,1511],{},[761,10521,1520],{},[761,10523,1523],{},[761,10525,1514],{},[761,10527,1517],{},[18,10529,10530,10531,10533,10534,10536],{},"go-pdf/fpdf の CJK 欄が ",[45,10532,1517],{}," なのは、検証したバージョンで ",[45,10535,2570],{}," 経路が NotoSansJP の cmap format 12 テーブル読み取り時にパニックするため。パッチで直せるが、フォーク自身がアーカイブされておりリリースは来ない。",[1948,10538,10539],{"id":10539},"数字の読み方",[18,10541,10542,10543,10546],{},"順位はワークロードを跨いでも安定している。すべてのケースで gpdf は 2 番手の ",[29,10544,10545],{},"10〜30 倍速い","。これは奇抜なトリックではなく、3 つの設計が積み重なった結果:",[18,10548,10549,10552,10553,10556],{},[29,10550,10551],{},"1 パスのレイアウト","。gpdf は中間 AST を構築してから直列化する、という段階を踏まない。ビルダーが解決した時点で直接 PDF content stream に書き出すため、他ライブラリより約半分のアロケーションで済む。100 ページベンチで 683 µs と 19,800 µs の差が出るのは、チューニングの差ではなく ",[29,10554,10555],{},"アーキテクチャが違う"," ため。",[18,10558,10559,10562],{},[29,10560,10561],{},"ホットパスにリフレクションを置かない","。レイアウトエンジンが触る型はすべて具象型。個々は微最適化だが、100 ページレポートのプロファイルではインタフェースのディスパッチが無視できない重さで出てくる。そこを避けてある。",[18,10564,10565,10568],{},[29,10566,10567],{},"cmap をキャッシュする TrueType サブセッター","。gofpdf はグリフ参照のたびに cmap テーブルを読み直す。gpdf は一度解決してキャッシュする。Latin だけならほぼ差が出ないが、CJK では 1 段落が漢字 + 仮名 + 約物で 150 グリフを触ることがあり、「同期生成で間に合う」と「キューに回す」の境目になる。",[18,10570,10571,10572,10575],{},"ベンチ表だけ見ると見落とす注意点を 1 つ: ",[29,10573,10574],{},"絶対速度は思ったほど重要ではない","。重要なのは「リクエストパスで同期生成しても大丈夫か」の閾値。hello world ならこの閾値はどのライブラリも越える。100 ページレポートで越えるのは gpdf だけ。最大サイズが 1 ページ領収書なら、現役 4 つはどれでも問題ない。API の使い心地とライセンスで選んで良い。",[14,10577,8560],{"id":8560},[18,10579,10580,10581,10584,10585,10587],{},"それぞれを ",[45,10582,10583],{},"go get"," 直後に ",[45,10586,7104],{}," した結果:",[738,10589,10590,10602],{},[741,10591,10592],{},[744,10593,10594,10596,10599],{},[747,10595,6856],{},[747,10597,10598],{},"外部モジュール",[747,10600,10601],{},"推移的にアーカイブ依存あり",[756,10603,10604,10617,10625,10635,10643,10655],{},[744,10605,10606,10611,10615],{},[761,10607,10608,10610],{},[29,10609,26],{}," (コア)",[761,10612,10613],{},[29,10614,10249],{},[761,10616,10242],{},[744,10618,10619,10621,10623],{},[761,10620,1434],{},[761,10622,10249],{},[761,10624,10242],{},[744,10626,10627,10629,10632],{},[761,10628,1428],{},[761,10630,10631],{},"0 (ただし自身がアーカイブ)",[761,10633,10634],{},"自身",[744,10636,10637,10639,10641],{},[761,10638,1431],{},[761,10640,10631],{},[761,10642,10634],{},[744,10644,10645,10647,10652],{},[761,10646,10181],{},[761,10648,10649],{},[29,10650,10651],{},"gofpdf (2021 アーカイブ)",[761,10653,10654],{},"あり — gofpdf",[744,10656,10657,10659,10662],{},[761,10658,6964],{},[761,10660,10661],{},"多数 (画像・暗号・圧縮)",[761,10663,10664],{},"アーカイブは無し",[18,10666,10667],{},"「本番の依存にアーカイブ済みリポジトリを入れない」という社内 lint ルールを持つチームでは、今日の Maroto v2 はこれで落ちる。Maroto のメンテナは 1 年以上かけて gofpdf 依存の排除を進めていて、それが完了したらこの行は変わる。この記事を書いた時点と読んでいる時点で状態がずれている可能性があるので、判断前に Maroto リポジトリの現状を確認したほうがいい。",[14,10669,5164],{"id":5164},[738,10671,10672,10680],{},[741,10673,10674],{},[744,10675,10676,10678],{},[747,10677,6856],{},[747,10679,5164],{},[756,10681,10682,10689,10695,10701,10707,10713],{},[744,10683,10684,10687],{},[761,10685,10686],{},"gpdf (コア)",[761,10688,6885],{},[744,10690,10691,10693],{},[761,10692,1434],{},[761,10694,6885],{},[744,10696,10697,10699],{},[761,10698,10181],{},[761,10700,6885],{},[744,10702,10703,10705],{},[761,10704,1428],{},[761,10706,6885],{},[744,10708,10709,10711],{},[761,10710,1431],{},[761,10712,6885],{},[744,10714,10715,10719],{},[761,10716,10717],{},[29,10718,6964],{},[761,10720,10721],{},[29,10722,10723],{},"AGPL-3.0 または商用ライセンス",[18,10725,10726],{},"unidoc の AGPL はかなり強い側。ユーザがネットワーク越しに触るサーバで使うなら、サーバ側コードも AGPL で公開する必要がある — ほとんどのクローズドソース SaaS では実質不可。結果的に商用ライセンスが唯一の現実解になり、価格は公開されていない。営業とのやりとりが前提。",[18,10728,10729,10730,10732],{},"GitHub スター数の比較で最も見落とされるのがここ。unidoc は機能もスターも最多。だが、ほとんどの商用ユースケースで「買う」の意思決定がセットになる。unidoc を貶める意図はない — モデルは正当だし製品は優秀。ただし ",[45,10731,10583],{}," する前に把握しておくべき。",[14,10734,10736],{"id":10735},"保守状況-平易な言い方で","保守状況 (平易な言い方で)",[1109,10738,10739,10744,10749,10754,10762,10770],{},[882,10740,10741,10743],{},[29,10742,26],{}," — 主メンテナはこのチーム (gpdf-dev)。リリースは 2〜4 週間ごと、ロードマップはリポジトリ内、CI は Go 1.22〜1.26 で回している。メインリポジトリの issue 対応は数営業日以内。このプロジェクトには本気。",[882,10745,10746,10748],{},[29,10747,1434],{}," — 現役でコミット頻度は控えめ。issue は見てくれるし PR は数週間で取り込まれる。低レベル生成向け。",[882,10750,10751,10753],{},[29,10752,1437],{}," — 現役。2023 年の v2 リライトで安定。gofpdf 依存は既知で置換作業中。判断前にリポジトリを確認。",[882,10755,10756,10758,10759,10761],{},[29,10757,1428],{}," — 2021-09-08 アーカイブ。リポジトリには ",[6712,10760,6714],{}," のバナー。セキュリティパッチもバグ修正も来ない。",[882,10763,10764,10766,10767,1149],{},[29,10765,1431],{}," — 2025 年アーカイブ。README は別のライブラリの利用を推奨している。専用の移行ガイドを書いた: ",[21,10768,10769],{"href":2906},"gofpdf からの移行ガイド",[882,10771,10772,10774],{},[29,10773,6964],{}," — 現役、商用チーム、潤沢なリソース。エンタープライズサポートあり。",[14,10776,10777],{"id":10777},"選び方",[18,10779,10780],{},"機能マトリクスではなく決定木で書く。「機能最多 = 正解」とは限らないから:",[1109,10782,10783,10789,10795,10801,10807,10816],{},[882,10784,10785,10788],{},[29,10786,10787],{},"「Go コードベースで請求書・レポート・書類を生成する。MIT を好み、依存ゼロが良く、CJK が混ざることがある」"," → gpdf。",[882,10790,10791,10794],{},[29,10792,10793],{},"「カスタム図形を描く低レベル生成がメインで、小さくて安定した手動寄りのライブラリが欲しい」"," → signintech/gopdf。",[882,10796,10797,10800],{},[29,10798,10799],{},"「Maroto 的なレイアウトコードが既に稼働している」"," → gofpdf 排除が完了するまで Maroto v2 を維持、その後再評価。API 自体は悪くない。",[882,10802,10803,10806],{},[29,10804,10805],{},"「PDF/A・OCR・赤塗り・電子署名が必要で、会社が商用ライセンスを出せる」"," → unidoc/unipdf。最初にライセンスの話を詰めること。",[882,10808,10809,10812,10813,1149],{},[29,10810,10811],{},"「今も gofpdf で、動いている」"," → 今日は大丈夫。関連依存の CVE が出て詰む前に移行計画を立てる。",[21,10814,10815],{"href":2906},"移行ガイドはこちら",[882,10817,10818,10821],{},[29,10819,10820],{},"「HTML/CSS をピクセル精度で PDF 化したい」"," → 上記のどれでもない。go-rod / chromedp + ヘッドレス Chromium、コールドスタートコストは覚悟の上で。",[18,10823,10824],{},"私たちは gpdf のチームなので、1 つ目と 5 つ目の多くで gpdf が妥当と思っている — 当然のバイアスがある。ベンチコードを読んで、自分で回して、この表を鵜呑みにしないでほしい。",[14,10826,10828],{"id":10827},"_30-行で書く-gpdf-例","30 行で書く gpdf 例",[18,10830,10831],{},"「一番速い」「依存グラフが最小」は、コードが読めなければ意味がない。完全に動く請求書 1 ページ、疑似コードも省略なしで:",[57,10833,10835],{"className":59,"code":10834,"language":61,"meta":62,"style":62},"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 時間\", \"¥15,000\", \"¥600,000\"},\n                    {\"バックエンド開発\",   \"60 時間\", \"¥15,000\", \"¥900,000\"},\n                    {\"UI デザイン\",        \"20 時間\", \"¥12,000\", \"¥240,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",[45,10836,10837,10843,10847,10853,10861,10869,10873,10881,10889,10898,10906,10910,10914,10924,10938,10956,10986,10990,10994,11008,11032,11062,11101,11123,11134,11181,11191,11232,11272,11313,11318,11349,11360,11372,11392,11418,11423,11428,11432,11436,11440,11458,11470,11484,11488,11528,11542,11546],{"__ignoreMap":62},[66,10838,10839,10841],{"class":68,"line":69},[66,10840,73],{"class":72},[66,10842,77],{"class":76},[66,10844,10845],{"class":68,"line":80},[66,10846,84],{"emptyLinePlaceholder":83},[66,10848,10849,10851],{"class":68,"line":87},[66,10850,91],{"class":90},[66,10852,94],{"class":72},[66,10854,10855,10857,10859],{"class":68,"line":97},[66,10856,100],{"class":72},[66,10858,103],{"class":76},[66,10860,106],{"class":72},[66,10862,10863,10865,10867],{"class":68,"line":109},[66,10864,100],{"class":72},[66,10866,114],{"class":76},[66,10868,106],{"class":72},[66,10870,10871],{"class":68,"line":119},[66,10872,84],{"emptyLinePlaceholder":83},[66,10874,10875,10877,10879],{"class":68,"line":124},[66,10876,100],{"class":72},[66,10878,129],{"class":76},[66,10880,106],{"class":72},[66,10882,10883,10885,10887],{"class":68,"line":134},[66,10884,100],{"class":72},[66,10886,139],{"class":76},[66,10888,106],{"class":72},[66,10890,10891,10893,10896],{"class":68,"line":144},[66,10892,100],{"class":72},[66,10894,10895],{"class":76},"github.com/gpdf-dev/gpdf/pdf",[66,10897,106],{"class":72},[66,10899,10900,10902,10904],{"class":68,"line":154},[66,10901,100],{"class":72},[66,10903,149],{"class":76},[66,10905,106],{"class":72},[66,10907,10908],{"class":68,"line":160},[66,10909,157],{"class":72},[66,10911,10912],{"class":68,"line":165},[66,10913,84],{"emptyLinePlaceholder":83},[66,10915,10916,10918,10920,10922],{"class":68,"line":181},[66,10917,168],{"class":72},[66,10919,172],{"class":171},[66,10921,175],{"class":72},[66,10923,178],{"class":72},[66,10925,10926,10928,10930,10932,10934,10936],{"class":68,"line":219},[66,10927,267],{"class":184},[66,10929,194],{"class":72},[66,10931,272],{"class":184},[66,10933,200],{"class":72},[66,10935,277],{"class":171},[66,10937,280],{"class":72},[66,10939,10940,10942,10944,10946,10948,10950,10952,10954],{"class":68,"line":235},[66,10941,286],{"class":184},[66,10943,200],{"class":72},[66,10945,291],{"class":171},[66,10947,206],{"class":72},[66,10949,318],{"class":184},[66,10951,200],{"class":72},[66,10953,300],{"class":184},[66,10955,303],{"class":72},[66,10957,10958,10960,10962,10964,10966,10968,10970,10972,10974,10976,10978,10980,10982,10984],{"class":68,"line":253},[66,10959,286],{"class":184},[66,10961,200],{"class":72},[66,10963,313],{"class":171},[66,10965,206],{"class":72},[66,10967,318],{"class":184},[66,10969,200],{"class":72},[66,10971,323],{"class":171},[66,10973,206],{"class":72},[66,10975,318],{"class":184},[66,10977,200],{"class":72},[66,10979,332],{"class":171},[66,10981,206],{"class":72},[66,10983,338],{"class":337},[66,10985,341],{"class":72},[66,10987,10988],{"class":68,"line":259},[66,10989,398],{"class":72},[66,10991,10992],{"class":68,"line":264},[66,10993,84],{"emptyLinePlaceholder":83},[66,10995,10996,10998,11000,11002,11004,11006],{"class":68,"line":283},[66,10997,409],{"class":184},[66,10999,194],{"class":72},[66,11001,414],{"class":184},[66,11003,200],{"class":72},[66,11005,419],{"class":171},[66,11007,422],{"class":72},[66,11009,11010,11012,11014,11016,11018,11020,11022,11024,11026,11028,11030],{"class":68,"line":306},[66,11011,428],{"class":184},[66,11013,200],{"class":72},[66,11015,433],{"class":171},[66,11017,436],{"class":72},[66,11019,440],{"class":439},[66,11021,443],{"class":72},[66,11023,446],{"class":76},[66,11025,200],{"class":72},[66,11027,451],{"class":76},[66,11029,454],{"class":72},[66,11031,178],{"class":72},[66,11033,11034,11036,11038,11040,11042,11044,11046,11048,11050,11052,11054,11056,11058,11060],{"class":68,"line":344},[66,11035,462],{"class":184},[66,11037,200],{"class":72},[66,11039,467],{"class":171},[66,11041,206],{"class":72},[66,11043,472],{"class":337},[66,11045,188],{"class":72},[66,11047,477],{"class":72},[66,11049,480],{"class":439},[66,11051,443],{"class":72},[66,11053,446],{"class":76},[66,11055,200],{"class":72},[66,11057,489],{"class":76},[66,11059,454],{"class":72},[66,11061,178],{"class":72},[66,11063,11064,11066,11068,11070,11072,11074,11077,11079,11081,11083,11085,11087,11089,11091,11093,11095,11097,11099],{"class":68,"line":370},[66,11065,499],{"class":184},[66,11067,200],{"class":72},[66,11069,504],{"class":171},[66,11071,206],{"class":72},[66,11073,209],{"class":72},[66,11075,11076],{"class":212},"請求書 #2026-0042",[66,11078,209],{"class":72},[66,11080,188],{"class":72},[66,11082,518],{"class":184},[66,11084,200],{"class":72},[66,11086,538],{"class":171},[66,11088,7650],{"class":72},[66,11090,518],{"class":184},[66,11092,200],{"class":72},[66,11094,523],{"class":171},[66,11096,206],{"class":72},[66,11098,338],{"class":337},[66,11100,5050],{"class":72},[66,11102,11103,11105,11107,11109,11111,11113,11115,11117,11119,11121],{"class":68,"line":395},[66,11104,499],{"class":184},[66,11106,200],{"class":72},[66,11108,2129],{"class":171},[66,11110,206],{"class":72},[66,11112,318],{"class":184},[66,11114,200],{"class":72},[66,11116,332],{"class":171},[66,11118,206],{"class":72},[66,11120,5679],{"class":337},[66,11122,5050],{"class":72},[66,11124,11125,11127,11129,11132],{"class":68,"line":401},[66,11126,499],{"class":184},[66,11128,200],{"class":72},[66,11130,11131],{"class":171},"Table",[66,11133,280],{"class":72},[66,11135,11136,11139,11141,11144,11146,11149,11151,11153,11155,11158,11160,11162,11164,11167,11169,11171,11173,11176,11178],{"class":68,"line":406},[66,11137,11138],{"class":72},"                []",[66,11140,3565],{"class":1620},[66,11142,11143],{"class":72},"{",[66,11145,209],{"class":72},[66,11147,11148],{"class":212},"品目",[66,11150,209],{"class":72},[66,11152,188],{"class":72},[66,11154,7462],{"class":72},[66,11156,11157],{"class":212},"数量",[66,11159,209],{"class":72},[66,11161,188],{"class":72},[66,11163,7462],{"class":72},[66,11165,11166],{"class":212},"単価",[66,11168,209],{"class":72},[66,11170,188],{"class":72},[66,11172,7462],{"class":72},[66,11174,11175],{"class":212},"金額",[66,11177,209],{"class":72},[66,11179,11180],{"class":72},"},\n",[66,11182,11183,11186,11188],{"class":68,"line":425},[66,11184,11185],{"class":72},"                [][]",[66,11187,3565],{"class":1620},[66,11189,11190],{"class":72},"{\n",[66,11192,11193,11196,11198,11201,11203,11205,11207,11210,11212,11214,11216,11219,11221,11223,11225,11228,11230],{"class":68,"line":459},[66,11194,11195],{"class":72},"                    {",[66,11197,209],{"class":72},[66,11199,11200],{"class":212},"フロントエンド開発",[66,11202,209],{"class":72},[66,11204,188],{"class":72},[66,11206,7462],{"class":72},[66,11208,11209],{"class":212},"40 時間",[66,11211,209],{"class":72},[66,11213,188],{"class":72},[66,11215,7462],{"class":72},[66,11217,11218],{"class":212},"¥15,000",[66,11220,209],{"class":72},[66,11222,188],{"class":72},[66,11224,7462],{"class":72},[66,11226,11227],{"class":212},"¥600,000",[66,11229,209],{"class":72},[66,11231,11180],{"class":72},[66,11233,11234,11236,11238,11241,11243,11245,11248,11251,11253,11255,11257,11259,11261,11263,11265,11268,11270],{"class":68,"line":496},[66,11235,11195],{"class":72},[66,11237,209],{"class":72},[66,11239,11240],{"class":212},"バックエンド開発",[66,11242,209],{"class":72},[66,11244,188],{"class":72},[66,11246,11247],{"class":72},"   \"",[66,11249,11250],{"class":212},"60 時間",[66,11252,209],{"class":72},[66,11254,188],{"class":72},[66,11256,7462],{"class":72},[66,11258,11218],{"class":212},[66,11260,209],{"class":72},[66,11262,188],{"class":72},[66,11264,7462],{"class":72},[66,11266,11267],{"class":212},"¥900,000",[66,11269,209],{"class":72},[66,11271,11180],{"class":72},[66,11273,11274,11276,11278,11281,11283,11285,11288,11291,11293,11295,11297,11300,11302,11304,11306,11309,11311],{"class":68,"line":544},[66,11275,11195],{"class":72},[66,11277,209],{"class":72},[66,11279,11280],{"class":212},"UI デザイン",[66,11282,209],{"class":72},[66,11284,188],{"class":72},[66,11286,11287],{"class":72},"        \"",[66,11289,11290],{"class":212},"20 時間",[66,11292,209],{"class":72},[66,11294,188],{"class":72},[66,11296,7462],{"class":72},[66,11298,11299],{"class":212},"¥12,000",[66,11301,209],{"class":72},[66,11303,188],{"class":72},[66,11305,7462],{"class":72},[66,11307,11308],{"class":212},"¥240,000",[66,11310,209],{"class":72},[66,11312,11180],{"class":72},[66,11314,11315],{"class":68,"line":564},[66,11316,11317],{"class":72},"                },\n",[66,11319,11320,11323,11325,11328,11330,11333,11335,11338,11340,11342,11344,11347],{"class":68,"line":570},[66,11321,11322],{"class":184},"                template",[66,11324,200],{"class":72},[66,11326,11327],{"class":171},"ColumnWidths",[66,11329,206],{"class":72},[66,11331,11332],{"class":337},"50",[66,11334,188],{"class":72},[66,11336,11337],{"class":337}," 15",[66,11339,188],{"class":72},[66,11341,11337],{"class":337},[66,11343,188],{"class":72},[66,11345,11346],{"class":337}," 20",[66,11348,303],{"class":72},[66,11350,11351,11353,11355,11358],{"class":68,"line":576},[66,11352,11322],{"class":184},[66,11354,200],{"class":72},[66,11356,11357],{"class":171},"TableHeaderStyle",[66,11359,280],{"class":72},[66,11361,11362,11365,11367,11369],{"class":68,"line":581},[66,11363,11364],{"class":184},"                    template",[66,11366,200],{"class":72},[66,11368,538],{"class":171},[66,11370,11371],{"class":72},"(),\n",[66,11373,11374,11376,11378,11381,11383,11385,11387,11390],{"class":68,"line":602},[66,11375,11364],{"class":184},[66,11377,200],{"class":72},[66,11379,11380],{"class":171},"TextColor",[66,11382,206],{"class":72},[66,11384,2276],{"class":184},[66,11386,200],{"class":72},[66,11388,11389],{"class":184},"White",[66,11391,303],{"class":72},[66,11393,11394,11396,11398,11401,11403,11405,11407,11410,11412,11415],{"class":68,"line":615},[66,11395,11364],{"class":184},[66,11397,200],{"class":72},[66,11399,11400],{"class":171},"BgColor",[66,11402,206],{"class":72},[66,11404,2276],{"class":184},[66,11406,200],{"class":72},[66,11408,11409],{"class":171},"RGBHex",[66,11411,206],{"class":72},[66,11413,11414],{"class":337},"0x1A237E",[66,11416,11417],{"class":72},")),\n",[66,11419,11420],{"class":68,"line":630},[66,11421,11422],{"class":72},"                ),\n",[66,11424,11425],{"class":68,"line":635},[66,11426,11427],{"class":72},"            )\n",[66,11429,11430],{"class":68,"line":681},[66,11431,567],{"class":72},[66,11433,11434],{"class":68,"line":696},[66,11435,573],{"class":72},[66,11437,11438],{"class":68,"line":701},[66,11439,84],{"emptyLinePlaceholder":83},[66,11441,11442,11444,11446,11448,11450,11452,11454,11456],{"class":68,"line":4629},[66,11443,584],{"class":184},[66,11445,188],{"class":72},[66,11447,191],{"class":184},[66,11449,194],{"class":72},[66,11451,414],{"class":184},[66,11453,200],{"class":72},[66,11455,597],{"class":171},[66,11457,422],{"class":72},[66,11459,11460,11462,11464,11466,11468],{"class":68,"line":5882},[66,11461,222],{"class":90},[66,11463,191],{"class":184},[66,11465,227],{"class":72},[66,11467,230],{"class":72},[66,11469,178],{"class":72},[66,11471,11472,11474,11476,11478,11480,11482],{"class":68,"line":5902},[66,11473,238],{"class":184},[66,11475,200],{"class":72},[66,11477,243],{"class":171},[66,11479,206],{"class":72},[66,11481,248],{"class":184},[66,11483,157],{"class":72},[66,11485,11486],{"class":68,"line":5907},[66,11487,256],{"class":72},[66,11489,11490,11492,11494,11496,11498,11500,11502,11504,11506,11508,11510,11512,11514,11516,11518,11520,11522,11524,11526],{"class":68,"line":5938},[66,11491,222],{"class":90},[66,11493,191],{"class":184},[66,11495,194],{"class":72},[66,11497,197],{"class":184},[66,11499,200],{"class":72},[66,11501,648],{"class":171},[66,11503,206],{"class":72},[66,11505,209],{"class":72},[66,11507,4588],{"class":212},[66,11509,209],{"class":72},[66,11511,188],{"class":72},[66,11513,662],{"class":184},[66,11515,188],{"class":72},[66,11517,667],{"class":337},[66,11519,670],{"class":72},[66,11521,191],{"class":184},[66,11523,227],{"class":72},[66,11525,230],{"class":72},[66,11527,178],{"class":72},[66,11529,11530,11532,11534,11536,11538,11540],{"class":68,"line":5958},[66,11531,238],{"class":184},[66,11533,200],{"class":72},[66,11535,243],{"class":171},[66,11537,206],{"class":72},[66,11539,248],{"class":184},[66,11541,157],{"class":72},[66,11543,11544],{"class":68,"line":5963},[66,11545,256],{"class":72},[66,11547,11548],{"class":68,"line":5994},[66,11549,704],{"class":72},[18,11551,11552,11554,11555,11557],{},[45,11553,1930],{}," ゼロ。列幅の手計算ゼロ。",[45,11556,8056],{}," をドキュメントオプションに足せば、上記の日本語がそのままレンダリングされる。豆腐にならない。",[14,11559,11560],{"id":11560},"載せなかったもの",[18,11562,11563],{},"比較記事には必ず「入れなかった理由」の節がある。ここも同じ:",[1109,11565,11566,11572,11584,11593],{},[882,11567,11568,11571],{},[29,11569,11570],{},"社内フォークの gofpdf","。本番運用されているクローズドフォークはある。コードが見えない以上ベンチできない。",[882,11573,11574,11579,11580,11583],{},[29,11575,11576],{},[45,11577,11578],{},"pdfcpu","。「Go PDF ライブラリ」の一覧には必ず載るが、主用途は ",[29,11581,11582],{},"PDF プロセッサ"," (マージ・分割・暗号化・スタンプ) であって生成ではない。この記事の対象外。処理系向けの記事を別で書く予定。",[882,11585,11586,11592],{},[29,11587,11588,11591],{},[45,11589,11590],{},"gotenberg"," やヘッドレスブラウザサービスのラッパー","。ライブラリではないし、公平な比較にならない。",[882,11594,11595,11601],{},[29,11596,11597,11598,11600],{},"自家製 ",[45,11599,26],{}," のベンチ","。比較の焦点はコアの数字。",[14,11603,2820],{"id":2819},[18,11605,11606,11609],{},[29,11607,11608],{},"なぜ gpdf は gofpdf の 10 倍速いのか? 何か魔法か?","\n魔法はない。3 つの設計が積み上がっている: 1 パスレイアウト (ビルダーとライター間に AST を置かない)、ホットパスの具象型、cmap をキャッシュする TrueType サブセッター。どれか 1 つでも 2 倍の改善。重ねて桁が変わる。",[18,11611,11612,11615,11616,11619],{},[29,11613,11614],{},"このベンチ、手元で本当に再現できる?","\nできる。",[45,11617,11618],{},"git clone https://github.com/gpdf-dev/gpdf && cd gpdf/_benchmark && go test -bench=. -benchmem","。同じ CPU アーキ・同じ Go バージョンで数字が合わなければ issue を立ててほしい。ベンチ結果のドリフトは起きるし、知りたい。",[18,11621,11622,11625],{},[29,11623,11624],{},"gofpdf は復活する?","\n現実的にはしない。最後のコミットは 2021 年。issue tracker はクローズ済み。仮に誰かが開け直したとしても、カーソル + シングルバイトフォント + グリッドなしというアーキテクチャは 2026 年のスタート地点としては正しくない。歴史的遺物として扱って、移行するのが実利的。",[18,11627,11628,11631],{},[29,11629,11630],{},"Java iText / Python ReportLab / Node pdfkit はどう?","\n言語横断ベンチは別記事のネタ。短く書くと: Go はスループットとコールドスタートでは勝つが、機能の幅 (特に HTML→PDF の忠実度) では負ける。既に Go のチームなら gpdf のほうが軽くて速い。Python や Node のチームが Go に移ろうとすると移行コストがかかるので、大量トラフィックでのみ投資対効果が出る。",[18,11633,11634,11637],{},[29,11635,11636],{},"競合が改善したらこの比較は公平性を保てる?","\n保つ。毎年回す。signintech/gopdf がテーブル API を出して所要時間が半分になれば、2027 年版にそう書く。Maroto v2 が gofpdf 排除を完了すれば、その行は書き換わる。誰かに信じてもらう必要がないよう、ベンチコードは意図的に公開している。",[14,11639,1241],{"id":1240},[18,11641,11642],{},"gpdf は Go の PDF ライブラリ。MIT、ゼロ依存、ネイティブ CJK 対応。",[57,11644,11645],{"className":1247,"code":1248,"language":1249,"meta":62,"style":62},[45,11646,11647],{"__ignoreMap":62},[66,11648,11649,11651,11653],{"class":68,"line":69},[66,11650,61],{"class":76},[66,11652,1258],{"class":212},[66,11654,1261],{"class":212},[18,11656,11657,1268,11660],{},[21,11658,2885],{"href":23,"rel":11659},[25],[21,11661,1273],{"href":1271,"rel":11662},[25],[14,11664,2892],{"id":2892},[1109,11666,11667,11673,11682],{},[882,11668,11669,11672],{},[21,11670,11671],{"href":2906},"gofpdf はアーカイブされた。gpdf への移行ガイド"," — 5 組の Before/After コードで API マッピング全体を説明。",[882,11674,11675,3983,11679,11681],{},[21,11676,11678],{"href":1271,"rel":11677},[25],"Quickstart",[45,11680,6731],{}," まで含めて 5 分で動く。",[882,11683,2911,11684,1149],{},[21,11685,11687],{"href":1391,"rel":11686},[25],[45,11688,1395],{},[1275,11690,11691],{},"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":62,"searchDepth":80,"depth":80,"links":11693},[11694,11695,11696,11697,11701,11702,11703,11704,11705,11706,11707,11708,11709],{"id":1334,"depth":80,"text":1335},{"id":10156,"depth":80,"text":10157},{"id":10200,"depth":80,"text":10200},{"id":8091,"depth":80,"text":8091,"children":11698},[11699,11700],{"id":10440,"depth":87,"text":10440},{"id":10539,"depth":87,"text":10539},{"id":8560,"depth":80,"text":8560},{"id":5164,"depth":80,"text":5164},{"id":10735,"depth":80,"text":10736},{"id":10777,"depth":80,"text":10777},{"id":10827,"depth":80,"text":10828},{"id":11560,"depth":80,"text":11560},{"id":2819,"depth":80,"text":2820},{"id":1240,"depth":80,"text":1241},{"id":2892,"depth":80,"text":2892},"2026-04-15","2026 年時点で稼働する Go の PDF ライブラリを 4 種のワークロードでベンチ。ライセンス・依存・保守状況を横並びで比較する。",{},{"title":2900,"description":11711},"ja/blog/002.go-pdf-library-showdown-2026",[2949,2947],"M3RZb2K157o17LomMEg09Ly4_BjMM_RMJNmAW8EbRFE",{"id":11718,"title":1222,"author":11719,"body":11720,"date":11710,"description":13015,"draft":1292,"extension":1293,"howTo":13016,"image":1316,"meta":13032,"navigation":83,"path":1221,"seo":13033,"stem":13034,"tags":13035,"updated":1316,"__hash__":13036},"blogJa/ja/blog/003.embed-japanese-font.md",{"name":8,"url":9},{"type":11,"value":11721,"toc":13004},[11722,11724,11733,11735,11750,11752,12256,12269,12273,12276,12282,12291,12304,12308,12318,12463,12479,12482,12489,12915,12922,12926,12932,12938,12952,12954,12978,12980,12982,12994,13002],[14,11723,16],{"id":16},[18,11725,11726,11729,11730,11732],{},[21,11727,26],{"href":23,"rel":11728},[25]," で日本語(あるいは CJK 全般)の PDF を作りたい。",[45,11731,2570],{}," のお作法も、CGO も、一枚ごとに 5 MB のフォントを埋め込むのも避けたい。最短でどう書くか。",[14,11734,1335],{"id":1334},[18,11736,707,11737,11739,11740,3559,11743,11745,11746,11749],{},[45,11738,2846],{}," で読み込み、",[45,11741,11742],{},"gpdf.WithFont(\"NotoSansJP\", fontBytes)",[45,11744,277],{}," に渡し、必要ならデフォルトフォントに指定するだけ。",[29,11747,11748],{},"セットアップ 3 行で、gpdf は使った文字のグリフだけを抽出して埋め込む"," — 5 MB のフォント丸ごとは入らない。",[14,11751,55],{"id":55},[57,11753,11755],{"className":59,"code":11754,"language":61,"meta":62,"style":62},"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",[45,11756,11757,11763,11767,11773,11781,11789,11793,11801,11809,11817,11821,11825,11835,11861,11873,11887,11891,11895,11909,11927,11957,11979,12001,12005,12009,12023,12047,12077,12115,12134,12138,12142,12146,12164,12176,12190,12194,12234,12248,12252],{"__ignoreMap":62},[66,11758,11759,11761],{"class":68,"line":69},[66,11760,73],{"class":72},[66,11762,77],{"class":76},[66,11764,11765],{"class":68,"line":80},[66,11766,84],{"emptyLinePlaceholder":83},[66,11768,11769,11771],{"class":68,"line":87},[66,11770,91],{"class":90},[66,11772,94],{"class":72},[66,11774,11775,11777,11779],{"class":68,"line":97},[66,11776,100],{"class":72},[66,11778,103],{"class":76},[66,11780,106],{"class":72},[66,11782,11783,11785,11787],{"class":68,"line":109},[66,11784,100],{"class":72},[66,11786,114],{"class":76},[66,11788,106],{"class":72},[66,11790,11791],{"class":68,"line":119},[66,11792,84],{"emptyLinePlaceholder":83},[66,11794,11795,11797,11799],{"class":68,"line":124},[66,11796,100],{"class":72},[66,11798,129],{"class":76},[66,11800,106],{"class":72},[66,11802,11803,11805,11807],{"class":68,"line":134},[66,11804,100],{"class":72},[66,11806,139],{"class":76},[66,11808,106],{"class":72},[66,11810,11811,11813,11815],{"class":68,"line":144},[66,11812,100],{"class":72},[66,11814,149],{"class":76},[66,11816,106],{"class":72},[66,11818,11819],{"class":68,"line":154},[66,11820,157],{"class":72},[66,11822,11823],{"class":68,"line":160},[66,11824,84],{"emptyLinePlaceholder":83},[66,11826,11827,11829,11831,11833],{"class":68,"line":165},[66,11828,168],{"class":72},[66,11830,172],{"class":171},[66,11832,175],{"class":72},[66,11834,178],{"class":72},[66,11836,11837,11839,11841,11843,11845,11847,11849,11851,11853,11855,11857,11859],{"class":68,"line":181},[66,11838,185],{"class":184},[66,11840,188],{"class":72},[66,11842,191],{"class":184},[66,11844,194],{"class":72},[66,11846,197],{"class":184},[66,11848,200],{"class":72},[66,11850,203],{"class":171},[66,11852,206],{"class":72},[66,11854,209],{"class":72},[66,11856,3134],{"class":212},[66,11858,209],{"class":72},[66,11860,157],{"class":72},[66,11862,11863,11865,11867,11869,11871],{"class":68,"line":219},[66,11864,222],{"class":90},[66,11866,191],{"class":184},[66,11868,227],{"class":72},[66,11870,230],{"class":72},[66,11872,178],{"class":72},[66,11874,11875,11877,11879,11881,11883,11885],{"class":68,"line":235},[66,11876,238],{"class":184},[66,11878,200],{"class":72},[66,11880,243],{"class":171},[66,11882,206],{"class":72},[66,11884,248],{"class":184},[66,11886,157],{"class":72},[66,11888,11889],{"class":68,"line":253},[66,11890,256],{"class":72},[66,11892,11893],{"class":68,"line":259},[66,11894,84],{"emptyLinePlaceholder":83},[66,11896,11897,11899,11901,11903,11905,11907],{"class":68,"line":264},[66,11898,267],{"class":184},[66,11900,194],{"class":72},[66,11902,272],{"class":184},[66,11904,200],{"class":72},[66,11906,277],{"class":171},[66,11908,280],{"class":72},[66,11910,11911,11913,11915,11917,11919,11921,11923,11925],{"class":68,"line":283},[66,11912,286],{"class":184},[66,11914,200],{"class":72},[66,11916,291],{"class":171},[66,11918,206],{"class":72},[66,11920,26],{"class":184},[66,11922,200],{"class":72},[66,11924,300],{"class":184},[66,11926,303],{"class":72},[66,11928,11929,11931,11933,11935,11937,11939,11941,11943,11945,11947,11949,11951,11953,11955],{"class":68,"line":306},[66,11930,286],{"class":184},[66,11932,200],{"class":72},[66,11934,313],{"class":171},[66,11936,206],{"class":72},[66,11938,318],{"class":184},[66,11940,200],{"class":72},[66,11942,323],{"class":171},[66,11944,206],{"class":72},[66,11946,318],{"class":184},[66,11948,200],{"class":72},[66,11950,332],{"class":171},[66,11952,206],{"class":72},[66,11954,338],{"class":337},[66,11956,341],{"class":72},[66,11958,11959,11961,11963,11965,11967,11969,11971,11973,11975,11977],{"class":68,"line":344},[66,11960,286],{"class":184},[66,11962,200],{"class":72},[66,11964,351],{"class":171},[66,11966,206],{"class":72},[66,11968,209],{"class":72},[66,11970,3249],{"class":212},[66,11972,209],{"class":72},[66,11974,188],{"class":72},[66,11976,365],{"class":184},[66,11978,303],{"class":72},[66,11980,11981,11983,11985,11987,11989,11991,11993,11995,11997,11999],{"class":68,"line":370},[66,11982,286],{"class":184},[66,11984,200],{"class":72},[66,11986,377],{"class":171},[66,11988,206],{"class":72},[66,11990,209],{"class":72},[66,11992,3249],{"class":212},[66,11994,209],{"class":72},[66,11996,188],{"class":72},[66,11998,3278],{"class":337},[66,12000,303],{"class":72},[66,12002,12003],{"class":68,"line":395},[66,12004,398],{"class":72},[66,12006,12007],{"class":68,"line":401},[66,12008,84],{"emptyLinePlaceholder":83},[66,12010,12011,12013,12015,12017,12019,12021],{"class":68,"line":406},[66,12012,409],{"class":184},[66,12014,194],{"class":72},[66,12016,414],{"class":184},[66,12018,200],{"class":72},[66,12020,419],{"class":171},[66,12022,422],{"class":72},[66,12024,12025,12027,12029,12031,12033,12035,12037,12039,12041,12043,12045],{"class":68,"line":425},[66,12026,428],{"class":184},[66,12028,200],{"class":72},[66,12030,433],{"class":171},[66,12032,436],{"class":72},[66,12034,440],{"class":439},[66,12036,443],{"class":72},[66,12038,446],{"class":76},[66,12040,200],{"class":72},[66,12042,451],{"class":76},[66,12044,454],{"class":72},[66,12046,178],{"class":72},[66,12048,12049,12051,12053,12055,12057,12059,12061,12063,12065,12067,12069,12071,12073,12075],{"class":68,"line":459},[66,12050,462],{"class":184},[66,12052,200],{"class":72},[66,12054,467],{"class":171},[66,12056,206],{"class":72},[66,12058,472],{"class":337},[66,12060,188],{"class":72},[66,12062,477],{"class":72},[66,12064,480],{"class":439},[66,12066,443],{"class":72},[66,12068,446],{"class":76},[66,12070,200],{"class":72},[66,12072,489],{"class":76},[66,12074,454],{"class":72},[66,12076,178],{"class":72},[66,12078,12079,12081,12083,12085,12087,12089,12091,12093,12095,12097,12099,12101,12103,12105,12107,12109,12111,12113],{"class":68,"line":496},[66,12080,499],{"class":184},[66,12082,200],{"class":72},[66,12084,504],{"class":171},[66,12086,206],{"class":72},[66,12088,209],{"class":72},[66,12090,3371],{"class":212},[66,12092,209],{"class":72},[66,12094,188],{"class":72},[66,12096,518],{"class":184},[66,12098,200],{"class":72},[66,12100,523],{"class":171},[66,12102,206],{"class":72},[66,12104,528],{"class":337},[66,12106,531],{"class":72},[66,12108,518],{"class":184},[66,12110,200],{"class":72},[66,12112,538],{"class":171},[66,12114,541],{"class":72},[66,12116,12117,12119,12121,12123,12125,12127,12130,12132],{"class":68,"line":544},[66,12118,499],{"class":184},[66,12120,200],{"class":72},[66,12122,504],{"class":171},[66,12124,206],{"class":72},[66,12126,209],{"class":72},[66,12128,12129],{"class":212},"日本語 PDF、これだけ。",[66,12131,209],{"class":72},[66,12133,157],{"class":72},[66,12135,12136],{"class":68,"line":564},[66,12137,567],{"class":72},[66,12139,12140],{"class":68,"line":570},[66,12141,573],{"class":72},[66,12143,12144],{"class":68,"line":576},[66,12145,84],{"emptyLinePlaceholder":83},[66,12147,12148,12150,12152,12154,12156,12158,12160,12162],{"class":68,"line":581},[66,12149,584],{"class":184},[66,12151,188],{"class":72},[66,12153,191],{"class":184},[66,12155,194],{"class":72},[66,12157,414],{"class":184},[66,12159,200],{"class":72},[66,12161,597],{"class":171},[66,12163,422],{"class":72},[66,12165,12166,12168,12170,12172,12174],{"class":68,"line":602},[66,12167,222],{"class":90},[66,12169,191],{"class":184},[66,12171,227],{"class":72},[66,12173,230],{"class":72},[66,12175,178],{"class":72},[66,12177,12178,12180,12182,12184,12186,12188],{"class":68,"line":615},[66,12179,238],{"class":184},[66,12181,200],{"class":72},[66,12183,243],{"class":171},[66,12185,206],{"class":72},[66,12187,248],{"class":184},[66,12189,157],{"class":72},[66,12191,12192],{"class":68,"line":630},[66,12193,256],{"class":72},[66,12195,12196,12198,12200,12202,12204,12206,12208,12210,12212,12214,12216,12218,12220,12222,12224,12226,12228,12230,12232],{"class":68,"line":635},[66,12197,222],{"class":90},[66,12199,191],{"class":184},[66,12201,194],{"class":72},[66,12203,197],{"class":184},[66,12205,200],{"class":72},[66,12207,648],{"class":171},[66,12209,206],{"class":72},[66,12211,209],{"class":72},[66,12213,3456],{"class":212},[66,12215,209],{"class":72},[66,12217,188],{"class":72},[66,12219,662],{"class":184},[66,12221,188],{"class":72},[66,12223,667],{"class":337},[66,12225,670],{"class":72},[66,12227,191],{"class":184},[66,12229,227],{"class":72},[66,12231,230],{"class":72},[66,12233,178],{"class":72},[66,12235,12236,12238,12240,12242,12244,12246],{"class":68,"line":681},[66,12237,238],{"class":184},[66,12239,200],{"class":72},[66,12241,243],{"class":171},[66,12243,206],{"class":72},[66,12245,248],{"class":184},[66,12247,157],{"class":72},[66,12249,12250],{"class":68,"line":696},[66,12251,256],{"class":72},[66,12253,12254],{"class":68,"line":701},[66,12255,704],{"class":72},[18,12257,12258,3559,12260,12263,12264,4648,12266,12268],{},[45,12259,3134],{},[21,12261,3520],{"href":3518,"rel":12262},[25]," からダウンロードして ",[45,12265,710],{},[45,12267,714],{}," を叩けば 1 ページの日本語 PDF が出る。",[14,12270,12272],{"id":12271},"この-3-行で何が起きているか","この 3 行で何が起きているか",[18,12274,12275],{},"背後では 2 つの処理が走っていて、どちらもアプリ側で面倒を見る必要はない。",[18,12277,12278,12281],{},[29,12279,12280],{},"サブセット埋め込み。"," Noto Sans JP は 17,000 字前後のグリフを含み、Regular だけでもディスク上で約 5 MB。これをそのまま埋め込むと、4 行の日本語が載った領収書でも PDF が 5 MB を超えてしまう。gpdf は描画したテキストを走査して使われたグリフ ID を洗い出し、そのサブセットだけを PDF に書き込む。短い請求書 1 枚ならフォントデータは 20〜40 KB 程度に収まる。",[18,12283,12284,12285,12287,12288,12290],{},"gofpdf もサブセット化自体は可能だったが、",[45,12286,2570],{}," にファイルパスと UTF-8 フラグを渡して都度読み込ませる API 設計のため、ドキュメント途中でのフォント切り替えが扱いづらかった。gpdf はドキュメント生成時に一度だけ登録し、以降は ",[45,12289,2991],{}," がファミリ名で参照する。呼び出しごとのお膳立ては不要。",[18,12292,12293,12296,12297,12300,12301,12303],{},[29,12294,12295],{},"CGO 不使用。"," これは地味に大きい。他言語のエコシステムではフォント処理が FreeType や HarfBuzz を経由することが多く、C 依存が入るとビルドキャッシュの挙動が変わり、Docker イメージにレイヤが増え、macOS から ",[45,12298,12299],{},"linux/arm64"," へのクロスコンパイルで一手間かかるようになる。gpdf は TrueType テーブルを純 Go で読んでいる。",[45,12302,7088],{}," は static のまま。distroless コンテナに Go バイナリと TTF だけ入れて出荷できる。",[14,12305,12307],{"id":12306},"bold-italic-を使う","Bold / Italic を使う",[18,12309,12310,12311,12314,12315,12317],{},"Noto Sans JP は Weight ごとに別ファイル。",[29,12312,12313],{},"太字","を使いたい場合は Bold 用 TTF を ",[45,12316,1089],{}," サフィックス付きで別登録する:",[57,12319,12321],{"className":59,"code":12320,"language":61,"meta":62,"style":62},"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",[45,12322,12323,12349,12375,12379,12393,12415,12437,12459],{"__ignoreMap":62},[66,12324,12325,12327,12329,12331,12333,12335,12337,12339,12341,12343,12345,12347],{"class":68,"line":69},[66,12326,939],{"class":184},[66,12328,188],{"class":72},[66,12330,972],{"class":184},[66,12332,194],{"class":72},[66,12334,197],{"class":184},[66,12336,200],{"class":72},[66,12338,203],{"class":171},[66,12340,206],{"class":72},[66,12342,209],{"class":72},[66,12344,3134],{"class":212},[66,12346,209],{"class":72},[66,12348,157],{"class":72},[66,12350,12351,12353,12355,12357,12359,12361,12363,12365,12367,12369,12371,12373],{"class":68,"line":80},[66,12352,967],{"class":184},[66,12354,188],{"class":72},[66,12356,972],{"class":184},[66,12358,194],{"class":72},[66,12360,197],{"class":184},[66,12362,200],{"class":72},[66,12364,203],{"class":171},[66,12366,206],{"class":72},[66,12368,209],{"class":72},[66,12370,8855],{"class":212},[66,12372,209],{"class":72},[66,12374,157],{"class":72},[66,12376,12377],{"class":68,"line":87},[66,12378,84],{"emptyLinePlaceholder":83},[66,12380,12381,12383,12385,12387,12389,12391],{"class":68,"line":97},[66,12382,1000],{"class":184},[66,12384,194],{"class":72},[66,12386,272],{"class":184},[66,12388,200],{"class":72},[66,12390,277],{"class":171},[66,12392,280],{"class":72},[66,12394,12395,12397,12399,12401,12403,12405,12407,12409,12411,12413],{"class":68,"line":109},[66,12396,1015],{"class":184},[66,12398,200],{"class":72},[66,12400,351],{"class":171},[66,12402,206],{"class":72},[66,12404,209],{"class":72},[66,12406,3249],{"class":212},[66,12408,209],{"class":72},[66,12410,188],{"class":72},[66,12412,1032],{"class":184},[66,12414,303],{"class":72},[66,12416,12417,12419,12421,12423,12425,12427,12429,12431,12433,12435],{"class":68,"line":119},[66,12418,1015],{"class":184},[66,12420,200],{"class":72},[66,12422,351],{"class":171},[66,12424,206],{"class":72},[66,12426,209],{"class":72},[66,12428,8993],{"class":212},[66,12430,209],{"class":72},[66,12432,188],{"class":72},[66,12434,1056],{"class":184},[66,12436,303],{"class":72},[66,12438,12439,12441,12443,12445,12447,12449,12451,12453,12455,12457],{"class":68,"line":124},[66,12440,1015],{"class":184},[66,12442,200],{"class":72},[66,12444,377],{"class":171},[66,12446,206],{"class":72},[66,12448,209],{"class":72},[66,12450,3249],{"class":212},[66,12452,209],{"class":72},[66,12454,188],{"class":72},[66,12456,3278],{"class":337},[66,12458,303],{"class":72},[66,12460,12461],{"class":68,"line":134},[66,12462,157],{"class":72},[18,12464,12465,12466,12468,12469,12471,12472,1155,12475,12478],{},"これで ",[45,12467,1093],{}," が ",[45,12470,1089],{}," バリアントを拾う。同じ規約で ",[45,12473,12474],{},"-Italic",[45,12476,12477],{},"-BoldItalic"," も使える。バリアントを登録していない場合は合成ウェイトにフォールバックされる — 画面では読めるが、タイポグラフィ的には正直でない。本番の請求書には実バリアントを登録しておく。",[14,12480,12481],{"id":12481},"日中韓を同じドキュメントに混在させる",[18,12483,12484,12485,12488],{},"登録は何ファミリでも OK。文字ごとに ",[45,12486,12487],{},"template.FontFamily(...)"," で切り替える:",[57,12490,12492],{"className":59,"code":12491,"language":61,"meta":62,"style":62},"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",[45,12493,12494,12521,12549,12577,12581,12595,12618,12642,12666,12688,12692,12696,12720,12750,12769,12773,12803,12838,12842,12872,12907,12911],{"__ignoreMap":62},[66,12495,12496,12499,12501,12503,12505,12507,12509,12511,12513,12515,12517,12519],{"class":68,"line":69},[66,12497,12498],{"class":184},"jp",[66,12500,188],{"class":72},[66,12502,972],{"class":184},[66,12504,194],{"class":72},[66,12506,197],{"class":184},[66,12508,200],{"class":72},[66,12510,203],{"class":171},[66,12512,206],{"class":72},[66,12514,209],{"class":72},[66,12516,3134],{"class":212},[66,12518,209],{"class":72},[66,12520,157],{"class":72},[66,12522,12523,12526,12528,12530,12532,12534,12536,12538,12540,12542,12545,12547],{"class":68,"line":80},[66,12524,12525],{"class":184},"sc",[66,12527,188],{"class":72},[66,12529,972],{"class":184},[66,12531,194],{"class":72},[66,12533,197],{"class":184},[66,12535,200],{"class":72},[66,12537,203],{"class":171},[66,12539,206],{"class":72},[66,12541,209],{"class":72},[66,12543,12544],{"class":212},"NotoSansSC-Regular.ttf",[66,12546,209],{"class":72},[66,12548,157],{"class":72},[66,12550,12551,12554,12556,12558,12560,12562,12564,12566,12568,12570,12573,12575],{"class":68,"line":87},[66,12552,12553],{"class":184},"kr",[66,12555,188],{"class":72},[66,12557,972],{"class":184},[66,12559,194],{"class":72},[66,12561,197],{"class":184},[66,12563,200],{"class":72},[66,12565,203],{"class":171},[66,12567,206],{"class":72},[66,12569,209],{"class":72},[66,12571,12572],{"class":212},"NotoSansKR-Regular.ttf",[66,12574,209],{"class":72},[66,12576,157],{"class":72},[66,12578,12579],{"class":68,"line":97},[66,12580,84],{"emptyLinePlaceholder":83},[66,12582,12583,12585,12587,12589,12591,12593],{"class":68,"line":109},[66,12584,1000],{"class":184},[66,12586,194],{"class":72},[66,12588,272],{"class":184},[66,12590,200],{"class":72},[66,12592,277],{"class":171},[66,12594,280],{"class":72},[66,12596,12597,12599,12601,12603,12605,12607,12609,12611,12613,12616],{"class":68,"line":119},[66,12598,1015],{"class":184},[66,12600,200],{"class":72},[66,12602,351],{"class":171},[66,12604,206],{"class":72},[66,12606,209],{"class":72},[66,12608,3249],{"class":212},[66,12610,209],{"class":72},[66,12612,188],{"class":72},[66,12614,12615],{"class":184}," jp",[66,12617,303],{"class":72},[66,12619,12620,12622,12624,12626,12628,12630,12633,12635,12637,12640],{"class":68,"line":124},[66,12621,1015],{"class":184},[66,12623,200],{"class":72},[66,12625,351],{"class":171},[66,12627,206],{"class":72},[66,12629,209],{"class":72},[66,12631,12632],{"class":212},"NotoSansSC",[66,12634,209],{"class":72},[66,12636,188],{"class":72},[66,12638,12639],{"class":184}," sc",[66,12641,303],{"class":72},[66,12643,12644,12646,12648,12650,12652,12654,12657,12659,12661,12664],{"class":68,"line":134},[66,12645,1015],{"class":184},[66,12647,200],{"class":72},[66,12649,351],{"class":171},[66,12651,206],{"class":72},[66,12653,209],{"class":72},[66,12655,12656],{"class":212},"NotoSansKR",[66,12658,209],{"class":72},[66,12660,188],{"class":72},[66,12662,12663],{"class":184}," kr",[66,12665,303],{"class":72},[66,12667,12668,12670,12672,12674,12676,12678,12680,12682,12684,12686],{"class":68,"line":144},[66,12669,1015],{"class":184},[66,12671,200],{"class":72},[66,12673,377],{"class":171},[66,12675,206],{"class":72},[66,12677,209],{"class":72},[66,12679,3249],{"class":212},[66,12681,209],{"class":72},[66,12683,188],{"class":72},[66,12685,3278],{"class":337},[66,12687,303],{"class":72},[66,12689,12690],{"class":68,"line":154},[66,12691,157],{"class":72},[66,12693,12694],{"class":68,"line":160},[66,12695,84],{"emptyLinePlaceholder":83},[66,12697,12698,12700,12702,12704,12706,12708,12710,12712,12714,12716,12718],{"class":68,"line":165},[66,12699,1926],{"class":184},[66,12701,200],{"class":72},[66,12703,433],{"class":171},[66,12705,436],{"class":72},[66,12707,440],{"class":439},[66,12709,443],{"class":72},[66,12711,446],{"class":76},[66,12713,200],{"class":72},[66,12715,451],{"class":76},[66,12717,454],{"class":72},[66,12719,178],{"class":72},[66,12721,12722,12724,12726,12728,12730,12732,12734,12736,12738,12740,12742,12744,12746,12748],{"class":68,"line":181},[66,12723,3673],{"class":184},[66,12725,200],{"class":72},[66,12727,467],{"class":171},[66,12729,206],{"class":72},[66,12731,5861],{"class":337},[66,12733,188],{"class":72},[66,12735,477],{"class":72},[66,12737,480],{"class":439},[66,12739,443],{"class":72},[66,12741,446],{"class":76},[66,12743,200],{"class":72},[66,12745,489],{"class":76},[66,12747,454],{"class":72},[66,12749,178],{"class":72},[66,12751,12752,12754,12756,12758,12760,12762,12765,12767],{"class":68,"line":219},[66,12753,3704],{"class":184},[66,12755,200],{"class":72},[66,12757,504],{"class":171},[66,12759,206],{"class":72},[66,12761,209],{"class":72},[66,12763,12764],{"class":212},"日本語",[66,12766,209],{"class":72},[66,12768,157],{"class":72},[66,12770,12771],{"class":68,"line":235},[66,12772,573],{"class":72},[66,12774,12775,12777,12779,12781,12783,12785,12787,12789,12791,12793,12795,12797,12799,12801],{"class":68,"line":253},[66,12776,3673],{"class":184},[66,12778,200],{"class":72},[66,12780,467],{"class":171},[66,12782,206],{"class":72},[66,12784,5861],{"class":337},[66,12786,188],{"class":72},[66,12788,477],{"class":72},[66,12790,480],{"class":439},[66,12792,443],{"class":72},[66,12794,446],{"class":76},[66,12796,200],{"class":72},[66,12798,489],{"class":76},[66,12800,454],{"class":72},[66,12802,178],{"class":72},[66,12804,12805,12807,12809,12811,12813,12815,12818,12820,12822,12824,12826,12828,12830,12832,12834,12836],{"class":68,"line":259},[66,12806,3704],{"class":184},[66,12808,200],{"class":72},[66,12810,504],{"class":171},[66,12812,206],{"class":72},[66,12814,209],{"class":72},[66,12816,12817],{"class":212},"中文",[66,12819,209],{"class":72},[66,12821,188],{"class":72},[66,12823,518],{"class":184},[66,12825,200],{"class":72},[66,12827,5027],{"class":171},[66,12829,206],{"class":72},[66,12831,209],{"class":72},[66,12833,12632],{"class":212},[66,12835,209],{"class":72},[66,12837,5050],{"class":72},[66,12839,12840],{"class":68,"line":264},[66,12841,573],{"class":72},[66,12843,12844,12846,12848,12850,12852,12854,12856,12858,12860,12862,12864,12866,12868,12870],{"class":68,"line":283},[66,12845,3673],{"class":184},[66,12847,200],{"class":72},[66,12849,467],{"class":171},[66,12851,206],{"class":72},[66,12853,5861],{"class":337},[66,12855,188],{"class":72},[66,12857,477],{"class":72},[66,12859,480],{"class":439},[66,12861,443],{"class":72},[66,12863,446],{"class":76},[66,12865,200],{"class":72},[66,12867,489],{"class":76},[66,12869,454],{"class":72},[66,12871,178],{"class":72},[66,12873,12874,12876,12878,12880,12882,12884,12887,12889,12891,12893,12895,12897,12899,12901,12903,12905],{"class":68,"line":306},[66,12875,3704],{"class":184},[66,12877,200],{"class":72},[66,12879,504],{"class":171},[66,12881,206],{"class":72},[66,12883,209],{"class":72},[66,12885,12886],{"class":212},"한국어",[66,12888,209],{"class":72},[66,12890,188],{"class":72},[66,12892,518],{"class":184},[66,12894,200],{"class":72},[66,12896,5027],{"class":171},[66,12898,206],{"class":72},[66,12900,209],{"class":72},[66,12902,12656],{"class":212},[66,12904,209],{"class":72},[66,12906,5050],{"class":72},[66,12908,12909],{"class":68,"line":344},[66,12910,573],{"class":72},[66,12912,12913],{"class":68,"line":370},[66,12914,3731],{"class":72},[18,12916,12917,12918,12921],{},"漢字統合 (Han unification) の都合で、日本語と中国語簡体字は Unicode コードポイントが重なるが、実際に描画される字形は別物。",[29,12919,12920],{},"同じコードポイントでもフォント次第で字の形が変わる"," ので、越境 EC 向けに和文・中文両対応の帳票を作るなら両方のフォントを登録する必要がある。国によって「骨」「直」などの形が違うことがあるのはここが原因。",[14,12923,12925],{"id":12924},"豆腐文字-が出たら","豆腐文字 (□□□□) が出たら",[18,12927,12928,12929,12931],{},"日本語を書いているのに ",[45,12930,351],{}," を忘れると、gpdf は標準 14 フォントにフォールバックし、そこには CJK のグリフが入っていないため文字が矩形のまま表示される。これが俗に言う「豆腐文字」。",[57,12933,12936],{"className":12934,"code":12935,"language":924},[922],"□□□□□、□□。\n",[45,12937,12935],{"__ignoreMap":62},[18,12939,12940,12941,12943,12944,12946,12947,2980,12949,12951],{},"この出力を見たら原因は一択: CJK フォントが未登録か、書いた文字を含まない別ファミリで描画している。直し方も一択: ",[45,12942,351],{}," を追加して ",[45,12945,377],{}," で既定にするか、",[45,12948,2991],{},[45,12950,1162],{}," を付ける。",[14,12953,1208],{"id":1208},[1109,12955,12956,12965,12970],{},[882,12957,12958,3983,12961,12964],{},[21,12959,12960],{"href":2906},"gofpdf がアーカイブされた。gpdf への移行ガイド",[45,12962,12963],{},"pdf.AddUTF8Font"," から乗り換える場合のフルマップ",[882,12966,12967,12969],{},[21,12968,6595],{"href":2899}," — gofpdf / gopdf / Maroto / unipdf と gpdf の CJK 対応比較",[882,12971,12972,3983,12975,12977],{},[21,12973,5247],{"href":5245,"rel":12974},[25],[45,12976,351],{}," のフルリファレンスとバリアント命名規則",[14,12979,1241],{"id":1240},[18,12981,1244],{},[57,12983,12984],{"className":1247,"code":1248,"language":1249,"meta":62,"style":62},[45,12985,12986],{"__ignoreMap":62},[66,12987,12988,12990,12992],{"class":68,"line":69},[66,12989,61],{"class":76},[66,12991,1258],{"class":212},[66,12993,1261],{"class":212},[18,12995,12996,1268,12999],{},[21,12997,1267],{"href":23,"rel":12998},[25],[21,13000,1273],{"href":1271,"rel":13001},[25],[1275,13003,1277],{},{"title":62,"searchDepth":80,"depth":80,"links":13005},[13006,13007,13008,13009,13010,13011,13012,13013,13014],{"id":16,"depth":80,"text":16},{"id":1334,"depth":80,"text":1335},{"id":55,"depth":80,"text":55},{"id":12271,"depth":80,"text":12272},{"id":12306,"depth":80,"text":12307},{"id":12481,"depth":80,"text":12481},{"id":12924,"depth":80,"text":12925},{"id":1208,"depth":80,"text":1208},{"id":1240,"depth":80,"text":1241},"gpdf.WithFont にTTFバイトを渡すだけ。サブセット埋め込みも自動、CGOも不要。Go で日本語 PDF を作る最短手順。",{"name":13017,"totalTime":5291,"tools":13018,"steps":13020},"gpdf ドキュメントに日本語 TrueType フォントを埋め込む",[1298,13019],"NotoSansJP-Regular.ttf (または任意の CJK 対応 TTF)",[13021,13023,13026,13029],{"name":5299,"text":13022},"プログラム起動時に NotoSansJP-Regular.ttf を os.ReadFile で []byte に読み込む。バイナリに焼き付けたいなら //go:embed でも可。",{"name":13024,"text":13025},"ドキュメント構築時に WithFont で登録する","gpdf.WithFont(\"NotoSansJP\", fontBytes) を gpdf.NewDocument に渡す。ファミリ名は任意 — 後で参照する名前を自由に決めてよい。サブセット化はレンダリング時に自動で走る。",{"name":13027,"text":13028},"デフォルトフォントに設定する","gpdf.WithDefaultFont(\"NotoSansJP\", 12) を追加すれば、FontFamily を毎回指定せずとも c.Text が日本語フォントで描画される。",{"name":13030,"text":13031},"日本語を書いて PDF を生成する","カラム内で c.Text(\"こんにちは、世界。\") を呼ぶ。doc.Generate() が []byte を返すので os.WriteFile でファイルに書き出す。",{},{"title":1222,"description":13015},"ja/blog/003.embed-japanese-font",[1322,1323,1324],"7Ku_4hRojpBbCvwsilwANzdGrF4MXNK-CmN0LOzC66g",{"id":13038,"title":13039,"author":13040,"body":13041,"date":11710,"description":14376,"draft":1292,"extension":1293,"howTo":14377,"image":1316,"meta":14394,"navigation":83,"path":831,"seo":14395,"stem":14396,"tags":14397,"updated":1316,"__hash__":14398},"blogJa/ja/blog/004.noto-sans-jp-with-gpdf.md","gpdf で Noto Sans JP を使うには?",{"name":8,"url":9},{"type":11,"value":13042,"toc":14363},[13043,13045,13054,13058,13071,13074,13579,13594,13598,13605,13630,13637,13655,13658,13662,13668,13735,13756,13766,13770,13773,13918,13931,13934,14003,14013,14016,14019,14022,14082,14085,14088,14092,14095,14100,14113,14122,14135,14138,14141,14298,14305,14307,14335,14337,14339,14351,14360],[14,13044,16],{"id":16},[18,13046,13047,13050,13051,1149],{},[21,13048,26],{"href":23,"rel":13049},[25]," で日本語 PDF を作りたい。フォントは Noto Sans JP にしたい (Google が配布している SIL OFL ライセンス、JIS 範囲をフルカバーする定番のゴシック体)。Google Fonts の zip を落としたところまでは分かった。知りたいのはここから先 — ",[29,13052,13053],{},"どのファイルを選ぶか、どの weight を登録するか、zip の中に仕込まれている 1 つの罠は何か",[14,13055,13057],{"id":13056},"結論-tldr","結論 (TL;DR)",[18,13059,13060,13061,13066,13067,13070],{},"Google Fonts の zip を展開したら ",[29,13062,13063],{},[45,13064,13065],{},"static/NotoSansJP-Regular.ttf"," を使う。zip ルートにある variable font ではない。これを ",[45,13068,13069],{},"gpdf.WithFont(\"NotoSansJP\", bytes)"," に渡してデフォルトフォントに指定するだけ。gpdf は約 17,000 グリフのうち、実際に描画した分だけをサブセット化して PDF に埋め込む。請求書 1 枚なら 20〜40 KB。",[14,13072,13073],{"id":13073},"完全なサンプル",[57,13075,13077],{"className":59,"code":13076,"language":61,"meta":62,"style":62},"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",[45,13078,13079,13085,13089,13095,13103,13111,13115,13123,13131,13139,13143,13147,13157,13183,13195,13209,13213,13217,13231,13249,13279,13301,13323,13327,13331,13345,13369,13399,13438,13457,13461,13465,13469,13487,13499,13513,13517,13557,13571,13575],{"__ignoreMap":62},[66,13080,13081,13083],{"class":68,"line":69},[66,13082,73],{"class":72},[66,13084,77],{"class":76},[66,13086,13087],{"class":68,"line":80},[66,13088,84],{"emptyLinePlaceholder":83},[66,13090,13091,13093],{"class":68,"line":87},[66,13092,91],{"class":90},[66,13094,94],{"class":72},[66,13096,13097,13099,13101],{"class":68,"line":97},[66,13098,100],{"class":72},[66,13100,103],{"class":76},[66,13102,106],{"class":72},[66,13104,13105,13107,13109],{"class":68,"line":109},[66,13106,100],{"class":72},[66,13108,114],{"class":76},[66,13110,106],{"class":72},[66,13112,13113],{"class":68,"line":119},[66,13114,84],{"emptyLinePlaceholder":83},[66,13116,13117,13119,13121],{"class":68,"line":124},[66,13118,100],{"class":72},[66,13120,129],{"class":76},[66,13122,106],{"class":72},[66,13124,13125,13127,13129],{"class":68,"line":134},[66,13126,100],{"class":72},[66,13128,139],{"class":76},[66,13130,106],{"class":72},[66,13132,13133,13135,13137],{"class":68,"line":144},[66,13134,100],{"class":72},[66,13136,149],{"class":76},[66,13138,106],{"class":72},[66,13140,13141],{"class":68,"line":154},[66,13142,157],{"class":72},[66,13144,13145],{"class":68,"line":160},[66,13146,84],{"emptyLinePlaceholder":83},[66,13148,13149,13151,13153,13155],{"class":68,"line":165},[66,13150,168],{"class":72},[66,13152,172],{"class":171},[66,13154,175],{"class":72},[66,13156,178],{"class":72},[66,13158,13159,13161,13163,13165,13167,13169,13171,13173,13175,13177,13179,13181],{"class":68,"line":181},[66,13160,185],{"class":184},[66,13162,188],{"class":72},[66,13164,191],{"class":184},[66,13166,194],{"class":72},[66,13168,197],{"class":184},[66,13170,200],{"class":72},[66,13172,203],{"class":171},[66,13174,206],{"class":72},[66,13176,209],{"class":72},[66,13178,3134],{"class":212},[66,13180,209],{"class":72},[66,13182,157],{"class":72},[66,13184,13185,13187,13189,13191,13193],{"class":68,"line":219},[66,13186,222],{"class":90},[66,13188,191],{"class":184},[66,13190,227],{"class":72},[66,13192,230],{"class":72},[66,13194,178],{"class":72},[66,13196,13197,13199,13201,13203,13205,13207],{"class":68,"line":235},[66,13198,238],{"class":184},[66,13200,200],{"class":72},[66,13202,243],{"class":171},[66,13204,206],{"class":72},[66,13206,248],{"class":184},[66,13208,157],{"class":72},[66,13210,13211],{"class":68,"line":253},[66,13212,256],{"class":72},[66,13214,13215],{"class":68,"line":259},[66,13216,84],{"emptyLinePlaceholder":83},[66,13218,13219,13221,13223,13225,13227,13229],{"class":68,"line":264},[66,13220,267],{"class":184},[66,13222,194],{"class":72},[66,13224,272],{"class":184},[66,13226,200],{"class":72},[66,13228,277],{"class":171},[66,13230,280],{"class":72},[66,13232,13233,13235,13237,13239,13241,13243,13245,13247],{"class":68,"line":283},[66,13234,286],{"class":184},[66,13236,200],{"class":72},[66,13238,291],{"class":171},[66,13240,206],{"class":72},[66,13242,26],{"class":184},[66,13244,200],{"class":72},[66,13246,300],{"class":184},[66,13248,303],{"class":72},[66,13250,13251,13253,13255,13257,13259,13261,13263,13265,13267,13269,13271,13273,13275,13277],{"class":68,"line":306},[66,13252,286],{"class":184},[66,13254,200],{"class":72},[66,13256,313],{"class":171},[66,13258,206],{"class":72},[66,13260,318],{"class":184},[66,13262,200],{"class":72},[66,13264,323],{"class":171},[66,13266,206],{"class":72},[66,13268,318],{"class":184},[66,13270,200],{"class":72},[66,13272,332],{"class":171},[66,13274,206],{"class":72},[66,13276,338],{"class":337},[66,13278,341],{"class":72},[66,13280,13281,13283,13285,13287,13289,13291,13293,13295,13297,13299],{"class":68,"line":344},[66,13282,286],{"class":184},[66,13284,200],{"class":72},[66,13286,351],{"class":171},[66,13288,206],{"class":72},[66,13290,209],{"class":72},[66,13292,3249],{"class":212},[66,13294,209],{"class":72},[66,13296,188],{"class":72},[66,13298,365],{"class":184},[66,13300,303],{"class":72},[66,13302,13303,13305,13307,13309,13311,13313,13315,13317,13319,13321],{"class":68,"line":370},[66,13304,286],{"class":184},[66,13306,200],{"class":72},[66,13308,377],{"class":171},[66,13310,206],{"class":72},[66,13312,209],{"class":72},[66,13314,3249],{"class":212},[66,13316,209],{"class":72},[66,13318,188],{"class":72},[66,13320,390],{"class":337},[66,13322,303],{"class":72},[66,13324,13325],{"class":68,"line":395},[66,13326,398],{"class":72},[66,13328,13329],{"class":68,"line":401},[66,13330,84],{"emptyLinePlaceholder":83},[66,13332,13333,13335,13337,13339,13341,13343],{"class":68,"line":406},[66,13334,409],{"class":184},[66,13336,194],{"class":72},[66,13338,414],{"class":184},[66,13340,200],{"class":72},[66,13342,419],{"class":171},[66,13344,422],{"class":72},[66,13346,13347,13349,13351,13353,13355,13357,13359,13361,13363,13365,13367],{"class":68,"line":425},[66,13348,428],{"class":184},[66,13350,200],{"class":72},[66,13352,433],{"class":171},[66,13354,436],{"class":72},[66,13356,440],{"class":439},[66,13358,443],{"class":72},[66,13360,446],{"class":76},[66,13362,200],{"class":72},[66,13364,451],{"class":76},[66,13366,454],{"class":72},[66,13368,178],{"class":72},[66,13370,13371,13373,13375,13377,13379,13381,13383,13385,13387,13389,13391,13393,13395,13397],{"class":68,"line":459},[66,13372,462],{"class":184},[66,13374,200],{"class":72},[66,13376,467],{"class":171},[66,13378,206],{"class":72},[66,13380,472],{"class":337},[66,13382,188],{"class":72},[66,13384,477],{"class":72},[66,13386,480],{"class":439},[66,13388,443],{"class":72},[66,13390,446],{"class":76},[66,13392,200],{"class":72},[66,13394,489],{"class":76},[66,13396,454],{"class":72},[66,13398,178],{"class":72},[66,13400,13401,13403,13405,13407,13409,13411,13413,13415,13417,13419,13421,13423,13425,13428,13430,13432,13434,13436],{"class":68,"line":496},[66,13402,499],{"class":184},[66,13404,200],{"class":72},[66,13406,504],{"class":171},[66,13408,206],{"class":72},[66,13410,209],{"class":72},[66,13412,4445],{"class":212},[66,13414,209],{"class":72},[66,13416,188],{"class":72},[66,13418,518],{"class":184},[66,13420,200],{"class":72},[66,13422,523],{"class":171},[66,13424,206],{"class":72},[66,13426,13427],{"class":337},"28",[66,13429,531],{"class":72},[66,13431,518],{"class":184},[66,13433,200],{"class":72},[66,13435,538],{"class":171},[66,13437,541],{"class":72},[66,13439,13440,13442,13444,13446,13448,13450,13453,13455],{"class":68,"line":544},[66,13441,499],{"class":184},[66,13443,200],{"class":72},[66,13445,504],{"class":171},[66,13447,206],{"class":72},[66,13449,209],{"class":72},[66,13451,13452],{"class":212},"Noto Sans JP、これで十分。",[66,13454,209],{"class":72},[66,13456,157],{"class":72},[66,13458,13459],{"class":68,"line":564},[66,13460,567],{"class":72},[66,13462,13463],{"class":68,"line":570},[66,13464,573],{"class":72},[66,13466,13467],{"class":68,"line":576},[66,13468,84],{"emptyLinePlaceholder":83},[66,13470,13471,13473,13475,13477,13479,13481,13483,13485],{"class":68,"line":581},[66,13472,584],{"class":184},[66,13474,188],{"class":72},[66,13476,191],{"class":184},[66,13478,194],{"class":72},[66,13480,414],{"class":184},[66,13482,200],{"class":72},[66,13484,597],{"class":171},[66,13486,422],{"class":72},[66,13488,13489,13491,13493,13495,13497],{"class":68,"line":602},[66,13490,222],{"class":90},[66,13492,191],{"class":184},[66,13494,227],{"class":72},[66,13496,230],{"class":72},[66,13498,178],{"class":72},[66,13500,13501,13503,13505,13507,13509,13511],{"class":68,"line":615},[66,13502,238],{"class":184},[66,13504,200],{"class":72},[66,13506,243],{"class":171},[66,13508,206],{"class":72},[66,13510,248],{"class":184},[66,13512,157],{"class":72},[66,13514,13515],{"class":68,"line":630},[66,13516,256],{"class":72},[66,13518,13519,13521,13523,13525,13527,13529,13531,13533,13535,13537,13539,13541,13543,13545,13547,13549,13551,13553,13555],{"class":68,"line":635},[66,13520,222],{"class":90},[66,13522,191],{"class":184},[66,13524,194],{"class":72},[66,13526,197],{"class":184},[66,13528,200],{"class":72},[66,13530,648],{"class":171},[66,13532,206],{"class":72},[66,13534,209],{"class":72},[66,13536,4588],{"class":212},[66,13538,209],{"class":72},[66,13540,188],{"class":72},[66,13542,662],{"class":184},[66,13544,188],{"class":72},[66,13546,667],{"class":337},[66,13548,670],{"class":72},[66,13550,191],{"class":184},[66,13552,227],{"class":72},[66,13554,230],{"class":72},[66,13556,178],{"class":72},[66,13558,13559,13561,13563,13565,13567,13569],{"class":68,"line":681},[66,13560,238],{"class":184},[66,13562,200],{"class":72},[66,13564,243],{"class":171},[66,13566,206],{"class":72},[66,13568,248],{"class":184},[66,13570,157],{"class":72},[66,13572,13573],{"class":68,"line":696},[66,13574,256],{"class":72},[66,13576,13577],{"class":68,"line":701},[66,13578,704],{"class":72},[18,13580,13581,13582,13585,13586,3559,13588,13590,13591,13593],{},"zip は ",[21,13583,3520],{"href":3518,"rel":13584},[25]," からダウンロード → 展開 → ",[45,13587,13065],{},[45,13589,710],{}," の横に置いて ",[45,13592,714],{},"。これで 1 ページの PDF が出る。",[14,13595,13597],{"id":13596},"variable-フォントではなく-static-ttf-を選ぶ","Variable フォントではなく static TTF を選ぶ",[18,13599,13600,13601,13604],{},"Google Fonts のページで ",[29,13602,13603],{},"Get font → Download all","、zip を展開すると、中に見た目が似ている 2 つの塊がある:",[1109,13606,13607,13617],{},[882,13608,13609,13612,13613,13616],{},[45,13610,13611],{},"NotoSansJP-VariableFont_wght.ttf"," (zip 直下) — weight 100〜900 を 1 ファイルに収めた ",[29,13614,13615],{},"variable font","、約 7 MB",[882,13618,13619,13622,13623,4639,13626,13629],{},[45,13620,13621],{},"static/"," ディレクトリ — ",[45,13624,13625],{},"NotoSansJP-Thin.ttf",[45,13627,13628],{},"NotoSansJP-Black.ttf"," まで weight 別に分かれた 9 本の TTF、各 5 MB",[18,13631,13632,1149],{},[29,13633,13634,13636],{},[45,13635,13621],{}," の方を使う",[18,13638,13639,13640,5330,13642,13644,13645,851,13648,851,13651,13654],{},"gpdf の TrueType パーサは意図して機能を絞ってある。グリフアウトライン、コンポジットグリフ、",[45,13641,857],{},[45,13643,732],{}," — 固定 weight のテキストを描画するために必要なテーブルは扱う。一方で variable font を動かすための ",[45,13646,13647],{},"fvar",[45,13649,13650],{},"gvar",[45,13652,13653],{},"HVAR"," は読まない。VariableFont_wght.ttf を渡すとパーサがエラーで止まるか、運が悪ければデフォルトインスタンスのグリフだけを拾って weight 指定を黙って無視する。設定した weight が反映されない理由が 2 週間分からなかった、ということになる。",[18,13656,13657],{},"ファイルサイズの観点でも variable は不利。variable font は weight 軸上のすべてのインスタンス分のアウトラインを 1 ファイルに持つ — それが設計意図だが、Regular しか使わないなら 8 weight 分の余計なデータを抱えることになる。static Regular が 5 MB、variable が 7 MB。どちらもサブセット化で削られるが、入力は static の方がクリーン。",[14,13659,13661],{"id":13660},"肝心なのはこの-4-行","肝心なのはこの 4 行",[18,13663,13664,13665,13667],{},"意味のあるコードは ",[45,13666,277],{}," のオプションだけ:",[57,13669,13671],{"className":59,"code":13670,"language":61,"meta":62,"style":62},"doc := gpdf.NewDocument(\n    gpdf.WithFont(\"NotoSansJP\", font),\n    gpdf.WithDefaultFont(\"NotoSansJP\", 11),\n)\n",[45,13672,13673,13687,13709,13731],{"__ignoreMap":62},[66,13674,13675,13677,13679,13681,13683,13685],{"class":68,"line":69},[66,13676,1000],{"class":184},[66,13678,194],{"class":72},[66,13680,272],{"class":184},[66,13682,200],{"class":72},[66,13684,277],{"class":171},[66,13686,280],{"class":72},[66,13688,13689,13691,13693,13695,13697,13699,13701,13703,13705,13707],{"class":68,"line":80},[66,13690,1015],{"class":184},[66,13692,200],{"class":72},[66,13694,351],{"class":171},[66,13696,206],{"class":72},[66,13698,209],{"class":72},[66,13700,3249],{"class":212},[66,13702,209],{"class":72},[66,13704,188],{"class":72},[66,13706,365],{"class":184},[66,13708,303],{"class":72},[66,13710,13711,13713,13715,13717,13719,13721,13723,13725,13727,13729],{"class":68,"line":87},[66,13712,1015],{"class":184},[66,13714,200],{"class":72},[66,13716,377],{"class":171},[66,13718,206],{"class":72},[66,13720,209],{"class":72},[66,13722,3249],{"class":212},[66,13724,209],{"class":72},[66,13726,188],{"class":72},[66,13728,390],{"class":337},[66,13730,303],{"class":72},[66,13732,13733],{"class":68,"line":97},[66,13734,157],{"class":72},[18,13736,13737,13738,13741,13742,13745,13746,13745,13749,13752,13753,13755],{},"ファミリ名 (",[45,13739,13740],{},"\"NotoSansJP\"",") は任意。gpdf はこれをルックアップキーとして使うだけで、ファイルパスでも、フォントのメタデータから読んだ名前でもない。チームで読みやすいなら ",[45,13743,13744],{},"\"body\""," でも ",[45,13747,13748],{},"\"jp\"",[45,13750,13751],{},"\"Noto\""," でも良い。",[45,13754,12487],{}," 側で同じ名前を指定していれば揃う。",[18,13757,13758,13760,13761,2980,13763,13765],{},[45,13759,377],{}," は、毎回の ",[45,13762,2991],{},[45,13764,3745],{}," を書かずに済ませるためのもの。これを省略すると gpdf は Helvetica にフォールバックする。Helvetica は CJK コードポイントを 1 つもカバーしていないので、見出しだけフォント指定しているようなコードだと、本文全部が豆腐 (□□□□) になって「なぜ見出しだけまともなんだ」と悩むことになる。",[14,13767,13769],{"id":13768},"weight-はどこまで登録すべきか","weight はどこまで登録すべきか",[18,13771,13772],{},"請求書・領収書・業務レポートなら Regular と Bold の 2 つで事足りる。",[57,13774,13776],{"className":59,"code":13775,"language":61,"meta":62,"style":62},"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",[45,13777,13778,13804,13830,13834,13848,13870,13892,13914],{"__ignoreMap":62},[66,13779,13780,13782,13784,13786,13788,13790,13792,13794,13796,13798,13800,13802],{"class":68,"line":69},[66,13781,939],{"class":184},[66,13783,188],{"class":72},[66,13785,944],{"class":184},[66,13787,194],{"class":72},[66,13789,197],{"class":184},[66,13791,200],{"class":72},[66,13793,203],{"class":171},[66,13795,206],{"class":72},[66,13797,209],{"class":72},[66,13799,3134],{"class":212},[66,13801,209],{"class":72},[66,13803,157],{"class":72},[66,13805,13806,13808,13810,13812,13814,13816,13818,13820,13822,13824,13826,13828],{"class":68,"line":80},[66,13807,967],{"class":184},[66,13809,188],{"class":72},[66,13811,972],{"class":184},[66,13813,194],{"class":72},[66,13815,197],{"class":184},[66,13817,200],{"class":72},[66,13819,203],{"class":171},[66,13821,206],{"class":72},[66,13823,209],{"class":72},[66,13825,8855],{"class":212},[66,13827,209],{"class":72},[66,13829,157],{"class":72},[66,13831,13832],{"class":68,"line":87},[66,13833,84],{"emptyLinePlaceholder":83},[66,13835,13836,13838,13840,13842,13844,13846],{"class":68,"line":97},[66,13837,1000],{"class":184},[66,13839,194],{"class":72},[66,13841,272],{"class":184},[66,13843,200],{"class":72},[66,13845,277],{"class":171},[66,13847,280],{"class":72},[66,13849,13850,13852,13854,13856,13858,13860,13862,13864,13866,13868],{"class":68,"line":109},[66,13851,1015],{"class":184},[66,13853,200],{"class":72},[66,13855,351],{"class":171},[66,13857,206],{"class":72},[66,13859,209],{"class":72},[66,13861,3249],{"class":212},[66,13863,209],{"class":72},[66,13865,188],{"class":72},[66,13867,1032],{"class":184},[66,13869,303],{"class":72},[66,13871,13872,13874,13876,13878,13880,13882,13884,13886,13888,13890],{"class":68,"line":119},[66,13873,1015],{"class":184},[66,13875,200],{"class":72},[66,13877,351],{"class":171},[66,13879,206],{"class":72},[66,13881,209],{"class":72},[66,13883,8993],{"class":212},[66,13885,209],{"class":72},[66,13887,188],{"class":72},[66,13889,1056],{"class":184},[66,13891,303],{"class":72},[66,13893,13894,13896,13898,13900,13902,13904,13906,13908,13910,13912],{"class":68,"line":124},[66,13895,1015],{"class":184},[66,13897,200],{"class":72},[66,13899,377],{"class":171},[66,13901,206],{"class":72},[66,13903,209],{"class":72},[66,13905,3249],{"class":212},[66,13907,209],{"class":72},[66,13909,188],{"class":72},[66,13911,390],{"class":337},[66,13913,303],{"class":72},[66,13915,13916],{"class":68,"line":134},[66,13917,157],{"class":72},[18,13919,13920,13922,13923,13925,13926,5330,13928,13930],{},[45,13921,1089],{}," というサフィックスで登録しておくと、",[45,13924,1093],{}," が自動でこちらを拾う。",[45,13927,12474],{},[45,13929,12477],{}," も同じ規約。ただし Noto Sans JP にイタリックは存在しない — CJK フォントには字形上の自然な斜体がないので、Noto 系列でも提供されていない。日本語で強調したい場合は色・サイズ・太字のどれかで差をつける。",[18,13932,13933],{},"パンフレットや見出しで Medium や SemiBold が欲しいときは、好きなサフィックスで登録して、ファミリ名で直接参照すればいい:",[57,13935,13937],{"className":59,"code":13936,"language":61,"meta":62,"style":62},"gpdf.WithFont(\"NotoSansJP-Medium\", medium)\n// ...\nc.Text(\"見出し\", template.FontFamily(\"NotoSansJP-Medium\"))\n",[45,13938,13939,13963,13968],{"__ignoreMap":62},[66,13940,13941,13943,13945,13947,13949,13951,13954,13956,13958,13961],{"class":68,"line":69},[66,13942,26],{"class":184},[66,13944,200],{"class":72},[66,13946,351],{"class":171},[66,13948,206],{"class":72},[66,13950,209],{"class":72},[66,13952,13953],{"class":212},"NotoSansJP-Medium",[66,13955,209],{"class":72},[66,13957,188],{"class":72},[66,13959,13960],{"class":184}," medium",[66,13962,157],{"class":72},[66,13964,13965],{"class":68,"line":80},[66,13966,13967],{"class":2209},"// ...\n",[66,13969,13970,13972,13974,13976,13978,13980,13983,13985,13987,13989,13991,13993,13995,13997,13999,14001],{"class":68,"line":87},[66,13971,480],{"class":184},[66,13973,200],{"class":72},[66,13975,504],{"class":171},[66,13977,206],{"class":72},[66,13979,209],{"class":72},[66,13981,13982],{"class":212},"見出し",[66,13984,209],{"class":72},[66,13986,188],{"class":72},[66,13988,518],{"class":184},[66,13990,200],{"class":72},[66,13992,5027],{"class":171},[66,13994,206],{"class":72},[66,13996,209],{"class":72},[66,13998,13953],{"class":212},[66,14000,209],{"class":72},[66,14002,5050],{"class":72},[18,14004,14005,14006,851,14008,851,14010,14012],{},"サフィックスで自動対応するのは ",[45,14007,1089],{},[45,14009,12474],{},[45,14011,12477],{}," の 3 つだけ。それ以外はファミリ名でアドレスする。",[14,14014,14015],{"id":14015},"サブセット化後の実サイズ",[18,14017,14018],{},"Noto Sans JP Regular はディスク上で約 5 MB。この数字を見て、フォント配信用 CDN を別に立てたり、PDF 生成後にフォントを剥がすポストプロセスを組んだりするチームが時々いる。gpdf 相手ならどちらも不要。",[18,14020,14021],{},"実際に PDF に入るバイト数はこれくらい:",[738,14023,14024,14036],{},[741,14025,14026],{},[744,14027,14028,14030,14033],{},[747,14029,2889],{},[747,14031,14032],{},"使用グリフ数",[747,14034,14035],{},"PDF 内のフォントデータ",[756,14037,14038,14049,14060,14071],{},[744,14039,14040,14043,14046],{},[761,14041,14042],{},"1 行のレシート (15 字)",[761,14044,14045],{},"約 14",[761,14047,14048],{},"約 11 KB",[744,14050,14051,14054,14057],{},[761,14052,14053],{},"一般的な請求書 (200 字)",[761,14055,14056],{},"約 80",[761,14058,14059],{},"約 28 KB",[744,14061,14062,14065,14068],{},[761,14063,14064],{},"10 ページのレポート (8,000 字)",[761,14066,14067],{},"約 900",[761,14069,14070],{},"約 180 KB",[744,14072,14073,14076,14079],{},[761,14074,14075],{},"JIS 第一水準フル使用",[761,14077,14078],{},"約 6,800",[761,14080,14081],{},"約 2.1 MB",[18,14083,14084],{},"(gpdf v1.0、static サブセット化有効時。CFF と hmtx のどこにグリフ ID が落ちるかで数 KB 前後する)",[18,14086,14087],{},"最終的に 50 KB の請求書 PDF なら、その半分以上がフォントデータ、ということになる。それでもサブセット化なしで 5 MB 丸々埋めるのと比べれば誤差みたいなもので、ビューアは即座に開く。",[14,14089,14091],{"id":14090},"noto-sans-jp-と-noto-sans-cjk-jp-を混同しない","Noto Sans JP と Noto Sans CJK JP を混同しない",[18,14093,14094],{},"日本語を扱える Noto 系ファミリは 2 つあって、名前が似ているので混同されやすい。中身は全く別物。",[18,14096,14097,14099],{},[29,14098,754],{}," が今回使う方。TTF 配布、日本語専用、weight ごとに別ファイル。Google Fonts からダウンロードできるのはこれ。",[18,14101,14102,14105,14106,14108,14109,14112],{},[29,14103,14104],{},"Noto Sans CJK JP"," は CJK 横断の大家族。OpenType Collection (",[45,14107,1119],{},") 形式で、日本語・簡体中国語・繁体中国語・韓国語のグリフを漢字統合 (Han unification) しつつ 1 ファイルに格納している。初期の Noto リリースや ",[45,14110,14111],{},"notofonts.github.io/noto-cjk"," に置かれているのはこちら。",[18,14114,14115,14116,14118,14119,14121],{},"gpdf は TTF をそのまま扱える。TTC はコンテナ形式なので、",[45,14117,351],{}," に渡す前にフェイスインデックスを選ぶ必要があるし、各フェイスの ",[45,14120,857],{}," は特定の CJK ロケール向けにチューニングされているため、漢字統合まわりの選択を暗黙にすることになる。JP 専用の TTF を選ぶ方が選択が明示的で事故が少ない。",[18,14123,14124,14125,14128,14129,3551,14132,14134],{},"新規プロジェクトなら Noto Sans JP を使う。レガシーで ",[45,14126,14127],{},"NotoSansCJK-Regular.ttc"," が既にリポジトリにある場合は、",[45,14130,14131],{},"pyftsubset",[45,14133,896],{}," で JP フェイスだけを抽出して TTF としてチェックインし直すのが推奨。",[14,14136,14137],{"id":14137},"バイナリに同梱する",[18,14139,14140],{},"PDF ジェネレーターはたいていコンテナで動く。フォントを一緒に配る一番きれいな方法はバイナリに焼き込むこと:",[57,14142,14144],{"className":59,"code":14143,"language":61,"meta":62,"style":62},"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",[45,14145,14146,14152,14156,14162,14174,14178,14186,14190,14194,14199,14212,14216,14226,14240,14263,14285,14289,14294],{"__ignoreMap":62},[66,14147,14148,14150],{"class":68,"line":69},[66,14149,73],{"class":72},[66,14151,77],{"class":76},[66,14153,14154],{"class":68,"line":80},[66,14155,84],{"emptyLinePlaceholder":83},[66,14157,14158,14160],{"class":68,"line":87},[66,14159,91],{"class":90},[66,14161,94],{"class":72},[66,14163,14164,14167,14169,14172],{"class":68,"line":97},[66,14165,14166],{"class":184},"    _ ",[66,14168,209],{"class":72},[66,14170,14171],{"class":76},"embed",[66,14173,106],{"class":72},[66,14175,14176],{"class":68,"line":109},[66,14177,84],{"emptyLinePlaceholder":83},[66,14179,14180,14182,14184],{"class":68,"line":119},[66,14181,100],{"class":72},[66,14183,129],{"class":76},[66,14185,106],{"class":72},[66,14187,14188],{"class":68,"line":124},[66,14189,157],{"class":72},[66,14191,14192],{"class":68,"line":134},[66,14193,84],{"emptyLinePlaceholder":83},[66,14195,14196],{"class":68,"line":144},[66,14197,14198],{"class":2209},"//go:embed NotoSansJP-Regular.ttf\n",[66,14200,14201,14204,14207,14209],{"class":68,"line":154},[66,14202,14203],{"class":72},"var",[66,14205,14206],{"class":184}," notoJP ",[66,14208,2474],{"class":72},[66,14210,14211],{"class":1620},"byte\n",[66,14213,14214],{"class":68,"line":160},[66,14215,84],{"emptyLinePlaceholder":83},[66,14217,14218,14220,14222,14224],{"class":68,"line":165},[66,14219,168],{"class":72},[66,14221,172],{"class":171},[66,14223,175],{"class":72},[66,14225,178],{"class":72},[66,14227,14228,14230,14232,14234,14236,14238],{"class":68,"line":181},[66,14229,267],{"class":184},[66,14231,194],{"class":72},[66,14233,272],{"class":184},[66,14235,200],{"class":72},[66,14237,277],{"class":171},[66,14239,280],{"class":72},[66,14241,14242,14244,14246,14248,14250,14252,14254,14256,14258,14261],{"class":68,"line":219},[66,14243,286],{"class":184},[66,14245,200],{"class":72},[66,14247,351],{"class":171},[66,14249,206],{"class":72},[66,14251,209],{"class":72},[66,14253,3249],{"class":212},[66,14255,209],{"class":72},[66,14257,188],{"class":72},[66,14259,14260],{"class":184}," notoJP",[66,14262,303],{"class":72},[66,14264,14265,14267,14269,14271,14273,14275,14277,14279,14281,14283],{"class":68,"line":235},[66,14266,286],{"class":184},[66,14268,200],{"class":72},[66,14270,377],{"class":171},[66,14272,206],{"class":72},[66,14274,209],{"class":72},[66,14276,3249],{"class":212},[66,14278,209],{"class":72},[66,14280,188],{"class":72},[66,14282,390],{"class":337},[66,14284,303],{"class":72},[66,14286,14287],{"class":68,"line":253},[66,14288,398],{"class":72},[66,14290,14291],{"class":68,"line":259},[66,14292,14293],{"class":2209},"    // ...\n",[66,14295,14296],{"class":68,"line":264},[66,14297,704],{"class":72},[18,14299,14300,14301,14304],{},"バイナリサイズは 8 MB → 13 MB 程度に膨らむ。代わりに Docker イメージの成果物が 1 つだけになり、",[45,14302,14303],{},"COPY --from=builder /app /app"," で済み、フォントファイル忘れで壊れたコンテナを誰かがリリースする事故がなくなる。1 日に数千 PDF 出すバッチジョブなら、これがデフォルトでいい。",[14,14306,1208],{"id":1208},[1109,14308,14309,14314,14321,14326],{},[882,14310,14311,14313],{},[21,14312,1222],{"href":1221}," — CJK TTF 全般に効く汎用レシピ",[882,14315,14316,3983,14318,14320],{},[21,14317,12960],{"href":2906},[45,14319,2570],{}," からの移行マップ",[882,14322,14323,14325],{},[21,14324,6595],{"href":2899}," — 主要ライブラリの CJK 対応比較",[882,14327,14328,3983,14332,14334],{},[21,14329,5247],{"href":14330,"rel":14331},"https://gpdf.dev/docs/guide/fonts",[25],[45,14333,351],{}," の完全リファレンス",[14,14336,1241],{"id":1240},[18,14338,2867],{},[57,14340,14341],{"className":1247,"code":1248,"language":1249,"meta":62,"style":62},[45,14342,14343],{"__ignoreMap":62},[66,14344,14345,14347,14349],{"class":68,"line":69},[66,14346,61],{"class":76},[66,14348,1258],{"class":212},[66,14350,1261],{"class":212},[18,14352,14353,1268,14356],{},[21,14354,2885],{"href":23,"rel":14355},[25],[21,14357,1273],{"href":14358,"rel":14359},"https://gpdf.dev/docs/quickstart",[25],[1275,14361,14362],{},"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":62,"searchDepth":80,"depth":80,"links":14364},[14365,14366,14367,14368,14369,14370,14371,14372,14373,14374,14375],{"id":16,"depth":80,"text":16},{"id":13056,"depth":80,"text":13057},{"id":13073,"depth":80,"text":13073},{"id":13596,"depth":80,"text":13597},{"id":13660,"depth":80,"text":13661},{"id":13768,"depth":80,"text":13769},{"id":14015,"depth":80,"text":14015},{"id":14090,"depth":80,"text":14091},{"id":14137,"depth":80,"text":14137},{"id":1208,"depth":80,"text":1208},{"id":1240,"depth":80,"text":1241},"static 版の NotoSansJP-Regular.ttf を gpdf.WithFont に登録するだけ。Variable フォントを避ける理由と、17,000 グリフが PDF 内で 40 KB 未満まで減るサブセット化の話。",{"name":14378,"totalTime":5291,"tools":14379,"steps":14381},"gpdf ドキュメントで Noto Sans JP をデフォルトフォントとして使う",[1298,14380],"NotoSansJP-Regular.ttf (Google Fonts の static TTF)",[14382,14385,14388,14391],{"name":14383,"text":14384},"Google Fonts から static TTF をダウンロード","fonts.google.com から Noto Sans JP を取得し、zip を展開して static/NotoSansJP-Regular.ttf を選ぶ。zip ルートにある NotoSansJP-VariableFont_wght.ttf は使わない。",{"name":14386,"text":14387},"起動時にバイト列として読み込む","os.ReadFile で NotoSansJP-Regular.ttf を読む。バイナリに同梱したい場合は //go:embed を使う。",{"name":14389,"text":14390},"ドキュメント生成時にフォントを登録","gpdf.WithFont(\"NotoSansJP\", fontBytes) と gpdf.WithDefaultFont(\"NotoSansJP\", 11) を gpdf.NewDocument に渡す。AddUTF8Font もフォントパスも不要。",{"name":14392,"text":14393},"日本語テキストを書いて PDF を生成","カラム内で c.Text(\"請求書\") を呼ぶ。doc.Generate() が []byte を返し、gpdf は実際に使ったグリフだけをサブセット化して最終 PDF に埋め込む。",{},{"title":13039,"description":14376},"ja/blog/004.noto-sans-jp-with-gpdf",[1322,1323,1324],"dfvr9vs908tZxQefPW9J1U9cpUZmXnkBlHmCD5Fg_IA",{"id":14400,"title":14401,"author":14402,"body":14403,"date":19141,"description":19142,"draft":1292,"extension":1293,"howTo":19143,"image":1316,"meta":19166,"navigation":83,"path":2906,"seo":19167,"stem":19168,"tags":19169,"updated":1316,"__hash__":19170},"blogJa/ja/blog/001.gofpdf-migration.md","アーカイブされた gofpdf から gpdf へ移行する完全ガイド",{"name":8,"url":9},{"type":11,"value":14404,"toc":19125},[14405,14407,14422,14433,14436,14452,14455,14459,14465,14468,14475,14481,14484,14488,14491,14517,14523,14527,14530,14795,14809,14836,14840,14843,14848,15020,15024,15405,15415,15427,15431,15441,15445,16173,16176,16180,16542,16559,16562,16566,16572,16576,16736,16749,16753,17197,17200,17209,17212,17215,17219,17239,17243,17558,17564,17568,18231,18246,18250,18263,18267,18529,18533,18852,18869,18873,18882,18959,18962,18965,18968,18971,19008,19011,19013,19019,19039,19048,19059,19068,19074,19076,19078,19090,19098,19100,19122],[14,14406,1335],{"id":1334},[18,14408,14409,14411,14412,14414,14415,14417,14418,14421],{},[29,14410,26],{}," は純 Go・外部依存ゼロ・ネイティブ CJK 対応の PDF ライブラリ。",[45,14413,2570],{}," のお作法も、",[45,14416,1930],{}," で座標を押し回す必要もない。Bootstrap 風の 12 カラムグリッドで宣言的に書ける。ベンチマークでは ",[29,14419,14420],{},"gofpdf より約 10 倍速い","。移行はだいたい「命令型のカーソル操作」を「宣言的なビルダー」に置き換える作業で、5 つの Before/After で全体像を示す。",[18,14423,14424,14425,14428,14429,14432],{},"先週、社内の同僚が新規プロジェクトで ",[45,14426,14427],{},"go get github.com/jung-kurt/gofpdf"," を叩いて、10 分後に GitHub のバナーのスクリーンショットを送ってきた。",[29,14430,14431],{},"「This repository has been archived by the owner. It is now read-only.」"," 続けて一言、「フォークもアーカイブされてるんだけど」。",[18,14434,14435],{},"両方ともそうです。",[18,14437,14438,14440,14441,14444,14445,14447,14448,14451],{},[45,14439,1353],{}," がアーカイブされたのは ",[29,14442,14443],{},"2021 年 9 月 8 日","。コミュニティフォークの ",[45,14446,1431],{}," も最終リリースは 2023 年で、",[29,14449,14450],{},"2025 年に正式にアーカイブ"," された。Stack Overflow や Qiita の Go PDF 記事の 7 割が今も指している先は、4 年以上 read-only。後継として推されていたフォークも消えた。",[18,14453,14454],{},"すでに gofpdf 製のコードが本番にあるなら、この記事は移行マップ。新規に「Go で PDF どうすればいい?」で gofpdf に手が伸びかけているなら、これがそのまま代替案。",[14,14456,14458],{"id":14457},"なぜ-gofpdf-は本当に死んだままなのか","なぜ gofpdf は本当に死んだままなのか",[18,14460,14461,14462,14464],{},"OSS は必ずしも死なない。メンテナが手を引いても誰かが拾うことはある。gofpdf もそのパターンになるはずだった。実際 ",[45,14463,1431],{}," はコードを再編成し、長年の bug を直し、PR を受け入れていた。普通に「後継」として機能していた。",[18,14466,14467],{},"それが 2025 年に止まった。README には「このプロジェクトは積極的にメンテナンスされていません。別のライブラリを検討してください」という一文。",[18,14469,14470,14471,14474],{},"理由はどうでもいい。問題は結果のほうだ。",[29,14472,14473],{},"gofpdf 依存の Go プロジェクトは、今や 2 段重ねの未メンテコードの上に座っている","。脆弱性が出ても誰も直さない。PDF 2.0 仕様は 2020 年に出たが、gofpdf はその大半に未対応。Go 1.25 のループ変数セマンティクスは今は gofpdf でも問題なく動くが、明日壊れたらフォークするのはあなたの仕事。",[18,14476,14477,14478,1149],{},"これは「ライブラリにバグがある」問題ではなく、",[29,14479,14480],{},"サプライチェーンの問題",[18,14482,14483],{},"日本のチームには特に効く。電子帳簿保存法対応で「PDF/A-3 で保存しろ」と言われたとき、未メンテのライブラリを根拠に出すのはきつい。監査も通らない。",[14,14485,14487],{"id":14486},"日本のチームが-gofpdf-で実際にやってきたこと","日本のチームが gofpdf で実際にやってきたこと",[18,14489,14490],{},"GitHub Issues や Qiita 投稿を眺めると、gofpdf の主用途はだいたい次の 4 つに集約される:",[879,14492,14493,14499,14505,14511],{},[882,14494,14495,14498],{},[29,14496,14497],{},"請求書・領収書・納品書"," — ヘッダ、宛先、明細表、合計、フッタ",[882,14500,14501,14504],{},[29,14502,14503],{},"帳票・レポート"," — ヘッダとページ番号が繰り返される複数ページの文書",[882,14506,14507,14510],{},[29,14508,14509],{},"証明書・チケット類"," — テンプレ画像の上に固定位置でテキストを乗せる",[882,14512,14513,14516],{},[29,14514,14515],{},"日本語・中国語・韓国語の PDF"," — 明細・配送ラベル・領収書",[18,14518,14519,14520,14522],{},"最初の 3 つは gpdf のビルダー API で素直に書ける。問題は 4 つ目。gofpdf は ",[45,14521,2570],{}," を呼び、TTF のパスを管理し、文字が基本面の外に出ないことを祈る、というお作法だった。gpdf は CJK を最初からファーストクラスとして扱う — TrueType を登録して日本語を書く、それで終わり。",[14,14524,14526],{"id":14525},"api-対応表","API 対応表",[18,14528,14529],{},"下の表がチートシート。後ろの章で 5 つの具体的な Before/After を見せる。",[738,14531,14532,14543],{},[741,14533,14534],{},[744,14535,14536,14539,14541],{},[747,14537,14538],{},"やりたいこと",[747,14540,1428],{},[747,14542,26],{},[756,14544,14545,14560,14581,14600,14617,14632,14650,14668,14683,14698,14716,14731,14746,14765,14780],{},[744,14546,14547,14550,14555],{},[761,14548,14549],{},"文書の作成",[761,14551,14552],{},[45,14553,14554],{},"gofpdf.New(\"P\", \"mm\", \"A4\", \"\")",[761,14556,14557],{},[45,14558,14559],{},"gpdf.NewDocument(gpdf.WithPageSize(document.A4))",[744,14561,14562,14565,14570],{},[761,14563,14564],{},"ページ追加",[761,14566,14567],{},[45,14568,14569],{},"pdf.AddPage()",[761,14571,14572,893,14575],{},[45,14573,14574],{},"doc.AddPage()",[6712,14576,206,14577,14580],{},[45,14578,14579],{},"*PageBuilder"," を返す)",[744,14582,14583,14586,14591],{},[761,14584,14585],{},"フォント指定",[761,14587,14588],{},[45,14589,14590],{},"pdf.SetFont(\"Arial\", \"B\", 16)",[761,14592,14593,851,14595,851,14597],{},[45,14594,12487],{},[45,14596,1093],{},[45,14598,14599],{},"template.FontSize(16)",[744,14601,14602,14605,14610],{},[761,14603,14604],{},"TTF 登録 (CJK)",[761,14606,14607],{},[45,14608,14609],{},"pdf.AddUTF8Font(\"noto\", \"\", \"NotoSansJP-Regular.ttf\")",[761,14611,14612,893,14614],{},[45,14613,8056],{},[6712,14615,14616],{},"(構築時に渡す)",[744,14618,14619,14622,14627],{},[761,14620,14621],{},"1 行テキスト",[761,14623,14624],{},[45,14625,14626],{},"pdf.Cell(40, 10, \"hi\")",[761,14628,14629],{},[45,14630,14631],{},"c.Text(\"hi\")",[744,14633,14634,14637,14642],{},[761,14635,14636],{},"折り返しテキスト",[761,14638,14639],{},[45,14640,14641],{},"pdf.MultiCell(0, 10, body, \"\", \"L\", false)",[761,14643,14644,893,14647],{},[45,14645,14646],{},"c.Text(body)",[6712,14648,14649],{},"(自動で折り返し)",[744,14651,14652,14655,14660],{},[761,14653,14654],{},"テキスト色",[761,14656,14657],{},[45,14658,14659],{},"pdf.SetTextColor(255, 0, 0)",[761,14661,14662,893,14665],{},[45,14663,14664],{},"template.TextColor(pdf.Red)",[6712,14666,14667],{},"(テキスト単位のオプション)",[744,14669,14670,14673,14678],{},[761,14671,14672],{},"横線",[761,14674,14675],{},[45,14676,14677],{},"pdf.Line(x1, y1, x2, y2)",[761,14679,14680],{},[45,14681,14682],{},"c.Line(template.LineThickness(document.Pt(1)))",[744,14684,14685,14688,14693],{},[761,14686,14687],{},"画像埋め込み",[761,14689,14690],{},[45,14691,14692],{},"pdf.ImageOptions(\"logo.png\", x, y, w, h, ...)",[761,14694,14695],{},[45,14696,14697],{},"c.Image(imgBytes, template.FitWidth(document.Mm(50)))",[744,14699,14700,14703,14708],{},[761,14701,14702],{},"カーソル位置",[761,14704,14705],{},[45,14706,14707],{},"pdf.SetXY(x, y)",[761,14709,14710],{},[6712,14711,14712,14713,454],{},"(なし — 行/列で書く、または ",[45,14714,14715],{},"page.Absolute(x, y, fn)",[744,14717,14718,14721,14726],{},[761,14719,14720],{},"全ページ共通のヘッダ",[761,14722,14723],{},[45,14724,14725],{},"pdf.SetHeaderFunc(fn)",[761,14727,14728],{},[45,14729,14730],{},"doc.Header(fn)",[744,14732,14733,14736,14741],{},[761,14734,14735],{},"全ページ共通のフッタ",[761,14737,14738],{},[45,14739,14740],{},"pdf.SetFooterFunc(fn)",[761,14742,14743],{},[45,14744,14745],{},"doc.Footer(fn)",[744,14747,14748,14751,14757],{},[761,14749,14750],{},"ページ番号",[761,14752,14753,14756],{},[45,14754,14755],{},"pdf.PageNo()","（手動）",[761,14758,14759,851,14762],{},[45,14760,14761],{},"c.PageNumber()",[45,14763,14764],{},"c.TotalPages()",[744,14766,14767,14770,14775],{},[761,14768,14769],{},"ファイル出力",[761,14771,14772],{},[45,14773,14774],{},"pdf.OutputFileAndClose(\"out.pdf\")",[761,14776,14777],{},[45,14778,14779],{},"data, _ := doc.Generate(); os.WriteFile(\"out.pdf\", data, 0o644)",[744,14781,14782,14785,14790],{},[761,14783,14784],{},"io.Writer に出力",[761,14786,14787],{},[45,14788,14789],{},"pdf.Output(w)",[761,14791,14792],{},[45,14793,14794],{},"doc.Render(w)",[18,14796,14797,14798,14801,14802,14805,14806,14808],{},"一番大きな変化は API の形。gofpdf は ",[29,14799,14800],{},"命令型"," で、カーソルを動かして書く。gpdf は ",[29,14803,14804],{},"宣言的"," で、行と列のツリーを記述してレイアウトエンジンに任せる。最初の数本は gpdf のほうが長く感じる。3 本目あたりで ",[45,14807,1930],{}," が恋しくなくなる。",[18,14810,14811,14812,851,14815,851,14818,14821,14822,851,14825,851,14828,14831,14832,14835],{},"単位の話。gofpdf は構築時に基準単位 (",[45,14813,14814],{},"\"mm\"",[45,14816,14817],{},"\"pt\"",[45,14819,14820],{},"\"in\"",") を選ぶ。gpdf は内部はすべて pt で固定し、呼び出し側で ",[45,14823,14824],{},"document.Mm(20)",[45,14826,14827],{},"document.Pt(12)",[45,14829,14830],{},"document.Cm(1)"," などのヘルパを使う。CSS に近い感覚で、ヘッダのマージンを ",[45,14833,14834],{},"document.Mm(15)"," で切ったあとは単位のことを意識しなくなる。",[14,14837,14839],{"id":14838},"before-after-1-一番シンプルな-pdf","Before / After 1: 一番シンプルな PDF",[18,14841,14842],{},"「Hello, World」のペア。gofpdf の短さこそが流行った理由なのは間違いない。gpdf 版は数行多い — カーソルを動かしているのではなく、ツリーを組んでいるからだ。",[18,14844,14845],{},[29,14846,14847],{},"Before — gofpdf:",[57,14849,14851],{"className":59,"code":14850,"language":61,"meta":62,"style":62},"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",[45,14852,14853,14859,14863,14874,14878,14888,14931,14941,14972,14998,15016],{"__ignoreMap":62},[66,14854,14855,14857],{"class":68,"line":69},[66,14856,73],{"class":72},[66,14858,77],{"class":76},[66,14860,14861],{"class":68,"line":80},[66,14862,84],{"emptyLinePlaceholder":83},[66,14864,14865,14867,14869,14872],{"class":68,"line":87},[66,14866,91],{"class":90},[66,14868,7462],{"class":72},[66,14870,14871],{"class":76},"github.com/jung-kurt/gofpdf",[66,14873,106],{"class":72},[66,14875,14876],{"class":68,"line":97},[66,14877,84],{"emptyLinePlaceholder":83},[66,14879,14880,14882,14884,14886],{"class":68,"line":109},[66,14881,168],{"class":72},[66,14883,172],{"class":171},[66,14885,175],{"class":72},[66,14887,178],{"class":72},[66,14889,14890,14892,14894,14897,14899,14901,14903,14905,14907,14909,14911,14913,14915,14917,14919,14921,14923,14925,14927,14929],{"class":68,"line":119},[66,14891,7438],{"class":184},[66,14893,194],{"class":72},[66,14895,14896],{"class":184}," gofpdf",[66,14898,200],{"class":72},[66,14900,7448],{"class":171},[66,14902,206],{"class":72},[66,14904,209],{"class":72},[66,14906,7455],{"class":212},[66,14908,209],{"class":72},[66,14910,188],{"class":72},[66,14912,7462],{"class":72},[66,14914,7465],{"class":212},[66,14916,209],{"class":72},[66,14918,188],{"class":72},[66,14920,7462],{"class":72},[66,14922,300],{"class":212},[66,14924,209],{"class":72},[66,14926,188],{"class":72},[66,14928,7480],{"class":72},[66,14930,157],{"class":72},[66,14932,14933,14935,14937,14939],{"class":68,"line":124},[66,14934,7487],{"class":184},[66,14936,200],{"class":72},[66,14938,419],{"class":171},[66,14940,422],{"class":72},[66,14942,14943,14945,14947,14949,14951,14953,14955,14957,14959,14961,14963,14965,14967,14970],{"class":68,"line":134},[66,14944,7487],{"class":184},[66,14946,200],{"class":72},[66,14948,1758],{"class":171},[66,14950,206],{"class":72},[66,14952,209],{"class":72},[66,14954,7508],{"class":212},[66,14956,209],{"class":72},[66,14958,188],{"class":72},[66,14960,7462],{"class":72},[66,14962,7517],{"class":212},[66,14964,209],{"class":72},[66,14966,188],{"class":72},[66,14968,14969],{"class":337}," 24",[66,14971,157],{"class":72},[66,14973,14974,14976,14978,14980,14982,14984,14986,14988,14990,14992,14994,14996],{"class":68,"line":144},[66,14975,7487],{"class":184},[66,14977,200],{"class":72},[66,14979,1934],{"class":171},[66,14981,206],{"class":72},[66,14983,7539],{"class":337},[66,14985,188],{"class":72},[66,14987,7544],{"class":337},[66,14989,188],{"class":72},[66,14991,7462],{"class":72},[66,14993,7551],{"class":212},[66,14995,209],{"class":72},[66,14997,157],{"class":72},[66,14999,15000,15002,15004,15006,15008,15010,15012,15014],{"class":68,"line":154},[66,15001,7487],{"class":184},[66,15003,200],{"class":72},[66,15005,1942],{"class":171},[66,15007,206],{"class":72},[66,15009,209],{"class":72},[66,15011,3456],{"class":212},[66,15013,209],{"class":72},[66,15015,157],{"class":72},[66,15017,15018],{"class":68,"line":160},[66,15019,704],{"class":72},[18,15021,15022],{},[29,15023,7668],{},[57,15025,15027],{"className":59,"code":15026,"language":61,"meta":62,"style":62},"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",[45,15028,15029,15035,15039,15045,15053,15061,15065,15073,15081,15089,15093,15097,15107,15121,15139,15169,15173,15177,15191,15215,15245,15283,15287,15291,15295,15313,15325,15339,15343,15383,15397,15401],{"__ignoreMap":62},[66,15030,15031,15033],{"class":68,"line":69},[66,15032,73],{"class":72},[66,15034,77],{"class":76},[66,15036,15037],{"class":68,"line":80},[66,15038,84],{"emptyLinePlaceholder":83},[66,15040,15041,15043],{"class":68,"line":87},[66,15042,91],{"class":90},[66,15044,94],{"class":72},[66,15046,15047,15049,15051],{"class":68,"line":97},[66,15048,100],{"class":72},[66,15050,103],{"class":76},[66,15052,106],{"class":72},[66,15054,15055,15057,15059],{"class":68,"line":109},[66,15056,100],{"class":72},[66,15058,114],{"class":76},[66,15060,106],{"class":72},[66,15062,15063],{"class":68,"line":119},[66,15064,84],{"emptyLinePlaceholder":83},[66,15066,15067,15069,15071],{"class":68,"line":124},[66,15068,100],{"class":72},[66,15070,129],{"class":76},[66,15072,106],{"class":72},[66,15074,15075,15077,15079],{"class":68,"line":134},[66,15076,100],{"class":72},[66,15078,139],{"class":76},[66,15080,106],{"class":72},[66,15082,15083,15085,15087],{"class":68,"line":144},[66,15084,100],{"class":72},[66,15086,149],{"class":76},[66,15088,106],{"class":72},[66,15090,15091],{"class":68,"line":154},[66,15092,157],{"class":72},[66,15094,15095],{"class":68,"line":160},[66,15096,84],{"emptyLinePlaceholder":83},[66,15098,15099,15101,15103,15105],{"class":68,"line":165},[66,15100,168],{"class":72},[66,15102,172],{"class":171},[66,15104,175],{"class":72},[66,15106,178],{"class":72},[66,15108,15109,15111,15113,15115,15117,15119],{"class":68,"line":181},[66,15110,267],{"class":184},[66,15112,194],{"class":72},[66,15114,272],{"class":184},[66,15116,200],{"class":72},[66,15118,277],{"class":171},[66,15120,280],{"class":72},[66,15122,15123,15125,15127,15129,15131,15133,15135,15137],{"class":68,"line":219},[66,15124,286],{"class":184},[66,15126,200],{"class":72},[66,15128,291],{"class":171},[66,15130,206],{"class":72},[66,15132,318],{"class":184},[66,15134,200],{"class":72},[66,15136,300],{"class":184},[66,15138,303],{"class":72},[66,15140,15141,15143,15145,15147,15149,15151,15153,15155,15157,15159,15161,15163,15165,15167],{"class":68,"line":235},[66,15142,286],{"class":184},[66,15144,200],{"class":72},[66,15146,313],{"class":171},[66,15148,206],{"class":72},[66,15150,318],{"class":184},[66,15152,200],{"class":72},[66,15154,323],{"class":171},[66,15156,206],{"class":72},[66,15158,318],{"class":184},[66,15160,200],{"class":72},[66,15162,332],{"class":171},[66,15164,206],{"class":72},[66,15166,338],{"class":337},[66,15168,341],{"class":72},[66,15170,15171],{"class":68,"line":253},[66,15172,398],{"class":72},[66,15174,15175],{"class":68,"line":259},[66,15176,84],{"emptyLinePlaceholder":83},[66,15178,15179,15181,15183,15185,15187,15189],{"class":68,"line":264},[66,15180,409],{"class":184},[66,15182,194],{"class":72},[66,15184,414],{"class":184},[66,15186,200],{"class":72},[66,15188,419],{"class":171},[66,15190,422],{"class":72},[66,15192,15193,15195,15197,15199,15201,15203,15205,15207,15209,15211,15213],{"class":68,"line":283},[66,15194,428],{"class":184},[66,15196,200],{"class":72},[66,15198,433],{"class":171},[66,15200,436],{"class":72},[66,15202,440],{"class":439},[66,15204,443],{"class":72},[66,15206,446],{"class":76},[66,15208,200],{"class":72},[66,15210,451],{"class":76},[66,15212,454],{"class":72},[66,15214,178],{"class":72},[66,15216,15217,15219,15221,15223,15225,15227,15229,15231,15233,15235,15237,15239,15241,15243],{"class":68,"line":306},[66,15218,462],{"class":184},[66,15220,200],{"class":72},[66,15222,467],{"class":171},[66,15224,206],{"class":72},[66,15226,472],{"class":337},[66,15228,188],{"class":72},[66,15230,477],{"class":72},[66,15232,480],{"class":439},[66,15234,443],{"class":72},[66,15236,446],{"class":76},[66,15238,200],{"class":72},[66,15240,489],{"class":76},[66,15242,454],{"class":72},[66,15244,178],{"class":72},[66,15246,15247,15249,15251,15253,15255,15257,15259,15261,15263,15265,15267,15269,15271,15273,15275,15277,15279,15281],{"class":68,"line":344},[66,15248,499],{"class":184},[66,15250,200],{"class":72},[66,15252,504],{"class":171},[66,15254,206],{"class":72},[66,15256,209],{"class":72},[66,15258,7551],{"class":212},[66,15260,209],{"class":72},[66,15262,188],{"class":72},[66,15264,518],{"class":184},[66,15266,200],{"class":72},[66,15268,523],{"class":171},[66,15270,206],{"class":72},[66,15272,528],{"class":337},[66,15274,531],{"class":72},[66,15276,518],{"class":184},[66,15278,200],{"class":72},[66,15280,538],{"class":171},[66,15282,541],{"class":72},[66,15284,15285],{"class":68,"line":370},[66,15286,567],{"class":72},[66,15288,15289],{"class":68,"line":395},[66,15290,573],{"class":72},[66,15292,15293],{"class":68,"line":401},[66,15294,84],{"emptyLinePlaceholder":83},[66,15296,15297,15299,15301,15303,15305,15307,15309,15311],{"class":68,"line":406},[66,15298,584],{"class":184},[66,15300,188],{"class":72},[66,15302,191],{"class":184},[66,15304,194],{"class":72},[66,15306,414],{"class":184},[66,15308,200],{"class":72},[66,15310,597],{"class":171},[66,15312,422],{"class":72},[66,15314,15315,15317,15319,15321,15323],{"class":68,"line":425},[66,15316,222],{"class":90},[66,15318,191],{"class":184},[66,15320,227],{"class":72},[66,15322,230],{"class":72},[66,15324,178],{"class":72},[66,15326,15327,15329,15331,15333,15335,15337],{"class":68,"line":459},[66,15328,238],{"class":184},[66,15330,200],{"class":72},[66,15332,243],{"class":171},[66,15334,206],{"class":72},[66,15336,248],{"class":184},[66,15338,157],{"class":72},[66,15340,15341],{"class":68,"line":496},[66,15342,256],{"class":72},[66,15344,15345,15347,15349,15351,15353,15355,15357,15359,15361,15363,15365,15367,15369,15371,15373,15375,15377,15379,15381],{"class":68,"line":544},[66,15346,222],{"class":90},[66,15348,191],{"class":184},[66,15350,194],{"class":72},[66,15352,197],{"class":184},[66,15354,200],{"class":72},[66,15356,648],{"class":171},[66,15358,206],{"class":72},[66,15360,209],{"class":72},[66,15362,3456],{"class":212},[66,15364,209],{"class":72},[66,15366,188],{"class":72},[66,15368,662],{"class":184},[66,15370,188],{"class":72},[66,15372,667],{"class":337},[66,15374,670],{"class":72},[66,15376,191],{"class":184},[66,15378,227],{"class":72},[66,15380,230],{"class":72},[66,15382,178],{"class":72},[66,15384,15385,15387,15389,15391,15393,15395],{"class":68,"line":564},[66,15386,238],{"class":184},[66,15388,200],{"class":72},[66,15390,243],{"class":171},[66,15392,206],{"class":72},[66,15394,248],{"class":184},[66,15396,157],{"class":72},[66,15398,15399],{"class":68,"line":570},[66,15400,256],{"class":72},[66,15402,15403],{"class":68,"line":576},[66,15404,704],{"class":72},[18,15406,15407,15408,15410,15411,15414],{},"グリッドが仕事をしている。",[45,15409,433],{}," は中身の高さで決まる行を追加し、",[45,15412,15413],{},"r.Col(12, ...)"," は「12 グリッド全部を占める列」。Bootstrap と同じ発想を PDF ページに適用しただけ。",[18,15416,15417,15419,15420,15422,15423,15426],{},[45,15418,3959],{}," はバイト列を返す。",[45,15421,1895],{}," に流したいなら ",[45,15424,15425],{},"Render(w)","。「ファイルを閉じる」処理がないのは gpdf がファイルハンドルを所有していないから。",[14,15428,15430],{"id":15429},"before-after-2-明細表","Before / After 2: 明細表",[18,15432,15433,15434,15436,15437,15440],{},"請求書の明細表は gofpdf が一番饒舌になる場所。built-in のテーブルがないので、",[45,15435,1934],{}," を二重ループで叩き、列幅を自分で管理し、",[45,15438,15439],{},"Ln(-1)"," で改行する。gofpdf の請求書チュートリアル記事は半分が表のボイラープレートで埋まっている。",[18,15442,15443],{},[29,15444,14847],{},[57,15446,15448],{"className":59,"code":15447,"language":61,"meta":62,"style":62},"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\", \"¥15,000\", \"¥600,000\"},\n    {\"バックエンド開発\",  \"60h\", \"¥15,000\", \"¥900,000\"},\n    {\"UI デザイン\",      \"20h\", \"¥12,000\", \"¥240,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",[45,15449,15450,15480,15505,15570,15629,15688,15747,15751,15777,15791,15829,15867,15905,15909,15932,15993,16051,16110,16169],{"__ignoreMap":62},[66,15451,15452,15454,15456,15458,15460,15462,15464,15466,15468,15470,15472,15474,15476,15478],{"class":68,"line":69},[66,15453,2276],{"class":184},[66,15455,200],{"class":72},[66,15457,1758],{"class":171},[66,15459,206],{"class":72},[66,15461,209],{"class":72},[66,15463,7508],{"class":212},[66,15465,209],{"class":72},[66,15467,188],{"class":72},[66,15469,7462],{"class":72},[66,15471,7517],{"class":212},[66,15473,209],{"class":72},[66,15475,188],{"class":72},[66,15477,390],{"class":337},[66,15479,157],{"class":72},[66,15481,15482,15484,15486,15489,15491,15494,15496,15499,15501,15503],{"class":68,"line":80},[66,15483,2276],{"class":184},[66,15485,200],{"class":72},[66,15487,15488],{"class":171},"SetFillColor",[66,15490,206],{"class":72},[66,15492,15493],{"class":337},"220",[66,15495,188],{"class":72},[66,15497,15498],{"class":337}," 220",[66,15500,188],{"class":72},[66,15502,15498],{"class":337},[66,15504,157],{"class":72},[66,15506,15507,15509,15511,15513,15515,15518,15520,15523,15525,15527,15529,15531,15533,15535,15538,15540,15542,15545,15547,15549,15552,15554,15556,15560,15562,15564,15566,15568],{"class":68,"line":87},[66,15508,2276],{"class":184},[66,15510,200],{"class":72},[66,15512,2008],{"class":171},[66,15514,206],{"class":72},[66,15516,15517],{"class":337},"80",[66,15519,188],{"class":72},[66,15521,15522],{"class":337}," 8",[66,15524,188],{"class":72},[66,15526,7462],{"class":72},[66,15528,11148],{"class":212},[66,15530,209],{"class":72},[66,15532,188],{"class":72},[66,15534,11247],{"class":72},[66,15536,15537],{"class":212},"1",[66,15539,209],{"class":72},[66,15541,188],{"class":72},[66,15543,15544],{"class":337}," 0",[66,15546,188],{"class":72},[66,15548,7462],{"class":72},[66,15550,15551],{"class":212},"L",[66,15553,209],{"class":72},[66,15555,188],{"class":72},[66,15557,15559],{"class":15558},"sfNiH"," true",[66,15561,188],{"class":72},[66,15563,15544],{"class":337},[66,15565,188],{"class":72},[66,15567,7480],{"class":72},[66,15569,157],{"class":72},[66,15571,15572,15574,15576,15578,15580,15582,15584,15586,15588,15590,15592,15594,15596,15598,15600,15602,15604,15606,15608,15610,15613,15615,15617,15619,15621,15623,15625,15627],{"class":68,"line":97},[66,15573,2276],{"class":184},[66,15575,200],{"class":72},[66,15577,2008],{"class":171},[66,15579,206],{"class":72},[66,15581,338],{"class":337},[66,15583,188],{"class":72},[66,15585,15522],{"class":337},[66,15587,188],{"class":72},[66,15589,7462],{"class":72},[66,15591,11157],{"class":212},[66,15593,209],{"class":72},[66,15595,188],{"class":72},[66,15597,11247],{"class":72},[66,15599,15537],{"class":212},[66,15601,209],{"class":72},[66,15603,188],{"class":72},[66,15605,15544],{"class":337},[66,15607,188],{"class":72},[66,15609,7462],{"class":72},[66,15611,15612],{"class":212},"C",[66,15614,209],{"class":72},[66,15616,188],{"class":72},[66,15618,15559],{"class":15558},[66,15620,188],{"class":72},[66,15622,15544],{"class":337},[66,15624,188],{"class":72},[66,15626,7480],{"class":72},[66,15628,157],{"class":72},[66,15630,15631,15633,15635,15637,15639,15641,15643,15645,15647,15649,15651,15653,15655,15657,15659,15661,15663,15665,15667,15669,15672,15674,15676,15678,15680,15682,15684,15686],{"class":68,"line":109},[66,15632,2276],{"class":184},[66,15634,200],{"class":72},[66,15636,2008],{"class":171},[66,15638,206],{"class":72},[66,15640,6426],{"class":337},[66,15642,188],{"class":72},[66,15644,15522],{"class":337},[66,15646,188],{"class":72},[66,15648,7462],{"class":72},[66,15650,11166],{"class":212},[66,15652,209],{"class":72},[66,15654,188],{"class":72},[66,15656,11247],{"class":72},[66,15658,15537],{"class":212},[66,15660,209],{"class":72},[66,15662,188],{"class":72},[66,15664,15544],{"class":337},[66,15666,188],{"class":72},[66,15668,7462],{"class":72},[66,15670,15671],{"class":212},"R",[66,15673,209],{"class":72},[66,15675,188],{"class":72},[66,15677,15559],{"class":15558},[66,15679,188],{"class":72},[66,15681,15544],{"class":337},[66,15683,188],{"class":72},[66,15685,7480],{"class":72},[66,15687,157],{"class":72},[66,15689,15690,15692,15694,15696,15698,15700,15702,15704,15706,15708,15710,15712,15714,15716,15718,15720,15722,15725,15727,15729,15731,15733,15735,15737,15739,15741,15743,15745],{"class":68,"line":119},[66,15691,2276],{"class":184},[66,15693,200],{"class":72},[66,15695,2008],{"class":171},[66,15697,206],{"class":72},[66,15699,6426],{"class":337},[66,15701,188],{"class":72},[66,15703,15522],{"class":337},[66,15705,188],{"class":72},[66,15707,7462],{"class":72},[66,15709,11175],{"class":212},[66,15711,209],{"class":72},[66,15713,188],{"class":72},[66,15715,11247],{"class":72},[66,15717,15537],{"class":212},[66,15719,209],{"class":72},[66,15721,188],{"class":72},[66,15723,15724],{"class":337}," 1",[66,15726,188],{"class":72},[66,15728,7462],{"class":72},[66,15730,15671],{"class":212},[66,15732,209],{"class":72},[66,15734,188],{"class":72},[66,15736,15559],{"class":15558},[66,15738,188],{"class":72},[66,15740,15544],{"class":337},[66,15742,188],{"class":72},[66,15744,7480],{"class":72},[66,15746,157],{"class":72},[66,15748,15749],{"class":68,"line":124},[66,15750,84],{"emptyLinePlaceholder":83},[66,15752,15753,15755,15757,15759,15761,15763,15765,15767,15769,15771,15773,15775],{"class":68,"line":134},[66,15754,2276],{"class":184},[66,15756,200],{"class":72},[66,15758,1758],{"class":171},[66,15760,206],{"class":72},[66,15762,209],{"class":72},[66,15764,7508],{"class":212},[66,15766,209],{"class":72},[66,15768,188],{"class":72},[66,15770,7480],{"class":72},[66,15772,188],{"class":72},[66,15774,390],{"class":337},[66,15776,157],{"class":72},[66,15778,15779,15782,15784,15787,15789],{"class":68,"line":144},[66,15780,15781],{"class":184},"items ",[66,15783,194],{"class":72},[66,15785,15786],{"class":72}," [][]",[66,15788,3565],{"class":1620},[66,15790,11190],{"class":72},[66,15792,15793,15796,15798,15800,15802,15804,15806,15809,15811,15813,15815,15817,15819,15821,15823,15825,15827],{"class":68,"line":154},[66,15794,15795],{"class":72},"    {",[66,15797,209],{"class":72},[66,15799,11200],{"class":212},[66,15801,209],{"class":72},[66,15803,188],{"class":72},[66,15805,7462],{"class":72},[66,15807,15808],{"class":212},"40h",[66,15810,209],{"class":72},[66,15812,188],{"class":72},[66,15814,7462],{"class":72},[66,15816,11218],{"class":212},[66,15818,209],{"class":72},[66,15820,188],{"class":72},[66,15822,7462],{"class":72},[66,15824,11227],{"class":212},[66,15826,209],{"class":72},[66,15828,11180],{"class":72},[66,15830,15831,15833,15835,15837,15839,15841,15844,15847,15849,15851,15853,15855,15857,15859,15861,15863,15865],{"class":68,"line":160},[66,15832,15795],{"class":72},[66,15834,209],{"class":72},[66,15836,11240],{"class":212},[66,15838,209],{"class":72},[66,15840,188],{"class":72},[66,15842,15843],{"class":72},"  \"",[66,15845,15846],{"class":212},"60h",[66,15848,209],{"class":72},[66,15850,188],{"class":72},[66,15852,7462],{"class":72},[66,15854,11218],{"class":212},[66,15856,209],{"class":72},[66,15858,188],{"class":72},[66,15860,7462],{"class":72},[66,15862,11267],{"class":212},[66,15864,209],{"class":72},[66,15866,11180],{"class":72},[66,15868,15869,15871,15873,15875,15877,15879,15882,15885,15887,15889,15891,15893,15895,15897,15899,15901,15903],{"class":68,"line":165},[66,15870,15795],{"class":72},[66,15872,209],{"class":72},[66,15874,11280],{"class":212},[66,15876,209],{"class":72},[66,15878,188],{"class":72},[66,15880,15881],{"class":72},"      \"",[66,15883,15884],{"class":212},"20h",[66,15886,209],{"class":72},[66,15888,188],{"class":72},[66,15890,7462],{"class":72},[66,15892,11299],{"class":212},[66,15894,209],{"class":72},[66,15896,188],{"class":72},[66,15898,7462],{"class":72},[66,15900,11308],{"class":212},[66,15902,209],{"class":72},[66,15904,11180],{"class":72},[66,15906,15907],{"class":68,"line":181},[66,15908,704],{"class":72},[66,15910,15911,15914,15917,15919,15922,15924,15927,15930],{"class":68,"line":219},[66,15912,15913],{"class":90},"for",[66,15915,15916],{"class":184}," _",[66,15918,188],{"class":72},[66,15920,15921],{"class":184}," row ",[66,15923,194],{"class":72},[66,15925,15926],{"class":90}," range",[66,15928,15929],{"class":184}," items ",[66,15931,11190],{"class":72},[66,15933,15934,15936,15938,15940,15942,15944,15946,15948,15950,15953,15955,15957,15960,15962,15964,15966,15968,15970,15972,15974,15976,15978,15980,15983,15985,15987,15989,15991],{"class":68,"line":235},[66,15935,7487],{"class":184},[66,15937,200],{"class":72},[66,15939,2008],{"class":171},[66,15941,206],{"class":72},[66,15943,15517],{"class":337},[66,15945,188],{"class":72},[66,15947,15522],{"class":337},[66,15949,188],{"class":72},[66,15951,15952],{"class":184}," row",[66,15954,2185],{"class":72},[66,15956,10249],{"class":337},[66,15958,15959],{"class":72},"],",[66,15961,7462],{"class":72},[66,15963,15537],{"class":212},[66,15965,209],{"class":72},[66,15967,188],{"class":72},[66,15969,15544],{"class":337},[66,15971,188],{"class":72},[66,15973,7462],{"class":72},[66,15975,15551],{"class":212},[66,15977,209],{"class":72},[66,15979,188],{"class":72},[66,15981,15982],{"class":15558}," false",[66,15984,188],{"class":72},[66,15986,15544],{"class":337},[66,15988,188],{"class":72},[66,15990,7480],{"class":72},[66,15992,157],{"class":72},[66,15994,15995,15997,15999,16001,16003,16005,16007,16009,16011,16013,16015,16017,16019,16021,16023,16025,16027,16029,16031,16033,16035,16037,16039,16041,16043,16045,16047,16049],{"class":68,"line":253},[66,15996,7487],{"class":184},[66,15998,200],{"class":72},[66,16000,2008],{"class":171},[66,16002,206],{"class":72},[66,16004,338],{"class":337},[66,16006,188],{"class":72},[66,16008,15522],{"class":337},[66,16010,188],{"class":72},[66,16012,15952],{"class":184},[66,16014,2185],{"class":72},[66,16016,15537],{"class":337},[66,16018,15959],{"class":72},[66,16020,7462],{"class":72},[66,16022,15537],{"class":212},[66,16024,209],{"class":72},[66,16026,188],{"class":72},[66,16028,15544],{"class":337},[66,16030,188],{"class":72},[66,16032,7462],{"class":72},[66,16034,15612],{"class":212},[66,16036,209],{"class":72},[66,16038,188],{"class":72},[66,16040,15982],{"class":15558},[66,16042,188],{"class":72},[66,16044,15544],{"class":337},[66,16046,188],{"class":72},[66,16048,7480],{"class":72},[66,16050,157],{"class":72},[66,16052,16053,16055,16057,16059,16061,16063,16065,16067,16069,16071,16073,16076,16078,16080,16082,16084,16086,16088,16090,16092,16094,16096,16098,16100,16102,16104,16106,16108],{"class":68,"line":259},[66,16054,7487],{"class":184},[66,16056,200],{"class":72},[66,16058,2008],{"class":171},[66,16060,206],{"class":72},[66,16062,6426],{"class":337},[66,16064,188],{"class":72},[66,16066,15522],{"class":337},[66,16068,188],{"class":72},[66,16070,15952],{"class":184},[66,16072,2185],{"class":72},[66,16074,16075],{"class":337},"2",[66,16077,15959],{"class":72},[66,16079,7462],{"class":72},[66,16081,15537],{"class":212},[66,16083,209],{"class":72},[66,16085,188],{"class":72},[66,16087,15544],{"class":337},[66,16089,188],{"class":72},[66,16091,7462],{"class":72},[66,16093,15671],{"class":212},[66,16095,209],{"class":72},[66,16097,188],{"class":72},[66,16099,15982],{"class":15558},[66,16101,188],{"class":72},[66,16103,15544],{"class":337},[66,16105,188],{"class":72},[66,16107,7480],{"class":72},[66,16109,157],{"class":72},[66,16111,16112,16114,16116,16118,16120,16122,16124,16126,16128,16130,16132,16135,16137,16139,16141,16143,16145,16147,16149,16151,16153,16155,16157,16159,16161,16163,16165,16167],{"class":68,"line":264},[66,16113,7487],{"class":184},[66,16115,200],{"class":72},[66,16117,2008],{"class":171},[66,16119,206],{"class":72},[66,16121,6426],{"class":337},[66,16123,188],{"class":72},[66,16125,15522],{"class":337},[66,16127,188],{"class":72},[66,16129,15952],{"class":184},[66,16131,2185],{"class":72},[66,16133,16134],{"class":337},"3",[66,16136,15959],{"class":72},[66,16138,7462],{"class":72},[66,16140,15537],{"class":212},[66,16142,209],{"class":72},[66,16144,188],{"class":72},[66,16146,15724],{"class":337},[66,16148,188],{"class":72},[66,16150,7462],{"class":72},[66,16152,15671],{"class":212},[66,16154,209],{"class":72},[66,16156,188],{"class":72},[66,16158,15982],{"class":15558},[66,16160,188],{"class":72},[66,16162,15544],{"class":337},[66,16164,188],{"class":72},[66,16166,7480],{"class":72},[66,16168,157],{"class":72},[66,16170,16171],{"class":68,"line":283},[66,16172,704],{"class":72},[18,16174,16175],{},"列幅を頭で計算しながら書く。品目名が折り返したら破綻する。",[18,16177,16178],{},[29,16179,7668],{},[57,16181,16183],{"className":59,"code":16182,"language":61,"meta":62,"style":62},"page.AutoRow(func(r *template.RowBuilder) {\n    r.Col(12, func(c *template.ColBuilder) {\n        c.Table(\n            []string{\"品目\", \"数量\", \"単価\", \"金額\"},\n            [][]string{\n                {\"フロントエンド開発\", \"40h\", \"¥15,000\", \"¥600,000\"},\n                {\"バックエンド開発\",  \"60h\", \"¥15,000\", \"¥900,000\"},\n                {\"UI デザイン\",      \"20h\", \"¥12,000\", \"¥240,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",[45,16184,16185,16209,16239,16249,16290,16299,16336,16372,16408,16413,16440,16450,16460,16478,16500,16505,16529,16534,16538],{"__ignoreMap":62},[66,16186,16187,16189,16191,16193,16195,16197,16199,16201,16203,16205,16207],{"class":68,"line":69},[66,16188,1926],{"class":184},[66,16190,200],{"class":72},[66,16192,433],{"class":171},[66,16194,436],{"class":72},[66,16196,440],{"class":439},[66,16198,443],{"class":72},[66,16200,446],{"class":76},[66,16202,200],{"class":72},[66,16204,451],{"class":76},[66,16206,454],{"class":72},[66,16208,178],{"class":72},[66,16210,16211,16213,16215,16217,16219,16221,16223,16225,16227,16229,16231,16233,16235,16237],{"class":68,"line":80},[66,16212,3673],{"class":184},[66,16214,200],{"class":72},[66,16216,467],{"class":171},[66,16218,206],{"class":72},[66,16220,472],{"class":337},[66,16222,188],{"class":72},[66,16224,477],{"class":72},[66,16226,480],{"class":439},[66,16228,443],{"class":72},[66,16230,446],{"class":76},[66,16232,200],{"class":72},[66,16234,489],{"class":76},[66,16236,454],{"class":72},[66,16238,178],{"class":72},[66,16240,16241,16243,16245,16247],{"class":68,"line":87},[66,16242,3704],{"class":184},[66,16244,200],{"class":72},[66,16246,11131],{"class":171},[66,16248,280],{"class":72},[66,16250,16251,16254,16256,16258,16260,16262,16264,16266,16268,16270,16272,16274,16276,16278,16280,16282,16284,16286,16288],{"class":68,"line":97},[66,16252,16253],{"class":72},"            []",[66,16255,3565],{"class":1620},[66,16257,11143],{"class":72},[66,16259,209],{"class":72},[66,16261,11148],{"class":212},[66,16263,209],{"class":72},[66,16265,188],{"class":72},[66,16267,7462],{"class":72},[66,16269,11157],{"class":212},[66,16271,209],{"class":72},[66,16273,188],{"class":72},[66,16275,7462],{"class":72},[66,16277,11166],{"class":212},[66,16279,209],{"class":72},[66,16281,188],{"class":72},[66,16283,7462],{"class":72},[66,16285,11175],{"class":212},[66,16287,209],{"class":72},[66,16289,11180],{"class":72},[66,16291,16292,16295,16297],{"class":68,"line":109},[66,16293,16294],{"class":72},"            [][]",[66,16296,3565],{"class":1620},[66,16298,11190],{"class":72},[66,16300,16301,16304,16306,16308,16310,16312,16314,16316,16318,16320,16322,16324,16326,16328,16330,16332,16334],{"class":68,"line":119},[66,16302,16303],{"class":72},"                {",[66,16305,209],{"class":72},[66,16307,11200],{"class":212},[66,16309,209],{"class":72},[66,16311,188],{"class":72},[66,16313,7462],{"class":72},[66,16315,15808],{"class":212},[66,16317,209],{"class":72},[66,16319,188],{"class":72},[66,16321,7462],{"class":72},[66,16323,11218],{"class":212},[66,16325,209],{"class":72},[66,16327,188],{"class":72},[66,16329,7462],{"class":72},[66,16331,11227],{"class":212},[66,16333,209],{"class":72},[66,16335,11180],{"class":72},[66,16337,16338,16340,16342,16344,16346,16348,16350,16352,16354,16356,16358,16360,16362,16364,16366,16368,16370],{"class":68,"line":124},[66,16339,16303],{"class":72},[66,16341,209],{"class":72},[66,16343,11240],{"class":212},[66,16345,209],{"class":72},[66,16347,188],{"class":72},[66,16349,15843],{"class":72},[66,16351,15846],{"class":212},[66,16353,209],{"class":72},[66,16355,188],{"class":72},[66,16357,7462],{"class":72},[66,16359,11218],{"class":212},[66,16361,209],{"class":72},[66,16363,188],{"class":72},[66,16365,7462],{"class":72},[66,16367,11267],{"class":212},[66,16369,209],{"class":72},[66,16371,11180],{"class":72},[66,16373,16374,16376,16378,16380,16382,16384,16386,16388,16390,16392,16394,16396,16398,16400,16402,16404,16406],{"class":68,"line":134},[66,16375,16303],{"class":72},[66,16377,209],{"class":72},[66,16379,11280],{"class":212},[66,16381,209],{"class":72},[66,16383,188],{"class":72},[66,16385,15881],{"class":72},[66,16387,15884],{"class":212},[66,16389,209],{"class":72},[66,16391,188],{"class":72},[66,16393,7462],{"class":72},[66,16395,11299],{"class":212},[66,16397,209],{"class":72},[66,16399,188],{"class":72},[66,16401,7462],{"class":72},[66,16403,11308],{"class":212},[66,16405,209],{"class":72},[66,16407,11180],{"class":72},[66,16409,16410],{"class":68,"line":144},[66,16411,16412],{"class":72},"            },\n",[66,16414,16415,16418,16420,16422,16424,16426,16428,16430,16432,16434,16436,16438],{"class":68,"line":154},[66,16416,16417],{"class":184},"            template",[66,16419,200],{"class":72},[66,16421,11327],{"class":171},[66,16423,206],{"class":72},[66,16425,11332],{"class":337},[66,16427,188],{"class":72},[66,16429,11337],{"class":337},[66,16431,188],{"class":72},[66,16433,11337],{"class":337},[66,16435,188],{"class":72},[66,16437,11346],{"class":337},[66,16439,303],{"class":72},[66,16441,16442,16444,16446,16448],{"class":68,"line":160},[66,16443,16417],{"class":184},[66,16445,200],{"class":72},[66,16447,11357],{"class":171},[66,16449,280],{"class":72},[66,16451,16452,16454,16456,16458],{"class":68,"line":165},[66,16453,11322],{"class":184},[66,16455,200],{"class":72},[66,16457,538],{"class":171},[66,16459,11371],{"class":72},[66,16461,16462,16464,16466,16468,16470,16472,16474,16476],{"class":68,"line":181},[66,16463,11322],{"class":184},[66,16465,200],{"class":72},[66,16467,11380],{"class":171},[66,16469,206],{"class":72},[66,16471,2276],{"class":184},[66,16473,200],{"class":72},[66,16475,11389],{"class":184},[66,16477,303],{"class":72},[66,16479,16480,16482,16484,16486,16488,16490,16492,16494,16496,16498],{"class":68,"line":219},[66,16481,11322],{"class":184},[66,16483,200],{"class":72},[66,16485,11400],{"class":171},[66,16487,206],{"class":72},[66,16489,2276],{"class":184},[66,16491,200],{"class":72},[66,16493,11409],{"class":171},[66,16495,206],{"class":72},[66,16497,11414],{"class":337},[66,16499,11417],{"class":72},[66,16501,16502],{"class":68,"line":235},[66,16503,16504],{"class":72},"            ),\n",[66,16506,16507,16509,16511,16514,16516,16518,16520,16522,16524,16527],{"class":68,"line":253},[66,16508,16417],{"class":184},[66,16510,200],{"class":72},[66,16512,16513],{"class":171},"TableStripe",[66,16515,206],{"class":72},[66,16517,2276],{"class":184},[66,16519,200],{"class":72},[66,16521,11409],{"class":171},[66,16523,206],{"class":72},[66,16525,16526],{"class":337},"0xF5F5F5",[66,16528,11417],{"class":72},[66,16530,16531],{"class":68,"line":259},[66,16532,16533],{"class":72},"        )\n",[66,16535,16536],{"class":68,"line":264},[66,16537,573],{"class":72},[66,16539,16540],{"class":68,"line":283},[66,16541,3731],{"class":72},[18,16543,16544,16547,16548,16551,16552,16555,16556,16558],{},[45,16545,16546],{},"ColumnWidths(50, 15, 15, 20)"," の数字は ",[29,16549,16550],{},"絶対 mm ではなく、表が乗っている列の中での割合","。同じ表を ",[45,16553,16554],{},"r.Col(6, ...)"," の中に入れても、この割合のままうまく収まる。",[45,16557,2008],{}," をラップしないと届かなかった抽象。",[18,16560,16561],{},"折り返しは自動。改ページも自動 — 表が下マージンを越えたら、次のページにヘッダ行が再描画される。",[14,16563,16565],{"id":16564},"before-after-3-お作法なしで日本語を書く","Before / After 3: お作法なしで日本語を書く",[18,16567,16568,16569,16571],{},"gofpdf を捨てる決め手はここだった。gofpdf で日本語を出すには ",[45,16570,2570],{}," を呼び、ディスク上の TTF を指し、フォントをセットして、祈る。サブセット化はだいたい動く。一部の TTF はグリッド ID 衝突を起こして文字化けを吐く。エラーメッセージは何の役にも立たない。",[18,16573,16574],{},[29,16575,14847],{},[57,16577,16579],{"className":59,"code":16578,"language":61,"meta":62,"style":62},"pdf := gofpdf.New(\"P\", \"mm\", \"A4\", \"\")\npdf.AddUTF8Font(\"notosansjp\", \"\", \"NotoSansJP-Regular.ttf\")\npdf.AddPage()\npdf.SetFont(\"notosansjp\", \"\", 14)\npdf.Cell(0, 10, \"こんにちは、世界。\")\npdf.OutputFileAndClose(\"ja.pdf\")\n",[45,16580,16581,16624,16654,16664,16691,16717],{"__ignoreMap":62},[66,16582,16583,16586,16588,16590,16592,16594,16596,16598,16600,16602,16604,16606,16608,16610,16612,16614,16616,16618,16620,16622],{"class":68,"line":69},[66,16584,16585],{"class":184},"pdf ",[66,16587,194],{"class":72},[66,16589,14896],{"class":184},[66,16591,200],{"class":72},[66,16593,7448],{"class":171},[66,16595,206],{"class":72},[66,16597,209],{"class":72},[66,16599,7455],{"class":212},[66,16601,209],{"class":72},[66,16603,188],{"class":72},[66,16605,7462],{"class":72},[66,16607,7465],{"class":212},[66,16609,209],{"class":72},[66,16611,188],{"class":72},[66,16613,7462],{"class":72},[66,16615,300],{"class":212},[66,16617,209],{"class":72},[66,16619,188],{"class":72},[66,16621,7480],{"class":72},[66,16623,157],{"class":72},[66,16625,16626,16628,16630,16632,16634,16636,16638,16640,16642,16644,16646,16648,16650,16652],{"class":68,"line":80},[66,16627,2276],{"class":184},[66,16629,200],{"class":72},[66,16631,2570],{"class":171},[66,16633,206],{"class":72},[66,16635,209],{"class":72},[66,16637,3761],{"class":212},[66,16639,209],{"class":72},[66,16641,188],{"class":72},[66,16643,7480],{"class":72},[66,16645,188],{"class":72},[66,16647,7462],{"class":72},[66,16649,3134],{"class":212},[66,16651,209],{"class":72},[66,16653,157],{"class":72},[66,16655,16656,16658,16660,16662],{"class":68,"line":87},[66,16657,2276],{"class":184},[66,16659,200],{"class":72},[66,16661,419],{"class":171},[66,16663,422],{"class":72},[66,16665,16666,16668,16670,16672,16674,16676,16678,16680,16682,16684,16686,16689],{"class":68,"line":97},[66,16667,2276],{"class":184},[66,16669,200],{"class":72},[66,16671,1758],{"class":171},[66,16673,206],{"class":72},[66,16675,209],{"class":72},[66,16677,3761],{"class":212},[66,16679,209],{"class":72},[66,16681,188],{"class":72},[66,16683,7480],{"class":72},[66,16685,188],{"class":72},[66,16687,16688],{"class":337}," 14",[66,16690,157],{"class":72},[66,16692,16693,16695,16697,16699,16701,16703,16705,16707,16709,16711,16713,16715],{"class":68,"line":109},[66,16694,2276],{"class":184},[66,16696,200],{"class":72},[66,16698,1934],{"class":171},[66,16700,206],{"class":72},[66,16702,10249],{"class":337},[66,16704,188],{"class":72},[66,16706,7544],{"class":337},[66,16708,188],{"class":72},[66,16710,7462],{"class":72},[66,16712,3371],{"class":212},[66,16714,209],{"class":72},[66,16716,157],{"class":72},[66,16718,16719,16721,16723,16725,16727,16729,16732,16734],{"class":68,"line":119},[66,16720,2276],{"class":184},[66,16722,200],{"class":72},[66,16724,1942],{"class":171},[66,16726,206],{"class":72},[66,16728,209],{"class":72},[66,16730,16731],{"class":212},"ja.pdf",[66,16733,209],{"class":72},[66,16735,157],{"class":72},[18,16737,16738,16739,16742,16743,16745,16746,16748],{},"地雷が 2 つある。TTF が ",[29,16740,16741],{},"実行時に"," 指定パスに存在している必要があり、Docker イメージにフォントを同梱する手間が発生する。",[45,16744,1934],{}," の幅を ",[45,16747,10249],{}," にすると「右マージンまで」になるが、CJK では幅推定が全角を正しく数えず、はみ出してクリップされることがある。",[18,16750,16751],{},[29,16752,7668],{},[57,16754,16756],{"className":59,"code":16755,"language":61,"meta":62,"style":62},"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(\"NotoSansJP-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(\"NotoSansJP\", fontData),\n        gpdf.WithDefaultFont(\"NotoSansJP\", 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(\"吾輩は猫である。名前はまだ無い。\")\n            c.Text(\"東京都渋谷区神宮前1-2-3\")\n        })\n    })\n\n    data, _ := doc.Generate()\n    os.WriteFile(\"ja.pdf\", data, 0o644)\n}\n",[45,16757,16758,16764,16768,16774,16782,16790,16794,16802,16810,16818,16822,16826,16836,16863,16875,16889,16893,16897,16911,16929,16959,16982,17004,17008,17012,17026,17050,17080,17098,17117,17136,17140,17144,17148,17166,17193],{"__ignoreMap":62},[66,16759,16760,16762],{"class":68,"line":69},[66,16761,73],{"class":72},[66,16763,77],{"class":76},[66,16765,16766],{"class":68,"line":80},[66,16767,84],{"emptyLinePlaceholder":83},[66,16769,16770,16772],{"class":68,"line":87},[66,16771,91],{"class":90},[66,16773,94],{"class":72},[66,16775,16776,16778,16780],{"class":68,"line":97},[66,16777,100],{"class":72},[66,16779,103],{"class":76},[66,16781,106],{"class":72},[66,16783,16784,16786,16788],{"class":68,"line":109},[66,16785,100],{"class":72},[66,16787,114],{"class":76},[66,16789,106],{"class":72},[66,16791,16792],{"class":68,"line":119},[66,16793,84],{"emptyLinePlaceholder":83},[66,16795,16796,16798,16800],{"class":68,"line":124},[66,16797,100],{"class":72},[66,16799,129],{"class":76},[66,16801,106],{"class":72},[66,16803,16804,16806,16808],{"class":68,"line":134},[66,16805,100],{"class":72},[66,16807,139],{"class":76},[66,16809,106],{"class":72},[66,16811,16812,16814,16816],{"class":68,"line":144},[66,16813,100],{"class":72},[66,16815,149],{"class":76},[66,16817,106],{"class":72},[66,16819,16820],{"class":68,"line":154},[66,16821,157],{"class":72},[66,16823,16824],{"class":68,"line":160},[66,16825,84],{"emptyLinePlaceholder":83},[66,16827,16828,16830,16832,16834],{"class":68,"line":165},[66,16829,168],{"class":72},[66,16831,172],{"class":171},[66,16833,175],{"class":72},[66,16835,178],{"class":72},[66,16837,16838,16841,16843,16845,16847,16849,16851,16853,16855,16857,16859,16861],{"class":68,"line":181},[66,16839,16840],{"class":184},"    fontData",[66,16842,188],{"class":72},[66,16844,191],{"class":184},[66,16846,194],{"class":72},[66,16848,197],{"class":184},[66,16850,200],{"class":72},[66,16852,203],{"class":171},[66,16854,206],{"class":72},[66,16856,209],{"class":72},[66,16858,3134],{"class":212},[66,16860,209],{"class":72},[66,16862,157],{"class":72},[66,16864,16865,16867,16869,16871,16873],{"class":68,"line":219},[66,16866,222],{"class":90},[66,16868,191],{"class":184},[66,16870,227],{"class":72},[66,16872,230],{"class":72},[66,16874,178],{"class":72},[66,16876,16877,16879,16881,16883,16885,16887],{"class":68,"line":235},[66,16878,238],{"class":184},[66,16880,200],{"class":72},[66,16882,243],{"class":171},[66,16884,206],{"class":72},[66,16886,248],{"class":184},[66,16888,157],{"class":72},[66,16890,16891],{"class":68,"line":253},[66,16892,256],{"class":72},[66,16894,16895],{"class":68,"line":259},[66,16896,84],{"emptyLinePlaceholder":83},[66,16898,16899,16901,16903,16905,16907,16909],{"class":68,"line":264},[66,16900,267],{"class":184},[66,16902,194],{"class":72},[66,16904,272],{"class":184},[66,16906,200],{"class":72},[66,16908,277],{"class":171},[66,16910,280],{"class":72},[66,16912,16913,16915,16917,16919,16921,16923,16925,16927],{"class":68,"line":283},[66,16914,286],{"class":184},[66,16916,200],{"class":72},[66,16918,291],{"class":171},[66,16920,206],{"class":72},[66,16922,318],{"class":184},[66,16924,200],{"class":72},[66,16926,300],{"class":184},[66,16928,303],{"class":72},[66,16930,16931,16933,16935,16937,16939,16941,16943,16945,16947,16949,16951,16953,16955,16957],{"class":68,"line":306},[66,16932,286],{"class":184},[66,16934,200],{"class":72},[66,16936,313],{"class":171},[66,16938,206],{"class":72},[66,16940,318],{"class":184},[66,16942,200],{"class":72},[66,16944,323],{"class":171},[66,16946,206],{"class":72},[66,16948,318],{"class":184},[66,16950,200],{"class":72},[66,16952,332],{"class":171},[66,16954,206],{"class":72},[66,16956,338],{"class":337},[66,16958,341],{"class":72},[66,16960,16961,16963,16965,16967,16969,16971,16973,16975,16977,16980],{"class":68,"line":344},[66,16962,286],{"class":184},[66,16964,200],{"class":72},[66,16966,351],{"class":171},[66,16968,206],{"class":72},[66,16970,209],{"class":72},[66,16972,3249],{"class":212},[66,16974,209],{"class":72},[66,16976,188],{"class":72},[66,16978,16979],{"class":184}," fontData",[66,16981,303],{"class":72},[66,16983,16984,16986,16988,16990,16992,16994,16996,16998,17000,17002],{"class":68,"line":370},[66,16985,286],{"class":184},[66,16987,200],{"class":72},[66,16989,377],{"class":171},[66,16991,206],{"class":72},[66,16993,209],{"class":72},[66,16995,3249],{"class":212},[66,16997,209],{"class":72},[66,16999,188],{"class":72},[66,17001,16688],{"class":337},[66,17003,303],{"class":72},[66,17005,17006],{"class":68,"line":395},[66,17007,398],{"class":72},[66,17009,17010],{"class":68,"line":401},[66,17011,84],{"emptyLinePlaceholder":83},[66,17013,17014,17016,17018,17020,17022,17024],{"class":68,"line":406},[66,17015,409],{"class":184},[66,17017,194],{"class":72},[66,17019,414],{"class":184},[66,17021,200],{"class":72},[66,17023,419],{"class":171},[66,17025,422],{"class":72},[66,17027,17028,17030,17032,17034,17036,17038,17040,17042,17044,17046,17048],{"class":68,"line":425},[66,17029,428],{"class":184},[66,17031,200],{"class":72},[66,17033,433],{"class":171},[66,17035,436],{"class":72},[66,17037,440],{"class":439},[66,17039,443],{"class":72},[66,17041,446],{"class":76},[66,17043,200],{"class":72},[66,17045,451],{"class":76},[66,17047,454],{"class":72},[66,17049,178],{"class":72},[66,17051,17052,17054,17056,17058,17060,17062,17064,17066,17068,17070,17072,17074,17076,17078],{"class":68,"line":459},[66,17053,462],{"class":184},[66,17055,200],{"class":72},[66,17057,467],{"class":171},[66,17059,206],{"class":72},[66,17061,472],{"class":337},[66,17063,188],{"class":72},[66,17065,477],{"class":72},[66,17067,480],{"class":439},[66,17069,443],{"class":72},[66,17071,446],{"class":76},[66,17073,200],{"class":72},[66,17075,489],{"class":76},[66,17077,454],{"class":72},[66,17079,178],{"class":72},[66,17081,17082,17084,17086,17088,17090,17092,17094,17096],{"class":68,"line":496},[66,17083,499],{"class":184},[66,17085,200],{"class":72},[66,17087,504],{"class":171},[66,17089,206],{"class":72},[66,17091,209],{"class":72},[66,17093,3371],{"class":212},[66,17095,209],{"class":72},[66,17097,157],{"class":72},[66,17099,17100,17102,17104,17106,17108,17110,17113,17115],{"class":68,"line":544},[66,17101,499],{"class":184},[66,17103,200],{"class":72},[66,17105,504],{"class":171},[66,17107,206],{"class":72},[66,17109,209],{"class":72},[66,17111,17112],{"class":212},"吾輩は猫である。名前はまだ無い。",[66,17114,209],{"class":72},[66,17116,157],{"class":72},[66,17118,17119,17121,17123,17125,17127,17129,17132,17134],{"class":68,"line":564},[66,17120,499],{"class":184},[66,17122,200],{"class":72},[66,17124,504],{"class":171},[66,17126,206],{"class":72},[66,17128,209],{"class":72},[66,17130,17131],{"class":212},"東京都渋谷区神宮前1-2-3",[66,17133,209],{"class":72},[66,17135,157],{"class":72},[66,17137,17138],{"class":68,"line":570},[66,17139,567],{"class":72},[66,17141,17142],{"class":68,"line":576},[66,17143,573],{"class":72},[66,17145,17146],{"class":68,"line":581},[66,17147,84],{"emptyLinePlaceholder":83},[66,17149,17150,17152,17154,17156,17158,17160,17162,17164],{"class":68,"line":602},[66,17151,584],{"class":184},[66,17153,188],{"class":72},[66,17155,972],{"class":184},[66,17157,194],{"class":72},[66,17159,414],{"class":184},[66,17161,200],{"class":72},[66,17163,597],{"class":171},[66,17165,422],{"class":72},[66,17167,17168,17171,17173,17175,17177,17179,17181,17183,17185,17187,17189,17191],{"class":68,"line":615},[66,17169,17170],{"class":184},"    os",[66,17172,200],{"class":72},[66,17174,648],{"class":171},[66,17176,206],{"class":72},[66,17178,209],{"class":72},[66,17180,16731],{"class":212},[66,17182,209],{"class":72},[66,17184,188],{"class":72},[66,17186,662],{"class":184},[66,17188,188],{"class":72},[66,17190,667],{"class":337},[66,17192,157],{"class":72},[66,17194,17195],{"class":68,"line":630},[66,17196,704],{"class":72},[18,17198,17199],{},"違いは 2 つ。",[18,17201,17202,17203,1149,17206,17208],{},"ひとつめ。",[29,17204,17205],{},"パスではなくバイト列を渡す",[45,17207,9953],{}," で TTF を埋め込めば、バイナリだけで完結する。本番で「フォントが見つからない」と言われない。Distroless でも Alpine でも動く。",[18,17210,17211],{},"ふたつめ。gpdf の TrueType サブセッタは CJK の cmap フォーマット (4, 6, 12) と Identity-H エンコーディングを理解している。出力 PDF には実際に使ったグリフだけが入る — NotoSansJP を 200 文字の請求書に使うと、約 30 KB のサブセットになる。フル埋め込みの 4 MB ではない。gofpdf で Japanese 1 ページの PDF が 5 MB になったことがある人は、まずこれに気づくはず。",[18,17213,17214],{},"IPAex ゴシック・源ノ角ゴシック・フォントフォールバックなど、もっと深い CJK 周りは別記事で扱う予定。",[14,17216,17218],{"id":17217},"before-after-4-全ページ共通ヘッダ-フッタにページ番号","Before / After 4: 全ページ共通ヘッダ + フッタにページ番号",[18,17220,17221,17222,851,17225,17228,17229,17232,17233,1155,17235,17238],{},"gofpdf の繰り返しクロムは ",[45,17223,17224],{},"SetHeaderFunc",[45,17226,17227],{},"SetFooterFunc"," で、それぞれカーソルに対して走る ",[45,17230,17231],{},"func()"," を渡す。ページ番号は ",[45,17234,14755],{},[45,17236,17237],{},"pdf.AliasNbPages()"," を組み合わせる。",[18,17240,17241],{},[29,17242,14847],{},[57,17244,17246],{"className":59,"code":17245,"language":61,"meta":62,"style":62},"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// ... 本文 ...\n",[45,17247,17248,17290,17303,17333,17360,17375,17379,17391,17406,17437,17455,17492,17523,17527,17543,17553],{"__ignoreMap":62},[66,17249,17250,17252,17254,17256,17258,17260,17262,17264,17266,17268,17270,17272,17274,17276,17278,17280,17282,17284,17286,17288],{"class":68,"line":69},[66,17251,16585],{"class":184},[66,17253,194],{"class":72},[66,17255,14896],{"class":184},[66,17257,200],{"class":72},[66,17259,7448],{"class":171},[66,17261,206],{"class":72},[66,17263,209],{"class":72},[66,17265,7455],{"class":212},[66,17267,209],{"class":72},[66,17269,188],{"class":72},[66,17271,7462],{"class":72},[66,17273,7465],{"class":212},[66,17275,209],{"class":72},[66,17277,188],{"class":72},[66,17279,7462],{"class":72},[66,17281,300],{"class":212},[66,17283,209],{"class":72},[66,17285,188],{"class":72},[66,17287,7480],{"class":72},[66,17289,157],{"class":72},[66,17291,17292,17294,17296,17298,17301],{"class":68,"line":80},[66,17293,2276],{"class":184},[66,17295,200],{"class":72},[66,17297,17224],{"class":171},[66,17299,17300],{"class":72},"(func()",[66,17302,178],{"class":72},[66,17304,17305,17307,17309,17311,17313,17315,17317,17319,17321,17323,17325,17327,17329,17331],{"class":68,"line":87},[66,17306,7487],{"class":184},[66,17308,200],{"class":72},[66,17310,1758],{"class":171},[66,17312,206],{"class":72},[66,17314,209],{"class":72},[66,17316,7508],{"class":212},[66,17318,209],{"class":72},[66,17320,188],{"class":72},[66,17322,7462],{"class":72},[66,17324,7517],{"class":212},[66,17326,209],{"class":72},[66,17328,188],{"class":72},[66,17330,3278],{"class":337},[66,17332,157],{"class":72},[66,17334,17335,17337,17339,17341,17343,17345,17347,17349,17351,17353,17356,17358],{"class":68,"line":97},[66,17336,7487],{"class":184},[66,17338,200],{"class":72},[66,17340,1934],{"class":171},[66,17342,206],{"class":72},[66,17344,10249],{"class":337},[66,17346,188],{"class":72},[66,17348,7544],{"class":337},[66,17350,188],{"class":72},[66,17352,7462],{"class":72},[66,17354,17355],{"class":212},"ACME 株式会社",[66,17357,209],{"class":72},[66,17359,157],{"class":72},[66,17361,17362,17364,17366,17369,17371,17373],{"class":68,"line":109},[66,17363,7487],{"class":184},[66,17365,200],{"class":72},[66,17367,17368],{"class":171},"Ln",[66,17370,206],{"class":72},[66,17372,5498],{"class":337},[66,17374,157],{"class":72},[66,17376,17377],{"class":68,"line":119},[66,17378,3731],{"class":72},[66,17380,17381,17383,17385,17387,17389],{"class":68,"line":124},[66,17382,2276],{"class":184},[66,17384,200],{"class":72},[66,17386,17227],{"class":171},[66,17388,17300],{"class":72},[66,17390,178],{"class":72},[66,17392,17393,17395,17397,17399,17402,17404],{"class":68,"line":134},[66,17394,7487],{"class":184},[66,17396,200],{"class":72},[66,17398,8316],{"class":171},[66,17400,17401],{"class":72},"(-",[66,17403,5498],{"class":337},[66,17405,157],{"class":72},[66,17407,17408,17410,17412,17414,17416,17418,17420,17422,17424,17426,17429,17431,17433,17435],{"class":68,"line":144},[66,17409,7487],{"class":184},[66,17411,200],{"class":72},[66,17413,1758],{"class":171},[66,17415,206],{"class":72},[66,17417,209],{"class":72},[66,17419,7508],{"class":212},[66,17421,209],{"class":72},[66,17423,188],{"class":72},[66,17425,7462],{"class":72},[66,17427,17428],{"class":212},"I",[66,17430,209],{"class":72},[66,17432,188],{"class":72},[66,17434,15522],{"class":337},[66,17436,157],{"class":72},[66,17438,17439,17441,17443,17445,17447,17449,17451,17453],{"class":68,"line":154},[66,17440,7487],{"class":184},[66,17442,200],{"class":72},[66,17444,2008],{"class":171},[66,17446,206],{"class":72},[66,17448,10249],{"class":337},[66,17450,188],{"class":72},[66,17452,7544],{"class":337},[66,17454,2059],{"class":72},[66,17456,17457,17460,17462,17465,17467,17469,17472,17475,17478,17480,17482,17484,17486,17489],{"class":68,"line":160},[66,17458,17459],{"class":184},"        fmt",[66,17461,200],{"class":72},[66,17463,17464],{"class":171},"Sprintf",[66,17466,206],{"class":72},[66,17468,209],{"class":72},[66,17470,17471],{"class":212},"Page ",[66,17473,17474],{"class":3858},"%d",[66,17476,17477],{"class":212},"/{nb}",[66,17479,209],{"class":72},[66,17481,188],{"class":72},[66,17483,7606],{"class":184},[66,17485,200],{"class":72},[66,17487,17488],{"class":171},"PageNo",[66,17490,17491],{"class":72},"()),\n",[66,17493,17494,17497,17499,17501,17503,17505,17507,17509,17511,17513,17515,17517,17519,17521],{"class":68,"line":165},[66,17495,17496],{"class":72},"        \"\"",[66,17498,188],{"class":72},[66,17500,15544],{"class":337},[66,17502,188],{"class":72},[66,17504,7462],{"class":72},[66,17506,15612],{"class":212},[66,17508,209],{"class":72},[66,17510,188],{"class":72},[66,17512,15982],{"class":15558},[66,17514,188],{"class":72},[66,17516,15544],{"class":337},[66,17518,188],{"class":72},[66,17520,7480],{"class":72},[66,17522,157],{"class":72},[66,17524,17525],{"class":68,"line":181},[66,17526,3731],{"class":72},[66,17528,17529,17531,17533,17536,17538,17541],{"class":68,"line":219},[66,17530,2276],{"class":184},[66,17532,200],{"class":72},[66,17534,17535],{"class":171},"AliasNbPages",[66,17537,206],{"class":72},[66,17539,17540],{"class":72},"\"\"",[66,17542,157],{"class":72},[66,17544,17545,17547,17549,17551],{"class":68,"line":235},[66,17546,2276],{"class":184},[66,17548,200],{"class":72},[66,17550,419],{"class":171},[66,17552,422],{"class":72},[66,17554,17555],{"class":68,"line":253},[66,17556,17557],{"class":2209},"// ... 本文 ...\n",[18,17559,17560,17563],{},[45,17561,17562],{},"{nb}"," は gofpdf が出力時に総ページ数で書き換えるセンチネル。動くんだけど「知っているかどうか」の世界。",[18,17565,17566],{},[29,17567,7668],{},[57,17569,17571],{"className":59,"code":17570,"language":61,"meta":62,"style":62},"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",[45,17572,17573,17587,17605,17635,17639,17643,17669,17694,17724,17762,17797,17819,17823,17827,17831,17835,17860,17884,17914,17932,17967,17971,18001,18006,18011,18030,18064,18068,18072,18076,18080,18111,18125,18149,18179,18219,18223,18227],{"__ignoreMap":62},[66,17574,17575,17577,17579,17581,17583,17585],{"class":68,"line":69},[66,17576,1000],{"class":184},[66,17578,194],{"class":72},[66,17580,272],{"class":184},[66,17582,200],{"class":72},[66,17584,277],{"class":171},[66,17586,280],{"class":72},[66,17588,17589,17591,17593,17595,17597,17599,17601,17603],{"class":68,"line":80},[66,17590,1015],{"class":184},[66,17592,200],{"class":72},[66,17594,291],{"class":171},[66,17596,206],{"class":72},[66,17598,318],{"class":184},[66,17600,200],{"class":72},[66,17602,300],{"class":184},[66,17604,303],{"class":72},[66,17606,17607,17609,17611,17613,17615,17617,17619,17621,17623,17625,17627,17629,17631,17633],{"class":68,"line":87},[66,17608,1015],{"class":184},[66,17610,200],{"class":72},[66,17612,313],{"class":171},[66,17614,206],{"class":72},[66,17616,318],{"class":184},[66,17618,200],{"class":72},[66,17620,323],{"class":171},[66,17622,206],{"class":72},[66,17624,318],{"class":184},[66,17626,200],{"class":72},[66,17628,332],{"class":171},[66,17630,206],{"class":72},[66,17632,338],{"class":337},[66,17634,341],{"class":72},[66,17636,17637],{"class":68,"line":97},[66,17638,157],{"class":72},[66,17640,17641],{"class":68,"line":109},[66,17642,84],{"emptyLinePlaceholder":83},[66,17644,17645,17648,17650,17652,17654,17656,17658,17660,17662,17665,17667],{"class":68,"line":119},[66,17646,17647],{"class":184},"doc",[66,17649,200],{"class":72},[66,17651,7569],{"class":171},[66,17653,436],{"class":72},[66,17655,18],{"class":439},[66,17657,443],{"class":72},[66,17659,446],{"class":76},[66,17661,200],{"class":72},[66,17663,17664],{"class":76},"PageBuilder",[66,17666,454],{"class":72},[66,17668,178],{"class":72},[66,17670,17671,17674,17676,17678,17680,17682,17684,17686,17688,17690,17692],{"class":68,"line":124},[66,17672,17673],{"class":184},"    p",[66,17675,200],{"class":72},[66,17677,433],{"class":171},[66,17679,436],{"class":72},[66,17681,440],{"class":439},[66,17683,443],{"class":72},[66,17685,446],{"class":76},[66,17687,200],{"class":72},[66,17689,451],{"class":76},[66,17691,454],{"class":72},[66,17693,178],{"class":72},[66,17695,17696,17698,17700,17702,17704,17706,17708,17710,17712,17714,17716,17718,17720,17722],{"class":68,"line":134},[66,17697,462],{"class":184},[66,17699,200],{"class":72},[66,17701,467],{"class":171},[66,17703,206],{"class":72},[66,17705,472],{"class":337},[66,17707,188],{"class":72},[66,17709,477],{"class":72},[66,17711,480],{"class":439},[66,17713,443],{"class":72},[66,17715,446],{"class":76},[66,17717,200],{"class":72},[66,17719,489],{"class":76},[66,17721,454],{"class":72},[66,17723,178],{"class":72},[66,17725,17726,17728,17730,17732,17734,17736,17738,17740,17742,17744,17746,17748,17750,17752,17754,17756,17758,17760],{"class":68,"line":144},[66,17727,499],{"class":184},[66,17729,200],{"class":72},[66,17731,504],{"class":171},[66,17733,206],{"class":72},[66,17735,209],{"class":72},[66,17737,17355],{"class":212},[66,17739,209],{"class":72},[66,17741,188],{"class":72},[66,17743,518],{"class":184},[66,17745,200],{"class":72},[66,17747,538],{"class":171},[66,17749,7650],{"class":72},[66,17751,518],{"class":184},[66,17753,200],{"class":72},[66,17755,523],{"class":171},[66,17757,206],{"class":72},[66,17759,472],{"class":337},[66,17761,5050],{"class":72},[66,17763,17764,17766,17768,17771,17773,17775,17777,17780,17782,17784,17786,17789,17791,17794],{"class":68,"line":154},[66,17765,499],{"class":184},[66,17767,200],{"class":72},[66,17769,17770],{"class":171},"Line",[66,17772,206],{"class":72},[66,17774,446],{"class":184},[66,17776,200],{"class":72},[66,17778,17779],{"class":171},"LineColor",[66,17781,206],{"class":72},[66,17783,2276],{"class":184},[66,17785,200],{"class":72},[66,17787,17788],{"class":171},"Gray",[66,17790,206],{"class":72},[66,17792,17793],{"class":337},"0.7",[66,17795,17796],{"class":72},")))\n",[66,17798,17799,17801,17803,17805,17807,17809,17811,17813,17815,17817],{"class":68,"line":160},[66,17800,499],{"class":184},[66,17802,200],{"class":72},[66,17804,2129],{"class":171},[66,17806,206],{"class":72},[66,17808,318],{"class":184},[66,17810,200],{"class":72},[66,17812,332],{"class":171},[66,17814,206],{"class":72},[66,17816,5861],{"class":337},[66,17818,5050],{"class":72},[66,17820,17821],{"class":68,"line":165},[66,17822,567],{"class":72},[66,17824,17825],{"class":68,"line":181},[66,17826,573],{"class":72},[66,17828,17829],{"class":68,"line":219},[66,17830,3731],{"class":72},[66,17832,17833],{"class":68,"line":235},[66,17834,84],{"emptyLinePlaceholder":83},[66,17836,17837,17839,17841,17844,17846,17848,17850,17852,17854,17856,17858],{"class":68,"line":253},[66,17838,17647],{"class":184},[66,17840,200],{"class":72},[66,17842,17843],{"class":171},"Footer",[66,17845,436],{"class":72},[66,17847,18],{"class":439},[66,17849,443],{"class":72},[66,17851,446],{"class":76},[66,17853,200],{"class":72},[66,17855,17664],{"class":76},[66,17857,454],{"class":72},[66,17859,178],{"class":72},[66,17861,17862,17864,17866,17868,17870,17872,17874,17876,17878,17880,17882],{"class":68,"line":259},[66,17863,17673],{"class":184},[66,17865,200],{"class":72},[66,17867,433],{"class":171},[66,17869,436],{"class":72},[66,17871,440],{"class":439},[66,17873,443],{"class":72},[66,17875,446],{"class":76},[66,17877,200],{"class":72},[66,17879,451],{"class":76},[66,17881,454],{"class":72},[66,17883,178],{"class":72},[66,17885,17886,17888,17890,17892,17894,17896,17898,17900,17902,17904,17906,17908,17910,17912],{"class":68,"line":264},[66,17887,462],{"class":184},[66,17889,200],{"class":72},[66,17891,467],{"class":171},[66,17893,206],{"class":72},[66,17895,5679],{"class":337},[66,17897,188],{"class":72},[66,17899,477],{"class":72},[66,17901,480],{"class":439},[66,17903,443],{"class":72},[66,17905,446],{"class":76},[66,17907,200],{"class":72},[66,17909,489],{"class":76},[66,17911,454],{"class":72},[66,17913,178],{"class":72},[66,17915,17916,17918,17920,17922,17924,17926,17928,17930],{"class":68,"line":283},[66,17917,499],{"class":184},[66,17919,200],{"class":72},[66,17921,504],{"class":171},[66,17923,206],{"class":72},[66,17925,209],{"class":72},[66,17927,17355],{"class":212},[66,17929,209],{"class":72},[66,17931,2059],{"class":72},[66,17933,17934,17936,17938,17940,17942,17944,17946,17948,17950,17952,17954,17956,17958,17960,17962,17965],{"class":68,"line":306},[66,17935,11322],{"class":184},[66,17937,200],{"class":72},[66,17939,523],{"class":171},[66,17941,206],{"class":72},[66,17943,6070],{"class":337},[66,17945,531],{"class":72},[66,17947,518],{"class":184},[66,17949,200],{"class":72},[66,17951,11380],{"class":171},[66,17953,206],{"class":72},[66,17955,2276],{"class":184},[66,17957,200],{"class":72},[66,17959,17788],{"class":171},[66,17961,206],{"class":72},[66,17963,17964],{"class":337},"0.5",[66,17966,17796],{"class":72},[66,17968,17969],{"class":68,"line":344},[66,17970,567],{"class":72},[66,17972,17973,17975,17977,17979,17981,17983,17985,17987,17989,17991,17993,17995,17997,17999],{"class":68,"line":370},[66,17974,462],{"class":184},[66,17976,200],{"class":72},[66,17978,467],{"class":171},[66,17980,206],{"class":72},[66,17982,5679],{"class":337},[66,17984,188],{"class":72},[66,17986,477],{"class":72},[66,17988,480],{"class":439},[66,17990,443],{"class":72},[66,17992,446],{"class":76},[66,17994,200],{"class":72},[66,17996,489],{"class":76},[66,17998,454],{"class":72},[66,18000,178],{"class":72},[66,18002,18003],{"class":68,"line":395},[66,18004,18005],{"class":2209},"            // \"ページ X / Y\" — どちらもプレースホルダで、\n",[66,18007,18008],{"class":68,"line":401},[66,18009,18010],{"class":2209},"            // ページネーション完了後にレイアウトエンジンが解決する。\n",[66,18012,18013,18015,18017,18020,18022,18024,18026,18028],{"class":68,"line":406},[66,18014,499],{"class":184},[66,18016,200],{"class":72},[66,18018,18019],{"class":171},"PageNumber",[66,18021,206],{"class":72},[66,18023,446],{"class":184},[66,18025,200],{"class":72},[66,18027,7294],{"class":171},[66,18029,11371],{"class":72},[66,18031,18032,18034,18036,18038,18040,18042,18044,18046,18048,18050,18052,18054,18056,18058,18060,18062],{"class":68,"line":425},[66,18033,11322],{"class":184},[66,18035,200],{"class":72},[66,18037,523],{"class":171},[66,18039,206],{"class":72},[66,18041,6070],{"class":337},[66,18043,531],{"class":72},[66,18045,518],{"class":184},[66,18047,200],{"class":72},[66,18049,11380],{"class":171},[66,18051,206],{"class":72},[66,18053,2276],{"class":184},[66,18055,200],{"class":72},[66,18057,17788],{"class":171},[66,18059,206],{"class":72},[66,18061,17964],{"class":337},[66,18063,17796],{"class":72},[66,18065,18066],{"class":68,"line":459},[66,18067,567],{"class":72},[66,18069,18070],{"class":68,"line":496},[66,18071,573],{"class":72},[66,18073,18074],{"class":68,"line":544},[66,18075,3731],{"class":72},[66,18077,18078],{"class":68,"line":564},[66,18079,84],{"emptyLinePlaceholder":83},[66,18081,18082,18084,18087,18089,18091,18094,18096,18099,18101,18103,18106,18109],{"class":68,"line":570},[66,18083,15913],{"class":90},[66,18085,18086],{"class":184}," i ",[66,18088,194],{"class":72},[66,18090,15544],{"class":337},[66,18092,18093],{"class":72},";",[66,18095,18086],{"class":184},[66,18097,18098],{"class":72},"\u003C",[66,18100,7544],{"class":337},[66,18102,18093],{"class":72},[66,18104,18105],{"class":184}," i",[66,18107,18108],{"class":72},"++",[66,18110,178],{"class":72},[66,18112,18113,18115,18117,18119,18121,18123],{"class":68,"line":576},[66,18114,409],{"class":184},[66,18116,194],{"class":72},[66,18118,414],{"class":184},[66,18120,200],{"class":72},[66,18122,419],{"class":171},[66,18124,422],{"class":72},[66,18126,18127,18129,18131,18133,18135,18137,18139,18141,18143,18145,18147],{"class":68,"line":581},[66,18128,428],{"class":184},[66,18130,200],{"class":72},[66,18132,433],{"class":171},[66,18134,436],{"class":72},[66,18136,440],{"class":439},[66,18138,443],{"class":72},[66,18140,446],{"class":76},[66,18142,200],{"class":72},[66,18144,451],{"class":76},[66,18146,454],{"class":72},[66,18148,178],{"class":72},[66,18150,18151,18153,18155,18157,18159,18161,18163,18165,18167,18169,18171,18173,18175,18177],{"class":68,"line":602},[66,18152,462],{"class":184},[66,18154,200],{"class":72},[66,18156,467],{"class":171},[66,18158,206],{"class":72},[66,18160,472],{"class":337},[66,18162,188],{"class":72},[66,18164,477],{"class":72},[66,18166,480],{"class":439},[66,18168,443],{"class":72},[66,18170,446],{"class":76},[66,18172,200],{"class":72},[66,18174,489],{"class":76},[66,18176,454],{"class":72},[66,18178,178],{"class":72},[66,18180,18181,18183,18185,18187,18189,18191,18193,18195,18197,18199,18202,18204,18207,18209,18211,18213,18215,18217],{"class":68,"line":615},[66,18182,499],{"class":184},[66,18184,200],{"class":72},[66,18186,504],{"class":171},[66,18188,206],{"class":72},[66,18190,3846],{"class":184},[66,18192,200],{"class":72},[66,18194,17464],{"class":171},[66,18196,206],{"class":72},[66,18198,209],{"class":72},[66,18200,18201],{"class":212},"ページ ",[66,18203,17474],{"class":3858},[66,18205,18206],{"class":212}," の本文。",[66,18208,209],{"class":72},[66,18210,188],{"class":72},[66,18212,18105],{"class":184},[66,18214,9995],{"class":72},[66,18216,15537],{"class":337},[66,18218,5050],{"class":72},[66,18220,18221],{"class":68,"line":630},[66,18222,567],{"class":72},[66,18224,18225],{"class":68,"line":635},[66,18226,573],{"class":72},[66,18228,18229],{"class":68,"line":681},[66,18230,704],{"class":72},[18,18232,18233,1155,18235,18238,18239,18241,18242,18245],{},[45,18234,18019],{},[45,18236,18237],{},"TotalPages"," はプレースホルダ。レイアウトエンジンがページ数を確定したあとに展開される。",[45,18240,17562],{}," センチネルも ",[45,18243,18244],{},"SetY(-15)"," でフッタを下端に打ち付ける必要もない — フッタは単なるツリーで、毎ページ自動的に空間が確保される。",[14,18247,18249],{"id":18248},"before-after-5-http-ハンドラからバイト列を返す","Before / After 5: HTTP ハンドラからバイト列を返す",[18,18251,18252,18253,18255,18256,18258,18259,18262],{},"実運用の gofpdf コードはたいていファイルではなく ",[45,18254,1895],{}," に書く — 多くは ",[45,18257,7591],{}," を返す ",[45,18260,18261],{},"http.ResponseWriter","。このペアは gpdf の API が gofpdf に一番近い場所。",[18,18264,18265],{},[29,18266,14847],{},[57,18268,18270],{"className":59,"code":18269,"language":61,"meta":62,"style":62},"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",[45,18271,18272,18304,18346,18356,18382,18433,18437,18467,18495,18521,18525],{"__ignoreMap":62},[66,18273,18274,18276,18278,18280,18282,18284,18286,18288,18290,18292,18294,18296,18298,18300,18302],{"class":68,"line":69},[66,18275,168],{"class":72},[66,18277,7402],{"class":171},[66,18279,206],{"class":72},[66,18281,1874],{"class":439},[66,18283,7409],{"class":76},[66,18285,200],{"class":72},[66,18287,7414],{"class":76},[66,18289,188],{"class":72},[66,18291,7419],{"class":439},[66,18293,443],{"class":72},[66,18295,7424],{"class":76},[66,18297,200],{"class":72},[66,18299,7429],{"class":76},[66,18301,454],{"class":72},[66,18303,178],{"class":72},[66,18305,18306,18308,18310,18312,18314,18316,18318,18320,18322,18324,18326,18328,18330,18332,18334,18336,18338,18340,18342,18344],{"class":68,"line":80},[66,18307,7438],{"class":184},[66,18309,194],{"class":72},[66,18311,14896],{"class":184},[66,18313,200],{"class":72},[66,18315,7448],{"class":171},[66,18317,206],{"class":72},[66,18319,209],{"class":72},[66,18321,7455],{"class":212},[66,18323,209],{"class":72},[66,18325,188],{"class":72},[66,18327,7462],{"class":72},[66,18329,7465],{"class":212},[66,18331,209],{"class":72},[66,18333,188],{"class":72},[66,18335,7462],{"class":72},[66,18337,300],{"class":212},[66,18339,209],{"class":72},[66,18341,188],{"class":72},[66,18343,7480],{"class":72},[66,18345,157],{"class":72},[66,18347,18348,18350,18352,18354],{"class":68,"line":87},[66,18349,7487],{"class":184},[66,18351,200],{"class":72},[66,18353,419],{"class":171},[66,18355,422],{"class":72},[66,18357,18358,18360,18362,18364,18366,18368,18370,18372,18374,18376,18378,18380],{"class":68,"line":97},[66,18359,7487],{"class":184},[66,18361,200],{"class":72},[66,18363,1758],{"class":171},[66,18365,206],{"class":72},[66,18367,209],{"class":72},[66,18369,7508],{"class":212},[66,18371,209],{"class":72},[66,18373,188],{"class":72},[66,18375,7480],{"class":72},[66,18377,188],{"class":72},[66,18379,3278],{"class":337},[66,18381,157],{"class":72},[66,18383,18384,18386,18388,18390,18392,18394,18396,18398,18400,18402,18405,18407,18409,18412,18414,18417,18419,18422,18424,18426,18428,18431],{"class":68,"line":109},[66,18385,7487],{"class":184},[66,18387,200],{"class":72},[66,18389,1934],{"class":171},[66,18391,206],{"class":72},[66,18393,10249],{"class":337},[66,18395,188],{"class":72},[66,18397,7544],{"class":337},[66,18399,188],{"class":72},[66,18401,7462],{"class":72},[66,18403,18404],{"class":212},"生成日時: ",[66,18406,209],{"class":72},[66,18408,9995],{"class":72},[66,18410,18411],{"class":184},"time",[66,18413,200],{"class":72},[66,18415,18416],{"class":171},"Now",[66,18418,7572],{"class":72},[66,18420,18421],{"class":171},"Format",[66,18423,206],{"class":72},[66,18425,18411],{"class":184},[66,18427,200],{"class":72},[66,18429,18430],{"class":184},"RFC3339",[66,18432,5050],{"class":72},[66,18434,18435],{"class":68,"line":119},[66,18436,84],{"emptyLinePlaceholder":83},[66,18438,18439,18441,18443,18445,18447,18449,18451,18453,18455,18457,18459,18461,18463,18465],{"class":68,"line":124},[66,18440,7564],{"class":184},[66,18442,200],{"class":72},[66,18444,7569],{"class":171},[66,18446,7572],{"class":72},[66,18448,7575],{"class":171},[66,18450,206],{"class":72},[66,18452,209],{"class":72},[66,18454,7582],{"class":212},[66,18456,209],{"class":72},[66,18458,188],{"class":72},[66,18460,7462],{"class":72},[66,18462,7591],{"class":212},[66,18464,209],{"class":72},[66,18466,157],{"class":72},[66,18468,18469,18471,18473,18475,18477,18479,18481,18483,18485,18487,18489,18491,18493],{"class":68,"line":134},[66,18470,222],{"class":90},[66,18472,191],{"class":184},[66,18474,194],{"class":72},[66,18476,7606],{"class":184},[66,18478,200],{"class":72},[66,18480,1938],{"class":171},[66,18482,206],{"class":72},[66,18484,1874],{"class":184},[66,18486,670],{"class":72},[66,18488,191],{"class":184},[66,18490,227],{"class":72},[66,18492,230],{"class":72},[66,18494,178],{"class":72},[66,18496,18497,18499,18501,18503,18505,18507,18509,18511,18513,18515,18517,18519],{"class":68,"line":144},[66,18498,7629],{"class":184},[66,18500,200],{"class":72},[66,18502,7634],{"class":171},[66,18504,206],{"class":72},[66,18506,1874],{"class":184},[66,18508,188],{"class":72},[66,18510,7643],{"class":184},[66,18512,200],{"class":72},[66,18514,7634],{"class":171},[66,18516,7650],{"class":72},[66,18518,7653],{"class":337},[66,18520,157],{"class":72},[66,18522,18523],{"class":68,"line":154},[66,18524,256],{"class":72},[66,18526,18527],{"class":68,"line":160},[66,18528,704],{"class":72},[18,18530,18531],{},[29,18532,7668],{},[57,18534,18536],{"className":59,"code":18535,"language":61,"meta":62,"style":62},"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",[45,18537,18538,18570,18584,18602,18632,18636,18640,18654,18678,18708,18748,18752,18756,18760,18790,18818,18844,18848],{"__ignoreMap":62},[66,18539,18540,18542,18544,18546,18548,18550,18552,18554,18556,18558,18560,18562,18564,18566,18568],{"class":68,"line":69},[66,18541,168],{"class":72},[66,18543,7402],{"class":171},[66,18545,206],{"class":72},[66,18547,1874],{"class":439},[66,18549,7409],{"class":76},[66,18551,200],{"class":72},[66,18553,7414],{"class":76},[66,18555,188],{"class":72},[66,18557,7419],{"class":439},[66,18559,443],{"class":72},[66,18561,7424],{"class":76},[66,18563,200],{"class":72},[66,18565,7429],{"class":76},[66,18567,454],{"class":72},[66,18569,178],{"class":72},[66,18571,18572,18574,18576,18578,18580,18582],{"class":68,"line":80},[66,18573,267],{"class":184},[66,18575,194],{"class":72},[66,18577,272],{"class":184},[66,18579,200],{"class":72},[66,18581,277],{"class":171},[66,18583,280],{"class":72},[66,18585,18586,18588,18590,18592,18594,18596,18598,18600],{"class":68,"line":87},[66,18587,286],{"class":184},[66,18589,200],{"class":72},[66,18591,291],{"class":171},[66,18593,206],{"class":72},[66,18595,318],{"class":184},[66,18597,200],{"class":72},[66,18599,300],{"class":184},[66,18601,303],{"class":72},[66,18603,18604,18606,18608,18610,18612,18614,18616,18618,18620,18622,18624,18626,18628,18630],{"class":68,"line":97},[66,18605,286],{"class":184},[66,18607,200],{"class":72},[66,18609,313],{"class":171},[66,18611,206],{"class":72},[66,18613,318],{"class":184},[66,18615,200],{"class":72},[66,18617,323],{"class":171},[66,18619,206],{"class":72},[66,18621,318],{"class":184},[66,18623,200],{"class":72},[66,18625,332],{"class":171},[66,18627,206],{"class":72},[66,18629,338],{"class":337},[66,18631,341],{"class":72},[66,18633,18634],{"class":68,"line":109},[66,18635,398],{"class":72},[66,18637,18638],{"class":68,"line":119},[66,18639,84],{"emptyLinePlaceholder":83},[66,18641,18642,18644,18646,18648,18650,18652],{"class":68,"line":124},[66,18643,409],{"class":184},[66,18645,194],{"class":72},[66,18647,414],{"class":184},[66,18649,200],{"class":72},[66,18651,419],{"class":171},[66,18653,422],{"class":72},[66,18655,18656,18658,18660,18662,18664,18666,18668,18670,18672,18674,18676],{"class":68,"line":134},[66,18657,428],{"class":184},[66,18659,200],{"class":72},[66,18661,433],{"class":171},[66,18663,436],{"class":72},[66,18665,440],{"class":439},[66,18667,443],{"class":72},[66,18669,446],{"class":76},[66,18671,200],{"class":72},[66,18673,451],{"class":76},[66,18675,454],{"class":72},[66,18677,178],{"class":72},[66,18679,18680,18682,18684,18686,18688,18690,18692,18694,18696,18698,18700,18702,18704,18706],{"class":68,"line":144},[66,18681,462],{"class":184},[66,18683,200],{"class":72},[66,18685,467],{"class":171},[66,18687,206],{"class":72},[66,18689,472],{"class":337},[66,18691,188],{"class":72},[66,18693,477],{"class":72},[66,18695,480],{"class":439},[66,18697,443],{"class":72},[66,18699,446],{"class":76},[66,18701,200],{"class":72},[66,18703,489],{"class":76},[66,18705,454],{"class":72},[66,18707,178],{"class":72},[66,18709,18710,18712,18714,18716,18718,18720,18722,18724,18727,18730,18732,18734,18736,18738,18740,18742,18744,18746],{"class":68,"line":154},[66,18711,499],{"class":184},[66,18713,200],{"class":72},[66,18715,504],{"class":171},[66,18717,206],{"class":72},[66,18719,209],{"class":72},[66,18721,18404],{"class":212},[66,18723,209],{"class":72},[66,18725,18726],{"class":72}," +",[66,18728,18729],{"class":184}," time",[66,18731,200],{"class":72},[66,18733,18416],{"class":171},[66,18735,7572],{"class":72},[66,18737,18421],{"class":171},[66,18739,206],{"class":72},[66,18741,18411],{"class":184},[66,18743,200],{"class":72},[66,18745,18430],{"class":184},[66,18747,5050],{"class":72},[66,18749,18750],{"class":68,"line":160},[66,18751,567],{"class":72},[66,18753,18754],{"class":68,"line":165},[66,18755,573],{"class":72},[66,18757,18758],{"class":68,"line":181},[66,18759,84],{"emptyLinePlaceholder":83},[66,18761,18762,18764,18766,18768,18770,18772,18774,18776,18778,18780,18782,18784,18786,18788],{"class":68,"line":219},[66,18763,7564],{"class":184},[66,18765,200],{"class":72},[66,18767,7569],{"class":171},[66,18769,7572],{"class":72},[66,18771,7575],{"class":171},[66,18773,206],{"class":72},[66,18775,209],{"class":72},[66,18777,7582],{"class":212},[66,18779,209],{"class":72},[66,18781,188],{"class":72},[66,18783,7462],{"class":72},[66,18785,7591],{"class":212},[66,18787,209],{"class":72},[66,18789,157],{"class":72},[66,18791,18792,18794,18796,18798,18800,18802,18804,18806,18808,18810,18812,18814,18816],{"class":68,"line":235},[66,18793,222],{"class":90},[66,18795,191],{"class":184},[66,18797,194],{"class":72},[66,18799,414],{"class":184},[66,18801,200],{"class":72},[66,18803,7999],{"class":171},[66,18805,206],{"class":72},[66,18807,1874],{"class":184},[66,18809,670],{"class":72},[66,18811,191],{"class":184},[66,18813,227],{"class":72},[66,18815,230],{"class":72},[66,18817,178],{"class":72},[66,18819,18820,18822,18824,18826,18828,18830,18832,18834,18836,18838,18840,18842],{"class":68,"line":253},[66,18821,7629],{"class":184},[66,18823,200],{"class":72},[66,18825,7634],{"class":171},[66,18827,206],{"class":72},[66,18829,1874],{"class":184},[66,18831,188],{"class":72},[66,18833,7643],{"class":184},[66,18835,200],{"class":72},[66,18837,7634],{"class":171},[66,18839,7650],{"class":72},[66,18841,7653],{"class":337},[66,18843,157],{"class":72},[66,18845,18846],{"class":68,"line":259},[66,18847,256],{"class":72},[66,18849,18850],{"class":68,"line":264},[66,18851,704],{"class":72},[18,18853,18854,18855,18857,18858,18861,18862,18864,18865,18868],{},"形は同じ。",[45,18856,14794],{}," がそのままレスポンスに流す。",[45,18859,18860],{},"Content-Length"," を立てたいなら、先に ",[45,18863,3959],{}," でバイト列を取って ",[45,18866,18867],{},"len()"," を入れる。",[14,18870,18872],{"id":18871},"どれだけ速ければ十分か","どれだけ速ければ「十分」か",[18,18874,18875,18876,18878,18879,18881],{},"gpdf は実用ワークロードで ",[29,18877,14420],{},"。下の数値は ",[45,18880,1395],{}," を Apple M1 / Go 1.25 で回したもの。",[738,18883,18884,18898],{},[741,18885,18886],{},[744,18887,18888,18890,18892,18894,18896],{},[747,18889,8091],{},[747,18891,26],{},[747,18893,1428],{},[747,18895,8098],{},[747,18897,1437],{},[756,18899,18900,18915,18930,18945],{},[744,18901,18902,18905,18909,18911,18913],{},[761,18903,18904],{},"単一ページ",[761,18906,18907],{},[29,18908,1341],{},[761,18910,1451],{},[761,18912,1457],{},[761,18914,1460],{},[744,18916,18917,18920,18924,18926,18928],{},[761,18918,18919],{},"4×10 表",[761,18921,18922],{},[29,18923,1345],{},[761,18925,1472],{},[761,18927,1478],{},[761,18929,8133],{},[744,18931,18932,18935,18939,18941,18943],{},[761,18933,18934],{},"100 ページ",[761,18936,18937],{},[29,18938,1349],{},[761,18940,1357],{},[761,18942,8133],{},[761,18944,8148],{},[744,18946,18947,18949,18953,18955,18957],{},[761,18948,10430],{},[761,18950,18951],{},[29,18952,1511],{},[761,18954,1514],{},[761,18956,1520],{},[761,18958,8164],{},[18,18960,18961],{},"合成ベンチではない。表ベンチは 4 列 10 行の請求書明細、100 ページベンチはヘッダとページ番号付きのレポート。本番コードが実際にやる形に揃えてある。",[18,18963,18964],{},"意味のほうも軽く。13 µs/単一ページなら 1 コアで毎秒 75,000 枚、108 µs/表ありなら毎秒 9,000 枚。「PDF 生成はキャッシュすべきか? 非同期キューに逃すべきか?」を考えなくてよくなる。多くのワークロードはリクエスト同期で生成して問題ない。",[14,18966,18967],{"id":18967},"移行で諦めるもの",[18,18969,18970],{},"ガイドが現実のギャップを隠していたら意味がない。gpdf がまだ gofpdf より弱いところを正直に挙げておく:",[1109,18972,18973,18981,18996,19002],{},[882,18974,18975,1149,18978,18980],{},[29,18976,18977],{},"任意角度の線、ベジェ、複雑なパス",[45,18979,8214],{}," は列を横切る水平線を引く。CAD 図面や独自グラフ描画には届いていない (チャートを画像化して埋めるのは普通に動く)。",[882,18982,18983,1149,18988,18990,18991,1931,18993,18995],{},[29,18984,18985,18987],{},[45,18986,1930],{}," 中心の絶対座標コード",[45,18989,14715],{}," で似たことはできるが、既存コードが 2,000 行の ",[45,18992,1930],{},[45,18994,1934],{}," だと、移行は事実上書き直しに近い。書き直すと 1/2 になることが多いので、それが救い。",[882,18997,18998,19001],{},[29,18999,19000],{},"AcroForm (入力可能フォーム)","。gpdf はまだ生成しない。閲覧側でユーザー入力を受けるテンプレ PDF を作っているなら、当面は AcroForm 対応のライブラリに残る選択肢。",[882,19003,19004,19007],{},[29,19005,19006],{},"注釈・ブックマーク","。基本的なアウトラインは出るが、リッチな注釈は未対応。",[18,19009,19010],{},"このどれにも刺さらないなら、移行はスルッと終わる。刺さるなら GitHub Issue を立ててほしい — ロードマップは要望ベース。",[14,19012,2820],{"id":2819},[18,19014,19015,19018],{},[29,19016,19017],{},"gpdf は gofpdf のフォーク?","\n違う。gpdf は純 Go でゼロからの再実装。PDF ワイヤフォーマット、レイアウトエンジン、TrueType サブセッタ、すべて新規。gofpdf やそのフォークと共有しているコードはない。なぜフォークではないかというと、gofpdf のアーキテクチャは「単一のミュータブルなカーソル」を前提に作られていて、宣言的グリッドを後付けすると既存の呼び出しが全部壊れるから。",[18,19020,19021,19024,19025,19027,19028,19031,19032,19034,19035,19038],{},[29,19022,19023],{},"外部依存はある?","\nコアはゼロ。",[45,19026,7100],{}," のあとに ",[45,19029,19030],{},"go mod graph | grep gpdf"," を叩くと 1 行しか返らない。",[45,19033,8235],{}," 拡張 (HTML→PDF、AES 暗号化、署名、PDF/A) は HTML パーサのために ",[45,19036,19037],{},"golang.org/x/net"," を引くが、これはオプトインで、移行に必須ではない。",[18,19040,19041,19044,19045,19047],{},[29,19042,19043],{},"CGO は? gofpdf は CGO フリーだったけど、gpdf は?","\n同じく純 Go・CGO なし。",[45,19046,7092],{}," でクロスコンパイルして静的バイナリで配布できる。Distroless や Alpine では CGO ツールチェーンがないだけでイメージが半分になるので、ここは大事。",[18,19049,19050,19056,19058],{},[29,19051,19052,19053,19055],{},"既存 gofpdf コードが ",[45,19054,1930],{}," だらけ。書き直しなしで移行できる?",[45,19057,14715],{}," をラップして似た感触は出せる。ただ、コード全体がカーソル操作中心の構造だと、レイアウトエンジン的モデルへの移行は構文ではなく頭の切り替え。多くのチームでは「書き直したほうが元より短い」になる。",[18,19060,19061,19064,19065,19067],{},[29,19062,19063],{},"電子帳簿保存法・適格請求書の対応は?","\nタイムスタンプとデジタル署名は ",[45,19066,8235],{}," で対応中。要求があるなら Issue を立てて優先度を上げてほしい。",[18,19069,19070,19073],{},[29,19071,19072],{},"go-pdf/fpdf がアーカイブ解除されたら?","\n選択肢が 1 つ増えるだけ。gpdf を作った賭けは「gofpdf が永久にアーカイブ」のほうではなく、「カーソルベース・1 バイトフォント・CJK 非対応というアーキテクチャ自体が、誰がメンテしても袋小路」のほう。2026 年の PDF 生成はプロッタを動かすより Web ページを組むのに近く、API もそれを反映すべき。",[14,19075,1241],{"id":1240},[18,19077,6609],{},[57,19079,19080],{"className":1247,"code":1248,"language":1249,"meta":62,"style":62},[45,19081,19082],{"__ignoreMap":62},[66,19083,19084,19086,19088],{"class":68,"line":69},[66,19085,61],{"class":76},[66,19087,1258],{"class":212},[66,19089,1261],{"class":212},[18,19091,19092,1268,19095],{},[21,19093,2885],{"href":23,"rel":19094},[25],[21,19096,2889],{"href":1271,"rel":19097},[25],[14,19099,2892],{"id":2892},[1109,19101,19102,19108,19113],{},[882,19103,19104,19105],{},"12 カラムグリッドの仕組み ",[6712,19106,19107],{},"(近日公開)",[882,19109,19110,19111],{},"gpdf で日本語フォントを埋め込むには? ",[6712,19112,19107],{},[882,19114,19115,19118,19119,19121],{},[21,19116,11678],{"href":1271,"rel":19117},[25]," — 5 分セットアップ、",[45,19120,6731],{}," 含む",[1275,19123,19124],{},"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":62,"searchDepth":80,"depth":80,"links":19126},[19127,19128,19129,19130,19131,19132,19133,19134,19135,19136,19137,19138,19139,19140],{"id":1334,"depth":80,"text":1335},{"id":14457,"depth":80,"text":14458},{"id":14486,"depth":80,"text":14487},{"id":14525,"depth":80,"text":14526},{"id":14838,"depth":80,"text":14839},{"id":15429,"depth":80,"text":15430},{"id":16564,"depth":80,"text":16565},{"id":17217,"depth":80,"text":17218},{"id":18248,"depth":80,"text":18249},{"id":18871,"depth":80,"text":18872},{"id":18967,"depth":80,"text":18967},{"id":2819,"depth":80,"text":2820},{"id":1240,"depth":80,"text":1241},{"id":2892,"depth":80,"text":2892},"2026-04-14","gofpdf は 2021 年アーカイブ、後継 go-pdf/fpdf も 2025 年停止。CJK 対応・ゼロ依存の純 Go ライブラリ gpdf への移行ガイド。",{"name":19144,"totalTime":19145,"tools":19146,"steps":19147},"Goプロジェクトを gofpdf から gpdf に移行する","PT30M",[1298],[19148,19151,19154,19157,19160,19163],{"name":19149,"text":19150},"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":19152,"text":19153},"カーソル操作ではなくビルダーでドキュメントを構築する","gpdf.NewDocument に WithPageSize / WithMargins / WithFont を渡して生成する。SetXY でカーソルを押し回す代わりに、doc.AddPage() でページを追加し RowBuilder / ColBuilder で内容を宣言する。",{"name":19155,"text":19156},"Cell / MultiCell を宣言的な Text に書き換える","pdf.Cell や pdf.MultiCell をカラム内の c.Text(...) に置き換える。テキストはカラム境界で自動折り返しされるため MultiCell の末尾フラグは不要。フォントサイズ・太字・色は per-text のオプションとして渡す。",{"name":19158,"text":19159},"CJK フォントを WithFont で登録する","日本語・中国語・韓国語のテキストでは、pdf.AddUTF8Font の代わりにドキュメント構築時 gpdf.WithFont(name, ttfBytes) を渡す。TTF パスの管理も UTF-8 フラグも不要になり、サブセット埋め込みは自動で行われる。",{"name":19161,"text":19162},"表を行とカラムで書き直す","列幅を手動管理する Cell のネストループをやめ、AutoRow の中で row.Col(n, fn) を並べて行を作る。12 カラムグリッドが幅計算もページ分割も扱ってくれる。",{"name":19164,"text":19165},"出力呼び出しを切り替える","pdf.OutputFileAndClose(path) の代わりに doc.Generate() で []byte を取得し os.WriteFile(path, data, 0o644) で書き出す。io.Writer に直接書くなら doc.Render(w) を使う。",{},{"title":14401,"description":19142},"ja/blog/001.gofpdf-migration",[8411,2949,1324,1323],"owtjceeRgENHD4xA9d9ovm2x1g5r07BZi2cDAJRyP2I",1776537636830]