如何在 gpdf 中使用思源黑体 JP(Source Han Sans JP)?
从 Adobe 的 GitHub 发布页下载 TTF 版本的思源黑体 JP,用 gpdf.WithFont 注册。七个字重,SIL OFL,与 Noto Sans JP 同源字形。
这个问题的另一种表达
你想在 gpdf 文档里用 Source Han Sans JP(思源黑体 JP) —— 2014 年 Adobe 与 Google 合作发布的泛 CJK 无衬线字体的 Adobe 品牌。也许你团队把字体锚定在 GitHub 的 release tag 上以保证可复现,也许你接手的设计系统多年前就标准化在思源黑体上,也许你就是喜欢 Adobe 的发布节奏。理由随意。下载之前值得先搞清楚三件事:该拿哪个文件、和 Noto Sans JP 究竟什么关系、gpdf 能读哪种格式。
速答
从 adobe-fonts/source-han-sans 发布页下载 SourceHanSansJP-Regular.ttf(TTF 包,不是 OTF),用 gpdf.WithFont("SourceHanSansJP", bytes) 注册并设为默认。Source Han Sans JP 与 Noto Sans JP 共用同一套字形;如果你对 Adobe 的发布流程没什么特别要求,Noto Sans JP 的获取路径更直接。
完整示例
package main
import (
"log"
"os"
"github.com/gpdf-dev/gpdf"
"github.com/gpdf-dev/gpdf/document"
"github.com/gpdf-dev/gpdf/template"
)
func main() {
font, err := os.ReadFile("SourceHanSansJP-Regular.ttf")
if err != nil {
log.Fatal(err)
}
doc := gpdf.NewDocument(
gpdf.WithPageSize(gpdf.A4),
gpdf.WithMargins(document.UniformEdges(document.Mm(20))),
gpdf.WithFont("SourceHanSansJP", font),
gpdf.WithDefaultFont("SourceHanSansJP", 11),
)
page := doc.AddPage()
page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Text("報告書", template.FontSize(24), template.Bold())
c.Text("Source Han Sans JP — Adobe 发布的免费 CJK 字体。")
})
})
data, err := doc.Generate()
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile("report.pdf", data, 0o644); err != nil {
log.Fatal(err)
}
}
把 TTF 放到 main.go 旁边,go run main.go,一页带日文的 PDF 就会出现在 report.pdf。
Source Han Sans JP 就是 Noto Sans CJK JP
最能帮你省几个小时查资料的事实只有一条:Source Han Sans 和 Noto Sans CJK 是同一套字体。字形设计、度量表、字符集覆盖都由 Adobe 完成,Google 以 Noto 品牌进行并行分发。两者都在 2014-07-15 同日发布。轮廓数据、hmtx 表、JIS X 0213 / Adobe-Japan1-6 的覆盖面 —— 比特级完全一致。Adobe 升版后,相应的字形变化会在数周内传到 Noto。
差异都在品牌和打包层面:
| Source Han Sans JP | Noto Sans JP | |
|---|---|---|
| 发行方 | Adobe | |
| 权威源 | adobe-fonts/source-han-sans | notofonts.github.io + Google Fonts |
| 主格式 | OTF(CFF 轮廓) | TTF(static)+ variable |
| 发布模型 | GitHub release tag 手动版本化 | Google Fonts CDN + git 仓库 |
| 语言打包 | 分语言 TTF + 泛 CJK OTC | 仅 JP |
如果你的团队要把字体锁在 Adobe 的 GitHub tag 上、已经在内部镜像 github.com/adobe-fonts、或者别的流水线也要用泛 CJK OTC,那就用 Source Han Sans JP。否则直接用能拿到 TTF 的 Noto Sans JP 更省事。
为什么非 TTF 不可
Adobe 对 Source Han Sans 的默认格式是 .otf,准确说是基于 CFF 的 OpenType。而 gpdf 的字体解析器只有一个文件 pdf/font/truetype.go,处理 glyf、loca、cmap、hmtx 和复合字形,不读 CFF / CFF2 轮廓。把 CFF 版 .otf 丢给它,会在文档构造阶段就抛出解析错误,还没到渲染就挂了。
Adobe 的发布页同时提供 OTF 和 TTF,要拿 TTF 包。如果某个点发行版恰好没出 TTF(偶尔发生),两条干净的备选:
- 换到 Noto Sans JP。 Google Fonts 直接提供静态 TTF,字形数据完全一致,免去转换。
- 一次性转换,提交结果。 用
fonttools的otf2ttf转一次就好,把产物提交到仓库或内部制品服务器,转换过程永远不进构建流水线。
别在构建时做转换。字体转换工具跨版本行为会变,hmtx 稍有差别就会让换行位置在一次 pip install -U 之后偷偷挪位。
七个字重
Source Han Sans JP 以单独 TTF 形式发布 ExtraLight 到 Heavy 共 7 个字重:
SourceHanSansJP-ExtraLight.ttf
SourceHanSansJP-Light.ttf
SourceHanSansJP-Normal.ttf
SourceHanSansJP-Regular.ttf
SourceHanSansJP-Medium.ttf
SourceHanSansJP-Bold.ttf
SourceHanSansJP-Heavy.ttf
大多数业务文档 Regular + Bold 两个就够:
reg, _ := os.ReadFile("SourceHanSansJP-Regular.ttf")
bold, _ := os.ReadFile("SourceHanSansJP-Bold.ttf")
doc := gpdf.NewDocument(
gpdf.WithFont("SourceHanSansJP", reg),
gpdf.WithFont("SourceHanSansJP-Bold", bold),
gpdf.WithDefaultFont("SourceHanSansJP", 11),
)
-Bold 后缀是 template.Bold() 和真 Bold TTF 之间的契约。不注册就用 template.Bold(),会退化为在 Regular 字形上叠 0.4 pt 描边的合成粗体,小标题还行,到大字号就明显比真 Bold 单薄。
CJK 字体按惯例不做斜体版,Source Han Sans JP 也没有。日文需要斜体强调时,换字重或换颜色更自然;对汉字做倾斜变换看起来不是强调,是坏掉了。
泛 CJK、JP 单语、还是 Super OTC
Adobe 以多种粒度发布 Source Han Sans,对 Go PDF 生成不能混用:
- SourceHanSans.ttc(Super OTC)—— 把全部 CJK 语言塞进一个 20 MB+ 的 TrueType Collection。gpdf 不会在
.ttc内部按 face index 取脸,需要先用fonttools切出 JP face 再作为 TTF 注册。通常不用这个。 - 地区统一 OTF(如
SourceHanSans-Regular.otf)—— 泛 CJK 共存,CFF 轮廓,gpdf 读不了。 - 分语言 TTF(
SourceHanSansJP-Regular.ttf)—— 仅 JP,glyf轮廓。就要这个。
如果你的文档同页混有日文和韩文 / 中文,不要依赖泛 CJK OTF,而是分别注册各语言 family:SourceHanSansJP 加 SourceHanSansKR,在语系切换处用 template.FontFamily 明确指定。泛 CJK OTF 把汉字做了 Han unification 统一到一个形上,会让日文正文里本该是日文字形的汉字变成中文字形。
什么时候选思源黑体而非 Noto
同一套轮廓,不同的发布渠道。选 Source Han Sans JP 合理的场景:
- 运维团队偏好把字体锚定到 Adobe 的 GitHub release tag(可复现、可审计)
- 公司内部已经镜像
github.com/adobe-fonts(严格制品政策的企业里常见) - 流水线别的环节也需要泛 CJK OTC 包(DTP 对接、品牌系统在 Adobe 命名下统一)
选 Noto Sans JP 更合适的场景:
- 想要最短路径拿到 TTF(
fonts.google.com/noto/specimen/Noto+Sans+JP→ zip → 搞定) - 不想在构建里做 OTF → TTF 转换
- 项目已经通过既有流程拉其他 Google Fonts
渲染结果一样。判断依据是运维层面的 —— 文件在哪里、怎么版本化、团队熟哪个 —— 而不是美感。
延伸阅读
- 如何在 gpdf 中使用 Noto Sans JP? —— 同样的字形,开箱即为 TTF
- 如何在 gpdf 中嵌入日文字体? —— CJK 嵌入的通用配方
- 如何在 gpdf 中使用 IPAex Gothic? —— 面向日本官方场合的 IPA 许可替代
- 为什么 gpdf 生成的 PDF 中日文显示为方块? —— 字形缺失排查
试试 gpdf
gpdf 是一个 Go PDF 生成库。MIT 协议、零外部依赖、原生支持 CJK。
go get github.com/gpdf-dev/gpdf