[{"data":1,"prerenderedAt":1572},["ShallowReactive",2],{"blog-ko-add-custom-truetype-font":3},{"id":4,"title":5,"author":6,"body":10,"date":1540,"description":1541,"draft":1542,"extension":1543,"howTo":1544,"image":1563,"meta":1564,"navigation":112,"path":1565,"seo":1566,"stem":1567,"tags":1568,"updated":1563,"__hash__":1571},"blogKo/ko/blog/018.add-custom-truetype-font.md","gpdf에 커스텀 TrueType 폰트를 추가하려면?",{"name":7,"url":8,"avatar":9},"Taiki Noda","https://nadai.dev/en/about","https://nadai.dev/og-default.png",{"type":11,"value":12,"toc":1528},"minimark",[13,18,38,42,65,81,85,724,742,746,778,784,788,795,867,875,878,1135,1146,1150,1156,1244,1250,1254,1257,1414,1417,1421,1461,1465,1488,1492,1495,1512,1524],[14,15,17],"h2",{"id":16},"다시-말하면","다시 말하면",[19,20,21,25,26,33,34,37],"p",{},[22,23,24],"code",{},".ttf"," 파일이 있다 — 브랜드용 Inter, 코드 블록용 JetBrains Mono, 아이콘용 글리프 폰트. 이걸 ",[27,28,32],"a",{"href":29,"rel":30},"https://github.com/gpdf-dev/gpdf",[31],"nofollow","gpdf"," 문서에 넣고 ",[22,35,36],{},"c.Text(...)","에서 이름으로 참조하려면?",[14,39,41],{"id":40},"빠른-답","빠른 답",[19,43,44,45,48,49,52,53,56,57,60,61,64],{},"TTF 바이트를 읽는다. ",[22,46,47],{},"gpdf.WithFont(\"내패밀리\", bytes)","를 ",[22,50,51],{},"NewDocument","에 전달한다. 그리고 ",[22,54,55],{},"template.FontFamily(...)","로 ",[22,58,59],{},"\"내패밀리\"","를 참조하거나, ",[22,62,63],{},"gpdf.WithDefaultFont","로 기본값 설정.",[19,66,67,68,72,73,76,77,80],{},"패밀리 이름은 ",[69,70,71],"strong",{},"임의 문자열","이다. 폰트 내부의 ",[22,74,75],{},"name"," 테이블과 아무 관계 없다 — gpdf가 ",[22,78,79],{},"FontFamily"," 옵션을 해결할 때 쓰는 lookup 키일 뿐. 짧게 짓는 게 좋다.",[14,82,84],{"id":83},"동작하는-코드","동작하는 코드",[86,87,92],"pre",{"className":88,"code":89,"language":90,"meta":91,"style":91},"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","",[22,93,94,107,114,124,136,146,151,161,171,181,187,192,208,247,263,281,287,292,310,333,371,397,422,428,433,452,486,523,561,581,587,593,598,619,632,647,652,698,713,718],{"__ignoreMap":91},[95,96,99,103],"span",{"class":97,"line":98},"line",1,[95,100,102],{"class":101},"sMK4o","package",[95,104,106],{"class":105},"sBMFI"," main\n",[95,108,110],{"class":97,"line":109},2,[95,111,113],{"emptyLinePlaceholder":112},true,"\n",[95,115,117,121],{"class":97,"line":116},3,[95,118,120],{"class":119},"s7zQu","import",[95,122,123],{"class":101}," (\n",[95,125,127,130,133],{"class":97,"line":126},4,[95,128,129],{"class":101},"    \"",[95,131,132],{"class":105},"log",[95,134,135],{"class":101},"\"\n",[95,137,139,141,144],{"class":97,"line":138},5,[95,140,129],{"class":101},[95,142,143],{"class":105},"os",[95,145,135],{"class":101},[95,147,149],{"class":97,"line":148},6,[95,150,113],{"emptyLinePlaceholder":112},[95,152,154,156,159],{"class":97,"line":153},7,[95,155,129],{"class":101},[95,157,158],{"class":105},"github.com/gpdf-dev/gpdf",[95,160,135],{"class":101},[95,162,164,166,169],{"class":97,"line":163},8,[95,165,129],{"class":101},[95,167,168],{"class":105},"github.com/gpdf-dev/gpdf/document",[95,170,135],{"class":101},[95,172,174,176,179],{"class":97,"line":173},9,[95,175,129],{"class":101},[95,177,178],{"class":105},"github.com/gpdf-dev/gpdf/template",[95,180,135],{"class":101},[95,182,184],{"class":97,"line":183},10,[95,185,186],{"class":101},")\n",[95,188,190],{"class":97,"line":189},11,[95,191,113],{"emptyLinePlaceholder":112},[95,193,195,198,202,205],{"class":97,"line":194},12,[95,196,197],{"class":101},"func",[95,199,201],{"class":200},"s2Zo4"," main",[95,203,204],{"class":101},"()",[95,206,207],{"class":101}," {\n",[95,209,211,215,218,221,224,227,230,233,236,239,243,245],{"class":97,"line":210},13,[95,212,214],{"class":213},"sTEyZ","    regular",[95,216,217],{"class":101},",",[95,219,220],{"class":213}," err ",[95,222,223],{"class":101},":=",[95,225,226],{"class":213}," os",[95,228,229],{"class":101},".",[95,231,232],{"class":200},"ReadFile",[95,234,235],{"class":101},"(",[95,237,238],{"class":101},"\"",[95,240,242],{"class":241},"sfazB","Inter-Regular.ttf",[95,244,238],{"class":101},[95,246,186],{"class":101},[95,248,250,253,255,258,261],{"class":97,"line":249},14,[95,251,252],{"class":119},"    if",[95,254,220],{"class":213},[95,256,257],{"class":101},"!=",[95,259,260],{"class":101}," nil",[95,262,207],{"class":101},[95,264,266,269,271,274,276,279],{"class":97,"line":265},15,[95,267,268],{"class":213},"        log",[95,270,229],{"class":101},[95,272,273],{"class":200},"Fatal",[95,275,235],{"class":101},[95,277,278],{"class":213},"err",[95,280,186],{"class":101},[95,282,284],{"class":97,"line":283},16,[95,285,286],{"class":101},"    }\n",[95,288,290],{"class":97,"line":289},17,[95,291,113],{"emptyLinePlaceholder":112},[95,293,295,298,300,303,305,307],{"class":97,"line":294},18,[95,296,297],{"class":213},"    doc ",[95,299,223],{"class":101},[95,301,302],{"class":213}," gpdf",[95,304,229],{"class":101},[95,306,51],{"class":200},[95,308,309],{"class":101},"(\n",[95,311,313,316,318,321,323,325,327,330],{"class":97,"line":312},19,[95,314,315],{"class":213},"        gpdf",[95,317,229],{"class":101},[95,319,320],{"class":200},"WithPageSize",[95,322,235],{"class":101},[95,324,32],{"class":213},[95,326,229],{"class":101},[95,328,329],{"class":213},"A4",[95,331,332],{"class":101},"),\n",[95,334,336,338,340,343,345,348,350,353,355,357,359,362,364,368],{"class":97,"line":335},20,[95,337,315],{"class":213},[95,339,229],{"class":101},[95,341,342],{"class":200},"WithMargins",[95,344,235],{"class":101},[95,346,347],{"class":213},"document",[95,349,229],{"class":101},[95,351,352],{"class":200},"UniformEdges",[95,354,235],{"class":101},[95,356,347],{"class":213},[95,358,229],{"class":101},[95,360,361],{"class":200},"Mm",[95,363,235],{"class":101},[95,365,367],{"class":366},"sbssI","20",[95,369,370],{"class":101},"))),\n",[95,372,374,376,378,381,383,385,388,390,392,395],{"class":97,"line":373},21,[95,375,315],{"class":213},[95,377,229],{"class":101},[95,379,380],{"class":200},"WithFont",[95,382,235],{"class":101},[95,384,238],{"class":101},[95,386,387],{"class":241},"Inter",[95,389,238],{"class":101},[95,391,217],{"class":101},[95,393,394],{"class":213}," regular",[95,396,332],{"class":101},[95,398,400,402,404,407,409,411,413,415,417,420],{"class":97,"line":399},22,[95,401,315],{"class":213},[95,403,229],{"class":101},[95,405,406],{"class":200},"WithDefaultFont",[95,408,235],{"class":101},[95,410,238],{"class":101},[95,412,387],{"class":241},[95,414,238],{"class":101},[95,416,217],{"class":101},[95,418,419],{"class":366}," 11",[95,421,332],{"class":101},[95,423,425],{"class":97,"line":424},23,[95,426,427],{"class":101},"    )\n",[95,429,431],{"class":97,"line":430},24,[95,432,113],{"emptyLinePlaceholder":112},[95,434,436,439,441,444,446,449],{"class":97,"line":435},25,[95,437,438],{"class":213},"    page ",[95,440,223],{"class":101},[95,442,443],{"class":213}," doc",[95,445,229],{"class":101},[95,447,448],{"class":200},"AddPage",[95,450,451],{"class":101},"()\n",[95,453,455,458,460,463,466,470,473,476,478,481,484],{"class":97,"line":454},26,[95,456,457],{"class":213},"    page",[95,459,229],{"class":101},[95,461,462],{"class":200},"AutoRow",[95,464,465],{"class":101},"(func(",[95,467,469],{"class":468},"sHdIc","r",[95,471,472],{"class":101}," *",[95,474,475],{"class":105},"template",[95,477,229],{"class":101},[95,479,480],{"class":105},"RowBuilder",[95,482,483],{"class":101},")",[95,485,207],{"class":101},[95,487,489,492,494,497,499,502,504,507,510,512,514,516,519,521],{"class":97,"line":488},27,[95,490,491],{"class":213},"        r",[95,493,229],{"class":101},[95,495,496],{"class":200},"Col",[95,498,235],{"class":101},[95,500,501],{"class":366},"12",[95,503,217],{"class":101},[95,505,506],{"class":101}," func(",[95,508,509],{"class":468},"c",[95,511,472],{"class":101},[95,513,475],{"class":105},[95,515,229],{"class":101},[95,517,518],{"class":105},"ColBuilder",[95,520,483],{"class":101},[95,522,207],{"class":101},[95,524,526,529,531,534,536,538,541,543,545,548,550,553,555,558],{"class":97,"line":525},28,[95,527,528],{"class":213},"            c",[95,530,229],{"class":101},[95,532,533],{"class":200},"Text",[95,535,235],{"class":101},[95,537,238],{"class":101},[95,539,540],{"class":241},"Quarterly Report",[95,542,238],{"class":101},[95,544,217],{"class":101},[95,546,547],{"class":213}," template",[95,549,229],{"class":101},[95,551,552],{"class":200},"FontSize",[95,554,235],{"class":101},[95,556,557],{"class":366},"28",[95,559,560],{"class":101},"))\n",[95,562,564,566,568,570,572,574,577,579],{"class":97,"line":563},29,[95,565,528],{"class":213},[95,567,229],{"class":101},[95,569,533],{"class":200},[95,571,235],{"class":101},[95,573,238],{"class":101},[95,575,576],{"class":241},"Generated with gpdf and Inter.",[95,578,238],{"class":101},[95,580,186],{"class":101},[95,582,584],{"class":97,"line":583},30,[95,585,586],{"class":101},"        })\n",[95,588,590],{"class":97,"line":589},31,[95,591,592],{"class":101},"    })\n",[95,594,596],{"class":97,"line":595},32,[95,597,113],{"emptyLinePlaceholder":112},[95,599,601,604,606,608,610,612,614,617],{"class":97,"line":600},33,[95,602,603],{"class":213},"    data",[95,605,217],{"class":101},[95,607,220],{"class":213},[95,609,223],{"class":101},[95,611,443],{"class":213},[95,613,229],{"class":101},[95,615,616],{"class":200},"Generate",[95,618,451],{"class":101},[95,620,622,624,626,628,630],{"class":97,"line":621},34,[95,623,252],{"class":119},[95,625,220],{"class":213},[95,627,257],{"class":101},[95,629,260],{"class":101},[95,631,207],{"class":101},[95,633,635,637,639,641,643,645],{"class":97,"line":634},35,[95,636,268],{"class":213},[95,638,229],{"class":101},[95,640,273],{"class":200},[95,642,235],{"class":101},[95,644,278],{"class":213},[95,646,186],{"class":101},[95,648,650],{"class":97,"line":649},36,[95,651,286],{"class":101},[95,653,655,657,659,661,663,665,668,670,672,675,677,679,682,684,687,690,692,694,696],{"class":97,"line":654},37,[95,656,252],{"class":119},[95,658,220],{"class":213},[95,660,223],{"class":101},[95,662,226],{"class":213},[95,664,229],{"class":101},[95,666,667],{"class":200},"WriteFile",[95,669,235],{"class":101},[95,671,238],{"class":101},[95,673,674],{"class":241},"report.pdf",[95,676,238],{"class":101},[95,678,217],{"class":101},[95,680,681],{"class":213}," data",[95,683,217],{"class":101},[95,685,686],{"class":366}," 0o644",[95,688,689],{"class":101},");",[95,691,220],{"class":213},[95,693,257],{"class":101},[95,695,260],{"class":101},[95,697,207],{"class":101},[95,699,701,703,705,707,709,711],{"class":97,"line":700},38,[95,702,268],{"class":213},[95,704,229],{"class":101},[95,706,273],{"class":200},[95,708,235],{"class":101},[95,710,278],{"class":213},[95,712,186],{"class":101},[95,714,716],{"class":97,"line":715},39,[95,717,286],{"class":101},[95,719,721],{"class":97,"line":720},40,[95,722,723],{"class":101},"}\n",[19,725,726,48,728,731,732,737,738,741],{},[22,727,242],{},[22,729,730],{},"main.go"," 옆에 두자 (",[27,733,736],{"href":734,"rel":735},"https://rsms.me/inter/",[31],"rsms.me/inter","에서 다운로드). ",[22,739,740],{},"go run main.go",". 끝.",[14,743,745],{"id":744},"gpdf가-바이트로-하는-일","gpdf가 바이트로 하는 일",[19,747,748,751,752,755,756,755,759,755,762,765,766,769,770,773,774,777],{},[22,749,750],{},"Generate()","가 호출되면 gpdf는 TrueType 테이블 (",[22,753,754],{},"cmap",", ",[22,757,758],{},"glyf",[22,760,761],{},"loca",[22,763,764],{},"hmtx"," …)을 순수 Go로 파싱한다 — FreeType도 CGO도 없다. 렌더링되는 텍스트를 훑어 실제 사용된 코드 포인트를 모으고, ",[69,767,768],{},"그 코드 포인트만 남기도록 글리프 테이블을 서브셋팅"," 한다. PDF에는 ",[22,771,772],{},"Type0"," / ",[22,775,776],{},"CIDFontType2"," 폰트로, 필요한 글리프만 임베드된다.",[19,779,780,781,783],{},"실제 효과: 600 KB짜리 ",[22,782,242],{},"도 문단 두어 개만 썼다면 PDF 내부 서브셋은 12 KB 정도다. 브랜드 폰트를 쓰면서도 파일이 부풀지 않는다.",[14,785,787],{"id":786},"bold와-italic은-각자-파일이-필요하다","Bold와 Italic은 각자 파일이 필요하다",[19,789,790,791,794],{},"여기서 사람들이 걸려 넘어진다. gpdf는 ",[69,792,793],{},"bold나 italic을 합성하지 않는다"," — \"더 굵게 그리는\" 알고리즘이 없다. 스타일 플래그에서 변형 ID를 만들어 lookup 한다:",[796,797,798,818],"table",{},[799,800,801],"thead",{},[802,803,804,810,815],"tr",{},[805,806,807],"th",{},[22,808,809],{},"Bold()",[805,811,812],{},[22,813,814],{},"Italic()",[805,816,817],{},"Lookup 키",[819,820,821,833,845,856],"tbody",{},[802,822,823,827,829],{},[824,825,826],"td",{},"아니오",[824,828,826],{},[824,830,831],{},[22,832,387],{},[802,834,835,838,840],{},[824,836,837],{},"예",[824,839,826],{},[824,841,842],{},[22,843,844],{},"Inter-Bold",[802,846,847,849,851],{},[824,848,826],{},[824,850,837],{},[824,852,853],{},[22,854,855],{},"Inter-Italic",[802,857,858,860,862],{},[824,859,837],{},[824,861,837],{},[824,863,864],{},[22,865,866],{},"Inter-BoldItalic",[19,868,869,871,872,874],{},[22,870,844],{},"를 등록하지 않았다면 lookup은 조용히 일반 ",[22,873,387],{},"로 폴백한다. PDF는 출력되지만 모두 일반 굵기. 경고는 없다.",[19,876,877],{},"네 변형 모두 등록:",[86,879,881],{"className":88,"code":880,"language":90,"meta":91,"style":91},"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",[22,882,883,911,940,969,998,1002,1017,1040,1063,1086,1109,1131],{"__ignoreMap":91},[95,884,885,888,890,893,895,897,899,901,903,905,907,909],{"class":97,"line":98},[95,886,887],{"class":213},"regular",[95,889,217],{"class":101},[95,891,892],{"class":213}," _    ",[95,894,223],{"class":101},[95,896,226],{"class":213},[95,898,229],{"class":101},[95,900,232],{"class":200},[95,902,235],{"class":101},[95,904,238],{"class":101},[95,906,242],{"class":241},[95,908,238],{"class":101},[95,910,186],{"class":101},[95,912,913,916,918,921,923,925,927,929,931,933,936,938],{"class":97,"line":109},[95,914,915],{"class":213},"bold",[95,917,217],{"class":101},[95,919,920],{"class":213}," _       ",[95,922,223],{"class":101},[95,924,226],{"class":213},[95,926,229],{"class":101},[95,928,232],{"class":200},[95,930,235],{"class":101},[95,932,238],{"class":101},[95,934,935],{"class":241},"Inter-Bold.ttf",[95,937,238],{"class":101},[95,939,186],{"class":101},[95,941,942,945,947,950,952,954,956,958,960,962,965,967],{"class":97,"line":116},[95,943,944],{"class":213},"italic",[95,946,217],{"class":101},[95,948,949],{"class":213}," _     ",[95,951,223],{"class":101},[95,953,226],{"class":213},[95,955,229],{"class":101},[95,957,232],{"class":200},[95,959,235],{"class":101},[95,961,238],{"class":101},[95,963,964],{"class":241},"Inter-Italic.ttf",[95,966,238],{"class":101},[95,968,186],{"class":101},[95,970,971,974,976,979,981,983,985,987,989,991,994,996],{"class":97,"line":126},[95,972,973],{"class":213},"boldItalic",[95,975,217],{"class":101},[95,977,978],{"class":213}," _ ",[95,980,223],{"class":101},[95,982,226],{"class":213},[95,984,229],{"class":101},[95,986,232],{"class":200},[95,988,235],{"class":101},[95,990,238],{"class":101},[95,992,993],{"class":241},"Inter-BoldItalic.ttf",[95,995,238],{"class":101},[95,997,186],{"class":101},[95,999,1000],{"class":97,"line":138},[95,1001,113],{"emptyLinePlaceholder":112},[95,1003,1004,1007,1009,1011,1013,1015],{"class":97,"line":148},[95,1005,1006],{"class":213},"doc ",[95,1008,223],{"class":101},[95,1010,302],{"class":213},[95,1012,229],{"class":101},[95,1014,51],{"class":200},[95,1016,309],{"class":101},[95,1018,1019,1022,1024,1026,1028,1030,1032,1034,1036,1038],{"class":97,"line":153},[95,1020,1021],{"class":213},"    gpdf",[95,1023,229],{"class":101},[95,1025,380],{"class":200},[95,1027,235],{"class":101},[95,1029,238],{"class":101},[95,1031,387],{"class":241},[95,1033,238],{"class":101},[95,1035,217],{"class":101},[95,1037,394],{"class":213},[95,1039,332],{"class":101},[95,1041,1042,1044,1046,1048,1050,1052,1054,1056,1058,1061],{"class":97,"line":163},[95,1043,1021],{"class":213},[95,1045,229],{"class":101},[95,1047,380],{"class":200},[95,1049,235],{"class":101},[95,1051,238],{"class":101},[95,1053,844],{"class":241},[95,1055,238],{"class":101},[95,1057,217],{"class":101},[95,1059,1060],{"class":213}," bold",[95,1062,332],{"class":101},[95,1064,1065,1067,1069,1071,1073,1075,1077,1079,1081,1084],{"class":97,"line":173},[95,1066,1021],{"class":213},[95,1068,229],{"class":101},[95,1070,380],{"class":200},[95,1072,235],{"class":101},[95,1074,238],{"class":101},[95,1076,855],{"class":241},[95,1078,238],{"class":101},[95,1080,217],{"class":101},[95,1082,1083],{"class":213}," italic",[95,1085,332],{"class":101},[95,1087,1088,1090,1092,1094,1096,1098,1100,1102,1104,1107],{"class":97,"line":183},[95,1089,1021],{"class":213},[95,1091,229],{"class":101},[95,1093,380],{"class":200},[95,1095,235],{"class":101},[95,1097,238],{"class":101},[95,1099,866],{"class":241},[95,1101,238],{"class":101},[95,1103,217],{"class":101},[95,1105,1106],{"class":213}," boldItalic",[95,1108,332],{"class":101},[95,1110,1111,1113,1115,1117,1119,1121,1123,1125,1127,1129],{"class":97,"line":189},[95,1112,1021],{"class":213},[95,1114,229],{"class":101},[95,1116,406],{"class":200},[95,1118,235],{"class":101},[95,1120,238],{"class":101},[95,1122,387],{"class":241},[95,1124,238],{"class":101},[95,1126,217],{"class":101},[95,1128,419],{"class":366},[95,1130,332],{"class":101},[95,1132,1133],{"class":97,"line":194},[95,1134,186],{"class":101},[19,1136,1137,1138,1141,1142,1145],{},"폰트가 한 가지 굵기만 제공한다면 (아이콘 폰트나 디스플레이 폰트는 흔하다), 그 폰트에는 ",[22,1139,1140],{},"template.Bold()","나 ",[22,1143,1144],{},"template.Italic()","을 아예 쓰지 말자. 변형 자체가 없는 건 괜찮다. 잘못된 변형으로 폴백하는 게 \"굵은 글씨가 안 굵다\" 이슈의 원인이다.",[14,1147,1149],{"id":1148},"폰트를-바이너리에-임베드","폰트를 바이너리에 임베드",[19,1151,1152,1155],{},[22,1153,1154],{},"os.ReadFile","을 시작 시점에 호출하는 건 개발에선 괜찮다. 운영에서 폰트는 프로그램의 일부 — 바이너리에 들어가야 한다:",[86,1157,1159],{"className":88,"code":1158,"language":90,"meta":91,"style":91},"import _ \"embed\"\n\n//go:embed fonts/Inter-Regular.ttf\nvar interRegular []byte\n\ndoc := gpdf.NewDocument(\n    gpdf.WithFont(\"Inter\", interRegular),\n)\n",[22,1160,1161,1174,1178,1184,1199,1203,1217,1240],{"__ignoreMap":91},[95,1162,1163,1165,1167,1169,1172],{"class":97,"line":98},[95,1164,120],{"class":119},[95,1166,978],{"class":213},[95,1168,238],{"class":101},[95,1170,1171],{"class":105},"embed",[95,1173,135],{"class":101},[95,1175,1176],{"class":97,"line":109},[95,1177,113],{"emptyLinePlaceholder":112},[95,1179,1180],{"class":97,"line":116},[95,1181,1183],{"class":1182},"sHwdD","//go:embed fonts/Inter-Regular.ttf\n",[95,1185,1186,1189,1192,1195],{"class":97,"line":126},[95,1187,1188],{"class":101},"var",[95,1190,1191],{"class":213}," interRegular ",[95,1193,1194],{"class":101},"[]",[95,1196,1198],{"class":1197},"spNyl","byte\n",[95,1200,1201],{"class":97,"line":138},[95,1202,113],{"emptyLinePlaceholder":112},[95,1204,1205,1207,1209,1211,1213,1215],{"class":97,"line":148},[95,1206,1006],{"class":213},[95,1208,223],{"class":101},[95,1210,302],{"class":213},[95,1212,229],{"class":101},[95,1214,51],{"class":200},[95,1216,309],{"class":101},[95,1218,1219,1221,1223,1225,1227,1229,1231,1233,1235,1238],{"class":97,"line":153},[95,1220,1021],{"class":213},[95,1222,229],{"class":101},[95,1224,380],{"class":200},[95,1226,235],{"class":101},[95,1228,238],{"class":101},[95,1230,387],{"class":241},[95,1232,238],{"class":101},[95,1234,217],{"class":101},[95,1236,1237],{"class":213}," interRegular",[95,1239,332],{"class":101},[95,1241,1242],{"class":97,"line":163},[95,1243,186],{"class":101},[19,1245,1246,1249],{},[22,1247,1248],{},"go build","가 바이트를 바이너리에 굽는다. \"배포 이미지의 .ttf가 어디로 갔지\"를 금요일 저녁에 추적할 일이 없어진다.",[14,1251,1253],{"id":1252},"아이콘-폰트도-동일","아이콘 폰트도 동일",[19,1255,1256],{},"Font Awesome, TTF로 내보낸 Material Symbols, IcoMoon, 자체 브랜드 글리프 셋 — 전부 TrueType 파일이다. 같은 방식으로 등록한다:",[86,1258,1260],{"className":88,"code":1259,"language":90,"meta":91,"style":91},"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",[22,1261,1262,1290,1304,1328,1354,1358,1362,1367],{"__ignoreMap":91},[95,1263,1264,1267,1269,1271,1273,1275,1277,1279,1281,1283,1286,1288],{"class":97,"line":98},[95,1265,1266],{"class":213},"icons",[95,1268,217],{"class":101},[95,1270,978],{"class":213},[95,1272,223],{"class":101},[95,1274,226],{"class":213},[95,1276,229],{"class":101},[95,1278,232],{"class":200},[95,1280,235],{"class":101},[95,1282,238],{"class":101},[95,1284,1285],{"class":241},"MaterialSymbols-Regular.ttf",[95,1287,238],{"class":101},[95,1289,186],{"class":101},[95,1291,1292,1294,1296,1298,1300,1302],{"class":97,"line":109},[95,1293,1006],{"class":213},[95,1295,223],{"class":101},[95,1297,302],{"class":213},[95,1299,229],{"class":101},[95,1301,51],{"class":200},[95,1303,309],{"class":101},[95,1305,1306,1308,1310,1312,1314,1316,1319,1321,1323,1326],{"class":97,"line":116},[95,1307,1021],{"class":213},[95,1309,229],{"class":101},[95,1311,380],{"class":200},[95,1313,235],{"class":101},[95,1315,238],{"class":101},[95,1317,1318],{"class":241},"Icons",[95,1320,238],{"class":101},[95,1322,217],{"class":101},[95,1324,1325],{"class":213}," icons",[95,1327,332],{"class":101},[95,1329,1330,1332,1334,1336,1338,1340,1342,1344,1346,1348,1351],{"class":97,"line":126},[95,1331,1021],{"class":213},[95,1333,229],{"class":101},[95,1335,406],{"class":200},[95,1337,235],{"class":101},[95,1339,238],{"class":101},[95,1341,387],{"class":241},[95,1343,238],{"class":101},[95,1345,217],{"class":101},[95,1347,419],{"class":366},[95,1349,1350],{"class":101},"),",[95,1352,1353],{"class":1182}," // 본문 기본\n",[95,1355,1356],{"class":97,"line":138},[95,1357,186],{"class":101},[95,1359,1360],{"class":97,"line":148},[95,1361,113],{"emptyLinePlaceholder":112},[95,1363,1364],{"class":97,"line":153},[95,1365,1366],{"class":1182},"// 컬럼 안에서:\n",[95,1368,1369,1371,1373,1375,1377,1380,1382,1384,1386,1388,1390,1392,1394,1396,1398,1400,1402,1404,1406,1408,1411],{"class":97,"line":163},[95,1370,509],{"class":213},[95,1372,229],{"class":101},[95,1374,533],{"class":200},[95,1376,235],{"class":101},[95,1378,1379],{"class":101},"\"\"",[95,1381,217],{"class":101},[95,1383,547],{"class":213},[95,1385,229],{"class":101},[95,1387,79],{"class":200},[95,1389,235],{"class":101},[95,1391,238],{"class":101},[95,1393,1318],{"class":241},[95,1395,238],{"class":101},[95,1397,1350],{"class":101},[95,1399,547],{"class":213},[95,1401,229],{"class":101},[95,1403,552],{"class":200},[95,1405,235],{"class":101},[95,1407,367],{"class":366},[95,1409,1410],{"class":101},"))",[95,1412,1413],{"class":1182}," // \"home\" 아이콘\n",[19,1415,1416],{},"Unicode 이스케이프는 폰트 문서가 명시한 값. gpdf는 그 글리프가 아이콘인지 신경 쓰지 않는다 — 그냥 코드 포인트로 보고, 글자와 똑같이 서브셋팅 한다.",[14,1418,1420],{"id":1419},"자주-하는-실수","자주 하는 실수",[1422,1423,1424,1435,1448],"ul",{},[1425,1426,1427,1430,1431,1434],"li",{},[69,1428,1429],{},"호출 측 패밀리 이름 오타."," ",[22,1432,1433],{},"template.FontFamily(\"Intr\")","는 조용히 기본 폰트로 폴백한다. 에러도 경고도 없다. 갑자기 Helvetica처럼 보이면 가장 먼저 의심한다.",[1425,1436,1437,1444,1445,1447],{},[69,1438,1439,1440,1443],{},"컨테이너에서 ",[22,1441,1442],{},"//go:embed","를 안 씀."," 다듬어진 Docker 컨텍스트가 ",[22,1446,24],{},"를 떨어뜨리고, 런타임 폴백이 작동하고, 고객 메일에서 알게 된다. 임베드.",[1425,1449,1450,1453,1454,1456,1457,1460],{},[69,1451,1452],{},"폰트의 PostScript 이름을 패밀리로 사용."," \"Inter-Regular\"는 PostScript 이름. 이걸 ",[22,1455,380],{},"에 넘기면 bold lookup이 \"Inter-Regular-Bold\"를 찾고 — 존재하지 않는다. 깔끔한 루트 패밀리 (",[22,1458,1459],{},"\"Inter\"",")를 쓰고 변형 접미사가 스타일을 처리하게 둔다.",[14,1462,1464],{"id":1463},"관련-레시피","관련 레시피",[1422,1466,1467,1474,1481],{},[1425,1468,1469,1473],{},[27,1470,1472],{"href":1471},"/ko/blog/embed-japanese-font","gpdf에 일본어 폰트를 임베드하려면?"," — 같은 메커니즘, CJK 관련 메모 포함",[1425,1475,1476,1480],{},[27,1477,1479],{"href":1478},"/ko/blog/bold-italic-together","Bold와 Italic을 함께 쓰려면?"," — 변형 리졸버 상세",[1425,1482,1483,1487],{},[27,1484,1486],{"href":1485},"/ko/blog/tofu-boxes-japanese","내 PDF에 두부 박스가 나오는 이유는?"," — 폰트가 코드 포인트를 커버하지 않을 때 일어나는 일",[14,1489,1491],{"id":1490},"gpdf-써보기","gpdf 써보기",[19,1493,1494],{},"gpdf는 Go용 PDF 생성 라이브러리다. MIT, 외부 의존 없음, TrueType 처리는 순수 Go.",[86,1496,1500],{"className":1497,"code":1498,"language":1499,"meta":91,"style":91},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go get github.com/gpdf-dev/gpdf\n","bash",[22,1501,1502],{"__ignoreMap":91},[95,1503,1504,1506,1509],{"class":97,"line":98},[95,1505,90],{"class":105},[95,1507,1508],{"class":241}," get",[95,1510,1511],{"class":241}," github.com/gpdf-dev/gpdf\n",[19,1513,1514,1518,1519],{},[27,1515,1517],{"href":29,"rel":1516},[31],"⭐ GitHub에서 Star"," · ",[27,1520,1523],{"href":1521,"rel":1522},"https://gpdf.dev/ko/docs/quickstart",[31],"문서 읽기",[1525,1526,1527],"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":91,"searchDepth":109,"depth":109,"links":1529},[1530,1531,1532,1533,1534,1535,1536,1537,1538,1539],{"id":16,"depth":109,"text":17},{"id":40,"depth":109,"text":41},{"id":83,"depth":109,"text":84},{"id":744,"depth":109,"text":745},{"id":786,"depth":109,"text":787},{"id":1148,"depth":109,"text":1149},{"id":1252,"depth":109,"text":1253},{"id":1419,"depth":109,"text":1420},{"id":1463,"depth":109,"text":1464},{"id":1490,"depth":109,"text":1491},"2026-04-30","TTF 바이트를 읽어 gpdf.WithFont로 패밀리를 등록하고 이름으로 참조한다. Inter, Roboto, 아이콘 폰트까지 어떤 TrueType이든 같은 방식으로 작동한다.",false,"md",{"name":1545,"totalTime":1546,"tools":1547,"steps":1550},"gpdf 문서에 커스텀 TrueType 폰트 등록하기","PT10M",[1548,1549],"Go 1.22+","임의의 TrueType .ttf 파일 (Inter, Roboto, JetBrains Mono, 아이콘 폰트 등)",[1551,1554,1557,1560],{"name":1552,"text":1553},"TTF 바이트 로드","os.ReadFile로 .ttf를 []byte로 읽는다. 배포 빌드에서는 //go:embed로 폰트를 바이너리에 포함하는 것이 좋다.",{"name":1555,"text":1556},"문서 생성 시 패밀리 등록","gpdf.NewDocument에 gpdf.WithFont(\"Inter\", fontBytes)를 전달한다. 패밀리 이름은 임의 문자열로, 폰트 내부 name 테이블과 무관한 단순 lookup 키다.",{"name":1558,"text":1559},"기본 폰트로 지정하거나 호출마다 전달","gpdf.WithDefaultFont(\"Inter\", 11)을 추가하면 모든 c.Text가 적용된다. 그렇지 않으면 template.FontFamily(\"Inter\")를 호출마다 전달한다.",{"name":1561,"text":1562},"Bold, Italic, BoldItalic은 각각 별도 등록","각 스타일은 독립된 TTF다. \"Inter-Bold\", \"Inter-Italic\", \"Inter-BoldItalic\"으로 등록한다. gpdf는 스타일 플래그로 변형 ID를 만들어 정확히 그 이름을 찾는다.",null,{},"/ko/blog/add-custom-truetype-font",{"title":5,"description":1541},"ko/blog/018.add-custom-truetype-font",[1569,1570],"recipe","tutorial","6zi74wS7Oi5bz1UW-BIlQckO7QG1rLma0V973npt4CQ",1779199026797]