gpdf にカスタム TrueType フォントを追加するには?
TTF をバイト列で読み込んで gpdf.WithFont でファミリ名を登録する。Inter からアイコンフォントまで、どんな TrueType でも同じ手順で動く。
言い換えると
.ttf がある。ブランドの Inter、コードブロック用の JetBrains Mono、アイコン用のグリフフォント。これを gpdf のドキュメントに取り込んで、c.Text(...) から名前で呼びたい。どうやる?
即答
TTF のバイト列を読む。gpdf.WithFont("好きなファミリ名", bytes) を NewDocument に渡す。あとは template.FontFamily(...) でその名前を指定するか、gpdf.WithDefaultFont で既定にしてしまえばいい。
ファミリ名は 任意の文字列。フォント内部の name テーブルとは無関係で、gpdf が FontFamily を解決するときに使うルックアップキーに過ぎない。短めの名前にしておく。
動くコード
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 は TrueType のテーブル (cmap, glyf, loca, hmtx …) を 純 Go でパースする。FreeType も CGO もない。レンダリング対象のテキストを走査し、実際に使われたコードポイントを集め、そのコードポイント分だけにグリフテーブルをサブセット化 する。PDF には Type0 / CIDFontType2 フォントとして、必要なグリフだけが埋め込まれる。
実際的な効果: 600 KB の Inter-Regular.ttf を 1〜2 段落で使っただけなら、PDF 内のフォントサブセットは 12 KB 程度になる。ブランドフォントを使ってもファイルが膨れない。
Bold / Italic は別ファイルが必要
ここが踏み抜きやすい。gpdf は bold や italic を合成しない。「線を太くする」ようなアルゴリズム処理はない。代わりに、スタイルフラグからバリアント ID を組み立ててルックアップする:
Bold() | Italic() | ルックアップキー |
|---|---|---|
| なし | なし | Inter |
| あり | なし | Inter-Bold |
| なし | あり | Inter-Italic |
| あり | あり | Inter-BoldItalic |
Inter-Bold を登録していなければ、ルックアップは黙って素の Inter にフォールバックする。PDF は出力されるが、太字のはずの箇所がレギュラーのまま。警告は出ない。
4 種類とも登録する:
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),
)
ウェイトが 1 種類しかないフォント (アイコンフォントやディスプレイフォントに多い) なら、そのフォントに対しては template.Bold() や template.Italic() を使わない。バリアントを持たないこと自体は問題ない。間違ったバリアントにフォールバックするのが「太字が太字にならない」バグ報告の正体。
バイナリにフォントを同梱する
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 はそれがアイコンかどうか気にしない — 単なるコードポイントとして文字と同じようにサブセット化する。
商用日本語フォント (モリサワ・フォントワークス) を使う場合
商用の日本語フォントをサーバーサイドで PDF に埋め込む用途は、ライセンスで明確に許諾されているか必ず確認する。サブセット埋め込みでも「PDF への埋め込み許可」と「サーバーでの動的生成許可」は別条項なことが多い。ライセンス確認なしに業務 PDF に埋め込むと、後から億単位の請求につながり得る。
無償で商用利用も可能な日本語 TTF が必要なら Noto Sans JP や IPAex Gothic を選ぶのが安全。
よくあるミス
- 呼び出し側でファミリ名のタイポ。
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