[{"data":1,"prerenderedAt":1587},["ShallowReactive",2],{"blog-ja-add-custom-truetype-font":3},{"id":4,"title":5,"author":6,"body":10,"date":1555,"description":1556,"draft":1557,"extension":1558,"howTo":1559,"image":1578,"meta":1579,"navigation":105,"path":1580,"seo":1581,"stem":1582,"tags":1583,"updated":1578,"__hash__":1586},"blogJa/ja/blog/018.add-custom-truetype-font.md","gpdf にカスタム TrueType フォントを追加するには？",{"name":7,"url":8,"avatar":9},"野田大貴","https://nadai.dev/ja/about","https://nadai.dev/og-default.png",{"type":11,"value":12,"toc":1542},"minimark",[13,17,37,40,59,75,78,717,735,739,775,781,785,792,864,872,875,1132,1143,1146,1152,1240,1246,1249,1252,1409,1412,1416,1423,1435,1438,1477,1480,1502,1506,1509,1526,1538],[14,15,16],"h2",{"id":16},"言い換えると",[18,19,20,24,25,32,33,36],"p",{},[21,22,23],"code",{},".ttf"," がある。ブランドの Inter、コードブロック用の JetBrains Mono、アイコン用のグリフフォント。これを ",[26,27,31],"a",{"href":28,"rel":29},"https://github.com/gpdf-dev/gpdf",[30],"nofollow","gpdf"," のドキュメントに取り込んで、",[21,34,35],{},"c.Text(...)"," から名前で呼びたい。どうやる?",[14,38,39],{"id":39},"即答",[18,41,42,43,46,47,50,51,54,55,58],{},"TTF のバイト列を読む。",[21,44,45],{},"gpdf.WithFont(\"好きなファミリ名\", bytes)"," を ",[21,48,49],{},"NewDocument"," に渡す。あとは ",[21,52,53],{},"template.FontFamily(...)"," でその名前を指定するか、",[21,56,57],{},"gpdf.WithDefaultFont"," で既定にしてしまえばいい。",[18,60,61,62,66,67,70,71,74],{},"ファミリ名は ",[63,64,65],"strong",{},"任意の文字列","。フォント内部の ",[21,68,69],{},"name"," テーブルとは無関係で、gpdf が ",[21,72,73],{},"FontFamily"," を解決するときに使うルックアップキーに過ぎない。短めの名前にしておく。",[14,76,77],{"id":77},"動くコード",[79,80,85],"pre",{"className":81,"code":82,"language":83,"meta":84,"style":84},"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    regular, err := os.ReadFile(\"Inter-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(\"Inter\", regular),\n        gpdf.WithDefaultFont(\"Inter\", 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(\"Quarterly Report\", template.FontSize(28))\n            c.Text(\"Generated with gpdf and Inter.\")\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","",[21,86,87,100,107,117,129,139,144,154,164,174,180,185,201,240,256,274,280,285,303,326,364,390,415,421,426,445,479,516,554,574,580,586,591,612,625,640,645,691,706,711],{"__ignoreMap":84},[88,89,92,96],"span",{"class":90,"line":91},"line",1,[88,93,95],{"class":94},"sMK4o","package",[88,97,99],{"class":98},"sBMFI"," main\n",[88,101,103],{"class":90,"line":102},2,[88,104,106],{"emptyLinePlaceholder":105},true,"\n",[88,108,110,114],{"class":90,"line":109},3,[88,111,113],{"class":112},"s7zQu","import",[88,115,116],{"class":94}," (\n",[88,118,120,123,126],{"class":90,"line":119},4,[88,121,122],{"class":94},"    \"",[88,124,125],{"class":98},"log",[88,127,128],{"class":94},"\"\n",[88,130,132,134,137],{"class":90,"line":131},5,[88,133,122],{"class":94},[88,135,136],{"class":98},"os",[88,138,128],{"class":94},[88,140,142],{"class":90,"line":141},6,[88,143,106],{"emptyLinePlaceholder":105},[88,145,147,149,152],{"class":90,"line":146},7,[88,148,122],{"class":94},[88,150,151],{"class":98},"github.com/gpdf-dev/gpdf",[88,153,128],{"class":94},[88,155,157,159,162],{"class":90,"line":156},8,[88,158,122],{"class":94},[88,160,161],{"class":98},"github.com/gpdf-dev/gpdf/document",[88,163,128],{"class":94},[88,165,167,169,172],{"class":90,"line":166},9,[88,168,122],{"class":94},[88,170,171],{"class":98},"github.com/gpdf-dev/gpdf/template",[88,173,128],{"class":94},[88,175,177],{"class":90,"line":176},10,[88,178,179],{"class":94},")\n",[88,181,183],{"class":90,"line":182},11,[88,184,106],{"emptyLinePlaceholder":105},[88,186,188,191,195,198],{"class":90,"line":187},12,[88,189,190],{"class":94},"func",[88,192,194],{"class":193},"s2Zo4"," main",[88,196,197],{"class":94},"()",[88,199,200],{"class":94}," {\n",[88,202,204,208,211,214,217,220,223,226,229,232,236,238],{"class":90,"line":203},13,[88,205,207],{"class":206},"sTEyZ","    regular",[88,209,210],{"class":94},",",[88,212,213],{"class":206}," err ",[88,215,216],{"class":94},":=",[88,218,219],{"class":206}," os",[88,221,222],{"class":94},".",[88,224,225],{"class":193},"ReadFile",[88,227,228],{"class":94},"(",[88,230,231],{"class":94},"\"",[88,233,235],{"class":234},"sfazB","Inter-Regular.ttf",[88,237,231],{"class":94},[88,239,179],{"class":94},[88,241,243,246,248,251,254],{"class":90,"line":242},14,[88,244,245],{"class":112},"    if",[88,247,213],{"class":206},[88,249,250],{"class":94},"!=",[88,252,253],{"class":94}," nil",[88,255,200],{"class":94},[88,257,259,262,264,267,269,272],{"class":90,"line":258},15,[88,260,261],{"class":206},"        log",[88,263,222],{"class":94},[88,265,266],{"class":193},"Fatal",[88,268,228],{"class":94},[88,270,271],{"class":206},"err",[88,273,179],{"class":94},[88,275,277],{"class":90,"line":276},16,[88,278,279],{"class":94},"    }\n",[88,281,283],{"class":90,"line":282},17,[88,284,106],{"emptyLinePlaceholder":105},[88,286,288,291,293,296,298,300],{"class":90,"line":287},18,[88,289,290],{"class":206},"    doc ",[88,292,216],{"class":94},[88,294,295],{"class":206}," gpdf",[88,297,222],{"class":94},[88,299,49],{"class":193},[88,301,302],{"class":94},"(\n",[88,304,306,309,311,314,316,318,320,323],{"class":90,"line":305},19,[88,307,308],{"class":206},"        gpdf",[88,310,222],{"class":94},[88,312,313],{"class":193},"WithPageSize",[88,315,228],{"class":94},[88,317,31],{"class":206},[88,319,222],{"class":94},[88,321,322],{"class":206},"A4",[88,324,325],{"class":94},"),\n",[88,327,329,331,333,336,338,341,343,346,348,350,352,355,357,361],{"class":90,"line":328},20,[88,330,308],{"class":206},[88,332,222],{"class":94},[88,334,335],{"class":193},"WithMargins",[88,337,228],{"class":94},[88,339,340],{"class":206},"document",[88,342,222],{"class":94},[88,344,345],{"class":193},"UniformEdges",[88,347,228],{"class":94},[88,349,340],{"class":206},[88,351,222],{"class":94},[88,353,354],{"class":193},"Mm",[88,356,228],{"class":94},[88,358,360],{"class":359},"sbssI","20",[88,362,363],{"class":94},"))),\n",[88,365,367,369,371,374,376,378,381,383,385,388],{"class":90,"line":366},21,[88,368,308],{"class":206},[88,370,222],{"class":94},[88,372,373],{"class":193},"WithFont",[88,375,228],{"class":94},[88,377,231],{"class":94},[88,379,380],{"class":234},"Inter",[88,382,231],{"class":94},[88,384,210],{"class":94},[88,386,387],{"class":206}," regular",[88,389,325],{"class":94},[88,391,393,395,397,400,402,404,406,408,410,413],{"class":90,"line":392},22,[88,394,308],{"class":206},[88,396,222],{"class":94},[88,398,399],{"class":193},"WithDefaultFont",[88,401,228],{"class":94},[88,403,231],{"class":94},[88,405,380],{"class":234},[88,407,231],{"class":94},[88,409,210],{"class":94},[88,411,412],{"class":359}," 11",[88,414,325],{"class":94},[88,416,418],{"class":90,"line":417},23,[88,419,420],{"class":94},"    )\n",[88,422,424],{"class":90,"line":423},24,[88,425,106],{"emptyLinePlaceholder":105},[88,427,429,432,434,437,439,442],{"class":90,"line":428},25,[88,430,431],{"class":206},"    page ",[88,433,216],{"class":94},[88,435,436],{"class":206}," doc",[88,438,222],{"class":94},[88,440,441],{"class":193},"AddPage",[88,443,444],{"class":94},"()\n",[88,446,448,451,453,456,459,463,466,469,471,474,477],{"class":90,"line":447},26,[88,449,450],{"class":206},"    page",[88,452,222],{"class":94},[88,454,455],{"class":193},"AutoRow",[88,457,458],{"class":94},"(func(",[88,460,462],{"class":461},"sHdIc","r",[88,464,465],{"class":94}," *",[88,467,468],{"class":98},"template",[88,470,222],{"class":94},[88,472,473],{"class":98},"RowBuilder",[88,475,476],{"class":94},")",[88,478,200],{"class":94},[88,480,482,485,487,490,492,495,497,500,503,505,507,509,512,514],{"class":90,"line":481},27,[88,483,484],{"class":206},"        r",[88,486,222],{"class":94},[88,488,489],{"class":193},"Col",[88,491,228],{"class":94},[88,493,494],{"class":359},"12",[88,496,210],{"class":94},[88,498,499],{"class":94}," func(",[88,501,502],{"class":461},"c",[88,504,465],{"class":94},[88,506,468],{"class":98},[88,508,222],{"class":94},[88,510,511],{"class":98},"ColBuilder",[88,513,476],{"class":94},[88,515,200],{"class":94},[88,517,519,522,524,527,529,531,534,536,538,541,543,546,548,551],{"class":90,"line":518},28,[88,520,521],{"class":206},"            c",[88,523,222],{"class":94},[88,525,526],{"class":193},"Text",[88,528,228],{"class":94},[88,530,231],{"class":94},[88,532,533],{"class":234},"Quarterly Report",[88,535,231],{"class":94},[88,537,210],{"class":94},[88,539,540],{"class":206}," template",[88,542,222],{"class":94},[88,544,545],{"class":193},"FontSize",[88,547,228],{"class":94},[88,549,550],{"class":359},"28",[88,552,553],{"class":94},"))\n",[88,555,557,559,561,563,565,567,570,572],{"class":90,"line":556},29,[88,558,521],{"class":206},[88,560,222],{"class":94},[88,562,526],{"class":193},[88,564,228],{"class":94},[88,566,231],{"class":94},[88,568,569],{"class":234},"Generated with gpdf and Inter.",[88,571,231],{"class":94},[88,573,179],{"class":94},[88,575,577],{"class":90,"line":576},30,[88,578,579],{"class":94},"        })\n",[88,581,583],{"class":90,"line":582},31,[88,584,585],{"class":94},"    })\n",[88,587,589],{"class":90,"line":588},32,[88,590,106],{"emptyLinePlaceholder":105},[88,592,594,597,599,601,603,605,607,610],{"class":90,"line":593},33,[88,595,596],{"class":206},"    data",[88,598,210],{"class":94},[88,600,213],{"class":206},[88,602,216],{"class":94},[88,604,436],{"class":206},[88,606,222],{"class":94},[88,608,609],{"class":193},"Generate",[88,611,444],{"class":94},[88,613,615,617,619,621,623],{"class":90,"line":614},34,[88,616,245],{"class":112},[88,618,213],{"class":206},[88,620,250],{"class":94},[88,622,253],{"class":94},[88,624,200],{"class":94},[88,626,628,630,632,634,636,638],{"class":90,"line":627},35,[88,629,261],{"class":206},[88,631,222],{"class":94},[88,633,266],{"class":193},[88,635,228],{"class":94},[88,637,271],{"class":206},[88,639,179],{"class":94},[88,641,643],{"class":90,"line":642},36,[88,644,279],{"class":94},[88,646,648,650,652,654,656,658,661,663,665,668,670,672,675,677,680,683,685,687,689],{"class":90,"line":647},37,[88,649,245],{"class":112},[88,651,213],{"class":206},[88,653,216],{"class":94},[88,655,219],{"class":206},[88,657,222],{"class":94},[88,659,660],{"class":193},"WriteFile",[88,662,228],{"class":94},[88,664,231],{"class":94},[88,666,667],{"class":234},"report.pdf",[88,669,231],{"class":94},[88,671,210],{"class":94},[88,673,674],{"class":206}," data",[88,676,210],{"class":94},[88,678,679],{"class":359}," 0o644",[88,681,682],{"class":94},");",[88,684,213],{"class":206},[88,686,250],{"class":94},[88,688,253],{"class":94},[88,690,200],{"class":94},[88,692,694,696,698,700,702,704],{"class":90,"line":693},38,[88,695,261],{"class":206},[88,697,222],{"class":94},[88,699,266],{"class":193},[88,701,228],{"class":94},[88,703,271],{"class":206},[88,705,179],{"class":94},[88,707,709],{"class":90,"line":708},39,[88,710,279],{"class":94},[88,712,714],{"class":90,"line":713},40,[88,715,716],{"class":94},"}\n",[18,718,719,46,721,724,725,730,731,734],{},[21,720,235],{},[21,722,723],{},"main.go"," の隣に置く (",[26,726,729],{"href":727,"rel":728},"https://rsms.me/inter/",[30],"rsms.me/inter"," からダウンロード)。",[21,732,733],{},"go run main.go","。これだけ。",[14,736,738],{"id":737},"gpdf-がバイト列に対してやっていること","gpdf がバイト列に対してやっていること",[18,740,741,744,745,748,749,748,752,748,755,758,759,762,763,766,767,770,771,774],{},[21,742,743],{},"Generate()"," が呼ばれると、gpdf は TrueType のテーブル (",[21,746,747],{},"cmap",", ",[21,750,751],{},"glyf",[21,753,754],{},"loca",[21,756,757],{},"hmtx"," …) を ",[63,760,761],{},"純 Go でパースする","。FreeType も CGO もない。レンダリング対象のテキストを走査し、実際に使われたコードポイントを集め、",[63,764,765],{},"そのコードポイント分だけにグリフテーブルをサブセット化"," する。PDF には ",[21,768,769],{},"Type0"," / ",[21,772,773],{},"CIDFontType2"," フォントとして、必要なグリフだけが埋め込まれる。",[18,776,777,778,780],{},"実際的な効果: 600 KB の ",[21,779,235],{}," を 1〜2 段落で使っただけなら、PDF 内のフォントサブセットは 12 KB 程度になる。ブランドフォントを使ってもファイルが膨れない。",[14,782,784],{"id":783},"bold-italic-は別ファイルが必要","Bold / Italic は別ファイルが必要",[18,786,787,788,791],{},"ここが踏み抜きやすい。gpdf は ",[63,789,790],{},"bold や italic を合成しない","。「線を太くする」ようなアルゴリズム処理はない。代わりに、スタイルフラグからバリアント ID を組み立ててルックアップする:",[793,794,795,815],"table",{},[796,797,798],"thead",{},[799,800,801,807,812],"tr",{},[802,803,804],"th",{},[21,805,806],{},"Bold()",[802,808,809],{},[21,810,811],{},"Italic()",[802,813,814],{},"ルックアップキー",[816,817,818,830,842,853],"tbody",{},[799,819,820,824,826],{},[821,822,823],"td",{},"なし",[821,825,823],{},[821,827,828],{},[21,829,380],{},[799,831,832,835,837],{},[821,833,834],{},"あり",[821,836,823],{},[821,838,839],{},[21,840,841],{},"Inter-Bold",[799,843,844,846,848],{},[821,845,823],{},[821,847,834],{},[821,849,850],{},[21,851,852],{},"Inter-Italic",[799,854,855,857,859],{},[821,856,834],{},[821,858,834],{},[821,860,861],{},[21,862,863],{},"Inter-BoldItalic",[18,865,866,868,869,871],{},[21,867,841],{}," を登録していなければ、ルックアップは黙って素の ",[21,870,380],{}," にフォールバックする。PDF は出力されるが、太字のはずの箇所がレギュラーのまま。警告は出ない。",[18,873,874],{},"4 種類とも登録する:",[79,876,878],{"className":81,"code":877,"language":83,"meta":84,"style":84},"regular, _    := os.ReadFile(\"Inter-Regular.ttf\")\nbold, _       := os.ReadFile(\"Inter-Bold.ttf\")\nitalic, _     := os.ReadFile(\"Inter-Italic.ttf\")\nboldItalic, _ := os.ReadFile(\"Inter-BoldItalic.ttf\")\n\ndoc := gpdf.NewDocument(\n    gpdf.WithFont(\"Inter\", regular),\n    gpdf.WithFont(\"Inter-Bold\", bold),\n    gpdf.WithFont(\"Inter-Italic\", italic),\n    gpdf.WithFont(\"Inter-BoldItalic\", boldItalic),\n    gpdf.WithDefaultFont(\"Inter\", 11),\n)\n",[21,879,880,908,937,966,995,999,1014,1037,1060,1083,1106,1128],{"__ignoreMap":84},[88,881,882,885,887,890,892,894,896,898,900,902,904,906],{"class":90,"line":91},[88,883,884],{"class":206},"regular",[88,886,210],{"class":94},[88,888,889],{"class":206}," _    ",[88,891,216],{"class":94},[88,893,219],{"class":206},[88,895,222],{"class":94},[88,897,225],{"class":193},[88,899,228],{"class":94},[88,901,231],{"class":94},[88,903,235],{"class":234},[88,905,231],{"class":94},[88,907,179],{"class":94},[88,909,910,913,915,918,920,922,924,926,928,930,933,935],{"class":90,"line":102},[88,911,912],{"class":206},"bold",[88,914,210],{"class":94},[88,916,917],{"class":206}," _       ",[88,919,216],{"class":94},[88,921,219],{"class":206},[88,923,222],{"class":94},[88,925,225],{"class":193},[88,927,228],{"class":94},[88,929,231],{"class":94},[88,931,932],{"class":234},"Inter-Bold.ttf",[88,934,231],{"class":94},[88,936,179],{"class":94},[88,938,939,942,944,947,949,951,953,955,957,959,962,964],{"class":90,"line":109},[88,940,941],{"class":206},"italic",[88,943,210],{"class":94},[88,945,946],{"class":206}," _     ",[88,948,216],{"class":94},[88,950,219],{"class":206},[88,952,222],{"class":94},[88,954,225],{"class":193},[88,956,228],{"class":94},[88,958,231],{"class":94},[88,960,961],{"class":234},"Inter-Italic.ttf",[88,963,231],{"class":94},[88,965,179],{"class":94},[88,967,968,971,973,976,978,980,982,984,986,988,991,993],{"class":90,"line":119},[88,969,970],{"class":206},"boldItalic",[88,972,210],{"class":94},[88,974,975],{"class":206}," _ ",[88,977,216],{"class":94},[88,979,219],{"class":206},[88,981,222],{"class":94},[88,983,225],{"class":193},[88,985,228],{"class":94},[88,987,231],{"class":94},[88,989,990],{"class":234},"Inter-BoldItalic.ttf",[88,992,231],{"class":94},[88,994,179],{"class":94},[88,996,997],{"class":90,"line":131},[88,998,106],{"emptyLinePlaceholder":105},[88,1000,1001,1004,1006,1008,1010,1012],{"class":90,"line":141},[88,1002,1003],{"class":206},"doc ",[88,1005,216],{"class":94},[88,1007,295],{"class":206},[88,1009,222],{"class":94},[88,1011,49],{"class":193},[88,1013,302],{"class":94},[88,1015,1016,1019,1021,1023,1025,1027,1029,1031,1033,1035],{"class":90,"line":146},[88,1017,1018],{"class":206},"    gpdf",[88,1020,222],{"class":94},[88,1022,373],{"class":193},[88,1024,228],{"class":94},[88,1026,231],{"class":94},[88,1028,380],{"class":234},[88,1030,231],{"class":94},[88,1032,210],{"class":94},[88,1034,387],{"class":206},[88,1036,325],{"class":94},[88,1038,1039,1041,1043,1045,1047,1049,1051,1053,1055,1058],{"class":90,"line":156},[88,1040,1018],{"class":206},[88,1042,222],{"class":94},[88,1044,373],{"class":193},[88,1046,228],{"class":94},[88,1048,231],{"class":94},[88,1050,841],{"class":234},[88,1052,231],{"class":94},[88,1054,210],{"class":94},[88,1056,1057],{"class":206}," bold",[88,1059,325],{"class":94},[88,1061,1062,1064,1066,1068,1070,1072,1074,1076,1078,1081],{"class":90,"line":166},[88,1063,1018],{"class":206},[88,1065,222],{"class":94},[88,1067,373],{"class":193},[88,1069,228],{"class":94},[88,1071,231],{"class":94},[88,1073,852],{"class":234},[88,1075,231],{"class":94},[88,1077,210],{"class":94},[88,1079,1080],{"class":206}," italic",[88,1082,325],{"class":94},[88,1084,1085,1087,1089,1091,1093,1095,1097,1099,1101,1104],{"class":90,"line":176},[88,1086,1018],{"class":206},[88,1088,222],{"class":94},[88,1090,373],{"class":193},[88,1092,228],{"class":94},[88,1094,231],{"class":94},[88,1096,863],{"class":234},[88,1098,231],{"class":94},[88,1100,210],{"class":94},[88,1102,1103],{"class":206}," boldItalic",[88,1105,325],{"class":94},[88,1107,1108,1110,1112,1114,1116,1118,1120,1122,1124,1126],{"class":90,"line":182},[88,1109,1018],{"class":206},[88,1111,222],{"class":94},[88,1113,399],{"class":193},[88,1115,228],{"class":94},[88,1117,231],{"class":94},[88,1119,380],{"class":234},[88,1121,231],{"class":94},[88,1123,210],{"class":94},[88,1125,412],{"class":359},[88,1127,325],{"class":94},[88,1129,1130],{"class":90,"line":187},[88,1131,179],{"class":94},[18,1133,1134,1135,1138,1139,1142],{},"ウェイトが 1 種類しかないフォント (アイコンフォントやディスプレイフォントに多い) なら、そのフォントに対しては ",[21,1136,1137],{},"template.Bold()"," や ",[21,1140,1141],{},"template.Italic()"," を使わない。バリアントを持たないこと自体は問題ない。間違ったバリアントにフォールバックするのが「太字が太字にならない」バグ報告の正体。",[14,1144,1145],{"id":1145},"バイナリにフォントを同梱する",[18,1147,1148,1151],{},[21,1149,1150],{},"os.ReadFile"," を起動時に読むのは開発中なら良い。本番ではフォントはプログラムの一部なので、バイナリに同梱するのが筋:",[79,1153,1155],{"className":81,"code":1154,"language":83,"meta":84,"style":84},"import _ \"embed\"\n\n//go:embed fonts/Inter-Regular.ttf\nvar interRegular []byte\n\ndoc := gpdf.NewDocument(\n    gpdf.WithFont(\"Inter\", interRegular),\n)\n",[21,1156,1157,1170,1174,1180,1195,1199,1213,1236],{"__ignoreMap":84},[88,1158,1159,1161,1163,1165,1168],{"class":90,"line":91},[88,1160,113],{"class":112},[88,1162,975],{"class":206},[88,1164,231],{"class":94},[88,1166,1167],{"class":98},"embed",[88,1169,128],{"class":94},[88,1171,1172],{"class":90,"line":102},[88,1173,106],{"emptyLinePlaceholder":105},[88,1175,1176],{"class":90,"line":109},[88,1177,1179],{"class":1178},"sHwdD","//go:embed fonts/Inter-Regular.ttf\n",[88,1181,1182,1185,1188,1191],{"class":90,"line":119},[88,1183,1184],{"class":94},"var",[88,1186,1187],{"class":206}," interRegular ",[88,1189,1190],{"class":94},"[]",[88,1192,1194],{"class":1193},"spNyl","byte\n",[88,1196,1197],{"class":90,"line":131},[88,1198,106],{"emptyLinePlaceholder":105},[88,1200,1201,1203,1205,1207,1209,1211],{"class":90,"line":141},[88,1202,1003],{"class":206},[88,1204,216],{"class":94},[88,1206,295],{"class":206},[88,1208,222],{"class":94},[88,1210,49],{"class":193},[88,1212,302],{"class":94},[88,1214,1215,1217,1219,1221,1223,1225,1227,1229,1231,1234],{"class":90,"line":146},[88,1216,1018],{"class":206},[88,1218,222],{"class":94},[88,1220,373],{"class":193},[88,1222,228],{"class":94},[88,1224,231],{"class":94},[88,1226,380],{"class":234},[88,1228,231],{"class":94},[88,1230,210],{"class":94},[88,1232,1233],{"class":206}," interRegular",[88,1235,325],{"class":94},[88,1237,1238],{"class":90,"line":156},[88,1239,179],{"class":94},[18,1241,1242,1245],{},[21,1243,1244],{},"go build"," がバイト列をバイナリに焼き込む。「デプロイイメージのどこに .ttf があるんだ」を金曜の夕方に追う羽目にならない。",[14,1247,1248],{"id":1248},"アイコンフォントも同じ手順",[18,1250,1251],{},"Font Awesome、Material Symbols の TTF 版、IcoMoon、自社ブランドのグリフセット。これらも全部 TrueType ファイル。同じ手順で登録する:",[79,1253,1255],{"className":81,"code":1254,"language":83,"meta":84,"style":84},"icons, _ := os.ReadFile(\"MaterialSymbols-Regular.ttf\")\ndoc := gpdf.NewDocument(\n    gpdf.WithFont(\"Icons\", icons),\n    gpdf.WithDefaultFont(\"Inter\", 11), // 本文の既定\n)\n\n// カラム内で:\nc.Text(\"\", template.FontFamily(\"Icons\"), template.FontSize(20)) // \"home\" アイコン\n",[21,1256,1257,1285,1299,1323,1349,1353,1357,1362],{"__ignoreMap":84},[88,1258,1259,1262,1264,1266,1268,1270,1272,1274,1276,1278,1281,1283],{"class":90,"line":91},[88,1260,1261],{"class":206},"icons",[88,1263,210],{"class":94},[88,1265,975],{"class":206},[88,1267,216],{"class":94},[88,1269,219],{"class":206},[88,1271,222],{"class":94},[88,1273,225],{"class":193},[88,1275,228],{"class":94},[88,1277,231],{"class":94},[88,1279,1280],{"class":234},"MaterialSymbols-Regular.ttf",[88,1282,231],{"class":94},[88,1284,179],{"class":94},[88,1286,1287,1289,1291,1293,1295,1297],{"class":90,"line":102},[88,1288,1003],{"class":206},[88,1290,216],{"class":94},[88,1292,295],{"class":206},[88,1294,222],{"class":94},[88,1296,49],{"class":193},[88,1298,302],{"class":94},[88,1300,1301,1303,1305,1307,1309,1311,1314,1316,1318,1321],{"class":90,"line":109},[88,1302,1018],{"class":206},[88,1304,222],{"class":94},[88,1306,373],{"class":193},[88,1308,228],{"class":94},[88,1310,231],{"class":94},[88,1312,1313],{"class":234},"Icons",[88,1315,231],{"class":94},[88,1317,210],{"class":94},[88,1319,1320],{"class":206}," icons",[88,1322,325],{"class":94},[88,1324,1325,1327,1329,1331,1333,1335,1337,1339,1341,1343,1346],{"class":90,"line":119},[88,1326,1018],{"class":206},[88,1328,222],{"class":94},[88,1330,399],{"class":193},[88,1332,228],{"class":94},[88,1334,231],{"class":94},[88,1336,380],{"class":234},[88,1338,231],{"class":94},[88,1340,210],{"class":94},[88,1342,412],{"class":359},[88,1344,1345],{"class":94},"),",[88,1347,1348],{"class":1178}," // 本文の既定\n",[88,1350,1351],{"class":90,"line":131},[88,1352,179],{"class":94},[88,1354,1355],{"class":90,"line":141},[88,1356,106],{"emptyLinePlaceholder":105},[88,1358,1359],{"class":90,"line":146},[88,1360,1361],{"class":1178},"// カラム内で:\n",[88,1363,1364,1366,1368,1370,1372,1375,1377,1379,1381,1383,1385,1387,1389,1391,1393,1395,1397,1399,1401,1403,1406],{"class":90,"line":156},[88,1365,502],{"class":206},[88,1367,222],{"class":94},[88,1369,526],{"class":193},[88,1371,228],{"class":94},[88,1373,1374],{"class":94},"\"\"",[88,1376,210],{"class":94},[88,1378,540],{"class":206},[88,1380,222],{"class":94},[88,1382,73],{"class":193},[88,1384,228],{"class":94},[88,1386,231],{"class":94},[88,1388,1313],{"class":234},[88,1390,231],{"class":94},[88,1392,1345],{"class":94},[88,1394,540],{"class":206},[88,1396,222],{"class":94},[88,1398,545],{"class":193},[88,1400,228],{"class":94},[88,1402,360],{"class":359},[88,1404,1405],{"class":94},"))",[88,1407,1408],{"class":1178}," // \"home\" アイコン\n",[18,1410,1411],{},"Unicode エスケープはフォントのドキュメントが書いてあるとおりの値。gpdf はそれがアイコンかどうか気にしない — 単なるコードポイントとして文字と同じようにサブセット化する。",[14,1413,1415],{"id":1414},"商用日本語フォント-モリサワフォントワークス-を使う場合","商用日本語フォント (モリサワ・フォントワークス) を使う場合",[18,1417,1418,1419,1422],{},"商用の日本語フォントをサーバーサイドで PDF に埋め込む用途は、ライセンスで明確に許諾されているか必ず確認する。サブセット埋め込みでも「PDF への埋め込み許可」と「サーバーでの動的生成許可」は別条項なことが多い。",[63,1420,1421],{},"ライセンス確認なしに業務 PDF に埋め込むと、後から億単位の請求につながり得る","。",[18,1424,1425,1426,1138,1430,1434],{},"無償で商用利用も可能な日本語 TTF が必要なら ",[26,1427,1429],{"href":1428},"/ja/blog/embed-japanese-font","Noto Sans JP",[26,1431,1433],{"href":1432},"/ja/blog/ipaex-gothic-gpdf","IPAex Gothic"," を選ぶのが安全。",[14,1436,1437],{"id":1437},"よくあるミス",[1439,1440,1441,1451,1464],"ul",{},[1442,1443,1444,1422,1447,1450],"li",{},[63,1445,1446],{},"呼び出し側でファミリ名のタイポ",[21,1448,1449],{},"template.FontFamily(\"Intr\")"," は黙って既定フォントにフォールバックする。エラーも警告も出ない。「急に Helvetica になった」と思ったらまずこれを疑う。",[1442,1452,1453,1460,1461,1463],{},[63,1454,1455,1456,1459],{},"コンテナで ",[21,1457,1458],{},"//go:embed"," を使わない","。Docker のビルドコンテキストから ",[21,1462,23],{}," が落ちて、ランタイムでフォールバックが効いて、顧客から指摘される、まで一連の流れ。組み込んでしまう。",[1442,1465,1466,1469,1470,1472,1473,1476],{},[63,1467,1468],{},"PostScript 名をファミリに使う","。「Inter-Regular」はフォントの PostScript 名。これを ",[21,1471,373],{}," に渡すと、bold ルックアップが「Inter-Regular-Bold」を探して見つからない。ルートのファミリ名 (",[21,1474,1475],{},"\"Inter\"",") を綺麗な形にして、バリアントサフィックスにスタイルを任せる。",[14,1478,1479],{"id":1479},"関連レシピ",[1439,1481,1482,1488,1495],{},[1442,1483,1484,1487],{},[26,1485,1486],{"href":1428},"gpdf に日本語フォントを埋め込むには?"," — 同じ仕組みで CJK 固有の補足あり",[1442,1489,1490,1494],{},[26,1491,1493],{"href":1492},"/ja/blog/bold-italic-together","Bold と Italic を同時に使うには?"," — バリアント解決の詳細",[1442,1496,1497,1501],{},[26,1498,1500],{"href":1499},"/ja/blog/tofu-boxes-japanese","なぜ PDF に豆腐 (□) が出るのか?"," — 登録したフォントがコードポイントをカバーしないとどうなるか",[14,1503,1505],{"id":1504},"gpdf-を使ってみる","gpdf を使ってみる",[18,1507,1508],{},"gpdf は Go の PDF 生成ライブラリ。MIT、外部依存ゼロ、TrueType 処理は純 Go 実装。",[79,1510,1514],{"className":1511,"code":1512,"language":1513,"meta":84,"style":84},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go get github.com/gpdf-dev/gpdf\n","bash",[21,1515,1516],{"__ignoreMap":84},[88,1517,1518,1520,1523],{"class":90,"line":91},[88,1519,83],{"class":98},[88,1521,1522],{"class":234}," get",[88,1524,1525],{"class":234}," github.com/gpdf-dev/gpdf\n",[18,1527,1528,1532,1533],{},[26,1529,1531],{"href":28,"rel":1530},[30],"⭐ GitHub でスター"," · ",[26,1534,1537],{"href":1535,"rel":1536},"https://gpdf.dev/ja/docs/quickstart",[30],"ドキュメントを読む",[1539,1540,1541],"style",{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}",{"title":84,"searchDepth":102,"depth":102,"links":1543},[1544,1545,1546,1547,1548,1549,1550,1551,1552,1553,1554],{"id":16,"depth":102,"text":16},{"id":39,"depth":102,"text":39},{"id":77,"depth":102,"text":77},{"id":737,"depth":102,"text":738},{"id":783,"depth":102,"text":784},{"id":1145,"depth":102,"text":1145},{"id":1248,"depth":102,"text":1248},{"id":1414,"depth":102,"text":1415},{"id":1437,"depth":102,"text":1437},{"id":1479,"depth":102,"text":1479},{"id":1504,"depth":102,"text":1505},"2026-04-30","TTF をバイト列で読み込んで gpdf.WithFont でファミリ名を登録する。Inter からアイコンフォントまで、どんな TrueType でも同じ手順で動く。",false,"md",{"name":1560,"totalTime":1561,"tools":1562,"steps":1565},"gpdf でカスタム TrueType フォントを登録する","PT10M",[1563,1564],"Go 1.22+","任意の TrueType .ttf ファイル (Inter、Roboto、JetBrains Mono、アイコンフォント等)",[1566,1569,1572,1575],{"name":1567,"text":1568},"TTF のバイト列を読み込む","os.ReadFile で .ttf を []byte に読み込む。本番ビルドではバイナリにフォントを同梱したいので //go:embed が望ましい。",{"name":1570,"text":1571},"ドキュメント生成時にファミリを登録する","gpdf.NewDocument に gpdf.WithFont(\"Inter\", fontBytes) を渡す。ファミリ名は呼び出し側で参照するためのキーで、フォント内部の name テーブルとは無関係に好きな文字列で良い。",{"name":1573,"text":1574},"既定フォントに設定するか、呼び出しごとに指定する","gpdf.WithDefaultFont(\"Inter\", 11) を加えれば全 c.Text に適用される。そうでなければ template.FontFamily(\"Inter\") を Text 呼び出しごとに渡す。",{"name":1576,"text":1577},"Bold / Italic / BoldItalic は別ファイルで登録する","各スタイルは独立した TTF。\"Inter-Bold\"、\"Inter-Italic\"、\"Inter-BoldItalic\" として登録する。gpdf はスタイルフラグからバリアント ID を組み立てて、その完全一致名でルックアップする。",null,{},"/ja/blog/add-custom-truetype-font",{"title":5,"description":1556},"ja/blog/018.add-custom-truetype-font",[1584,1585],"recipe","tutorial","fYiwz3FAX9qO0oK0tto8k4hddIrpD79N-p2zeUO9CyU",1779199021889]