[{"data":1,"prerenderedAt":19123},["ShallowReactive",2],{"blog-tag-zh-troubleshooting":3},[4,1327,2945,4034,5270,6630,8368,10064,11667,12987,14349],{"id":5,"title":6,"author":7,"body":10,"date":1291,"description":1292,"draft":1293,"extension":1294,"howTo":1295,"image":1317,"meta":1318,"navigation":85,"path":1319,"seo":1320,"stem":1321,"tags":1322,"updated":1317,"__hash__":1326},"blogZh/zh/blog/010.source-han-sans-jp-with-gpdf.md","如何在 gpdf 中使用思源黑体 JP（Source Han Sans JP）？",{"name":8,"url":9},"gpdf team","https://gpdf.dev",{"type":11,"value":12,"toc":1279},"minimark",[13,17,34,37,55,58,707,721,725,736,739,824,835,839,874,881,905,915,918,921,929,932,1087,1100,1103,1107,1110,1151,1165,1169,1172,1186,1189,1204,1207,1210,1239,1243,1246,1263,1275],[14,15,16],"h2",{"id":16},"这个问题的另一种表达",[18,19,20,21,28,29,33],"p",{},"你想在 ",[22,23,27],"a",{"href":24,"rel":25},"https://github.com/gpdf-dev/gpdf",[26],"nofollow","gpdf"," 文档里用 ",[30,31,32],"strong",{},"Source Han Sans JP（思源黑体 JP）"," —— 2014 年 Adobe 与 Google 合作发布的泛 CJK 无衬线字体的 Adobe 品牌。也许你团队把字体锚定在 GitHub 的 release tag 上以保证可复现，也许你接手的设计系统多年前就标准化在思源黑体上，也许你就是喜欢 Adobe 的发布节奏。理由随意。下载之前值得先搞清楚三件事：该拿哪个文件、和 Noto Sans JP 究竟什么关系、gpdf 能读哪种格式。",[14,35,36],{"id":36},"速答",[18,38,39,40,45,46,50,51,54],{},"从 ",[22,41,44],{"href":42,"rel":43},"https://github.com/adobe-fonts/source-han-sans/releases",[26],"adobe-fonts/source-han-sans"," 发布页下载 ",[47,48,49],"code",{},"SourceHanSansJP-Regular.ttf","（TTF 包，不是 OTF），用 ",[47,52,53],{},"gpdf.WithFont(\"SourceHanSansJP\", bytes)"," 注册并设为默认。Source Han Sans JP 与 Noto Sans JP 共用同一套字形；如果你对 Adobe 的发布流程没什么特别要求,Noto Sans JP 的获取路径更直接。",[14,56,57],{"id":57},"完整示例",[59,60,65],"pre",{"className":61,"code":62,"language":63,"meta":64,"style":64},"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","",[47,66,67,80,87,97,109,119,124,134,144,154,160,165,181,219,235,253,259,264,283,306,344,370,395,401,406,425,459,496,544,564,570,576,581,602,615,630,635,681,696,701],{"__ignoreMap":64},[68,69,72,76],"span",{"class":70,"line":71},"line",1,[68,73,75],{"class":74},"sMK4o","package",[68,77,79],{"class":78},"sBMFI"," main\n",[68,81,83],{"class":70,"line":82},2,[68,84,86],{"emptyLinePlaceholder":85},true,"\n",[68,88,90,94],{"class":70,"line":89},3,[68,91,93],{"class":92},"s7zQu","import",[68,95,96],{"class":74}," (\n",[68,98,100,103,106],{"class":70,"line":99},4,[68,101,102],{"class":74},"    \"",[68,104,105],{"class":78},"log",[68,107,108],{"class":74},"\"\n",[68,110,112,114,117],{"class":70,"line":111},5,[68,113,102],{"class":74},[68,115,116],{"class":78},"os",[68,118,108],{"class":74},[68,120,122],{"class":70,"line":121},6,[68,123,86],{"emptyLinePlaceholder":85},[68,125,127,129,132],{"class":70,"line":126},7,[68,128,102],{"class":74},[68,130,131],{"class":78},"github.com/gpdf-dev/gpdf",[68,133,108],{"class":74},[68,135,137,139,142],{"class":70,"line":136},8,[68,138,102],{"class":74},[68,140,141],{"class":78},"github.com/gpdf-dev/gpdf/document",[68,143,108],{"class":74},[68,145,147,149,152],{"class":70,"line":146},9,[68,148,102],{"class":74},[68,150,151],{"class":78},"github.com/gpdf-dev/gpdf/template",[68,153,108],{"class":74},[68,155,157],{"class":70,"line":156},10,[68,158,159],{"class":74},")\n",[68,161,163],{"class":70,"line":162},11,[68,164,86],{"emptyLinePlaceholder":85},[68,166,168,171,175,178],{"class":70,"line":167},12,[68,169,170],{"class":74},"func",[68,172,174],{"class":173},"s2Zo4"," main",[68,176,177],{"class":74},"()",[68,179,180],{"class":74}," {\n",[68,182,184,188,191,194,197,200,203,206,209,212,215,217],{"class":70,"line":183},13,[68,185,187],{"class":186},"sTEyZ","    font",[68,189,190],{"class":74},",",[68,192,193],{"class":186}," err ",[68,195,196],{"class":74},":=",[68,198,199],{"class":186}," os",[68,201,202],{"class":74},".",[68,204,205],{"class":173},"ReadFile",[68,207,208],{"class":74},"(",[68,210,211],{"class":74},"\"",[68,213,49],{"class":214},"sfazB",[68,216,211],{"class":74},[68,218,159],{"class":74},[68,220,222,225,227,230,233],{"class":70,"line":221},14,[68,223,224],{"class":92},"    if",[68,226,193],{"class":186},[68,228,229],{"class":74},"!=",[68,231,232],{"class":74}," nil",[68,234,180],{"class":74},[68,236,238,241,243,246,248,251],{"class":70,"line":237},15,[68,239,240],{"class":186},"        log",[68,242,202],{"class":74},[68,244,245],{"class":173},"Fatal",[68,247,208],{"class":74},[68,249,250],{"class":186},"err",[68,252,159],{"class":74},[68,254,256],{"class":70,"line":255},16,[68,257,258],{"class":74},"    }\n",[68,260,262],{"class":70,"line":261},17,[68,263,86],{"emptyLinePlaceholder":85},[68,265,267,270,272,275,277,280],{"class":70,"line":266},18,[68,268,269],{"class":186},"    doc ",[68,271,196],{"class":74},[68,273,274],{"class":186}," gpdf",[68,276,202],{"class":74},[68,278,279],{"class":173},"NewDocument",[68,281,282],{"class":74},"(\n",[68,284,286,289,291,294,296,298,300,303],{"class":70,"line":285},19,[68,287,288],{"class":186},"        gpdf",[68,290,202],{"class":74},[68,292,293],{"class":173},"WithPageSize",[68,295,208],{"class":74},[68,297,27],{"class":186},[68,299,202],{"class":74},[68,301,302],{"class":186},"A4",[68,304,305],{"class":74},"),\n",[68,307,309,311,313,316,318,321,323,326,328,330,332,335,337,341],{"class":70,"line":308},20,[68,310,288],{"class":186},[68,312,202],{"class":74},[68,314,315],{"class":173},"WithMargins",[68,317,208],{"class":74},[68,319,320],{"class":186},"document",[68,322,202],{"class":74},[68,324,325],{"class":173},"UniformEdges",[68,327,208],{"class":74},[68,329,320],{"class":186},[68,331,202],{"class":74},[68,333,334],{"class":173},"Mm",[68,336,208],{"class":74},[68,338,340],{"class":339},"sbssI","20",[68,342,343],{"class":74},"))),\n",[68,345,347,349,351,354,356,358,361,363,365,368],{"class":70,"line":346},21,[68,348,288],{"class":186},[68,350,202],{"class":74},[68,352,353],{"class":173},"WithFont",[68,355,208],{"class":74},[68,357,211],{"class":74},[68,359,360],{"class":214},"SourceHanSansJP",[68,362,211],{"class":74},[68,364,190],{"class":74},[68,366,367],{"class":186}," font",[68,369,305],{"class":74},[68,371,373,375,377,380,382,384,386,388,390,393],{"class":70,"line":372},22,[68,374,288],{"class":186},[68,376,202],{"class":74},[68,378,379],{"class":173},"WithDefaultFont",[68,381,208],{"class":74},[68,383,211],{"class":74},[68,385,360],{"class":214},[68,387,211],{"class":74},[68,389,190],{"class":74},[68,391,392],{"class":339}," 11",[68,394,305],{"class":74},[68,396,398],{"class":70,"line":397},23,[68,399,400],{"class":74},"    )\n",[68,402,404],{"class":70,"line":403},24,[68,405,86],{"emptyLinePlaceholder":85},[68,407,409,412,414,417,419,422],{"class":70,"line":408},25,[68,410,411],{"class":186},"    page ",[68,413,196],{"class":74},[68,415,416],{"class":186}," doc",[68,418,202],{"class":74},[68,420,421],{"class":173},"AddPage",[68,423,424],{"class":74},"()\n",[68,426,428,431,433,436,439,443,446,449,451,454,457],{"class":70,"line":427},26,[68,429,430],{"class":186},"    page",[68,432,202],{"class":74},[68,434,435],{"class":173},"AutoRow",[68,437,438],{"class":74},"(func(",[68,440,442],{"class":441},"sHdIc","r",[68,444,445],{"class":74}," *",[68,447,448],{"class":78},"template",[68,450,202],{"class":74},[68,452,453],{"class":78},"RowBuilder",[68,455,456],{"class":74},")",[68,458,180],{"class":74},[68,460,462,465,467,470,472,475,477,480,483,485,487,489,492,494],{"class":70,"line":461},27,[68,463,464],{"class":186},"        r",[68,466,202],{"class":74},[68,468,469],{"class":173},"Col",[68,471,208],{"class":74},[68,473,474],{"class":339},"12",[68,476,190],{"class":74},[68,478,479],{"class":74}," func(",[68,481,482],{"class":441},"c",[68,484,445],{"class":74},[68,486,448],{"class":78},[68,488,202],{"class":74},[68,490,491],{"class":78},"ColBuilder",[68,493,456],{"class":74},[68,495,180],{"class":74},[68,497,499,502,504,507,509,511,514,516,518,521,523,526,528,531,534,536,538,541],{"class":70,"line":498},28,[68,500,501],{"class":186},"            c",[68,503,202],{"class":74},[68,505,506],{"class":173},"Text",[68,508,208],{"class":74},[68,510,211],{"class":74},[68,512,513],{"class":214},"報告書",[68,515,211],{"class":74},[68,517,190],{"class":74},[68,519,520],{"class":186}," template",[68,522,202],{"class":74},[68,524,525],{"class":173},"FontSize",[68,527,208],{"class":74},[68,529,530],{"class":339},"24",[68,532,533],{"class":74},"),",[68,535,520],{"class":186},[68,537,202],{"class":74},[68,539,540],{"class":173},"Bold",[68,542,543],{"class":74},"())\n",[68,545,547,549,551,553,555,557,560,562],{"class":70,"line":546},29,[68,548,501],{"class":186},[68,550,202],{"class":74},[68,552,506],{"class":173},[68,554,208],{"class":74},[68,556,211],{"class":74},[68,558,559],{"class":214},"Source Han Sans JP — Adobe 发布的免费 CJK 字体。",[68,561,211],{"class":74},[68,563,159],{"class":74},[68,565,567],{"class":70,"line":566},30,[68,568,569],{"class":74},"        })\n",[68,571,573],{"class":70,"line":572},31,[68,574,575],{"class":74},"    })\n",[68,577,579],{"class":70,"line":578},32,[68,580,86],{"emptyLinePlaceholder":85},[68,582,584,587,589,591,593,595,597,600],{"class":70,"line":583},33,[68,585,586],{"class":186},"    data",[68,588,190],{"class":74},[68,590,193],{"class":186},[68,592,196],{"class":74},[68,594,416],{"class":186},[68,596,202],{"class":74},[68,598,599],{"class":173},"Generate",[68,601,424],{"class":74},[68,603,605,607,609,611,613],{"class":70,"line":604},34,[68,606,224],{"class":92},[68,608,193],{"class":186},[68,610,229],{"class":74},[68,612,232],{"class":74},[68,614,180],{"class":74},[68,616,618,620,622,624,626,628],{"class":70,"line":617},35,[68,619,240],{"class":186},[68,621,202],{"class":74},[68,623,245],{"class":173},[68,625,208],{"class":74},[68,627,250],{"class":186},[68,629,159],{"class":74},[68,631,633],{"class":70,"line":632},36,[68,634,258],{"class":74},[68,636,638,640,642,644,646,648,651,653,655,658,660,662,665,667,670,673,675,677,679],{"class":70,"line":637},37,[68,639,224],{"class":92},[68,641,193],{"class":186},[68,643,196],{"class":74},[68,645,199],{"class":186},[68,647,202],{"class":74},[68,649,650],{"class":173},"WriteFile",[68,652,208],{"class":74},[68,654,211],{"class":74},[68,656,657],{"class":214},"report.pdf",[68,659,211],{"class":74},[68,661,190],{"class":74},[68,663,664],{"class":186}," data",[68,666,190],{"class":74},[68,668,669],{"class":339}," 0o644",[68,671,672],{"class":74},");",[68,674,193],{"class":186},[68,676,229],{"class":74},[68,678,232],{"class":74},[68,680,180],{"class":74},[68,682,684,686,688,690,692,694],{"class":70,"line":683},38,[68,685,240],{"class":186},[68,687,202],{"class":74},[68,689,245],{"class":173},[68,691,208],{"class":74},[68,693,250],{"class":186},[68,695,159],{"class":74},[68,697,699],{"class":70,"line":698},39,[68,700,258],{"class":74},[68,702,704],{"class":70,"line":703},40,[68,705,706],{"class":74},"}\n",[18,708,709,710,713,714,717,718,720],{},"把 TTF 放到 ",[47,711,712],{},"main.go"," 旁边，",[47,715,716],{},"go run main.go","，一页带日文的 PDF 就会出现在 ",[47,719,657],{},"。",[14,722,724],{"id":723},"source-han-sans-jp-就是-noto-sans-cjk-jp","Source Han Sans JP 就是 Noto Sans CJK JP",[18,726,727,728,731,732,735],{},"最能帮你省几个小时查资料的事实只有一条：",[30,729,730],{},"Source Han Sans 和 Noto Sans CJK 是同一套字体","。字形设计、度量表、字符集覆盖都由 Adobe 完成，Google 以 Noto 品牌进行并行分发。两者都在 2014-07-15 同日发布。轮廓数据、",[47,733,734],{},"hmtx"," 表、JIS X 0213 / Adobe-Japan1-6 的覆盖面 —— 比特级完全一致。Adobe 升版后，相应的字形变化会在数周内传到 Noto。",[18,737,738],{},"差异都在品牌和打包层面：",[740,741,742,757],"table",{},[743,744,745],"thead",{},[746,747,748,751,754],"tr",{},[749,750],"th",{},[749,752,753],{},"Source Han Sans JP",[749,755,756],{},"Noto Sans JP",[758,759,760,772,791,802,813],"tbody",{},[746,761,762,766,769],{},[763,764,765],"td",{},"发行方",[763,767,768],{},"Adobe",[763,770,771],{},"Google",[746,773,774,777,783],{},[763,775,776],{},"权威源",[763,778,779],{},[22,780,44],{"href":781,"rel":782},"https://github.com/adobe-fonts/source-han-sans",[26],[763,784,785,790],{},[22,786,789],{"href":787,"rel":788},"https://notofonts.github.io",[26],"notofonts.github.io"," + Google Fonts",[746,792,793,796,799],{},[763,794,795],{},"主格式",[763,797,798],{},"OTF（CFF 轮廓）",[763,800,801],{},"TTF（static）+ variable",[746,803,804,807,810],{},[763,805,806],{},"发布模型",[763,808,809],{},"GitHub release tag 手动版本化",[763,811,812],{},"Google Fonts CDN + git 仓库",[746,814,815,818,821],{},[763,816,817],{},"语言打包",[763,819,820],{},"分语言 TTF + 泛 CJK OTC",[763,822,823],{},"仅 JP",[18,825,826,827,830,831,834],{},"如果你的团队要把字体锁在 Adobe 的 GitHub tag 上、已经在内部镜像 ",[47,828,829],{},"github.com/adobe-fonts","、或者别的流水线也要用泛 CJK OTC，那就用 Source Han Sans JP。否则直接用能拿到 TTF 的 ",[22,832,756],{"href":833},"/zh/blog/noto-sans-jp-with-gpdf"," 更省事。",[14,836,838],{"id":837},"为什么非-ttf-不可","为什么非 TTF 不可",[18,840,841,842,845,846,849,850,853,854,853,857,853,860,862,863,866,867,870,871,873],{},"Adobe 对 Source Han Sans 的默认格式是 ",[47,843,844],{},".otf","，准确说是基于 CFF 的 OpenType。而 gpdf 的字体解析器只有一个文件 ",[47,847,848],{},"pdf/font/truetype.go","，处理 ",[47,851,852],{},"glyf","、",[47,855,856],{},"loca",[47,858,859],{},"cmap",[47,861,734],{}," 和复合字形，不读 ",[47,864,865],{},"CFF "," / ",[47,868,869],{},"CFF2"," 轮廓。把 CFF 版 ",[47,872,844],{}," 丢给它，会在文档构造阶段就抛出解析错误，还没到渲染就挂了。",[18,875,876,877,880],{},"Adobe 的发布页同时提供 OTF 和 TTF，",[30,878,879],{},"要拿 TTF 包","。如果某个点发行版恰好没出 TTF（偶尔发生），两条干净的备选：",[882,883,884,891],"ol",{},[885,886,887,890],"li",{},[30,888,889],{},"换到 Noto Sans JP。"," Google Fonts 直接提供静态 TTF，字形数据完全一致，免去转换。",[885,892,893,896,897,900,901,904],{},[30,894,895],{},"一次性转换，提交结果。"," 用 ",[47,898,899],{},"fonttools"," 的 ",[47,902,903],{},"otf2ttf"," 转一次就好，把产物提交到仓库或内部制品服务器，转换过程永远不进构建流水线。",[18,906,907,908,910,911,914],{},"别在构建时做转换。字体转换工具跨版本行为会变，",[47,909,734],{}," 稍有差别就会让换行位置在一次 ",[47,912,913],{},"pip install -U"," 之后偷偷挪位。",[14,916,917],{"id":917},"七个字重",[18,919,920],{},"Source Han Sans JP 以单独 TTF 形式发布 ExtraLight 到 Heavy 共 7 个字重：",[59,922,927],{"className":923,"code":925,"language":926},[924],"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",[47,928,925],{"__ignoreMap":64},[18,930,931],{},"大多数业务文档 Regular + Bold 两个就够：",[59,933,935],{"className":61,"code":934,"language":63,"meta":64,"style":64},"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",[47,936,937,965,994,998,1013,1037,1061,1083],{"__ignoreMap":64},[68,938,939,942,944,947,949,951,953,955,957,959,961,963],{"class":70,"line":71},[68,940,941],{"class":186},"reg",[68,943,190],{"class":74},[68,945,946],{"class":186},"  _ ",[68,948,196],{"class":74},[68,950,199],{"class":186},[68,952,202],{"class":74},[68,954,205],{"class":173},[68,956,208],{"class":74},[68,958,211],{"class":74},[68,960,49],{"class":214},[68,962,211],{"class":74},[68,964,159],{"class":74},[68,966,967,970,972,975,977,979,981,983,985,987,990,992],{"class":70,"line":82},[68,968,969],{"class":186},"bold",[68,971,190],{"class":74},[68,973,974],{"class":186}," _ ",[68,976,196],{"class":74},[68,978,199],{"class":186},[68,980,202],{"class":74},[68,982,205],{"class":173},[68,984,208],{"class":74},[68,986,211],{"class":74},[68,988,989],{"class":214},"SourceHanSansJP-Bold.ttf",[68,991,211],{"class":74},[68,993,159],{"class":74},[68,995,996],{"class":70,"line":89},[68,997,86],{"emptyLinePlaceholder":85},[68,999,1000,1003,1005,1007,1009,1011],{"class":70,"line":99},[68,1001,1002],{"class":186},"doc ",[68,1004,196],{"class":74},[68,1006,274],{"class":186},[68,1008,202],{"class":74},[68,1010,279],{"class":173},[68,1012,282],{"class":74},[68,1014,1015,1018,1020,1022,1024,1026,1028,1030,1032,1035],{"class":70,"line":111},[68,1016,1017],{"class":186},"    gpdf",[68,1019,202],{"class":74},[68,1021,353],{"class":173},[68,1023,208],{"class":74},[68,1025,211],{"class":74},[68,1027,360],{"class":214},[68,1029,211],{"class":74},[68,1031,190],{"class":74},[68,1033,1034],{"class":186}," reg",[68,1036,305],{"class":74},[68,1038,1039,1041,1043,1045,1047,1049,1052,1054,1056,1059],{"class":70,"line":121},[68,1040,1017],{"class":186},[68,1042,202],{"class":74},[68,1044,353],{"class":173},[68,1046,208],{"class":74},[68,1048,211],{"class":74},[68,1050,1051],{"class":214},"SourceHanSansJP-Bold",[68,1053,211],{"class":74},[68,1055,190],{"class":74},[68,1057,1058],{"class":186}," bold",[68,1060,305],{"class":74},[68,1062,1063,1065,1067,1069,1071,1073,1075,1077,1079,1081],{"class":70,"line":126},[68,1064,1017],{"class":186},[68,1066,202],{"class":74},[68,1068,379],{"class":173},[68,1070,208],{"class":74},[68,1072,211],{"class":74},[68,1074,360],{"class":214},[68,1076,211],{"class":74},[68,1078,190],{"class":74},[68,1080,392],{"class":339},[68,1082,305],{"class":74},[68,1084,1085],{"class":70,"line":136},[68,1086,159],{"class":74},[18,1088,1089,1092,1093,1096,1097,1099],{},[47,1090,1091],{},"-Bold"," 后缀是 ",[47,1094,1095],{},"template.Bold()"," 和真 Bold TTF 之间的契约。不注册就用 ",[47,1098,1095],{},"，会退化为在 Regular 字形上叠 0.4 pt 描边的合成粗体，小标题还行，到大字号就明显比真 Bold 单薄。",[18,1101,1102],{},"CJK 字体按惯例不做斜体版，Source Han Sans JP 也没有。日文需要斜体强调时，换字重或换颜色更自然；对汉字做倾斜变换看起来不是强调，是坏掉了。",[14,1104,1106],{"id":1105},"泛-cjkjp-单语还是-super-otc","泛 CJK、JP 单语、还是 Super OTC",[18,1108,1109],{},"Adobe 以多种粒度发布 Source Han Sans，对 Go PDF 生成不能混用：",[1111,1112,1113,1126,1136],"ul",{},[885,1114,1115,1118,1119,1122,1123,1125],{},[30,1116,1117],{},"SourceHanSans.ttc","（Super OTC）—— 把全部 CJK 语言塞进一个 20 MB+ 的 TrueType Collection。gpdf 不会在 ",[47,1120,1121],{},".ttc"," 内部按 face index 取脸，需要先用 ",[47,1124,899],{}," 切出 JP face 再作为 TTF 注册。通常不用这个。",[885,1127,1128,1131,1132,1135],{},[30,1129,1130],{},"地区统一 OTF","（如 ",[47,1133,1134],{},"SourceHanSans-Regular.otf","）—— 泛 CJK 共存，CFF 轮廓，gpdf 读不了。",[885,1137,1138,1141,1142,1144,1145,1147,1148,720],{},[30,1139,1140],{},"分语言 TTF","（",[47,1143,49],{},"）—— 仅 JP，",[47,1146,852],{}," 轮廓。",[30,1149,1150],{},"就要这个",[18,1152,1153,1154,1156,1157,1160,1161,1164],{},"如果你的文档同页混有日文和韩文 / 中文，不要依赖泛 CJK OTF，而是分别注册各语言 family：",[47,1155,360],{}," 加 ",[47,1158,1159],{},"SourceHanSansKR","，在语系切换处用 ",[47,1162,1163],{},"template.FontFamily"," 明确指定。泛 CJK OTF 把汉字做了 Han unification 统一到一个形上，会让日文正文里本该是日文字形的汉字变成中文字形。",[14,1166,1168],{"id":1167},"什么时候选思源黑体而非-noto","什么时候选思源黑体而非 Noto",[18,1170,1171],{},"同一套轮廓，不同的发布渠道。选 Source Han Sans JP 合理的场景：",[1111,1173,1174,1177,1183],{},[885,1175,1176],{},"运维团队偏好把字体锚定到 Adobe 的 GitHub release tag（可复现、可审计）",[885,1178,1179,1180,1182],{},"公司内部已经镜像 ",[47,1181,829],{},"（严格制品政策的企业里常见）",[885,1184,1185],{},"流水线别的环节也需要泛 CJK OTC 包（DTP 对接、品牌系统在 Adobe 命名下统一）",[18,1187,1188],{},"选 Noto Sans JP 更合适的场景：",[1111,1190,1191,1198,1201],{},[885,1192,1193,1194,1197],{},"想要最短路径拿到 TTF（",[47,1195,1196],{},"fonts.google.com/noto/specimen/Noto+Sans+JP"," → zip → 搞定）",[885,1199,1200],{},"不想在构建里做 OTF → TTF 转换",[885,1202,1203],{},"项目已经通过既有流程拉其他 Google Fonts",[18,1205,1206],{},"渲染结果一样。判断依据是运维层面的 —— 文件在哪里、怎么版本化、团队熟哪个 —— 而不是美感。",[14,1208,1209],{"id":1209},"延伸阅读",[1111,1211,1212,1218,1225,1232],{},[885,1213,1214,1217],{},[22,1215,1216],{"href":833},"如何在 gpdf 中使用 Noto Sans JP？"," —— 同样的字形，开箱即为 TTF",[885,1219,1220,1224],{},[22,1221,1223],{"href":1222},"/zh/blog/embed-japanese-font","如何在 gpdf 中嵌入日文字体？"," —— CJK 嵌入的通用配方",[885,1226,1227,1231],{},[22,1228,1230],{"href":1229},"/zh/blog/ipaex-gothic-gpdf","如何在 gpdf 中使用 IPAex Gothic？"," —— 面向日本官方场合的 IPA 许可替代",[885,1233,1234,1238],{},[22,1235,1237],{"href":1236},"/zh/blog/tofu-boxes-japanese","为什么 gpdf 生成的 PDF 中日文显示为方块？"," —— 字形缺失排查",[14,1240,1242],{"id":1241},"试试-gpdf","试试 gpdf",[18,1244,1245],{},"gpdf 是一个 Go PDF 生成库。MIT 协议、零外部依赖、原生支持 CJK。",[59,1247,1251],{"className":1248,"code":1249,"language":1250,"meta":64,"style":64},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go get github.com/gpdf-dev/gpdf\n","bash",[47,1252,1253],{"__ignoreMap":64},[68,1254,1255,1257,1260],{"class":70,"line":71},[68,1256,63],{"class":78},[68,1258,1259],{"class":214}," get",[68,1261,1262],{"class":214}," github.com/gpdf-dev/gpdf\n",[18,1264,1265,1269,1270],{},[22,1266,1268],{"href":24,"rel":1267},[26],"⭐ 在 GitHub 上 Star"," · ",[22,1271,1274],{"href":1272,"rel":1273},"https://gpdf.dev/zh/docs/quickstart",[26],"阅读文档",[1276,1277,1278],"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":64,"searchDepth":82,"depth":82,"links":1280},[1281,1282,1283,1284,1285,1286,1287,1288,1289,1290],{"id":16,"depth":82,"text":16},{"id":36,"depth":82,"text":36},{"id":57,"depth":82,"text":57},{"id":723,"depth":82,"text":724},{"id":837,"depth":82,"text":838},{"id":917,"depth":82,"text":917},{"id":1105,"depth":82,"text":1106},{"id":1167,"depth":82,"text":1168},{"id":1209,"depth":82,"text":1209},{"id":1241,"depth":82,"text":1242},"2026-04-19","从 Adobe 的 GitHub 发布页下载 TTF 版本的思源黑体 JP，用 gpdf.WithFont 注册。七个字重，SIL OFL，与 Noto Sans JP 同源字形。",false,"md",{"name":1296,"totalTime":1297,"tools":1298,"steps":1301},"在 gpdf 文档中把 Source Han Sans JP（思源黑体 JP）设为默认字体","PT15M",[1299,1300],"Go 1.22+","SourceHanSansJP-Regular.ttf（来自 adobe-fonts/source-han-sans）",[1302,1305,1308,1311,1314],{"name":1303,"text":1304},"从 Adobe 的 GitHub 发布页下载 TTF 版","打开 github.com/adobe-fonts/source-han-sans/releases，在最新版本中下载 TTF 包（不是 OTF 也不是 SuperOTC），解压出 SourceHanSansJP-Regular.ttf。gpdf 只解析 TrueType，不读 CFF 风格的 OpenType。",{"name":1306,"text":1307},"启动时加载字节","用 os.ReadFile(\"SourceHanSansJP-Regular.ttf\") 或 //go:embed 载入。为保证 CI 可复现，建议把 Adobe 的某个 release tag 固定到仓库里，不要在构建时下载。",{"name":1309,"text":1310},"在文档构造时注册","把 gpdf.WithFont(\"SourceHanSansJP\", fontBytes) 和 gpdf.WithDefaultFont(\"SourceHanSansJP\", 11) 传给 gpdf.NewDocument。不需要 AddUTF8Font，也不要传文件路径。",{"name":1312,"text":1313},"按需注册额外字重","思源黑体 JP 以单独 TTF 形式发布 ExtraLight 到 Heavy 共 7 个字重。把 Bold TTF 用 SourceHanSansJP-Bold 作为键注册，template.Bold() 就会选择真正的 Bold 轮廓，而不是合成描边。",{"name":1315,"text":1316},"随二进制保留 OFL.txt","SIL OFL 1.1 要求字体二进制分发到哪里，许可证文本就要跟到哪里。若用 //go:embed 嵌入 TTF，把 OFL.txt 也一并嵌入 LICENSES/，并从 NOTICE 引用。",null,{},"/zh/blog/source-han-sans-jp-with-gpdf",{"title":6,"description":1292},"zh/blog/010.source-han-sans-jp-with-gpdf",[1323,1324,1325],"recipe","cjk","tutorial","lsPAS14UzbYTcykWtazm4inRezgTgdWkIo1gSxyRlWA",{"id":1328,"title":1329,"author":1330,"body":1331,"date":1291,"description":2935,"draft":1293,"extension":1294,"howTo":1317,"image":1317,"meta":2936,"navigation":85,"path":2937,"seo":2938,"stem":2939,"tags":2940,"updated":1317,"__hash__":2944},"blogZh/zh/blog/011.why-gpdf-is-faster.md","为什么 gpdf 比其他 Go PDF 库快 10–30 倍",{"name":8,"url":9},{"type":11,"value":1332,"toc":2914},[1333,1337,1360,1384,1387,1398,1405,1409,1416,1525,1535,1542,1549,1553,1556,1562,1573,1579,1586,1884,1919,1942,1945,1949,1952,1970,1980,1989,1992,1996,1999,2006,2103,2131,2134,2137,2283,2292,2302,2305,2312,2318,2328,2331,2337,2340,2343,2357,2360,2364,2367,2374,2377,2403,2410,2413,2420,2549,2552,2556,2569,2572,2575,2589,2596,2599,2602,2680,2687,2690,2694,2697,2707,2717,2730,2739,2742,2745,2748,2788,2797,2800,2811,2815,2825,2831,2844,2850,2856,2860,2863,2875,2885,2888,2911],[14,1334,1336],{"id":1335},"tldr","TL;DR",[18,1338,1339,1340,1343,1344,1347,1348,1351,1352,1355,1356,1359],{},"gpdf 生成单页 ",[30,1341,1342],{},"13 µs","，4×10 发票表格 ",[30,1345,1346],{},"108 µs","，100 页分页报告 ",[30,1349,1350],{},"683 µs","。次快的 ",[47,1353,1354],{},"jung-kurt/gofpdf"," 做同样的 100 页需要 ",[30,1357,1358],{},"11.7 ms","，大约慢 17 倍。这不是调参差异，而是三个设计决策相互叠加的结果:",[882,1361,1362,1368,1378],{},[885,1363,1364,1367],{},[30,1365,1366],{},"单遍布局。"," Builder API 和 PDF 内容流之间没有中间 AST。",[885,1369,1370,1373,1374,1377],{},[30,1371,1372],{},"热点路径使用具体类型。"," 布局循环里没有反射、没有 ",[47,1375,1376],{},"interface{}","、没有虚拟派发。",[885,1379,1380,1383],{},[30,1381,1382],{},"TrueType 子集器只解析 cmap 一次。"," 不是每个字形一次，也不是每页一次。就一次。",[18,1385,1386],{},"三个里任何一个单独都能带来 2–3 倍。叠起来就是一个数量级。",[18,1388,1389,1390,1397],{},"本文直接追那些产生这些数字的代码路径。基准测试源码公开在 ",[22,1391,1394],{"href":1392,"rel":1393},"https://github.com/gpdf-dev/gpdf/tree/main/_benchmark",[26],[47,1395,1396],{},"_benchmark/benchmark_test.go"," — 克隆下来在自己的机器上跑，数字对不上就提 issue。",[18,1399,1400,1401,1404],{},"先声明偏向: 我们是 gpdf 团队。\"我们更快\"的诚实版本是\"我们做了不同的权衡\"，真正有意思的问题是 ",[30,1402,1403],{},"为了这份速度放弃了什么","。文章后半讲这个。",[14,1406,1408],{"id":1407},"快在这里指什么","\"快\"在这里指什么",[18,1410,1411,1412,1415],{},"在讲架构之前，先把要解释的积分板列出来 (Apple M1, Go 1.25, 关闭 CGO, ",[47,1413,1414],{},"-benchmem"," 开启):",[740,1417,1418,1439],{},[743,1419,1420],{},[746,1421,1422,1425,1427,1430,1433,1436],{},[749,1423,1424],{},"工作负载",[749,1426,27],{},[749,1428,1429],{},"gofpdf",[749,1431,1432],{},"go-pdf/fpdf",[749,1434,1435],{},"signintech/gopdf",[749,1437,1438],{},"Maroto v2",[758,1440,1441,1462,1483,1503],{},[746,1442,1443,1446,1450,1453,1456,1459],{},[763,1444,1445],{},"单页 Hello World",[763,1447,1448],{},[30,1449,1342],{},[763,1451,1452],{},"132 µs",[763,1454,1455],{},"135 µs",[763,1457,1458],{},"423 µs",[763,1460,1461],{},"237 µs",[746,1463,1464,1467,1471,1474,1477,1480],{},[763,1465,1466],{},"4×10 发票表格",[763,1468,1469],{},[30,1470,1346],{},[763,1472,1473],{},"241 µs",[763,1475,1476],{},"243 µs",[763,1478,1479],{},"835 µs",[763,1481,1482],{},"8,600 µs",[746,1484,1485,1488,1492,1495,1498,1500],{},[763,1486,1487],{},"100 页分页报告",[763,1489,1490],{},[30,1491,1350],{},[763,1493,1494],{},"11,700 µs",[763,1496,1497],{},"11,900 µs",[763,1499,1482],{},[763,1501,1502],{},"19,800 µs",[746,1504,1505,1508,1513,1516,1519,1522],{},[763,1506,1507],{},"复杂 CJK 发票",[763,1509,1510],{},[30,1511,1512],{},"133 µs",[763,1514,1515],{},"254 µs",[763,1517,1518],{},"n/a",[763,1520,1521],{},"997 µs",[763,1523,1524],{},"10,400 µs",[18,1526,1527,1528,1531,1532,1534],{},"在解释之前能看到两个形状。页数越多 ",[30,1529,1530],{},"差距越大"," (Hello World 10 倍、100 页 17 倍)。复杂度越高 ",[30,1533,1530],{}," (表格单独 108 µs，Maroto 经 gofpdf 后端 8.6 ms)。",[18,1536,1537,1538,1541],{},"两个形状的根源相同: ",[30,1539,1540],{},"gpdf 的布局循环在公共路径上不分配内存","，所以每个元素的成本几乎是平的。原因下面讲。",[18,1543,1544,1545,1548],{},"免责声明没人想读但还是要写: ",[30,1546,1547],{},"对大多数 PDF 工作负载，绝对速度没想象中那么重要","。如果最大的文档只是一张一页收据，这张表里的每个维护中的库都能在请求路径上生成。起作用的阈值是\"能不能在一个批次里同步生成 100 份而不排队\"。",[14,1550,1552],{"id":1551},"决策-1-不构造中间-ast","决策 1: 不构造中间 AST",[18,1554,1555],{},"大多数 PDF Builder 库是这样工作的:",[59,1557,1560],{"className":1558,"code":1559,"language":926},[924],"builder API → 文档树 (AST) → 布局遍历 → 序列化器 → 字节\n",[47,1561,1559],{"__ignoreMap":64},[18,1563,1564,1565,1568,1569,1572],{},"文档树那一步是问题。每次 ",[47,1566,1567],{},".Text()"," 都分配一个节点。每次 ",[47,1570,1571],{},".Row()"," 都分配一个容器。布局遍历走一次树算位置，序列化器再走一次树吐字节。三次遍、三组分配、三趟 CPU 缓存。",[18,1574,1575,1576,720],{},"gpdf 没有第 2 步。Builder 直接写入布局上下文，布局上下文直接写入内容流。",[30,1577,1578],{},"一遍",[18,1580,1581,1582,1585],{},"文本元素实际的代码路径 (从 ",[47,1583,1584],{},"template/col_builder.go"," 裁剪):",[59,1587,1589],{"className":61,"code":1588,"language":63,"meta":64,"style":64},"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",[47,1590,1591,1635,1657,1673,1698,1727,1744,1777,1825,1844,1859,1880],{"__ignoreMap":64},[68,1592,1593,1595,1598,1601,1604,1606,1608,1611,1613,1616,1620,1622,1625,1628,1631,1633],{"class":70,"line":71},[68,1594,170],{"class":74},[68,1596,1597],{"class":74}," (",[68,1599,1600],{"class":441},"c ",[68,1602,1603],{"class":74},"*",[68,1605,491],{"class":78},[68,1607,456],{"class":74},[68,1609,1610],{"class":173}," Text",[68,1612,208],{"class":74},[68,1614,1615],{"class":441},"s",[68,1617,1619],{"class":1618},"spNyl"," string",[68,1621,190],{"class":74},[68,1623,1624],{"class":441}," opts",[68,1626,1627],{"class":74}," ...",[68,1629,1630],{"class":78},"TextOption",[68,1632,456],{"class":74},[68,1634,180],{"class":74},[68,1636,1637,1640,1642,1645,1647,1650,1652,1655],{"class":70,"line":82},[68,1638,1639],{"class":186},"    opt ",[68,1641,196],{"class":74},[68,1643,1644],{"class":186}," c",[68,1646,202],{"class":74},[68,1648,1649],{"class":173},"resolveOptions",[68,1651,208],{"class":74},[68,1653,1654],{"class":186},"opts",[68,1656,159],{"class":74},[68,1658,1659,1662,1664,1666,1668,1671],{"class":70,"line":89},[68,1660,1661],{"class":186},"    box ",[68,1663,196],{"class":74},[68,1665,1644],{"class":186},[68,1667,202],{"class":74},[68,1669,1670],{"class":173},"currentBox",[68,1672,424],{"class":74},[68,1674,1675,1678,1680,1682,1684,1687,1689,1691,1693,1696],{"class":70,"line":99},[68,1676,1677],{"class":186},"    w ",[68,1679,196],{"class":74},[68,1681,1644],{"class":186},[68,1683,202],{"class":74},[68,1685,1686],{"class":173},"measureText",[68,1688,208],{"class":74},[68,1690,1615],{"class":186},[68,1692,190],{"class":74},[68,1694,1695],{"class":186}," opt",[68,1697,159],{"class":74},[68,1699,1700,1703,1705,1707,1709,1711,1713,1716,1718,1720,1722,1724],{"class":70,"line":111},[68,1701,1702],{"class":186},"    h ",[68,1704,196],{"class":74},[68,1706,1695],{"class":186},[68,1708,202],{"class":74},[68,1710,525],{"class":186},[68,1712,202],{"class":74},[68,1714,1715],{"class":173},"Pt",[68,1717,177],{"class":74},[68,1719,445],{"class":74},[68,1721,1695],{"class":186},[68,1723,202],{"class":74},[68,1725,1726],{"class":186},"LineHeight\n",[68,1728,1729,1732,1734,1737,1739,1742],{"class":70,"line":121},[68,1730,1731],{"class":186},"    c",[68,1733,202],{"class":74},[68,1735,1736],{"class":186},"writer",[68,1738,202],{"class":74},[68,1740,1741],{"class":173},"BeginText",[68,1743,424],{"class":74},[68,1745,1746,1748,1750,1752,1754,1757,1759,1762,1764,1767,1769,1771,1773,1775],{"class":70,"line":126},[68,1747,1731],{"class":186},[68,1749,202],{"class":74},[68,1751,1736],{"class":186},[68,1753,202],{"class":74},[68,1755,1756],{"class":173},"SetFont",[68,1758,208],{"class":74},[68,1760,1761],{"class":186},"opt",[68,1763,202],{"class":74},[68,1765,1766],{"class":186},"Font",[68,1768,190],{"class":74},[68,1770,1695],{"class":186},[68,1772,202],{"class":74},[68,1774,525],{"class":186},[68,1776,159],{"class":74},[68,1778,1779,1781,1783,1785,1787,1790,1792,1795,1797,1800,1802,1805,1807,1810,1813,1815,1817,1819,1821,1823],{"class":70,"line":136},[68,1780,1731],{"class":186},[68,1782,202],{"class":74},[68,1784,1736],{"class":186},[68,1786,202],{"class":74},[68,1788,1789],{"class":173},"MoveTo",[68,1791,208],{"class":74},[68,1793,1794],{"class":186},"box",[68,1796,202],{"class":74},[68,1798,1799],{"class":186},"X",[68,1801,190],{"class":74},[68,1803,1804],{"class":186}," box",[68,1806,202],{"class":74},[68,1808,1809],{"class":186},"Y",[68,1811,1812],{"class":74},"-",[68,1814,1761],{"class":186},[68,1816,202],{"class":74},[68,1818,525],{"class":186},[68,1820,202],{"class":74},[68,1822,1715],{"class":173},[68,1824,543],{"class":74},[68,1826,1827,1829,1831,1833,1835,1838,1840,1842],{"class":70,"line":146},[68,1828,1731],{"class":186},[68,1830,202],{"class":74},[68,1832,1736],{"class":186},[68,1834,202],{"class":74},[68,1836,1837],{"class":173},"ShowString",[68,1839,208],{"class":74},[68,1841,1615],{"class":186},[68,1843,159],{"class":74},[68,1845,1846,1848,1850,1852,1854,1857],{"class":70,"line":156},[68,1847,1731],{"class":186},[68,1849,202],{"class":74},[68,1851,1736],{"class":186},[68,1853,202],{"class":74},[68,1855,1856],{"class":173},"EndText",[68,1858,424],{"class":74},[68,1860,1861,1863,1865,1868,1870,1873,1875,1878],{"class":70,"line":162},[68,1862,1731],{"class":186},[68,1864,202],{"class":74},[68,1866,1867],{"class":173},"advance",[68,1869,208],{"class":74},[68,1871,1872],{"class":186},"w",[68,1874,190],{"class":74},[68,1876,1877],{"class":186}," h",[68,1879,159],{"class":74},[68,1881,1882],{"class":70,"line":167},[68,1883,706],{"class":74},[18,1885,1886,1887,1890,1891,1894,1895,1898,1899,866,1901,866,1903,1905,1906,866,1909,866,1912,866,1915,1918],{},"没有节点入树。没有位置被推迟。writer 是一个 ",[47,1888,1889],{},"*pdf.Writer","，持有一个 ",[47,1892,1893],{},"io.Writer"," (通常是 ",[47,1896,1897],{},"bytes.Buffer",")。",[47,1900,1741],{},[47,1902,1789],{},[47,1904,1837],{}," 会立刻把 ",[47,1907,1908],{},"BT",[47,1910,1911],{},"Td",[47,1913,1914],{},"Tj",[47,1916,1917],{},"ET"," 这些 PDF 算子写进 buffer。",[18,1920,1921,1922,1925,1926,1929,1930,1933,1934,1937,1938,1941],{},"对比 gofpdf 怎么做同样的逻辑操作。gofpdf 维护一个 ",[47,1923,1924],{},"page"," 对象，里面有一个操作切片。每次 ",[47,1927,1928],{},"SetXY"," + ",[47,1931,1932],{},"Cell"," 调用都追加到那个切片。最后 ",[47,1935,1936],{},"Output"," (或 ",[47,1939,1940],{},"OutputFileAndClose",") 走一遍切片把字节吐出来。每个 cell 两次分配 — 一次操作结构体、一次字符串拷贝 — 加上对数据的额外一遍扫描。",[18,1943,1944],{},"100 页报告每页约 40 行，就是 gpdf 不会做的 4,000 次额外分配。",[1946,1947,1948],"h3",{"id":1948},"单遍路线痛在哪里",[18,1950,1951],{},"明显的疑问: 那些必须在开始写字节之前就知道最终页面布局的功能怎么办? 带页码的页眉。跨页表格。锚在最后一行正文下的页脚。",[18,1953,1954,1955,1958,1959,1962,1963,853,1966,1969],{},"两个回答。第一，缓冲的是 ",[30,1956,1957],{},"页","，不是整个文档。一页是有界单元，几十 KB，不是几 MB。下一次 ",[47,1960,1961],{},"AddPage()"," 被调用时，当前页的内容流会被敲定 (",[47,1964,1965],{},"Length",[47,1967,1968],{},"Filter","、偏移)，它的 xref 入口被写出，页缓冲被重置。内存高水位保持在 O(一页)。",[18,1971,1972,1973,1976,1977,720],{},"第二，对真正的全局元素 (\"第 3/27 页\")，把 ",[30,1974,1975],{},"那部分范围"," 延迟到 fix-up 扫描。其余内容已经在流里。fix-up 扫一个短的 deferred-reference 标记列表并打补丁。这是代码库里唯一支付接近 AST 代价的地方，而且 ",[30,1978,1979],{},"只对真正需要的部分支付",[18,1981,1982,1983,900,1986,1988],{},"代价是: 你没法对节点树做任意后处理，因为根本没有节点树。你没法写一个\"把所有 ",[47,1984,1985],{},"bold: true",[47,1987,506],{}," 节点重排\"的插件。需要这种 API 形状就用 Maroto v2。",[18,1990,1991],{},"我们认为这个权衡对 gpdf 面向的使用场景是对的。大多数 PDF 是从左到右、从上到下按构造时就知道的布局生成的。为少数场景保留 AST 的成本被多数场景在每页都付。比例被我们反过来了。",[14,1993,1995],{"id":1994},"决策-2-热点路径没有反射和-interface","决策 2: 热点路径没有反射和 interface",[18,1997,1998],{},"写起来不比上面有趣，但用 profile 看，剩下一半速度差距在这里。",[18,2000,2001,2002,2005],{},"gofpdf 的 ",[47,2003,2004],{},"CellFormat"," 签名:",[59,2007,2009],{"className":61,"code":2008,"language":63,"meta":64,"style":64},"func (f *Fpdf) CellFormat(w, h float64, txtStr, borderStr string,\n    ln int, alignStr string, fill bool, link int, linkStr string) { ... }\n",[47,2010,2011,2056],{"__ignoreMap":64},[68,2012,2013,2015,2017,2020,2022,2025,2027,2030,2032,2034,2036,2038,2041,2043,2046,2048,2051,2053],{"class":70,"line":71},[68,2014,170],{"class":74},[68,2016,1597],{"class":74},[68,2018,2019],{"class":441},"f ",[68,2021,1603],{"class":74},[68,2023,2024],{"class":78},"Fpdf",[68,2026,456],{"class":74},[68,2028,2029],{"class":173}," CellFormat",[68,2031,208],{"class":74},[68,2033,1872],{"class":441},[68,2035,190],{"class":74},[68,2037,1877],{"class":441},[68,2039,2040],{"class":1618}," float64",[68,2042,190],{"class":74},[68,2044,2045],{"class":441}," txtStr",[68,2047,190],{"class":74},[68,2049,2050],{"class":441}," borderStr",[68,2052,1619],{"class":1618},[68,2054,2055],{"class":74},",\n",[68,2057,2058,2061,2064,2066,2069,2071,2073,2076,2079,2081,2084,2086,2088,2091,2093,2095,2098,2100],{"class":70,"line":82},[68,2059,2060],{"class":441},"    ln",[68,2062,2063],{"class":1618}," int",[68,2065,190],{"class":74},[68,2067,2068],{"class":441}," alignStr",[68,2070,1619],{"class":1618},[68,2072,190],{"class":74},[68,2074,2075],{"class":441}," fill",[68,2077,2078],{"class":1618}," bool",[68,2080,190],{"class":74},[68,2082,2083],{"class":441}," link",[68,2085,2063],{"class":1618},[68,2087,190],{"class":74},[68,2089,2090],{"class":441}," linkStr",[68,2092,1619],{"class":1618},[68,2094,456],{"class":74},[68,2096,2097],{"class":74}," {",[68,2099,1627],{"class":74},[68,2101,2102],{"class":74}," }\n",[18,2104,2105,2106,2109,2110,720,2113,2116,2117,2120,2121,2123,2124,2126,2127,2130],{},"这没问题。看 Maroto 的组件树。",[47,2107,2108],{},"Row"," 持有 ",[47,2111,2112],{},"[]Component",[47,2114,2115],{},"Component"," 是 interface。每次布局操作都是虚拟派发: ",[47,2118,2119],{},"component.Render(ctx)","。一个 ",[47,2122,469],{}," 里放一个 ",[47,2125,506],{}," 和一个 ",[47,2128,2129],{},"Spacer"," 就是三次派发。100 页 × 每页 30 行 × 每行 3 组件 = 9,000 次派发。",[18,2132,2133],{},"单次 Go interface 派发约 2–3 ns，单独不算罪。但 interface 也强制编译器把装箱的值放到堆上 — 没有 Go 编译器不总能做的去虚化，interface 后面没法栈分配。所以代价不只是派发本身，还有喂它的那次分配。",[18,2135,2136],{},"gpdf 的布局引擎用具体结构体:",[59,2138,2140],{"className":61,"code":2139,"language":63,"meta":64,"style":64},"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",[47,2141,2142,2155,2165,2175,2191,2208,2216,2220,2224,2235,2245,2252,2264,2279],{"__ignoreMap":64},[68,2143,2144,2147,2150,2153],{"class":70,"line":71},[68,2145,2146],{"class":74},"type",[68,2148,2149],{"class":78}," RowBuilder",[68,2151,2152],{"class":74}," struct",[68,2154,180],{"class":74},[68,2156,2157,2160,2162],{"class":70,"line":82},[68,2158,2159],{"class":186},"    doc    ",[68,2161,1603],{"class":74},[68,2163,2164],{"class":78},"Document\n",[68,2166,2167,2170,2172],{"class":70,"line":89},[68,2168,2169],{"class":186},"    parent ",[68,2171,1603],{"class":74},[68,2173,2174],{"class":78},"pageState\n",[68,2176,2177,2180,2183,2185,2188],{"class":70,"line":99},[68,2178,2179],{"class":186},"    spans  ",[68,2181,2182],{"class":74},"[",[68,2184,474],{"class":339},[68,2186,2187],{"class":74},"]",[68,2189,2190],{"class":1618},"int\n",[68,2192,2193,2196,2198,2200,2202,2204],{"class":70,"line":111},[68,2194,2195],{"class":186},"    cols   ",[68,2197,2182],{"class":74},[68,2199,474],{"class":339},[68,2201,2187],{"class":74},[68,2203,491],{"class":78},[68,2205,2207],{"class":2206},"sHwdD","  // 值数组，不是指针，不是 interface\n",[68,2209,2210,2213],{"class":70,"line":121},[68,2211,2212],{"class":186},"    n      ",[68,2214,2215],{"class":1618},"uint8\n",[68,2217,2218],{"class":70,"line":126},[68,2219,706],{"class":74},[68,2221,2222],{"class":70,"line":136},[68,2223,86],{"emptyLinePlaceholder":85},[68,2225,2226,2228,2231,2233],{"class":70,"line":146},[68,2227,2146],{"class":74},[68,2229,2230],{"class":78}," ColBuilder",[68,2232,2152],{"class":74},[68,2234,180],{"class":74},[68,2236,2237,2240,2242],{"class":70,"line":156},[68,2238,2239],{"class":186},"    row    ",[68,2241,1603],{"class":74},[68,2243,2244],{"class":78},"RowBuilder\n",[68,2246,2247,2250],{"class":70,"line":162},[68,2248,2249],{"class":186},"    span   ",[68,2251,2190],{"class":1618},[68,2253,2254,2257,2259,2261],{"class":70,"line":167},[68,2255,2256],{"class":186},"    cursor ",[68,2258,320],{"class":78},[68,2260,202],{"class":74},[68,2262,2263],{"class":78},"Point\n",[68,2265,2266,2269,2271,2274,2276],{"class":70,"line":183},[68,2267,2268],{"class":186},"    writer ",[68,2270,1603],{"class":74},[68,2272,2273],{"class":78},"pdf",[68,2275,202],{"class":74},[68,2277,2278],{"class":78},"Writer\n",[68,2280,2281],{"class":70,"line":221},[68,2282,706],{"class":74},[18,2284,2285,2288,2289,720],{},[47,2286,2287],{},"cols"," 是按网格最大列数 (12) 固定大小的值数组。不在堆上。行迭代它的列时也没有 interface 派发。Builder 持 writer 的指针，而 ",[30,2290,2291],{},"writer 不知道 Builder 树的存在",[18,2293,2294,2295,2298,2299,2301],{},"回调模式 (",[47,2296,2297],{},"r.Col(4, func(c *ColBuilder) { ... })",") 不是偶然。我们原型过的其他形式 — 返回可链式调用的 struct、Component interface 装箱树 — 都更慢。这个闭包零分配的原因是 ",[47,2300,491],{}," 是调用方通过指针参数持有的值，闭包本身在多数情况下被 escape analysis 移到栈。",[1946,2303,2304],{"id":2304},"怎么知道它起作用了",[18,2306,2307,2308,2311],{},"在 gpdf 跑 ",[47,2309,2310],{},"go test -run=XXX -bench=BenchmarkSinglePage -memprofile=mem.out","，得到一个数我们自豪:",[59,2313,2316],{"className":2314,"code":2315,"language":926},[924],"BenchmarkSinglePage-8   91270   13120 ns/op   8321 B/op   52 allocs/op\n",[47,2317,2315],{"__ignoreMap":64},[18,2319,2320,2321,2324,2325,2327],{},"整个 PDF 页 ",[30,2322,2323],{},"52 次分配","。几乎全部是初始页缓冲、字体度量查找 (每字体一次，不是每字形一次)、最后的 ",[47,2326,1897],{}," 扩容。布局循环零分配 — 看 profile 就知道。",[18,2329,2330],{},"gofpdf 同一页:",[59,2332,2335],{"className":2333,"code":2334,"language":926},[924],"BenchmarkGofpdfSinglePage-8   7500   132400 ns/op   71200 B/op   430 allocs/op\n",[47,2336,2334],{"__ignoreMap":64},[18,2338,2339],{},"430 次分配。大部分是操作切片和填它的字符串拷贝。把这 8 倍的分配差走一遍 GC，10 倍的运行时差就是自然结果。",[1946,2341,2342],{"id":2342},"放弃了什么",[18,2344,2345,2346,2349,2350,2352,2353,2356],{},"热点路径零人体工学意味着 ",[30,2347,2348],{},"扩展点少","。想写一个接入 gpdf 布局的自定义元素类型 — 类似于在 Maroto 里实现 ",[47,2351,2115],{}," — 做不到。没有可满足的 interface。替代是 ",[47,2354,2355],{},"template.WithWriterSetup()","，提供对 PDF writer 的钩子，用来注入自定义注解、PDF/A 元数据、加密。布局层的扩展用一个调用相同 Builder 方法的辅助函数来实现。",[18,2358,2359],{},"扩展点少是真实的代价。当前判断是平衡的。如果项目方向改变让这判断不再成立，会重新考虑。",[14,2361,2363],{"id":2362},"决策-3-不重走的-truetype-子集器","决策 3: 不重走的 TrueType 子集器",[18,2365,2366],{},"CJK 基准 (gpdf 133 µs 对 gofpdf 254 µs) 的差距主要来自这里。",[18,2368,2369,2370,2373],{},"TrueType 子集化的作用简述: 把日文字体嵌入 PDF 时，你不会想嵌入所有 20,000+ 字形 — 一个 100 KB 的文档里放 15 MB 字体数据。你想 ",[30,2371,2372],{},"只嵌入文档实际用到的字形","，打包成 PDF 阅读器能解码的有效子集 TTF。",[18,2375,2376],{},"流程:",[882,2378,2379,2394,2397,2400],{},[885,2380,2381,2382,2384,2385,2387,2388,2390,2391,2393],{},"解析完整 TTF 表: ",[47,2383,859],{}," (字符到字形映射)、",[47,2386,852],{}," (轮廓)、",[47,2389,856],{}," (到 glyf 的偏移)、",[47,2392,734],{}," (水平度量) 等。",[885,2395,2396],{},"对文档每个字符，通过 cmap 查字形 ID。",[885,2398,2399],{},"递归收集复合字形引用的字形。",[885,2401,2402],{},"输出只含那些字形、重新编号的新 TTF。",[18,2404,2405,2406,2409],{},"步骤 2 — cmap 查找 — 是热点路径。gofpdf 实现 ",[30,2407,2408],{},"每次字形查找都从头走一遍 cmap 表","。只含 Latin 的页面没问题; cmap 小、缓存友好。一个 CJK 页面有 150 个唯一字形，就是对表做 150 次完整走查。",[18,2411,2412],{},"cmap format 12 (大多数现代 CJK 字体使用) 是 (start, end, startGlyphID) 三元组的有序数组。一次走查对范围数是 O(n)，NotoSansJP 大约 200–500 个范围。150 次查找 × 每范围比较 × 400 范围 = 远超必要的工作量。",[18,2414,2415,2416,2419],{},"gpdf 在字体首次加载时把整张 cmap 展开成 ",[47,2417,2418],{},"map[rune]uint16","。之后每次查找都是 O(1)。NotoSansJP 的一次性成本约 150 µs，之后每字符 10 ns。",[59,2421,2423],{"className":61,"code":2422,"language":63,"meta":64,"style":64},"// 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",[47,2424,2425,2430,2441,2460,2474,2484,2488,2492,2523,2545],{"__ignoreMap":64},[68,2426,2427],{"class":70,"line":71},[68,2428,2429],{"class":2206},"// pdf/font/ttf.go 简化\n",[68,2431,2432,2434,2437,2439],{"class":70,"line":82},[68,2433,2146],{"class":74},[68,2435,2436],{"class":78}," Font",[68,2438,2152],{"class":74},[68,2440,180],{"class":74},[68,2442,2443,2446,2449,2452,2454,2457],{"class":70,"line":89},[68,2444,2445],{"class":186},"    runeToGID ",[68,2447,2448],{"class":74},"map[",[68,2450,2451],{"class":1618},"rune",[68,2453,2187],{"class":74},[68,2455,2456],{"class":1618},"uint16",[68,2458,2459],{"class":2206},"  // 加载时解析一次\n",[68,2461,2462,2465,2468,2471],{"class":70,"line":99},[68,2463,2464],{"class":186},"    glyphs    ",[68,2466,2467],{"class":74},"[]",[68,2469,2470],{"class":78},"glyph",[68,2472,2473],{"class":2206},"          // 按 GID 索引\n",[68,2475,2476,2479,2481],{"class":70,"line":111},[68,2477,2478],{"class":186},"    metrics   ",[68,2480,2467],{"class":74},[68,2482,2483],{"class":78},"glyphMetric\n",[68,2485,2486],{"class":70,"line":121},[68,2487,706],{"class":74},[68,2489,2490],{"class":70,"line":126},[68,2491,86],{"emptyLinePlaceholder":85},[68,2493,2494,2496,2498,2500,2502,2504,2506,2509,2511,2513,2516,2518,2521],{"class":70,"line":136},[68,2495,170],{"class":74},[68,2497,1597],{"class":74},[68,2499,2019],{"class":441},[68,2501,1603],{"class":74},[68,2503,1766],{"class":78},[68,2505,456],{"class":74},[68,2507,2508],{"class":173}," GlyphFor",[68,2510,208],{"class":74},[68,2512,442],{"class":441},[68,2514,2515],{"class":1618}," rune",[68,2517,456],{"class":74},[68,2519,2520],{"class":1618}," uint16",[68,2522,180],{"class":74},[68,2524,2525,2528,2531,2533,2536,2538,2540,2542],{"class":70,"line":146},[68,2526,2527],{"class":92},"    return",[68,2529,2530],{"class":186}," f",[68,2532,202],{"class":74},[68,2534,2535],{"class":186},"runeToGID",[68,2537,2182],{"class":74},[68,2539,442],{"class":186},[68,2541,2187],{"class":74},[68,2543,2544],{"class":2206},"  // O(1)、缓存友好、无表走查\n",[68,2546,2547],{"class":70,"line":156},[68,2548,706],{"class":74},[18,2550,2551],{},"一个按 rune 索引的 map，对 cmap 表做一次线性扫描构建。多个页面使用同一字体的文档 (通常是所有页) 中，字形查找从\"约为页数 × 字形数的二次\"变成\"总字形数加常量\"。",[1946,2553,2555],{"id":2554},"为什么-format-12-是关键细节","为什么 \"format 12\" 是关键细节",[18,2557,2558,2559,900,2561,2564,2565,2568],{},"很多老的 Go PDF 库写于只关注 Latin 文本的年代，实现的是 cmap format 4 — Basic Multilingual Plane (U+0000–U+FFFF) 的分段范围。BMP 之外的日文 (不常见但有一些异体 Kanji) 需要 format 12。",[47,2560,1432],{},[47,2562,2563],{},"AddUTF8Font"," 在 NotoSansJP-Regular.ttf 上 ",[30,2566,2567],{},"panic","，因为 format 12 的解析器从来没写完。",[18,2570,2571],{},"这不是吐槽。这是历史遗物: gofpdf 在 2015 年对 Latin 为主的 Web 应用来说是一个优秀的库，fork 继承了它的范围。世界变了，CJK 从\"别人的问题\"变成\"日文和中文 Go 生态的多数问题\"。gpdf 实现了完整 cmap 规范，因为另一种选择是给\"品目\"渲染出豆腐方块的发票 — 这是公开发布第一周就收到的真实 bug 报告。",[1946,2573,2574],{"id":2574},"按字体数扩展的缓存",[18,2576,2577,2578,2581,2582,2584,2585,2588],{},"字体缓存按 ",[47,2579,2580],{},"Document"," 而不是全局。用同一字体生成 10,000 份 PDF，就要付 10,000 次 150 µs 的解析成本 — 除非跨文档共享 ",[47,2583,1766],{}," 实例，API 通过 ",[47,2586,2587],{},"gpdf.WithSharedFont(preloadedFont)"," 支持。",[18,2590,2591,2592,2595],{},"对高吞吐批量生成 (SaaS 的 ",[47,2593,2594],{},"gpdf-api"," 就是这样)，这个共享字体模式让 P95 延迟可预测。文档里有说明。OSS 用户多数不需要。",[14,2597,2598],{"id":2598},"三个决策叠加的效果",[18,2600,2601],{},"把三个决策拿到 100 页基准 (gpdf 683 µs, gofpdf 11.7 ms) 上:",[740,2603,2604,2617],{},[743,2605,2606],{},[746,2607,2608,2611,2614],{},[749,2609,2610],{},"时间去向",[749,2612,2613],{},"gofpdf (每页概估)",[749,2615,2616],{},"gpdf (每页概估)",[758,2618,2619,2630,2641,2652,2663],{},[746,2620,2621,2624,2627],{},[763,2622,2623],{},"操作切片构建",[763,2625,2626],{},"约 60 µs",[763,2628,2629],{},"0 (直接流)",[746,2631,2632,2635,2638],{},[763,2633,2634],{},"操作序列化",[763,2636,2637],{},"约 35 µs",[763,2639,2640],{},"0 (已写入)",[746,2642,2643,2646,2649],{},[763,2644,2645],{},"字形查找 (40 字符)",[763,2647,2648],{},"约 6 µs",[763,2650,2651],{},"约 0.4 µs",[746,2653,2654,2657,2660],{},[763,2655,2656],{},"分配 / GC 压力",[763,2658,2659],{},"约 20 µs",[763,2661,2662],{},"约 2 µs",[746,2664,2665,2670,2675],{},[763,2666,2667],{},[30,2668,2669],{},"合计",[763,2671,2672],{},[30,2673,2674],{},"约 120 µs",[763,2676,2677],{},[30,2678,2679],{},"约 7 µs",[18,2681,2682,2683,2686],{},"数字是从 profile 估的，实际分布看内容。但形状是对的。",[30,2684,2685],{},"三个里任何一个单独都赢不了 10 倍","。叠在一起才 10 倍。",[18,2688,2689],{},"推论: 把其中一个设计抄到已有库里能拿到 2–3 倍。想要 10 倍得三个都要，而且把第一个 (单遍) 改到基于 AST 的库里只能重写。",[14,2691,2693],{"id":2692},"放弃了什么-诚实那一节","放弃了什么 (诚实那一节)",[18,2695,2696],{},"之前一直绕着说。列全:",[18,2698,2699,2702,2703,2706],{},[30,2700,2701],{},"基于 AST 的后处理。"," 没有插件架构。没有\"走节点树应用变换\"。要在渲染前全局编辑整个文档的文本样式，在调用 Builder ",[30,2704,2705],{},"之前"," 做。",[18,2708,2709,2712,2713,2716],{},[30,2710,2711],{},"内省。"," 没有 ",[47,2714,2715],{},"doc.Components()"," 返回放进去的所有东西。任何有意义的方法能跑的时候，文档已经是操作符流了。多数用户用不上。写文档操作工具的少数用户用得上。",[18,2718,2719,2712,2722,2725,2726,2729],{},[30,2720,2721],{},"基于反射的序列化。",[47,2723,2724],{},"json.Unmarshal"," 风格把任意 struct 变成 PDF 的 API。JSON Schema 入口 (",[47,2727,2728],{},"template.FromJSON",") 明确支持的形状，故意的。要把通用 Go struct 喂进去得到 PDF，那是 unidoc 的领地。",[18,2731,2732,2735,2736,2738],{},[30,2733,2734],{},"interface 的扩展性。"," 不能实现 ",[47,2737,2115],{}," 注册自定义元素。可以写一个包裹 Builder 调用的辅助函数。实用上能覆盖 95% 需求，但模型不同。",[18,2740,2741],{},"都是刻意的。任何一个采纳都会让速度死掉。我们选择优先服务\"快而有主见\"受益的那桶用户，\"灵活和插件丰富\"那桶用户 Maroto v2 或 unidoc 更合适。",[14,2743,2744],{"id":2744},"能复现基准吗",[18,2746,2747],{},"能。公开代码的全部意义就是这个。",[59,2749,2751],{"className":1248,"code":2750,"language":1250,"meta":64,"style":64},"git clone https://github.com/gpdf-dev/gpdf\ncd gpdf/_benchmark\ngo test -bench=. -benchmem -benchtime=5s\n",[47,2752,2753,2764,2772],{"__ignoreMap":64},[68,2754,2755,2758,2761],{"class":70,"line":71},[68,2756,2757],{"class":78},"git",[68,2759,2760],{"class":214}," clone",[68,2762,2763],{"class":214}," https://github.com/gpdf-dev/gpdf\n",[68,2765,2766,2769],{"class":70,"line":82},[68,2767,2768],{"class":173},"cd",[68,2770,2771],{"class":214}," gpdf/_benchmark\n",[68,2773,2774,2776,2779,2782,2785],{"class":70,"line":89},[68,2775,63],{"class":78},[68,2777,2778],{"class":214}," test",[68,2780,2781],{"class":214}," -bench=.",[68,2783,2784],{"class":214}," -benchmem",[68,2786,2787],{"class":214}," -benchtime=5s\n",[18,2789,2790,2791,2796],{},"那个目录的 README 讲了四个工作负载和度量什么。在同架构、同 Go 版本下差距超过 20%，",[22,2792,2795],{"href":2793,"rel":2794},"https://github.com/gpdf-dev/gpdf/issues",[26],"提 issue"," — drift 真实存在。",[18,2798,2799],{},"两个补充:",[1111,2801,2802,2808],{},[885,2803,2804,2805,2807],{},"基准带 ",[47,2806,1414],{},"。去掉能全面提升约 5%，但那不是真实代码的跑法，所以不写进公开数字。",[885,2809,2810],{},"关 CGO。有人问 CGO 挂 FreeType 做字体操作会不会更快，测过，FFI 边界的 marshal 代价盖过收益。对 PDF 生成器的访问模式，纯 Go 子集器赢。",[14,2812,2814],{"id":2813},"faq","FAQ",[18,2816,2817,2820,2821,720],{},[30,2818,2819],{},"为什么要和已归档的 gofpdf 比?","\n因为它还是 GitHub 搜 \"go pdf\" 的第一名，落到 gpdf 的团队多数是从那迁来的。基准需要回答这波人\"值不值得迁\"。简版: 值得，还写了 ",[22,2822,2824],{"href":2823},"/zh/blog/gofpdf-migration","迁移指南",[18,2826,2827,2830],{},[30,2828,2829],{},"PDF 生成 10 倍快真有意义吗?","\n看工作负载。单请求单文档 — 其实没差，两边都过\"请求路径内生成\"的门槛。批处理 (夜间账单、大批量发票、从 DB 查询生成报表)，差距直接对应机器数变少。第一个迁批处理流水线的团队反馈\"worker 数变成十分之一\"，没审他们的算账，但和基准的形状一致。",[18,2832,2833,2836,2837,2840,2841,2843],{},[30,2834,2835],{},"CJK 那个数字的陷阱?","\n字体文件你得自己带。gpdf 帮你做子集，但 3 MB 的 NotoSansJP TTF 是 3 MB，要嘛编进 Go 二进制要嘛启动时 ",[47,2838,2839],{},"os.ReadFile","。在 distroless 镜像里这会影响。SaaS ",[47,2842,2594],{}," 通过在镜像里带常用字体解决。OSS 用户自理。",[18,2845,2846,2849],{},[30,2847,2848],{},"加功能会不会变慢?","\n我们最在意这问题。答: 每个 release 都和上一版跑基准，四个负载任意一个退化超过 5% 就卡 release。基准和库在同一仓库，原因就在此。",[18,2851,2852,2855],{},[30,2853,2854],{},"名字哪来的?","\ngpdf = Go + PDF。没有巧思。刻意简单。",[14,2857,2859],{"id":2858},"试用-gpdf","试用 gpdf",[18,2861,2862],{},"gpdf 是 Go 的 PDF 生成库。MIT、零依赖、原生 CJK。",[59,2864,2865],{"className":1248,"code":1249,"language":1250,"meta":64,"style":64},[47,2866,2867],{"__ignoreMap":64},[68,2868,2869,2871,2873],{"class":70,"line":71},[68,2870,63],{"class":78},[68,2872,1259],{"class":214},[68,2874,1262],{"class":214},[18,2876,2877,1269,2881],{},[22,2878,2880],{"href":24,"rel":2879},[26],"⭐ Star on GitHub",[22,2882,2884],{"href":1272,"rel":2883},[26],"文档",[14,2886,2887],{"id":2887},"下一步阅读",[1111,2889,2890,2897,2903],{},[885,2891,2892,2896],{},[22,2893,2895],{"href":2894},"/zh/blog/go-pdf-library-showdown-2026","2026 年 Go PDF 库横评"," — 含许可证和依赖的完整库比较。",[885,2898,2899,2902],{},[22,2900,2901],{"href":2823},"gofpdf 已归档，如何迁移到 gpdf"," — 五组 Before/After API 对照，全部可运行。",[885,2904,2905,2906,720],{},"基准代码: ",[22,2907,2909],{"href":1392,"rel":2908},[26],[47,2910,1396],{},[1276,2912,2913],{},"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":64,"searchDepth":82,"depth":82,"links":2915},[2916,2917,2918,2921,2925,2929,2930,2931,2932,2933,2934],{"id":1335,"depth":82,"text":1336},{"id":1407,"depth":82,"text":1408},{"id":1551,"depth":82,"text":1552,"children":2919},[2920],{"id":1948,"depth":89,"text":1948},{"id":1994,"depth":82,"text":1995,"children":2922},[2923,2924],{"id":2304,"depth":89,"text":2304},{"id":2342,"depth":89,"text":2342},{"id":2362,"depth":82,"text":2363,"children":2926},[2927,2928],{"id":2554,"depth":89,"text":2555},{"id":2574,"depth":89,"text":2574},{"id":2598,"depth":82,"text":2598},{"id":2692,"depth":82,"text":2693},{"id":2744,"depth":82,"text":2744},{"id":2813,"depth":82,"text":2814},{"id":2858,"depth":82,"text":2859},{"id":2887,"depth":82,"text":2887},"单页 13 µs，100 页报告 683 µs。不是调参，而是三个架构决策的叠加。本文走一遍代码路径。",{},"/zh/blog/why-gpdf-is-faster",{"title":1329,"description":2935},"zh/blog/011.why-gpdf-is-faster",[2941,2942,2943],"benchmark","internals","comparison","r8Xboa3Vyt-wlvC53aR8syMVr3oVZ-tUJGXgfqGmRXs",{"id":2946,"title":2947,"author":2948,"body":2949,"date":4006,"description":4007,"draft":1293,"extension":1294,"howTo":4008,"image":1317,"meta":4028,"navigation":85,"path":1236,"seo":4029,"stem":4030,"tags":4031,"updated":1317,"__hash__":4033},"blogZh/zh/blog/008.tofu-boxes-japanese.md","为什么 gpdf 生成的 PDF 中日文显示为方块（豆腐字）？",{"name":8,"url":9},{"type":11,"value":2950,"toc":3994},[2951,2953,2956,2958,2961,2964,3020,3024,3027,3496,3509,3519,3522,3525,3541,3560,3566,3570,3577,3718,3751,3755,3762,3765,3768,3796,3799,3803,3809,3873,3884,3887,3920,3923,3939,3941,3967,3969,3971,3983,3991],[14,2952,16],{"id":16},[18,2954,2955],{},"我用 gpdf 写了日文，输出的 PDF 里那些字都变成了空方块。这是什么，怎么让真的日文字形出现在文件里？",[14,2957,36],{"id":36},[18,2959,2960],{},"这就是豆腐字（tofu）——PDF 查看器在嵌入的字体里找不到对应 Unicode 码位的字形时，会画一个占位矩形。原因有 4 种，其中一种远比其余的常见。",[18,2962,2963],{},"按频率排序：",[882,2965,2966,2979,2996,3010],{},[885,2967,2968,2971,2972,2975,2976,2978],{},[30,2969,2970],{},"没有注册 CJK 字体。"," ",[47,2973,2974],{},"gpdf.NewDocument"," 里没有 ",[47,2977,353],{}," 调用，所以文档回落到 PDF Base-14 字体（Helvetica、Times、Courier）。它们都不覆盖 U+3040–U+9FFF。",[885,2980,2981,2971,2988,2991,2992,2995],{},[30,2982,2983,2984,2987],{},"注册了 CJK 字体，但 ",[47,2985,2986],{},"c.Text"," 的族名不对。",[47,2989,2990],{},"WithFont(\"NotoSansJP\", ...)"," 设好了，但文本上写着 ",[47,2993,2994],{},"template.FontFamily(\"Arial\")","，于是 gpdf 在一个 Latin 字体里查日文字形。",[885,2997,2998,3001,3002,3005,3006,3009],{},[30,2999,3000],{},"字体文件本身没有 CJK 字形。"," 磁盘上的 TTF 是 Latin 子集（",[47,3003,3004],{},"NotoSans-Regular.ttf","，不是 ",[47,3007,3008],{},"NotoSansJP-Regular.ttf","）。文件名看起来对，覆盖却是空的。",[885,3011,3012,3015,3016,3019],{},[30,3013,3014],{},"字节在到达 gpdf 之前就坏了。"," 字符串在上游被当成 Shift-JIS 或 Latin-1 解码过，你想渲染的已经不再是日文码位。如果看到的是 ",[47,3017,3018],{},"縺ゅ→縺"," 而不是方块，属于这一种。",[14,3021,3023],{"id":3022},"原因-1-的标准修复","原因 #1 的标准修复",[18,3025,3026],{},"十有八九是这个：",[59,3028,3030],{"className":61,"code":3029,"language":63,"meta":64,"style":64},"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",[47,3031,3032,3038,3042,3048,3056,3064,3068,3076,3084,3092,3096,3100,3110,3136,3148,3162,3166,3170,3184,3202,3232,3255,3278,3282,3286,3300,3324,3354,3373,3377,3381,3385,3403,3415,3429,3433,3474,3488,3492],{"__ignoreMap":64},[68,3033,3034,3036],{"class":70,"line":71},[68,3035,75],{"class":74},[68,3037,79],{"class":78},[68,3039,3040],{"class":70,"line":82},[68,3041,86],{"emptyLinePlaceholder":85},[68,3043,3044,3046],{"class":70,"line":89},[68,3045,93],{"class":92},[68,3047,96],{"class":74},[68,3049,3050,3052,3054],{"class":70,"line":99},[68,3051,102],{"class":74},[68,3053,105],{"class":78},[68,3055,108],{"class":74},[68,3057,3058,3060,3062],{"class":70,"line":111},[68,3059,102],{"class":74},[68,3061,116],{"class":78},[68,3063,108],{"class":74},[68,3065,3066],{"class":70,"line":121},[68,3067,86],{"emptyLinePlaceholder":85},[68,3069,3070,3072,3074],{"class":70,"line":126},[68,3071,102],{"class":74},[68,3073,131],{"class":78},[68,3075,108],{"class":74},[68,3077,3078,3080,3082],{"class":70,"line":136},[68,3079,102],{"class":74},[68,3081,141],{"class":78},[68,3083,108],{"class":74},[68,3085,3086,3088,3090],{"class":70,"line":146},[68,3087,102],{"class":74},[68,3089,151],{"class":78},[68,3091,108],{"class":74},[68,3093,3094],{"class":70,"line":156},[68,3095,159],{"class":74},[68,3097,3098],{"class":70,"line":162},[68,3099,86],{"emptyLinePlaceholder":85},[68,3101,3102,3104,3106,3108],{"class":70,"line":167},[68,3103,170],{"class":74},[68,3105,174],{"class":173},[68,3107,177],{"class":74},[68,3109,180],{"class":74},[68,3111,3112,3114,3116,3118,3120,3122,3124,3126,3128,3130,3132,3134],{"class":70,"line":183},[68,3113,187],{"class":186},[68,3115,190],{"class":74},[68,3117,193],{"class":186},[68,3119,196],{"class":74},[68,3121,199],{"class":186},[68,3123,202],{"class":74},[68,3125,205],{"class":173},[68,3127,208],{"class":74},[68,3129,211],{"class":74},[68,3131,3008],{"class":214},[68,3133,211],{"class":74},[68,3135,159],{"class":74},[68,3137,3138,3140,3142,3144,3146],{"class":70,"line":221},[68,3139,224],{"class":92},[68,3141,193],{"class":186},[68,3143,229],{"class":74},[68,3145,232],{"class":74},[68,3147,180],{"class":74},[68,3149,3150,3152,3154,3156,3158,3160],{"class":70,"line":237},[68,3151,240],{"class":186},[68,3153,202],{"class":74},[68,3155,245],{"class":173},[68,3157,208],{"class":74},[68,3159,250],{"class":186},[68,3161,159],{"class":74},[68,3163,3164],{"class":70,"line":255},[68,3165,258],{"class":74},[68,3167,3168],{"class":70,"line":261},[68,3169,86],{"emptyLinePlaceholder":85},[68,3171,3172,3174,3176,3178,3180,3182],{"class":70,"line":266},[68,3173,269],{"class":186},[68,3175,196],{"class":74},[68,3177,274],{"class":186},[68,3179,202],{"class":74},[68,3181,279],{"class":173},[68,3183,282],{"class":74},[68,3185,3186,3188,3190,3192,3194,3196,3198,3200],{"class":70,"line":285},[68,3187,288],{"class":186},[68,3189,202],{"class":74},[68,3191,293],{"class":173},[68,3193,208],{"class":74},[68,3195,27],{"class":186},[68,3197,202],{"class":74},[68,3199,302],{"class":186},[68,3201,305],{"class":74},[68,3203,3204,3206,3208,3210,3212,3214,3216,3218,3220,3222,3224,3226,3228,3230],{"class":70,"line":308},[68,3205,288],{"class":186},[68,3207,202],{"class":74},[68,3209,315],{"class":173},[68,3211,208],{"class":74},[68,3213,320],{"class":186},[68,3215,202],{"class":74},[68,3217,325],{"class":173},[68,3219,208],{"class":74},[68,3221,320],{"class":186},[68,3223,202],{"class":74},[68,3225,334],{"class":173},[68,3227,208],{"class":74},[68,3229,340],{"class":339},[68,3231,343],{"class":74},[68,3233,3234,3236,3238,3240,3242,3244,3247,3249,3251,3253],{"class":70,"line":346},[68,3235,288],{"class":186},[68,3237,202],{"class":74},[68,3239,353],{"class":173},[68,3241,208],{"class":74},[68,3243,211],{"class":74},[68,3245,3246],{"class":214},"NotoSansJP",[68,3248,211],{"class":74},[68,3250,190],{"class":74},[68,3252,367],{"class":186},[68,3254,305],{"class":74},[68,3256,3257,3259,3261,3263,3265,3267,3269,3271,3273,3276],{"class":70,"line":372},[68,3258,288],{"class":186},[68,3260,202],{"class":74},[68,3262,379],{"class":173},[68,3264,208],{"class":74},[68,3266,211],{"class":74},[68,3268,3246],{"class":214},[68,3270,211],{"class":74},[68,3272,190],{"class":74},[68,3274,3275],{"class":339}," 12",[68,3277,305],{"class":74},[68,3279,3280],{"class":70,"line":397},[68,3281,400],{"class":74},[68,3283,3284],{"class":70,"line":403},[68,3285,86],{"emptyLinePlaceholder":85},[68,3287,3288,3290,3292,3294,3296,3298],{"class":70,"line":408},[68,3289,411],{"class":186},[68,3291,196],{"class":74},[68,3293,416],{"class":186},[68,3295,202],{"class":74},[68,3297,421],{"class":173},[68,3299,424],{"class":74},[68,3301,3302,3304,3306,3308,3310,3312,3314,3316,3318,3320,3322],{"class":70,"line":427},[68,3303,430],{"class":186},[68,3305,202],{"class":74},[68,3307,435],{"class":173},[68,3309,438],{"class":74},[68,3311,442],{"class":441},[68,3313,445],{"class":74},[68,3315,448],{"class":78},[68,3317,202],{"class":74},[68,3319,453],{"class":78},[68,3321,456],{"class":74},[68,3323,180],{"class":74},[68,3325,3326,3328,3330,3332,3334,3336,3338,3340,3342,3344,3346,3348,3350,3352],{"class":70,"line":461},[68,3327,464],{"class":186},[68,3329,202],{"class":74},[68,3331,469],{"class":173},[68,3333,208],{"class":74},[68,3335,474],{"class":339},[68,3337,190],{"class":74},[68,3339,479],{"class":74},[68,3341,482],{"class":441},[68,3343,445],{"class":74},[68,3345,448],{"class":78},[68,3347,202],{"class":74},[68,3349,491],{"class":78},[68,3351,456],{"class":74},[68,3353,180],{"class":74},[68,3355,3356,3358,3360,3362,3364,3366,3369,3371],{"class":70,"line":498},[68,3357,501],{"class":186},[68,3359,202],{"class":74},[68,3361,506],{"class":173},[68,3363,208],{"class":74},[68,3365,211],{"class":74},[68,3367,3368],{"class":214},"こんにちは、世界。",[68,3370,211],{"class":74},[68,3372,159],{"class":74},[68,3374,3375],{"class":70,"line":546},[68,3376,569],{"class":74},[68,3378,3379],{"class":70,"line":566},[68,3380,575],{"class":74},[68,3382,3383],{"class":70,"line":572},[68,3384,86],{"emptyLinePlaceholder":85},[68,3386,3387,3389,3391,3393,3395,3397,3399,3401],{"class":70,"line":578},[68,3388,586],{"class":186},[68,3390,190],{"class":74},[68,3392,193],{"class":186},[68,3394,196],{"class":74},[68,3396,416],{"class":186},[68,3398,202],{"class":74},[68,3400,599],{"class":173},[68,3402,424],{"class":74},[68,3404,3405,3407,3409,3411,3413],{"class":70,"line":583},[68,3406,224],{"class":92},[68,3408,193],{"class":186},[68,3410,229],{"class":74},[68,3412,232],{"class":74},[68,3414,180],{"class":74},[68,3416,3417,3419,3421,3423,3425,3427],{"class":70,"line":604},[68,3418,240],{"class":186},[68,3420,202],{"class":74},[68,3422,245],{"class":173},[68,3424,208],{"class":74},[68,3426,250],{"class":186},[68,3428,159],{"class":74},[68,3430,3431],{"class":70,"line":617},[68,3432,258],{"class":74},[68,3434,3435,3437,3439,3441,3443,3445,3447,3449,3451,3454,3456,3458,3460,3462,3464,3466,3468,3470,3472],{"class":70,"line":632},[68,3436,224],{"class":92},[68,3438,193],{"class":186},[68,3440,196],{"class":74},[68,3442,199],{"class":186},[68,3444,202],{"class":74},[68,3446,650],{"class":173},[68,3448,208],{"class":74},[68,3450,211],{"class":74},[68,3452,3453],{"class":214},"hello.pdf",[68,3455,211],{"class":74},[68,3457,190],{"class":74},[68,3459,664],{"class":186},[68,3461,190],{"class":74},[68,3463,669],{"class":339},[68,3465,672],{"class":74},[68,3467,193],{"class":186},[68,3469,229],{"class":74},[68,3471,232],{"class":74},[68,3473,180],{"class":74},[68,3475,3476,3478,3480,3482,3484,3486],{"class":70,"line":637},[68,3477,240],{"class":186},[68,3479,202],{"class":74},[68,3481,245],{"class":173},[68,3483,208],{"class":74},[68,3485,250],{"class":186},[68,3487,159],{"class":74},[68,3489,3490],{"class":70,"line":683},[68,3491,258],{"class":74},[68,3493,3494],{"class":70,"line":698},[68,3495,706],{"class":74},[18,3497,3498,3499,3501,3502,3505,3506,3508],{},"两行完成字体注册与默认设置。不依赖 CGO，不需要 ",[47,3500,2563],{}," 的那套簿记。如果之前看到 ",[47,3503,3504],{},"□□□□□、□□。","，现在把上面这段代码和真实的 ",[47,3507,3008],{}," 放一起跑，会出真的字形。",[18,3510,39,3511,3516,3517,720],{},[22,3512,3515],{"href":3513,"rel":3514},"https://fonts.google.com/noto/specimen/Noto+Sans+JP",[26],"Google Fonts"," 下载 ",[47,3518,3008],{},[14,3520,3521],{"id":3521},"怎么判断是哪种原因",[18,3523,3524],{},"要盯三个地方：文档构造处、写文本的地方、以及 TTF 文件本身。",[18,3526,3527,3533,3534,3537,3538,3540],{},[30,3528,3529,3530],{},"输出是整齐的 ",[47,3531,3532],{},"□□□","（所有矩形一模一样）：原因 1、2 或 3。PDF 里确实嵌入了一个字体，但它没有那些字形。在 Acrobat 中打开 PDF，进入 ",[47,3535,3536],{},"文件 → 属性 → 字体","，看实际嵌入的是什么。如果只有 Helvetica / Times / Courier，就是原因 1；如果 ",[47,3539,3246],{}," 已经列出来但还是方块，就是原因 2 或 3。",[18,3542,3543,3552,3553,3555,3556,3559],{},[30,3544,3545,3546,3548,3549],{},"输出像 ",[47,3547,3018],{}," 或 ",[47,3550,3551],{},"ã\"ã‚\"ã«ã¡ã¯"," 这种乱码：原因 4。日文字符串在到达 gpdf 之前已经被重新编码过了。常见元凶：Excel 保存的 Shift-JIS CSV 被 ",[47,3554,2839],{}," 直接当成 UTF-8 读入，或者一个 HTTP 接口没有声明 ",[47,3557,3558],{},"charset=utf-8","。修的是解码器，不是 PDF。",[18,3561,3562,3565],{},[30,3563,3564],{},"部分字形正常，部分是方块","：字体覆盖不完整。号称\"日文支持\"的字体可能只有假名和常用汉字，而跳过 鬱、龠 这类罕用字。切换到 Noto Sans JP（覆盖 JIS X 0213）或 Source Han Sans JP 即可。",[14,3567,3569],{"id":3568},"原因-2-细节字体对了族名错了","原因 2 细节：字体对了，族名错了",[18,3571,3572,3573,3576],{},"这种最隐蔽，因为字体",[30,3574,3575],{},"的确","被嵌入了，只是没被用。最小复现：",[59,3578,3580],{"className":61,"code":3579,"language":63,"meta":64,"style":64},"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",[47,3581,3582,3596,3618,3623,3627,3631,3655,3686,3709,3713],{"__ignoreMap":64},[68,3583,3584,3586,3588,3590,3592,3594],{"class":70,"line":71},[68,3585,1002],{"class":186},[68,3587,196],{"class":74},[68,3589,274],{"class":186},[68,3591,202],{"class":74},[68,3593,279],{"class":173},[68,3595,282],{"class":74},[68,3597,3598,3600,3602,3604,3606,3608,3610,3612,3614,3616],{"class":70,"line":82},[68,3599,1017],{"class":186},[68,3601,202],{"class":74},[68,3603,353],{"class":173},[68,3605,208],{"class":74},[68,3607,211],{"class":74},[68,3609,3246],{"class":214},[68,3611,211],{"class":74},[68,3613,190],{"class":74},[68,3615,367],{"class":186},[68,3617,305],{"class":74},[68,3619,3620],{"class":70,"line":89},[68,3621,3622],{"class":2206},"    // 漏了 WithDefaultFont\n",[68,3624,3625],{"class":70,"line":99},[68,3626,159],{"class":74},[68,3628,3629],{"class":70,"line":111},[68,3630,86],{"emptyLinePlaceholder":85},[68,3632,3633,3635,3637,3639,3641,3643,3645,3647,3649,3651,3653],{"class":70,"line":121},[68,3634,1924],{"class":186},[68,3636,202],{"class":74},[68,3638,435],{"class":173},[68,3640,438],{"class":74},[68,3642,442],{"class":441},[68,3644,445],{"class":74},[68,3646,448],{"class":78},[68,3648,202],{"class":74},[68,3650,453],{"class":78},[68,3652,456],{"class":74},[68,3654,180],{"class":74},[68,3656,3657,3660,3662,3664,3666,3668,3670,3672,3674,3676,3678,3680,3682,3684],{"class":70,"line":126},[68,3658,3659],{"class":186},"    r",[68,3661,202],{"class":74},[68,3663,469],{"class":173},[68,3665,208],{"class":74},[68,3667,474],{"class":339},[68,3669,190],{"class":74},[68,3671,479],{"class":74},[68,3673,482],{"class":441},[68,3675,445],{"class":74},[68,3677,448],{"class":78},[68,3679,202],{"class":74},[68,3681,491],{"class":78},[68,3683,456],{"class":74},[68,3685,180],{"class":74},[68,3687,3688,3691,3693,3695,3697,3699,3702,3704,3706],{"class":70,"line":136},[68,3689,3690],{"class":186},"        c",[68,3692,202],{"class":74},[68,3694,506],{"class":173},[68,3696,208],{"class":74},[68,3698,211],{"class":74},[68,3700,3701],{"class":214},"こんにちは",[68,3703,211],{"class":74},[68,3705,456],{"class":74},[68,3707,3708],{"class":2206}," // 用的是默认字体 Helvetica\n",[68,3710,3711],{"class":70,"line":146},[68,3712,575],{"class":74},[68,3714,3715],{"class":70,"line":156},[68,3716,3717],{"class":74},"})\n",[18,3719,3720,3721,3723,3724,3727,3728,3730,3731,720,3734,3736,3737,3739,3740,3743,3744,3746,3747,3750],{},"修复：要么在 ",[47,3722,279],{}," 里加 ",[47,3725,3726],{},"gpdf.WithDefaultFont(\"NotoSansJP\", 12)","，要么在每个要渲染日文的 ",[47,3729,2986],{}," 上都传 ",[47,3732,3733],{},"template.FontFamily(\"NotoSansJP\")",[47,3735,353],{}," 里的族名和 ",[47,3738,2986],{}," 里的族名必须",[30,3741,3742],{},"完全一致","，包括大小写。",[47,3745,3246],{}," 和 ",[47,3748,3749],{},"notosansjp"," 在 gpdf 看来是两个不同的字体。",[14,3752,3754],{"id":3753},"原因-3-细节拿错了-ttf-文件","原因 3 细节：拿错了 TTF 文件",[18,3756,3757,3746,3759,3761],{},[47,3758,3004],{},[47,3760,3008],{}," 是两个不同的文件。前者是 Latin 字体，CJK 覆盖为零；后者是日文版，约 17,000 个字形。目录列表里看起来几乎一样，自动补全很容易补成错的那个。",[18,3763,3764],{},"gpdf 在注册字体时不做字形覆盖校验——你给它字节，它就相信你。失败只会在渲染时以豆腐字的形式暴露。",[18,3766,3767],{},"最快的检查方法：",[1111,3769,3770,3777,3784],{},[885,3771,3772,3773,3776],{},"macOS：",[47,3774,3775],{},"字体册"," 里双击文件，能看到字形网格",[885,3778,3779,3780,3783],{},"Linux：",[47,3781,3782],{},"otfinfo -u NotoSans-Regular.ttf"," 打印 Unicode 覆盖",[885,3785,3786,3787,900,3792,3795],{},"跨平台：",[22,3788,3791],{"href":3789,"rel":3790},"https://github.com/fonttools/fonttools",[26],"fontTools",[47,3793,3794],{},"ttx -t cmap NotoSans-Regular.ttf"," 把 cmap 表导出为 XML",[18,3797,3798],{},"如果列表里没有 U+3042（あ），手上拿的就是 Latin 子集。",[14,3800,3802],{"id":3801},"原因-4-细节编码损坏","原因 4 细节：编码损坏",[18,3804,3805,3806,3808],{},"这其实和 gpdf 无关。传给 ",[47,3807,2986],{}," 的字符串在更早的时候就已经坏了。渲染前先打印：",[59,3810,3812],{"className":61,"code":3811,"language":63,"meta":64,"style":64},"text := loadLabelFromSomewhere()\nfmt.Printf(\"%q\\n\", text) // 打印实际 rune\nc.Text(text)\n",[47,3813,3814,3826,3859],{"__ignoreMap":64},[68,3815,3816,3819,3821,3824],{"class":70,"line":71},[68,3817,3818],{"class":186},"text ",[68,3820,196],{"class":74},[68,3822,3823],{"class":173}," loadLabelFromSomewhere",[68,3825,424],{"class":74},[68,3827,3828,3831,3833,3836,3838,3840,3844,3847,3849,3851,3854,3856],{"class":70,"line":82},[68,3829,3830],{"class":186},"fmt",[68,3832,202],{"class":74},[68,3834,3835],{"class":173},"Printf",[68,3837,208],{"class":74},[68,3839,211],{"class":74},[68,3841,3843],{"class":3842},"swJcz","%q",[68,3845,3846],{"class":186},"\\n",[68,3848,211],{"class":74},[68,3850,190],{"class":74},[68,3852,3853],{"class":186}," text",[68,3855,456],{"class":74},[68,3857,3858],{"class":2206}," // 打印实际 rune\n",[68,3860,3861,3863,3865,3867,3869,3871],{"class":70,"line":89},[68,3862,482],{"class":186},[68,3864,202],{"class":74},[68,3866,506],{"class":173},[68,3868,208],{"class":74},[68,3870,926],{"class":186},[68,3872,159],{"class":74},[18,3874,3875,3876,3879,3880,3883],{},"如果这里输出 ",[47,3877,3878],{},"\"縺ゅ→縺\""," 而不是 ",[47,3881,3882],{},"\"あいうえ\"","，损坏发生在上游。gpdf 修不了——去找 UTF-8 被错误解码的那一步。",[18,3885,3886],{},"常见上游元凶：",[1111,3888,3889,3899,3913],{},[885,3890,3891,3892,3894,3895,3898],{},"用 ",[47,3893,2839],{}," 读 Excel 导出的 Shift-JIS CSV，然后直接 ",[47,3896,3897],{},"string(data)"," 转换",[885,3900,3901,3902,3548,3905,3908,3909,3912],{},"数据库字段定义为 ",[47,3903,3904],{},"latin1",[47,3906,3907],{},"utf8mb3","（而不是 ",[47,3910,3911],{},"utf8mb4","），存的本来就是乱码",[885,3914,3915,3916,3919],{},"HTTP 响应没有 ",[47,3917,3918],{},"Content-Type: application/json; charset=utf-8","，客户端猜成了 Latin-1",[14,3921,3922],{"id":3922},"一个容易忽略的边界情况",[18,3924,3925,3926,3929,3930,3932,3933,3936,3937,720],{},"gpdf 会自动做字形子集化，时机是 ",[47,3927,3928],{},"Generate()"," 被调用的那一刻。如果你在文档构造中先渲染 ",[47,3931,3701],{},"，后渲染 ",[47,3934,3935],{},"鬱陶しい","，第二次也会被正确加入子集。但如果生成完 PDF 再用 Acrobat 打开、手动输入一个原本没有出现过的汉字，那个字就会变成豆腐——子集已经冻结了。不要后编辑 PDF，回到 Go 里重新 ",[47,3938,3928],{},[14,3940,1209],{"id":1209},[1111,3942,3943,3951,3960],{},[885,3944,3945,3947,3948,3950],{},[22,3946,1223],{"href":1222}," —— ",[47,3949,353],{}," 的完整走查，含粗体/斜体变体与多 CJK 文档",[885,3952,3953,3955,3956,3959],{},[22,3954,1216],{"href":833}," —— 该选哪个 Noto 文件，以及用 ",[47,3957,3958],{},"go:embed"," 简化分发",[885,3961,3962,3966],{},[22,3963,3965],{"href":3964},"/zh/blog/japanese-pdf-in-go","Go 日文 PDF 决定版指南（2026）"," —— 字体、竖排、ruby 及日文特有排版的长篇指南",[14,3968,1242],{"id":1241},[18,3970,1245],{},[59,3972,3973],{"className":1248,"code":1249,"language":1250,"meta":64,"style":64},[47,3974,3975],{"__ignoreMap":64},[68,3976,3977,3979,3981],{"class":70,"line":71},[68,3978,63],{"class":78},[68,3980,1259],{"class":214},[68,3982,1262],{"class":214},[18,3984,3985,1269,3988],{},[22,3986,1268],{"href":24,"rel":3987},[26],[22,3989,1274],{"href":1272,"rel":3990},[26],[1276,3992,3993],{},"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":64,"searchDepth":82,"depth":82,"links":3995},[3996,3997,3998,3999,4000,4001,4002,4003,4004,4005],{"id":16,"depth":82,"text":16},{"id":36,"depth":82,"text":36},{"id":3022,"depth":82,"text":3023},{"id":3521,"depth":82,"text":3521},{"id":3568,"depth":82,"text":3569},{"id":3753,"depth":82,"text":3754},{"id":3801,"depth":82,"text":3802},{"id":3922,"depth":82,"text":3922},{"id":1209,"depth":82,"text":1209},{"id":1241,"depth":82,"text":1242},"2026-04-17","日文字符变成空方块（□），通常是字体未注册或族名不匹配。整理 4 种常见原因和最快的修复路径。",{"name":4009,"totalTime":1297,"tools":4010,"steps":4012},"定位并修复 gpdf 文档中的豆腐字",[1299,4011],"支持 CJK 的 TTF（如 NotoSansJP-Regular.ttf）",[4013,4016,4019,4022,4025],{"name":4014,"text":4015},"先区分是豆腐字还是 mojibake","打开 PDF。日文显示为空方块（□）表示字体查找失败；显示为 縺ゅ→縺 这种乱码则说明 UTF-8 在到达 gpdf 之前就被错误解码了。两种问题解法完全不同。",{"name":4017,"text":4018},"检查是否注册了 CJK 字体","在文档构造处搜索 gpdf.WithFont。如果没有注册任何 CJK TTF，gpdf 会回落到 PDF Base-14 字体，而这些字体不覆盖 CJK 码位。",{"name":4020,"text":4021},"核对每个 c.Text 的字体族名","如果没有 WithDefaultFont，每个渲染日文的 c.Text 都需要显式传 template.FontFamily(\"NotoSansJP\")。族名不一致会静默回落到默认字体。",{"name":4023,"text":4024},"确认 TTF 文件本身含有 CJK 字形","NotoSans-Regular.ttf（Latin 子集）和 NotoSansJP-Regular.ttf 是两个不同的文件。gpdf 在注册时不会校验字形覆盖。",{"name":4026,"text":4027},"在两个查看器中重新验证","在 Adobe Acrobat 和 Chrome 中分别打开生成的 PDF。两者都正常才算通过。只在其中一个正常，说明字形嵌入了但子集注册有偏差。",{},{"title":2947,"description":4007},"zh/blog/008.tofu-boxes-japanese",[1323,4032,1324],"troubleshooting","HMNxWGP0gjjUxtKru9BRqi2g3l-6AdCGPuzL6Ggf5e0",{"id":4035,"title":4036,"author":4037,"body":4038,"date":4006,"description":5244,"draft":1293,"extension":1294,"howTo":5245,"image":1317,"meta":5265,"navigation":85,"path":1229,"seo":5266,"stem":5267,"tags":5268,"updated":1317,"__hash__":5269},"blogZh/zh/blog/009.ipaex-gothic-gpdf.md","如何在 gpdf 中使用 IPAex 哥特体（IPAex Gothic）？",{"name":8,"url":9},{"type":11,"value":4039,"toc":5233},[4040,4042,4054,4056,4069,4071,4600,4619,4622,4625,4681,4684,4696,4700,4703,4706,4714,4746,4752,5035,5038,5042,5051,5077,5084,5087,5091,5175,5178,5180,5207,5209,5211,5223,5231],[14,4041,16],{"id":16},[18,4043,20,4044,4047,4048,4053],{},[22,4045,27],{"href":24,"rel":4046},[26]," 文档里用 IPAex Gothic —— 日本 ",[22,4049,4052],{"href":4050,"rel":4051},"https://moji.or.jp/ipafont/",[26],"情报处理推进机构","（IPA）维护的比例西文哥特体。常见场景：e-Tax 的 PDF 附件、面向政府的书面文件，或者自 2010 年前后就统一使用 IPAex 的内部样式。三处容易踩坑：该拿哪个文件、没有 Bold 怎么办、IPA Font License 到底要求你做什么。",[14,4055,36],{"id":36},[18,4057,3891,4058,4061,4062,4065,4066,4068],{},[47,4059,4060],{},"gpdf.WithFont(\"IPAexGothic\", bytes)"," 注册 ",[47,4063,4064],{},"ipaexg.ttf","，设为默认字体。粗体强调要么用 ",[47,4067,1095],{}," 合成，要么与 IPAex Mincho 配对，因为 IPAex 只发行 Regular。随字体一起保留许可证文本。",[14,4070,57],{"id":57},[59,4072,4074],{"className":61,"code":4073,"language":63,"meta":64,"style":64},"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",[47,4075,4076,4082,4086,4092,4100,4108,4112,4120,4128,4136,4140,4144,4154,4180,4192,4206,4210,4214,4228,4246,4277,4300,4323,4327,4331,4345,4369,4399,4438,4457,4476,4480,4484,4488,4506,4518,4532,4536,4577,4591,4595],{"__ignoreMap":64},[68,4077,4078,4080],{"class":70,"line":71},[68,4079,75],{"class":74},[68,4081,79],{"class":78},[68,4083,4084],{"class":70,"line":82},[68,4085,86],{"emptyLinePlaceholder":85},[68,4087,4088,4090],{"class":70,"line":89},[68,4089,93],{"class":92},[68,4091,96],{"class":74},[68,4093,4094,4096,4098],{"class":70,"line":99},[68,4095,102],{"class":74},[68,4097,105],{"class":78},[68,4099,108],{"class":74},[68,4101,4102,4104,4106],{"class":70,"line":111},[68,4103,102],{"class":74},[68,4105,116],{"class":78},[68,4107,108],{"class":74},[68,4109,4110],{"class":70,"line":121},[68,4111,86],{"emptyLinePlaceholder":85},[68,4113,4114,4116,4118],{"class":70,"line":126},[68,4115,102],{"class":74},[68,4117,131],{"class":78},[68,4119,108],{"class":74},[68,4121,4122,4124,4126],{"class":70,"line":136},[68,4123,102],{"class":74},[68,4125,141],{"class":78},[68,4127,108],{"class":74},[68,4129,4130,4132,4134],{"class":70,"line":146},[68,4131,102],{"class":74},[68,4133,151],{"class":78},[68,4135,108],{"class":74},[68,4137,4138],{"class":70,"line":156},[68,4139,159],{"class":74},[68,4141,4142],{"class":70,"line":162},[68,4143,86],{"emptyLinePlaceholder":85},[68,4145,4146,4148,4150,4152],{"class":70,"line":167},[68,4147,170],{"class":74},[68,4149,174],{"class":173},[68,4151,177],{"class":74},[68,4153,180],{"class":74},[68,4155,4156,4158,4160,4162,4164,4166,4168,4170,4172,4174,4176,4178],{"class":70,"line":183},[68,4157,187],{"class":186},[68,4159,190],{"class":74},[68,4161,193],{"class":186},[68,4163,196],{"class":74},[68,4165,199],{"class":186},[68,4167,202],{"class":74},[68,4169,205],{"class":173},[68,4171,208],{"class":74},[68,4173,211],{"class":74},[68,4175,4064],{"class":214},[68,4177,211],{"class":74},[68,4179,159],{"class":74},[68,4181,4182,4184,4186,4188,4190],{"class":70,"line":221},[68,4183,224],{"class":92},[68,4185,193],{"class":186},[68,4187,229],{"class":74},[68,4189,232],{"class":74},[68,4191,180],{"class":74},[68,4193,4194,4196,4198,4200,4202,4204],{"class":70,"line":237},[68,4195,240],{"class":186},[68,4197,202],{"class":74},[68,4199,245],{"class":173},[68,4201,208],{"class":74},[68,4203,250],{"class":186},[68,4205,159],{"class":74},[68,4207,4208],{"class":70,"line":255},[68,4209,258],{"class":74},[68,4211,4212],{"class":70,"line":261},[68,4213,86],{"emptyLinePlaceholder":85},[68,4215,4216,4218,4220,4222,4224,4226],{"class":70,"line":266},[68,4217,269],{"class":186},[68,4219,196],{"class":74},[68,4221,274],{"class":186},[68,4223,202],{"class":74},[68,4225,279],{"class":173},[68,4227,282],{"class":74},[68,4229,4230,4232,4234,4236,4238,4240,4242,4244],{"class":70,"line":285},[68,4231,288],{"class":186},[68,4233,202],{"class":74},[68,4235,293],{"class":173},[68,4237,208],{"class":74},[68,4239,27],{"class":186},[68,4241,202],{"class":74},[68,4243,302],{"class":186},[68,4245,305],{"class":74},[68,4247,4248,4250,4252,4254,4256,4258,4260,4262,4264,4266,4268,4270,4272,4275],{"class":70,"line":308},[68,4249,288],{"class":186},[68,4251,202],{"class":74},[68,4253,315],{"class":173},[68,4255,208],{"class":74},[68,4257,320],{"class":186},[68,4259,202],{"class":74},[68,4261,325],{"class":173},[68,4263,208],{"class":74},[68,4265,320],{"class":186},[68,4267,202],{"class":74},[68,4269,334],{"class":173},[68,4271,208],{"class":74},[68,4273,4274],{"class":339},"25",[68,4276,343],{"class":74},[68,4278,4279,4281,4283,4285,4287,4289,4292,4294,4296,4298],{"class":70,"line":346},[68,4280,288],{"class":186},[68,4282,202],{"class":74},[68,4284,353],{"class":173},[68,4286,208],{"class":74},[68,4288,211],{"class":74},[68,4290,4291],{"class":214},"IPAexGothic",[68,4293,211],{"class":74},[68,4295,190],{"class":74},[68,4297,367],{"class":186},[68,4299,305],{"class":74},[68,4301,4302,4304,4306,4308,4310,4312,4314,4316,4318,4321],{"class":70,"line":372},[68,4303,288],{"class":186},[68,4305,202],{"class":74},[68,4307,379],{"class":173},[68,4309,208],{"class":74},[68,4311,211],{"class":74},[68,4313,4291],{"class":214},[68,4315,211],{"class":74},[68,4317,190],{"class":74},[68,4319,4320],{"class":339}," 10.5",[68,4322,305],{"class":74},[68,4324,4325],{"class":70,"line":397},[68,4326,400],{"class":74},[68,4328,4329],{"class":70,"line":403},[68,4330,86],{"emptyLinePlaceholder":85},[68,4332,4333,4335,4337,4339,4341,4343],{"class":70,"line":408},[68,4334,411],{"class":186},[68,4336,196],{"class":74},[68,4338,416],{"class":186},[68,4340,202],{"class":74},[68,4342,421],{"class":173},[68,4344,424],{"class":74},[68,4346,4347,4349,4351,4353,4355,4357,4359,4361,4363,4365,4367],{"class":70,"line":427},[68,4348,430],{"class":186},[68,4350,202],{"class":74},[68,4352,435],{"class":173},[68,4354,438],{"class":74},[68,4356,442],{"class":441},[68,4358,445],{"class":74},[68,4360,448],{"class":78},[68,4362,202],{"class":74},[68,4364,453],{"class":78},[68,4366,456],{"class":74},[68,4368,180],{"class":74},[68,4370,4371,4373,4375,4377,4379,4381,4383,4385,4387,4389,4391,4393,4395,4397],{"class":70,"line":461},[68,4372,464],{"class":186},[68,4374,202],{"class":74},[68,4376,469],{"class":173},[68,4378,208],{"class":74},[68,4380,474],{"class":339},[68,4382,190],{"class":74},[68,4384,479],{"class":74},[68,4386,482],{"class":441},[68,4388,445],{"class":74},[68,4390,448],{"class":78},[68,4392,202],{"class":74},[68,4394,491],{"class":78},[68,4396,456],{"class":74},[68,4398,180],{"class":74},[68,4400,4401,4403,4405,4407,4409,4411,4414,4416,4418,4420,4422,4424,4426,4428,4430,4432,4434,4436],{"class":70,"line":498},[68,4402,501],{"class":186},[68,4404,202],{"class":74},[68,4406,506],{"class":173},[68,4408,208],{"class":74},[68,4410,211],{"class":74},[68,4412,4413],{"class":214},"請求書",[68,4415,211],{"class":74},[68,4417,190],{"class":74},[68,4419,520],{"class":186},[68,4421,202],{"class":74},[68,4423,525],{"class":173},[68,4425,208],{"class":74},[68,4427,530],{"class":339},[68,4429,533],{"class":74},[68,4431,520],{"class":186},[68,4433,202],{"class":74},[68,4435,540],{"class":173},[68,4437,543],{"class":74},[68,4439,4440,4442,4444,4446,4448,4450,4453,4455],{"class":70,"line":546},[68,4441,501],{"class":186},[68,4443,202],{"class":74},[68,4445,506],{"class":173},[68,4447,208],{"class":74},[68,4449,211],{"class":74},[68,4451,4452],{"class":214},"令和8年4月17日発行",[68,4454,211],{"class":74},[68,4456,159],{"class":74},[68,4458,4459,4461,4463,4465,4467,4469,4472,4474],{"class":70,"line":566},[68,4460,501],{"class":186},[68,4462,202],{"class":74},[68,4464,506],{"class":173},[68,4466,208],{"class":74},[68,4468,211],{"class":74},[68,4470,4471],{"class":214},"金額: ¥100,000 (税込)",[68,4473,211],{"class":74},[68,4475,159],{"class":74},[68,4477,4478],{"class":70,"line":572},[68,4479,569],{"class":74},[68,4481,4482],{"class":70,"line":578},[68,4483,575],{"class":74},[68,4485,4486],{"class":70,"line":583},[68,4487,86],{"emptyLinePlaceholder":85},[68,4489,4490,4492,4494,4496,4498,4500,4502,4504],{"class":70,"line":604},[68,4491,586],{"class":186},[68,4493,190],{"class":74},[68,4495,193],{"class":186},[68,4497,196],{"class":74},[68,4499,416],{"class":186},[68,4501,202],{"class":74},[68,4503,599],{"class":173},[68,4505,424],{"class":74},[68,4507,4508,4510,4512,4514,4516],{"class":70,"line":617},[68,4509,224],{"class":92},[68,4511,193],{"class":186},[68,4513,229],{"class":74},[68,4515,232],{"class":74},[68,4517,180],{"class":74},[68,4519,4520,4522,4524,4526,4528,4530],{"class":70,"line":632},[68,4521,240],{"class":186},[68,4523,202],{"class":74},[68,4525,245],{"class":173},[68,4527,208],{"class":74},[68,4529,250],{"class":186},[68,4531,159],{"class":74},[68,4533,4534],{"class":70,"line":637},[68,4535,258],{"class":74},[68,4537,4538,4540,4542,4544,4546,4548,4550,4552,4554,4557,4559,4561,4563,4565,4567,4569,4571,4573,4575],{"class":70,"line":683},[68,4539,224],{"class":92},[68,4541,193],{"class":186},[68,4543,196],{"class":74},[68,4545,199],{"class":186},[68,4547,202],{"class":74},[68,4549,650],{"class":173},[68,4551,208],{"class":74},[68,4553,211],{"class":74},[68,4555,4556],{"class":214},"invoice.pdf",[68,4558,211],{"class":74},[68,4560,190],{"class":74},[68,4562,664],{"class":186},[68,4564,190],{"class":74},[68,4566,669],{"class":339},[68,4568,672],{"class":74},[68,4570,193],{"class":186},[68,4572,229],{"class":74},[68,4574,232],{"class":74},[68,4576,180],{"class":74},[68,4578,4579,4581,4583,4585,4587,4589],{"class":70,"line":698},[68,4580,240],{"class":186},[68,4582,202],{"class":74},[68,4584,245],{"class":173},[68,4586,208],{"class":74},[68,4588,250],{"class":186},[68,4590,159],{"class":74},[68,4592,4593],{"class":70,"line":703},[68,4594,258],{"class":74},[68,4596,4598],{"class":70,"line":4597},41,[68,4599,706],{"class":74},[18,4601,4602,4603,3516,4607,4610,4611,4613,4614,4616,4617,720],{},"在 ",[22,4604,4606],{"href":4050,"rel":4605},[26],"moji.or.jp/ipafont",[47,4608,4609],{},"IPAex00401.zip","，解压出 ",[47,4612,4064],{}," 放到 ",[47,4615,712],{}," 旁边，然后 ",[47,4618,716],{},[14,4620,4621],{"id":4621},"三个文件到底选哪个",[18,4623,4624],{},"打开 zip，你会看到 3 个 TTF 和一份许可证。名字很容易弄混：",[740,4626,4627,4637],{},[743,4628,4629],{},[746,4630,4631,4634],{},[749,4632,4633],{},"文件",[749,4635,4636],{},"内容",[758,4638,4639,4651,4664],{},[746,4640,4641,4645],{},[763,4642,4643],{},[47,4644,4064],{},[763,4646,4647,4650],{},[30,4648,4649],{},"IPAex Gothic"," —— 无衬线，西文比例宽度。大多数文档选这个。",[746,4652,4653,4658],{},[763,4654,4655],{},[47,4656,4657],{},"ipaexm.ttf",[763,4659,4660,4663],{},[30,4661,4662],{},"IPAex Mincho"," —— 衬线（明朝体），西文比例宽度。长文本或与哥特体配对强调时用。",[746,4665,4666,4671],{},[763,4667,4668],{},[47,4669,4670],{},"ipag.ttf",[763,4672,4673,4676,4677,4680],{},[30,4674,4675],{},"IPA Gothic","（没有 \"ex\"）—— 无衬线，",[30,4678,4679],{},"西文等宽","。现在很少用。",[18,4682,4683],{},"IPAex 里的 \"ex\" 是 \"extended proportional\" 的缩写。原版 IPA 字体把西文字符放在 CJK 全角格子上，中英混排时显得拉伸。IPAex 把西文改为比例宽度，同时保持 CJK 字符在常规格子上。任何夹杂英文词、URL、数字的业务文档——也就是日本的几乎所有业务文档——都该用 IPAex。",[18,4685,4686,4687,4689,4690,4692,4693,4695],{},"如果你接手的旧项目用 ",[47,4688,4670],{},"，原因大概是历史遗留（原版 IPA Gothic: 2003 年；IPAex: 2010 年）。把族名保持为 ",[47,4691,4291],{},"、文件换成 ",[47,4694,4064],{},"，代码侧改一行即可。",[14,4697,4699],{"id":4698},"没有-bold-怎么办","没有 Bold 怎么办",[18,4701,4702],{},"IPAex 每族只发行一个字重：Regular。这和 Noto Sans JP 的 9 字重相比是最大短板，也是大家看完 IPAex 决定放弃的首要原因。",[18,4704,4705],{},"gpdf 里有两种处理方式。",[18,4707,4708,2971,4711,4713],{},[30,4709,4710],{},"合成粗体。",[47,4712,1095],{}," 在 Regular 字形上叠加描边。排版上算作弊——真正的粗体字重有专门重画的粗笔画轮廓，不是 Regular 再描一次。但对于 10 pt 以上的发票标题和表格标签，合成粗体对大多数读者来说是看不出差别的：",[59,4715,4717],{"className":61,"code":4716,"language":63,"meta":64,"style":64},"c.Text(\"合計金額\", template.Bold())\n",[47,4718,4719],{"__ignoreMap":64},[68,4720,4721,4723,4725,4727,4729,4731,4734,4736,4738,4740,4742,4744],{"class":70,"line":71},[68,4722,482],{"class":186},[68,4724,202],{"class":74},[68,4726,506],{"class":173},[68,4728,208],{"class":74},[68,4730,211],{"class":74},[68,4732,4733],{"class":214},"合計金額",[68,4735,211],{"class":74},[68,4737,190],{"class":74},[68,4739,520],{"class":186},[68,4741,202],{"class":74},[68,4743,540],{"class":173},[68,4745,543],{"class":74},[18,4747,4748,4751],{},[30,4749,4750],{},"和 IPAex 明朝配对。"," 日文排版的经典强调手法并不是加粗，而是衬线 / 无衬线切换。同时注册两族：",[59,4753,4755],{"className":61,"code":4754,"language":63,"meta":64,"style":64},"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",[47,4756,4757,4784,4811,4815,4829,4852,4876,4898,4902,4906,4930,4960,5008,5027,5031],{"__ignoreMap":64},[68,4758,4759,4762,4764,4766,4768,4770,4772,4774,4776,4778,4780,4782],{"class":70,"line":71},[68,4760,4761],{"class":186},"gothic",[68,4763,190],{"class":74},[68,4765,946],{"class":186},[68,4767,196],{"class":74},[68,4769,199],{"class":186},[68,4771,202],{"class":74},[68,4773,205],{"class":173},[68,4775,208],{"class":74},[68,4777,211],{"class":74},[68,4779,4064],{"class":214},[68,4781,211],{"class":74},[68,4783,159],{"class":74},[68,4785,4786,4789,4791,4793,4795,4797,4799,4801,4803,4805,4807,4809],{"class":70,"line":82},[68,4787,4788],{"class":186},"mincho",[68,4790,190],{"class":74},[68,4792,974],{"class":186},[68,4794,196],{"class":74},[68,4796,199],{"class":186},[68,4798,202],{"class":74},[68,4800,205],{"class":173},[68,4802,208],{"class":74},[68,4804,211],{"class":74},[68,4806,4657],{"class":214},[68,4808,211],{"class":74},[68,4810,159],{"class":74},[68,4812,4813],{"class":70,"line":89},[68,4814,86],{"emptyLinePlaceholder":85},[68,4816,4817,4819,4821,4823,4825,4827],{"class":70,"line":99},[68,4818,1002],{"class":186},[68,4820,196],{"class":74},[68,4822,274],{"class":186},[68,4824,202],{"class":74},[68,4826,279],{"class":173},[68,4828,282],{"class":74},[68,4830,4831,4833,4835,4837,4839,4841,4843,4845,4847,4850],{"class":70,"line":111},[68,4832,1017],{"class":186},[68,4834,202],{"class":74},[68,4836,353],{"class":173},[68,4838,208],{"class":74},[68,4840,211],{"class":74},[68,4842,4291],{"class":214},[68,4844,211],{"class":74},[68,4846,190],{"class":74},[68,4848,4849],{"class":186}," gothic",[68,4851,305],{"class":74},[68,4853,4854,4856,4858,4860,4862,4864,4867,4869,4871,4874],{"class":70,"line":121},[68,4855,1017],{"class":186},[68,4857,202],{"class":74},[68,4859,353],{"class":173},[68,4861,208],{"class":74},[68,4863,211],{"class":74},[68,4865,4866],{"class":214},"IPAexMincho",[68,4868,211],{"class":74},[68,4870,190],{"class":74},[68,4872,4873],{"class":186}," mincho",[68,4875,305],{"class":74},[68,4877,4878,4880,4882,4884,4886,4888,4890,4892,4894,4896],{"class":70,"line":126},[68,4879,1017],{"class":186},[68,4881,202],{"class":74},[68,4883,379],{"class":173},[68,4885,208],{"class":74},[68,4887,211],{"class":74},[68,4889,4291],{"class":214},[68,4891,211],{"class":74},[68,4893,190],{"class":74},[68,4895,4320],{"class":339},[68,4897,305],{"class":74},[68,4899,4900],{"class":70,"line":136},[68,4901,159],{"class":74},[68,4903,4904],{"class":70,"line":146},[68,4905,86],{"emptyLinePlaceholder":85},[68,4907,4908,4910,4912,4914,4916,4918,4920,4922,4924,4926,4928],{"class":70,"line":156},[68,4909,1924],{"class":186},[68,4911,202],{"class":74},[68,4913,435],{"class":173},[68,4915,438],{"class":74},[68,4917,442],{"class":441},[68,4919,445],{"class":74},[68,4921,448],{"class":78},[68,4923,202],{"class":74},[68,4925,453],{"class":78},[68,4927,456],{"class":74},[68,4929,180],{"class":74},[68,4931,4932,4934,4936,4938,4940,4942,4944,4946,4948,4950,4952,4954,4956,4958],{"class":70,"line":162},[68,4933,3659],{"class":186},[68,4935,202],{"class":74},[68,4937,469],{"class":173},[68,4939,208],{"class":74},[68,4941,474],{"class":339},[68,4943,190],{"class":74},[68,4945,479],{"class":74},[68,4947,482],{"class":441},[68,4949,445],{"class":74},[68,4951,448],{"class":78},[68,4953,202],{"class":74},[68,4955,491],{"class":78},[68,4957,456],{"class":74},[68,4959,180],{"class":74},[68,4961,4962,4964,4966,4968,4970,4972,4974,4976,4978,4980,4982,4985,4987,4989,4991,4993,4995,4997,4999,5001,5003,5005],{"class":70,"line":167},[68,4963,3690],{"class":186},[68,4965,202],{"class":74},[68,4967,506],{"class":173},[68,4969,208],{"class":74},[68,4971,211],{"class":74},[68,4973,4413],{"class":214},[68,4975,211],{"class":74},[68,4977,190],{"class":74},[68,4979,520],{"class":186},[68,4981,202],{"class":74},[68,4983,4984],{"class":173},"FontFamily",[68,4986,208],{"class":74},[68,4988,211],{"class":74},[68,4990,4866],{"class":214},[68,4992,211],{"class":74},[68,4994,533],{"class":74},[68,4996,520],{"class":186},[68,4998,202],{"class":74},[68,5000,525],{"class":173},[68,5002,208],{"class":74},[68,5004,530],{"class":339},[68,5006,5007],{"class":74},"))\n",[68,5009,5010,5012,5014,5016,5018,5020,5023,5025],{"class":70,"line":183},[68,5011,3690],{"class":186},[68,5013,202],{"class":74},[68,5015,506],{"class":173},[68,5017,208],{"class":74},[68,5019,211],{"class":74},[68,5021,5022],{"class":214},"ご請求内容は下記の通りです。",[68,5024,211],{"class":74},[68,5026,159],{"class":74},[68,5028,5029],{"class":70,"line":221},[68,5030,575],{"class":74},[68,5032,5033],{"class":70,"line":237},[68,5034,3717],{"class":74},[18,5036,5037],{},"这种组合在日本的婚礼请柬和正式报告里很常见 —— 标题用明朝、正文用哥特。如果你的文档要交到政府机关，这个组合通常正是对方期待的。",[14,5039,5041],{"id":5040},"简述-ipa-font-license","简述 IPA Font License",[18,5043,5044,5045,5050],{},"IPAex 不是 SIL OFL，而是 ",[22,5046,5049],{"href":5047,"rel":5048},"https://opensource.org/licenses/IPA",[26],"IPA Font License Agreement v1.0","，OSI 批准，总体宽松，但有两点值得注意：",[882,5052,5053,5067],{},[885,5054,5055,5058,5059,5062,5063,5066],{},[30,5056,5057],{},"字体二进制分发到哪里，许可证文本就要跟到哪里。"," 如果你 ",[47,5060,5061],{},"//go:embed"," 了 TTF，就把许可证文本一起打包。项目根目录放一个 ",[47,5064,5065],{},"LICENSES/IPA-FONT-1.0.txt"," 对大多数分发场景已经够用。",[885,5068,5069,5072,5073,5076],{},[30,5070,5071],{},"不要重命名字体。"," 如果你修改 TTF 本身再分发，衍生字体必须使用不同名字（不得包含 \"IPA\" 或 \"IPAex\"）。注意：这个限制",[30,5074,5075],{},"不适用于","渲染时的字形子集化。许可证第 3.4 条明确把\"用该字体生成的输出文档\"从命名限制中豁免。",[18,5078,5079,5080,5083],{},"含义：gpdf 在 ",[47,5081,5082],{},"doc.Generate()"," 时的子集化没问题。嵌入到 PDF 里的子集不需要改名，也不会触发\"衍生字体程序\"相关条款。你是在创建文档，不是在重新分发字体。",[18,5085,5086],{},"补充一点，gpdf 核心 OSS 仓库本身没有内置 IPAex（golden 测试用的是 Noto 系 SIL OFL 字体），这样下游用户的仓库顶层 LICENSE 就不用为 IPA 许可证的兼容性操心。你的应用里怎么用 IPAex 是你项目的决定，不是我们的。",[14,5088,5090],{"id":5089},"什么时候选-ipaex-而非-noto-sans-jp","什么时候选 IPAex 而非 Noto Sans JP",[740,5092,5093,5104],{},[743,5094,5095],{},[746,5096,5097,5100,5102],{},[749,5098,5099],{},"维度",[749,5101,4649],{},[749,5103,756],{},[758,5105,5106,5117,5128,5139,5153,5164],{},[746,5107,5108,5111,5114],{},[763,5109,5110],{},"字重数",[763,5112,5113],{},"1（Regular）",[763,5115,5116],{},"9（Thin → Black）",[746,5118,5119,5122,5125],{},[763,5120,5121],{},"许可证",[763,5123,5124],{},"IPA Font License v1.0",[763,5126,5127],{},"SIL OFL 1.1",[746,5129,5130,5133,5136],{},[763,5131,5132],{},"西文处理",[763,5134,5135],{},"比例宽度（IPAex）或等宽（IPA）",[763,5137,5138],{},"比例宽度",[746,5140,5141,5144,5150],{},[763,5142,5143],{},"预装于",[763,5145,5146,5147],{},"部分日本系 Linux 发行版、TeX Live ",[47,5148,5149],{},"ptex-fonts",[763,5151,5152],{},"Android、ChromeOS",[746,5154,5155,5158,5161],{},[763,5156,5157],{},"典型受众",[763,5159,5160],{},"日本政府、法律、学术",[763,5162,5163],{},"消费者 Web、国际",[746,5165,5166,5169,5172],{},[763,5167,5168],{},"文件大小",[763,5170,5171],{},"7.5 MB（Gothic）",[763,5173,5174],{},"5 MB（仅 Regular）",[18,5176,5177],{},"当你的输出要穿越日本的制度性边界时 —— e-Tax PDF 附件、法院文书、投向日本期刊的学术论文 —— 选 IPAex，因为这些生态里的评审、审稿人和 OCR 工具都是按 IPA 调校的。其他场景用 Noto Sans JP 即可。两者渲染结果非常接近，选择的依据是\"出口在哪个生态\"而非美学。",[14,5179,1209],{"id":1209},[1111,5181,5182,5187,5192,5197],{},[885,5183,5184,5186],{},[22,5185,1223],{"href":1222}," —— 适用于任何 CJK TTF 的通用配方",[885,5188,5189,5191],{},[22,5190,1216],{"href":833}," —— SIL OFL 且有 9 字重的替代选项",[885,5193,5194,5196],{},[22,5195,1237],{"href":1236}," —— 当字形没显示时的排查",[885,5198,5199,3947,5204,5206],{},[22,5200,5203],{"href":5201,"rel":5202},"https://gpdf.dev/zh/docs/guide/fonts",[26],"字体指南",[47,5205,353],{}," 完整参考",[14,5208,1242],{"id":1241},[18,5210,1245],{},[59,5212,5213],{"className":1248,"code":1249,"language":1250,"meta":64,"style":64},[47,5214,5215],{"__ignoreMap":64},[68,5216,5217,5219,5221],{"class":70,"line":71},[68,5218,63],{"class":78},[68,5220,1259],{"class":214},[68,5222,1262],{"class":214},[18,5224,5225,1269,5228],{},[22,5226,1268],{"href":24,"rel":5227},[26],[22,5229,1274],{"href":1272,"rel":5230},[26],[1276,5232,1278],{},{"title":64,"searchDepth":82,"depth":82,"links":5234},[5235,5236,5237,5238,5239,5240,5241,5242,5243],{"id":16,"depth":82,"text":16},{"id":36,"depth":82,"text":36},{"id":57,"depth":82,"text":57},{"id":4621,"depth":82,"text":4621},{"id":4698,"depth":82,"text":4699},{"id":5040,"depth":82,"text":5041},{"id":5089,"depth":82,"text":5090},{"id":1209,"depth":82,"text":1209},{"id":1241,"depth":82,"text":1242},"用 gpdf.WithFont 注册 ipaexg.ttf。IPAex 只提供 Regular 一个字重，粗体需要合成或与 Mincho 配对。",{"name":5246,"totalTime":5247,"tools":5248,"steps":5250},"在 gpdf 文档中将 IPAex Gothic 设为默认字体","PT10M",[1299,5249],"ipaexg.ttf（来自 moji.or.jp 的 IPAex Gothic v4.01）",[5251,5254,5257,5259,5262],{"name":5252,"text":5253},"从 moji.or.jp 下载 IPAex 字体包","在 moji.or.jp/ipafont 下载 IPAex00401.zip，解压后保留 ipaexg.ttf 以及随附的 IPA Font License Agreement v1.0 文本。",{"name":5255,"text":5256},"加载 TTF 字节","程序启动时用 os.ReadFile(\"ipaexg.ttf\") 读入 []byte。容器部署场景下用 //go:embed 把字体编进 Go 二进制更便于分发。",{"name":1309,"text":5258},"把 gpdf.WithFont(\"IPAexGothic\", fontBytes) 和 gpdf.WithDefaultFont(\"IPAexGothic\", 10.5) 传给 gpdf.NewDocument。10.5 pt 与 Word 日文文档的默认字号一致。",{"name":5260,"text":5261},"处理没有 Bold 文件的问题","IPAex Gothic 没有 Bold 变体。可以用 template.Bold() 合成粗体（gpdf 叠加 0.4 pt 描边），或把 IPAex Mincho 作为另一族注册用于强调。",{"name":5263,"text":5264},"随同二进制保留许可证文本","IPA Font License v1.0 要求字体二进制分发到哪里，许可证文本就要跟到哪里。如果用 //go:embed 嵌入 TTF，就把 LICENSES/IPA-FONT-1.0.txt 也嵌入并从 NOTICE 中引用。",{},{"title":4036,"description":5244},"zh/blog/009.ipaex-gothic-gpdf",[1323,1324,1325],"uOHF6J5BHEbi8tFpDdfK4wrlV8FdVlaiPy8RqLI32JI",{"id":5271,"title":5272,"author":5273,"body":5274,"date":5756,"description":6603,"draft":1293,"extension":1294,"howTo":6604,"image":1317,"meta":6623,"navigation":85,"path":6624,"seo":6625,"stem":6626,"tags":6627,"updated":1317,"__hash__":6629},"blogZh/zh/blog/005.12-column-grid.md","gpdf 的 12 列网格是怎么工作的？",{"name":8,"url":9},{"type":11,"value":5275,"toc":6591},[5276,5279,5289,5292,5306,5308,6252,6258,6262,6269,6272,6291,6305,6309,6312,6326,6340,6344,6350,6356,6510,6513,6519,6529,6532,6538,6540,6561,6563,6566,6578,6588],[14,5277,5278],{"id":5278},"换个说法的问题",[18,5280,5281,5282,853,5285,5288],{},"你用过 gpdf 的 API——页面构建器、行构建器、列构建器——列的构造函数接收一个数字：",[47,5283,5284],{},"r.Col(4, fn)",[47,5286,5287],{},"r.Col(8, fn)","。这个数字是什么？总和不到 12 会怎样？和你已经熟悉的 CSS 网格有什么区别？",[14,5290,5291],{"id":5291},"短答",[18,5293,5294,5297,5298,5301,5302,5305],{},[47,5295,5296],{},"r.Col(span, fn)"," 接收 1 到 12 的整数。这个整数代表此列在行里的占比——",[47,5299,5300],{},"span / 12"," 的可用宽度。小于 1 会被夹到 1，大于 12 会被夹到 12，",[30,5303,5304],{},"每行总和是否为 12 由你决定，库本身不强制","。网格被固定为 12 个分格，剩下的就是你如何切行的问题。",[14,5307,57],{"id":57},[59,5309,5311],{"className":61,"code":5310,"language":63,"meta":64,"style":64},"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    // 两列表头 (6 + 6)\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(6, func(c *template.ColBuilder) {\n            c.Text(\"收票方\")\n            c.Text(\"Acme 有限公司\")\n        })\n        r.Col(6, func(c *template.ColBuilder) {\n            c.Text(\"开票日期\")\n            c.Text(\"2026-04-16\")\n        })\n    })\n\n    // 三列摘要 (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",[47,5312,5313,5319,5323,5329,5337,5345,5349,5357,5365,5373,5377,5381,5391,5405,5423,5454,5458,5462,5476,5480,5485,5509,5539,5579,5583,5587,5591,5596,5620,5651,5670,5689,5693,5723,5742,5761,5765,5769,5773,5778,5802,5833,5853,5858,5889,5909,5914,5945,5964,5969,5974,5979,5985,6010,6042,6062,6067,6098,6118,6123,6128,6133,6152,6165,6180,6185,6227,6242,6247],{"__ignoreMap":64},[68,5314,5315,5317],{"class":70,"line":71},[68,5316,75],{"class":74},[68,5318,79],{"class":78},[68,5320,5321],{"class":70,"line":82},[68,5322,86],{"emptyLinePlaceholder":85},[68,5324,5325,5327],{"class":70,"line":89},[68,5326,93],{"class":92},[68,5328,96],{"class":74},[68,5330,5331,5333,5335],{"class":70,"line":99},[68,5332,102],{"class":74},[68,5334,105],{"class":78},[68,5336,108],{"class":74},[68,5338,5339,5341,5343],{"class":70,"line":111},[68,5340,102],{"class":74},[68,5342,116],{"class":78},[68,5344,108],{"class":74},[68,5346,5347],{"class":70,"line":121},[68,5348,86],{"emptyLinePlaceholder":85},[68,5350,5351,5353,5355],{"class":70,"line":126},[68,5352,102],{"class":74},[68,5354,131],{"class":78},[68,5356,108],{"class":74},[68,5358,5359,5361,5363],{"class":70,"line":136},[68,5360,102],{"class":74},[68,5362,141],{"class":78},[68,5364,108],{"class":74},[68,5366,5367,5369,5371],{"class":70,"line":146},[68,5368,102],{"class":74},[68,5370,151],{"class":78},[68,5372,108],{"class":74},[68,5374,5375],{"class":70,"line":156},[68,5376,159],{"class":74},[68,5378,5379],{"class":70,"line":162},[68,5380,86],{"emptyLinePlaceholder":85},[68,5382,5383,5385,5387,5389],{"class":70,"line":167},[68,5384,170],{"class":74},[68,5386,174],{"class":173},[68,5388,177],{"class":74},[68,5390,180],{"class":74},[68,5392,5393,5395,5397,5399,5401,5403],{"class":70,"line":183},[68,5394,269],{"class":186},[68,5396,196],{"class":74},[68,5398,274],{"class":186},[68,5400,202],{"class":74},[68,5402,279],{"class":173},[68,5404,282],{"class":74},[68,5406,5407,5409,5411,5413,5415,5417,5419,5421],{"class":70,"line":221},[68,5408,288],{"class":186},[68,5410,202],{"class":74},[68,5412,293],{"class":173},[68,5414,208],{"class":74},[68,5416,320],{"class":186},[68,5418,202],{"class":74},[68,5420,302],{"class":186},[68,5422,305],{"class":74},[68,5424,5425,5427,5429,5431,5433,5435,5437,5439,5441,5443,5445,5447,5449,5452],{"class":70,"line":237},[68,5426,288],{"class":186},[68,5428,202],{"class":74},[68,5430,315],{"class":173},[68,5432,208],{"class":74},[68,5434,320],{"class":186},[68,5436,202],{"class":74},[68,5438,325],{"class":173},[68,5440,208],{"class":74},[68,5442,320],{"class":186},[68,5444,202],{"class":74},[68,5446,334],{"class":173},[68,5448,208],{"class":74},[68,5450,5451],{"class":339},"15",[68,5453,343],{"class":74},[68,5455,5456],{"class":70,"line":255},[68,5457,400],{"class":74},[68,5459,5460],{"class":70,"line":261},[68,5461,86],{"emptyLinePlaceholder":85},[68,5463,5464,5466,5468,5470,5472,5474],{"class":70,"line":266},[68,5465,411],{"class":186},[68,5467,196],{"class":74},[68,5469,416],{"class":186},[68,5471,202],{"class":74},[68,5473,421],{"class":173},[68,5475,424],{"class":74},[68,5477,5478],{"class":70,"line":285},[68,5479,86],{"emptyLinePlaceholder":85},[68,5481,5482],{"class":70,"line":308},[68,5483,5484],{"class":2206},"    // 整行\n",[68,5486,5487,5489,5491,5493,5495,5497,5499,5501,5503,5505,5507],{"class":70,"line":346},[68,5488,430],{"class":186},[68,5490,202],{"class":74},[68,5492,435],{"class":173},[68,5494,438],{"class":74},[68,5496,442],{"class":441},[68,5498,445],{"class":74},[68,5500,448],{"class":78},[68,5502,202],{"class":74},[68,5504,453],{"class":78},[68,5506,456],{"class":74},[68,5508,180],{"class":74},[68,5510,5511,5513,5515,5517,5519,5521,5523,5525,5527,5529,5531,5533,5535,5537],{"class":70,"line":372},[68,5512,464],{"class":186},[68,5514,202],{"class":74},[68,5516,469],{"class":173},[68,5518,208],{"class":74},[68,5520,474],{"class":339},[68,5522,190],{"class":74},[68,5524,479],{"class":74},[68,5526,482],{"class":441},[68,5528,445],{"class":74},[68,5530,448],{"class":78},[68,5532,202],{"class":74},[68,5534,491],{"class":78},[68,5536,456],{"class":74},[68,5538,180],{"class":74},[68,5540,5541,5543,5545,5547,5549,5551,5554,5556,5558,5560,5562,5564,5566,5569,5571,5573,5575,5577],{"class":70,"line":397},[68,5542,501],{"class":186},[68,5544,202],{"class":74},[68,5546,506],{"class":173},[68,5548,208],{"class":74},[68,5550,211],{"class":74},[68,5552,5553],{"class":214},"发票 #2026-0416",[68,5555,211],{"class":74},[68,5557,190],{"class":74},[68,5559,520],{"class":186},[68,5561,202],{"class":74},[68,5563,525],{"class":173},[68,5565,208],{"class":74},[68,5567,5568],{"class":339},"18",[68,5570,533],{"class":74},[68,5572,520],{"class":186},[68,5574,202],{"class":74},[68,5576,540],{"class":173},[68,5578,543],{"class":74},[68,5580,5581],{"class":70,"line":403},[68,5582,569],{"class":74},[68,5584,5585],{"class":70,"line":408},[68,5586,575],{"class":74},[68,5588,5589],{"class":70,"line":427},[68,5590,86],{"emptyLinePlaceholder":85},[68,5592,5593],{"class":70,"line":461},[68,5594,5595],{"class":2206},"    // 两列表头 (6 + 6)\n",[68,5597,5598,5600,5602,5604,5606,5608,5610,5612,5614,5616,5618],{"class":70,"line":498},[68,5599,430],{"class":186},[68,5601,202],{"class":74},[68,5603,435],{"class":173},[68,5605,438],{"class":74},[68,5607,442],{"class":441},[68,5609,445],{"class":74},[68,5611,448],{"class":78},[68,5613,202],{"class":74},[68,5615,453],{"class":78},[68,5617,456],{"class":74},[68,5619,180],{"class":74},[68,5621,5622,5624,5626,5628,5630,5633,5635,5637,5639,5641,5643,5645,5647,5649],{"class":70,"line":546},[68,5623,464],{"class":186},[68,5625,202],{"class":74},[68,5627,469],{"class":173},[68,5629,208],{"class":74},[68,5631,5632],{"class":339},"6",[68,5634,190],{"class":74},[68,5636,479],{"class":74},[68,5638,482],{"class":441},[68,5640,445],{"class":74},[68,5642,448],{"class":78},[68,5644,202],{"class":74},[68,5646,491],{"class":78},[68,5648,456],{"class":74},[68,5650,180],{"class":74},[68,5652,5653,5655,5657,5659,5661,5663,5666,5668],{"class":70,"line":566},[68,5654,501],{"class":186},[68,5656,202],{"class":74},[68,5658,506],{"class":173},[68,5660,208],{"class":74},[68,5662,211],{"class":74},[68,5664,5665],{"class":214},"收票方",[68,5667,211],{"class":74},[68,5669,159],{"class":74},[68,5671,5672,5674,5676,5678,5680,5682,5685,5687],{"class":70,"line":572},[68,5673,501],{"class":186},[68,5675,202],{"class":74},[68,5677,506],{"class":173},[68,5679,208],{"class":74},[68,5681,211],{"class":74},[68,5683,5684],{"class":214},"Acme 有限公司",[68,5686,211],{"class":74},[68,5688,159],{"class":74},[68,5690,5691],{"class":70,"line":578},[68,5692,569],{"class":74},[68,5694,5695,5697,5699,5701,5703,5705,5707,5709,5711,5713,5715,5717,5719,5721],{"class":70,"line":583},[68,5696,464],{"class":186},[68,5698,202],{"class":74},[68,5700,469],{"class":173},[68,5702,208],{"class":74},[68,5704,5632],{"class":339},[68,5706,190],{"class":74},[68,5708,479],{"class":74},[68,5710,482],{"class":441},[68,5712,445],{"class":74},[68,5714,448],{"class":78},[68,5716,202],{"class":74},[68,5718,491],{"class":78},[68,5720,456],{"class":74},[68,5722,180],{"class":74},[68,5724,5725,5727,5729,5731,5733,5735,5738,5740],{"class":70,"line":604},[68,5726,501],{"class":186},[68,5728,202],{"class":74},[68,5730,506],{"class":173},[68,5732,208],{"class":74},[68,5734,211],{"class":74},[68,5736,5737],{"class":214},"开票日期",[68,5739,211],{"class":74},[68,5741,159],{"class":74},[68,5743,5744,5746,5748,5750,5752,5754,5757,5759],{"class":70,"line":617},[68,5745,501],{"class":186},[68,5747,202],{"class":74},[68,5749,506],{"class":173},[68,5751,208],{"class":74},[68,5753,211],{"class":74},[68,5755,5756],{"class":214},"2026-04-16",[68,5758,211],{"class":74},[68,5760,159],{"class":74},[68,5762,5763],{"class":70,"line":632},[68,5764,569],{"class":74},[68,5766,5767],{"class":70,"line":637},[68,5768,575],{"class":74},[68,5770,5771],{"class":70,"line":683},[68,5772,86],{"emptyLinePlaceholder":85},[68,5774,5775],{"class":70,"line":698},[68,5776,5777],{"class":2206},"    // 三列摘要 (4 + 4 + 4)\n",[68,5779,5780,5782,5784,5786,5788,5790,5792,5794,5796,5798,5800],{"class":70,"line":703},[68,5781,430],{"class":186},[68,5783,202],{"class":74},[68,5785,435],{"class":173},[68,5787,438],{"class":74},[68,5789,442],{"class":441},[68,5791,445],{"class":74},[68,5793,448],{"class":78},[68,5795,202],{"class":74},[68,5797,453],{"class":78},[68,5799,456],{"class":74},[68,5801,180],{"class":74},[68,5803,5804,5806,5808,5810,5812,5815,5817,5819,5821,5823,5825,5827,5829,5831],{"class":70,"line":4597},[68,5805,464],{"class":186},[68,5807,202],{"class":74},[68,5809,469],{"class":173},[68,5811,208],{"class":74},[68,5813,5814],{"class":339},"4",[68,5816,190],{"class":74},[68,5818,479],{"class":74},[68,5820,482],{"class":441},[68,5822,445],{"class":74},[68,5824,448],{"class":78},[68,5826,202],{"class":74},[68,5828,491],{"class":78},[68,5830,456],{"class":74},[68,5832,180],{"class":74},[68,5834,5836,5838,5840,5842,5844,5846,5849,5851],{"class":70,"line":5835},42,[68,5837,501],{"class":186},[68,5839,202],{"class":74},[68,5841,506],{"class":173},[68,5843,208],{"class":74},[68,5845,211],{"class":74},[68,5847,5848],{"class":214},"小计",[68,5850,211],{"class":74},[68,5852,159],{"class":74},[68,5854,5856],{"class":70,"line":5855},43,[68,5857,569],{"class":74},[68,5859,5861,5863,5865,5867,5869,5871,5873,5875,5877,5879,5881,5883,5885,5887],{"class":70,"line":5860},44,[68,5862,464],{"class":186},[68,5864,202],{"class":74},[68,5866,469],{"class":173},[68,5868,208],{"class":74},[68,5870,5814],{"class":339},[68,5872,190],{"class":74},[68,5874,479],{"class":74},[68,5876,482],{"class":441},[68,5878,445],{"class":74},[68,5880,448],{"class":78},[68,5882,202],{"class":74},[68,5884,491],{"class":78},[68,5886,456],{"class":74},[68,5888,180],{"class":74},[68,5890,5892,5894,5896,5898,5900,5902,5905,5907],{"class":70,"line":5891},45,[68,5893,501],{"class":186},[68,5895,202],{"class":74},[68,5897,506],{"class":173},[68,5899,208],{"class":74},[68,5901,211],{"class":74},[68,5903,5904],{"class":214},"税额",[68,5906,211],{"class":74},[68,5908,159],{"class":74},[68,5910,5912],{"class":70,"line":5911},46,[68,5913,569],{"class":74},[68,5915,5917,5919,5921,5923,5925,5927,5929,5931,5933,5935,5937,5939,5941,5943],{"class":70,"line":5916},47,[68,5918,464],{"class":186},[68,5920,202],{"class":74},[68,5922,469],{"class":173},[68,5924,208],{"class":74},[68,5926,5814],{"class":339},[68,5928,190],{"class":74},[68,5930,479],{"class":74},[68,5932,482],{"class":441},[68,5934,445],{"class":74},[68,5936,448],{"class":78},[68,5938,202],{"class":74},[68,5940,491],{"class":78},[68,5942,456],{"class":74},[68,5944,180],{"class":74},[68,5946,5948,5950,5952,5954,5956,5958,5960,5962],{"class":70,"line":5947},48,[68,5949,501],{"class":186},[68,5951,202],{"class":74},[68,5953,506],{"class":173},[68,5955,208],{"class":74},[68,5957,211],{"class":74},[68,5959,2669],{"class":214},[68,5961,211],{"class":74},[68,5963,159],{"class":74},[68,5965,5967],{"class":70,"line":5966},49,[68,5968,569],{"class":74},[68,5970,5972],{"class":70,"line":5971},50,[68,5973,575],{"class":74},[68,5975,5977],{"class":70,"line":5976},51,[68,5978,86],{"emptyLinePlaceholder":85},[68,5980,5982],{"class":70,"line":5981},52,[68,5983,5984],{"class":2206},"    // 非对称 (8 + 4) — 正文 + 侧栏\n",[68,5986,5988,5990,5992,5994,5996,5998,6000,6002,6004,6006,6008],{"class":70,"line":5987},53,[68,5989,430],{"class":186},[68,5991,202],{"class":74},[68,5993,435],{"class":173},[68,5995,438],{"class":74},[68,5997,442],{"class":441},[68,5999,445],{"class":74},[68,6001,448],{"class":78},[68,6003,202],{"class":74},[68,6005,453],{"class":78},[68,6007,456],{"class":74},[68,6009,180],{"class":74},[68,6011,6013,6015,6017,6019,6021,6024,6026,6028,6030,6032,6034,6036,6038,6040],{"class":70,"line":6012},54,[68,6014,464],{"class":186},[68,6016,202],{"class":74},[68,6018,469],{"class":173},[68,6020,208],{"class":74},[68,6022,6023],{"class":339},"8",[68,6025,190],{"class":74},[68,6027,479],{"class":74},[68,6029,482],{"class":441},[68,6031,445],{"class":74},[68,6033,448],{"class":78},[68,6035,202],{"class":74},[68,6037,491],{"class":78},[68,6039,456],{"class":74},[68,6041,180],{"class":74},[68,6043,6045,6047,6049,6051,6053,6055,6058,6060],{"class":70,"line":6044},55,[68,6046,501],{"class":186},[68,6048,202],{"class":74},[68,6050,506],{"class":173},[68,6052,208],{"class":74},[68,6054,211],{"class":74},[68,6056,6057],{"class":214},"明细在这里列出",[68,6059,211],{"class":74},[68,6061,159],{"class":74},[68,6063,6065],{"class":70,"line":6064},56,[68,6066,569],{"class":74},[68,6068,6070,6072,6074,6076,6078,6080,6082,6084,6086,6088,6090,6092,6094,6096],{"class":70,"line":6069},57,[68,6071,464],{"class":186},[68,6073,202],{"class":74},[68,6075,469],{"class":173},[68,6077,208],{"class":74},[68,6079,5814],{"class":339},[68,6081,190],{"class":74},[68,6083,479],{"class":74},[68,6085,482],{"class":441},[68,6087,445],{"class":74},[68,6089,448],{"class":78},[68,6091,202],{"class":74},[68,6093,491],{"class":78},[68,6095,456],{"class":74},[68,6097,180],{"class":74},[68,6099,6101,6103,6105,6107,6109,6111,6114,6116],{"class":70,"line":6100},58,[68,6102,501],{"class":186},[68,6104,202],{"class":74},[68,6106,506],{"class":173},[68,6108,208],{"class":74},[68,6110,211],{"class":74},[68,6112,6113],{"class":214},"备注",[68,6115,211],{"class":74},[68,6117,159],{"class":74},[68,6119,6121],{"class":70,"line":6120},59,[68,6122,569],{"class":74},[68,6124,6126],{"class":70,"line":6125},60,[68,6127,575],{"class":74},[68,6129,6131],{"class":70,"line":6130},61,[68,6132,86],{"emptyLinePlaceholder":85},[68,6134,6136,6138,6140,6142,6144,6146,6148,6150],{"class":70,"line":6135},62,[68,6137,586],{"class":186},[68,6139,190],{"class":74},[68,6141,193],{"class":186},[68,6143,196],{"class":74},[68,6145,416],{"class":186},[68,6147,202],{"class":74},[68,6149,599],{"class":173},[68,6151,424],{"class":74},[68,6153,6155,6157,6159,6161,6163],{"class":70,"line":6154},63,[68,6156,224],{"class":92},[68,6158,193],{"class":186},[68,6160,229],{"class":74},[68,6162,232],{"class":74},[68,6164,180],{"class":74},[68,6166,6168,6170,6172,6174,6176,6178],{"class":70,"line":6167},64,[68,6169,240],{"class":186},[68,6171,202],{"class":74},[68,6173,245],{"class":173},[68,6175,208],{"class":74},[68,6177,250],{"class":186},[68,6179,159],{"class":74},[68,6181,6183],{"class":70,"line":6182},65,[68,6184,258],{"class":74},[68,6186,6188,6190,6192,6194,6196,6198,6200,6202,6204,6207,6209,6211,6213,6215,6217,6219,6221,6223,6225],{"class":70,"line":6187},66,[68,6189,224],{"class":92},[68,6191,193],{"class":186},[68,6193,196],{"class":74},[68,6195,199],{"class":186},[68,6197,202],{"class":74},[68,6199,650],{"class":173},[68,6201,208],{"class":74},[68,6203,211],{"class":74},[68,6205,6206],{"class":214},"layout.pdf",[68,6208,211],{"class":74},[68,6210,190],{"class":74},[68,6212,664],{"class":186},[68,6214,190],{"class":74},[68,6216,669],{"class":339},[68,6218,672],{"class":74},[68,6220,193],{"class":186},[68,6222,229],{"class":74},[68,6224,232],{"class":74},[68,6226,180],{"class":74},[68,6228,6230,6232,6234,6236,6238,6240],{"class":70,"line":6229},67,[68,6231,240],{"class":186},[68,6233,202],{"class":74},[68,6235,245],{"class":173},[68,6237,208],{"class":74},[68,6239,250],{"class":186},[68,6241,159],{"class":74},[68,6243,6245],{"class":70,"line":6244},68,[68,6246,258],{"class":74},[68,6248,6250],{"class":70,"line":6249},69,[68,6251,706],{"class":74},[18,6253,6254,6255,6257],{},"运行 ",[47,6256,716],{}," 即得一页 PDF，四行各用不同方式切分。",[14,6259,6261],{"id":6260},"为什么是-12","为什么是 12",[18,6263,6264,6265,6268],{},"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 特意沿用这个习语——",[30,6266,6267],{},"PDF 布局和网页布局在思维模型上并无本质差别","，哪怕输出的是固定宽度的纸张。",[14,6270,6271],{"id":6271},"把数学写清楚",[18,6273,6274,6275,6278,6279,6282,6283,6286,6287,6290],{},"A4 纵向、四边各 15 mm 的话，可用宽度是 180 mm。行内的 ",[47,6276,6277],{},"Col(4)"," 占 4/12，也就是 60 mm。",[47,6280,6281],{},"Col(8)"," 占 120 mm。列与列之间默认没有槽宽 (gutter)。如果想要留白，就在较短的列里放一个 ",[47,6284,6285],{},"c.Spacer","，或把一个 ",[47,6288,6289],{},"Col(1)"," 留空。",[18,6292,6293,6294,6297,6298,6301,6302,720],{},"宽度在构建期按百分比计算（相关实现在 ",[47,6295,6296],{},"gpdf/template/grid.go","），布局引擎再用\"当前页宽减去边距\"换算为实际的点值。也就是说同样的 ",[47,6299,6300],{},"r.Col(6, fn)","，在 A4 和 Letter 上物理宽度不同，但",[30,6303,6304],{},"占行的比例始终不变",[14,6306,6308],{"id":6307},"总和不是-12-会怎样","总和不是 12 会怎样",[18,6310,6311],{},"gpdf 并不校验 span 的合计。这是有意的。",[1111,6313,6314,6320],{},[885,6315,6316,6319],{},[30,6317,6318],{},"合计 \u003C 12","：行的右侧留空。想让元素贴在左边、其余留白时用得上。",[885,6321,6322,6325],{},[30,6323,6324],{},"合计 > 12","：最后一列溢出右边距。通常是 bug。PDF 看起来会错位，但不会 crash。",[18,6327,6328,6329,6332,6333,6336,6337,6339],{},"多数布局正好是每行 12，因为那样能填满页面。但你想\"在行中间只放一块 6 宽的内容\"时，最直接的写法就是 ",[47,6330,6331],{},"Col(3)"," 空、",[47,6334,6335],{},"Col(6)"," 内容、",[47,6338,6331],{}," 空——这个网格本来就是为这种简写设计的。",[14,6341,6343],{"id":6342},"autorow-与-row-的区别","AutoRow 与 Row 的区别",[18,6345,6346,6349],{},[47,6347,6348],{},"page.AutoRow(fn)"," 的高度随最高的列伸展。大多数行都应该用它。",[18,6351,6352,6355],{},[47,6353,6354],{},"page.Row(height, fn)"," 固定高度，超出的内容会被裁剪。用在\"发票表头必须刚好 30 mm 以便后续装订对齐\"之类的场景——视觉一致性优先于内容自由度。",[59,6357,6359],{"className":61,"code":6358,"language":63,"meta":64,"style":64},"page.Row(document.Mm(30), func(r *template.RowBuilder) {\n    r.Col(8, func(c *template.ColBuilder) {\n        c.Text(\"Logo\")\n    })\n    r.Col(4, func(c *template.ColBuilder) {\n        c.Text(\"发票号\")\n    })\n})\n",[47,6360,6361,6400,6430,6449,6453,6483,6502,6506],{"__ignoreMap":64},[68,6362,6363,6365,6367,6369,6371,6373,6375,6377,6379,6382,6384,6386,6388,6390,6392,6394,6396,6398],{"class":70,"line":71},[68,6364,1924],{"class":186},[68,6366,202],{"class":74},[68,6368,2108],{"class":173},[68,6370,208],{"class":74},[68,6372,320],{"class":186},[68,6374,202],{"class":74},[68,6376,334],{"class":173},[68,6378,208],{"class":74},[68,6380,6381],{"class":339},"30",[68,6383,533],{"class":74},[68,6385,479],{"class":74},[68,6387,442],{"class":441},[68,6389,445],{"class":74},[68,6391,448],{"class":78},[68,6393,202],{"class":74},[68,6395,453],{"class":78},[68,6397,456],{"class":74},[68,6399,180],{"class":74},[68,6401,6402,6404,6406,6408,6410,6412,6414,6416,6418,6420,6422,6424,6426,6428],{"class":70,"line":82},[68,6403,3659],{"class":186},[68,6405,202],{"class":74},[68,6407,469],{"class":173},[68,6409,208],{"class":74},[68,6411,6023],{"class":339},[68,6413,190],{"class":74},[68,6415,479],{"class":74},[68,6417,482],{"class":441},[68,6419,445],{"class":74},[68,6421,448],{"class":78},[68,6423,202],{"class":74},[68,6425,491],{"class":78},[68,6427,456],{"class":74},[68,6429,180],{"class":74},[68,6431,6432,6434,6436,6438,6440,6442,6445,6447],{"class":70,"line":89},[68,6433,3690],{"class":186},[68,6435,202],{"class":74},[68,6437,506],{"class":173},[68,6439,208],{"class":74},[68,6441,211],{"class":74},[68,6443,6444],{"class":214},"Logo",[68,6446,211],{"class":74},[68,6448,159],{"class":74},[68,6450,6451],{"class":70,"line":99},[68,6452,575],{"class":74},[68,6454,6455,6457,6459,6461,6463,6465,6467,6469,6471,6473,6475,6477,6479,6481],{"class":70,"line":111},[68,6456,3659],{"class":186},[68,6458,202],{"class":74},[68,6460,469],{"class":173},[68,6462,208],{"class":74},[68,6464,5814],{"class":339},[68,6466,190],{"class":74},[68,6468,479],{"class":74},[68,6470,482],{"class":441},[68,6472,445],{"class":74},[68,6474,448],{"class":78},[68,6476,202],{"class":74},[68,6478,491],{"class":78},[68,6480,456],{"class":74},[68,6482,180],{"class":74},[68,6484,6485,6487,6489,6491,6493,6495,6498,6500],{"class":70,"line":121},[68,6486,3690],{"class":186},[68,6488,202],{"class":74},[68,6490,506],{"class":173},[68,6492,208],{"class":74},[68,6494,211],{"class":74},[68,6496,6497],{"class":214},"发票号",[68,6499,211],{"class":74},[68,6501,159],{"class":74},[68,6503,6504],{"class":70,"line":126},[68,6505,575],{"class":74},[68,6507,6508],{"class":70,"line":136},[68,6509,3717],{"class":74},[14,6511,6512],{"id":6512},"网格没做的事",[18,6514,6515,6516,6518],{},"不支持嵌套。",[47,6517,491],{}," 接收内容元素 (Text / Image / Table / List / Spacer)，但不能塞一个子行进去。看似需要嵌套的结构，通常用页面层面的两个兄弟行就能更清爽地表达。",[18,6520,6521,6522,6525,6526,720],{},"没有偏移列。Bootstrap 的 ",[47,6523,6524],{},".offset-2"," 在 gpdf 里不存在。要把内容推到右边，就在左边放一个空的 ",[47,6527,6528],{},"Col(n)",[18,6530,6531],{},"没有断点。PDF 页面不会自适应。在任何设备上打开都是同一版式——因为输出是定坐标的光栅，而不是会重新流式布局的 DOM。",[18,6533,6534,6537],{},[30,6535,6536],{},"这些\"没有\"正是设计的核心","。网格少一个特性，读 PDF 结果时就少一类需要推理的歧义。",[14,6539,1209],{"id":1209},[1111,6541,6542,6547,6553],{},[885,6543,6544,6546],{},[22,6545,1223],{"href":1222}," — 在网格列里处理 CJK",[885,6548,6549,6552],{},[22,6550,6551],{"href":2894},"Go PDF 库横评 2026"," — Builder API 与 gofpdf / gopdf / Maroto 的对比",[885,6554,6555,6560],{},[22,6556,6559],{"href":6557,"rel":6558},"https://gpdf.dev/zh/docs/guide/layout",[26],"布局指南"," — 行、列、间距的完整参考",[14,6562,2859],{"id":2858},[18,6564,6565],{},"gpdf 是一个 Go 语言的 PDF 生成库。MIT 许可、零外部依赖、原生 CJK 支持。",[59,6567,6568],{"className":1248,"code":1249,"language":1250,"meta":64,"style":64},[47,6569,6570],{"__ignoreMap":64},[68,6571,6572,6574,6576],{"class":70,"line":71},[68,6573,63],{"class":78},[68,6575,1259],{"class":214},[68,6577,1262],{"class":214},[18,6579,6580,1269,6584],{},[22,6581,6583],{"href":24,"rel":6582},[26],"⭐ 在 GitHub 加星",[22,6585,6587],{"href":1272,"rel":6586},[26],"查看文档",[1276,6589,6590],{},"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":64,"searchDepth":82,"depth":82,"links":6592},[6593,6594,6595,6596,6597,6598,6599,6600,6601,6602],{"id":5278,"depth":82,"text":5278},{"id":5291,"depth":82,"text":5291},{"id":57,"depth":82,"text":57},{"id":6260,"depth":82,"text":6261},{"id":6271,"depth":82,"text":6271},{"id":6307,"depth":82,"text":6308},{"id":6342,"depth":82,"text":6343},{"id":6512,"depth":82,"text":6512},{"id":1209,"depth":82,"text":1209},{"id":2858,"depth":82,"text":2859},"gpdf 的 12 列网格用 r.Col(span, fn) 接收 1–12 的整数。列宽为 span/12 的比例，没有断点、没有槽宽间距，为 PDF 固定宽度设计。",{"name":6605,"totalTime":5247,"tools":6606,"steps":6607},"用 gpdf 的 12 列网格布局一页 PDF",[1299,131],[6608,6611,6614,6617,6620],{"name":6609,"text":6610},"在页面上开一行","若要让行高随最高的列自动伸展，调用 page.AutoRow(fn)；若要固定行高，调用 page.Row(height, fn)。",{"name":6612,"text":6613},"用 r.Col(span, fn) 声明列","在行内按列的数量调用 r.Col(span, fn)。span 是 1 到 12 的整数，表示该列占行宽的份额。",{"name":6615,"text":6616},"让每行的 span 总和不超过 12","若总和小于 12，右侧留空。若总和大于 12，最后一列会越过右边距——通常是 bug 的信号。",{"name":6618,"text":6619},"在列里填入内容","在 ColBuilder 的回调里调用 c.Text、c.Image、c.Table 或 c.Spacer。内容按添加顺序垂直堆叠。",{"name":6621,"text":6622},"开启下一行","下一个视觉行再调用一次 page.AutoRow。行与行彼此独立——4+8 的行可以直接压在 3+3+3+3 的行上面。",{},"/zh/blog/12-column-grid",{"title":5272,"description":6603},"zh/blog/005.12-column-grid",[1323,1325,6628],"templates","IGZV8tU2CRvHTI6HGihwe-y0oR-_SOZHikShsgE1lwk",{"id":6631,"title":6632,"author":6633,"body":6634,"date":5756,"description":8360,"draft":1293,"extension":1294,"howTo":1317,"image":1317,"meta":8361,"navigation":85,"path":8362,"seo":8363,"stem":8364,"tags":8365,"updated":1317,"__hash__":8367},"blogZh/zh/blog/006.go-pdf-fpdf-archived.md","go-pdf/fpdf 也归档了。2026 年的 Go PDF 栈长这样。",{"name":8,"url":9},{"type":11,"value":6635,"toc":8344},[6636,6638,6660,6663,6682,6685,6701,6711,6715,6718,6751,6757,6761,6767,6782,6785,6794,6800,6804,6807,6982,6985,6997,7009,7015,7021,7024,7028,7034,7037,7077,7080,7261,7274,7278,7293,7296,7301,7620,7625,8004,8019,8028,8031,8038,8120,8127,8133,8136,8144,8147,8196,8203,8206,8213,8222,8228,8230,8241,8247,8257,8275,8283,8289,8291,8293,8305,8313,8315,8341],[14,6637,1336],{"id":1335},[18,6639,6640,6643,6644,6646,6647,6650,6651,6646,6653,6656,6657,6659],{},[47,6641,6642],{},"fpdf"," 系两个还在维护的分支都已经只读。",[47,6645,1354],{}," 于 ",[30,6648,6649],{},"2021 年 9 月"," 归档；社区 fork ",[47,6652,1432],{},[30,6654,6655],{},"2025 年"," 归档。不会再有\"下一任维护者\"。新项目的现代默认是 ",[30,6658,27],{},"：纯 Go、零外部依赖、CJK 原生、在常见工作负载上快 10–30 倍。这篇文章是 2026 年的局势图，以及对 \"何时选 gpdf、何时不选\" 的诚实回答。",[14,6661,6662],{"id":6662},"现状",[18,6664,6665,6666,6669,6670,6674,6675,6678,6679,6681],{},"上周同事敲下 ",[47,6667,6668],{},"go get github.com/go-pdf/fpdf","，在 GitHub 的横幅前停住：",[6671,6672,6673],"em",{},"\"This repository has been archived by the owner. It is now read-only.\""," —— 这本来应该是 ",[6671,6676,6677],{},"修复版","，是 2021 年被归档的 ",[47,6680,1354],{}," 的社区接棒 fork。",[18,6683,6684],{},"它也归档了。README 现在建议去找别的库。",[18,6686,6687,6688,6691,6692,6694,6695,720,6697,6700],{},"过去五年里，如果你用 Go 写服务、输出 PDF（发票、报表、运单、增值税发票、电子归档文件），",[47,6689,6690],{},"go.mod"," 最底下那一行几乎一定是这两个之一。Stack Overflow 的回答指向 ",[47,6693,1354],{},"；更新一点的教程指向 ",[47,6696,1432],{},[30,6698,6699],{},"现在两个都是供应链上的负债"," —— CVE 分诊、Go 版本跟进、性能修复、规范更新，全部冻结。",[18,6702,6703,6704,6707,6708],{},"这篇不是又一份逐行迁移指南——",[22,6705,6706],{"href":2823},"那份我们已经写过了","。这篇想回答迁移指南没回答的问题：",[30,6709,6710],{},"2026 年用 Go 生成 PDF，到底应该选什么？生态为什么会走到这一步？",[14,6712,6714],{"id":6713},"归档-在生产中意味着什么","\"归档\" 在生产中意味着什么",[18,6716,6717],{},"GitHub 的 \"archived\" 标签看起来温柔。对于一个在你 import 图里的库，它其实意味着四件很具体的事：",[882,6719,6720,6726,6736,6742],{},[885,6721,6722,6725],{},[30,6723,6724],{},"不会有安全补丁","。TTF 解析器里出现内存安全问题，不会有人合并修复到上游。你可以自己 fork 来修，大多数团队不会。",[885,6727,6728,6731,6732,6735],{},[30,6729,6730],{},"不会跟进 Go 工具链","。Go 1.25 的循环变量语义今天在 gofpdf 上跑得好好的。但明天 ",[47,6733,6734],{},"for range"," 或某个标准库 API 废弃之类的改动如果把它弄坏，修补只读仓库的 fork 就是你自己的事。",[885,6737,6738,6741],{},[30,6739,6740],{},"不会跟进规范","。PDF 2.0（ISO 32000-2）在 2020 年就定稿了。gofpdf 大部分实现停留在 PDF 1.7。页面级关联文件、丰富的 XMP 元数据、现代数字签名（PAdES-B-LT），要么缺失，要么靠第三方胶水拼上去。",[885,6743,6744,6747,6748,6750],{},[30,6745,6746],{},"CJK 不会再前进","。gofpdf 的 Unicode 路径是在 \"单字节字体\" 的旧设计上后加的。能跑，但在大多数真实配置下会嵌入完整字体而不是子集；某些 CJK TTF 上会触发 glyph-id 冲突，输出乱码。",[47,6749,1432],{}," 继承了同样的架构。",[18,6752,6753,6754,720],{},"安全和前向兼容两条，是合规会议上最刺眼的。\"我们的 PDF 库已归档且无人发 CVE 补丁\"，不是审计想听的回答。",[30,6755,6756],{},"如果你的 PDF 还涉及电子会计档案或增值税电子发票的保存期，这个问题不能再拖",[14,6758,6760],{"id":6759},"为什么两个-fork-都死了","为什么两个 fork 都死了",[18,6762,6763,6764,720],{},"把归档简单归咎于维护者倦怠很容易——一位累了的 reviewer，一个 bus factor 为 1 的维护人下线。那是原因之一，但不是全貌。",[30,6765,6766],{},"架构让追赶变得困难",[18,6768,6769,6771,6772,853,6775,853,6778,6781],{},[47,6770,1354],{}," 是 FPDF 的移植——一个 2002 年的 PHP 库。PHP 原版通过在页面上推游标、按流程吐内容：",[47,6773,6774],{},"SetXY(x, y)",[47,6776,6777],{},"Cell(w, h, text)",[47,6779,6780],{},"Ln(h)","。这个模型在 2002 年的 PHP 下是合理折中——当时的替代方案是裸 PostScript 或商业工具包。移植到 Go 时，保留了游标、保留了单字节字体表、保留了手动分页管理。",[18,6783,6784],{},"每过一年，\"人们想生成的东西\"和\"游标模型能表达的东西\"之间的落差就变大一点。发票是表格。报告是带重复页眉页脚的网格。运单是二维码 + 本地语言文本。游标被辅助函数包起来，辅助函数又被教程包起来，到 2023 年左右，大多数人\"针对 gofpdf 写的代码\"其实不是 gofpdf——是每个团队自己的胶水层，试图把游标装成布局引擎。",[18,6786,6787,6789,6790,6793],{},[47,6788,1432],{}," 继承了这一切。这个 fork 重构了内部实现、修了一些老 bug，但",[30,6791,6792],{},"没法改公开 API 的形状","——一动就要让所有下游工程崩。库的外形冻结在 2002 年的 PHP 里，维护这个形状的成本比收益涨得更快。",[18,6795,6796,6799],{},[30,6797,6798],{},"所以","：两位维护者，两次归档，一个架构上的原因。2026 年要重新来一遍，就得选一个匹配今天 PDF 生成方式的方法——今天的方式更像搭网页，不像驱动绘图仪。",[14,6801,6803],{"id":6802},"_2026-年-go-pdf-的局势","2026 年 Go PDF 的局势",[18,6805,6806],{},"在推荐任何东西之前，先把场面列出来。\"maintained\"（在维护）这里的意思是\"最近 6 个月有提交、issue 有响应\"。",[740,6808,6809,6829],{},[743,6810,6811],{},[746,6812,6813,6816,6819,6821,6824,6827],{},[749,6814,6815],{},"库",[749,6817,6818],{},"状态 (2026-04)",[749,6820,5121],{},[749,6822,6823],{},"CJK 原生",[749,6825,6826],{},"零依赖",[749,6828,6113],{},[758,6830,6831,6854,6874,6893,6916,6937,6960],{},[746,6832,6833,6837,6842,6845,6848,6851],{},[763,6834,6835],{},[47,6836,1354],{},[763,6838,6839],{},[30,6840,6841],{},"2021 已归档",[763,6843,6844],{},"MIT",[763,6846,6847],{},"后加的",[763,6849,6850],{},"是",[763,6852,6853],{},"原版。在大多数语言的搜索结果里至今仍是第一。",[746,6855,6856,6860,6865,6867,6869,6871],{},[763,6857,6858],{},[47,6859,1432],{},[763,6861,6862],{},[30,6863,6864],{},"2025 已归档",[763,6866,6844],{},[763,6868,6847],{},[763,6870,6850],{},[763,6872,6873],{},"上者的社区 fork。同样的架构，同样的天花板。",[746,6875,6876,6880,6883,6885,6888,6890],{},[763,6877,6878],{},[47,6879,1435],{},[763,6881,6882],{},"维护中",[763,6884,6844],{},[763,6886,6887],{},"部分",[763,6889,6850],{},[763,6891,6892],{},"低层库，你自己写坐标。适合表单叠加。",[746,6894,6895,6901,6903,6905,6908,6911],{},[763,6896,6897,6900],{},[47,6898,6899],{},"johnfercher/maroto"," v2",[763,6902,6882],{},[763,6904,6844],{},[763,6906,6907],{},"经 gofpdf",[763,6909,6910],{},"否",[763,6912,6913,6914,720],{},"网格优先的 builder，但底层用 ",[47,6915,1432],{},[746,6917,6918,6923,6925,6930,6932,6934],{},[763,6919,6920],{},[47,6921,6922],{},"unidoc/unipdf",[763,6924,6882],{},[763,6926,6927],{},[30,6928,6929],{},"商业",[763,6931,6850],{},[763,6933,6910],{},[763,6935,6936],{},"功能齐全的 PDF SDK。商用需付费许可。",[746,6938,6939,6945,6947,6950,6952,6957],{},[763,6940,6941,6944],{},[47,6942,6943],{},"chromedp"," + Chromium",[763,6946,6882],{},[763,6948,6949],{},"MIT + Chrome",[763,6951,6850],{},[763,6953,6954],{},[30,6955,6956],{},"否——自带浏览器",[763,6958,6959],{},"用无头 Chrome 做 HTML→PDF。运行时巨大。",[746,6961,6962,6966,6968,6970,6975,6979],{},[763,6963,6964],{},[47,6965,27],{},[763,6967,6882],{},[763,6969,6844],{},[763,6971,6972],{},[30,6973,6974],{},"原生",[763,6976,6977],{},[30,6978,6850],{},[763,6980,6981],{},"纯 Go 重写。Builder API、12 列网格。",[18,6983,6984],{},"只看这张表就能得出几个结论：",[18,6986,6987,6990,6991,6993,6994,6996],{},[30,6988,6989],{},"目前\"仍在维护\"的每个方案，要么是商业许可、要么自带巨大运行时、要么坐在一个即将过时的地基上","。例外是 ",[47,6992,1435],{},"——确实在维护、依赖也轻。但它是坐标级库，你又回到了换个包名继续写 ",[47,6995,1928],{}," 的老路。",[18,6998,6999,7002,7003,7005,7006,7008],{},[30,7000,7001],{},"Maroto v2 是个 API 不错的网格优先 builder","。问题在于 ",[47,7004,6690],{}," 底下是 ",[47,7007,1432],{},"。fpdf 的性能天花板和 CJK 限制，就是 Maroto 的天花板。v3 可能摆脱它，但还没出来。",[18,7010,7011,7014],{},[30,7012,7013],{},"unipdf 功能丰富，但对商业项目不是 MIT 兼容","。按席位或按部署收费。如果你的营收撑得起这个账单，它是合理选择；对 OSS 副业或早期创业公司，许可证数学算不过来。",[18,7016,7017,7020],{},[30,7018,7019],{},"chromedp 能跑，但你是在发布一个浏览器","。100 MB 的基础镜像变成 1 GB+，Serverless 冷启动痛苦，字体还得单独装进容器。好处是可以复用 React 模板；坏处是为了出一张发票一直在跑 Chromium。",[18,7022,7023],{},"缺口很明显：一个纯 Go、零依赖、CJK 原生、网格优先、不用商业许可也不用浏览器运行时的库。gpdf 就是为此而生。",[14,7025,7027],{"id":7026},"gpdf-是什么","gpdf 是什么",[18,7029,7030,7031,7033],{},"gpdf（",[47,7032,131],{},"）是一次干净重写，不是 fork。PDF 线格式写入、布局引擎、TrueType 子集化器——全部用纯 Go 从零写。",[18,7035,7036],{},"对大多数团队最重要的三个属性：",[1111,7038,7039,7052,7065],{},[885,7040,7041,720,7044,7047,7048,7051],{},[30,7042,7043],{},"纯 Go，无 CGO",[47,7045,7046],{},"go build"," 是静态的。",[47,7049,7050],{},"GOOS=linux GOARCH=arm64 go build"," 在 MacBook 上不配工具链就能过。Docker 镜像保持小——12 MB 的 distroless 容器跑得动。",[885,7053,7054,720,7057,7060,7061,7064],{},[30,7055,7056],{},"零外部依赖",[47,7058,7059],{},"go get github.com/gpdf-dev/gpdf"," 之后 ",[47,7062,7063],{},"go mod graph"," 只有一行：gpdf 自己。核心只用标准库。（HTML→PDF 或数字签名的可选扩展会带少量依赖，但都是选择性的。）",[885,7066,7067,720,7070,7072,7073,7076],{},[30,7068,7069],{},"原生 CJK",[47,7071,353],{}," 在构建 document 时注册一个 TrueType 字体。子集嵌入在渲染时自动发生。",[30,7074,7075],{},"一张 200 字的中文/日文发票里嵌入的字体约 30 KB 子集","，不是 5 MB 的完整字体。",[18,7078,7079],{},"API 形态是声明式的。你描述一棵行/列的树，布局引擎负责放置。网格是 12 列——Bootstrap 从 2011 年用到今天的同一个 idiom。写过哪怕一行 HTML/CSS，gpdf 的 API 都会让你觉得眼熟：",[59,7081,7083],{"className":61,"code":7082,"language":63,"meta":64,"style":64},"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",[47,7084,7085,7100,7124,7154,7192,7196,7226,7253,7257],{"__ignoreMap":64},[68,7086,7087,7090,7092,7094,7096,7098],{"class":70,"line":71},[68,7088,7089],{"class":186},"page ",[68,7091,196],{"class":74},[68,7093,416],{"class":186},[68,7095,202],{"class":74},[68,7097,421],{"class":173},[68,7099,424],{"class":74},[68,7101,7102,7104,7106,7108,7110,7112,7114,7116,7118,7120,7122],{"class":70,"line":82},[68,7103,1924],{"class":186},[68,7105,202],{"class":74},[68,7107,435],{"class":173},[68,7109,438],{"class":74},[68,7111,442],{"class":441},[68,7113,445],{"class":74},[68,7115,448],{"class":78},[68,7117,202],{"class":74},[68,7119,453],{"class":78},[68,7121,456],{"class":74},[68,7123,180],{"class":74},[68,7125,7126,7128,7130,7132,7134,7136,7138,7140,7142,7144,7146,7148,7150,7152],{"class":70,"line":89},[68,7127,3659],{"class":186},[68,7129,202],{"class":74},[68,7131,469],{"class":173},[68,7133,208],{"class":74},[68,7135,6023],{"class":339},[68,7137,190],{"class":74},[68,7139,479],{"class":74},[68,7141,482],{"class":441},[68,7143,445],{"class":74},[68,7145,448],{"class":78},[68,7147,202],{"class":74},[68,7149,491],{"class":78},[68,7151,456],{"class":74},[68,7153,180],{"class":74},[68,7155,7156,7158,7160,7162,7164,7166,7168,7170,7172,7174,7176,7178,7180,7182,7184,7186,7188,7190],{"class":70,"line":99},[68,7157,3690],{"class":186},[68,7159,202],{"class":74},[68,7161,506],{"class":173},[68,7163,208],{"class":74},[68,7165,211],{"class":74},[68,7167,5553],{"class":214},[68,7169,211],{"class":74},[68,7171,190],{"class":74},[68,7173,520],{"class":186},[68,7175,202],{"class":74},[68,7177,525],{"class":173},[68,7179,208],{"class":74},[68,7181,5568],{"class":339},[68,7183,533],{"class":74},[68,7185,520],{"class":186},[68,7187,202],{"class":74},[68,7189,540],{"class":173},[68,7191,543],{"class":74},[68,7193,7194],{"class":70,"line":111},[68,7195,575],{"class":74},[68,7197,7198,7200,7202,7204,7206,7208,7210,7212,7214,7216,7218,7220,7222,7224],{"class":70,"line":121},[68,7199,3659],{"class":186},[68,7201,202],{"class":74},[68,7203,469],{"class":173},[68,7205,208],{"class":74},[68,7207,5814],{"class":339},[68,7209,190],{"class":74},[68,7211,479],{"class":74},[68,7213,482],{"class":441},[68,7215,445],{"class":74},[68,7217,448],{"class":78},[68,7219,202],{"class":74},[68,7221,491],{"class":78},[68,7223,456],{"class":74},[68,7225,180],{"class":74},[68,7227,7228,7230,7232,7234,7236,7238,7240,7242,7244,7246,7248,7251],{"class":70,"line":126},[68,7229,3690],{"class":186},[68,7231,202],{"class":74},[68,7233,506],{"class":173},[68,7235,208],{"class":74},[68,7237,211],{"class":74},[68,7239,5756],{"class":214},[68,7241,211],{"class":74},[68,7243,190],{"class":74},[68,7245,520],{"class":186},[68,7247,202],{"class":74},[68,7249,7250],{"class":173},"AlignRight",[68,7252,543],{"class":74},[68,7254,7255],{"class":70,"line":136},[68,7256,575],{"class":74},[68,7258,7259],{"class":70,"line":146},[68,7260,3717],{"class":74},[18,7262,7263,7264,7266,7267,7270,7271,7273],{},"网格细节见 ",[22,7265,5272],{"href":6624},"。一句话版本：",[47,7268,7269],{},"Col(span, fn)"," 接受 1–12 的 span，",[47,7272,5300],{}," 是该列在行宽中的比例。",[14,7275,7277],{"id":7276},"最小的-go-pdffpdf-gpdf-差分","最小的 go-pdf/fpdf → gpdf 差分",[18,7279,7280,7281,3908,7283,7285,7286,7288,7289,7292],{},"如果你是从 ",[47,7282,1432],{},[47,7284,1354],{},"）过来的，好消息：API 表面几乎一样——",[47,7287,1432],{}," 在调用层几乎没改过什么。迁移到 gpdf 的步骤和 ",[22,7290,7291],{"href":2823},"gofpdf 指南","一致，从改一行 import 路径开始。",[18,7294,7295],{},"最小差分——一个\"生成 PDF\"的 HTTP handler：",[18,7297,7298],{},[30,7299,7300],{},"Before — go-pdf/fpdf:",[59,7302,7304],{"className":61,"code":7303,"language":63,"meta":64,"style":64},"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",[47,7305,7306,7312,7316,7322,7331,7335,7344,7348,7352,7390,7439,7450,7483,7512,7516,7552,7581,7612,7616],{"__ignoreMap":64},[68,7307,7308,7310],{"class":70,"line":71},[68,7309,75],{"class":74},[68,7311,79],{"class":78},[68,7313,7314],{"class":70,"line":82},[68,7315,86],{"emptyLinePlaceholder":85},[68,7317,7318,7320],{"class":70,"line":89},[68,7319,93],{"class":92},[68,7321,96],{"class":74},[68,7323,7324,7326,7329],{"class":70,"line":99},[68,7325,102],{"class":74},[68,7327,7328],{"class":78},"net/http",[68,7330,108],{"class":74},[68,7332,7333],{"class":70,"line":111},[68,7334,86],{"emptyLinePlaceholder":85},[68,7336,7337,7339,7342],{"class":70,"line":121},[68,7338,102],{"class":74},[68,7340,7341],{"class":78},"github.com/go-pdf/fpdf",[68,7343,108],{"class":74},[68,7345,7346],{"class":70,"line":126},[68,7347,159],{"class":74},[68,7349,7350],{"class":70,"line":136},[68,7351,86],{"emptyLinePlaceholder":85},[68,7353,7354,7356,7359,7361,7363,7366,7368,7371,7373,7376,7378,7381,7383,7386,7388],{"class":70,"line":146},[68,7355,170],{"class":74},[68,7357,7358],{"class":173}," handler",[68,7360,208],{"class":74},[68,7362,1872],{"class":441},[68,7364,7365],{"class":78}," http",[68,7367,202],{"class":74},[68,7369,7370],{"class":78},"ResponseWriter",[68,7372,190],{"class":74},[68,7374,7375],{"class":441}," r",[68,7377,445],{"class":74},[68,7379,7380],{"class":78},"http",[68,7382,202],{"class":74},[68,7384,7385],{"class":78},"Request",[68,7387,456],{"class":74},[68,7389,180],{"class":74},[68,7391,7392,7395,7397,7400,7402,7405,7407,7409,7412,7414,7416,7419,7422,7424,7426,7428,7430,7432,7434,7437],{"class":70,"line":156},[68,7393,7394],{"class":186},"    pdf ",[68,7396,196],{"class":74},[68,7398,7399],{"class":186}," fpdf",[68,7401,202],{"class":74},[68,7403,7404],{"class":173},"New",[68,7406,208],{"class":74},[68,7408,211],{"class":74},[68,7410,7411],{"class":214},"P",[68,7413,211],{"class":74},[68,7415,190],{"class":74},[68,7417,7418],{"class":74}," \"",[68,7420,7421],{"class":214},"mm",[68,7423,211],{"class":74},[68,7425,190],{"class":74},[68,7427,7418],{"class":74},[68,7429,302],{"class":214},[68,7431,211],{"class":74},[68,7433,190],{"class":74},[68,7435,7436],{"class":74}," \"\"",[68,7438,159],{"class":74},[68,7440,7441,7444,7446,7448],{"class":70,"line":162},[68,7442,7443],{"class":186},"    pdf",[68,7445,202],{"class":74},[68,7447,421],{"class":173},[68,7449,424],{"class":74},[68,7451,7452,7454,7456,7458,7460,7462,7465,7467,7469,7471,7474,7476,7478,7481],{"class":70,"line":167},[68,7453,7443],{"class":186},[68,7455,202],{"class":74},[68,7457,1756],{"class":173},[68,7459,208],{"class":74},[68,7461,211],{"class":74},[68,7463,7464],{"class":214},"Arial",[68,7466,211],{"class":74},[68,7468,190],{"class":74},[68,7470,7418],{"class":74},[68,7472,7473],{"class":214},"B",[68,7475,211],{"class":74},[68,7477,190],{"class":74},[68,7479,7480],{"class":339}," 16",[68,7482,159],{"class":74},[68,7484,7485,7487,7489,7491,7493,7496,7498,7501,7503,7505,7508,7510],{"class":70,"line":183},[68,7486,7443],{"class":186},[68,7488,202],{"class":74},[68,7490,1932],{"class":173},[68,7492,208],{"class":74},[68,7494,7495],{"class":339},"40",[68,7497,190],{"class":74},[68,7499,7500],{"class":339}," 10",[68,7502,190],{"class":74},[68,7504,7418],{"class":74},[68,7506,7507],{"class":214},"Hello, World!",[68,7509,211],{"class":74},[68,7511,159],{"class":74},[68,7513,7514],{"class":70,"line":221},[68,7515,86],{"emptyLinePlaceholder":85},[68,7517,7518,7521,7523,7526,7529,7532,7534,7536,7539,7541,7543,7545,7548,7550],{"class":70,"line":237},[68,7519,7520],{"class":186},"    w",[68,7522,202],{"class":74},[68,7524,7525],{"class":173},"Header",[68,7527,7528],{"class":74},"().",[68,7530,7531],{"class":173},"Set",[68,7533,208],{"class":74},[68,7535,211],{"class":74},[68,7537,7538],{"class":214},"Content-Type",[68,7540,211],{"class":74},[68,7542,190],{"class":74},[68,7544,7418],{"class":74},[68,7546,7547],{"class":214},"application/pdf",[68,7549,211],{"class":74},[68,7551,159],{"class":74},[68,7553,7554,7556,7558,7560,7563,7565,7567,7569,7571,7573,7575,7577,7579],{"class":70,"line":255},[68,7555,224],{"class":92},[68,7557,193],{"class":186},[68,7559,196],{"class":74},[68,7561,7562],{"class":186}," pdf",[68,7564,202],{"class":74},[68,7566,1936],{"class":173},[68,7568,208],{"class":74},[68,7570,1872],{"class":186},[68,7572,672],{"class":74},[68,7574,193],{"class":186},[68,7576,229],{"class":74},[68,7578,232],{"class":74},[68,7580,180],{"class":74},[68,7582,7583,7586,7588,7591,7593,7595,7597,7600,7602,7604,7607,7610],{"class":70,"line":261},[68,7584,7585],{"class":186},"        http",[68,7587,202],{"class":74},[68,7589,7590],{"class":173},"Error",[68,7592,208],{"class":74},[68,7594,1872],{"class":186},[68,7596,190],{"class":74},[68,7598,7599],{"class":186}," err",[68,7601,202],{"class":74},[68,7603,7590],{"class":173},[68,7605,7606],{"class":74},"(),",[68,7608,7609],{"class":339}," 500",[68,7611,159],{"class":74},[68,7613,7614],{"class":70,"line":266},[68,7615,258],{"class":74},[68,7617,7618],{"class":70,"line":285},[68,7619,706],{"class":74},[18,7621,7622],{},[30,7623,7624],{},"After — gpdf:",[59,7626,7628],{"className":61,"code":7627,"language":63,"meta":64,"style":64},"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",[47,7629,7630,7636,7640,7646,7654,7658,7666,7674,7682,7686,7690,7722,7736,7754,7784,7788,7792,7806,7830,7860,7899,7903,7907,7911,7941,7970,7996,8000],{"__ignoreMap":64},[68,7631,7632,7634],{"class":70,"line":71},[68,7633,75],{"class":74},[68,7635,79],{"class":78},[68,7637,7638],{"class":70,"line":82},[68,7639,86],{"emptyLinePlaceholder":85},[68,7641,7642,7644],{"class":70,"line":89},[68,7643,93],{"class":92},[68,7645,96],{"class":74},[68,7647,7648,7650,7652],{"class":70,"line":99},[68,7649,102],{"class":74},[68,7651,7328],{"class":78},[68,7653,108],{"class":74},[68,7655,7656],{"class":70,"line":111},[68,7657,86],{"emptyLinePlaceholder":85},[68,7659,7660,7662,7664],{"class":70,"line":121},[68,7661,102],{"class":74},[68,7663,131],{"class":78},[68,7665,108],{"class":74},[68,7667,7668,7670,7672],{"class":70,"line":126},[68,7669,102],{"class":74},[68,7671,141],{"class":78},[68,7673,108],{"class":74},[68,7675,7676,7678,7680],{"class":70,"line":136},[68,7677,102],{"class":74},[68,7679,151],{"class":78},[68,7681,108],{"class":74},[68,7683,7684],{"class":70,"line":146},[68,7685,159],{"class":74},[68,7687,7688],{"class":70,"line":156},[68,7689,86],{"emptyLinePlaceholder":85},[68,7691,7692,7694,7696,7698,7700,7702,7704,7706,7708,7710,7712,7714,7716,7718,7720],{"class":70,"line":162},[68,7693,170],{"class":74},[68,7695,7358],{"class":173},[68,7697,208],{"class":74},[68,7699,1872],{"class":441},[68,7701,7365],{"class":78},[68,7703,202],{"class":74},[68,7705,7370],{"class":78},[68,7707,190],{"class":74},[68,7709,7375],{"class":441},[68,7711,445],{"class":74},[68,7713,7380],{"class":78},[68,7715,202],{"class":74},[68,7717,7385],{"class":78},[68,7719,456],{"class":74},[68,7721,180],{"class":74},[68,7723,7724,7726,7728,7730,7732,7734],{"class":70,"line":167},[68,7725,269],{"class":186},[68,7727,196],{"class":74},[68,7729,274],{"class":186},[68,7731,202],{"class":74},[68,7733,279],{"class":173},[68,7735,282],{"class":74},[68,7737,7738,7740,7742,7744,7746,7748,7750,7752],{"class":70,"line":183},[68,7739,288],{"class":186},[68,7741,202],{"class":74},[68,7743,293],{"class":173},[68,7745,208],{"class":74},[68,7747,320],{"class":186},[68,7749,202],{"class":74},[68,7751,302],{"class":186},[68,7753,305],{"class":74},[68,7755,7756,7758,7760,7762,7764,7766,7768,7770,7772,7774,7776,7778,7780,7782],{"class":70,"line":221},[68,7757,288],{"class":186},[68,7759,202],{"class":74},[68,7761,315],{"class":173},[68,7763,208],{"class":74},[68,7765,320],{"class":186},[68,7767,202],{"class":74},[68,7769,325],{"class":173},[68,7771,208],{"class":74},[68,7773,320],{"class":186},[68,7775,202],{"class":74},[68,7777,334],{"class":173},[68,7779,208],{"class":74},[68,7781,340],{"class":339},[68,7783,343],{"class":74},[68,7785,7786],{"class":70,"line":237},[68,7787,400],{"class":74},[68,7789,7790],{"class":70,"line":255},[68,7791,86],{"emptyLinePlaceholder":85},[68,7793,7794,7796,7798,7800,7802,7804],{"class":70,"line":261},[68,7795,411],{"class":186},[68,7797,196],{"class":74},[68,7799,416],{"class":186},[68,7801,202],{"class":74},[68,7803,421],{"class":173},[68,7805,424],{"class":74},[68,7807,7808,7810,7812,7814,7816,7818,7820,7822,7824,7826,7828],{"class":70,"line":266},[68,7809,430],{"class":186},[68,7811,202],{"class":74},[68,7813,435],{"class":173},[68,7815,438],{"class":74},[68,7817,442],{"class":441},[68,7819,445],{"class":74},[68,7821,448],{"class":78},[68,7823,202],{"class":74},[68,7825,453],{"class":78},[68,7827,456],{"class":74},[68,7829,180],{"class":74},[68,7831,7832,7834,7836,7838,7840,7842,7844,7846,7848,7850,7852,7854,7856,7858],{"class":70,"line":285},[68,7833,464],{"class":186},[68,7835,202],{"class":74},[68,7837,469],{"class":173},[68,7839,208],{"class":74},[68,7841,474],{"class":339},[68,7843,190],{"class":74},[68,7845,479],{"class":74},[68,7847,482],{"class":441},[68,7849,445],{"class":74},[68,7851,448],{"class":78},[68,7853,202],{"class":74},[68,7855,491],{"class":78},[68,7857,456],{"class":74},[68,7859,180],{"class":74},[68,7861,7862,7864,7866,7868,7870,7872,7874,7876,7878,7880,7882,7884,7886,7889,7891,7893,7895,7897],{"class":70,"line":308},[68,7863,501],{"class":186},[68,7865,202],{"class":74},[68,7867,506],{"class":173},[68,7869,208],{"class":74},[68,7871,211],{"class":74},[68,7873,7507],{"class":214},[68,7875,211],{"class":74},[68,7877,190],{"class":74},[68,7879,520],{"class":186},[68,7881,202],{"class":74},[68,7883,525],{"class":173},[68,7885,208],{"class":74},[68,7887,7888],{"class":339},"16",[68,7890,533],{"class":74},[68,7892,520],{"class":186},[68,7894,202],{"class":74},[68,7896,540],{"class":173},[68,7898,543],{"class":74},[68,7900,7901],{"class":70,"line":346},[68,7902,569],{"class":74},[68,7904,7905],{"class":70,"line":372},[68,7906,575],{"class":74},[68,7908,7909],{"class":70,"line":397},[68,7910,86],{"emptyLinePlaceholder":85},[68,7912,7913,7915,7917,7919,7921,7923,7925,7927,7929,7931,7933,7935,7937,7939],{"class":70,"line":403},[68,7914,7520],{"class":186},[68,7916,202],{"class":74},[68,7918,7525],{"class":173},[68,7920,7528],{"class":74},[68,7922,7531],{"class":173},[68,7924,208],{"class":74},[68,7926,211],{"class":74},[68,7928,7538],{"class":214},[68,7930,211],{"class":74},[68,7932,190],{"class":74},[68,7934,7418],{"class":74},[68,7936,7547],{"class":214},[68,7938,211],{"class":74},[68,7940,159],{"class":74},[68,7942,7943,7945,7947,7949,7951,7953,7956,7958,7960,7962,7964,7966,7968],{"class":70,"line":408},[68,7944,224],{"class":92},[68,7946,193],{"class":186},[68,7948,196],{"class":74},[68,7950,416],{"class":186},[68,7952,202],{"class":74},[68,7954,7955],{"class":173},"Render",[68,7957,208],{"class":74},[68,7959,1872],{"class":186},[68,7961,672],{"class":74},[68,7963,193],{"class":186},[68,7965,229],{"class":74},[68,7967,232],{"class":74},[68,7969,180],{"class":74},[68,7971,7972,7974,7976,7978,7980,7982,7984,7986,7988,7990,7992,7994],{"class":70,"line":427},[68,7973,7585],{"class":186},[68,7975,202],{"class":74},[68,7977,7590],{"class":173},[68,7979,208],{"class":74},[68,7981,1872],{"class":186},[68,7983,190],{"class":74},[68,7985,7599],{"class":186},[68,7987,202],{"class":74},[68,7989,7590],{"class":173},[68,7991,7606],{"class":74},[68,7993,7609],{"class":339},[68,7995,159],{"class":74},[68,7997,7998],{"class":70,"line":461},[68,7999,258],{"class":74},[68,8001,8002],{"class":70,"line":498},[68,8003,706],{"class":74},[18,8005,8006,8007,8009,8010,8013,8014,8016,8017,720],{},"三行游标代码变成三次 builder 调用。结构直接呈现在源码里——不再藏在 ",[47,8008,1932],{}," 调用顺序中。CJK 只需再加 ",[47,8011,8012],{},"gpdf.WithFont(\"NotoSansJP\", ttfBytes)"," ——不需要 ",[47,8015,2563],{},"，不需要文件系统路径，不需要 UTF-8 标志。完整流程见 ",[22,8018,1223],{"href":1222},[18,8020,8021,8024,8025,8027],{},[22,8022,8023],{"href":2823},"gofpdf 迁移指南"," 中还有 5 组 before/after：表格、重复页眉页脚、页码、绝对定位。那些内容对 ",[47,8026,1432],{}," 用户一字不差地适用——只需改 import 路径。",[14,8029,8030],{"id":8030},"基准数据",[18,8032,8033,8034,8037],{},"\"快\"很容易说出口，很难说清楚。下表来自 ",[47,8035,8036],{},"gpdf/_benchmark/benchmark_test.go","，在 Apple M1、Go 1.25 上测得。工作负载是生产代码真正会做的——不是专门挑来讨好某个库的 micro-benchmark。",[740,8039,8040,8056],{},[743,8041,8042],{},[746,8043,8044,8047,8049,8051,8054],{},[749,8045,8046],{},"基准",[749,8048,27],{},[749,8050,1429],{},[749,8052,8053],{},"gopdf",[749,8055,1438],{},[758,8057,8058,8073,8089,8105],{},[746,8059,8060,8063,8067,8069,8071],{},[763,8061,8062],{},"单页 (hello)",[763,8064,8065],{},[30,8066,1342],{},[763,8068,1452],{},[763,8070,1458],{},[763,8072,1461],{},[746,8074,8075,8078,8082,8084,8086],{},[763,8076,8077],{},"4×10 明细表",[763,8079,8080],{},[30,8081,1346],{},[763,8083,1473],{},[763,8085,1479],{},[763,8087,8088],{},"8.6 ms",[746,8090,8091,8094,8098,8100,8102],{},[763,8092,8093],{},"100 页报告",[763,8095,8096],{},[30,8097,1350],{},[763,8099,1358],{},[763,8101,8088],{},[763,8103,8104],{},"19.8 ms",[746,8106,8107,8109,8113,8115,8117],{},[763,8108,1507],{},[763,8110,8111],{},[30,8112,1512],{},[763,8114,1515],{},[763,8116,1521],{},[763,8118,8119],{},"10.4 ms",[18,8121,8122,8123,8126],{},"单页 13 µs 意味着单核每秒可以产出约 75,000 份 hello-world PDF；108 µs 的发票意味着每秒约 9,000 份。",[30,8124,8125],{},"重点不是跑分自吹","——而是你可以不再纠结\"PDF 生成要不要缓存、要不要塞进异步队列\"。对大多数工作负载，请求路径上直接生成就够了。",[18,8128,8129,8130,8132],{},"表格基准里 Maroto v2 之所以慢，是因为它底层驱动 ",[47,8131,1432],{}," 又在上面加了一层自己的布局。这不是在批评 Maroto 的 API——API 是好的——而是坐在 fpdf 地基上的结构性成本。等 Maroto v3 甩掉 fpdf 依赖时，这一列的数字会变。",[18,8134,8135],{},"100 页基准值得多说两句。gpdf 的流式写入会随着行的布局过程同时输出内容；gofpdf 每页缓冲更多状态。对分页密集的工作负载（月度报告、目录、合规导出），在文档体量上限处差距是\"分钟 vs 秒\"的数量级。",[14,8137,8139,8140,8143],{"id":8138},"什么时候-不该-选-gpdf","什么时候 ",[6671,8141,8142],{},"不该"," 选 gpdf",[18,8145,8146],{},"迁移类文章必须诚实回答 \"什么时候不迁\"：",[1111,8148,8149,8162,8171,8183],{},[885,8150,8151,8154,8155,8158,8159,8161],{},[30,8152,8153],{},"AcroForm / 可填写表单","。如果你要生成让用户在 Acrobat 里填写的 PDF，gpdf 的表单字段支持还很少。",[47,8156,8157],{},"unidoc"," 在这块更完整；",[47,8160,1435],{}," 有部分 AcroForm 支持。未来会补上，但今天是缺口。",[885,8163,8164,720,8167,8170],{},[30,8165,8166],{},"任意矢量路径、复杂绘图",[47,8168,8169],{},"c.Line()"," 在列内画一条横线。如果你要贝塞尔曲线、自定义路径、渐变填充来画图表或技术图，gpdf 现在到不了。（预渲染的图表图片嵌入没问题——这里说的是绘图原语本身。）",[885,8172,8173,8179,8180,8182],{},[30,8174,8175,8176,8178],{},"大量使用 ",[47,8177,1928],{}," 的现成 gofpdf 代码库","。如果你的代码是 2000 行游标操作，迁移更像重写而不是替换。重写后的代码几乎总是更短，但在 deadline 当天 \"几乎\" 是很冷的安慰。",[22,8181,2824],{"href":2823}," 里有诚实的工作量估算。",[885,8184,8185,8188,8189,8192,8193,8195],{},[30,8186,8187],{},"现在就要全 CSS 支持的 HTML → PDF","。gpdf 的 ",[47,8190,8191],{},"gpdf-pro"," 扩展有 HTML 子集，但与 Chromium 的完整 CSS 对等不是目标。如果模板是复杂的 React 组件，",[47,8194,6943],{}," 或商业 API 更直接。",[18,8197,8198,8199,8202],{},"以上都不刺到你，gpdf 就是默认选择。有一项刺到了，正常的做法是",[30,8200,8201],{},"两个库并存","——新 PDF 用 gpdf，边缘场景留在原库，等 gpdf 追上后再迁。",[14,8204,8205],{"id":8205},"合规视角",[18,8207,8208,8209,8212],{},"生态文章里比较少有人讲这个：",[30,8210,8211],{},"归档的依赖会出现在 SOC 2 和 ISO 27001 的审计报告里","。审计官想确认供应链里的三方代码在积极维护。\"2021 年归档\" 会触发 finding，\"2025 年归档\" 也会。\"内部 fork\" 会触发关于 0day 怎么打补丁的追问。",[18,8214,8215,8216,720,8219,8221],{},"这就是为什么一些在大公司过安全评审的团队悄悄问我们，\"gpdf 什么时候出稳定 v1\"。答案是：",[30,8217,8218],{},"已经出了",[47,8220,131],{}," 有 semver tag，v1 API 面已冻结。项目有安全联系人、负责任披露政策，CI 会在 Go 1.22 到 1.26 全范围跑测试。",[18,8223,8224,8225,720],{},"你不是为了过审才迁——",[30,8226,8227],{},"是在审计开口要求之前先动",[14,8229,2814],{"id":2813},[18,8231,8232,8235,8236,3548,8238,8240],{},[30,8233,8234],{},"\"现代 Go PDF 栈\" 是 gpdf 一个库，还是多个库组合？","\n对大多数团队就是 gpdf 一个。单库覆盖文档生成、CJK、表格、网格、分页、输出。有可填写表单需求的团队会给那类文档单独搭上 ",[47,8237,1435],{},[47,8239,8157],{},"；图表密集的团队会把图表预渲染成 PNG 再嵌入。这里\"栈\"指的是一张短清单，不是分层架构。",[18,8242,8243,8246],{},[30,8244,8245],{},"迁移期间能把 gpdf 和 go-pdf/fpdf 并存吗？","\n可以。import 路径和类型都不一样。新接口走 gpdf，旧接口留在 go-pdf/fpdf，等有时间再重写。运行时没有冲突。",[18,8248,8249,8252,8253,8256],{},[30,8250,8251],{},"会有 go-pdf/fpdf v3 或新 fork 吗？","\n也许。gpdf 的赌注不是\"那个 fork 永远不会 unarchive\"——而是",[30,8254,8255],{},"架构跟不上现在要造的东西","。新 fork 如果不改布局模型，会继承同样的限制；如果改了，那它就更接近 gpdf 而不是 fpdf。",[18,8258,8259,8264,8265,853,8268,853,8271,8274],{},[30,8260,8261,8263],{},[47,8262,1435],{}," 作为现代替代怎么样？","\n真的在维护、真的零依赖。API 是坐标级的——",[47,8266,8267],{},"SetX",[47,8269,8270],{},"SetY",[47,8272,8273],{},"CellWithOption","——适合表单叠加和固定模板。对于带表格和重复页眉页脚的发票类文档，你最后还是得在上面写一层布局辅助，掉进 gofpdf 用户掉过的同一个坑。gpdf 和 gopdf 其实不真正竞争——解决的是相邻问题。",[18,8276,8277,8280,8282],{},[30,8278,8279],{},"gpdf 有商业/托管版吗？",[47,8281,2594],{}," 正在做——一个接收 JSON 模板、返回 PDF 的托管 API。还没公开。上线时这里会发文章。OSS 库会继续保持 MIT、零依赖、独立可用。",[18,8284,8285,8288],{},[30,8286,8287],{},"路线图的优先级？","\n2026-04 公开路线图：(1) AcroForm 表单字段；(2) 完整 PDF/A-3 合规；(3) gpdf-pro 的 HTML→PDF 覆盖扩展；(4) RTL 文本支持（阿拉伯语、希伯来语）。优先级反馈欢迎在 GitHub issue 里提。",[14,8290,2859],{"id":2858},[18,8292,6565],{},[59,8294,8295],{"className":1248,"code":1249,"language":1250,"meta":64,"style":64},[47,8296,8297],{"__ignoreMap":64},[68,8298,8299,8301,8303],{"class":70,"line":71},[68,8300,63],{"class":78},[68,8302,1259],{"class":214},[68,8304,1262],{"class":214},[18,8306,8307,1269,8310],{},[22,8308,6583],{"href":24,"rel":8309},[26],[22,8311,6587],{"href":1272,"rel":8312},[26],[14,8314,1209],{"id":1209},[1111,8316,8317,8323,8328,8333],{},[885,8318,8319,8322],{},[22,8320,8321],{"href":2823},"gofpdf 已归档。如何迁移到 gpdf。"," — 逐 API 对照",[885,8324,8325,8327],{},[22,8326,6551],{"href":2894}," — 更深入的对比基准与特性表",[885,8329,8330,8332],{},[22,8331,5272],{"href":6624}," — 取代游标操作的 builder 习语",[885,8334,8335,8337,8338,8340],{},[22,8336,1223],{"href":1222}," — 不用 ",[47,8339,2563],{}," 的 CJK",[1276,8342,8343],{},"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":64,"searchDepth":82,"depth":82,"links":8345},[8346,8347,8348,8349,8350,8351,8352,8353,8354,8356,8357,8358,8359],{"id":1335,"depth":82,"text":1336},{"id":6662,"depth":82,"text":6662},{"id":6713,"depth":82,"text":6714},{"id":6759,"depth":82,"text":6760},{"id":6802,"depth":82,"text":6803},{"id":7026,"depth":82,"text":7027},{"id":7276,"depth":82,"text":7277},{"id":8030,"depth":82,"text":8030},{"id":8138,"depth":82,"text":8355},"什么时候 不该 选 gpdf",{"id":8205,"depth":82,"text":8205},{"id":2813,"depth":82,"text":2814},{"id":2858,"depth":82,"text":2859},{"id":1209,"depth":82,"text":1209},"jung-kurt/gofpdf 2021 年归档，go-pdf/fpdf 2025 年跟进。2026 年我们实际在用的 Go PDF 栈是 gpdf — 原因、权衡与迁移路径。",{},"/zh/blog/go-pdf-fpdf-archived",{"title":6632,"description":8360},"zh/blog/006.go-pdf-fpdf-archived",[8366,2943,2941],"migration","4SZ-8PgoRS0KICBmNt9aOdHaR_NxJblqX_2WL5rscSc",{"id":8369,"title":8370,"author":8371,"body":8372,"date":5756,"description":10034,"draft":1293,"extension":1294,"howTo":10035,"image":1317,"meta":10059,"navigation":85,"path":3964,"seo":10060,"stem":10061,"tags":10062,"updated":1317,"__hash__":10063},"blogZh/zh/blog/007.japanese-pdf-in-go.md","用 Go 生成日文 PDF 的 2026 权威指南",{"name":8,"url":9},{"type":11,"value":8373,"toc":10020},[8374,8376,8396,8399,8402,8408,8418,8421,8425,8436,8475,8487,8497,8501,8504,8620,8623,8629,8639,8642,8650,9466,9469,9521,9530,9534,9544,9547,9553,9599,9612,9615,9639,9655,9659,9662,9668,9671,9685,9695,9776,9782,9784,9787,9797,9807,9831,9845,9848,9850,9857,9863,9872,9886,9889,9891,9916,9922,9932,9946,9956,9965,9967,9970,9982,9990,9992,10018],[14,8375,1336],{"id":1335},[18,8377,8378,8379,8381,8382,8385,8386,8388,8389,8392,8393,720],{},"如果你的 Go PDF 把 ",[47,8380,3701],{}," 渲染成 5 个豆腐方块,修复方式是两行配置,不是重写。加载一个日文 TTF,把 ",[47,8383,8384],{},"gpdf.WithFont"," 传给 ",[47,8387,279],{},",写日文。",[30,8390,8391],{},"gpdf 会自动子集化字形表",",所以输出只带你实际用过的字符 — 约 30 KB,而不是整套 5 MB 的字体。本文是这条路径的地图:为什么 Go 里的日文 PDF 出奇地难、2026 年真正的四个选项、一份完整可跑的示例、字体子集化的内部机制、混合排版的边界情况,以及",[30,8394,8395],{},"仍然难解的部分",[14,8397,8398],{"id":8398},"为什么需要这篇指南",[18,8400,8401],{},"Go 里渲染一个日文 PDF 本该是 5 分钟的事。对很多团队而言,它要花一天半。",[18,8403,8404,8405,8407],{},"典型剧情:有人换上 ",[47,8406,2563],{},",PDF 里出现一排空白方框 — 臭名昭著的\"豆腐\" — 一个资深工程师花一下午排查到底是字体路径、子集标志、CMap、UTF-8 开关,还是 PDF 阅读器。到傍晚 Slack 上出现了一条名为\"为什么漢字还在坏\"的讨论,第二天提交了一个新增三个谁都后悔的辅助函数的 PR。",[18,8409,8410,8411,8414,8415,720],{},"根源不是这些任何一条。",[30,8412,8413],{},"Go 上寿命最长的 PDF 库是 2002 年为 PHP 和 Latin-1 设计的",",之后几乎所有日文教程都在与这个遗产作战。本文是 2026 版:从干净的起点出发,真正能工作的做法,以及",[30,8416,8417],{},"仍然困难的部分",[18,8419,8420],{},"本文代码基于 gpdf v1.x (2026-04)。基准数据来自 Apple M1 + Go 1.25。",[14,8422,8424],{"id":8423},"_90-秒看懂豆腐问题","90 秒看懂豆腐问题",[18,8426,8427,8428,8431,8432,8435],{},"PDF 不在乎 Unicode。它在乎的是",[30,8429,8430],{},"字形 ID"," — 嵌入字体字形表的整数索引。要把 ",[47,8433,8434],{},"\"こんにちは\""," 写进 PDF,必须有人完成:",[882,8437,8438,8447,8453,8459],{},[885,8439,8440,8443,8444,8446],{},[30,8441,8442],{},"解析 TTF",",从 ",[47,8445,859],{}," 子表里找出每个码点对应的字形 ID。",[885,8448,8449,8452],{},[30,8450,8451],{},"写 ToUnicode CMap",",这样 PDF 阅读器在用户复制或搜索时能把字形映射回文本。",[885,8454,8455,8458],{},[30,8456,8457],{},"子集化",",不要把 Noto Sans JP 的两万字形全塞进去。",[885,8460,8461,8464,8465,866,8468,866,8471,8474],{},[30,8462,8463],{},"嵌入",",正确拼接 ",[47,8466,8467],{},"name",[47,8469,8470],{},"OS/2",[47,8472,8473],{},"head"," 表和编码对象。",[18,8476,8477,8478,3746,8480,8482,8483,8486],{},"任何一步缺失或出错,阅读器都找不到字形,画出豆腐。已归档的 ",[47,8479,1354],{},[47,8481,1432],{}," 系列把上面所有步骤",[30,8484,8485],{},"后挂","在单字节字体模型上 — 2002 年的 FPDF 只认 Latin-1。这就是为什么设置易碎、输出经常嵌入整套字体而非子集、故障模式因操作系统和阅读器而异。",[18,8488,8489,8490,8493,8494,8496],{},"gpdf 把 CJK 当成",[30,8491,8492],{},"一等用例","。TTF 子集器在核心包内。ToUnicode CMap 自动写出。没有单字节字体的历史包袱,因此也没有 ",[47,8495,2563],{}," 的折腾。",[14,8498,8500],{"id":8499},"_2026-年的四个真实选项","2026 年的四个真实选项",[18,8502,8503],{},"先摆牌。\"支持日文\"指\"给定正确 TTF 时,能不崩溃、不豆腐地渲染任意日文\"。",[740,8505,8506,8527],{},[743,8507,8508],{},[746,8509,8510,8513,8516,8519,8522,8525],{},[749,8511,8512],{},"选项",[749,8514,8515],{},"许可",[749,8517,8518],{},"依赖",[749,8520,8521],{},"CJK 路径",[749,8523,8524],{},"300 字文档大小",[749,8526,6113],{},[758,8528,8529,8552,8574,8596],{},[746,8530,8531,8536,8538,8541,8546,8549],{},[763,8532,8533,8535],{},[47,8534,1432],{}," (2025 归档)",[763,8537,6844],{},[763,8539,8540],{},"标准库",[763,8542,8543,8545],{},[47,8544,2563],{}," 后挂",[763,8547,8548],{},"约 5 MB (整套)",[763,8550,8551],{},"后挂在 Latin-1 核心上。子集化需显式开启且不完整。",[746,8553,8554,8558,8560,8562,8568,8571],{},[763,8555,8556],{},[47,8557,1435],{},[763,8559,6844],{},[763,8561,8540],{},[763,8563,8564,8567],{},[47,8565,8566],{},"AddTTFFont"," + 手工",[763,8569,8570],{},"约 3 MB",[763,8572,8573],{},"低层。自己写坐标。有子集化但要自己驱动。",[746,8575,8576,8580,8582,8587,8590,8593],{},[763,8577,8578,6944],{},[47,8579,6943],{},[763,8581,6949],{},[763,8583,8584],{},[30,8585,8586],{},"Chromium 二进制",[763,8588,8589],{},"浏览器原生",[763,8591,8592],{},"可变",[763,8594,8595],{},"HTML/CSS。容器里要装字体。镜像 500 MB+。",[746,8597,8598,8602,8604,8609,8612,8617],{},[763,8599,8600],{},[47,8601,27],{},[763,8603,6844],{},[763,8605,8606],{},[30,8607,8608],{},"仅标准库",[763,8610,8611],{},"原生,自动子集化",[763,8613,8614],{},[30,8615,8616],{},"约 30 KB",[763,8618,8619],{},"纯 Go。Builder API。自动写 ToUnicode CMap。",[18,8621,8622],{},"两点值得强调。",[18,8624,8625,8628],{},[30,8626,8627],{},"\"整套嵌入\"与\"自动子集\"之间 160 倍的差不是小事。"," 一张十条明细的日文电商发票,唯一的日文字符可能只有 120 个。每张发票都嵌入整套 Noto Sans JP (5.1 MB) 意味着到年底,同样 5 MB 的字形数据会在对象存储里存在一千万份。子集嵌入只带你用到的字形。",[18,8630,8631,8634,8635,8638],{},[30,8632,8633],{},"\"chromedp 能用\"是事实,也是最贵的答案。"," 如果团队已经在跑一队无头 Chrome 做截图,给它加个 PDF 用途也行。如果没跑,",[30,8636,8637],{},"仅仅为了打印日文","就立起一个 Chromium,相对于一个 40 行 Go 能解决的问题而言,基础设施开销过大。",[14,8640,8641],{"id":8641},"最短可行路径",[18,8643,8644,8645,8647,8648,720],{},"先试这个。完整可运行 — 拷贝保存为 ",[47,8646,712],{},",把两个 TTF 放在旁边,",[47,8649,716],{},[59,8651,8653],{"className":61,"code":8652,"language":63,"meta":64,"style":64},"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",[47,8654,8655,8661,8665,8671,8679,8687,8691,8699,8707,8715,8719,8723,8733,8760,8772,8786,8790,8818,8830,8844,8848,8852,8866,8884,8914,8937,8960,8982,8986,8990,9004,9028,9058,9105,9124,9128,9132,9156,9187,9219,9238,9242,9273,9316,9343,9347,9351,9355,9373,9385,9399,9403,9444,9458,9462],{"__ignoreMap":64},[68,8656,8657,8659],{"class":70,"line":71},[68,8658,75],{"class":74},[68,8660,79],{"class":78},[68,8662,8663],{"class":70,"line":82},[68,8664,86],{"emptyLinePlaceholder":85},[68,8666,8667,8669],{"class":70,"line":89},[68,8668,93],{"class":92},[68,8670,96],{"class":74},[68,8672,8673,8675,8677],{"class":70,"line":99},[68,8674,102],{"class":74},[68,8676,105],{"class":78},[68,8678,108],{"class":74},[68,8680,8681,8683,8685],{"class":70,"line":111},[68,8682,102],{"class":74},[68,8684,116],{"class":78},[68,8686,108],{"class":74},[68,8688,8689],{"class":70,"line":121},[68,8690,86],{"emptyLinePlaceholder":85},[68,8692,8693,8695,8697],{"class":70,"line":126},[68,8694,102],{"class":74},[68,8696,131],{"class":78},[68,8698,108],{"class":74},[68,8700,8701,8703,8705],{"class":70,"line":136},[68,8702,102],{"class":74},[68,8704,141],{"class":78},[68,8706,108],{"class":74},[68,8708,8709,8711,8713],{"class":70,"line":146},[68,8710,102],{"class":74},[68,8712,151],{"class":78},[68,8714,108],{"class":74},[68,8716,8717],{"class":70,"line":156},[68,8718,159],{"class":74},[68,8720,8721],{"class":70,"line":162},[68,8722,86],{"emptyLinePlaceholder":85},[68,8724,8725,8727,8729,8731],{"class":70,"line":167},[68,8726,170],{"class":74},[68,8728,174],{"class":173},[68,8730,177],{"class":74},[68,8732,180],{"class":74},[68,8734,8735,8738,8740,8742,8744,8746,8748,8750,8752,8754,8756,8758],{"class":70,"line":183},[68,8736,8737],{"class":186},"    regular",[68,8739,190],{"class":74},[68,8741,193],{"class":186},[68,8743,196],{"class":74},[68,8745,199],{"class":186},[68,8747,202],{"class":74},[68,8749,205],{"class":173},[68,8751,208],{"class":74},[68,8753,211],{"class":74},[68,8755,3008],{"class":214},[68,8757,211],{"class":74},[68,8759,159],{"class":74},[68,8761,8762,8764,8766,8768,8770],{"class":70,"line":221},[68,8763,224],{"class":92},[68,8765,193],{"class":186},[68,8767,229],{"class":74},[68,8769,232],{"class":74},[68,8771,180],{"class":74},[68,8773,8774,8776,8778,8780,8782,8784],{"class":70,"line":237},[68,8775,240],{"class":186},[68,8777,202],{"class":74},[68,8779,245],{"class":173},[68,8781,208],{"class":74},[68,8783,250],{"class":186},[68,8785,159],{"class":74},[68,8787,8788],{"class":70,"line":255},[68,8789,258],{"class":74},[68,8791,8792,8795,8797,8799,8801,8803,8805,8807,8809,8811,8814,8816],{"class":70,"line":261},[68,8793,8794],{"class":186},"    bold",[68,8796,190],{"class":74},[68,8798,193],{"class":186},[68,8800,196],{"class":74},[68,8802,199],{"class":186},[68,8804,202],{"class":74},[68,8806,205],{"class":173},[68,8808,208],{"class":74},[68,8810,211],{"class":74},[68,8812,8813],{"class":214},"NotoSansJP-Bold.ttf",[68,8815,211],{"class":74},[68,8817,159],{"class":74},[68,8819,8820,8822,8824,8826,8828],{"class":70,"line":266},[68,8821,224],{"class":92},[68,8823,193],{"class":186},[68,8825,229],{"class":74},[68,8827,232],{"class":74},[68,8829,180],{"class":74},[68,8831,8832,8834,8836,8838,8840,8842],{"class":70,"line":285},[68,8833,240],{"class":186},[68,8835,202],{"class":74},[68,8837,245],{"class":173},[68,8839,208],{"class":74},[68,8841,250],{"class":186},[68,8843,159],{"class":74},[68,8845,8846],{"class":70,"line":308},[68,8847,258],{"class":74},[68,8849,8850],{"class":70,"line":346},[68,8851,86],{"emptyLinePlaceholder":85},[68,8853,8854,8856,8858,8860,8862,8864],{"class":70,"line":372},[68,8855,269],{"class":186},[68,8857,196],{"class":74},[68,8859,274],{"class":186},[68,8861,202],{"class":74},[68,8863,279],{"class":173},[68,8865,282],{"class":74},[68,8867,8868,8870,8872,8874,8876,8878,8880,8882],{"class":70,"line":397},[68,8869,288],{"class":186},[68,8871,202],{"class":74},[68,8873,293],{"class":173},[68,8875,208],{"class":74},[68,8877,320],{"class":186},[68,8879,202],{"class":74},[68,8881,302],{"class":186},[68,8883,305],{"class":74},[68,8885,8886,8888,8890,8892,8894,8896,8898,8900,8902,8904,8906,8908,8910,8912],{"class":70,"line":403},[68,8887,288],{"class":186},[68,8889,202],{"class":74},[68,8891,315],{"class":173},[68,8893,208],{"class":74},[68,8895,320],{"class":186},[68,8897,202],{"class":74},[68,8899,325],{"class":173},[68,8901,208],{"class":74},[68,8903,320],{"class":186},[68,8905,202],{"class":74},[68,8907,334],{"class":173},[68,8909,208],{"class":74},[68,8911,340],{"class":339},[68,8913,343],{"class":74},[68,8915,8916,8918,8920,8922,8924,8926,8928,8930,8932,8935],{"class":70,"line":408},[68,8917,288],{"class":186},[68,8919,202],{"class":74},[68,8921,353],{"class":173},[68,8923,208],{"class":74},[68,8925,211],{"class":74},[68,8927,3246],{"class":214},[68,8929,211],{"class":74},[68,8931,190],{"class":74},[68,8933,8934],{"class":186}," regular",[68,8936,305],{"class":74},[68,8938,8939,8941,8943,8945,8947,8949,8952,8954,8956,8958],{"class":70,"line":427},[68,8940,288],{"class":186},[68,8942,202],{"class":74},[68,8944,353],{"class":173},[68,8946,208],{"class":74},[68,8948,211],{"class":74},[68,8950,8951],{"class":214},"NotoSansJP-Bold",[68,8953,211],{"class":74},[68,8955,190],{"class":74},[68,8957,1058],{"class":186},[68,8959,305],{"class":74},[68,8961,8962,8964,8966,8968,8970,8972,8974,8976,8978,8980],{"class":70,"line":461},[68,8963,288],{"class":186},[68,8965,202],{"class":74},[68,8967,379],{"class":173},[68,8969,208],{"class":74},[68,8971,211],{"class":74},[68,8973,3246],{"class":214},[68,8975,211],{"class":74},[68,8977,190],{"class":74},[68,8979,392],{"class":339},[68,8981,305],{"class":74},[68,8983,8984],{"class":70,"line":498},[68,8985,400],{"class":74},[68,8987,8988],{"class":70,"line":546},[68,8989,86],{"emptyLinePlaceholder":85},[68,8991,8992,8994,8996,8998,9000,9002],{"class":70,"line":566},[68,8993,411],{"class":186},[68,8995,196],{"class":74},[68,8997,416],{"class":186},[68,8999,202],{"class":74},[68,9001,421],{"class":173},[68,9003,424],{"class":74},[68,9005,9006,9008,9010,9012,9014,9016,9018,9020,9022,9024,9026],{"class":70,"line":572},[68,9007,430],{"class":186},[68,9009,202],{"class":74},[68,9011,435],{"class":173},[68,9013,438],{"class":74},[68,9015,442],{"class":441},[68,9017,445],{"class":74},[68,9019,448],{"class":78},[68,9021,202],{"class":74},[68,9023,453],{"class":78},[68,9025,456],{"class":74},[68,9027,180],{"class":74},[68,9029,9030,9032,9034,9036,9038,9040,9042,9044,9046,9048,9050,9052,9054,9056],{"class":70,"line":578},[68,9031,464],{"class":186},[68,9033,202],{"class":74},[68,9035,469],{"class":173},[68,9037,208],{"class":74},[68,9039,474],{"class":339},[68,9041,190],{"class":74},[68,9043,479],{"class":74},[68,9045,482],{"class":441},[68,9047,445],{"class":74},[68,9049,448],{"class":78},[68,9051,202],{"class":74},[68,9053,491],{"class":78},[68,9055,456],{"class":74},[68,9057,180],{"class":74},[68,9059,9060,9062,9064,9066,9068,9070,9072,9074,9076,9078,9080,9082,9084,9086,9088,9090,9092,9094,9096,9098,9100,9103],{"class":70,"line":583},[68,9061,501],{"class":186},[68,9063,202],{"class":74},[68,9065,506],{"class":173},[68,9067,208],{"class":74},[68,9069,211],{"class":74},[68,9071,4413],{"class":214},[68,9073,211],{"class":74},[68,9075,190],{"class":74},[68,9077,520],{"class":186},[68,9079,202],{"class":74},[68,9081,4984],{"class":173},[68,9083,208],{"class":74},[68,9085,211],{"class":74},[68,9087,8951],{"class":214},[68,9089,211],{"class":74},[68,9091,533],{"class":74},[68,9093,520],{"class":186},[68,9095,202],{"class":74},[68,9097,525],{"class":173},[68,9099,208],{"class":74},[68,9101,9102],{"class":339},"22",[68,9104,5007],{"class":74},[68,9106,9107,9109,9111,9113,9115,9117,9120,9122],{"class":70,"line":604},[68,9108,501],{"class":186},[68,9110,202],{"class":74},[68,9112,506],{"class":173},[68,9114,208],{"class":74},[68,9116,211],{"class":74},[68,9118,9119],{"class":214},"2026 年 4 月 16 日",[68,9121,211],{"class":74},[68,9123,159],{"class":74},[68,9125,9126],{"class":70,"line":617},[68,9127,569],{"class":74},[68,9129,9130],{"class":70,"line":632},[68,9131,575],{"class":74},[68,9133,9134,9136,9138,9140,9142,9144,9146,9148,9150,9152,9154],{"class":70,"line":637},[68,9135,430],{"class":186},[68,9137,202],{"class":74},[68,9139,435],{"class":173},[68,9141,438],{"class":74},[68,9143,442],{"class":441},[68,9145,445],{"class":74},[68,9147,448],{"class":78},[68,9149,202],{"class":74},[68,9151,453],{"class":78},[68,9153,456],{"class":74},[68,9155,180],{"class":74},[68,9157,9158,9160,9162,9164,9166,9169,9171,9173,9175,9177,9179,9181,9183,9185],{"class":70,"line":683},[68,9159,464],{"class":186},[68,9161,202],{"class":74},[68,9163,469],{"class":173},[68,9165,208],{"class":74},[68,9167,9168],{"class":339},"7",[68,9170,190],{"class":74},[68,9172,479],{"class":74},[68,9174,482],{"class":441},[68,9176,445],{"class":74},[68,9178,448],{"class":78},[68,9180,202],{"class":74},[68,9182,491],{"class":78},[68,9184,456],{"class":74},[68,9186,180],{"class":74},[68,9188,9189,9191,9193,9195,9197,9199,9202,9204,9206,9208,9210,9212,9214,9217],{"class":70,"line":698},[68,9190,501],{"class":186},[68,9192,202],{"class":74},[68,9194,506],{"class":173},[68,9196,208],{"class":74},[68,9198,211],{"class":74},[68,9200,9201],{"class":214},"株式会社 ABC 御中",[68,9203,211],{"class":74},[68,9205,190],{"class":74},[68,9207,520],{"class":186},[68,9209,202],{"class":74},[68,9211,525],{"class":173},[68,9213,208],{"class":74},[68,9215,9216],{"class":339},"13",[68,9218,5007],{"class":74},[68,9220,9221,9223,9225,9227,9229,9231,9234,9236],{"class":70,"line":703},[68,9222,501],{"class":186},[68,9224,202],{"class":74},[68,9226,506],{"class":173},[68,9228,208],{"class":74},[68,9230,211],{"class":74},[68,9232,9233],{"class":214},"〒 100-0001 東京都千代田区千代田 1-1",[68,9235,211],{"class":74},[68,9237,159],{"class":74},[68,9239,9240],{"class":70,"line":4597},[68,9241,569],{"class":74},[68,9243,9244,9246,9248,9250,9252,9255,9257,9259,9261,9263,9265,9267,9269,9271],{"class":70,"line":5835},[68,9245,464],{"class":186},[68,9247,202],{"class":74},[68,9249,469],{"class":173},[68,9251,208],{"class":74},[68,9253,9254],{"class":339},"5",[68,9256,190],{"class":74},[68,9258,479],{"class":74},[68,9260,482],{"class":441},[68,9262,445],{"class":74},[68,9264,448],{"class":78},[68,9266,202],{"class":74},[68,9268,491],{"class":78},[68,9270,456],{"class":74},[68,9272,180],{"class":74},[68,9274,9275,9277,9279,9281,9283,9285,9288,9290,9292,9294,9296,9298,9300,9302,9304,9306,9308,9310,9312,9314],{"class":70,"line":5855},[68,9276,501],{"class":186},[68,9278,202],{"class":74},[68,9280,506],{"class":173},[68,9282,208],{"class":74},[68,9284,211],{"class":74},[68,9286,9287],{"class":214},"合計 ¥ 128,000",[68,9289,211],{"class":74},[68,9291,190],{"class":74},[68,9293,520],{"class":186},[68,9295,202],{"class":74},[68,9297,4984],{"class":173},[68,9299,208],{"class":74},[68,9301,211],{"class":74},[68,9303,8951],{"class":214},[68,9305,211],{"class":74},[68,9307,533],{"class":74},[68,9309,520],{"class":186},[68,9311,202],{"class":74},[68,9313,7250],{"class":173},[68,9315,543],{"class":74},[68,9317,9318,9320,9322,9324,9326,9328,9331,9333,9335,9337,9339,9341],{"class":70,"line":5860},[68,9319,501],{"class":186},[68,9321,202],{"class":74},[68,9323,506],{"class":173},[68,9325,208],{"class":74},[68,9327,211],{"class":74},[68,9329,9330],{"class":214},"支払期限: 2026-05-31",[68,9332,211],{"class":74},[68,9334,190],{"class":74},[68,9336,520],{"class":186},[68,9338,202],{"class":74},[68,9340,7250],{"class":173},[68,9342,543],{"class":74},[68,9344,9345],{"class":70,"line":5891},[68,9346,569],{"class":74},[68,9348,9349],{"class":70,"line":5911},[68,9350,575],{"class":74},[68,9352,9353],{"class":70,"line":5916},[68,9354,86],{"emptyLinePlaceholder":85},[68,9356,9357,9359,9361,9363,9365,9367,9369,9371],{"class":70,"line":5947},[68,9358,586],{"class":186},[68,9360,190],{"class":74},[68,9362,193],{"class":186},[68,9364,196],{"class":74},[68,9366,416],{"class":186},[68,9368,202],{"class":74},[68,9370,599],{"class":173},[68,9372,424],{"class":74},[68,9374,9375,9377,9379,9381,9383],{"class":70,"line":5966},[68,9376,224],{"class":92},[68,9378,193],{"class":186},[68,9380,229],{"class":74},[68,9382,232],{"class":74},[68,9384,180],{"class":74},[68,9386,9387,9389,9391,9393,9395,9397],{"class":70,"line":5971},[68,9388,240],{"class":186},[68,9390,202],{"class":74},[68,9392,245],{"class":173},[68,9394,208],{"class":74},[68,9396,250],{"class":186},[68,9398,159],{"class":74},[68,9400,9401],{"class":70,"line":5976},[68,9402,258],{"class":74},[68,9404,9405,9407,9409,9411,9413,9415,9417,9419,9421,9424,9426,9428,9430,9432,9434,9436,9438,9440,9442],{"class":70,"line":5981},[68,9406,224],{"class":92},[68,9408,193],{"class":186},[68,9410,196],{"class":74},[68,9412,199],{"class":186},[68,9414,202],{"class":74},[68,9416,650],{"class":173},[68,9418,208],{"class":74},[68,9420,211],{"class":74},[68,9422,9423],{"class":214},"invoice-ja.pdf",[68,9425,211],{"class":74},[68,9427,190],{"class":74},[68,9429,664],{"class":186},[68,9431,190],{"class":74},[68,9433,669],{"class":339},[68,9435,672],{"class":74},[68,9437,193],{"class":186},[68,9439,229],{"class":74},[68,9441,232],{"class":74},[68,9443,180],{"class":74},[68,9445,9446,9448,9450,9452,9454,9456],{"class":70,"line":5987},[68,9447,240],{"class":186},[68,9449,202],{"class":74},[68,9451,245],{"class":173},[68,9453,208],{"class":74},[68,9455,250],{"class":186},[68,9457,159],{"class":74},[68,9459,9460],{"class":70,"line":6012},[68,9461,258],{"class":74},[68,9463,9464],{"class":70,"line":6044},[68,9465,706],{"class":74},[18,9467,9468],{},"几点值得注意:",[1111,9470,9471,9488,9497,9512],{},[885,9472,9473,720,9482,9484,9485,9487],{},[30,9474,9475,9476,9478,9479,9481],{},"没有 ",[47,9477,2563],{},"、没有 UTF-8 标志、",[47,9480,506],{}," 也不需要字体路径参数",[47,9483,8384],{}," 注册一个 family,",[47,9486,2986],{}," 直接写 Unicode。底层连线全部在内部。",[885,9489,9490,9493,9494,9496],{},[30,9491,9492],{},"粗体是独立的 family,不是标志","。这与 TTF 的分发方式吻合 (Noto Sans JP Regular 和 Bold 是不同的 TTF 文件,",[47,9495,8467],{}," 表不同)。Gothic 和 Mincho、Source Han Sans JP 的 Normal / Heavy 都遵循同一模式。",[885,9498,9499,720,9502,3746,9505,9508,9509,720],{},[30,9500,9501],{},"布局用网格,不是光标",[47,9503,9504],{},"r.Col(7, ...)",[47,9506,9507],{},"r.Col(5, ...)"," 相加为 12。宽度是声明式的,你不算 x 坐标。详见 ",[22,9510,9511],{"href":6624},"gpdf 的 12 列网格如何工作",[885,9513,9514,9520],{},[30,9515,9516,9519],{},[47,9517,9518],{},"AlignRight()"," 与区域无关","。日文 \"¥ 128,000\" 和 \"$1,280.00\" 用同样的方式右对齐。文本内容不影响布局代码。",[18,9522,9523,9524,9526,9527,9529],{},"用任意阅读器打开生成的 ",[47,9525,9423],{},"。选中\"株式会社 ABC 御中\"粘贴到文本编辑器。得到 ",[47,9528,9201],{},",不乱码。这就是 ToUnicode CMap 在工作;gpdf 默认写出。",[14,9531,9533],{"id":9532},"字体子集化隐藏的体积炸弹","字体子集化:隐藏的体积炸弹",[18,9535,9536,9537,9540,9541,720],{},"教程最常跳过的 CJK in PDF ",[30,9538,9539],{},"最重要性质"," — ",[30,9542,9543],{},"子集嵌入",[18,9545,9546],{},"TTF 是字形轮廓和元数据表的集合。Noto Sans JP Regular 约 17,500 字形、5.1 MB。典型发票用到的唯一日文字符 60〜200 个。每个文档嵌入整套字体是数量级浪费。",[18,9548,9543,9549,9552],{},[30,9550,9551],{},"只保留用过的字形","。gpdf 自动完成。运行上面示例验证:",[59,9554,9556],{"className":1248,"code":9555,"language":1250,"meta":64,"style":64},"$ ls -l invoice-ja.pdf\n-rw-r--r--  1 dev  staff  34892 Apr 16 10:12 invoice-ja.pdf\n",[47,9557,9558,9572],{"__ignoreMap":64},[68,9559,9560,9563,9566,9569],{"class":70,"line":71},[68,9561,9562],{"class":78},"$",[68,9564,9565],{"class":214}," ls",[68,9567,9568],{"class":214}," -l",[68,9570,9571],{"class":214}," invoice-ja.pdf\n",[68,9573,9574,9577,9580,9583,9586,9589,9592,9594,9597],{"class":70,"line":82},[68,9575,9576],{"class":78},"-rw-r--r--",[68,9578,9579],{"class":339},"  1",[68,9581,9582],{"class":214}," dev",[68,9584,9585],{"class":214},"  staff",[68,9587,9588],{"class":339},"  34892",[68,9590,9591],{"class":214}," Apr",[68,9593,7480],{"class":339},[68,9595,9596],{"class":214}," 10:12",[68,9598,9571],{"class":214},[18,9600,9601,9602,1929,9604,9607,9608,9611],{},"34 KB。对照:同文档用 ",[47,9603,1432],{},[47,9605,9606],{},"AddUTF8Font(\"NotoSansJP\", \"NotoSansJP-Regular.ttf\", true)"," 生成 (第三个参数是 UTF-8 标志) 是 ",[30,9609,9610],{},"4.9 MB","。同样的输入,同样的输出文本,文件大 143 倍。原因是 fpdf 代码路径在输出时不做子集化,直接嵌入整张字体表。",[18,9613,9614],{},"生产影响:",[1111,9616,9617,9627,9633],{},[885,9618,9619,9622,9623,9626],{},[30,9620,9621],{},"每秒 10 张发票"," (常见 SaaS 规模),子集差 = ",[30,9624,9625],{},"0.3 MB/s vs 43 MB/s"," 的出口字节差。负载均衡器对此有意见。",[885,9628,9629,9632],{},[30,9630,9631],{},"冷存储费用与 PDF 大小线性","。500 万张归档发票 × 5 MB = 25 TB。× 30 KB = 150 GB。对象存储价格把这做成月度四位数对比两位数的差别。",[885,9634,9635,9638],{},[30,9636,9637],{},"邮件投递"," 附件限额 10〜25 MB。一张 5 MB 日文发票 + 其他附件 + MIME 编码,很容易顶到上限。",[18,9640,9641,9642,853,9645,853,9648,853,9651,9654],{},"gpdf 在渲染时子集化。没有开启开关。要看哪些字形进了输出,可以用 gpdf 的本地验证工具,但短版本是:如果你用了 ",[47,9643,9644],{},"株",[47,9646,9647],{},"式",[47,9649,9650],{},"会",[47,9652,9653],{},"社",",这四个字形进输出,其余 17,496 不进。",[14,9656,9658],{"id":9657},"混合排版同一行里的漢字-仮名-ascii","混合排版:同一行里的漢字 + 仮名 + ASCII",[18,9660,9661],{},"日文文本很少只有日文。真实世界的一行看起来像这样:",[59,9663,9666],{"className":9664,"code":9665,"language":926},[924],"API の P95 レイテンシは 50 ms 未満です。\n",[47,9667,9665],{"__ignoreMap":64},[18,9669,9670],{},"5 种文字并存:罗马字 (ASCII Latin)、片假名、平假名、漢字 (Han)、数字。朴素实现给 ASCII 部分挑错字体,结果单间距的 \"API\" 贴着比例排布的日文,视觉崩坏。",[18,9672,9673,9674,9677,9678,3746,9681,9684],{},"gpdf 的默认行为是",[30,9675,9676],{},"用注册的 family 渲染每个码点","。如果 Noto Sans JP 是默认,",[47,9679,9680],{},"API",[47,9682,9683],{},"50 ms"," 就用 Noto Sans JP 的拉丁字形来画 — Noto 有这些 (大多数日文超家族都有)。结果看起来是单一字体,因为它就是。",[18,9686,9687,9688,9691,9692,9694],{},"若要",[30,9689,9690],{},"有意混合"," family (ASCII 用 condensed 无衬线、日文用 Noto Sans JP),注册两个并按 ",[47,9693,2986],{}," 覆盖:",[59,9696,9698],{"className":61,"code":9697,"language":63,"meta":64,"style":64},"c.Text(\"API の P95 レイテンシは 50 ms 未満です。\",\n    template.FontFamily(\"NotoSansJP\"))\nc.Text(\"API latency (P95) is under 50 ms.\",\n    template.FontFamily(\"InterVariable\"))\n",[47,9699,9700,9719,9738,9757],{"__ignoreMap":64},[68,9701,9702,9704,9706,9708,9710,9712,9715,9717],{"class":70,"line":71},[68,9703,482],{"class":186},[68,9705,202],{"class":74},[68,9707,506],{"class":173},[68,9709,208],{"class":74},[68,9711,211],{"class":74},[68,9713,9714],{"class":214},"API の P95 レイテンシは 50 ms 未満です。",[68,9716,211],{"class":74},[68,9718,2055],{"class":74},[68,9720,9721,9724,9726,9728,9730,9732,9734,9736],{"class":70,"line":82},[68,9722,9723],{"class":186},"    template",[68,9725,202],{"class":74},[68,9727,4984],{"class":173},[68,9729,208],{"class":74},[68,9731,211],{"class":74},[68,9733,3246],{"class":214},[68,9735,211],{"class":74},[68,9737,5007],{"class":74},[68,9739,9740,9742,9744,9746,9748,9750,9753,9755],{"class":70,"line":89},[68,9741,482],{"class":186},[68,9743,202],{"class":74},[68,9745,506],{"class":173},[68,9747,208],{"class":74},[68,9749,211],{"class":74},[68,9751,9752],{"class":214},"API latency (P95) is under 50 ms.",[68,9754,211],{"class":74},[68,9756,2055],{"class":74},[68,9758,9759,9761,9763,9765,9767,9769,9772,9774],{"class":70,"line":99},[68,9760,9723],{"class":186},[68,9762,202],{"class":74},[68,9764,4984],{"class":173},[68,9766,208],{"class":74},[68,9768,211],{"class":74},[68,9770,9771],{"class":214},"InterVariable",[68,9773,211],{"class":74},[68,9775,5007],{"class":74},[18,9777,9778,9779,9781],{},"两次 ",[47,9780,2986],{},",两个 family,你的代码里没有脚本检测逻辑。同一行内混合 (同一句子里 ASCII 走 Inter、日文走 Noto) 的功能计划在 gpdf v1.2 加入;现在的绕行是手动按脚本边界切分,用横向的列排布。",[14,9783,8395],{"id":8395},[18,9785,9786],{},"Go 上的日文 PDF 故事已解决 95%。诚实写出剩下 5%。",[18,9788,9789,9792,9793,9796],{},[30,9790,9791],{},"纵排 (縦書き) 尚未支持","。gpdf v1.x 仅支持横排。传统日文排版 — 从右向左的列、自上而下的字符、合适的字形旋转和标点重定位 — 是布局引擎的深度改动,不是渲染微调。有已开设计案的 issue,落地时会落地。现在若必须纵排 (书籍、正式书信),用其它工具 (Word、InDesign、pandoc + LuaLaTeX 管道) 产出纵排 PDF,再用 ",[47,9794,9795],{},"gpdf.Merge"," 合并。",[18,9798,9799,9802,9803,9806],{},[30,9800,9801],{},"旁注假名 (ルビ、振り仮名) 只能绕行","。没有 ",[47,9804,9805],{},"c.Ruby(\"漢字\", \"かんじ\")"," 原语。若儿童内容或语言教材需要,绕行是两行列结构:上面小号假名、下面普通汉字,对齐。能用,手动,假名边界的精细字距要小心。",[18,9808,9809,9812,9813,853,9816,853,9819,9822,9823,9825,9826,9830],{},[30,9810,9811],{},"多 CJK 字体间的自动回退不存在","。若用户输入混了日文漢字和中文专有字 (",[47,9814,9815],{},"直",[47,9817,9818],{},"骨",[47,9820,9821],{},"角"," 在 JP/CN 字形略有差别),你要手动分割并用两个 family。同一 ",[47,9824,2986],{}," 调用内不会跨 family 自动回退。实际上很少有文档需要这种,但若需要请参考 ",[22,9827,9829],{"href":9828},"/zh/blog/","JP/CN/KR/EN 混排 PDF"," (B-070 待发)。",[18,9832,9833,9836,9837,9840,9841,9844],{},[30,9834,9835],{},"严格 PDF/A-2b + 日文","。gpdf 通过 ",[47,9838,9839],{},"gpdf.WithPDFA"," 产出 PDF/A,但嵌入字形元数据、CJK 段的 ",[47,9842,9843],{},"ActualText","、带标签的结构树等严格合规要求在 CJK 场景下仍在打磨。若要长期归档 (中国的会计凭证归档条例、日本的电子帐簿保存法),在提交前用第三方工具 (veraPDF 免费) 校验。",[18,9846,9847],{},"这些都不是常见场景 (发票、报表、对账单、收据、凭证) 的阻塞项。写出来是因为总会有人在生产里踩到其中之一,\"在路线图上\"没有\"这里是绕行方案\"实用。",[14,9849,8205],{"id":8205},[18,9851,9852,9853,9856],{},"一条生态上下文通常说得太少:",[30,9854,9855],{},"2026 年的日文 PDF 生成不只是排版问题","。两项监管要求把它推进了合规讨论。",[18,9858,9859,9862],{},[30,9860,9861],{},"适格请求書 (资格发票)"," 制度要求发票包含特定字段 (登录业务编号、适用税率、税额明细) 并以防篡改方式保存。PDF 是默认格式,\"防篡改\"映射为 PDF 数字签名 — 严格模式下即 PAdES-B-LT。",[18,9864,9865,9868,9869,720],{},[30,9866,9867],{},"电子帳簿保存法"," (2024 修订) 扩展了电子形式收到的发票的留存要求。归档 PDF 必须满足完整性要求。事实目标格式是 ",[30,9870,9871],{},"PDF/A-2b 或 PDF/A-3b",[18,9873,9874,9875,9878,9879,9882,9883,9885],{},"两者都依赖 ",[30,9876,9877],{},"PDF 原生能力"," — 签名、长期验证、PDF/A 嵌入元数据。经无头浏览器的 HTML→PDF 两边都不能干净满足:Chromium 的 PDF 输出不是 PDF/A,也不能一步嵌入数字签名。原生 Go 栈 (gpdf + ",[47,9880,9881],{},"gpdf/signature"," 走 PAdES + ",[47,9884,9839],{},") 可以在单一流水线、不出进程地完成全链条。",[18,9887,9888],{},"这是预告,不是深潜 — 签名与 PDF/A 各自值一篇长文 (待发 B-067、B-068)。但若今天要选日文 PDF 栈且合规进入视野,请挑一个原生支持签名和 PDF/A 的。从\"能跑\"到\"过审\"的迁移税是真实的,推迟支付更贵。",[14,9890,2814],{"id":2813},[18,9892,9893,9896,9897,3548,9900,9903,9904,9907,9908,9911,9912,9915],{},[30,9894,9895],{},"需要在服务器或容器里装字体吗?","\n不需要。gpdf 读取 TTF 字节,不走系统字体缓存。",[47,9898,9899],{},"os.ReadFile(\"NotoSansJP-Regular.ttf\")",[47,9901,9902],{},"//go:embed NotoSansJP-Regular.ttf"," 在 macOS / Linux / Windows、distroless 容器、AWS Lambda 上等价。无需 ",[47,9905,9906],{},"fontconfig",",无需 ",[47,9909,9910],{},"fc-cache -fv","。这是 gpdf 能在 ",[47,9913,9914],{},"FROM scratch"," 镜像里运行的理由之一。",[18,9917,9918,9921],{},[30,9919,9920],{},"Noto Sans JP vs Source Han Sans JP 有区别吗?","\n同字体,两个名字。Adobe 发行 Source Han Sans JP,Google 重新打包为 Noto Sans JP。字形覆盖相同。都是 SIL Open Font License — 选法务容易通过的那个。gpdf 示例默认用 Noto Sans JP 是因为文件名好记。",[18,9923,9924,9927,9928,9931],{},[30,9925,9926],{},"游ゴシック (Yu Gothic) 或 Hiragino 呢?","\nOS 自带的商用字体。只要部署目标授权 (Windows Server 带 Yu Gothic、macOS 带 Hiragino) 就能用,但 TTF 文件的获取和容器构建中的再分发条款得自行确认。开放部署用 ",[30,9929,9930],{},"Noto Sans JP 或 IPAex 黑体"," (均可自由再分发)。",[18,9933,9934,9941,9942,9945],{},[30,9935,9936,9937,9940],{},"PDF 出来但 ",[47,9938,9939],{},"Ctrl+F"," 搜不到","\n几乎总是 ToUnicode CMap 问题。gpdf 默认写出,若用 gpdf 仍遇到请带阅读器名字开 issue。用 gofpdf 遇到的话,修复方法是启用 UTF-8 标志",[30,9943,9944],{},"并","确认阅读器支持 CID 字体 (旧版 macOS Preview.app 有已知问题)。用 Adobe Reader 或 Chrome 做对照。",[18,9947,9948,9951,9952,9955],{},[30,9949,9950],{},"字体里没有的 JIS X 0213 字符怎么办?","\n没有字形就画不出来。实用答:\"用覆盖 JIS X 0213 的字体\"。Noto Sans JP 覆盖 BMP 全域加 JIS X 0213 第一水平。罕见异体有 Hanazono Mincho (花园明朝) 这种末端回退。若任何字体都没有该码点,gpdf 输出 Unicode 替换字符 (U+FFFD) — 不是无声的豆腐,会显示 ",[47,9953,9954],{},"�",",提示你去查。",[18,9957,9958,9961,9962,9964],{},[30,9959,9960],{},"CJK 比 ASCII 慢吗?","\n小幅慢。gpdf 的\"complex CJK invoice\"基准在 Apple M1 是 133 µs,ASCII 4×10 表格是 108 µs。约 23% 开销,主要来自字形查找和子集化。参考:同一 CJK 基准 ",[47,9963,1432],{}," 254 µs、Maroto v2 10.4 ms。日文渲染不会成为服务瓶颈。",[14,9966,2859],{"id":2858},[18,9968,9969],{},"gpdf 是一个 Go 的 PDF 生成库。MIT,零外部依赖,原生 CJK。",[59,9971,9972],{"className":1248,"code":1249,"language":1250,"meta":64,"style":64},[47,9973,9974],{"__ignoreMap":64},[68,9975,9976,9978,9980],{"class":70,"line":71},[68,9977,63],{"class":78},[68,9979,1259],{"class":214},[68,9981,1262],{"class":214},[18,9983,9984,1269,9987],{},[22,9985,2880],{"href":24,"rel":9986},[26],[22,9988,1274],{"href":1272,"rel":9989},[26],[14,9991,1209],{"id":1209},[1111,9993,9994,10000,10006,10012],{},[885,9995,9996,9999],{},[22,9997,9998],{"href":1222},"如何用 gpdf 嵌入日文字体?"," — 不含背景的三行配方",[885,10001,10002,10005],{},[22,10003,10004],{"href":833},"如何用 Noto Sans JP 配合 gpdf?"," — Regular / Bold / Medium 字重设置",[885,10007,10008,10011],{},[22,10009,10010],{"href":6624},"gpdf 的 12 列网格如何工作?"," — 取代光标计算的布局习语",[885,10013,10014,10017],{},[22,10015,10016],{"href":8362},"go-pdf/fpdf 也归档了。2026 年的 Go PDF 栈"," — 更广的 2026 版图",[1276,10019,1278],{},{"title":64,"searchDepth":82,"depth":82,"links":10021},[10022,10023,10024,10025,10026,10027,10028,10029,10030,10031,10032,10033],{"id":1335,"depth":82,"text":1336},{"id":8398,"depth":82,"text":8398},{"id":8423,"depth":82,"text":8424},{"id":8499,"depth":82,"text":8500},{"id":8641,"depth":82,"text":8641},{"id":9532,"depth":82,"text":9533},{"id":9657,"depth":82,"text":9658},{"id":8395,"depth":82,"text":8395},{"id":8205,"depth":82,"text":8205},{"id":2813,"depth":82,"text":2814},{"id":2858,"depth":82,"text":2859},{"id":1209,"depth":82,"text":1209},"用 Go 生成日文 PDF 的完整流程。无 CGO、无 Chromium、无豆腐字。涵盖字体、子集化、混合排版、纵排。",{"name":10036,"totalTime":10037,"tools":10038,"steps":10040},"用 Go 生成带原生 TrueType 子集嵌入的日文 PDF","PT20M",[1299,10039],"NotoSansJP-Regular.ttf 和 NotoSansJP-Bold.ttf(或任意支持日文的 TTF 字体对)",[10041,10044,10047,10050,10053,10056],{"name":10042,"text":10043},"安装 gpdf 并准备字体","执行 go get github.com/gpdf-dev/gpdf。从 Google Fonts 下载 Noto Sans JP Regular 和 Bold,放在 main.go 旁边。无需 CGO,无需系统字体配置。",{"name":10045,"text":10046},"启动时读取 TTF 字节","用 os.ReadFile 把两个 TTF 文件读入 []byte。如果想把字体打进二进制,也可以用 //go:embed。",{"name":10048,"text":10049},"在文档构造时注册字体","把 gpdf.WithFont(\"NotoSansJP\", regular) 和 gpdf.WithFont(\"NotoSansJP-Bold\", bold) 传给 gpdf.NewDocument。family 名字是任意句柄,后续引用时保持一致即可。",{"name":10051,"text":10052},"将日文字体设为默认","添加 gpdf.WithDefaultFont(\"NotoSansJP\", 11)。之后的 c.Text 调用无需显式 FontFamily 选项就会用日文字体。",{"name":10054,"text":10055},"用 c.Text 构建文档树","在 page.AutoRow 块内调用 r.Col(span, fn),写 c.Text(\"こんにちは、世界。\")。粗体和字号是 template 选项,不是单独方法。",{"name":10057,"text":10058},"生成并验证输出","调用 doc.Generate() 得到 []byte,用 os.WriteFile 写盘。打开 PDF,选中文字粘贴到文本编辑器 — ToUnicode CMap 保证复制粘贴可用。",{},{"title":8370,"description":10034},"zh/blog/007.japanese-pdf-in-go",[1325,1324,4032],"XF2bLbr-URdjq46rnznhVD3z4d5i-OziNbpWceX4ZaU",{"id":10065,"title":10066,"author":10067,"body":10068,"date":11660,"description":11661,"draft":1293,"extension":1294,"howTo":1317,"image":1317,"meta":11662,"navigation":85,"path":2894,"seo":11663,"stem":11664,"tags":11665,"updated":1317,"__hash__":11666},"blogZh/zh/blog/002.go-pdf-library-showdown-2026.md","2026 年 Go PDF 库终极对决",{"name":8,"url":9},{"type":11,"value":10069,"toc":11642},[10070,10072,10078,10098,10101,10107,10111,10114,10145,10148,10151,10154,10332,10338,10341,10353,10356,10381,10387,10390,10476,10485,10488,10495,10504,10510,10516,10523,10525,10535,10612,10615,10617,10671,10674,10680,10683,10721,10724,10727,10767,10770,10774,10777,11495,11505,11508,11511,11550,11552,11558,11568,11574,11580,11586,11588,11590,11602,11611,11613,11639],[14,10071,1336],{"id":1335},[18,10073,10074,10075,10077],{},"5 年前，搜索\"Go PDF\"几乎必然落在 ",[30,10076,1354],{},"。今天，它已归档。社区 fork go-pdf/fpdf 也归档了。真正的选择比搜索结果暗示的少得多：",[1111,10079,10080,10086,10092],{},[885,10081,10082,10085],{},[30,10083,10084],{},"仍在积极维护","：gpdf（本团队）、signintech/gopdf、johnfercher/maroto v2 —— 但 Maroto 仍依赖已归档的 gofpdf。",[885,10087,10088,10091],{},[30,10089,10090],{},"已归档","：jung-kurt/gofpdf（2021）、go-pdf/fpdf（2025）。",[885,10093,10094,10097],{},[30,10095,10096],{},"商用 / AGPL","：unidoc/unipdf。",[18,10099,10100],{},"本文在 4 种工作负载上对现役库做基准测试，列出许可证与依赖图，给出按用例的推荐。明年再跑一次。",[18,10102,10103,10104,10106],{},"偏见声明：我们是 gpdf 团队。基准测试代码公开（",[47,10105,1396],{},"），欢迎自行克隆复测，若数字对不上请告知。",[14,10108,10110],{"id":10109},"go-pdf-库其实是三种东西","\"Go PDF 库\"其实是三种东西",[18,10112,10113],{},"\"Go PDF 库\"这个短语至少盖住 3 种不同的东西：",[882,10115,10116,10126,10137],{},[885,10117,10118,10121,10122,853,10124,720],{},[30,10119,10120],{},"低层 PDF 写入器"," —— 推字节、用图元绘制。",[47,10123,1354],{},[47,10125,1435],{},[885,10127,10128,10131,10132,853,10135,720],{},[30,10129,10130],{},"包装写入器的布局库"," —— 声明式的行列。",[47,10133,10134],{},"johnfercher/maroto v2",[47,10136,27],{},[885,10138,10139,10142,10143,720],{},[30,10140,10141],{},"完整文档套件"," —— 解析、签名、PDF/A、OCR、涂黑。",[47,10144,6922],{},[18,10146,10147],{},"不明确类别就问\"哪个 Go PDF 库最好\"，是 Reddit / 知乎上的讨论跑偏的主因。下文每节都尽量保持这个区分清晰。",[18,10149,10150],{},"未列入：任何调起 headless Chromium 的方案（go-rod、chromedp）。它们不是 PDF 库，是\"会打印的浏览器\"。对 CSS 重的设计还原度好，但冷启动慢、内存大、不适合 distroless。如果需求是\"设计师给我 HTML/CSS，要像素级精确\"，那些工具存在，本文不参与那块比较。",[14,10152,10153],{"id":10153},"记分板",[740,10155,10156,10182],{},[743,10157,10158],{},[746,10159,10160,10162,10165,10168,10170,10173,10176,10179],{},[749,10161,6815],{},[749,10163,10164],{},"最后发布",[749,10166,10167],{},"归档",[749,10169,5121],{},[749,10171,10172],{},"核心依赖",[749,10174,10175],{},"CJK",[749,10177,10178],{},"布局栅格",[749,10180,10181],{},"2026 状态",[758,10183,10184,10213,10233,10256,10282,10308],{},[746,10185,10186,10191,10194,10197,10199,10204,10206,10209],{},[763,10187,10188,10190],{},[30,10189,27],{},"（本团队）",[763,10192,10193],{},"活跃",[763,10195,10196],{},"—",[763,10198,6844],{},[763,10200,10201],{},[30,10202,10203],{},"0",[763,10205,6974],{},[763,10207,10208],{},"12 栅格",[763,10210,10211],{},[30,10212,6882],{},[746,10214,10215,10217,10219,10221,10223,10225,10228,10231],{},[763,10216,1435],{},[763,10218,10193],{},[763,10220,10196],{},[763,10222,6844],{},[763,10224,10203],{},[763,10226,10227],{},"手动 TTF",[763,10229,10230],{},"无",[763,10232,6882],{},[746,10234,10235,10237,10239,10241,10243,10248,10250,10253],{},[763,10236,10134],{},[763,10238,10193],{},[763,10240,10196],{},[763,10242,6844],{},[763,10244,10245],{},[30,10246,10247],{},"gofpdf（已归档）",[763,10249,6907],{},[763,10251,10252],{},"行/列",[763,10254,10255],{},"地基已死",[746,10257,10258,10260,10263,10268,10270,10272,10276,10278],{},[763,10259,1354],{},[763,10261,10262],{},"2021",[763,10264,10265],{},[30,10266,10267],{},"2021-09-08",[763,10269,6844],{},[763,10271,10203],{},[763,10273,10274],{},[47,10275,2563],{},[763,10277,10230],{},[763,10279,10280],{},[30,10281,10090],{},[746,10283,10284,10286,10289,10294,10296,10298,10302,10304],{},[763,10285,1432],{},[763,10287,10288],{},"2023",[763,10290,10291],{},[30,10292,10293],{},"2025",[763,10295,6844],{},[763,10297,10203],{},[763,10299,10300],{},[47,10301,2563],{},[763,10303,10230],{},[763,10305,10306],{},[30,10307,10090],{},[746,10309,10310,10312,10314,10316,10321,10324,10327,10329],{},[763,10311,6922],{},[763,10313,10193],{},[763,10315,10196],{},[763,10317,10318],{},[30,10319,10320],{},"AGPL-3.0 / 商用",[763,10322,10323],{},"多",[763,10325,10326],{},"有",[763,10328,10230],{},[763,10330,10331],{},"商用",[18,10333,10334,10335,720],{},"要注意 3 点。半数已归档。Maroto 自身在维护，但地基没人管 —— 即便今天能编译，仍是供应链问题。不接受 AGPL 的团队，unidoc 的选择不是技术问题而是 ",[30,10336,10337],{},"商业许可采购问题",[14,10339,10340],{"id":10340},"基准测试",[18,10342,10343,10344,10349,10350,10352],{},"代码：gpdf 仓库里的 ",[22,10345,10347],{"href":1392,"rel":10346},[26],[47,10348,1396],{},"。环境：Apple M1（Max, 32 GB, macOS 14.5），Go 1.25，无 CGO。每个用例至少跑 5 秒实时，",[47,10351,1414],{}," 打开，记录 ns/op 和分配次数。",[18,10354,10355],{},"挑这 4 个场景是因为它们接近生产中实际生成的东西，不是合成玩具：",[882,10357,10358,10364,10370,10376],{},[885,10359,10360,10363],{},[30,10361,10362],{},"单页 hello world","。1 页、1 行字、1 字体。每文档固定开销的下限。",[885,10365,10366,10369],{},[30,10367,10368],{},"4 列 10 行发票表","。表头 + 10 行内容 + 列对齐 + 细边框。\"生成发票\"的形状。",[885,10371,10372,10375],{},[30,10373,10374],{},"100 页分页报表","。重复页眉、页脚、页码，每页正文。度量分页成本。",[885,10377,10378,10380],{},[30,10379,1507],{},"。日文（平假名、片假名、汉字）混排，4 列 15 行明细、页眉、带页码的页脚，嵌入 NotoSansJP TrueType 子集。",[18,10382,10383,10384,10386],{},"未纳入：",[47,10385,6922],{},"。其二进制由许可证门控，在公开基准仓库里复现其发布方法论会产生误导。若在评估 unidoc，请跑其官方基准。",[1946,10388,10389],{"id":10389},"结果",[740,10391,10392,10408],{},[743,10393,10394],{},[746,10395,10396,10398,10400,10402,10404,10406],{},[749,10397,1424],{},[749,10399,27],{},[749,10401,1435],{},[749,10403,1438],{},[749,10405,1429],{},[749,10407,1432],{},[758,10409,10410,10426,10443,10460],{},[746,10411,10412,10414,10418,10420,10422,10424],{},[763,10413,10362],{},[763,10415,10416],{},[30,10417,1342],{},[763,10419,1458],{},[763,10421,1461],{},[763,10423,1452],{},[763,10425,1455],{},[746,10427,10428,10431,10435,10437,10439,10441],{},[763,10429,10430],{},"4×10 发票表",[763,10432,10433],{},[30,10434,1346],{},[763,10436,1479],{},[763,10438,1482],{},[763,10440,1473],{},[763,10442,1476],{},[746,10444,10445,10448,10452,10454,10456,10458],{},[763,10446,10447],{},"100 页报表",[763,10449,10450],{},[30,10451,1350],{},[763,10453,1482],{},[763,10455,1502],{},[763,10457,1494],{},[763,10459,1497],{},[746,10461,10462,10464,10468,10470,10472,10474],{},[763,10463,1507],{},[763,10465,10466],{},[30,10467,1512],{},[763,10469,1521],{},[763,10471,1524],{},[763,10473,1515],{},[763,10475,1518],{},[18,10477,10478,10479,10481,10482,10484],{},"go-pdf/fpdf 的 CJK 栏 ",[47,10480,1518],{},"：测试版本中 ",[47,10483,2563],{}," 在读取 NotoSansJP 的 cmap format 12 表时 panic。可修但 fork 已归档，没人发补丁。",[1946,10486,10487],{"id":10487},"读数字",[18,10489,10490,10491,10494],{},"跨场景排名稳定。在所有用例里 gpdf 比第二名 ",[30,10492,10493],{},"快 10–30 倍","，既不魔也不意外。3 个设计叠加：",[18,10496,10497,10500,10501,720],{},[30,10498,10499],{},"单遍布局","。gpdf 不构建中间 AST 再序列化。构建器在解析时直接写 PDF 内容流，分配数约为其他库的一半。100 页基准里 683 µs 对 19,800 µs 的差距，不是调参差异，是 ",[30,10502,10503],{},"架构不同",[18,10505,10506,10509],{},[30,10507,10508],{},"热路径无反射","。布局引擎触碰的类型都是具体类型。单独看是微优化，但跨 100 页报表累积后，接口分派会在 profile 中显形。我们避开了。",[18,10511,10512,10515],{},[30,10513,10514],{},"TrueType 子集器不重读 cmap","。gofpdf 每次字形查询都重读 cmap 表；gpdf 解析一次后缓存。纯 Latin 几乎无感；CJK 一段可能跨汉字、假名、标点用到 150 个字形，差距就是\"同步生成够用\"和\"要排队\"。",[18,10517,10518,10519,10522],{},"基准表看不到的告诫：",[30,10520,10521],{},"对大部分 PDF 工作负载，绝对速度不如人想得那么重要","。有意义的阈值是\"能不能在请求路径上同步生成\"。单页 hello world，本文所有库都过阈值；100 页报表，只有 gpdf 过。若最大文档就是一张收据，现役 4 库都行，按 API 与许可证选。",[14,10524,8518],{"id":8518},[18,10526,10527,10528,10531,10532,10534],{},"各库 ",[47,10529,10530],{},"go get"," 后 ",[47,10533,7063],{}," 的结果：",[740,10536,10537,10549],{},[743,10538,10539],{},[746,10540,10541,10543,10546],{},[749,10542,6815],{},[749,10544,10545],{},"外部模块",[749,10547,10548],{},"传递性归档依赖",[758,10550,10551,10564,10572,10582,10590,10602],{},[746,10552,10553,10558,10562],{},[763,10554,10555,10557],{},[30,10556,27],{},"（核心）",[763,10559,10560],{},[30,10561,10203],{},[763,10563,10196],{},[746,10565,10566,10568,10570],{},[763,10567,1435],{},[763,10569,10203],{},[763,10571,10196],{},[746,10573,10574,10576,10579],{},[763,10575,1429],{},[763,10577,10578],{},"0（自身归档）",[763,10580,10581],{},"自身",[746,10583,10584,10586,10588],{},[763,10585,1432],{},[763,10587,10578],{},[763,10589,10581],{},[746,10591,10592,10594,10599],{},[763,10593,10134],{},[763,10595,10596],{},[30,10597,10598],{},"gofpdf（2021 归档）",[763,10600,10601],{},"是 — gofpdf",[746,10603,10604,10606,10609],{},[763,10605,6922],{},[763,10607,10608],{},"多（图像、加密、压缩）",[763,10610,10611],{},"无归档",[18,10613,10614],{},"有\"生产依赖禁止归档仓库\"lint 规则的团队，今天的 Maroto v2 会被这条挡下。Maroto 维护者一年多来在替换 gofpdf，完成后此行会变。请在决策前确认 Maroto 仓库的当前状态。",[14,10616,5121],{"id":5121},[740,10618,10619,10627],{},[743,10620,10621],{},[746,10622,10623,10625],{},[749,10624,6815],{},[749,10626,5121],{},[758,10628,10629,10636,10642,10648,10654,10660],{},[746,10630,10631,10634],{},[763,10632,10633],{},"gpdf（核心）",[763,10635,6844],{},[746,10637,10638,10640],{},[763,10639,1435],{},[763,10641,6844],{},[746,10643,10644,10646],{},[763,10645,10134],{},[763,10647,6844],{},[746,10649,10650,10652],{},[763,10651,1429],{},[763,10653,6844],{},[746,10655,10656,10658],{},[763,10657,1432],{},[763,10659,6844],{},[746,10661,10662,10666],{},[763,10663,10664],{},[30,10665,6922],{},[763,10667,10668],{},[30,10669,10670],{},"AGPL-3.0 或商用许可",[18,10672,10673],{},"unidoc 的 AGPL 条款严格：若用于用户通过网络交互的服务器，你的服务器端代码也必须以 AGPL 发布 —— 多数闭源 SaaS 无法接受。实际上商用许可成为唯一可行路径，价格不公开，需与销售沟通。",[18,10675,10676,10677,10679],{},"GitHub star 比较中最常被忽视的就是此处。unidoc 功能最多、星最多，但许可证对多数商用用例关上门（需购买）。并非贬低 unidoc —— 商业模式合理、产品优秀 —— 只是 ",[47,10678,10530],{}," 前请心里有数。",[14,10681,10682],{"id":10682},"维护状况",[1111,10684,10685,10690,10695,10700,10708,10716],{},[885,10686,10687,10689],{},[30,10688,27],{}," —— 主维护者为本团队（gpdf-dev）。每 2–4 周发布一次；路线图在 repo 内；CI 覆盖 Go 1.22–1.26；主 repo 的 issue 数工作日内回应。",[885,10691,10692,10694],{},[30,10693,1435],{}," —— 活跃，提交节奏较慢。issue 会被看，PR 一般数周内合并。主用途仍是低层生成。",[885,10696,10697,10699],{},[30,10698,1438],{}," —— 活跃。v2 在 2023 年落地后稳定。gofpdf 依赖已知、正在替换。决策前看 repo 当前状态。",[885,10701,10702,10704,10705,10707],{},[30,10703,1429],{}," —— 2021-09-08 归档。仓库横幅：",[6671,10706,6673],{}," 不发安全补丁、不修 bug。",[885,10709,10710,10712,10713,720],{},[30,10711,1432],{}," —— 2025 年归档。README 建议改用其他库。我们写了专门的迁移指南：",[22,10714,10715],{"href":2823},"gofpdf 已归档：迁移到 gpdf",[885,10717,10718,10720],{},[30,10719,6922],{}," —— 活跃，商业团队，资源充足，提供企业支持。",[14,10722,10723],{"id":10723},"怎么选",[18,10725,10726],{},"给决策树而不是功能矩阵。\"功能最多\"往往不是正确问题：",[1111,10728,10729,10735,10741,10747,10753,10761],{},[885,10730,10731,10734],{},[30,10732,10733],{},"\"Go 代码库要生成发票、报表、文档，希望 MIT、零依赖，文档中偶尔有 CJK。\""," → gpdf。",[885,10736,10737,10740],{},[30,10738,10739],{},"\"低层 PDF 生成，带自定义几何，想要小、稳、手动控制的库。\""," → signintech/gopdf。",[885,10742,10743,10746],{},[30,10744,10745],{},"\"已有 Maroto 风格的布局代码，能跑。\""," → 保持 Maroto v2 直到 gofpdf 替换落地后再评估。API 本身不是问题。",[885,10748,10749,10752],{},[30,10750,10751],{},"\"需要 PDF/A、OCR、涂黑、电子签名，公司愿意付商用许可费。\""," → unidoc/unipdf，先谈许可证。",[885,10754,10755,10758,10759,720],{},[30,10756,10757],{},"\"仍在 gofpdf 上，跑得好好的。\""," → 今天没事。在下一个相关依赖 CVE 出现前规划迁移。",[22,10760,2824],{"href":2823},[885,10762,10763,10766],{},[30,10764,10765],{},"\"需要像素级 HTML/CSS 转 PDF。\""," → 以上都不行。用 go-rod / chromedp + headless Chromium，接受冷启动成本。",[18,10768,10769],{},"我们是 gpdf 团队，自然认为第 1 和第 5 类的大多数场景 gpdf 是合理默认 —— 偏见在所难免。请读基准代码、在本地跑一跑，别照单全收这张表。",[14,10771,10773],{"id":10772},"_30-行的-gpdf-示例","30 行的 gpdf 示例",[18,10775,10776],{},"\"最快\"与\"依赖图最小\"只有代码能读时才有意义。完整可运行的发票一页，无伪代码、无省略 import：",[59,10778,10780],{"className":61,"code":10779,"language":63,"meta":64,"style":64},"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 小时\", \"¥1,050\", \"¥42,000\"},\n                    {\"后端开发\", \"60 小时\", \"¥1,050\", \"¥63,000\"},\n                    {\"UI 设计\", \"20 小时\", \"¥840\",   \"¥16,800\"},\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",[47,10781,10782,10788,10792,10798,10806,10814,10818,10826,10834,10843,10851,10855,10859,10869,10883,10901,10931,10935,10939,10953,10977,11007,11046,11068,11079,11127,11137,11178,11217,11258,11263,11294,11305,11317,11337,11363,11368,11373,11377,11381,11385,11403,11415,11429,11433,11473,11487,11491],{"__ignoreMap":64},[68,10783,10784,10786],{"class":70,"line":71},[68,10785,75],{"class":74},[68,10787,79],{"class":78},[68,10789,10790],{"class":70,"line":82},[68,10791,86],{"emptyLinePlaceholder":85},[68,10793,10794,10796],{"class":70,"line":89},[68,10795,93],{"class":92},[68,10797,96],{"class":74},[68,10799,10800,10802,10804],{"class":70,"line":99},[68,10801,102],{"class":74},[68,10803,105],{"class":78},[68,10805,108],{"class":74},[68,10807,10808,10810,10812],{"class":70,"line":111},[68,10809,102],{"class":74},[68,10811,116],{"class":78},[68,10813,108],{"class":74},[68,10815,10816],{"class":70,"line":121},[68,10817,86],{"emptyLinePlaceholder":85},[68,10819,10820,10822,10824],{"class":70,"line":126},[68,10821,102],{"class":74},[68,10823,131],{"class":78},[68,10825,108],{"class":74},[68,10827,10828,10830,10832],{"class":70,"line":136},[68,10829,102],{"class":74},[68,10831,141],{"class":78},[68,10833,108],{"class":74},[68,10835,10836,10838,10841],{"class":70,"line":146},[68,10837,102],{"class":74},[68,10839,10840],{"class":78},"github.com/gpdf-dev/gpdf/pdf",[68,10842,108],{"class":74},[68,10844,10845,10847,10849],{"class":70,"line":156},[68,10846,102],{"class":74},[68,10848,151],{"class":78},[68,10850,108],{"class":74},[68,10852,10853],{"class":70,"line":162},[68,10854,159],{"class":74},[68,10856,10857],{"class":70,"line":167},[68,10858,86],{"emptyLinePlaceholder":85},[68,10860,10861,10863,10865,10867],{"class":70,"line":183},[68,10862,170],{"class":74},[68,10864,174],{"class":173},[68,10866,177],{"class":74},[68,10868,180],{"class":74},[68,10870,10871,10873,10875,10877,10879,10881],{"class":70,"line":221},[68,10872,269],{"class":186},[68,10874,196],{"class":74},[68,10876,274],{"class":186},[68,10878,202],{"class":74},[68,10880,279],{"class":173},[68,10882,282],{"class":74},[68,10884,10885,10887,10889,10891,10893,10895,10897,10899],{"class":70,"line":237},[68,10886,288],{"class":186},[68,10888,202],{"class":74},[68,10890,293],{"class":173},[68,10892,208],{"class":74},[68,10894,320],{"class":186},[68,10896,202],{"class":74},[68,10898,302],{"class":186},[68,10900,305],{"class":74},[68,10902,10903,10905,10907,10909,10911,10913,10915,10917,10919,10921,10923,10925,10927,10929],{"class":70,"line":255},[68,10904,288],{"class":186},[68,10906,202],{"class":74},[68,10908,315],{"class":173},[68,10910,208],{"class":74},[68,10912,320],{"class":186},[68,10914,202],{"class":74},[68,10916,325],{"class":173},[68,10918,208],{"class":74},[68,10920,320],{"class":186},[68,10922,202],{"class":74},[68,10924,334],{"class":173},[68,10926,208],{"class":74},[68,10928,340],{"class":339},[68,10930,343],{"class":74},[68,10932,10933],{"class":70,"line":261},[68,10934,400],{"class":74},[68,10936,10937],{"class":70,"line":266},[68,10938,86],{"emptyLinePlaceholder":85},[68,10940,10941,10943,10945,10947,10949,10951],{"class":70,"line":285},[68,10942,411],{"class":186},[68,10944,196],{"class":74},[68,10946,416],{"class":186},[68,10948,202],{"class":74},[68,10950,421],{"class":173},[68,10952,424],{"class":74},[68,10954,10955,10957,10959,10961,10963,10965,10967,10969,10971,10973,10975],{"class":70,"line":308},[68,10956,430],{"class":186},[68,10958,202],{"class":74},[68,10960,435],{"class":173},[68,10962,438],{"class":74},[68,10964,442],{"class":441},[68,10966,445],{"class":74},[68,10968,448],{"class":78},[68,10970,202],{"class":74},[68,10972,453],{"class":78},[68,10974,456],{"class":74},[68,10976,180],{"class":74},[68,10978,10979,10981,10983,10985,10987,10989,10991,10993,10995,10997,10999,11001,11003,11005],{"class":70,"line":346},[68,10980,464],{"class":186},[68,10982,202],{"class":74},[68,10984,469],{"class":173},[68,10986,208],{"class":74},[68,10988,474],{"class":339},[68,10990,190],{"class":74},[68,10992,479],{"class":74},[68,10994,482],{"class":441},[68,10996,445],{"class":74},[68,10998,448],{"class":78},[68,11000,202],{"class":74},[68,11002,491],{"class":78},[68,11004,456],{"class":74},[68,11006,180],{"class":74},[68,11008,11009,11011,11013,11015,11017,11019,11022,11024,11026,11028,11030,11032,11034,11036,11038,11040,11042,11044],{"class":70,"line":372},[68,11010,501],{"class":186},[68,11012,202],{"class":74},[68,11014,506],{"class":173},[68,11016,208],{"class":74},[68,11018,211],{"class":74},[68,11020,11021],{"class":214},"发票 #2026-0042",[68,11023,211],{"class":74},[68,11025,190],{"class":74},[68,11027,520],{"class":186},[68,11029,202],{"class":74},[68,11031,540],{"class":173},[68,11033,7606],{"class":74},[68,11035,520],{"class":186},[68,11037,202],{"class":74},[68,11039,525],{"class":173},[68,11041,208],{"class":74},[68,11043,340],{"class":339},[68,11045,5007],{"class":74},[68,11047,11048,11050,11052,11054,11056,11058,11060,11062,11064,11066],{"class":70,"line":397},[68,11049,501],{"class":186},[68,11051,202],{"class":74},[68,11053,2129],{"class":173},[68,11055,208],{"class":74},[68,11057,320],{"class":186},[68,11059,202],{"class":74},[68,11061,334],{"class":173},[68,11063,208],{"class":74},[68,11065,5632],{"class":339},[68,11067,5007],{"class":74},[68,11069,11070,11072,11074,11077],{"class":70,"line":403},[68,11071,501],{"class":186},[68,11073,202],{"class":74},[68,11075,11076],{"class":173},"Table",[68,11078,282],{"class":74},[68,11080,11081,11084,11087,11090,11092,11095,11097,11099,11101,11104,11106,11108,11110,11113,11115,11117,11119,11122,11124],{"class":70,"line":408},[68,11082,11083],{"class":74},"                []",[68,11085,11086],{"class":1618},"string",[68,11088,11089],{"class":74},"{",[68,11091,211],{"class":74},[68,11093,11094],{"class":214},"项目",[68,11096,211],{"class":74},[68,11098,190],{"class":74},[68,11100,7418],{"class":74},[68,11102,11103],{"class":214},"数量",[68,11105,211],{"class":74},[68,11107,190],{"class":74},[68,11109,7418],{"class":74},[68,11111,11112],{"class":214},"单价",[68,11114,211],{"class":74},[68,11116,190],{"class":74},[68,11118,7418],{"class":74},[68,11120,11121],{"class":214},"金额",[68,11123,211],{"class":74},[68,11125,11126],{"class":74},"},\n",[68,11128,11129,11132,11134],{"class":70,"line":427},[68,11130,11131],{"class":74},"                [][]",[68,11133,11086],{"class":1618},[68,11135,11136],{"class":74},"{\n",[68,11138,11139,11142,11144,11147,11149,11151,11153,11156,11158,11160,11162,11165,11167,11169,11171,11174,11176],{"class":70,"line":461},[68,11140,11141],{"class":74},"                    {",[68,11143,211],{"class":74},[68,11145,11146],{"class":214},"前端开发",[68,11148,211],{"class":74},[68,11150,190],{"class":74},[68,11152,7418],{"class":74},[68,11154,11155],{"class":214},"40 小时",[68,11157,211],{"class":74},[68,11159,190],{"class":74},[68,11161,7418],{"class":74},[68,11163,11164],{"class":214},"¥1,050",[68,11166,211],{"class":74},[68,11168,190],{"class":74},[68,11170,7418],{"class":74},[68,11172,11173],{"class":214},"¥42,000",[68,11175,211],{"class":74},[68,11177,11126],{"class":74},[68,11179,11180,11182,11184,11187,11189,11191,11193,11196,11198,11200,11202,11204,11206,11208,11210,11213,11215],{"class":70,"line":498},[68,11181,11141],{"class":74},[68,11183,211],{"class":74},[68,11185,11186],{"class":214},"后端开发",[68,11188,211],{"class":74},[68,11190,190],{"class":74},[68,11192,7418],{"class":74},[68,11194,11195],{"class":214},"60 小时",[68,11197,211],{"class":74},[68,11199,190],{"class":74},[68,11201,7418],{"class":74},[68,11203,11164],{"class":214},[68,11205,211],{"class":74},[68,11207,190],{"class":74},[68,11209,7418],{"class":74},[68,11211,11212],{"class":214},"¥63,000",[68,11214,211],{"class":74},[68,11216,11126],{"class":74},[68,11218,11219,11221,11223,11226,11228,11230,11232,11235,11237,11239,11241,11244,11246,11248,11251,11254,11256],{"class":70,"line":546},[68,11220,11141],{"class":74},[68,11222,211],{"class":74},[68,11224,11225],{"class":214},"UI 设计",[68,11227,211],{"class":74},[68,11229,190],{"class":74},[68,11231,7418],{"class":74},[68,11233,11234],{"class":214},"20 小时",[68,11236,211],{"class":74},[68,11238,190],{"class":74},[68,11240,7418],{"class":74},[68,11242,11243],{"class":214},"¥840",[68,11245,211],{"class":74},[68,11247,190],{"class":74},[68,11249,11250],{"class":74},"   \"",[68,11252,11253],{"class":214},"¥16,800",[68,11255,211],{"class":74},[68,11257,11126],{"class":74},[68,11259,11260],{"class":70,"line":566},[68,11261,11262],{"class":74},"                },\n",[68,11264,11265,11268,11270,11273,11275,11278,11280,11283,11285,11287,11289,11292],{"class":70,"line":572},[68,11266,11267],{"class":186},"                template",[68,11269,202],{"class":74},[68,11271,11272],{"class":173},"ColumnWidths",[68,11274,208],{"class":74},[68,11276,11277],{"class":339},"50",[68,11279,190],{"class":74},[68,11281,11282],{"class":339}," 15",[68,11284,190],{"class":74},[68,11286,11282],{"class":339},[68,11288,190],{"class":74},[68,11290,11291],{"class":339}," 20",[68,11293,305],{"class":74},[68,11295,11296,11298,11300,11303],{"class":70,"line":578},[68,11297,11267],{"class":186},[68,11299,202],{"class":74},[68,11301,11302],{"class":173},"TableHeaderStyle",[68,11304,282],{"class":74},[68,11306,11307,11310,11312,11314],{"class":70,"line":583},[68,11308,11309],{"class":186},"                    template",[68,11311,202],{"class":74},[68,11313,540],{"class":173},[68,11315,11316],{"class":74},"(),\n",[68,11318,11319,11321,11323,11326,11328,11330,11332,11335],{"class":70,"line":604},[68,11320,11309],{"class":186},[68,11322,202],{"class":74},[68,11324,11325],{"class":173},"TextColor",[68,11327,208],{"class":74},[68,11329,2273],{"class":186},[68,11331,202],{"class":74},[68,11333,11334],{"class":186},"White",[68,11336,305],{"class":74},[68,11338,11339,11341,11343,11346,11348,11350,11352,11355,11357,11360],{"class":70,"line":617},[68,11340,11309],{"class":186},[68,11342,202],{"class":74},[68,11344,11345],{"class":173},"BgColor",[68,11347,208],{"class":74},[68,11349,2273],{"class":186},[68,11351,202],{"class":74},[68,11353,11354],{"class":173},"RGBHex",[68,11356,208],{"class":74},[68,11358,11359],{"class":339},"0x1A237E",[68,11361,11362],{"class":74},")),\n",[68,11364,11365],{"class":70,"line":632},[68,11366,11367],{"class":74},"                ),\n",[68,11369,11370],{"class":70,"line":637},[68,11371,11372],{"class":74},"            )\n",[68,11374,11375],{"class":70,"line":683},[68,11376,569],{"class":74},[68,11378,11379],{"class":70,"line":698},[68,11380,575],{"class":74},[68,11382,11383],{"class":70,"line":703},[68,11384,86],{"emptyLinePlaceholder":85},[68,11386,11387,11389,11391,11393,11395,11397,11399,11401],{"class":70,"line":4597},[68,11388,586],{"class":186},[68,11390,190],{"class":74},[68,11392,193],{"class":186},[68,11394,196],{"class":74},[68,11396,416],{"class":186},[68,11398,202],{"class":74},[68,11400,599],{"class":173},[68,11402,424],{"class":74},[68,11404,11405,11407,11409,11411,11413],{"class":70,"line":5835},[68,11406,224],{"class":92},[68,11408,193],{"class":186},[68,11410,229],{"class":74},[68,11412,232],{"class":74},[68,11414,180],{"class":74},[68,11416,11417,11419,11421,11423,11425,11427],{"class":70,"line":5855},[68,11418,240],{"class":186},[68,11420,202],{"class":74},[68,11422,245],{"class":173},[68,11424,208],{"class":74},[68,11426,250],{"class":186},[68,11428,159],{"class":74},[68,11430,11431],{"class":70,"line":5860},[68,11432,258],{"class":74},[68,11434,11435,11437,11439,11441,11443,11445,11447,11449,11451,11453,11455,11457,11459,11461,11463,11465,11467,11469,11471],{"class":70,"line":5891},[68,11436,224],{"class":92},[68,11438,193],{"class":186},[68,11440,196],{"class":74},[68,11442,199],{"class":186},[68,11444,202],{"class":74},[68,11446,650],{"class":173},[68,11448,208],{"class":74},[68,11450,211],{"class":74},[68,11452,4556],{"class":214},[68,11454,211],{"class":74},[68,11456,190],{"class":74},[68,11458,664],{"class":186},[68,11460,190],{"class":74},[68,11462,669],{"class":339},[68,11464,672],{"class":74},[68,11466,193],{"class":186},[68,11468,229],{"class":74},[68,11470,232],{"class":74},[68,11472,180],{"class":74},[68,11474,11475,11477,11479,11481,11483,11485],{"class":70,"line":5911},[68,11476,240],{"class":186},[68,11478,202],{"class":74},[68,11480,245],{"class":173},[68,11482,208],{"class":74},[68,11484,250],{"class":186},[68,11486,159],{"class":74},[68,11488,11489],{"class":70,"line":5916},[68,11490,258],{"class":74},[68,11492,11493],{"class":70,"line":5947},[68,11494,706],{"class":74},[18,11496,11497,11498,11500,11501,11504],{},"零 ",[47,11499,1928],{},"。零手动列宽计算。给文档选项加 ",[47,11502,11503],{},"gpdf.WithFont(\"NotoSansCJK\", ttfBytes)","，上面的中文就能正常渲染，不会变豆腐。",[14,11506,11507],{"id":11507},"未纳入的部分",[18,11509,11510],{},"每篇比较都有\"因为 X 而未纳入\"的小节。我们的：",[1111,11512,11513,11519,11531,11541],{},[885,11514,11515,11518],{},[30,11516,11517],{},"私有 gofpdf fork","。确实存在在生产里的内部 fork。看不到的代码无法基准。",[885,11520,11521,11526,11527,11530],{},[30,11522,11523],{},[47,11524,11525],{},"pdfcpu","。常在\"Go PDF 库\"列表里出现，但主用途是 ",[30,11528,11529],{},"PDF 处理器","（合并、拆分、加密、盖章），不是生成。超出本文范围，会单写一篇处理向的文章。",[885,11532,11533,11540],{},[30,11534,11535,11536,11539],{},"任何包装 ",[47,11537,11538],{},"gotenberg"," 或 headless 浏览器服务的","。不是库，比较不公平。",[885,11542,11543,11549],{},[30,11544,11545,11546,11548],{},"我们自家的 ",[47,11547,27],{}," 基准","。本次比较的焦点是核心数字。",[14,11551,2814],{"id":2813},[18,11553,11554,11557],{},[30,11555,11556],{},"gpdf 为什么比 gofpdf 快 10 倍？是不是有魔法？","\n没有单一魔法。3 个设计叠加：单遍布局（构建器到写入器之间无 AST）、热路径用具体类型、cmap 缓存的 TrueType 子集器。任一单独提供 2× 收益，叠起来是数量级差异。",[18,11559,11560,11563,11564,11567],{},[30,11561,11562],{},"能自己复现这个基准吗？","\n能。",[47,11565,11566],{},"git clone https://github.com/gpdf-dev/gpdf && cd gpdf/_benchmark && go test -bench=. -benchmem","。相同 CPU 架构、相同 Go 版本下数字对不上，请开 issue。基准漂移会发生，我们想知道。",[18,11569,11570,11573],{},[30,11571,11572],{},"gofpdf 会回来吗？","\n现实地讲，不会。最后一次提交是 2021 年。issue tracker 已关闭。即使有人重新打开，游标 + 单字节字体 + 无栅格的架构不是 2026 年合适的起点。当作历史文物、直接迁移更实际。",[18,11575,11576,11579],{},[30,11577,11578],{},"Java iText / Python ReportLab / Node pdfkit 呢？","\n跨语言基准是另一篇。简短版：Go 在吞吐和冷启动上通常赢，在功能广度（尤其 HTML→PDF 保真度）上输。已在 Go 的团队用 gpdf 更快、更小；Python / Node 团队迁移成本高，只有在大流量场景才划算。",[18,11581,11582,11585],{},[30,11583,11584],{},"如果 gpdf 的竞争对手变强，这份对比会保持公平吗？","\n会。每年跑一次。若 signintech/gopdf 推出表格 API 把时间减半，2027 版会写进去。若 Maroto v2 替换完 gofpdf，那一行会改。基准代码故意公开，不用任何人相信我们。",[14,11587,1242],{"id":1241},[18,11589,2862],{},[59,11591,11592],{"className":1248,"code":1249,"language":1250,"meta":64,"style":64},[47,11593,11594],{"__ignoreMap":64},[68,11595,11596,11598,11600],{"class":70,"line":71},[68,11597,63],{"class":78},[68,11599,1259],{"class":214},[68,11601,1262],{"class":214},[18,11603,11604,1269,11608],{},[22,11605,11607],{"href":24,"rel":11606},[26],"⭐ 在 GitHub 点星",[22,11609,1274],{"href":1272,"rel":11610},[26],[14,11612,2887],{"id":2887},[1111,11614,11615,11621,11631],{},[885,11616,11617,11620],{},[22,11618,11619],{"href":2823},"gofpdf 已归档：迁移到 gpdf 的完整指南"," —— 完整 API 映射 + 5 组 Before/After 代码。",[885,11622,11623,11627,11628,11630],{},[22,11624,11626],{"href":1272,"rel":11625},[26],"Quickstart"," —— 含 ",[47,11629,6690],{}," 的 5 分钟上手。",[885,11632,11633,11634,720],{},"基准代码本身：",[22,11635,11637],{"href":1392,"rel":11636},[26],[47,11638,1396],{},[1276,11640,11641],{},"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":64,"searchDepth":82,"depth":82,"links":11643},[11644,11645,11646,11647,11651,11652,11653,11654,11655,11656,11657,11658,11659],{"id":1335,"depth":82,"text":1336},{"id":10109,"depth":82,"text":10110},{"id":10153,"depth":82,"text":10153},{"id":10340,"depth":82,"text":10340,"children":11648},[11649,11650],{"id":10389,"depth":89,"text":10389},{"id":10487,"depth":89,"text":10487},{"id":8518,"depth":82,"text":8518},{"id":5121,"depth":82,"text":5121},{"id":10682,"depth":82,"text":10682},{"id":10723,"depth":82,"text":10723},{"id":10772,"depth":82,"text":10773},{"id":11507,"depth":82,"text":11507},{"id":2813,"depth":82,"text":2814},{"id":1241,"depth":82,"text":1242},{"id":2887,"depth":82,"text":2887},"2026-04-15","2026 年仍在维护的 Go PDF 库，在 4 种工作负载上基准测试，并对比许可证、依赖与维护状态。",{},{"title":10066,"description":11661},"zh/blog/002.go-pdf-library-showdown-2026",[2943,2941],"yXXt1Esago0CnHoFxknEwym8EOoHrbkdoxqhMvutRrA",{"id":11668,"title":1223,"author":11669,"body":11670,"date":11660,"description":12964,"draft":1293,"extension":1294,"howTo":12965,"image":1317,"meta":12982,"navigation":85,"path":1222,"seo":12983,"stem":12984,"tags":12985,"updated":1317,"__hash__":12986},"blogZh/zh/blog/003.embed-japanese-font.md",{"name":8,"url":9},{"type":11,"value":11671,"toc":12953},[11672,11674,11684,11686,11699,11701,12205,12219,12222,12225,12231,12240,12253,12256,12266,12411,12428,12431,12438,12864,12871,12875,12881,12887,12901,12903,12927,12929,12931,12943,12951],[14,11673,16],{"id":16},[18,11675,11676,11677,11680,11681,11683],{},"如何用 ",[22,11678,27],{"href":24,"rel":11679},[26]," 生成带日文（或任意 CJK）文本的 PDF —— 不用重复 ",[47,11682,2563],{}," 那一套，不引入 CGO，也不在每个 PDF 里塞 5 MB 的字体文件？",[14,11685,36],{"id":36},[18,11687,11688,11689,8385,11692,11694,11695,11698],{},"读取 TTF 字节；把 ",[47,11690,11691],{},"gpdf.WithFont(\"NotoSansJP\", fontBytes)",[47,11693,279],{},"；可选地设为默认字体。",[30,11696,11697],{},"三行设置，gpdf 会自动把实际用到的字形子集化","，最终 PDF 里只携带你用到的那部分字形。",[14,11700,57],{"id":57},[59,11702,11704],{"className":61,"code":11703,"language":63,"meta":64,"style":64},"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",[47,11705,11706,11712,11716,11722,11730,11738,11742,11750,11758,11766,11770,11774,11784,11810,11822,11836,11840,11844,11858,11876,11906,11928,11950,11954,11958,11972,11996,12026,12064,12083,12087,12091,12095,12113,12125,12139,12143,12183,12197,12201],{"__ignoreMap":64},[68,11707,11708,11710],{"class":70,"line":71},[68,11709,75],{"class":74},[68,11711,79],{"class":78},[68,11713,11714],{"class":70,"line":82},[68,11715,86],{"emptyLinePlaceholder":85},[68,11717,11718,11720],{"class":70,"line":89},[68,11719,93],{"class":92},[68,11721,96],{"class":74},[68,11723,11724,11726,11728],{"class":70,"line":99},[68,11725,102],{"class":74},[68,11727,105],{"class":78},[68,11729,108],{"class":74},[68,11731,11732,11734,11736],{"class":70,"line":111},[68,11733,102],{"class":74},[68,11735,116],{"class":78},[68,11737,108],{"class":74},[68,11739,11740],{"class":70,"line":121},[68,11741,86],{"emptyLinePlaceholder":85},[68,11743,11744,11746,11748],{"class":70,"line":126},[68,11745,102],{"class":74},[68,11747,131],{"class":78},[68,11749,108],{"class":74},[68,11751,11752,11754,11756],{"class":70,"line":136},[68,11753,102],{"class":74},[68,11755,141],{"class":78},[68,11757,108],{"class":74},[68,11759,11760,11762,11764],{"class":70,"line":146},[68,11761,102],{"class":74},[68,11763,151],{"class":78},[68,11765,108],{"class":74},[68,11767,11768],{"class":70,"line":156},[68,11769,159],{"class":74},[68,11771,11772],{"class":70,"line":162},[68,11773,86],{"emptyLinePlaceholder":85},[68,11775,11776,11778,11780,11782],{"class":70,"line":167},[68,11777,170],{"class":74},[68,11779,174],{"class":173},[68,11781,177],{"class":74},[68,11783,180],{"class":74},[68,11785,11786,11788,11790,11792,11794,11796,11798,11800,11802,11804,11806,11808],{"class":70,"line":183},[68,11787,187],{"class":186},[68,11789,190],{"class":74},[68,11791,193],{"class":186},[68,11793,196],{"class":74},[68,11795,199],{"class":186},[68,11797,202],{"class":74},[68,11799,205],{"class":173},[68,11801,208],{"class":74},[68,11803,211],{"class":74},[68,11805,3008],{"class":214},[68,11807,211],{"class":74},[68,11809,159],{"class":74},[68,11811,11812,11814,11816,11818,11820],{"class":70,"line":221},[68,11813,224],{"class":92},[68,11815,193],{"class":186},[68,11817,229],{"class":74},[68,11819,232],{"class":74},[68,11821,180],{"class":74},[68,11823,11824,11826,11828,11830,11832,11834],{"class":70,"line":237},[68,11825,240],{"class":186},[68,11827,202],{"class":74},[68,11829,245],{"class":173},[68,11831,208],{"class":74},[68,11833,250],{"class":186},[68,11835,159],{"class":74},[68,11837,11838],{"class":70,"line":255},[68,11839,258],{"class":74},[68,11841,11842],{"class":70,"line":261},[68,11843,86],{"emptyLinePlaceholder":85},[68,11845,11846,11848,11850,11852,11854,11856],{"class":70,"line":266},[68,11847,269],{"class":186},[68,11849,196],{"class":74},[68,11851,274],{"class":186},[68,11853,202],{"class":74},[68,11855,279],{"class":173},[68,11857,282],{"class":74},[68,11859,11860,11862,11864,11866,11868,11870,11872,11874],{"class":70,"line":285},[68,11861,288],{"class":186},[68,11863,202],{"class":74},[68,11865,293],{"class":173},[68,11867,208],{"class":74},[68,11869,27],{"class":186},[68,11871,202],{"class":74},[68,11873,302],{"class":186},[68,11875,305],{"class":74},[68,11877,11878,11880,11882,11884,11886,11888,11890,11892,11894,11896,11898,11900,11902,11904],{"class":70,"line":308},[68,11879,288],{"class":186},[68,11881,202],{"class":74},[68,11883,315],{"class":173},[68,11885,208],{"class":74},[68,11887,320],{"class":186},[68,11889,202],{"class":74},[68,11891,325],{"class":173},[68,11893,208],{"class":74},[68,11895,320],{"class":186},[68,11897,202],{"class":74},[68,11899,334],{"class":173},[68,11901,208],{"class":74},[68,11903,340],{"class":339},[68,11905,343],{"class":74},[68,11907,11908,11910,11912,11914,11916,11918,11920,11922,11924,11926],{"class":70,"line":346},[68,11909,288],{"class":186},[68,11911,202],{"class":74},[68,11913,353],{"class":173},[68,11915,208],{"class":74},[68,11917,211],{"class":74},[68,11919,3246],{"class":214},[68,11921,211],{"class":74},[68,11923,190],{"class":74},[68,11925,367],{"class":186},[68,11927,305],{"class":74},[68,11929,11930,11932,11934,11936,11938,11940,11942,11944,11946,11948],{"class":70,"line":372},[68,11931,288],{"class":186},[68,11933,202],{"class":74},[68,11935,379],{"class":173},[68,11937,208],{"class":74},[68,11939,211],{"class":74},[68,11941,3246],{"class":214},[68,11943,211],{"class":74},[68,11945,190],{"class":74},[68,11947,3275],{"class":339},[68,11949,305],{"class":74},[68,11951,11952],{"class":70,"line":397},[68,11953,400],{"class":74},[68,11955,11956],{"class":70,"line":403},[68,11957,86],{"emptyLinePlaceholder":85},[68,11959,11960,11962,11964,11966,11968,11970],{"class":70,"line":408},[68,11961,411],{"class":186},[68,11963,196],{"class":74},[68,11965,416],{"class":186},[68,11967,202],{"class":74},[68,11969,421],{"class":173},[68,11971,424],{"class":74},[68,11973,11974,11976,11978,11980,11982,11984,11986,11988,11990,11992,11994],{"class":70,"line":427},[68,11975,430],{"class":186},[68,11977,202],{"class":74},[68,11979,435],{"class":173},[68,11981,438],{"class":74},[68,11983,442],{"class":441},[68,11985,445],{"class":74},[68,11987,448],{"class":78},[68,11989,202],{"class":74},[68,11991,453],{"class":78},[68,11993,456],{"class":74},[68,11995,180],{"class":74},[68,11997,11998,12000,12002,12004,12006,12008,12010,12012,12014,12016,12018,12020,12022,12024],{"class":70,"line":461},[68,11999,464],{"class":186},[68,12001,202],{"class":74},[68,12003,469],{"class":173},[68,12005,208],{"class":74},[68,12007,474],{"class":339},[68,12009,190],{"class":74},[68,12011,479],{"class":74},[68,12013,482],{"class":441},[68,12015,445],{"class":74},[68,12017,448],{"class":78},[68,12019,202],{"class":74},[68,12021,491],{"class":78},[68,12023,456],{"class":74},[68,12025,180],{"class":74},[68,12027,12028,12030,12032,12034,12036,12038,12040,12042,12044,12046,12048,12050,12052,12054,12056,12058,12060,12062],{"class":70,"line":498},[68,12029,501],{"class":186},[68,12031,202],{"class":74},[68,12033,506],{"class":173},[68,12035,208],{"class":74},[68,12037,211],{"class":74},[68,12039,3368],{"class":214},[68,12041,211],{"class":74},[68,12043,190],{"class":74},[68,12045,520],{"class":186},[68,12047,202],{"class":74},[68,12049,525],{"class":173},[68,12051,208],{"class":74},[68,12053,530],{"class":339},[68,12055,533],{"class":74},[68,12057,520],{"class":186},[68,12059,202],{"class":74},[68,12061,540],{"class":173},[68,12063,543],{"class":74},[68,12065,12066,12068,12070,12072,12074,12076,12079,12081],{"class":70,"line":546},[68,12067,501],{"class":186},[68,12069,202],{"class":74},[68,12071,506],{"class":173},[68,12073,208],{"class":74},[68,12075,211],{"class":74},[68,12077,12078],{"class":214},"日本語 PDF、これだけ。",[68,12080,211],{"class":74},[68,12082,159],{"class":74},[68,12084,12085],{"class":70,"line":566},[68,12086,569],{"class":74},[68,12088,12089],{"class":70,"line":572},[68,12090,575],{"class":74},[68,12092,12093],{"class":70,"line":578},[68,12094,86],{"emptyLinePlaceholder":85},[68,12096,12097,12099,12101,12103,12105,12107,12109,12111],{"class":70,"line":583},[68,12098,586],{"class":186},[68,12100,190],{"class":74},[68,12102,193],{"class":186},[68,12104,196],{"class":74},[68,12106,416],{"class":186},[68,12108,202],{"class":74},[68,12110,599],{"class":173},[68,12112,424],{"class":74},[68,12114,12115,12117,12119,12121,12123],{"class":70,"line":604},[68,12116,224],{"class":92},[68,12118,193],{"class":186},[68,12120,229],{"class":74},[68,12122,232],{"class":74},[68,12124,180],{"class":74},[68,12126,12127,12129,12131,12133,12135,12137],{"class":70,"line":617},[68,12128,240],{"class":186},[68,12130,202],{"class":74},[68,12132,245],{"class":173},[68,12134,208],{"class":74},[68,12136,250],{"class":186},[68,12138,159],{"class":74},[68,12140,12141],{"class":70,"line":632},[68,12142,258],{"class":74},[68,12144,12145,12147,12149,12151,12153,12155,12157,12159,12161,12163,12165,12167,12169,12171,12173,12175,12177,12179,12181],{"class":70,"line":637},[68,12146,224],{"class":92},[68,12148,193],{"class":186},[68,12150,196],{"class":74},[68,12152,199],{"class":186},[68,12154,202],{"class":74},[68,12156,650],{"class":173},[68,12158,208],{"class":74},[68,12160,211],{"class":74},[68,12162,3453],{"class":214},[68,12164,211],{"class":74},[68,12166,190],{"class":74},[68,12168,664],{"class":186},[68,12170,190],{"class":74},[68,12172,669],{"class":339},[68,12174,672],{"class":74},[68,12176,193],{"class":186},[68,12178,229],{"class":74},[68,12180,232],{"class":74},[68,12182,180],{"class":74},[68,12184,12185,12187,12189,12191,12193,12195],{"class":70,"line":683},[68,12186,240],{"class":186},[68,12188,202],{"class":74},[68,12190,245],{"class":173},[68,12192,208],{"class":74},[68,12194,250],{"class":186},[68,12196,159],{"class":74},[68,12198,12199],{"class":70,"line":698},[68,12200,258],{"class":74},[68,12202,12203],{"class":70,"line":703},[68,12204,706],{"class":74},[18,12206,39,12207,3516,12210,12212,12213,12215,12216,12218],{},[22,12208,3515],{"href":3513,"rel":12209},[26],[47,12211,3008],{},"，放到 ",[47,12214,712],{}," 旁边，运行 ",[47,12217,716],{},"，就能得到一页带日文的 PDF。",[14,12220,12221],{"id":12221},"这三行背后发生了什么",[18,12223,12224],{},"两件事在后台默默完成，都不需要你插手。",[18,12226,12227,12230],{},[30,12228,12229],{},"子集化嵌入。"," Noto Sans JP 含约 17,000 个字形，Regular 单体约 5 MB。如果整字体直接嵌入，一张只印了四行日文的收据也会是 5 MB 起步。gpdf 会扫描已渲染的文本，算出用到的字形 ID，仅把这个子集写进 PDF。一张短小的发票通常只携带 20–40 KB 的字体数据，而不是 5 MB。",[18,12232,12233,12234,12236,12237,12239],{},"gofpdf 也能做子集化，但它要求 ",[47,12235,2563],{}," 接收文件路径和 UTF-8 标志，并在游标移动过程中完成加载 —— 文档中途切换字体就显得别扭。gpdf 在构造阶段一次性注册，之后每次 ",[47,12238,2986],{}," 只按字体族名引用，不再需要逐次准备。",[18,12241,12242,12245,12246,12249,12250,12252],{},[30,12243,12244],{},"不使用 CGO。"," 这一点比听起来重要。很多语言生态里的字体处理要走 FreeType 或 HarfBuzz，结果就是一个 C 依赖、构建缓存失效方式改变、Docker 镜像多出层次、从 macOS 交叉编译到 ",[47,12247,12248],{},"linux/arm64"," 也开始需要额外配置。gpdf 用纯 Go 解析 TrueType 表，",[47,12251,7046],{}," 仍然产出静态二进制。distroless 镜像里放一个 Go 二进制加一个 TTF 文件就够了。",[14,12254,12255],{"id":12255},"粗体与斜体",[18,12257,12258,12259,12262,12263,12265],{},"日文 Noto 家族每个字重都是单独的文件。要用",[30,12260,12261],{},"粗体","，把 Bold 的 TTF 以 ",[47,12264,1091],{}," 后缀单独注册：",[59,12267,12269],{"className":61,"code":12268,"language":63,"meta":64,"style":64},"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",[47,12270,12271,12297,12323,12327,12341,12363,12385,12407],{"__ignoreMap":64},[68,12272,12273,12275,12277,12279,12281,12283,12285,12287,12289,12291,12293,12295],{"class":70,"line":71},[68,12274,941],{"class":186},[68,12276,190],{"class":74},[68,12278,974],{"class":186},[68,12280,196],{"class":74},[68,12282,199],{"class":186},[68,12284,202],{"class":74},[68,12286,205],{"class":173},[68,12288,208],{"class":74},[68,12290,211],{"class":74},[68,12292,3008],{"class":214},[68,12294,211],{"class":74},[68,12296,159],{"class":74},[68,12298,12299,12301,12303,12305,12307,12309,12311,12313,12315,12317,12319,12321],{"class":70,"line":82},[68,12300,969],{"class":186},[68,12302,190],{"class":74},[68,12304,974],{"class":186},[68,12306,196],{"class":74},[68,12308,199],{"class":186},[68,12310,202],{"class":74},[68,12312,205],{"class":173},[68,12314,208],{"class":74},[68,12316,211],{"class":74},[68,12318,8813],{"class":214},[68,12320,211],{"class":74},[68,12322,159],{"class":74},[68,12324,12325],{"class":70,"line":89},[68,12326,86],{"emptyLinePlaceholder":85},[68,12328,12329,12331,12333,12335,12337,12339],{"class":70,"line":99},[68,12330,1002],{"class":186},[68,12332,196],{"class":74},[68,12334,274],{"class":186},[68,12336,202],{"class":74},[68,12338,279],{"class":173},[68,12340,282],{"class":74},[68,12342,12343,12345,12347,12349,12351,12353,12355,12357,12359,12361],{"class":70,"line":111},[68,12344,1017],{"class":186},[68,12346,202],{"class":74},[68,12348,353],{"class":173},[68,12350,208],{"class":74},[68,12352,211],{"class":74},[68,12354,3246],{"class":214},[68,12356,211],{"class":74},[68,12358,190],{"class":74},[68,12360,1034],{"class":186},[68,12362,305],{"class":74},[68,12364,12365,12367,12369,12371,12373,12375,12377,12379,12381,12383],{"class":70,"line":121},[68,12366,1017],{"class":186},[68,12368,202],{"class":74},[68,12370,353],{"class":173},[68,12372,208],{"class":74},[68,12374,211],{"class":74},[68,12376,8951],{"class":214},[68,12378,211],{"class":74},[68,12380,190],{"class":74},[68,12382,1058],{"class":186},[68,12384,305],{"class":74},[68,12386,12387,12389,12391,12393,12395,12397,12399,12401,12403,12405],{"class":70,"line":126},[68,12388,1017],{"class":186},[68,12390,202],{"class":74},[68,12392,379],{"class":173},[68,12394,208],{"class":74},[68,12396,211],{"class":74},[68,12398,3246],{"class":214},[68,12400,211],{"class":74},[68,12402,190],{"class":74},[68,12404,3275],{"class":339},[68,12406,305],{"class":74},[68,12408,12409],{"class":70,"line":136},[68,12410,159],{"class":74},[18,12412,12413,12414,12416,12417,12419,12420,12423,12424,12427],{},"然后 ",[47,12415,1095],{}," 就会选中 ",[47,12418,1091],{}," 变体。",[47,12421,12422],{},"-Italic"," 与 ",[47,12425,12426],{},"-BoldItalic"," 同理。没注册变体时会回退到合成字重 —— 屏幕上能读，但字形并不地道。正式发票请注册真实字重。",[14,12429,12430],{"id":12430},"在同一文档中混用中日韩",[18,12432,12433,12434,12437],{},"注册多少字体族都行，gpdf 彼此独立管理。用 ",[47,12435,12436],{},"template.FontFamily(...)"," 按文本切换：",[59,12439,12441],{"className":61,"code":12440,"language":63,"meta":64,"style":64},"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",[47,12442,12443,12470,12498,12526,12530,12544,12567,12591,12615,12637,12641,12645,12669,12699,12718,12722,12752,12787,12791,12821,12856,12860],{"__ignoreMap":64},[68,12444,12445,12448,12450,12452,12454,12456,12458,12460,12462,12464,12466,12468],{"class":70,"line":71},[68,12446,12447],{"class":186},"jp",[68,12449,190],{"class":74},[68,12451,974],{"class":186},[68,12453,196],{"class":74},[68,12455,199],{"class":186},[68,12457,202],{"class":74},[68,12459,205],{"class":173},[68,12461,208],{"class":74},[68,12463,211],{"class":74},[68,12465,3008],{"class":214},[68,12467,211],{"class":74},[68,12469,159],{"class":74},[68,12471,12472,12475,12477,12479,12481,12483,12485,12487,12489,12491,12494,12496],{"class":70,"line":82},[68,12473,12474],{"class":186},"sc",[68,12476,190],{"class":74},[68,12478,974],{"class":186},[68,12480,196],{"class":74},[68,12482,199],{"class":186},[68,12484,202],{"class":74},[68,12486,205],{"class":173},[68,12488,208],{"class":74},[68,12490,211],{"class":74},[68,12492,12493],{"class":214},"NotoSansSC-Regular.ttf",[68,12495,211],{"class":74},[68,12497,159],{"class":74},[68,12499,12500,12503,12505,12507,12509,12511,12513,12515,12517,12519,12522,12524],{"class":70,"line":89},[68,12501,12502],{"class":186},"kr",[68,12504,190],{"class":74},[68,12506,974],{"class":186},[68,12508,196],{"class":74},[68,12510,199],{"class":186},[68,12512,202],{"class":74},[68,12514,205],{"class":173},[68,12516,208],{"class":74},[68,12518,211],{"class":74},[68,12520,12521],{"class":214},"NotoSansKR-Regular.ttf",[68,12523,211],{"class":74},[68,12525,159],{"class":74},[68,12527,12528],{"class":70,"line":99},[68,12529,86],{"emptyLinePlaceholder":85},[68,12531,12532,12534,12536,12538,12540,12542],{"class":70,"line":111},[68,12533,1002],{"class":186},[68,12535,196],{"class":74},[68,12537,274],{"class":186},[68,12539,202],{"class":74},[68,12541,279],{"class":173},[68,12543,282],{"class":74},[68,12545,12546,12548,12550,12552,12554,12556,12558,12560,12562,12565],{"class":70,"line":121},[68,12547,1017],{"class":186},[68,12549,202],{"class":74},[68,12551,353],{"class":173},[68,12553,208],{"class":74},[68,12555,211],{"class":74},[68,12557,3246],{"class":214},[68,12559,211],{"class":74},[68,12561,190],{"class":74},[68,12563,12564],{"class":186}," jp",[68,12566,305],{"class":74},[68,12568,12569,12571,12573,12575,12577,12579,12582,12584,12586,12589],{"class":70,"line":126},[68,12570,1017],{"class":186},[68,12572,202],{"class":74},[68,12574,353],{"class":173},[68,12576,208],{"class":74},[68,12578,211],{"class":74},[68,12580,12581],{"class":214},"NotoSansSC",[68,12583,211],{"class":74},[68,12585,190],{"class":74},[68,12587,12588],{"class":186}," sc",[68,12590,305],{"class":74},[68,12592,12593,12595,12597,12599,12601,12603,12606,12608,12610,12613],{"class":70,"line":136},[68,12594,1017],{"class":186},[68,12596,202],{"class":74},[68,12598,353],{"class":173},[68,12600,208],{"class":74},[68,12602,211],{"class":74},[68,12604,12605],{"class":214},"NotoSansKR",[68,12607,211],{"class":74},[68,12609,190],{"class":74},[68,12611,12612],{"class":186}," kr",[68,12614,305],{"class":74},[68,12616,12617,12619,12621,12623,12625,12627,12629,12631,12633,12635],{"class":70,"line":146},[68,12618,1017],{"class":186},[68,12620,202],{"class":74},[68,12622,379],{"class":173},[68,12624,208],{"class":74},[68,12626,211],{"class":74},[68,12628,3246],{"class":214},[68,12630,211],{"class":74},[68,12632,190],{"class":74},[68,12634,3275],{"class":339},[68,12636,305],{"class":74},[68,12638,12639],{"class":70,"line":156},[68,12640,159],{"class":74},[68,12642,12643],{"class":70,"line":162},[68,12644,86],{"emptyLinePlaceholder":85},[68,12646,12647,12649,12651,12653,12655,12657,12659,12661,12663,12665,12667],{"class":70,"line":167},[68,12648,1924],{"class":186},[68,12650,202],{"class":74},[68,12652,435],{"class":173},[68,12654,438],{"class":74},[68,12656,442],{"class":441},[68,12658,445],{"class":74},[68,12660,448],{"class":78},[68,12662,202],{"class":74},[68,12664,453],{"class":78},[68,12666,456],{"class":74},[68,12668,180],{"class":74},[68,12670,12671,12673,12675,12677,12679,12681,12683,12685,12687,12689,12691,12693,12695,12697],{"class":70,"line":183},[68,12672,3659],{"class":186},[68,12674,202],{"class":74},[68,12676,469],{"class":173},[68,12678,208],{"class":74},[68,12680,5814],{"class":339},[68,12682,190],{"class":74},[68,12684,479],{"class":74},[68,12686,482],{"class":441},[68,12688,445],{"class":74},[68,12690,448],{"class":78},[68,12692,202],{"class":74},[68,12694,491],{"class":78},[68,12696,456],{"class":74},[68,12698,180],{"class":74},[68,12700,12701,12703,12705,12707,12709,12711,12714,12716],{"class":70,"line":221},[68,12702,3690],{"class":186},[68,12704,202],{"class":74},[68,12706,506],{"class":173},[68,12708,208],{"class":74},[68,12710,211],{"class":74},[68,12712,12713],{"class":214},"日本語",[68,12715,211],{"class":74},[68,12717,159],{"class":74},[68,12719,12720],{"class":70,"line":237},[68,12721,575],{"class":74},[68,12723,12724,12726,12728,12730,12732,12734,12736,12738,12740,12742,12744,12746,12748,12750],{"class":70,"line":255},[68,12725,3659],{"class":186},[68,12727,202],{"class":74},[68,12729,469],{"class":173},[68,12731,208],{"class":74},[68,12733,5814],{"class":339},[68,12735,190],{"class":74},[68,12737,479],{"class":74},[68,12739,482],{"class":441},[68,12741,445],{"class":74},[68,12743,448],{"class":78},[68,12745,202],{"class":74},[68,12747,491],{"class":78},[68,12749,456],{"class":74},[68,12751,180],{"class":74},[68,12753,12754,12756,12758,12760,12762,12764,12767,12769,12771,12773,12775,12777,12779,12781,12783,12785],{"class":70,"line":261},[68,12755,3690],{"class":186},[68,12757,202],{"class":74},[68,12759,506],{"class":173},[68,12761,208],{"class":74},[68,12763,211],{"class":74},[68,12765,12766],{"class":214},"中文",[68,12768,211],{"class":74},[68,12770,190],{"class":74},[68,12772,520],{"class":186},[68,12774,202],{"class":74},[68,12776,4984],{"class":173},[68,12778,208],{"class":74},[68,12780,211],{"class":74},[68,12782,12581],{"class":214},[68,12784,211],{"class":74},[68,12786,5007],{"class":74},[68,12788,12789],{"class":70,"line":266},[68,12790,575],{"class":74},[68,12792,12793,12795,12797,12799,12801,12803,12805,12807,12809,12811,12813,12815,12817,12819],{"class":70,"line":285},[68,12794,3659],{"class":186},[68,12796,202],{"class":74},[68,12798,469],{"class":173},[68,12800,208],{"class":74},[68,12802,5814],{"class":339},[68,12804,190],{"class":74},[68,12806,479],{"class":74},[68,12808,482],{"class":441},[68,12810,445],{"class":74},[68,12812,448],{"class":78},[68,12814,202],{"class":74},[68,12816,491],{"class":78},[68,12818,456],{"class":74},[68,12820,180],{"class":74},[68,12822,12823,12825,12827,12829,12831,12833,12836,12838,12840,12842,12844,12846,12848,12850,12852,12854],{"class":70,"line":308},[68,12824,3690],{"class":186},[68,12826,202],{"class":74},[68,12828,506],{"class":173},[68,12830,208],{"class":74},[68,12832,211],{"class":74},[68,12834,12835],{"class":214},"한국어",[68,12837,211],{"class":74},[68,12839,190],{"class":74},[68,12841,520],{"class":186},[68,12843,202],{"class":74},[68,12845,4984],{"class":173},[68,12847,208],{"class":74},[68,12849,211],{"class":74},[68,12851,12605],{"class":214},[68,12853,211],{"class":74},[68,12855,5007],{"class":74},[68,12857,12858],{"class":70,"line":346},[68,12859,575],{"class":74},[68,12861,12862],{"class":70,"line":372},[68,12863,3717],{"class":74},[18,12865,12866,12867,12870],{},"受 Han Unification 影响，日文与简体中文共享 Unicode 码位，但字形并不相同。",[30,12868,12869],{},"同一码位在不同字体下会渲染成不同字形"," —— 字体选择不是美学问题，而是正确性问题。给跨境业务做发票、运单时，需要同时注册两个字体族。",[14,12872,12874],{"id":12873},"常见陷阱豆腐字","常见陷阱：豆腐字",[18,12876,12877,12878,12880],{},"如果写了日文却没注册 ",[47,12879,353],{},"，gpdf 会回退到 Base-14 标准 PDF 字体 —— 它们不包含 CJK 字形，结果字符会显示成一串空矩形，俗称「豆腐字」：",[59,12882,12885],{"className":12883,"code":12884,"language":926},[924],"□□□□□、□□。\n",[47,12886,12884],{"__ignoreMap":64},[18,12888,12889,12890,12892,12893,12895,12896,12898,12899,720],{},"看到这种输出，原因只有一个：没有注册 CJK 字体，或者用的字体族不含这些字形。修复方式也只有一个：加上 ",[47,12891,353],{},"，再用 ",[47,12894,379],{}," 设为默认，或在 ",[47,12897,2986],{}," 上显式传入 ",[47,12900,1163],{},[14,12902,1209],{"id":1209},[1111,12904,12905,12914,12919],{},[885,12906,12907,12909,12910,12913],{},[22,12908,8321],{"href":2823}," —— 从 ",[47,12911,12912],{},"pdf.AddUTF8Font"," 迁移的完整映射",[885,12915,12916,12918],{},[22,12917,6551],{"href":2894}," —— gpdf 与 gofpdf、gopdf、Maroto、unipdf 在 CJK 上的对比",[885,12920,12921,3947,12924,12926],{},[22,12922,5203],{"href":5201,"rel":12923},[26],[47,12925,353],{}," 的完整参考与变体命名规则",[14,12928,1242],{"id":1241},[18,12930,1245],{},[59,12932,12933],{"className":1248,"code":1249,"language":1250,"meta":64,"style":64},[47,12934,12935],{"__ignoreMap":64},[68,12936,12937,12939,12941],{"class":70,"line":71},[68,12938,63],{"class":78},[68,12940,1259],{"class":214},[68,12942,1262],{"class":214},[18,12944,12945,1269,12948],{},[22,12946,1268],{"href":24,"rel":12947},[26],[22,12949,1274],{"href":1272,"rel":12950},[26],[1276,12952,1278],{},{"title":64,"searchDepth":82,"depth":82,"links":12954},[12955,12956,12957,12958,12959,12960,12961,12962,12963],{"id":16,"depth":82,"text":16},{"id":36,"depth":82,"text":36},{"id":57,"depth":82,"text":57},{"id":12221,"depth":82,"text":12221},{"id":12255,"depth":82,"text":12255},{"id":12430,"depth":82,"text":12430},{"id":12873,"depth":82,"text":12874},{"id":1209,"depth":82,"text":1209},{"id":1241,"depth":82,"text":1242},"将 TTF 字节传给 gpdf.WithFont 即可。自动子集化嵌入、无需 CGO，用 Go 生成日文 PDF 的最短路径。",{"name":12966,"totalTime":5247,"tools":12967,"steps":12969},"在 gpdf 文档中嵌入日文 TrueType 字体",[1299,12968],"NotoSansJP-Regular.ttf（或任意支持 CJK 的 TTF）",[12970,12973,12976,12979],{"name":12971,"text":12972},"读取 TTF 字节","程序启动时用 os.ReadFile 将 NotoSansJP-Regular.ttf 读入 []byte。若希望把字体编进二进制，也可以使用 //go:embed。",{"name":12974,"text":12975},"在构造文档时注册字体","把 gpdf.WithFont(\"NotoSansJP\", fontBytes) 传给 gpdf.NewDocument。字体族名可自由命名 —— 只要后续引用的名字一致即可。gpdf 在渲染时会自动做字形子集化。",{"name":12977,"text":12978},"设为默认字体","再加一行 gpdf.WithDefaultFont(\"NotoSansJP\", 12)，之后 c.Text 调用就会默认使用日文字体，无需每次传 FontFamily。",{"name":12980,"text":12981},"写入日文并生成 PDF","在列中调用 c.Text(\"こんにちは、世界。\")。然后 doc.Generate() 返回 []byte，再用 os.WriteFile 写盘即可。",{},{"title":1223,"description":12964},"zh/blog/003.embed-japanese-font",[1323,1324,1325],"3Ln4Ib5HbWG7IO0kOQdIug4QaGnJzDe3GaMA8MoPtKk",{"id":12988,"title":1216,"author":12989,"body":12990,"date":11660,"description":14327,"draft":1293,"extension":1294,"howTo":14328,"image":1317,"meta":14344,"navigation":85,"path":833,"seo":14345,"stem":14346,"tags":14347,"updated":1317,"__hash__":14348},"blogZh/zh/blog/004.noto-sans-jp-with-gpdf.md",{"name":8,"url":9},{"type":11,"value":12991,"toc":14314},[12992,12995,13004,13008,13021,13023,13528,13541,13545,13552,13578,13586,13604,13607,13611,13614,13681,13701,13712,13716,13719,13864,13877,13880,13949,13959,13962,13965,13968,14028,14031,14034,14038,14041,14046,14059,14068,14081,14084,14087,14244,14251,14254,14284,14286,14289,14301,14311],[14,12993,12994],{"id":12994},"把问题换个说法",[18,12996,20,12997,13000,13001,720],{},[22,12998,27],{"href":24,"rel":12999},[26]," 文档里渲染日语，字体选了 Noto Sans JP — Google 发布的 SIL OFL 免费无衬线字体，完整覆盖 JIS 字符集。你已经下好了 Google Fonts 的 zip 包。从这里开始，你想知道的三件事是：",[30,13002,13003],{},"选哪个文件、注册哪几个字重、zip 里藏着的那一个坑是什么",[14,13005,13007],{"id":13006},"结论-tldr","结论 (TL;DR)",[18,13009,13010,13011,13016,13017,13020],{},"解压 zip 之后，用 ",[30,13012,13013],{},[47,13014,13015],{},"static/NotoSansJP-Regular.ttf"," — 不是根目录下的 variable font。把它传给 ",[47,13018,13019],{},"gpdf.WithFont(\"NotoSansJP\", bytes)"," 并设为默认字体。gpdf 会从大约 17,000 个字形里只把你实际渲染过的那些子集化后嵌入 PDF — 一份普通发票最终携带的字体数据通常在 20–40 KB。",[14,13022,57],{"id":57},[59,13024,13026],{"className":61,"code":13025,"language":63,"meta":64,"style":64},"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",[47,13027,13028,13034,13038,13044,13052,13060,13064,13072,13080,13088,13092,13096,13106,13132,13144,13158,13162,13166,13180,13198,13228,13250,13272,13276,13280,13294,13318,13348,13387,13406,13410,13414,13418,13436,13448,13462,13466,13506,13520,13524],{"__ignoreMap":64},[68,13029,13030,13032],{"class":70,"line":71},[68,13031,75],{"class":74},[68,13033,79],{"class":78},[68,13035,13036],{"class":70,"line":82},[68,13037,86],{"emptyLinePlaceholder":85},[68,13039,13040,13042],{"class":70,"line":89},[68,13041,93],{"class":92},[68,13043,96],{"class":74},[68,13045,13046,13048,13050],{"class":70,"line":99},[68,13047,102],{"class":74},[68,13049,105],{"class":78},[68,13051,108],{"class":74},[68,13053,13054,13056,13058],{"class":70,"line":111},[68,13055,102],{"class":74},[68,13057,116],{"class":78},[68,13059,108],{"class":74},[68,13061,13062],{"class":70,"line":121},[68,13063,86],{"emptyLinePlaceholder":85},[68,13065,13066,13068,13070],{"class":70,"line":126},[68,13067,102],{"class":74},[68,13069,131],{"class":78},[68,13071,108],{"class":74},[68,13073,13074,13076,13078],{"class":70,"line":136},[68,13075,102],{"class":74},[68,13077,141],{"class":78},[68,13079,108],{"class":74},[68,13081,13082,13084,13086],{"class":70,"line":146},[68,13083,102],{"class":74},[68,13085,151],{"class":78},[68,13087,108],{"class":74},[68,13089,13090],{"class":70,"line":156},[68,13091,159],{"class":74},[68,13093,13094],{"class":70,"line":162},[68,13095,86],{"emptyLinePlaceholder":85},[68,13097,13098,13100,13102,13104],{"class":70,"line":167},[68,13099,170],{"class":74},[68,13101,174],{"class":173},[68,13103,177],{"class":74},[68,13105,180],{"class":74},[68,13107,13108,13110,13112,13114,13116,13118,13120,13122,13124,13126,13128,13130],{"class":70,"line":183},[68,13109,187],{"class":186},[68,13111,190],{"class":74},[68,13113,193],{"class":186},[68,13115,196],{"class":74},[68,13117,199],{"class":186},[68,13119,202],{"class":74},[68,13121,205],{"class":173},[68,13123,208],{"class":74},[68,13125,211],{"class":74},[68,13127,3008],{"class":214},[68,13129,211],{"class":74},[68,13131,159],{"class":74},[68,13133,13134,13136,13138,13140,13142],{"class":70,"line":221},[68,13135,224],{"class":92},[68,13137,193],{"class":186},[68,13139,229],{"class":74},[68,13141,232],{"class":74},[68,13143,180],{"class":74},[68,13145,13146,13148,13150,13152,13154,13156],{"class":70,"line":237},[68,13147,240],{"class":186},[68,13149,202],{"class":74},[68,13151,245],{"class":173},[68,13153,208],{"class":74},[68,13155,250],{"class":186},[68,13157,159],{"class":74},[68,13159,13160],{"class":70,"line":255},[68,13161,258],{"class":74},[68,13163,13164],{"class":70,"line":261},[68,13165,86],{"emptyLinePlaceholder":85},[68,13167,13168,13170,13172,13174,13176,13178],{"class":70,"line":266},[68,13169,269],{"class":186},[68,13171,196],{"class":74},[68,13173,274],{"class":186},[68,13175,202],{"class":74},[68,13177,279],{"class":173},[68,13179,282],{"class":74},[68,13181,13182,13184,13186,13188,13190,13192,13194,13196],{"class":70,"line":285},[68,13183,288],{"class":186},[68,13185,202],{"class":74},[68,13187,293],{"class":173},[68,13189,208],{"class":74},[68,13191,27],{"class":186},[68,13193,202],{"class":74},[68,13195,302],{"class":186},[68,13197,305],{"class":74},[68,13199,13200,13202,13204,13206,13208,13210,13212,13214,13216,13218,13220,13222,13224,13226],{"class":70,"line":308},[68,13201,288],{"class":186},[68,13203,202],{"class":74},[68,13205,315],{"class":173},[68,13207,208],{"class":74},[68,13209,320],{"class":186},[68,13211,202],{"class":74},[68,13213,325],{"class":173},[68,13215,208],{"class":74},[68,13217,320],{"class":186},[68,13219,202],{"class":74},[68,13221,334],{"class":173},[68,13223,208],{"class":74},[68,13225,340],{"class":339},[68,13227,343],{"class":74},[68,13229,13230,13232,13234,13236,13238,13240,13242,13244,13246,13248],{"class":70,"line":346},[68,13231,288],{"class":186},[68,13233,202],{"class":74},[68,13235,353],{"class":173},[68,13237,208],{"class":74},[68,13239,211],{"class":74},[68,13241,3246],{"class":214},[68,13243,211],{"class":74},[68,13245,190],{"class":74},[68,13247,367],{"class":186},[68,13249,305],{"class":74},[68,13251,13252,13254,13256,13258,13260,13262,13264,13266,13268,13270],{"class":70,"line":372},[68,13253,288],{"class":186},[68,13255,202],{"class":74},[68,13257,379],{"class":173},[68,13259,208],{"class":74},[68,13261,211],{"class":74},[68,13263,3246],{"class":214},[68,13265,211],{"class":74},[68,13267,190],{"class":74},[68,13269,392],{"class":339},[68,13271,305],{"class":74},[68,13273,13274],{"class":70,"line":397},[68,13275,400],{"class":74},[68,13277,13278],{"class":70,"line":403},[68,13279,86],{"emptyLinePlaceholder":85},[68,13281,13282,13284,13286,13288,13290,13292],{"class":70,"line":408},[68,13283,411],{"class":186},[68,13285,196],{"class":74},[68,13287,416],{"class":186},[68,13289,202],{"class":74},[68,13291,421],{"class":173},[68,13293,424],{"class":74},[68,13295,13296,13298,13300,13302,13304,13306,13308,13310,13312,13314,13316],{"class":70,"line":427},[68,13297,430],{"class":186},[68,13299,202],{"class":74},[68,13301,435],{"class":173},[68,13303,438],{"class":74},[68,13305,442],{"class":441},[68,13307,445],{"class":74},[68,13309,448],{"class":78},[68,13311,202],{"class":74},[68,13313,453],{"class":78},[68,13315,456],{"class":74},[68,13317,180],{"class":74},[68,13319,13320,13322,13324,13326,13328,13330,13332,13334,13336,13338,13340,13342,13344,13346],{"class":70,"line":461},[68,13321,464],{"class":186},[68,13323,202],{"class":74},[68,13325,469],{"class":173},[68,13327,208],{"class":74},[68,13329,474],{"class":339},[68,13331,190],{"class":74},[68,13333,479],{"class":74},[68,13335,482],{"class":441},[68,13337,445],{"class":74},[68,13339,448],{"class":78},[68,13341,202],{"class":74},[68,13343,491],{"class":78},[68,13345,456],{"class":74},[68,13347,180],{"class":74},[68,13349,13350,13352,13354,13356,13358,13360,13362,13364,13366,13368,13370,13372,13374,13377,13379,13381,13383,13385],{"class":70,"line":498},[68,13351,501],{"class":186},[68,13353,202],{"class":74},[68,13355,506],{"class":173},[68,13357,208],{"class":74},[68,13359,211],{"class":74},[68,13361,4413],{"class":214},[68,13363,211],{"class":74},[68,13365,190],{"class":74},[68,13367,520],{"class":186},[68,13369,202],{"class":74},[68,13371,525],{"class":173},[68,13373,208],{"class":74},[68,13375,13376],{"class":339},"28",[68,13378,533],{"class":74},[68,13380,520],{"class":186},[68,13382,202],{"class":74},[68,13384,540],{"class":173},[68,13386,543],{"class":74},[68,13388,13389,13391,13393,13395,13397,13399,13402,13404],{"class":70,"line":546},[68,13390,501],{"class":186},[68,13392,202],{"class":74},[68,13394,506],{"class":173},[68,13396,208],{"class":74},[68,13398,211],{"class":74},[68,13400,13401],{"class":214},"Noto Sans JP、これで十分。",[68,13403,211],{"class":74},[68,13405,159],{"class":74},[68,13407,13408],{"class":70,"line":566},[68,13409,569],{"class":74},[68,13411,13412],{"class":70,"line":572},[68,13413,575],{"class":74},[68,13415,13416],{"class":70,"line":578},[68,13417,86],{"emptyLinePlaceholder":85},[68,13419,13420,13422,13424,13426,13428,13430,13432,13434],{"class":70,"line":583},[68,13421,586],{"class":186},[68,13423,190],{"class":74},[68,13425,193],{"class":186},[68,13427,196],{"class":74},[68,13429,416],{"class":186},[68,13431,202],{"class":74},[68,13433,599],{"class":173},[68,13435,424],{"class":74},[68,13437,13438,13440,13442,13444,13446],{"class":70,"line":604},[68,13439,224],{"class":92},[68,13441,193],{"class":186},[68,13443,229],{"class":74},[68,13445,232],{"class":74},[68,13447,180],{"class":74},[68,13449,13450,13452,13454,13456,13458,13460],{"class":70,"line":617},[68,13451,240],{"class":186},[68,13453,202],{"class":74},[68,13455,245],{"class":173},[68,13457,208],{"class":74},[68,13459,250],{"class":186},[68,13461,159],{"class":74},[68,13463,13464],{"class":70,"line":632},[68,13465,258],{"class":74},[68,13467,13468,13470,13472,13474,13476,13478,13480,13482,13484,13486,13488,13490,13492,13494,13496,13498,13500,13502,13504],{"class":70,"line":637},[68,13469,224],{"class":92},[68,13471,193],{"class":186},[68,13473,196],{"class":74},[68,13475,199],{"class":186},[68,13477,202],{"class":74},[68,13479,650],{"class":173},[68,13481,208],{"class":74},[68,13483,211],{"class":74},[68,13485,4556],{"class":214},[68,13487,211],{"class":74},[68,13489,190],{"class":74},[68,13491,664],{"class":186},[68,13493,190],{"class":74},[68,13495,669],{"class":339},[68,13497,672],{"class":74},[68,13499,193],{"class":186},[68,13501,229],{"class":74},[68,13503,232],{"class":74},[68,13505,180],{"class":74},[68,13507,13508,13510,13512,13514,13516,13518],{"class":70,"line":683},[68,13509,240],{"class":186},[68,13511,202],{"class":74},[68,13513,245],{"class":173},[68,13515,208],{"class":74},[68,13517,250],{"class":186},[68,13519,159],{"class":74},[68,13521,13522],{"class":70,"line":698},[68,13523,258],{"class":74},[68,13525,13526],{"class":70,"line":703},[68,13527,706],{"class":74},[18,13529,39,13530,13533,13534,4613,13536,12215,13538,13540],{},[22,13531,3515],{"href":3513,"rel":13532},[26]," 下载 zip，解压后把 ",[47,13535,13015],{},[47,13537,712],{},[47,13539,716],{}," — 就能得到一页的 PDF。",[14,13542,13544],{"id":13543},"选-static-ttf不要选-variable-font","选 static TTF，不要选 variable font",[18,13546,13547,13548,13551],{},"在 Google Fonts 点 ",[30,13549,13550],{},"Get font → Download all","，解压 zip，里面有两组看起来差不多、实际上完全不一样的文件:",[1111,13553,13554,13564],{},[885,13555,13556,13557,9540,13560,13563],{},"根目录的 ",[47,13558,13559],{},"NotoSansJP-VariableFont_wght.ttf",[30,13561,13562],{},"variable 字体","，把 100–900 所有字重合并在一个文件里，约 7 MB",[885,13565,13566,13569,13570,13573,13574,13577],{},[47,13567,13568],{},"static/"," 目录 — 9 个独立的 TTF，从 ",[47,13571,13572],{},"NotoSansJP-Thin.ttf"," 到 ",[47,13575,13576],{},"NotoSansJP-Black.ttf","，每个约 5 MB",[18,13579,13580,720],{},[30,13581,13582,13583,13585],{},"选 ",[47,13584,13568],{}," 里的那一个",[18,13587,13588,13589,853,13591,13593,13594,866,13597,866,13600,13603],{},"gpdf 的 TrueType 解析器是刻意收窄过功能的。它处理字形轮廓、复合字形、",[47,13590,859],{},[47,13592,734],{}," — 渲染固定字重文本所需的表。但它不处理让 variable font 真正可变的 ",[47,13595,13596],{},"fvar",[47,13598,13599],{},"gvar",[47,13601,13602],{},"HVAR"," 表。如果你把 VariableFont_wght.ttf 喂给它，要么解析器直接报错，要么更糟糕 — 它拿到默认实例的字形，然后默默无视你以为自己设好的字重轴。",[18,13605,13606],{},"文件大小也支持 static 方案。variable 字体把整个字重轴上的所有 outline 都塞在一个文件里，这是设计初衷 — 但如果你只用 Regular，就等于白付了另外 8 个字重的数据。static Regular 是 5 MB，variable 是 7 MB。子集化会把两者都削掉，但 static 是更干净的输入。",[14,13608,13610],{"id":13609},"关键就是这-4-行","关键就是这 4 行",[18,13612,13613],{},"真正有意思的只有构造函数的选项:",[59,13615,13617],{"className":61,"code":13616,"language":63,"meta":64,"style":64},"doc := gpdf.NewDocument(\n    gpdf.WithFont(\"NotoSansJP\", font),\n    gpdf.WithDefaultFont(\"NotoSansJP\", 11),\n)\n",[47,13618,13619,13633,13655,13677],{"__ignoreMap":64},[68,13620,13621,13623,13625,13627,13629,13631],{"class":70,"line":71},[68,13622,1002],{"class":186},[68,13624,196],{"class":74},[68,13626,274],{"class":186},[68,13628,202],{"class":74},[68,13630,279],{"class":173},[68,13632,282],{"class":74},[68,13634,13635,13637,13639,13641,13643,13645,13647,13649,13651,13653],{"class":70,"line":82},[68,13636,1017],{"class":186},[68,13638,202],{"class":74},[68,13640,353],{"class":173},[68,13642,208],{"class":74},[68,13644,211],{"class":74},[68,13646,3246],{"class":214},[68,13648,211],{"class":74},[68,13650,190],{"class":74},[68,13652,367],{"class":186},[68,13654,305],{"class":74},[68,13656,13657,13659,13661,13663,13665,13667,13669,13671,13673,13675],{"class":70,"line":89},[68,13658,1017],{"class":186},[68,13660,202],{"class":74},[68,13662,379],{"class":173},[68,13664,208],{"class":74},[68,13666,211],{"class":74},[68,13668,3246],{"class":214},[68,13670,211],{"class":74},[68,13672,190],{"class":74},[68,13674,392],{"class":339},[68,13676,305],{"class":74},[68,13678,13679],{"class":70,"line":99},[68,13680,159],{"class":74},[18,13682,13683,13684,13687,13688,853,13691,853,13694,13697,13698,13700],{},"字体族名 (",[47,13685,13686],{},"\"NotoSansJP\"",") 是任意的。gpdf 把它当查找键用 — 不是文件路径，也不是读自字体元数据的名字。如果你的项目里 ",[47,13689,13690],{},"\"body\"",[47,13692,13693],{},"\"jp\"",[47,13695,13696],{},"\"Noto\""," 读起来更顺手就用那个。只要后面 ",[47,13699,12436],{}," 保持一致即可。",[18,13702,13703,13705,13706,13708,13709,13711],{},[47,13704,379],{}," 能让你不用在每个 ",[47,13707,2986],{}," 里写 ",[47,13710,3733],{},"。省掉它，gpdf 会回落到 Helvetica — 而 Helvetica 不覆盖任何 CJK 码位，你会得到一篇只有标题正常、正文全是豆腐 (□□□) 的 PDF。",[14,13713,13715],{"id":13714},"到底需要哪几个字重","到底需要哪几个字重？",[18,13717,13718],{},"发票、收据、业务报告只需要 Regular 和 Bold 两个:",[59,13720,13722],{"className":61,"code":13721,"language":63,"meta":64,"style":64},"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",[47,13723,13724,13750,13776,13780,13794,13816,13838,13860],{"__ignoreMap":64},[68,13725,13726,13728,13730,13732,13734,13736,13738,13740,13742,13744,13746,13748],{"class":70,"line":71},[68,13727,941],{"class":186},[68,13729,190],{"class":74},[68,13731,946],{"class":186},[68,13733,196],{"class":74},[68,13735,199],{"class":186},[68,13737,202],{"class":74},[68,13739,205],{"class":173},[68,13741,208],{"class":74},[68,13743,211],{"class":74},[68,13745,3008],{"class":214},[68,13747,211],{"class":74},[68,13749,159],{"class":74},[68,13751,13752,13754,13756,13758,13760,13762,13764,13766,13768,13770,13772,13774],{"class":70,"line":82},[68,13753,969],{"class":186},[68,13755,190],{"class":74},[68,13757,974],{"class":186},[68,13759,196],{"class":74},[68,13761,199],{"class":186},[68,13763,202],{"class":74},[68,13765,205],{"class":173},[68,13767,208],{"class":74},[68,13769,211],{"class":74},[68,13771,8813],{"class":214},[68,13773,211],{"class":74},[68,13775,159],{"class":74},[68,13777,13778],{"class":70,"line":89},[68,13779,86],{"emptyLinePlaceholder":85},[68,13781,13782,13784,13786,13788,13790,13792],{"class":70,"line":99},[68,13783,1002],{"class":186},[68,13785,196],{"class":74},[68,13787,274],{"class":186},[68,13789,202],{"class":74},[68,13791,279],{"class":173},[68,13793,282],{"class":74},[68,13795,13796,13798,13800,13802,13804,13806,13808,13810,13812,13814],{"class":70,"line":111},[68,13797,1017],{"class":186},[68,13799,202],{"class":74},[68,13801,353],{"class":173},[68,13803,208],{"class":74},[68,13805,211],{"class":74},[68,13807,3246],{"class":214},[68,13809,211],{"class":74},[68,13811,190],{"class":74},[68,13813,1034],{"class":186},[68,13815,305],{"class":74},[68,13817,13818,13820,13822,13824,13826,13828,13830,13832,13834,13836],{"class":70,"line":121},[68,13819,1017],{"class":186},[68,13821,202],{"class":74},[68,13823,353],{"class":173},[68,13825,208],{"class":74},[68,13827,211],{"class":74},[68,13829,8951],{"class":214},[68,13831,211],{"class":74},[68,13833,190],{"class":74},[68,13835,1058],{"class":186},[68,13837,305],{"class":74},[68,13839,13840,13842,13844,13846,13848,13850,13852,13854,13856,13858],{"class":70,"line":126},[68,13841,1017],{"class":186},[68,13843,202],{"class":74},[68,13845,379],{"class":173},[68,13847,208],{"class":74},[68,13849,211],{"class":74},[68,13851,3246],{"class":214},[68,13853,211],{"class":74},[68,13855,190],{"class":74},[68,13857,392],{"class":339},[68,13859,305],{"class":74},[68,13861,13862],{"class":70,"line":136},[68,13863,159],{"class":74},[18,13865,3891,13866,13868,13869,13871,13872,3746,13874,13876],{},[47,13867,1091],{}," 这个后缀注册之后，",[47,13870,1095],{}," 会自动挑到它。",[47,13873,12422],{},[47,13875,12426],{}," 规则相同。不过 Noto Sans JP 没有斜体 — CJK 字体通常不发布斜体，因为字形本身没有自然的倾斜形式。需要在日语里做强调时，用颜色、字号或粗体代替。",[18,13878,13879],{},"宣传册需要 Medium 或 SemiBold 时，用任意后缀注册，再按字体族名直接引用即可:",[59,13881,13883],{"className":61,"code":13882,"language":63,"meta":64,"style":64},"gpdf.WithFont(\"NotoSansJP-Medium\", medium)\n// ...\nc.Text(\"見出し\", template.FontFamily(\"NotoSansJP-Medium\"))\n",[47,13884,13885,13909,13914],{"__ignoreMap":64},[68,13886,13887,13889,13891,13893,13895,13897,13900,13902,13904,13907],{"class":70,"line":71},[68,13888,27],{"class":186},[68,13890,202],{"class":74},[68,13892,353],{"class":173},[68,13894,208],{"class":74},[68,13896,211],{"class":74},[68,13898,13899],{"class":214},"NotoSansJP-Medium",[68,13901,211],{"class":74},[68,13903,190],{"class":74},[68,13905,13906],{"class":186}," medium",[68,13908,159],{"class":74},[68,13910,13911],{"class":70,"line":82},[68,13912,13913],{"class":2206},"// ...\n",[68,13915,13916,13918,13920,13922,13924,13926,13929,13931,13933,13935,13937,13939,13941,13943,13945,13947],{"class":70,"line":89},[68,13917,482],{"class":186},[68,13919,202],{"class":74},[68,13921,506],{"class":173},[68,13923,208],{"class":74},[68,13925,211],{"class":74},[68,13927,13928],{"class":214},"見出し",[68,13930,211],{"class":74},[68,13932,190],{"class":74},[68,13934,520],{"class":186},[68,13936,202],{"class":74},[68,13938,4984],{"class":173},[68,13940,208],{"class":74},[68,13942,211],{"class":74},[68,13944,13899],{"class":214},[68,13946,211],{"class":74},[68,13948,5007],{"class":74},[18,13950,13951,13952,866,13954,866,13956,13958],{},"基于后缀的 Bold/Italic 快捷方式只在 ",[47,13953,1091],{},[47,13955,12422],{},[47,13957,12426],{}," 这三个字面名上生效，其他都按族名显式引用。",[14,13960,13961],{"id":13961},"子集化之后的真实体积",[18,13963,13964],{},"Noto Sans JP Regular 磁盘上约 5 MB。这个数字让一些团队去另建字体 CDN、或者给 PDF 做后处理剥离字体。用 gpdf 不需要。",[18,13966,13967],{},"下面是实际落到 PDF 里的数据量:",[740,13969,13970,13982],{},[743,13971,13972],{},[746,13973,13974,13976,13979],{},[749,13975,2884],{},[749,13977,13978],{},"使用字形数",[749,13980,13981],{},"PDF 中的字体数据",[758,13983,13984,13995,14006,14017],{},[746,13985,13986,13989,13992],{},[763,13987,13988],{},"一行收据 (约 15 字)",[763,13990,13991],{},"约 14",[763,13993,13994],{},"约 11 KB",[746,13996,13997,14000,14003],{},[763,13998,13999],{},"普通发票 (约 200 字)",[763,14001,14002],{},"约 80",[763,14004,14005],{},"约 28 KB",[746,14007,14008,14011,14014],{},[763,14009,14010],{},"10 页报告 (约 8,000 字)",[763,14012,14013],{},"约 900",[763,14015,14016],{},"约 180 KB",[746,14018,14019,14022,14025],{},[763,14020,14021],{},"字典级满字 (JIS Level 1 全)",[763,14023,14024],{},"约 6,800",[763,14026,14027],{},"约 2.1 MB",[18,14029,14030],{},"(gpdf v1.0，静态子集化开启。数字会因字形 ID 落在 CFF 和 hmtx 哪一块而上下浮动几 KB)",[18,14032,14033],{},"一份最终 50 KB 的发票 PDF，一半以上是字体数据。但比起不做子集化直接嵌入 5 MB 来说，这点开销几乎可以忽略，查看器会瞬间打开。",[14,14035,14037],{"id":14036},"noto-sans-jp-与-noto-sans-cjk-jp-不要搞混","Noto Sans JP 与 Noto Sans CJK JP — 不要搞混",[18,14039,14040],{},"Noto 家族里有两个都声称能处理日语的子族，名字相似得让人以为可以互换。实际上不是。",[18,14042,14043,14045],{},[30,14044,756],{}," 是你要用的。TTF 格式，单一语言，每个字重一个文件。就是 Google Fonts 上下载的那个。",[18,14047,14048,14051,14052,14054,14055,14058],{},[30,14049,14050],{},"Noto Sans CJK JP"," 是覆盖整个 CJK 的超级族。以 OpenType Collection (",[47,14053,1121],{},") 形式发布，把日语、简体中文、繁体中文、韩语的字形经过汉字统合 (Han unification) 后塞在一个文件里。早期 Noto 发行版和 ",[47,14056,14057],{},"notofonts.github.io/noto-cjk"," 上的都是这个。",[18,14060,14061,14062,14064,14065,14067],{},"gpdf 直接支持 TTF。TTC 是容器格式 — 你需要在传给 ",[47,14063,353],{}," 之前挑选正确的 face index，而且每个 face 里的 ",[47,14066,859],{}," 是针对特定 CJK 地区调过的，等于你在默默地替汉字统合做选择。直接选 JP 专用的 TTF 会让这些选择显式化。",[18,14069,14070,14071,14074,14075,3548,14078,14080],{},"新项目用 Noto Sans JP。如果遗留项目里已经有 ",[47,14072,14073],{},"NotoSansCJK-Regular.ttc","，用 ",[47,14076,14077],{},"pyftsubset",[47,14079,899],{}," 抽出 JP face，把结果 TTF 作为项目里的标准产物 check in。",[14,14082,14083],{"id":14083},"把字体编译进二进制",[18,14085,14086],{},"PDF 生成服务大多跑在容器里，发字体最干净的方式是编译进去:",[59,14088,14090],{"className":61,"code":14089,"language":63,"meta":64,"style":64},"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",[47,14091,14092,14098,14102,14108,14120,14124,14132,14136,14140,14145,14158,14162,14172,14186,14209,14231,14235,14240],{"__ignoreMap":64},[68,14093,14094,14096],{"class":70,"line":71},[68,14095,75],{"class":74},[68,14097,79],{"class":78},[68,14099,14100],{"class":70,"line":82},[68,14101,86],{"emptyLinePlaceholder":85},[68,14103,14104,14106],{"class":70,"line":89},[68,14105,93],{"class":92},[68,14107,96],{"class":74},[68,14109,14110,14113,14115,14118],{"class":70,"line":99},[68,14111,14112],{"class":186},"    _ ",[68,14114,211],{"class":74},[68,14116,14117],{"class":78},"embed",[68,14119,108],{"class":74},[68,14121,14122],{"class":70,"line":111},[68,14123,86],{"emptyLinePlaceholder":85},[68,14125,14126,14128,14130],{"class":70,"line":121},[68,14127,102],{"class":74},[68,14129,131],{"class":78},[68,14131,108],{"class":74},[68,14133,14134],{"class":70,"line":126},[68,14135,159],{"class":74},[68,14137,14138],{"class":70,"line":136},[68,14139,86],{"emptyLinePlaceholder":85},[68,14141,14142],{"class":70,"line":146},[68,14143,14144],{"class":2206},"//go:embed NotoSansJP-Regular.ttf\n",[68,14146,14147,14150,14153,14155],{"class":70,"line":156},[68,14148,14149],{"class":74},"var",[68,14151,14152],{"class":186}," notoJP ",[68,14154,2467],{"class":74},[68,14156,14157],{"class":1618},"byte\n",[68,14159,14160],{"class":70,"line":162},[68,14161,86],{"emptyLinePlaceholder":85},[68,14163,14164,14166,14168,14170],{"class":70,"line":167},[68,14165,170],{"class":74},[68,14167,174],{"class":173},[68,14169,177],{"class":74},[68,14171,180],{"class":74},[68,14173,14174,14176,14178,14180,14182,14184],{"class":70,"line":183},[68,14175,269],{"class":186},[68,14177,196],{"class":74},[68,14179,274],{"class":186},[68,14181,202],{"class":74},[68,14183,279],{"class":173},[68,14185,282],{"class":74},[68,14187,14188,14190,14192,14194,14196,14198,14200,14202,14204,14207],{"class":70,"line":221},[68,14189,288],{"class":186},[68,14191,202],{"class":74},[68,14193,353],{"class":173},[68,14195,208],{"class":74},[68,14197,211],{"class":74},[68,14199,3246],{"class":214},[68,14201,211],{"class":74},[68,14203,190],{"class":74},[68,14205,14206],{"class":186}," notoJP",[68,14208,305],{"class":74},[68,14210,14211,14213,14215,14217,14219,14221,14223,14225,14227,14229],{"class":70,"line":237},[68,14212,288],{"class":186},[68,14214,202],{"class":74},[68,14216,379],{"class":173},[68,14218,208],{"class":74},[68,14220,211],{"class":74},[68,14222,3246],{"class":214},[68,14224,211],{"class":74},[68,14226,190],{"class":74},[68,14228,392],{"class":339},[68,14230,305],{"class":74},[68,14232,14233],{"class":70,"line":255},[68,14234,400],{"class":74},[68,14236,14237],{"class":70,"line":261},[68,14238,14239],{"class":2206},"    // ...\n",[68,14241,14242],{"class":70,"line":266},[68,14243,706],{"class":74},[18,14245,14246,14247,14250],{},"二进制从约 8 MB 涨到约 13 MB。换来的是：Docker 镜像只有一个产物而不是两个，",[47,14248,14249],{},"COPY --from=builder /app /app"," 就够用，也不会有人因为忘记复制字体文件而上线一个坏掉的容器。每天生成几千份 PDF 的批处理任务，这是合理的默认方案。",[14,14252,14253],{"id":14253},"相关阅读",[1111,14255,14256,14262,14271,14276],{},[885,14257,14258,14261],{},[22,14259,14260],{"href":1222},"如何在 gpdf 中嵌入日语字体？"," — 适用于任何 CJK TTF 的通用方法",[885,14263,14264,14267,14268,14270],{},[22,14265,14266],{"href":2823},"gofpdf 已归档。gpdf 迁移指南"," — 从 ",[47,14269,2563],{}," 过来的迁移表",[885,14272,14273,14275],{},[22,14274,6551],{"href":2894}," — 主要库在 CJK 上的对比",[885,14277,14278,9540,14282,5206],{},[22,14279,5203],{"href":14280,"rel":14281},"https://gpdf.dev/docs/guide/fonts",[26],[47,14283,353],{},[14,14285,2859],{"id":2858},[18,14287,14288],{},"gpdf 是 Go 的 PDF 生成库。MIT 许可，零外部依赖，原生 CJK 支持。",[59,14290,14291],{"className":1248,"code":1249,"language":1250,"meta":64,"style":64},[47,14292,14293],{"__ignoreMap":64},[68,14294,14295,14297,14299],{"class":70,"line":71},[68,14296,63],{"class":78},[68,14298,1259],{"class":214},[68,14300,1262],{"class":214},[18,14302,14303,1269,14307],{},[22,14304,14306],{"href":24,"rel":14305},[26],"⭐ 在 GitHub 上加星",[22,14308,1274],{"href":14309,"rel":14310},"https://gpdf.dev/docs/quickstart",[26],[1276,14312,14313],{},"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":64,"searchDepth":82,"depth":82,"links":14315},[14316,14317,14318,14319,14320,14321,14322,14323,14324,14325,14326],{"id":12994,"depth":82,"text":12994},{"id":13006,"depth":82,"text":13007},{"id":57,"depth":82,"text":57},{"id":13543,"depth":82,"text":13544},{"id":13609,"depth":82,"text":13610},{"id":13714,"depth":82,"text":13715},{"id":13961,"depth":82,"text":13961},{"id":14036,"depth":82,"text":14037},{"id":14083,"depth":82,"text":14083},{"id":14253,"depth":82,"text":14253},{"id":2858,"depth":82,"text":2859},"用 gpdf.WithFont 注册 static 版 NotoSansJP-Regular.ttf，不要用 variable font。gpdf 会把 17,000 个字形子集化到每份 PDF 不到 40 KB。",{"name":14329,"totalTime":5247,"tools":14330,"steps":14332},"在 gpdf 文档中把 Noto Sans JP 设为默认字体",[1299,14331],"NotoSansJP-Regular.ttf (Google Fonts 的 static TTF)",[14333,14336,14339,14341],{"name":14334,"text":14335},"从 Google Fonts 下载 static TTF","在 fonts.google.com 下载 Noto Sans JP 的 zip 包，解压后选择 static/NotoSansJP-Regular.ttf。不要用根目录下的 NotoSansJP-VariableFont_wght.ttf。",{"name":14337,"text":14338},"在启动时读取字节","用 os.ReadFile 读取 NotoSansJP-Regular.ttf。如果想做成自包含的二进制，可用 //go:embed 编译进去。",{"name":12974,"text":14340},"把 gpdf.WithFont(\"NotoSansJP\", fontBytes) 和 gpdf.WithDefaultFont(\"NotoSansJP\", 11) 传给 gpdf.NewDocument。不需要 AddUTF8Font，也不需要文件路径。",{"name":14342,"text":14343},"写入日语文本并生成 PDF","在列里调用 c.Text(\"請求書\")。doc.Generate() 返回 []byte，gpdf 会把你实际用到的字形子集化后嵌入 PDF。",{},{"title":1216,"description":14327},"zh/blog/004.noto-sans-jp-with-gpdf",[1323,1324,1325],"4FD_pEfyAQq5_MH8ZwQlbflMrT7uQE-NnOc7ENXZago",{"id":14350,"title":11619,"author":14351,"body":14352,"date":19093,"description":19094,"draft":1293,"extension":1294,"howTo":19095,"image":1317,"meta":19118,"navigation":85,"path":2823,"seo":19119,"stem":19120,"tags":19121,"updated":1317,"__hash__":19122},"blogZh/zh/blog/001.gofpdf-migration.md",{"name":8,"url":9},{"type":11,"value":14353,"toc":19077},[14354,14356,14371,14381,14384,14395,14398,14402,14408,14411,14418,14425,14428,14432,14435,14461,14467,14471,14474,14740,14754,14781,14785,14788,14793,14965,14969,15350,15360,15372,15376,15386,15390,16122,16125,16129,16491,16508,16511,16515,16521,16525,16687,16700,16704,17148,17151,17162,17165,17168,17172,17191,17195,17510,17516,17520,18184,18199,18203,18215,18219,18481,18485,18804,18820,18824,18833,18910,18913,18916,18919,18922,18960,18963,18965,18971,18990,18999,19011,19020,19026,19028,19031,19043,19051,19053,19074],[14,14355,1336],{"id":1335},[18,14357,14358,14360,14361,14363,14364,14366,14367,14370],{},[30,14359,27],{}," 是一个纯 Go、零外部依赖的 PDF 生成库，原生支持 CJK（无需 ",[47,14362,2563],{}," 那套繁琐流程），用 12 栅格替代 ",[47,14365,1928],{}," 的像素级定位，在相同负载下 ",[30,14368,14369],{},"比 gofpdf 快约 10 倍","。迁移的核心是把命令式的游标调用换成声明式的构建器。本文用 5 组 Before/After 示例讲完整个映射。",[18,14372,14373,14374,14377,14378,14380],{},"上周一位同事开了个新 Go 项目，跑了 ",[47,14375,14376],{},"go get github.com/jung-kurt/gofpdf","，10 分钟后发来 GitHub 的横幅截图：",[30,14379,6673],{}," 接着一句：\"等等，那个 fork 也归档了？\"",[18,14382,14383],{},"是的，两个都归档了。",[18,14385,14386,6646,14388,14391,14392,14394],{},[47,14387,1354],{},[30,14389,14390],{},"2021 年 9 月 8 日"," 归档。社区 fork ",[47,14393,1432],{}," 最后一次发版是 2023 年，2025 年正式归档。Stack Overflow 和中文博客上约三分之二的 Go PDF 答案仍然指向 gofpdf，但它已经只读 4 年多，连接棒者也消失了。",[18,14396,14397],{},"如果你的 gofpdf 代码已经在生产，这篇是迁移地图。如果你正新开项目因为搜索结果推 gofpdf 而反手就装，这篇就是替代方案。",[14,14399,14401],{"id":14400},"为什么-gofpdf-真的复活不了","为什么 gofpdf 真的复活不了",[18,14403,14404,14405,14407],{},"开源库不一定会死。有时原维护者退出，有人接手。大家都以为 gofpdf 会这样 —— 一段时间内也的确如此。",[47,14406,1432],{}," 重新整理了代码、修了几个老 bug、接受 PR，看起来像真正的延续。",[18,14409,14410],{},"但 2025 年初，fork 也归档了。README 写着：\"该项目不再积极维护，请考虑使用其他库。\"",[18,14412,14413,14414,14417],{},"原因不重要，结果才重要：",[30,14415,14416],{},"所有依赖 gofpdf 的 Go 项目，现在都坐在两层无人维护的代码之上","。安全漏洞不会被修复。PDF 2.0 规范 2020 年发布，gofpdf 大部分都没跟进。Go 1.25 的循环变量语义现在还能和 gofpdf 配合，但明天坏了的话只能自己维护一个 fork。",[18,14419,14420,14421,14424],{},"这不是\"这个库有 bug\"的问题，是",[30,14422,14423],{},"供应链","的问题。",[18,14426,14427],{},"对中国团队尤其重要 —— 电子发票、增值税凭证、合规归档，拿一个未维护的库当技术栈基础，难以通过审计。",[14,14429,14431],{"id":14430},"中文团队用-gofpdf-都在做什么","中文团队用 gofpdf 都在做什么",[18,14433,14434],{},"翻 GitHub Issues 和中文社区的提问，gofpdf 的主要用途集中在：",[882,14436,14437,14443,14449,14455],{},[885,14438,14439,14442],{},[30,14440,14441],{},"发票、收据、送货单"," —— 页眉、客户信息、明细表、合计、页脚",[885,14444,14445,14448],{},[30,14446,14447],{},"报表"," —— 重复页眉页脚、页码、图表（以图像形式插入）的多页文档",[885,14450,14451,14454],{},[30,14452,14453],{},"证书和表单"," —— 固定位置文字叠加在模板之上",[885,14456,14457,14460],{},[30,14458,14459],{},"CJK 文档"," —— 中日韩文发票和物流标签",[18,14462,14463,14464,14466],{},"前三类 gpdf 的 builder API 都能直接覆盖。第四类 CJK 才是 gpdf 相对 gofpdf 最大的差距 —— gofpdf 要求调用 ",[47,14465,2563],{},"，管理 TTF 文件路径，并祈祷你的文本不会超出基本字符面。gpdf 从设计之初就把 CJK 当作一等公民：注册 TrueType 字体，写中文，输出 PDF。",[14,14468,14470],{"id":14469},"api-对照表","API 对照表",[18,14472,14473],{},"下表是速查表。后面章节逐个讲 5 组具体的 Before/After。",[740,14475,14476,14487],{},[743,14477,14478],{},[746,14479,14480,14483,14485],{},[749,14481,14482],{},"你想做什么",[749,14484,1429],{},[749,14486,27],{},[758,14488,14489,14504,14525,14544,14562,14577,14595,14613,14628,14643,14661,14676,14691,14710,14725],{},[746,14490,14491,14494,14499],{},[763,14492,14493],{},"创建文档",[763,14495,14496],{},[47,14497,14498],{},"gofpdf.New(\"P\", \"mm\", \"A4\", \"\")",[763,14500,14501],{},[47,14502,14503],{},"gpdf.NewDocument(gpdf.WithPageSize(document.A4))",[746,14505,14506,14509,14514],{},[763,14507,14508],{},"添加页面",[763,14510,14511],{},[47,14512,14513],{},"pdf.AddPage()",[763,14515,14516,2971,14519],{},[47,14517,14518],{},"doc.AddPage()",[6671,14520,14521,14522,456],{},"(返回 ",[47,14523,14524],{},"*PageBuilder",[746,14526,14527,14530,14535],{},[763,14528,14529],{},"设置字体",[763,14531,14532],{},[47,14533,14534],{},"pdf.SetFont(\"Arial\", \"B\", 16)",[763,14536,14537,866,14539,866,14541],{},[47,14538,12436],{},[47,14540,1095],{},[47,14542,14543],{},"template.FontSize(16)",[746,14545,14546,14549,14554],{},[763,14547,14548],{},"注册 TTF (CJK)",[763,14550,14551],{},[47,14552,14553],{},"pdf.AddUTF8Font(\"noto\", \"\", \"NotoSansSC-Regular.ttf\")",[763,14555,14556,2971,14559],{},[47,14557,14558],{},"gpdf.WithFont(\"NotoSansSC\", ttfBytes)",[6671,14560,14561],{},"(构造时传入)",[746,14563,14564,14567,14572],{},[763,14565,14566],{},"写单行文本",[763,14568,14569],{},[47,14570,14571],{},"pdf.Cell(40, 10, \"hi\")",[763,14573,14574],{},[47,14575,14576],{},"c.Text(\"hi\")",[746,14578,14579,14582,14587],{},[763,14580,14581],{},"写自动换行文本",[763,14583,14584],{},[47,14585,14586],{},"pdf.MultiCell(0, 10, body, \"\", \"L\", false)",[763,14588,14589,2971,14592],{},[47,14590,14591],{},"c.Text(body)",[6671,14593,14594],{},"(自动换行)",[746,14596,14597,14600,14605],{},[763,14598,14599],{},"设置文字颜色",[763,14601,14602],{},[47,14603,14604],{},"pdf.SetTextColor(255, 0, 0)",[763,14606,14607,2971,14610],{},[47,14608,14609],{},"template.TextColor(pdf.Red)",[6671,14611,14612],{},"(per-text 选项)",[746,14614,14615,14618,14623],{},[763,14616,14617],{},"画横线",[763,14619,14620],{},[47,14621,14622],{},"pdf.Line(x1, y1, x2, y2)",[763,14624,14625],{},[47,14626,14627],{},"c.Line(template.LineThickness(document.Pt(1)))",[746,14629,14630,14633,14638],{},[763,14631,14632],{},"嵌入图像",[763,14634,14635],{},[47,14636,14637],{},"pdf.ImageOptions(\"logo.png\", x, y, w, h, ...)",[763,14639,14640],{},[47,14641,14642],{},"c.Image(imgBytes, template.FitWidth(document.Mm(50)))",[746,14644,14645,14648,14653],{},[763,14646,14647],{},"设置光标坐标",[763,14649,14650],{},[47,14651,14652],{},"pdf.SetXY(x, y)",[763,14654,14655],{},[6671,14656,14657,14658,456],{},"(无对应 —— 用行/列，或 ",[47,14659,14660],{},"page.Absolute(x, y, fn)",[746,14662,14663,14666,14671],{},[763,14664,14665],{},"每页重复页眉",[763,14667,14668],{},[47,14669,14670],{},"pdf.SetHeaderFunc(fn)",[763,14672,14673],{},[47,14674,14675],{},"doc.Header(fn)",[746,14677,14678,14681,14686],{},[763,14679,14680],{},"每页重复页脚",[763,14682,14683],{},[47,14684,14685],{},"pdf.SetFooterFunc(fn)",[763,14687,14688],{},[47,14689,14690],{},"doc.Footer(fn)",[746,14692,14693,14696,14702],{},[763,14694,14695],{},"页码",[763,14697,14698,14701],{},[47,14699,14700],{},"pdf.PageNo()","（手动）",[763,14703,14704,866,14707],{},[47,14705,14706],{},"c.PageNumber()",[47,14708,14709],{},"c.TotalPages()",[746,14711,14712,14715,14720],{},[763,14713,14714],{},"输出到文件",[763,14716,14717],{},[47,14718,14719],{},"pdf.OutputFileAndClose(\"out.pdf\")",[763,14721,14722],{},[47,14723,14724],{},"data, _ := doc.Generate(); os.WriteFile(\"out.pdf\", data, 0o644)",[746,14726,14727,14730,14735],{},[763,14728,14729],{},"输出到 Writer",[763,14731,14732],{},[47,14733,14734],{},"pdf.Output(w)",[763,14736,14737],{},[47,14738,14739],{},"doc.Render(w)",[18,14741,14742,14743,14746,14747,14750,14751,14753],{},"最大的变化是 API 形态：gofpdf 是",[30,14744,14745],{},"命令式","，gpdf 是",[30,14748,14749],{},"声明式","。gofpdf 里你推着光标走，光标到哪写到哪。gpdf 里你描述一棵由行与列组成的树，布局引擎负责摆放。前几段代码 gpdf 写起来感觉更长。写到第三个，你就不再想念 ",[47,14752,1928],{}," 了。",[18,14755,14756,14757,866,14760,866,14763,14766,14767,853,14770,853,14773,14776,14777,14780],{},"关于单位。gofpdf 构造时选一种基准单位（",[47,14758,14759],{},"\"mm\"",[47,14761,14762],{},"\"pt\"",[47,14764,14765],{},"\"in\"","），之后都按那个来。gpdf 内部统一用 pt，调用时用 ",[47,14768,14769],{},"document.Mm(20)",[47,14771,14772],{},"document.Pt(12)",[47,14774,14775],{},"document.Cm(1)"," 等帮助函数，按场景选单位。这更像 CSS 的思路，一旦用 ",[47,14778,14779],{},"document.Mm(15)"," 定好页边距，就不再需要操心单位的事。",[14,14782,14784],{"id":14783},"before-after-1最简单的-pdf","Before / After 1：最简单的 PDF",[18,14786,14787],{},"\"hello world\" 对。gofpdf 的简洁是它流行的原因之一。gpdf 版本多几行，因为它在构建一棵树，不是驱动光标。",[18,14789,14790],{},[30,14791,14792],{},"Before — gofpdf:",[59,14794,14796],{"className":61,"code":14795,"language":63,"meta":64,"style":64},"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",[47,14797,14798,14804,14808,14819,14823,14833,14876,14886,14917,14943,14961],{"__ignoreMap":64},[68,14799,14800,14802],{"class":70,"line":71},[68,14801,75],{"class":74},[68,14803,79],{"class":78},[68,14805,14806],{"class":70,"line":82},[68,14807,86],{"emptyLinePlaceholder":85},[68,14809,14810,14812,14814,14817],{"class":70,"line":89},[68,14811,93],{"class":92},[68,14813,7418],{"class":74},[68,14815,14816],{"class":78},"github.com/jung-kurt/gofpdf",[68,14818,108],{"class":74},[68,14820,14821],{"class":70,"line":99},[68,14822,86],{"emptyLinePlaceholder":85},[68,14824,14825,14827,14829,14831],{"class":70,"line":111},[68,14826,170],{"class":74},[68,14828,174],{"class":173},[68,14830,177],{"class":74},[68,14832,180],{"class":74},[68,14834,14835,14837,14839,14842,14844,14846,14848,14850,14852,14854,14856,14858,14860,14862,14864,14866,14868,14870,14872,14874],{"class":70,"line":121},[68,14836,7394],{"class":186},[68,14838,196],{"class":74},[68,14840,14841],{"class":186}," gofpdf",[68,14843,202],{"class":74},[68,14845,7404],{"class":173},[68,14847,208],{"class":74},[68,14849,211],{"class":74},[68,14851,7411],{"class":214},[68,14853,211],{"class":74},[68,14855,190],{"class":74},[68,14857,7418],{"class":74},[68,14859,7421],{"class":214},[68,14861,211],{"class":74},[68,14863,190],{"class":74},[68,14865,7418],{"class":74},[68,14867,302],{"class":214},[68,14869,211],{"class":74},[68,14871,190],{"class":74},[68,14873,7436],{"class":74},[68,14875,159],{"class":74},[68,14877,14878,14880,14882,14884],{"class":70,"line":126},[68,14879,7443],{"class":186},[68,14881,202],{"class":74},[68,14883,421],{"class":173},[68,14885,424],{"class":74},[68,14887,14888,14890,14892,14894,14896,14898,14900,14902,14904,14906,14908,14910,14912,14915],{"class":70,"line":136},[68,14889,7443],{"class":186},[68,14891,202],{"class":74},[68,14893,1756],{"class":173},[68,14895,208],{"class":74},[68,14897,211],{"class":74},[68,14899,7464],{"class":214},[68,14901,211],{"class":74},[68,14903,190],{"class":74},[68,14905,7418],{"class":74},[68,14907,7473],{"class":214},[68,14909,211],{"class":74},[68,14911,190],{"class":74},[68,14913,14914],{"class":339}," 24",[68,14916,159],{"class":74},[68,14918,14919,14921,14923,14925,14927,14929,14931,14933,14935,14937,14939,14941],{"class":70,"line":146},[68,14920,7443],{"class":186},[68,14922,202],{"class":74},[68,14924,1932],{"class":173},[68,14926,208],{"class":74},[68,14928,7495],{"class":339},[68,14930,190],{"class":74},[68,14932,7500],{"class":339},[68,14934,190],{"class":74},[68,14936,7418],{"class":74},[68,14938,7507],{"class":214},[68,14940,211],{"class":74},[68,14942,159],{"class":74},[68,14944,14945,14947,14949,14951,14953,14955,14957,14959],{"class":70,"line":156},[68,14946,7443],{"class":186},[68,14948,202],{"class":74},[68,14950,1940],{"class":173},[68,14952,208],{"class":74},[68,14954,211],{"class":74},[68,14956,3453],{"class":214},[68,14958,211],{"class":74},[68,14960,159],{"class":74},[68,14962,14963],{"class":70,"line":162},[68,14964,706],{"class":74},[18,14966,14967],{},[30,14968,7624],{},[59,14970,14972],{"className":61,"code":14971,"language":63,"meta":64,"style":64},"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",[47,14973,14974,14980,14984,14990,14998,15006,15010,15018,15026,15034,15038,15042,15052,15066,15084,15114,15118,15122,15136,15160,15190,15228,15232,15236,15240,15258,15270,15284,15288,15328,15342,15346],{"__ignoreMap":64},[68,14975,14976,14978],{"class":70,"line":71},[68,14977,75],{"class":74},[68,14979,79],{"class":78},[68,14981,14982],{"class":70,"line":82},[68,14983,86],{"emptyLinePlaceholder":85},[68,14985,14986,14988],{"class":70,"line":89},[68,14987,93],{"class":92},[68,14989,96],{"class":74},[68,14991,14992,14994,14996],{"class":70,"line":99},[68,14993,102],{"class":74},[68,14995,105],{"class":78},[68,14997,108],{"class":74},[68,14999,15000,15002,15004],{"class":70,"line":111},[68,15001,102],{"class":74},[68,15003,116],{"class":78},[68,15005,108],{"class":74},[68,15007,15008],{"class":70,"line":121},[68,15009,86],{"emptyLinePlaceholder":85},[68,15011,15012,15014,15016],{"class":70,"line":126},[68,15013,102],{"class":74},[68,15015,131],{"class":78},[68,15017,108],{"class":74},[68,15019,15020,15022,15024],{"class":70,"line":136},[68,15021,102],{"class":74},[68,15023,141],{"class":78},[68,15025,108],{"class":74},[68,15027,15028,15030,15032],{"class":70,"line":146},[68,15029,102],{"class":74},[68,15031,151],{"class":78},[68,15033,108],{"class":74},[68,15035,15036],{"class":70,"line":156},[68,15037,159],{"class":74},[68,15039,15040],{"class":70,"line":162},[68,15041,86],{"emptyLinePlaceholder":85},[68,15043,15044,15046,15048,15050],{"class":70,"line":167},[68,15045,170],{"class":74},[68,15047,174],{"class":173},[68,15049,177],{"class":74},[68,15051,180],{"class":74},[68,15053,15054,15056,15058,15060,15062,15064],{"class":70,"line":183},[68,15055,269],{"class":186},[68,15057,196],{"class":74},[68,15059,274],{"class":186},[68,15061,202],{"class":74},[68,15063,279],{"class":173},[68,15065,282],{"class":74},[68,15067,15068,15070,15072,15074,15076,15078,15080,15082],{"class":70,"line":221},[68,15069,288],{"class":186},[68,15071,202],{"class":74},[68,15073,293],{"class":173},[68,15075,208],{"class":74},[68,15077,320],{"class":186},[68,15079,202],{"class":74},[68,15081,302],{"class":186},[68,15083,305],{"class":74},[68,15085,15086,15088,15090,15092,15094,15096,15098,15100,15102,15104,15106,15108,15110,15112],{"class":70,"line":237},[68,15087,288],{"class":186},[68,15089,202],{"class":74},[68,15091,315],{"class":173},[68,15093,208],{"class":74},[68,15095,320],{"class":186},[68,15097,202],{"class":74},[68,15099,325],{"class":173},[68,15101,208],{"class":74},[68,15103,320],{"class":186},[68,15105,202],{"class":74},[68,15107,334],{"class":173},[68,15109,208],{"class":74},[68,15111,340],{"class":339},[68,15113,343],{"class":74},[68,15115,15116],{"class":70,"line":255},[68,15117,400],{"class":74},[68,15119,15120],{"class":70,"line":261},[68,15121,86],{"emptyLinePlaceholder":85},[68,15123,15124,15126,15128,15130,15132,15134],{"class":70,"line":266},[68,15125,411],{"class":186},[68,15127,196],{"class":74},[68,15129,416],{"class":186},[68,15131,202],{"class":74},[68,15133,421],{"class":173},[68,15135,424],{"class":74},[68,15137,15138,15140,15142,15144,15146,15148,15150,15152,15154,15156,15158],{"class":70,"line":285},[68,15139,430],{"class":186},[68,15141,202],{"class":74},[68,15143,435],{"class":173},[68,15145,438],{"class":74},[68,15147,442],{"class":441},[68,15149,445],{"class":74},[68,15151,448],{"class":78},[68,15153,202],{"class":74},[68,15155,453],{"class":78},[68,15157,456],{"class":74},[68,15159,180],{"class":74},[68,15161,15162,15164,15166,15168,15170,15172,15174,15176,15178,15180,15182,15184,15186,15188],{"class":70,"line":308},[68,15163,464],{"class":186},[68,15165,202],{"class":74},[68,15167,469],{"class":173},[68,15169,208],{"class":74},[68,15171,474],{"class":339},[68,15173,190],{"class":74},[68,15175,479],{"class":74},[68,15177,482],{"class":441},[68,15179,445],{"class":74},[68,15181,448],{"class":78},[68,15183,202],{"class":74},[68,15185,491],{"class":78},[68,15187,456],{"class":74},[68,15189,180],{"class":74},[68,15191,15192,15194,15196,15198,15200,15202,15204,15206,15208,15210,15212,15214,15216,15218,15220,15222,15224,15226],{"class":70,"line":346},[68,15193,501],{"class":186},[68,15195,202],{"class":74},[68,15197,506],{"class":173},[68,15199,208],{"class":74},[68,15201,211],{"class":74},[68,15203,7507],{"class":214},[68,15205,211],{"class":74},[68,15207,190],{"class":74},[68,15209,520],{"class":186},[68,15211,202],{"class":74},[68,15213,525],{"class":173},[68,15215,208],{"class":74},[68,15217,530],{"class":339},[68,15219,533],{"class":74},[68,15221,520],{"class":186},[68,15223,202],{"class":74},[68,15225,540],{"class":173},[68,15227,543],{"class":74},[68,15229,15230],{"class":70,"line":372},[68,15231,569],{"class":74},[68,15233,15234],{"class":70,"line":397},[68,15235,575],{"class":74},[68,15237,15238],{"class":70,"line":403},[68,15239,86],{"emptyLinePlaceholder":85},[68,15241,15242,15244,15246,15248,15250,15252,15254,15256],{"class":70,"line":408},[68,15243,586],{"class":186},[68,15245,190],{"class":74},[68,15247,193],{"class":186},[68,15249,196],{"class":74},[68,15251,416],{"class":186},[68,15253,202],{"class":74},[68,15255,599],{"class":173},[68,15257,424],{"class":74},[68,15259,15260,15262,15264,15266,15268],{"class":70,"line":427},[68,15261,224],{"class":92},[68,15263,193],{"class":186},[68,15265,229],{"class":74},[68,15267,232],{"class":74},[68,15269,180],{"class":74},[68,15271,15272,15274,15276,15278,15280,15282],{"class":70,"line":461},[68,15273,240],{"class":186},[68,15275,202],{"class":74},[68,15277,245],{"class":173},[68,15279,208],{"class":74},[68,15281,250],{"class":186},[68,15283,159],{"class":74},[68,15285,15286],{"class":70,"line":498},[68,15287,258],{"class":74},[68,15289,15290,15292,15294,15296,15298,15300,15302,15304,15306,15308,15310,15312,15314,15316,15318,15320,15322,15324,15326],{"class":70,"line":546},[68,15291,224],{"class":92},[68,15293,193],{"class":186},[68,15295,196],{"class":74},[68,15297,199],{"class":186},[68,15299,202],{"class":74},[68,15301,650],{"class":173},[68,15303,208],{"class":74},[68,15305,211],{"class":74},[68,15307,3453],{"class":214},[68,15309,211],{"class":74},[68,15311,190],{"class":74},[68,15313,664],{"class":186},[68,15315,190],{"class":74},[68,15317,669],{"class":339},[68,15319,672],{"class":74},[68,15321,193],{"class":186},[68,15323,229],{"class":74},[68,15325,232],{"class":74},[68,15327,180],{"class":74},[68,15329,15330,15332,15334,15336,15338,15340],{"class":70,"line":566},[68,15331,240],{"class":186},[68,15333,202],{"class":74},[68,15335,245],{"class":173},[68,15337,208],{"class":74},[68,15339,250],{"class":186},[68,15341,159],{"class":74},[68,15343,15344],{"class":70,"line":572},[68,15345,258],{"class":74},[68,15347,15348],{"class":70,"line":578},[68,15349,706],{"class":74},[18,15351,15352,15353,15355,15356,15359],{},"栅格替你干活。",[47,15354,435],{}," 添加一个高度由内容决定的行，",[47,15357,15358],{},"r.Col(12, ...)"," 表示\"这列占满 12 栅格\"。就是 Bootstrap 思路搬到 PDF 页面上。",[18,15361,15362,15364,15365,15368,15369,15371],{},[47,15363,3928],{}," 返回字节切片；",[47,15366,15367],{},"Render(w)"," 流式写入 ",[47,15370,1893],{},"，避免分配。没有\"关闭文件\"一步，因为 gpdf 不持有文件句柄。",[14,15373,15375],{"id":15374},"before-after-2发票明细表","Before / After 2：发票明细表",[18,15377,15378,15379,15381,15382,15385],{},"表格是 gofpdf 最啰嗦的地方。它没有内建表格，你得在嵌套循环里调 ",[47,15380,1932],{},"，自己算列宽，用 ",[47,15383,15384],{},"Ln(-1)"," 换行。网上一半的 gofpdf 发票教程，代码量都耗在了表格样板上。",[18,15387,15388],{},[30,15389,14792],{},[59,15391,15393],{"className":61,"code":15392,"language":63,"meta":64,"style":64},"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\", \"¥1,500\", \"¥60,000\"},\n    {\"后端开发\", \"60h\", \"¥1,500\", \"¥90,000\"},\n    {\"UI 设计\", \"20h\", \"¥1,200\", \"¥24,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",[47,15394,15395,15425,15450,15516,15575,15634,15693,15697,15723,15737,15777,15815,15854,15858,15881,15942,16000,16059,16118],{"__ignoreMap":64},[68,15396,15397,15399,15401,15403,15405,15407,15409,15411,15413,15415,15417,15419,15421,15423],{"class":70,"line":71},[68,15398,2273],{"class":186},[68,15400,202],{"class":74},[68,15402,1756],{"class":173},[68,15404,208],{"class":74},[68,15406,211],{"class":74},[68,15408,7464],{"class":214},[68,15410,211],{"class":74},[68,15412,190],{"class":74},[68,15414,7418],{"class":74},[68,15416,7473],{"class":214},[68,15418,211],{"class":74},[68,15420,190],{"class":74},[68,15422,392],{"class":339},[68,15424,159],{"class":74},[68,15426,15427,15429,15431,15434,15436,15439,15441,15444,15446,15448],{"class":70,"line":82},[68,15428,2273],{"class":186},[68,15430,202],{"class":74},[68,15432,15433],{"class":173},"SetFillColor",[68,15435,208],{"class":74},[68,15437,15438],{"class":339},"220",[68,15440,190],{"class":74},[68,15442,15443],{"class":339}," 220",[68,15445,190],{"class":74},[68,15447,15443],{"class":339},[68,15449,159],{"class":74},[68,15451,15452,15454,15456,15458,15460,15463,15465,15468,15470,15472,15475,15477,15479,15481,15484,15486,15488,15491,15493,15495,15498,15500,15502,15506,15508,15510,15512,15514],{"class":70,"line":89},[68,15453,2273],{"class":186},[68,15455,202],{"class":74},[68,15457,2004],{"class":173},[68,15459,208],{"class":74},[68,15461,15462],{"class":339},"80",[68,15464,190],{"class":74},[68,15466,15467],{"class":339}," 8",[68,15469,190],{"class":74},[68,15471,7418],{"class":74},[68,15473,15474],{"class":214},"品名",[68,15476,211],{"class":74},[68,15478,190],{"class":74},[68,15480,11250],{"class":74},[68,15482,15483],{"class":214},"1",[68,15485,211],{"class":74},[68,15487,190],{"class":74},[68,15489,15490],{"class":339}," 0",[68,15492,190],{"class":74},[68,15494,7418],{"class":74},[68,15496,15497],{"class":214},"L",[68,15499,211],{"class":74},[68,15501,190],{"class":74},[68,15503,15505],{"class":15504},"sfNiH"," true",[68,15507,190],{"class":74},[68,15509,15490],{"class":339},[68,15511,190],{"class":74},[68,15513,7436],{"class":74},[68,15515,159],{"class":74},[68,15517,15518,15520,15522,15524,15526,15528,15530,15532,15534,15536,15538,15540,15542,15544,15546,15548,15550,15552,15554,15556,15559,15561,15563,15565,15567,15569,15571,15573],{"class":70,"line":99},[68,15519,2273],{"class":186},[68,15521,202],{"class":74},[68,15523,2004],{"class":173},[68,15525,208],{"class":74},[68,15527,340],{"class":339},[68,15529,190],{"class":74},[68,15531,15467],{"class":339},[68,15533,190],{"class":74},[68,15535,7418],{"class":74},[68,15537,11103],{"class":214},[68,15539,211],{"class":74},[68,15541,190],{"class":74},[68,15543,11250],{"class":74},[68,15545,15483],{"class":214},[68,15547,211],{"class":74},[68,15549,190],{"class":74},[68,15551,15490],{"class":339},[68,15553,190],{"class":74},[68,15555,7418],{"class":74},[68,15557,15558],{"class":214},"C",[68,15560,211],{"class":74},[68,15562,190],{"class":74},[68,15564,15505],{"class":15504},[68,15566,190],{"class":74},[68,15568,15490],{"class":339},[68,15570,190],{"class":74},[68,15572,7436],{"class":74},[68,15574,159],{"class":74},[68,15576,15577,15579,15581,15583,15585,15587,15589,15591,15593,15595,15597,15599,15601,15603,15605,15607,15609,15611,15613,15615,15618,15620,15622,15624,15626,15628,15630,15632],{"class":70,"line":111},[68,15578,2273],{"class":186},[68,15580,202],{"class":74},[68,15582,2004],{"class":173},[68,15584,208],{"class":74},[68,15586,6381],{"class":339},[68,15588,190],{"class":74},[68,15590,15467],{"class":339},[68,15592,190],{"class":74},[68,15594,7418],{"class":74},[68,15596,11112],{"class":214},[68,15598,211],{"class":74},[68,15600,190],{"class":74},[68,15602,11250],{"class":74},[68,15604,15483],{"class":214},[68,15606,211],{"class":74},[68,15608,190],{"class":74},[68,15610,15490],{"class":339},[68,15612,190],{"class":74},[68,15614,7418],{"class":74},[68,15616,15617],{"class":214},"R",[68,15619,211],{"class":74},[68,15621,190],{"class":74},[68,15623,15505],{"class":15504},[68,15625,190],{"class":74},[68,15627,15490],{"class":339},[68,15629,190],{"class":74},[68,15631,7436],{"class":74},[68,15633,159],{"class":74},[68,15635,15636,15638,15640,15642,15644,15646,15648,15650,15652,15654,15656,15658,15660,15662,15664,15666,15668,15671,15673,15675,15677,15679,15681,15683,15685,15687,15689,15691],{"class":70,"line":121},[68,15637,2273],{"class":186},[68,15639,202],{"class":74},[68,15641,2004],{"class":173},[68,15643,208],{"class":74},[68,15645,6381],{"class":339},[68,15647,190],{"class":74},[68,15649,15467],{"class":339},[68,15651,190],{"class":74},[68,15653,7418],{"class":74},[68,15655,11121],{"class":214},[68,15657,211],{"class":74},[68,15659,190],{"class":74},[68,15661,11250],{"class":74},[68,15663,15483],{"class":214},[68,15665,211],{"class":74},[68,15667,190],{"class":74},[68,15669,15670],{"class":339}," 1",[68,15672,190],{"class":74},[68,15674,7418],{"class":74},[68,15676,15617],{"class":214},[68,15678,211],{"class":74},[68,15680,190],{"class":74},[68,15682,15505],{"class":15504},[68,15684,190],{"class":74},[68,15686,15490],{"class":339},[68,15688,190],{"class":74},[68,15690,7436],{"class":74},[68,15692,159],{"class":74},[68,15694,15695],{"class":70,"line":126},[68,15696,86],{"emptyLinePlaceholder":85},[68,15698,15699,15701,15703,15705,15707,15709,15711,15713,15715,15717,15719,15721],{"class":70,"line":136},[68,15700,2273],{"class":186},[68,15702,202],{"class":74},[68,15704,1756],{"class":173},[68,15706,208],{"class":74},[68,15708,211],{"class":74},[68,15710,7464],{"class":214},[68,15712,211],{"class":74},[68,15714,190],{"class":74},[68,15716,7436],{"class":74},[68,15718,190],{"class":74},[68,15720,392],{"class":339},[68,15722,159],{"class":74},[68,15724,15725,15728,15730,15733,15735],{"class":70,"line":146},[68,15726,15727],{"class":186},"items ",[68,15729,196],{"class":74},[68,15731,15732],{"class":74}," [][]",[68,15734,11086],{"class":1618},[68,15736,11136],{"class":74},[68,15738,15739,15742,15744,15746,15748,15750,15752,15755,15757,15759,15761,15764,15766,15768,15770,15773,15775],{"class":70,"line":156},[68,15740,15741],{"class":74},"    {",[68,15743,211],{"class":74},[68,15745,11146],{"class":214},[68,15747,211],{"class":74},[68,15749,190],{"class":74},[68,15751,7418],{"class":74},[68,15753,15754],{"class":214},"40h",[68,15756,211],{"class":74},[68,15758,190],{"class":74},[68,15760,7418],{"class":74},[68,15762,15763],{"class":214},"¥1,500",[68,15765,211],{"class":74},[68,15767,190],{"class":74},[68,15769,7418],{"class":74},[68,15771,15772],{"class":214},"¥60,000",[68,15774,211],{"class":74},[68,15776,11126],{"class":74},[68,15778,15779,15781,15783,15785,15787,15789,15791,15794,15796,15798,15800,15802,15804,15806,15808,15811,15813],{"class":70,"line":162},[68,15780,15741],{"class":74},[68,15782,211],{"class":74},[68,15784,11186],{"class":214},[68,15786,211],{"class":74},[68,15788,190],{"class":74},[68,15790,7418],{"class":74},[68,15792,15793],{"class":214},"60h",[68,15795,211],{"class":74},[68,15797,190],{"class":74},[68,15799,7418],{"class":74},[68,15801,15763],{"class":214},[68,15803,211],{"class":74},[68,15805,190],{"class":74},[68,15807,7418],{"class":74},[68,15809,15810],{"class":214},"¥90,000",[68,15812,211],{"class":74},[68,15814,11126],{"class":74},[68,15816,15817,15819,15821,15823,15825,15827,15829,15832,15834,15836,15838,15841,15843,15845,15847,15850,15852],{"class":70,"line":167},[68,15818,15741],{"class":74},[68,15820,211],{"class":74},[68,15822,11225],{"class":214},[68,15824,211],{"class":74},[68,15826,190],{"class":74},[68,15828,7418],{"class":74},[68,15830,15831],{"class":214},"20h",[68,15833,211],{"class":74},[68,15835,190],{"class":74},[68,15837,7418],{"class":74},[68,15839,15840],{"class":214},"¥1,200",[68,15842,211],{"class":74},[68,15844,190],{"class":74},[68,15846,7418],{"class":74},[68,15848,15849],{"class":214},"¥24,000",[68,15851,211],{"class":74},[68,15853,11126],{"class":74},[68,15855,15856],{"class":70,"line":183},[68,15857,706],{"class":74},[68,15859,15860,15863,15866,15868,15871,15873,15876,15879],{"class":70,"line":221},[68,15861,15862],{"class":92},"for",[68,15864,15865],{"class":186}," _",[68,15867,190],{"class":74},[68,15869,15870],{"class":186}," row ",[68,15872,196],{"class":74},[68,15874,15875],{"class":92}," range",[68,15877,15878],{"class":186}," items ",[68,15880,11136],{"class":74},[68,15882,15883,15885,15887,15889,15891,15893,15895,15897,15899,15902,15904,15906,15909,15911,15913,15915,15917,15919,15921,15923,15925,15927,15929,15932,15934,15936,15938,15940],{"class":70,"line":237},[68,15884,7443],{"class":186},[68,15886,202],{"class":74},[68,15888,2004],{"class":173},[68,15890,208],{"class":74},[68,15892,15462],{"class":339},[68,15894,190],{"class":74},[68,15896,15467],{"class":339},[68,15898,190],{"class":74},[68,15900,15901],{"class":186}," row",[68,15903,2182],{"class":74},[68,15905,10203],{"class":339},[68,15907,15908],{"class":74},"],",[68,15910,7418],{"class":74},[68,15912,15483],{"class":214},[68,15914,211],{"class":74},[68,15916,190],{"class":74},[68,15918,15490],{"class":339},[68,15920,190],{"class":74},[68,15922,7418],{"class":74},[68,15924,15497],{"class":214},[68,15926,211],{"class":74},[68,15928,190],{"class":74},[68,15930,15931],{"class":15504}," false",[68,15933,190],{"class":74},[68,15935,15490],{"class":339},[68,15937,190],{"class":74},[68,15939,7436],{"class":74},[68,15941,159],{"class":74},[68,15943,15944,15946,15948,15950,15952,15954,15956,15958,15960,15962,15964,15966,15968,15970,15972,15974,15976,15978,15980,15982,15984,15986,15988,15990,15992,15994,15996,15998],{"class":70,"line":255},[68,15945,7443],{"class":186},[68,15947,202],{"class":74},[68,15949,2004],{"class":173},[68,15951,208],{"class":74},[68,15953,340],{"class":339},[68,15955,190],{"class":74},[68,15957,15467],{"class":339},[68,15959,190],{"class":74},[68,15961,15901],{"class":186},[68,15963,2182],{"class":74},[68,15965,15483],{"class":339},[68,15967,15908],{"class":74},[68,15969,7418],{"class":74},[68,15971,15483],{"class":214},[68,15973,211],{"class":74},[68,15975,190],{"class":74},[68,15977,15490],{"class":339},[68,15979,190],{"class":74},[68,15981,7418],{"class":74},[68,15983,15558],{"class":214},[68,15985,211],{"class":74},[68,15987,190],{"class":74},[68,15989,15931],{"class":15504},[68,15991,190],{"class":74},[68,15993,15490],{"class":339},[68,15995,190],{"class":74},[68,15997,7436],{"class":74},[68,15999,159],{"class":74},[68,16001,16002,16004,16006,16008,16010,16012,16014,16016,16018,16020,16022,16025,16027,16029,16031,16033,16035,16037,16039,16041,16043,16045,16047,16049,16051,16053,16055,16057],{"class":70,"line":261},[68,16003,7443],{"class":186},[68,16005,202],{"class":74},[68,16007,2004],{"class":173},[68,16009,208],{"class":74},[68,16011,6381],{"class":339},[68,16013,190],{"class":74},[68,16015,15467],{"class":339},[68,16017,190],{"class":74},[68,16019,15901],{"class":186},[68,16021,2182],{"class":74},[68,16023,16024],{"class":339},"2",[68,16026,15908],{"class":74},[68,16028,7418],{"class":74},[68,16030,15483],{"class":214},[68,16032,211],{"class":74},[68,16034,190],{"class":74},[68,16036,15490],{"class":339},[68,16038,190],{"class":74},[68,16040,7418],{"class":74},[68,16042,15617],{"class":214},[68,16044,211],{"class":74},[68,16046,190],{"class":74},[68,16048,15931],{"class":15504},[68,16050,190],{"class":74},[68,16052,15490],{"class":339},[68,16054,190],{"class":74},[68,16056,7436],{"class":74},[68,16058,159],{"class":74},[68,16060,16061,16063,16065,16067,16069,16071,16073,16075,16077,16079,16081,16084,16086,16088,16090,16092,16094,16096,16098,16100,16102,16104,16106,16108,16110,16112,16114,16116],{"class":70,"line":266},[68,16062,7443],{"class":186},[68,16064,202],{"class":74},[68,16066,2004],{"class":173},[68,16068,208],{"class":74},[68,16070,6381],{"class":339},[68,16072,190],{"class":74},[68,16074,15467],{"class":339},[68,16076,190],{"class":74},[68,16078,15901],{"class":186},[68,16080,2182],{"class":74},[68,16082,16083],{"class":339},"3",[68,16085,15908],{"class":74},[68,16087,7418],{"class":74},[68,16089,15483],{"class":214},[68,16091,211],{"class":74},[68,16093,190],{"class":74},[68,16095,15670],{"class":339},[68,16097,190],{"class":74},[68,16099,7418],{"class":74},[68,16101,15617],{"class":214},[68,16103,211],{"class":74},[68,16105,190],{"class":74},[68,16107,15931],{"class":15504},[68,16109,190],{"class":74},[68,16111,15490],{"class":339},[68,16113,190],{"class":74},[68,16115,7436],{"class":74},[68,16117,159],{"class":74},[68,16119,16120],{"class":70,"line":285},[68,16121,706],{"class":74},[18,16123,16124],{},"列宽自己心算，品名换行则崩。",[18,16126,16127],{},[30,16128,7624],{},[59,16130,16132],{"className":61,"code":16131,"language":63,"meta":64,"style":64},"page.AutoRow(func(r *template.RowBuilder) {\n    r.Col(12, func(c *template.ColBuilder) {\n        c.Table(\n            []string{\"品名\", \"数量\", \"单价\", \"金额\"},\n            [][]string{\n                {\"前端开发\", \"40h\", \"¥1,500\", \"¥60,000\"},\n                {\"后端开发\", \"60h\", \"¥1,500\", \"¥90,000\"},\n                {\"UI 设计\", \"20h\", \"¥1,200\", \"¥24,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",[47,16133,16134,16158,16188,16198,16239,16248,16285,16321,16357,16362,16389,16399,16409,16427,16449,16454,16478,16483,16487],{"__ignoreMap":64},[68,16135,16136,16138,16140,16142,16144,16146,16148,16150,16152,16154,16156],{"class":70,"line":71},[68,16137,1924],{"class":186},[68,16139,202],{"class":74},[68,16141,435],{"class":173},[68,16143,438],{"class":74},[68,16145,442],{"class":441},[68,16147,445],{"class":74},[68,16149,448],{"class":78},[68,16151,202],{"class":74},[68,16153,453],{"class":78},[68,16155,456],{"class":74},[68,16157,180],{"class":74},[68,16159,16160,16162,16164,16166,16168,16170,16172,16174,16176,16178,16180,16182,16184,16186],{"class":70,"line":82},[68,16161,3659],{"class":186},[68,16163,202],{"class":74},[68,16165,469],{"class":173},[68,16167,208],{"class":74},[68,16169,474],{"class":339},[68,16171,190],{"class":74},[68,16173,479],{"class":74},[68,16175,482],{"class":441},[68,16177,445],{"class":74},[68,16179,448],{"class":78},[68,16181,202],{"class":74},[68,16183,491],{"class":78},[68,16185,456],{"class":74},[68,16187,180],{"class":74},[68,16189,16190,16192,16194,16196],{"class":70,"line":89},[68,16191,3690],{"class":186},[68,16193,202],{"class":74},[68,16195,11076],{"class":173},[68,16197,282],{"class":74},[68,16199,16200,16203,16205,16207,16209,16211,16213,16215,16217,16219,16221,16223,16225,16227,16229,16231,16233,16235,16237],{"class":70,"line":99},[68,16201,16202],{"class":74},"            []",[68,16204,11086],{"class":1618},[68,16206,11089],{"class":74},[68,16208,211],{"class":74},[68,16210,15474],{"class":214},[68,16212,211],{"class":74},[68,16214,190],{"class":74},[68,16216,7418],{"class":74},[68,16218,11103],{"class":214},[68,16220,211],{"class":74},[68,16222,190],{"class":74},[68,16224,7418],{"class":74},[68,16226,11112],{"class":214},[68,16228,211],{"class":74},[68,16230,190],{"class":74},[68,16232,7418],{"class":74},[68,16234,11121],{"class":214},[68,16236,211],{"class":74},[68,16238,11126],{"class":74},[68,16240,16241,16244,16246],{"class":70,"line":111},[68,16242,16243],{"class":74},"            [][]",[68,16245,11086],{"class":1618},[68,16247,11136],{"class":74},[68,16249,16250,16253,16255,16257,16259,16261,16263,16265,16267,16269,16271,16273,16275,16277,16279,16281,16283],{"class":70,"line":121},[68,16251,16252],{"class":74},"                {",[68,16254,211],{"class":74},[68,16256,11146],{"class":214},[68,16258,211],{"class":74},[68,16260,190],{"class":74},[68,16262,7418],{"class":74},[68,16264,15754],{"class":214},[68,16266,211],{"class":74},[68,16268,190],{"class":74},[68,16270,7418],{"class":74},[68,16272,15763],{"class":214},[68,16274,211],{"class":74},[68,16276,190],{"class":74},[68,16278,7418],{"class":74},[68,16280,15772],{"class":214},[68,16282,211],{"class":74},[68,16284,11126],{"class":74},[68,16286,16287,16289,16291,16293,16295,16297,16299,16301,16303,16305,16307,16309,16311,16313,16315,16317,16319],{"class":70,"line":126},[68,16288,16252],{"class":74},[68,16290,211],{"class":74},[68,16292,11186],{"class":214},[68,16294,211],{"class":74},[68,16296,190],{"class":74},[68,16298,7418],{"class":74},[68,16300,15793],{"class":214},[68,16302,211],{"class":74},[68,16304,190],{"class":74},[68,16306,7418],{"class":74},[68,16308,15763],{"class":214},[68,16310,211],{"class":74},[68,16312,190],{"class":74},[68,16314,7418],{"class":74},[68,16316,15810],{"class":214},[68,16318,211],{"class":74},[68,16320,11126],{"class":74},[68,16322,16323,16325,16327,16329,16331,16333,16335,16337,16339,16341,16343,16345,16347,16349,16351,16353,16355],{"class":70,"line":136},[68,16324,16252],{"class":74},[68,16326,211],{"class":74},[68,16328,11225],{"class":214},[68,16330,211],{"class":74},[68,16332,190],{"class":74},[68,16334,7418],{"class":74},[68,16336,15831],{"class":214},[68,16338,211],{"class":74},[68,16340,190],{"class":74},[68,16342,7418],{"class":74},[68,16344,15840],{"class":214},[68,16346,211],{"class":74},[68,16348,190],{"class":74},[68,16350,7418],{"class":74},[68,16352,15849],{"class":214},[68,16354,211],{"class":74},[68,16356,11126],{"class":74},[68,16358,16359],{"class":70,"line":146},[68,16360,16361],{"class":74},"            },\n",[68,16363,16364,16367,16369,16371,16373,16375,16377,16379,16381,16383,16385,16387],{"class":70,"line":156},[68,16365,16366],{"class":186},"            template",[68,16368,202],{"class":74},[68,16370,11272],{"class":173},[68,16372,208],{"class":74},[68,16374,11277],{"class":339},[68,16376,190],{"class":74},[68,16378,11282],{"class":339},[68,16380,190],{"class":74},[68,16382,11282],{"class":339},[68,16384,190],{"class":74},[68,16386,11291],{"class":339},[68,16388,305],{"class":74},[68,16390,16391,16393,16395,16397],{"class":70,"line":162},[68,16392,16366],{"class":186},[68,16394,202],{"class":74},[68,16396,11302],{"class":173},[68,16398,282],{"class":74},[68,16400,16401,16403,16405,16407],{"class":70,"line":167},[68,16402,11267],{"class":186},[68,16404,202],{"class":74},[68,16406,540],{"class":173},[68,16408,11316],{"class":74},[68,16410,16411,16413,16415,16417,16419,16421,16423,16425],{"class":70,"line":183},[68,16412,11267],{"class":186},[68,16414,202],{"class":74},[68,16416,11325],{"class":173},[68,16418,208],{"class":74},[68,16420,2273],{"class":186},[68,16422,202],{"class":74},[68,16424,11334],{"class":186},[68,16426,305],{"class":74},[68,16428,16429,16431,16433,16435,16437,16439,16441,16443,16445,16447],{"class":70,"line":221},[68,16430,11267],{"class":186},[68,16432,202],{"class":74},[68,16434,11345],{"class":173},[68,16436,208],{"class":74},[68,16438,2273],{"class":186},[68,16440,202],{"class":74},[68,16442,11354],{"class":173},[68,16444,208],{"class":74},[68,16446,11359],{"class":339},[68,16448,11362],{"class":74},[68,16450,16451],{"class":70,"line":237},[68,16452,16453],{"class":74},"            ),\n",[68,16455,16456,16458,16460,16463,16465,16467,16469,16471,16473,16476],{"class":70,"line":255},[68,16457,16366],{"class":186},[68,16459,202],{"class":74},[68,16461,16462],{"class":173},"TableStripe",[68,16464,208],{"class":74},[68,16466,2273],{"class":186},[68,16468,202],{"class":74},[68,16470,11354],{"class":173},[68,16472,208],{"class":74},[68,16474,16475],{"class":339},"0xF5F5F5",[68,16477,11362],{"class":74},[68,16479,16480],{"class":70,"line":261},[68,16481,16482],{"class":74},"        )\n",[68,16484,16485],{"class":70,"line":266},[68,16486,575],{"class":74},[68,16488,16489],{"class":70,"line":285},[68,16490,3717],{"class":74},[18,16492,16493,16496,16497,16500,16501,16504,16505,16507],{},[47,16494,16495],{},"ColumnWidths(50, 15, 15, 20)"," 里的数字是",[30,16498,16499],{},"所在列宽的百分比","，不是绝对毫米。把这张表放进 ",[47,16502,16503],{},"r.Col(6, ...)","，同一组百分比依然成立 —— 这是 ",[47,16506,2004],{}," 必须包一层才能达到的抽象。",[18,16509,16510],{},"自动换行。自动分页 —— 表格超出下边距时，下一页自动重绘表头。",[14,16512,16514],{"id":16513},"before-after-3不用再跳那套-cjk-舞步","Before / After 3：不用再跳那套 CJK 舞步",[18,16516,16517,16518,16520],{},"这是我决定弃用 gofpdf 的那一点。在 gofpdf 里要渲染中文，你得调 ",[47,16519,2563],{},"，指一个磁盘 TTF 路径，设置字体，然后祈祷。子集化大部分时候能用。有些 TTF 会触发 glyph-id 冲突并吐出乱码。错误信息帮不上忙。",[18,16522,16523],{},[30,16524,14792],{},[59,16526,16528],{"className":61,"code":16527,"language":63,"meta":64,"style":64},"pdf := gofpdf.New(\"P\", \"mm\", \"A4\", \"\")\npdf.AddUTF8Font(\"notosanssc\", \"\", \"NotoSansSC-Regular.ttf\")\npdf.AddPage()\npdf.SetFont(\"notosanssc\", \"\", 14)\npdf.Cell(0, 10, \"你好，世界。\")\npdf.OutputFileAndClose(\"zh.pdf\")\n",[47,16529,16530,16573,16604,16614,16641,16668],{"__ignoreMap":64},[68,16531,16532,16535,16537,16539,16541,16543,16545,16547,16549,16551,16553,16555,16557,16559,16561,16563,16565,16567,16569,16571],{"class":70,"line":71},[68,16533,16534],{"class":186},"pdf ",[68,16536,196],{"class":74},[68,16538,14841],{"class":186},[68,16540,202],{"class":74},[68,16542,7404],{"class":173},[68,16544,208],{"class":74},[68,16546,211],{"class":74},[68,16548,7411],{"class":214},[68,16550,211],{"class":74},[68,16552,190],{"class":74},[68,16554,7418],{"class":74},[68,16556,7421],{"class":214},[68,16558,211],{"class":74},[68,16560,190],{"class":74},[68,16562,7418],{"class":74},[68,16564,302],{"class":214},[68,16566,211],{"class":74},[68,16568,190],{"class":74},[68,16570,7436],{"class":74},[68,16572,159],{"class":74},[68,16574,16575,16577,16579,16581,16583,16585,16588,16590,16592,16594,16596,16598,16600,16602],{"class":70,"line":82},[68,16576,2273],{"class":186},[68,16578,202],{"class":74},[68,16580,2563],{"class":173},[68,16582,208],{"class":74},[68,16584,211],{"class":74},[68,16586,16587],{"class":214},"notosanssc",[68,16589,211],{"class":74},[68,16591,190],{"class":74},[68,16593,7436],{"class":74},[68,16595,190],{"class":74},[68,16597,7418],{"class":74},[68,16599,12493],{"class":214},[68,16601,211],{"class":74},[68,16603,159],{"class":74},[68,16605,16606,16608,16610,16612],{"class":70,"line":89},[68,16607,2273],{"class":186},[68,16609,202],{"class":74},[68,16611,421],{"class":173},[68,16613,424],{"class":74},[68,16615,16616,16618,16620,16622,16624,16626,16628,16630,16632,16634,16636,16639],{"class":70,"line":99},[68,16617,2273],{"class":186},[68,16619,202],{"class":74},[68,16621,1756],{"class":173},[68,16623,208],{"class":74},[68,16625,211],{"class":74},[68,16627,16587],{"class":214},[68,16629,211],{"class":74},[68,16631,190],{"class":74},[68,16633,7436],{"class":74},[68,16635,190],{"class":74},[68,16637,16638],{"class":339}," 14",[68,16640,159],{"class":74},[68,16642,16643,16645,16647,16649,16651,16653,16655,16657,16659,16661,16664,16666],{"class":70,"line":111},[68,16644,2273],{"class":186},[68,16646,202],{"class":74},[68,16648,1932],{"class":173},[68,16650,208],{"class":74},[68,16652,10203],{"class":339},[68,16654,190],{"class":74},[68,16656,7500],{"class":339},[68,16658,190],{"class":74},[68,16660,7418],{"class":74},[68,16662,16663],{"class":214},"你好，世界。",[68,16665,211],{"class":74},[68,16667,159],{"class":74},[68,16669,16670,16672,16674,16676,16678,16680,16683,16685],{"class":70,"line":121},[68,16671,2273],{"class":186},[68,16673,202],{"class":74},[68,16675,1940],{"class":173},[68,16677,208],{"class":74},[68,16679,211],{"class":74},[68,16681,16682],{"class":214},"zh.pdf",[68,16684,211],{"class":74},[68,16686,159],{"class":74},[18,16688,16689,16690,16693,16694,16696,16697,16699],{},"两颗地雷：TTF 必须在",[30,16691,16692],{},"运行时","以指定路径存在于运行二进制的机器上（所以你的 Docker 镜像得捆一份字体）；",[47,16695,1932],{}," 宽度给 ",[47,16698,10203],{}," 意思是\"到右边距\"，但中文里宽度估算常算不准全角字符，容易超框被截断。",[18,16701,16702],{},[30,16703,7624],{},[59,16705,16707],{"className":61,"code":16706,"language":63,"meta":64,"style":64},"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(\"NotoSansSC-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(\"NotoSansSC\", fontData),\n        gpdf.WithDefaultFont(\"NotoSansSC\", 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 号\")\n        })\n    })\n\n    data, _ := doc.Generate()\n    os.WriteFile(\"zh.pdf\", data, 0o644)\n}\n",[47,16708,16709,16715,16719,16725,16733,16741,16745,16753,16761,16769,16773,16777,16787,16814,16826,16840,16844,16848,16862,16880,16910,16933,16955,16959,16963,16977,17001,17031,17049,17068,17087,17091,17095,17099,17117,17144],{"__ignoreMap":64},[68,16710,16711,16713],{"class":70,"line":71},[68,16712,75],{"class":74},[68,16714,79],{"class":78},[68,16716,16717],{"class":70,"line":82},[68,16718,86],{"emptyLinePlaceholder":85},[68,16720,16721,16723],{"class":70,"line":89},[68,16722,93],{"class":92},[68,16724,96],{"class":74},[68,16726,16727,16729,16731],{"class":70,"line":99},[68,16728,102],{"class":74},[68,16730,105],{"class":78},[68,16732,108],{"class":74},[68,16734,16735,16737,16739],{"class":70,"line":111},[68,16736,102],{"class":74},[68,16738,116],{"class":78},[68,16740,108],{"class":74},[68,16742,16743],{"class":70,"line":121},[68,16744,86],{"emptyLinePlaceholder":85},[68,16746,16747,16749,16751],{"class":70,"line":126},[68,16748,102],{"class":74},[68,16750,131],{"class":78},[68,16752,108],{"class":74},[68,16754,16755,16757,16759],{"class":70,"line":136},[68,16756,102],{"class":74},[68,16758,141],{"class":78},[68,16760,108],{"class":74},[68,16762,16763,16765,16767],{"class":70,"line":146},[68,16764,102],{"class":74},[68,16766,151],{"class":78},[68,16768,108],{"class":74},[68,16770,16771],{"class":70,"line":156},[68,16772,159],{"class":74},[68,16774,16775],{"class":70,"line":162},[68,16776,86],{"emptyLinePlaceholder":85},[68,16778,16779,16781,16783,16785],{"class":70,"line":167},[68,16780,170],{"class":74},[68,16782,174],{"class":173},[68,16784,177],{"class":74},[68,16786,180],{"class":74},[68,16788,16789,16792,16794,16796,16798,16800,16802,16804,16806,16808,16810,16812],{"class":70,"line":183},[68,16790,16791],{"class":186},"    fontData",[68,16793,190],{"class":74},[68,16795,193],{"class":186},[68,16797,196],{"class":74},[68,16799,199],{"class":186},[68,16801,202],{"class":74},[68,16803,205],{"class":173},[68,16805,208],{"class":74},[68,16807,211],{"class":74},[68,16809,12493],{"class":214},[68,16811,211],{"class":74},[68,16813,159],{"class":74},[68,16815,16816,16818,16820,16822,16824],{"class":70,"line":221},[68,16817,224],{"class":92},[68,16819,193],{"class":186},[68,16821,229],{"class":74},[68,16823,232],{"class":74},[68,16825,180],{"class":74},[68,16827,16828,16830,16832,16834,16836,16838],{"class":70,"line":237},[68,16829,240],{"class":186},[68,16831,202],{"class":74},[68,16833,245],{"class":173},[68,16835,208],{"class":74},[68,16837,250],{"class":186},[68,16839,159],{"class":74},[68,16841,16842],{"class":70,"line":255},[68,16843,258],{"class":74},[68,16845,16846],{"class":70,"line":261},[68,16847,86],{"emptyLinePlaceholder":85},[68,16849,16850,16852,16854,16856,16858,16860],{"class":70,"line":266},[68,16851,269],{"class":186},[68,16853,196],{"class":74},[68,16855,274],{"class":186},[68,16857,202],{"class":74},[68,16859,279],{"class":173},[68,16861,282],{"class":74},[68,16863,16864,16866,16868,16870,16872,16874,16876,16878],{"class":70,"line":285},[68,16865,288],{"class":186},[68,16867,202],{"class":74},[68,16869,293],{"class":173},[68,16871,208],{"class":74},[68,16873,320],{"class":186},[68,16875,202],{"class":74},[68,16877,302],{"class":186},[68,16879,305],{"class":74},[68,16881,16882,16884,16886,16888,16890,16892,16894,16896,16898,16900,16902,16904,16906,16908],{"class":70,"line":308},[68,16883,288],{"class":186},[68,16885,202],{"class":74},[68,16887,315],{"class":173},[68,16889,208],{"class":74},[68,16891,320],{"class":186},[68,16893,202],{"class":74},[68,16895,325],{"class":173},[68,16897,208],{"class":74},[68,16899,320],{"class":186},[68,16901,202],{"class":74},[68,16903,334],{"class":173},[68,16905,208],{"class":74},[68,16907,340],{"class":339},[68,16909,343],{"class":74},[68,16911,16912,16914,16916,16918,16920,16922,16924,16926,16928,16931],{"class":70,"line":346},[68,16913,288],{"class":186},[68,16915,202],{"class":74},[68,16917,353],{"class":173},[68,16919,208],{"class":74},[68,16921,211],{"class":74},[68,16923,12581],{"class":214},[68,16925,211],{"class":74},[68,16927,190],{"class":74},[68,16929,16930],{"class":186}," fontData",[68,16932,305],{"class":74},[68,16934,16935,16937,16939,16941,16943,16945,16947,16949,16951,16953],{"class":70,"line":372},[68,16936,288],{"class":186},[68,16938,202],{"class":74},[68,16940,379],{"class":173},[68,16942,208],{"class":74},[68,16944,211],{"class":74},[68,16946,12581],{"class":214},[68,16948,211],{"class":74},[68,16950,190],{"class":74},[68,16952,16638],{"class":339},[68,16954,305],{"class":74},[68,16956,16957],{"class":70,"line":397},[68,16958,400],{"class":74},[68,16960,16961],{"class":70,"line":403},[68,16962,86],{"emptyLinePlaceholder":85},[68,16964,16965,16967,16969,16971,16973,16975],{"class":70,"line":408},[68,16966,411],{"class":186},[68,16968,196],{"class":74},[68,16970,416],{"class":186},[68,16972,202],{"class":74},[68,16974,421],{"class":173},[68,16976,424],{"class":74},[68,16978,16979,16981,16983,16985,16987,16989,16991,16993,16995,16997,16999],{"class":70,"line":427},[68,16980,430],{"class":186},[68,16982,202],{"class":74},[68,16984,435],{"class":173},[68,16986,438],{"class":74},[68,16988,442],{"class":441},[68,16990,445],{"class":74},[68,16992,448],{"class":78},[68,16994,202],{"class":74},[68,16996,453],{"class":78},[68,16998,456],{"class":74},[68,17000,180],{"class":74},[68,17002,17003,17005,17007,17009,17011,17013,17015,17017,17019,17021,17023,17025,17027,17029],{"class":70,"line":461},[68,17004,464],{"class":186},[68,17006,202],{"class":74},[68,17008,469],{"class":173},[68,17010,208],{"class":74},[68,17012,474],{"class":339},[68,17014,190],{"class":74},[68,17016,479],{"class":74},[68,17018,482],{"class":441},[68,17020,445],{"class":74},[68,17022,448],{"class":78},[68,17024,202],{"class":74},[68,17026,491],{"class":78},[68,17028,456],{"class":74},[68,17030,180],{"class":74},[68,17032,17033,17035,17037,17039,17041,17043,17045,17047],{"class":70,"line":498},[68,17034,501],{"class":186},[68,17036,202],{"class":74},[68,17038,506],{"class":173},[68,17040,208],{"class":74},[68,17042,211],{"class":74},[68,17044,16663],{"class":214},[68,17046,211],{"class":74},[68,17048,159],{"class":74},[68,17050,17051,17053,17055,17057,17059,17061,17064,17066],{"class":70,"line":546},[68,17052,501],{"class":186},[68,17054,202],{"class":74},[68,17056,506],{"class":173},[68,17058,208],{"class":74},[68,17060,211],{"class":74},[68,17062,17063],{"class":214},"天行健，君子以自强不息。",[68,17065,211],{"class":74},[68,17067,159],{"class":74},[68,17069,17070,17072,17074,17076,17078,17080,17083,17085],{"class":70,"line":566},[68,17071,501],{"class":186},[68,17073,202],{"class":74},[68,17075,506],{"class":173},[68,17077,208],{"class":74},[68,17079,211],{"class":74},[68,17081,17082],{"class":214},"北京市朝阳区建国门外大街 1 号",[68,17084,211],{"class":74},[68,17086,159],{"class":74},[68,17088,17089],{"class":70,"line":572},[68,17090,569],{"class":74},[68,17092,17093],{"class":70,"line":578},[68,17094,575],{"class":74},[68,17096,17097],{"class":70,"line":583},[68,17098,86],{"emptyLinePlaceholder":85},[68,17100,17101,17103,17105,17107,17109,17111,17113,17115],{"class":70,"line":604},[68,17102,586],{"class":186},[68,17104,190],{"class":74},[68,17106,974],{"class":186},[68,17108,196],{"class":74},[68,17110,416],{"class":186},[68,17112,202],{"class":74},[68,17114,599],{"class":173},[68,17116,424],{"class":74},[68,17118,17119,17122,17124,17126,17128,17130,17132,17134,17136,17138,17140,17142],{"class":70,"line":617},[68,17120,17121],{"class":186},"    os",[68,17123,202],{"class":74},[68,17125,650],{"class":173},[68,17127,208],{"class":74},[68,17129,211],{"class":74},[68,17131,16682],{"class":214},[68,17133,211],{"class":74},[68,17135,190],{"class":74},[68,17137,664],{"class":186},[68,17139,190],{"class":74},[68,17141,669],{"class":339},[68,17143,159],{"class":74},[68,17145,17146],{"class":70,"line":632},[68,17147,706],{"class":74},[18,17149,17150],{},"两处不同。",[18,17152,17153,17154,17157,17158,17161],{},"其一：",[30,17155,17156],{},"传字节切片，不传路径","。用 ",[47,17159,17160],{},"//go:embed NotoSansSC-Regular.ttf"," 把 TTF 嵌进二进制，部署时就自包含，不会出现\"生产环境找不到字体\"的情况。",[18,17163,17164],{},"其二：gpdf 的 TrueType 子集化器理解 CJK 的 cmap 格式（4、6、12）与 Identity-H 编码。最终 PDF 只包含你实际用到的字形 —— 一份 200 字符的中文发票嵌入 NotoSansSC 后，字体子集约 30 KB，而不是完整 4 MB。如果你见过 gofpdf 生成一页中文就 5 MB 的 PDF，这是你会第一眼察觉的差别。",[18,17166,17167],{},"关于思源黑体、方正系列、字体回退等更深入的 CJK 话题，会在后续文章细讲。",[14,17169,17171],{"id":17170},"before-after-4每页固定页眉-页脚带页码","Before / After 4：每页固定页眉 + 页脚带页码",[18,17173,17174,17175,866,17178,17181,17182,17185,17186,1929,17188,720],{},"gofpdf 处理重复框架用 ",[47,17176,17177],{},"SetHeaderFunc",[47,17179,17180],{},"SetFooterFunc","，两个都接收一个针对当前光标运行的 ",[47,17183,17184],{},"func()","。页码用 ",[47,17187,14700],{},[47,17189,17190],{},"pdf.AliasNbPages()",[18,17192,17193],{},[30,17194,14792],{},[59,17196,17198],{"className":61,"code":17197,"language":63,"meta":64,"style":64},"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// ... body ...\n",[47,17199,17200,17242,17255,17285,17312,17327,17331,17343,17358,17389,17407,17444,17475,17479,17495,17505],{"__ignoreMap":64},[68,17201,17202,17204,17206,17208,17210,17212,17214,17216,17218,17220,17222,17224,17226,17228,17230,17232,17234,17236,17238,17240],{"class":70,"line":71},[68,17203,16534],{"class":186},[68,17205,196],{"class":74},[68,17207,14841],{"class":186},[68,17209,202],{"class":74},[68,17211,7404],{"class":173},[68,17213,208],{"class":74},[68,17215,211],{"class":74},[68,17217,7411],{"class":214},[68,17219,211],{"class":74},[68,17221,190],{"class":74},[68,17223,7418],{"class":74},[68,17225,7421],{"class":214},[68,17227,211],{"class":74},[68,17229,190],{"class":74},[68,17231,7418],{"class":74},[68,17233,302],{"class":214},[68,17235,211],{"class":74},[68,17237,190],{"class":74},[68,17239,7436],{"class":74},[68,17241,159],{"class":74},[68,17243,17244,17246,17248,17250,17253],{"class":70,"line":82},[68,17245,2273],{"class":186},[68,17247,202],{"class":74},[68,17249,17177],{"class":173},[68,17251,17252],{"class":74},"(func()",[68,17254,180],{"class":74},[68,17256,17257,17259,17261,17263,17265,17267,17269,17271,17273,17275,17277,17279,17281,17283],{"class":70,"line":89},[68,17258,7443],{"class":186},[68,17260,202],{"class":74},[68,17262,1756],{"class":173},[68,17264,208],{"class":74},[68,17266,211],{"class":74},[68,17268,7464],{"class":214},[68,17270,211],{"class":74},[68,17272,190],{"class":74},[68,17274,7418],{"class":74},[68,17276,7473],{"class":214},[68,17278,211],{"class":74},[68,17280,190],{"class":74},[68,17282,3275],{"class":339},[68,17284,159],{"class":74},[68,17286,17287,17289,17291,17293,17295,17297,17299,17301,17303,17305,17308,17310],{"class":70,"line":99},[68,17288,7443],{"class":186},[68,17290,202],{"class":74},[68,17292,1932],{"class":173},[68,17294,208],{"class":74},[68,17296,10203],{"class":339},[68,17298,190],{"class":74},[68,17300,7500],{"class":339},[68,17302,190],{"class":74},[68,17304,7418],{"class":74},[68,17306,17307],{"class":214},"ACME 科技有限公司",[68,17309,211],{"class":74},[68,17311,159],{"class":74},[68,17313,17314,17316,17318,17321,17323,17325],{"class":70,"line":111},[68,17315,7443],{"class":186},[68,17317,202],{"class":74},[68,17319,17320],{"class":173},"Ln",[68,17322,208],{"class":74},[68,17324,5451],{"class":339},[68,17326,159],{"class":74},[68,17328,17329],{"class":70,"line":121},[68,17330,3717],{"class":74},[68,17332,17333,17335,17337,17339,17341],{"class":70,"line":126},[68,17334,2273],{"class":186},[68,17336,202],{"class":74},[68,17338,17180],{"class":173},[68,17340,17252],{"class":74},[68,17342,180],{"class":74},[68,17344,17345,17347,17349,17351,17354,17356],{"class":70,"line":136},[68,17346,7443],{"class":186},[68,17348,202],{"class":74},[68,17350,8270],{"class":173},[68,17352,17353],{"class":74},"(-",[68,17355,5451],{"class":339},[68,17357,159],{"class":74},[68,17359,17360,17362,17364,17366,17368,17370,17372,17374,17376,17378,17381,17383,17385,17387],{"class":70,"line":146},[68,17361,7443],{"class":186},[68,17363,202],{"class":74},[68,17365,1756],{"class":173},[68,17367,208],{"class":74},[68,17369,211],{"class":74},[68,17371,7464],{"class":214},[68,17373,211],{"class":74},[68,17375,190],{"class":74},[68,17377,7418],{"class":74},[68,17379,17380],{"class":214},"I",[68,17382,211],{"class":74},[68,17384,190],{"class":74},[68,17386,15467],{"class":339},[68,17388,159],{"class":74},[68,17390,17391,17393,17395,17397,17399,17401,17403,17405],{"class":70,"line":156},[68,17392,7443],{"class":186},[68,17394,202],{"class":74},[68,17396,2004],{"class":173},[68,17398,208],{"class":74},[68,17400,10203],{"class":339},[68,17402,190],{"class":74},[68,17404,7500],{"class":339},[68,17406,2055],{"class":74},[68,17408,17409,17412,17414,17417,17419,17421,17424,17427,17430,17432,17434,17436,17438,17441],{"class":70,"line":162},[68,17410,17411],{"class":186},"        fmt",[68,17413,202],{"class":74},[68,17415,17416],{"class":173},"Sprintf",[68,17418,208],{"class":74},[68,17420,211],{"class":74},[68,17422,17423],{"class":214},"Page ",[68,17425,17426],{"class":3842},"%d",[68,17428,17429],{"class":214},"/{nb}",[68,17431,211],{"class":74},[68,17433,190],{"class":74},[68,17435,7562],{"class":186},[68,17437,202],{"class":74},[68,17439,17440],{"class":173},"PageNo",[68,17442,17443],{"class":74},"()),\n",[68,17445,17446,17449,17451,17453,17455,17457,17459,17461,17463,17465,17467,17469,17471,17473],{"class":70,"line":167},[68,17447,17448],{"class":74},"        \"\"",[68,17450,190],{"class":74},[68,17452,15490],{"class":339},[68,17454,190],{"class":74},[68,17456,7418],{"class":74},[68,17458,15558],{"class":214},[68,17460,211],{"class":74},[68,17462,190],{"class":74},[68,17464,15931],{"class":15504},[68,17466,190],{"class":74},[68,17468,15490],{"class":339},[68,17470,190],{"class":74},[68,17472,7436],{"class":74},[68,17474,159],{"class":74},[68,17476,17477],{"class":70,"line":183},[68,17478,3717],{"class":74},[68,17480,17481,17483,17485,17488,17490,17493],{"class":70,"line":221},[68,17482,2273],{"class":186},[68,17484,202],{"class":74},[68,17486,17487],{"class":173},"AliasNbPages",[68,17489,208],{"class":74},[68,17491,17492],{"class":74},"\"\"",[68,17494,159],{"class":74},[68,17496,17497,17499,17501,17503],{"class":70,"line":237},[68,17498,2273],{"class":186},[68,17500,202],{"class":74},[68,17502,421],{"class":173},[68,17504,424],{"class":74},[68,17506,17507],{"class":70,"line":255},[68,17508,17509],{"class":2206},"// ... body ...\n",[18,17511,17512,17515],{},[47,17513,17514],{},"{nb}"," 是 gofpdf 在输出时用总页数重写的哨兵。能用，但得\"知道\"才行。",[18,17517,17518],{},[30,17519,7624],{},[59,17521,17523],{"className":61,"code":17522,"language":63,"meta":64,"style":64},"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",[47,17524,17525,17539,17557,17587,17591,17595,17621,17646,17676,17714,17749,17771,17775,17779,17783,17787,17812,17836,17866,17884,17919,17923,17953,17958,17963,17982,18016,18020,18024,18028,18032,18063,18077,18101,18131,18172,18176,18180],{"__ignoreMap":64},[68,17526,17527,17529,17531,17533,17535,17537],{"class":70,"line":71},[68,17528,1002],{"class":186},[68,17530,196],{"class":74},[68,17532,274],{"class":186},[68,17534,202],{"class":74},[68,17536,279],{"class":173},[68,17538,282],{"class":74},[68,17540,17541,17543,17545,17547,17549,17551,17553,17555],{"class":70,"line":82},[68,17542,1017],{"class":186},[68,17544,202],{"class":74},[68,17546,293],{"class":173},[68,17548,208],{"class":74},[68,17550,320],{"class":186},[68,17552,202],{"class":74},[68,17554,302],{"class":186},[68,17556,305],{"class":74},[68,17558,17559,17561,17563,17565,17567,17569,17571,17573,17575,17577,17579,17581,17583,17585],{"class":70,"line":89},[68,17560,1017],{"class":186},[68,17562,202],{"class":74},[68,17564,315],{"class":173},[68,17566,208],{"class":74},[68,17568,320],{"class":186},[68,17570,202],{"class":74},[68,17572,325],{"class":173},[68,17574,208],{"class":74},[68,17576,320],{"class":186},[68,17578,202],{"class":74},[68,17580,334],{"class":173},[68,17582,208],{"class":74},[68,17584,340],{"class":339},[68,17586,343],{"class":74},[68,17588,17589],{"class":70,"line":99},[68,17590,159],{"class":74},[68,17592,17593],{"class":70,"line":111},[68,17594,86],{"emptyLinePlaceholder":85},[68,17596,17597,17600,17602,17604,17606,17608,17610,17612,17614,17617,17619],{"class":70,"line":121},[68,17598,17599],{"class":186},"doc",[68,17601,202],{"class":74},[68,17603,7525],{"class":173},[68,17605,438],{"class":74},[68,17607,18],{"class":441},[68,17609,445],{"class":74},[68,17611,448],{"class":78},[68,17613,202],{"class":74},[68,17615,17616],{"class":78},"PageBuilder",[68,17618,456],{"class":74},[68,17620,180],{"class":74},[68,17622,17623,17626,17628,17630,17632,17634,17636,17638,17640,17642,17644],{"class":70,"line":126},[68,17624,17625],{"class":186},"    p",[68,17627,202],{"class":74},[68,17629,435],{"class":173},[68,17631,438],{"class":74},[68,17633,442],{"class":441},[68,17635,445],{"class":74},[68,17637,448],{"class":78},[68,17639,202],{"class":74},[68,17641,453],{"class":78},[68,17643,456],{"class":74},[68,17645,180],{"class":74},[68,17647,17648,17650,17652,17654,17656,17658,17660,17662,17664,17666,17668,17670,17672,17674],{"class":70,"line":136},[68,17649,464],{"class":186},[68,17651,202],{"class":74},[68,17653,469],{"class":173},[68,17655,208],{"class":74},[68,17657,474],{"class":339},[68,17659,190],{"class":74},[68,17661,479],{"class":74},[68,17663,482],{"class":441},[68,17665,445],{"class":74},[68,17667,448],{"class":78},[68,17669,202],{"class":74},[68,17671,491],{"class":78},[68,17673,456],{"class":74},[68,17675,180],{"class":74},[68,17677,17678,17680,17682,17684,17686,17688,17690,17692,17694,17696,17698,17700,17702,17704,17706,17708,17710,17712],{"class":70,"line":146},[68,17679,501],{"class":186},[68,17681,202],{"class":74},[68,17683,506],{"class":173},[68,17685,208],{"class":74},[68,17687,211],{"class":74},[68,17689,17307],{"class":214},[68,17691,211],{"class":74},[68,17693,190],{"class":74},[68,17695,520],{"class":186},[68,17697,202],{"class":74},[68,17699,540],{"class":173},[68,17701,7606],{"class":74},[68,17703,520],{"class":186},[68,17705,202],{"class":74},[68,17707,525],{"class":173},[68,17709,208],{"class":74},[68,17711,474],{"class":339},[68,17713,5007],{"class":74},[68,17715,17716,17718,17720,17723,17725,17727,17729,17732,17734,17736,17738,17741,17743,17746],{"class":70,"line":156},[68,17717,501],{"class":186},[68,17719,202],{"class":74},[68,17721,17722],{"class":173},"Line",[68,17724,208],{"class":74},[68,17726,448],{"class":186},[68,17728,202],{"class":74},[68,17730,17731],{"class":173},"LineColor",[68,17733,208],{"class":74},[68,17735,2273],{"class":186},[68,17737,202],{"class":74},[68,17739,17740],{"class":173},"Gray",[68,17742,208],{"class":74},[68,17744,17745],{"class":339},"0.7",[68,17747,17748],{"class":74},")))\n",[68,17750,17751,17753,17755,17757,17759,17761,17763,17765,17767,17769],{"class":70,"line":162},[68,17752,501],{"class":186},[68,17754,202],{"class":74},[68,17756,2129],{"class":173},[68,17758,208],{"class":74},[68,17760,320],{"class":186},[68,17762,202],{"class":74},[68,17764,334],{"class":173},[68,17766,208],{"class":74},[68,17768,5814],{"class":339},[68,17770,5007],{"class":74},[68,17772,17773],{"class":70,"line":167},[68,17774,569],{"class":74},[68,17776,17777],{"class":70,"line":183},[68,17778,575],{"class":74},[68,17780,17781],{"class":70,"line":221},[68,17782,3717],{"class":74},[68,17784,17785],{"class":70,"line":237},[68,17786,86],{"emptyLinePlaceholder":85},[68,17788,17789,17791,17793,17796,17798,17800,17802,17804,17806,17808,17810],{"class":70,"line":255},[68,17790,17599],{"class":186},[68,17792,202],{"class":74},[68,17794,17795],{"class":173},"Footer",[68,17797,438],{"class":74},[68,17799,18],{"class":441},[68,17801,445],{"class":74},[68,17803,448],{"class":78},[68,17805,202],{"class":74},[68,17807,17616],{"class":78},[68,17809,456],{"class":74},[68,17811,180],{"class":74},[68,17813,17814,17816,17818,17820,17822,17824,17826,17828,17830,17832,17834],{"class":70,"line":261},[68,17815,17625],{"class":186},[68,17817,202],{"class":74},[68,17819,435],{"class":173},[68,17821,438],{"class":74},[68,17823,442],{"class":441},[68,17825,445],{"class":74},[68,17827,448],{"class":78},[68,17829,202],{"class":74},[68,17831,453],{"class":78},[68,17833,456],{"class":74},[68,17835,180],{"class":74},[68,17837,17838,17840,17842,17844,17846,17848,17850,17852,17854,17856,17858,17860,17862,17864],{"class":70,"line":266},[68,17839,464],{"class":186},[68,17841,202],{"class":74},[68,17843,469],{"class":173},[68,17845,208],{"class":74},[68,17847,5632],{"class":339},[68,17849,190],{"class":74},[68,17851,479],{"class":74},[68,17853,482],{"class":441},[68,17855,445],{"class":74},[68,17857,448],{"class":78},[68,17859,202],{"class":74},[68,17861,491],{"class":78},[68,17863,456],{"class":74},[68,17865,180],{"class":74},[68,17867,17868,17870,17872,17874,17876,17878,17880,17882],{"class":70,"line":285},[68,17869,501],{"class":186},[68,17871,202],{"class":74},[68,17873,506],{"class":173},[68,17875,208],{"class":74},[68,17877,211],{"class":74},[68,17879,17307],{"class":214},[68,17881,211],{"class":74},[68,17883,2055],{"class":74},[68,17885,17886,17888,17890,17892,17894,17896,17898,17900,17902,17904,17906,17908,17910,17912,17914,17917],{"class":70,"line":308},[68,17887,11267],{"class":186},[68,17889,202],{"class":74},[68,17891,525],{"class":173},[68,17893,208],{"class":74},[68,17895,6023],{"class":339},[68,17897,533],{"class":74},[68,17899,520],{"class":186},[68,17901,202],{"class":74},[68,17903,11325],{"class":173},[68,17905,208],{"class":74},[68,17907,2273],{"class":186},[68,17909,202],{"class":74},[68,17911,17740],{"class":173},[68,17913,208],{"class":74},[68,17915,17916],{"class":339},"0.5",[68,17918,17748],{"class":74},[68,17920,17921],{"class":70,"line":346},[68,17922,569],{"class":74},[68,17924,17925,17927,17929,17931,17933,17935,17937,17939,17941,17943,17945,17947,17949,17951],{"class":70,"line":372},[68,17926,464],{"class":186},[68,17928,202],{"class":74},[68,17930,469],{"class":173},[68,17932,208],{"class":74},[68,17934,5632],{"class":339},[68,17936,190],{"class":74},[68,17938,479],{"class":74},[68,17940,482],{"class":441},[68,17942,445],{"class":74},[68,17944,448],{"class":78},[68,17946,202],{"class":74},[68,17948,491],{"class":78},[68,17950,456],{"class":74},[68,17952,180],{"class":74},[68,17954,17955],{"class":70,"line":397},[68,17956,17957],{"class":2206},"            // \"第 X 页 / 共 Y 页\" —— 两者都是占位符，\n",[68,17959,17960],{"class":70,"line":403},[68,17961,17962],{"class":2206},"            // 分页完成后由布局引擎展开。\n",[68,17964,17965,17967,17969,17972,17974,17976,17978,17980],{"class":70,"line":408},[68,17966,501],{"class":186},[68,17968,202],{"class":74},[68,17970,17971],{"class":173},"PageNumber",[68,17973,208],{"class":74},[68,17975,448],{"class":186},[68,17977,202],{"class":74},[68,17979,7250],{"class":173},[68,17981,11316],{"class":74},[68,17983,17984,17986,17988,17990,17992,17994,17996,17998,18000,18002,18004,18006,18008,18010,18012,18014],{"class":70,"line":427},[68,17985,11267],{"class":186},[68,17987,202],{"class":74},[68,17989,525],{"class":173},[68,17991,208],{"class":74},[68,17993,6023],{"class":339},[68,17995,533],{"class":74},[68,17997,520],{"class":186},[68,17999,202],{"class":74},[68,18001,11325],{"class":173},[68,18003,208],{"class":74},[68,18005,2273],{"class":186},[68,18007,202],{"class":74},[68,18009,17740],{"class":173},[68,18011,208],{"class":74},[68,18013,17916],{"class":339},[68,18015,17748],{"class":74},[68,18017,18018],{"class":70,"line":461},[68,18019,569],{"class":74},[68,18021,18022],{"class":70,"line":498},[68,18023,575],{"class":74},[68,18025,18026],{"class":70,"line":546},[68,18027,3717],{"class":74},[68,18029,18030],{"class":70,"line":566},[68,18031,86],{"emptyLinePlaceholder":85},[68,18033,18034,18036,18039,18041,18043,18046,18048,18051,18053,18055,18058,18061],{"class":70,"line":572},[68,18035,15862],{"class":92},[68,18037,18038],{"class":186}," i ",[68,18040,196],{"class":74},[68,18042,15490],{"class":339},[68,18044,18045],{"class":74},";",[68,18047,18038],{"class":186},[68,18049,18050],{"class":74},"\u003C",[68,18052,7500],{"class":339},[68,18054,18045],{"class":74},[68,18056,18057],{"class":186}," i",[68,18059,18060],{"class":74},"++",[68,18062,180],{"class":74},[68,18064,18065,18067,18069,18071,18073,18075],{"class":70,"line":578},[68,18066,411],{"class":186},[68,18068,196],{"class":74},[68,18070,416],{"class":186},[68,18072,202],{"class":74},[68,18074,421],{"class":173},[68,18076,424],{"class":74},[68,18078,18079,18081,18083,18085,18087,18089,18091,18093,18095,18097,18099],{"class":70,"line":583},[68,18080,430],{"class":186},[68,18082,202],{"class":74},[68,18084,435],{"class":173},[68,18086,438],{"class":74},[68,18088,442],{"class":441},[68,18090,445],{"class":74},[68,18092,448],{"class":78},[68,18094,202],{"class":74},[68,18096,453],{"class":78},[68,18098,456],{"class":74},[68,18100,180],{"class":74},[68,18102,18103,18105,18107,18109,18111,18113,18115,18117,18119,18121,18123,18125,18127,18129],{"class":70,"line":604},[68,18104,464],{"class":186},[68,18106,202],{"class":74},[68,18108,469],{"class":173},[68,18110,208],{"class":74},[68,18112,474],{"class":339},[68,18114,190],{"class":74},[68,18116,479],{"class":74},[68,18118,482],{"class":441},[68,18120,445],{"class":74},[68,18122,448],{"class":78},[68,18124,202],{"class":74},[68,18126,491],{"class":78},[68,18128,456],{"class":74},[68,18130,180],{"class":74},[68,18132,18133,18135,18137,18139,18141,18143,18145,18147,18149,18151,18154,18156,18159,18161,18163,18165,18168,18170],{"class":70,"line":617},[68,18134,501],{"class":186},[68,18136,202],{"class":74},[68,18138,506],{"class":173},[68,18140,208],{"class":74},[68,18142,3830],{"class":186},[68,18144,202],{"class":74},[68,18146,17416],{"class":173},[68,18148,208],{"class":74},[68,18150,211],{"class":74},[68,18152,18153],{"class":214},"第 ",[68,18155,17426],{"class":3842},[68,18157,18158],{"class":214}," 页正文。",[68,18160,211],{"class":74},[68,18162,190],{"class":74},[68,18164,18057],{"class":186},[68,18166,18167],{"class":74},"+",[68,18169,15483],{"class":339},[68,18171,5007],{"class":74},[68,18173,18174],{"class":70,"line":632},[68,18175,569],{"class":74},[68,18177,18178],{"class":70,"line":637},[68,18179,575],{"class":74},[68,18181,18182],{"class":70,"line":683},[68,18183,706],{"class":74},[18,18185,18186,3746,18188,18191,18192,18194,18195,18198],{},[47,18187,17971],{},[47,18189,18190],{},"TotalPages"," 是占位符，分页完成、布局引擎知道总页数后才展开。不需要 ",[47,18193,17514],{}," 哨兵，也不需要 ",[47,18196,18197],{},"SetY(-15)"," 把页脚钉在底部 —— 页脚就是一棵树，引擎每页自动为它预留空间。",[14,18200,18202],{"id":18201},"before-after-5给-http-handler-返回字节流","Before / After 5：给 HTTP handler 返回字节流",[18,18204,18205,18206,18208,18209,900,18211,18214],{},"实际生产 gofpdf 代码大多不写文件，而是写到 ",[47,18207,1893],{}," —— 一般是返回 ",[47,18210,7547],{},[47,18212,18213],{},"http.ResponseWriter","。这组里 gpdf 的 API 最接近 gofpdf。",[18,18216,18217],{},[30,18218,14792],{},[59,18220,18222],{"className":61,"code":18221,"language":63,"meta":64,"style":64},"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",[47,18223,18224,18256,18298,18308,18334,18385,18389,18419,18447,18473,18477],{"__ignoreMap":64},[68,18225,18226,18228,18230,18232,18234,18236,18238,18240,18242,18244,18246,18248,18250,18252,18254],{"class":70,"line":71},[68,18227,170],{"class":74},[68,18229,7358],{"class":173},[68,18231,208],{"class":74},[68,18233,1872],{"class":441},[68,18235,7365],{"class":78},[68,18237,202],{"class":74},[68,18239,7370],{"class":78},[68,18241,190],{"class":74},[68,18243,7375],{"class":441},[68,18245,445],{"class":74},[68,18247,7380],{"class":78},[68,18249,202],{"class":74},[68,18251,7385],{"class":78},[68,18253,456],{"class":74},[68,18255,180],{"class":74},[68,18257,18258,18260,18262,18264,18266,18268,18270,18272,18274,18276,18278,18280,18282,18284,18286,18288,18290,18292,18294,18296],{"class":70,"line":82},[68,18259,7394],{"class":186},[68,18261,196],{"class":74},[68,18263,14841],{"class":186},[68,18265,202],{"class":74},[68,18267,7404],{"class":173},[68,18269,208],{"class":74},[68,18271,211],{"class":74},[68,18273,7411],{"class":214},[68,18275,211],{"class":74},[68,18277,190],{"class":74},[68,18279,7418],{"class":74},[68,18281,7421],{"class":214},[68,18283,211],{"class":74},[68,18285,190],{"class":74},[68,18287,7418],{"class":74},[68,18289,302],{"class":214},[68,18291,211],{"class":74},[68,18293,190],{"class":74},[68,18295,7436],{"class":74},[68,18297,159],{"class":74},[68,18299,18300,18302,18304,18306],{"class":70,"line":89},[68,18301,7443],{"class":186},[68,18303,202],{"class":74},[68,18305,421],{"class":173},[68,18307,424],{"class":74},[68,18309,18310,18312,18314,18316,18318,18320,18322,18324,18326,18328,18330,18332],{"class":70,"line":99},[68,18311,7443],{"class":186},[68,18313,202],{"class":74},[68,18315,1756],{"class":173},[68,18317,208],{"class":74},[68,18319,211],{"class":74},[68,18321,7464],{"class":214},[68,18323,211],{"class":74},[68,18325,190],{"class":74},[68,18327,7436],{"class":74},[68,18329,190],{"class":74},[68,18331,3275],{"class":339},[68,18333,159],{"class":74},[68,18335,18336,18338,18340,18342,18344,18346,18348,18350,18352,18354,18357,18359,18361,18364,18366,18369,18371,18374,18376,18378,18380,18383],{"class":70,"line":111},[68,18337,7443],{"class":186},[68,18339,202],{"class":74},[68,18341,1932],{"class":173},[68,18343,208],{"class":74},[68,18345,10203],{"class":339},[68,18347,190],{"class":74},[68,18349,7500],{"class":339},[68,18351,190],{"class":74},[68,18353,7418],{"class":74},[68,18355,18356],{"class":214},"生成时间: ",[68,18358,211],{"class":74},[68,18360,18167],{"class":74},[68,18362,18363],{"class":186},"time",[68,18365,202],{"class":74},[68,18367,18368],{"class":173},"Now",[68,18370,7528],{"class":74},[68,18372,18373],{"class":173},"Format",[68,18375,208],{"class":74},[68,18377,18363],{"class":186},[68,18379,202],{"class":74},[68,18381,18382],{"class":186},"RFC3339",[68,18384,5007],{"class":74},[68,18386,18387],{"class":70,"line":121},[68,18388,86],{"emptyLinePlaceholder":85},[68,18390,18391,18393,18395,18397,18399,18401,18403,18405,18407,18409,18411,18413,18415,18417],{"class":70,"line":126},[68,18392,7520],{"class":186},[68,18394,202],{"class":74},[68,18396,7525],{"class":173},[68,18398,7528],{"class":74},[68,18400,7531],{"class":173},[68,18402,208],{"class":74},[68,18404,211],{"class":74},[68,18406,7538],{"class":214},[68,18408,211],{"class":74},[68,18410,190],{"class":74},[68,18412,7418],{"class":74},[68,18414,7547],{"class":214},[68,18416,211],{"class":74},[68,18418,159],{"class":74},[68,18420,18421,18423,18425,18427,18429,18431,18433,18435,18437,18439,18441,18443,18445],{"class":70,"line":136},[68,18422,224],{"class":92},[68,18424,193],{"class":186},[68,18426,196],{"class":74},[68,18428,7562],{"class":186},[68,18430,202],{"class":74},[68,18432,1936],{"class":173},[68,18434,208],{"class":74},[68,18436,1872],{"class":186},[68,18438,672],{"class":74},[68,18440,193],{"class":186},[68,18442,229],{"class":74},[68,18444,232],{"class":74},[68,18446,180],{"class":74},[68,18448,18449,18451,18453,18455,18457,18459,18461,18463,18465,18467,18469,18471],{"class":70,"line":146},[68,18450,7585],{"class":186},[68,18452,202],{"class":74},[68,18454,7590],{"class":173},[68,18456,208],{"class":74},[68,18458,1872],{"class":186},[68,18460,190],{"class":74},[68,18462,7599],{"class":186},[68,18464,202],{"class":74},[68,18466,7590],{"class":173},[68,18468,7606],{"class":74},[68,18470,7609],{"class":339},[68,18472,159],{"class":74},[68,18474,18475],{"class":70,"line":156},[68,18476,258],{"class":74},[68,18478,18479],{"class":70,"line":162},[68,18480,706],{"class":74},[18,18482,18483],{},[30,18484,7624],{},[59,18486,18488],{"className":61,"code":18487,"language":63,"meta":64,"style":64},"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",[47,18489,18490,18522,18536,18554,18584,18588,18592,18606,18630,18660,18700,18704,18708,18712,18742,18770,18796,18800],{"__ignoreMap":64},[68,18491,18492,18494,18496,18498,18500,18502,18504,18506,18508,18510,18512,18514,18516,18518,18520],{"class":70,"line":71},[68,18493,170],{"class":74},[68,18495,7358],{"class":173},[68,18497,208],{"class":74},[68,18499,1872],{"class":441},[68,18501,7365],{"class":78},[68,18503,202],{"class":74},[68,18505,7370],{"class":78},[68,18507,190],{"class":74},[68,18509,7375],{"class":441},[68,18511,445],{"class":74},[68,18513,7380],{"class":78},[68,18515,202],{"class":74},[68,18517,7385],{"class":78},[68,18519,456],{"class":74},[68,18521,180],{"class":74},[68,18523,18524,18526,18528,18530,18532,18534],{"class":70,"line":82},[68,18525,269],{"class":186},[68,18527,196],{"class":74},[68,18529,274],{"class":186},[68,18531,202],{"class":74},[68,18533,279],{"class":173},[68,18535,282],{"class":74},[68,18537,18538,18540,18542,18544,18546,18548,18550,18552],{"class":70,"line":89},[68,18539,288],{"class":186},[68,18541,202],{"class":74},[68,18543,293],{"class":173},[68,18545,208],{"class":74},[68,18547,320],{"class":186},[68,18549,202],{"class":74},[68,18551,302],{"class":186},[68,18553,305],{"class":74},[68,18555,18556,18558,18560,18562,18564,18566,18568,18570,18572,18574,18576,18578,18580,18582],{"class":70,"line":99},[68,18557,288],{"class":186},[68,18559,202],{"class":74},[68,18561,315],{"class":173},[68,18563,208],{"class":74},[68,18565,320],{"class":186},[68,18567,202],{"class":74},[68,18569,325],{"class":173},[68,18571,208],{"class":74},[68,18573,320],{"class":186},[68,18575,202],{"class":74},[68,18577,334],{"class":173},[68,18579,208],{"class":74},[68,18581,340],{"class":339},[68,18583,343],{"class":74},[68,18585,18586],{"class":70,"line":111},[68,18587,400],{"class":74},[68,18589,18590],{"class":70,"line":121},[68,18591,86],{"emptyLinePlaceholder":85},[68,18593,18594,18596,18598,18600,18602,18604],{"class":70,"line":126},[68,18595,411],{"class":186},[68,18597,196],{"class":74},[68,18599,416],{"class":186},[68,18601,202],{"class":74},[68,18603,421],{"class":173},[68,18605,424],{"class":74},[68,18607,18608,18610,18612,18614,18616,18618,18620,18622,18624,18626,18628],{"class":70,"line":136},[68,18609,430],{"class":186},[68,18611,202],{"class":74},[68,18613,435],{"class":173},[68,18615,438],{"class":74},[68,18617,442],{"class":441},[68,18619,445],{"class":74},[68,18621,448],{"class":78},[68,18623,202],{"class":74},[68,18625,453],{"class":78},[68,18627,456],{"class":74},[68,18629,180],{"class":74},[68,18631,18632,18634,18636,18638,18640,18642,18644,18646,18648,18650,18652,18654,18656,18658],{"class":70,"line":146},[68,18633,464],{"class":186},[68,18635,202],{"class":74},[68,18637,469],{"class":173},[68,18639,208],{"class":74},[68,18641,474],{"class":339},[68,18643,190],{"class":74},[68,18645,479],{"class":74},[68,18647,482],{"class":441},[68,18649,445],{"class":74},[68,18651,448],{"class":78},[68,18653,202],{"class":74},[68,18655,491],{"class":78},[68,18657,456],{"class":74},[68,18659,180],{"class":74},[68,18661,18662,18664,18666,18668,18670,18672,18674,18676,18679,18682,18684,18686,18688,18690,18692,18694,18696,18698],{"class":70,"line":156},[68,18663,501],{"class":186},[68,18665,202],{"class":74},[68,18667,506],{"class":173},[68,18669,208],{"class":74},[68,18671,211],{"class":74},[68,18673,18356],{"class":214},[68,18675,211],{"class":74},[68,18677,18678],{"class":74}," +",[68,18680,18681],{"class":186}," time",[68,18683,202],{"class":74},[68,18685,18368],{"class":173},[68,18687,7528],{"class":74},[68,18689,18373],{"class":173},[68,18691,208],{"class":74},[68,18693,18363],{"class":186},[68,18695,202],{"class":74},[68,18697,18382],{"class":186},[68,18699,5007],{"class":74},[68,18701,18702],{"class":70,"line":162},[68,18703,569],{"class":74},[68,18705,18706],{"class":70,"line":167},[68,18707,575],{"class":74},[68,18709,18710],{"class":70,"line":183},[68,18711,86],{"emptyLinePlaceholder":85},[68,18713,18714,18716,18718,18720,18722,18724,18726,18728,18730,18732,18734,18736,18738,18740],{"class":70,"line":221},[68,18715,7520],{"class":186},[68,18717,202],{"class":74},[68,18719,7525],{"class":173},[68,18721,7528],{"class":74},[68,18723,7531],{"class":173},[68,18725,208],{"class":74},[68,18727,211],{"class":74},[68,18729,7538],{"class":214},[68,18731,211],{"class":74},[68,18733,190],{"class":74},[68,18735,7418],{"class":74},[68,18737,7547],{"class":214},[68,18739,211],{"class":74},[68,18741,159],{"class":74},[68,18743,18744,18746,18748,18750,18752,18754,18756,18758,18760,18762,18764,18766,18768],{"class":70,"line":237},[68,18745,224],{"class":92},[68,18747,193],{"class":186},[68,18749,196],{"class":74},[68,18751,416],{"class":186},[68,18753,202],{"class":74},[68,18755,7955],{"class":173},[68,18757,208],{"class":74},[68,18759,1872],{"class":186},[68,18761,672],{"class":74},[68,18763,193],{"class":186},[68,18765,229],{"class":74},[68,18767,232],{"class":74},[68,18769,180],{"class":74},[68,18771,18772,18774,18776,18778,18780,18782,18784,18786,18788,18790,18792,18794],{"class":70,"line":255},[68,18773,7585],{"class":186},[68,18775,202],{"class":74},[68,18777,7590],{"class":173},[68,18779,208],{"class":74},[68,18781,1872],{"class":186},[68,18783,190],{"class":74},[68,18785,7599],{"class":186},[68,18787,202],{"class":74},[68,18789,7590],{"class":173},[68,18791,7606],{"class":74},[68,18793,7609],{"class":339},[68,18795,159],{"class":74},[68,18797,18798],{"class":70,"line":261},[68,18799,258],{"class":74},[68,18801,18802],{"class":70,"line":266},[68,18803,706],{"class":74},[18,18805,18806,18807,18809,18810,18813,18814,18816,18817,720],{},"形态一样。",[47,18808,14739],{}," 直接把 PDF 流进响应。如果要带 ",[47,18811,18812],{},"Content-Length","，先调 ",[47,18815,3928],{}," 拿字节切片再 ",[47,18818,18819],{},"len()",[14,18821,18823],{"id":18822},"够快到底是多快","\"够快\"到底是多快？",[18,18825,18826,18827,18829,18830,18832],{},"gpdf 在实际负载上",[30,18828,14369],{},"。下表数据来自 ",[47,18831,1396],{},"，Apple M1、Go 1.25。",[740,18834,18835,18849],{},[743,18836,18837],{},[746,18838,18839,18841,18843,18845,18847],{},[749,18840,8046],{},[749,18842,27],{},[749,18844,1429],{},[749,18846,8053],{},[749,18848,1438],{},[758,18850,18851,18866,18881,18896],{},[746,18852,18853,18856,18860,18862,18864],{},[763,18854,18855],{},"单页",[763,18857,18858],{},[30,18859,1342],{},[763,18861,1452],{},[763,18863,1458],{},[763,18865,1461],{},[746,18867,18868,18871,18875,18877,18879],{},[763,18869,18870],{},"4×10 表格",[763,18872,18873],{},[30,18874,1346],{},[763,18876,1473],{},[763,18878,1479],{},[763,18880,8088],{},[746,18882,18883,18886,18890,18892,18894],{},[763,18884,18885],{},"100 页文档",[763,18887,18888],{},[30,18889,1350],{},[763,18891,1358],{},[763,18893,8088],{},[763,18895,8104],{},[746,18897,18898,18900,18904,18906,18908],{},[763,18899,1507],{},[763,18901,18902],{},[30,18903,1512],{},[763,18905,1515],{},[763,18907,1521],{},[763,18909,8119],{},[18,18911,18912],{},"不是合成数。表格基准是 4 列 10 行发票明细，100 页基准是带重复页眉和页码的报表 —— 正是生产代码的形态。",[18,18914,18915],{},"顺便说一下含义。13 µs/单页 = 单核每秒 7.5 万张 hello-world PDF；108 µs/带表页 = 每秒约 9,000 张发票。重点不是炫耀，而是你可以不用再纠结 \"PDF 生成要不要缓存？是不是得丢进异步队列？\" 这种事。多数场景下，请求链路里同步生成就够。",[14,18917,18918],{"id":18918},"迁移要放弃的东西",[18,18920,18921],{},"指南不该掩盖真实差距。gpdf 目前相对 gofpdf 欠缺的：",[1111,18923,18924,18932,18948,18954],{},[885,18925,18926,720,18929,18931],{},[30,18927,18928],{},"任意角度的直线、贝塞尔曲线、复杂路径",[47,18930,8169],{}," 画的是跨列的水平线。CAD 图或自定义图表几何，gpdf 还没到。（但图表以图像形式预渲染嵌入是完全没问题的。）",[885,18933,18934,18939,18940,18942,18943,1929,18945,18947],{},[30,18935,18936,18938],{},[47,18937,1928],{}," 与绝对光标","。能用 ",[47,18941,14660],{}," 做绝对定位，但如果现有代码是 2,000 行 ",[47,18944,1928],{},[47,18946,1932],{},"，迁移基本等同重写。好处是重写后通常只有原来一半长。",[885,18949,18950,18953],{},[30,18951,18952],{},"AcroForm 可填写表单","。gpdf 还不生成可填字段。如果你的 PDF 是给用户在阅读器里填写的模板，暂时留在支持 AcroForm 的库上。",[885,18955,18956,18959],{},[30,18957,18958],{},"注释与书签","。基础大纲有，丰富注释没有。",[18,18961,18962],{},"如果这些都不卡你，迁移一气呵成。如果卡，开一个 Issue —— 路线图是由需求驱动的。",[14,18964,2814],{"id":2813},[18,18966,18967,18970],{},[30,18968,18969],{},"gpdf 是 gofpdf 的 fork 吗？","\n不是。gpdf 是纯 Go 从零重写的。PDF 线格式、布局引擎、TrueType 子集化器 —— 全部独立实现，与 gofpdf 及其 fork 没有代码血缘。必须重写而非 fork，是因为 gofpdf 的架构建立在\"单一可变光标\"上，声明式栅格塞不进去而不把现有调用全部打碎。",[18,18972,18973,18976,18977,10531,18979,18982,18983,18985,18986,18989],{},[30,18974,18975],{},"gpdf 有外部依赖吗？","\n核心零依赖。",[47,18978,7059],{},[47,18980,18981],{},"go mod graph | grep gpdf"," 只返回一行。",[47,18984,8191],{}," 扩展（HTML→PDF、AES 加密、数字签名、PDF/A）会因 HTML 解析引入 ",[47,18987,18988],{},"golang.org/x/net","，但那是可选，迁移用不到。",[18,18991,18992,18995,18996,18998],{},[30,18993,18994],{},"CGO 呢？gofpdf 是 CGO-free 的，gpdf 呢？","\n同样纯 Go、无 CGO。",[47,18997,7050],{}," 就能交叉编译出静态二进制。这对 distroless 和 Alpine 镜像尤其重要 —— 没有 CGO 工具链，镜像大小能差一半。",[18,19000,19001,19007,19008,19010],{},[30,19002,19003,19004,19006],{},"我现有 gofpdf 代码里到处是 ",[47,19005,1928],{}," 绝对定位。能不重写就迁过来吗？","\n可以包一层 ",[47,19009,14660],{}," 做类似感觉。但如果整个代码围绕光标操作组织，迁到布局引擎模型是心智切换而非语法切换。多数团队发现重写后比原版更短。",[18,19012,19013,19016,19017,19019],{},[30,19014,19015],{},"增值税发票、合规归档怎么办？","\n时间戳和数字签名在 ",[47,19018,8191],{}," 里实现中。有具体需求，开 Issue 提优先级。",[18,19021,19022,19025],{},[30,19023,19024],{},"要是 go-pdf/fpdf 又恢复维护呢？","\n那就多一个选择。gpdf 的赌注不是\"gofpdf 永远归档\"，而是\"基于光标 + 单字节字体 + 无原生 CJK 的架构，谁维护都是死胡同\"。2026 年的 PDF 生成更像搭网页而非驱动绘图仪，API 应该反映这一点。",[14,19027,1242],{"id":1241},[18,19029,19030],{},"gpdf 是 Go 的 PDF 生成库。MIT 协议、零外部依赖、原生 CJK。",[59,19032,19033],{"className":1248,"code":1249,"language":1250,"meta":64,"style":64},[47,19034,19035],{"__ignoreMap":64},[68,19036,19037,19039,19041],{"class":70,"line":71},[68,19038,63],{"class":78},[68,19040,1259],{"class":214},[68,19042,1262],{"class":214},[18,19044,19045,1269,19048],{},[22,19046,6583],{"href":24,"rel":19047},[26],[22,19049,1274],{"href":1272,"rel":19050},[26],[14,19052,1209],{"id":1209},[1111,19054,19055,19061,19066],{},[885,19056,19057,19058],{},"12 栅格在 gpdf 里是怎么工作的 ",[6671,19059,19060],{},"(即将发布)",[885,19062,19063,19064],{},"如何在 gpdf 里嵌入中文字体 ",[6671,19065,19060],{},[885,19067,19068,19071,19072],{},[22,19069,11626],{"href":1272,"rel":19070},[26]," —— 5 分钟上手，含 ",[47,19073,6690],{},[1276,19075,19076],{},"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":64,"searchDepth":82,"depth":82,"links":19078},[19079,19080,19081,19082,19083,19084,19085,19086,19087,19088,19089,19090,19091,19092],{"id":1335,"depth":82,"text":1336},{"id":14400,"depth":82,"text":14401},{"id":14430,"depth":82,"text":14431},{"id":14469,"depth":82,"text":14470},{"id":14783,"depth":82,"text":14784},{"id":15374,"depth":82,"text":15375},{"id":16513,"depth":82,"text":16514},{"id":17170,"depth":82,"text":17171},{"id":18201,"depth":82,"text":18202},{"id":18822,"depth":82,"text":18823},{"id":18918,"depth":82,"text":18918},{"id":2813,"depth":82,"text":2814},{"id":1241,"depth":82,"text":1242},{"id":1209,"depth":82,"text":1209},"2026-04-14","gofpdf 于 2021 年归档，后继 go-pdf/fpdf 也在 2025 年停止。纯 Go、零依赖、原生 CJK 的 gpdf 迁移指南。",{"name":19096,"totalTime":19097,"tools":19098,"steps":19099},"将 Go 项目从 gofpdf 迁移到 gpdf","PT30M",[1299],[19100,19103,19106,19109,19112,19115],{"name":19101,"text":19102},"替换 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":19104,"text":19105},"用 builder 构建文档，替代游标","调用 gpdf.NewDocument 并传入 WithPageSize / WithMargins / WithFont。不再用 SetXY 推动游标，而是用 doc.AddPage() 添加页面，通过 RowBuilder 和 ColBuilder 声明内容。",{"name":19107,"text":19108},"把 Cell / MultiCell 换成声明式的 Text","把 pdf.Cell 和 pdf.MultiCell 换成列里的 c.Text(...)。文本会在列边界自动换行，因此 MultiCell 的尾部标志可以去掉。字号、粗体、颜色都作为 per-text 选项传入。",{"name":19110,"text":19111},"通过 WithFont 注册 CJK 字体","对于中文、日文、韩文，把 pdf.AddUTF8Font 换成在文档构建时传入 gpdf.WithFont(name, ttfBytes)。不必维护 TTF 路径或 UTF-8 标志，子集嵌入由 gpdf 自动完成。",{"name":19113,"text":19114},"用行和列重写表格","去掉手动管理列宽的 Cell 嵌套循环，在 AutoRow 内用 row.Col(n, fn) 构建表格行。12 栅格会替你计算宽度并处理分页。",{"name":19116,"text":19117},"切换输出调用","把 pdf.OutputFileAndClose(path) 换成 doc.Generate() 取得字节，再用 os.WriteFile(path, data, 0o644) 写入。想直接写到 io.Writer 就用 doc.Render(w)。",{},{"title":11619,"description":19094},"zh/blog/001.gofpdf-migration",[8366,2943,1325,1324],"mlCYWU2UEcAx2swleLfU-F_R-F0JN-3obO81vzsn2n4",1776529263666]