如何在 gpdf 中添加自定义 TrueType 字体?
读取 TTF 字节,用 gpdf.WithFont 注册字族,再按名称引用即可。Inter、Roboto、图标字体、品牌字体都同样适用。
换个说法
我有一个 .ttf 文件 — 品牌用的 Inter、代码块用的 JetBrains Mono、图标用的字体。怎么把它放进 gpdf 文档,并在 c.Text(...) 里按名字引用?
速答
读取 TTF 字节。把 gpdf.WithFont("你的字族名", bytes) 传给 NewDocument。然后用 template.FontFamily(...) 引用 "你的字族名",或用 gpdf.WithDefaultFont 设为默认。
字族名是 任意字符串。它和字体内部的 name 表毫无关系 — 只是 gpdf 解析 FontFamily 选项时使用的查找 key。取个短的。
可运行代码
package main
import (
"log"
"os"
"github.com/gpdf-dev/gpdf"
"github.com/gpdf-dev/gpdf/document"
"github.com/gpdf-dev/gpdf/template"
)
func main() {
regular, err := os.ReadFile("Inter-Regular.ttf")
if err != nil {
log.Fatal(err)
}
doc := gpdf.NewDocument(
gpdf.WithPageSize(gpdf.A4),
gpdf.WithMargins(document.UniformEdges(document.Mm(20))),
gpdf.WithFont("Inter", regular),
gpdf.WithDefaultFont("Inter", 11),
)
page := doc.AddPage()
page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Text("Quarterly Report", template.FontSize(28))
c.Text("Generated with gpdf and Inter.")
})
})
data, err := doc.Generate()
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile("report.pdf", data, 0o644); err != nil {
log.Fatal(err)
}
}
把 Inter-Regular.ttf 放在 main.go 旁边 (从 rsms.me/inter 下载)。go run main.go。完事。
gpdf 对字节做了什么
Generate() 触发后,gpdf 用纯 Go 解析 TrueType 表 (cmap, glyf, loca, hmtx …) — 不依赖 FreeType,不需要 CGO。它扫描渲染文本,收集实际用到的码点,然后对字形表做子集化,只保留这些码点所需的字形。PDF 内嵌入一个 Type0 / CIDFontType2 字体,仅承载真正需要的字形。
实际效果: 600 KB 的 Inter-Regular.ttf,如果文档只用了几段,PDF 内的字体子集大约只有 12 KB。能用上品牌字体而不让文件变大。
Bold 和 Italic 需要各自的文件
这一点最容易踩坑。gpdf 不会合成 bold 或 italic — 没有「让笔画变粗」这种算法。它根据样式标志拼接出变体 ID 来查找:
Bold() | Italic() | 查找 key |
|---|---|---|
| 否 | 否 | Inter |
| 是 | 否 | Inter-Bold |
| 否 | 是 | Inter-Italic |
| 是 | 是 | Inter-BoldItalic |
如果没注册 Inter-Bold,查找会静默回退到普通的 Inter。PDF 仍然会生成,但所有「应该是粗体」的部分都是常规字重。没有任何警告。
四个变体一起注册:
regular, _ := os.ReadFile("Inter-Regular.ttf")
bold, _ := os.ReadFile("Inter-Bold.ttf")
italic, _ := os.ReadFile("Inter-Italic.ttf")
boldItalic, _ := os.ReadFile("Inter-BoldItalic.ttf")
doc := gpdf.NewDocument(
gpdf.WithFont("Inter", regular),
gpdf.WithFont("Inter-Bold", bold),
gpdf.WithFont("Inter-Italic", italic),
gpdf.WithFont("Inter-BoldItalic", boldItalic),
gpdf.WithDefaultFont("Inter", 11),
)
如果某个字体只有一种字重 (很多图标字体和展示字体都是这样),就不要对它调用 template.Bold() 或 template.Italic()。少一个变体没关系。错误地回退到另一个变体才是 bug 报告的根源。
把字体打进二进制
启动时 os.ReadFile 在开发阶段没问题。生产环境里,字体是程序的一部分,应该跟着二进制走:
import _ "embed"
//go:embed fonts/Inter-Regular.ttf
var interRegular []byte
doc := gpdf.NewDocument(
gpdf.WithFont("Inter", interRegular),
)
go build 把字节直接编译进二进制。再也不会在周五傍晚追查「部署镜像里的 .ttf 跑哪去了」。
图标字体一样的玩法
Font Awesome、Material Symbols 的 TTF 版、IcoMoon、企业自定义字形集 — 都只是 TrueType 文件。注册方式相同:
icons, _ := os.ReadFile("MaterialSymbols-Regular.ttf")
doc := gpdf.NewDocument(
gpdf.WithFont("Icons", icons),
gpdf.WithDefaultFont("Inter", 11), // 正文默认
)
// 在某列里:
c.Text("", template.FontFamily("Icons"), template.FontSize(20)) // "home" 图标
Unicode 转义码是字体文档里写的值。gpdf 不在乎那个字形是不是图标 — 在它眼里就是个码点,按字母同样的方式做子集化。
常见错误
- 调用处字族名打错。
template.FontFamily("Intr")静默回退到默认字族。无报错无警告。文字突然变成 Helvetica?先查这个。 - 容器化时不用
//go:embed。 精简过的 Docker 上下文丢掉了.ttf,运行时静默回退,最后从用户邮件里得知问题。打进二进制。 - 把字体的 PostScript 名当字族名用。 "Inter-Regular" 是 PostScript 名。用它注册
WithFont,bold 查找会去找 "Inter-Regular-Bold" — 根本不存在。取干净的根字族名 ("Inter"),让变体后缀去处理样式。
相关菜谱
- 如何在 gpdf 中嵌入日文字体? — 同样的机制,附带 CJK 注意事项
- 如何同时使用 bold 和 italic? — 变体解析的细节
- 为什么我的 PDF 显示豆腐方块? — 字体不覆盖码点时会发生什么
试试 gpdf
gpdf 是一个 Go PDF 生成库。MIT 协议、零外部依赖、TrueType 处理纯 Go 实现。
go get github.com/gpdf-dev/gpdf