Como incorporar uma fonte japonesa no gpdf?
Passe os bytes do TTF para gpdf.WithFont ao construir o documento. Três linhas, subset embedding automático e sem CGO.
A pergunta, em outras palavras
Como renderizar texto japonês (ou CJK em geral) em um PDF gerado com o gpdf — sem a cerimônia do AddUTF8Font, sem CGO, sem embutir cinco megabytes de fonte em cada documento?
Resposta rápida
Leia os bytes do TTF. Passe gpdf.WithFont("NotoSansJP", fontBytes) para o NewDocument. Opcionalmente marque-a como padrão. Três linhas de setup, e o gpdf embute automaticamente só os glifos que você realmente usou — não os 5 MB inteiros.
O exemplo completo
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("NotoSansJP-Regular.ttf")
if err != nil {
log.Fatal(err)
}
doc := gpdf.NewDocument(
gpdf.WithPageSize(gpdf.A4),
gpdf.WithMargins(document.UniformEdges(document.Mm(20))),
gpdf.WithFont("NotoSansJP", font),
gpdf.WithDefaultFont("NotoSansJP", 12),
)
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("日本語 PDF、これだけ。")
})
})
data, err := doc.Generate()
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile("hello.pdf", data, 0o644); err != nil {
log.Fatal(err)
}
}
Baixe NotoSansJP-Regular.ttf no Google Fonts, coloque ao lado de main.go e rode go run main.go. Sai um PDF de uma página com texto japonês.
O que essas três linhas fazem por baixo
Duas coisas acontecem nos bastidores, e nenhuma delas exige sua intervenção.
Subset embedding. A Noto Sans JP traz cerca de 17.000 glifos — o peso Regular ocupa uns 5 MB em disco. Se você embutisse a fonte inteira, um recibo com quatro linhas de japonês custaria cinco megabytes em dados tipográficos. O gpdf percorre o texto renderizado, identifica quais IDs de glifo você usou e grava apenas esse subconjunto no PDF. Uma nota curta costuma acabar com 20–40 KB de dados de fonte, não 5 MB.
O gofpdf também sabia fazer subset, mas o AddUTF8Font exigia caminho de arquivo e flag UTF-8, com o carregamento ocorrendo enquanto o cursor se movia; trocar de fonte no meio do documento ficava desconfortável. O gpdf registra a fonte uma vez na construção do documento; dali em diante cada c.Text só a referencia pelo nome de família. Não há preparação por chamada.
Sem CGO. Isso pesa mais do que parece. Em outros ecossistemas o tratamento de fontes passa por FreeType ou HarfBuzz — o que significa dependência em C, caches de build invalidando diferente, camadas extras nas imagens Docker e cross-compile de macOS para linux/arm64 deixando de ser trivial. O gpdf faz o parsing das tabelas TrueType em Go puro. go build continua produzindo um binário estático. Empacote num container distroless o binário Go e o TTF; é só disso que você precisa.
Variantes Bold e Italic
As famílias Noto japonesas entregam um arquivo por peso. Para usar negrito, registre o TTF Bold com o sufixo -Bold:
reg, _ := os.ReadFile("NotoSansJP-Regular.ttf")
bold, _ := os.ReadFile("NotoSansJP-Bold.ttf")
doc := gpdf.NewDocument(
gpdf.WithFont("NotoSansJP", reg),
gpdf.WithFont("NotoSansJP-Bold", bold),
gpdf.WithDefaultFont("NotoSansJP", 12),
)
Agora template.Bold() pega a variante -Bold. Mesma convenção para -Italic e -BoldItalic. Sem registrar a variante, o negrito cai num peso sintetizado — legível na tela, mas não tipograficamente honesto. Em faturas de produção (NFSe, DANFE), registre o peso real.
Múltiplos idiomas CJK no mesmo documento
Pode registrar quantas famílias quiser — o gpdf gerencia cada uma de forma independente. Alterne por texto com template.FontFamily(...):
jp, _ := os.ReadFile("NotoSansJP-Regular.ttf")
sc, _ := os.ReadFile("NotoSansSC-Regular.ttf")
kr, _ := os.ReadFile("NotoSansKR-Regular.ttf")
doc := gpdf.NewDocument(
gpdf.WithFont("NotoSansJP", jp),
gpdf.WithFont("NotoSansSC", sc),
gpdf.WithFont("NotoSansKR", kr),
gpdf.WithDefaultFont("NotoSansJP", 12),
)
page.AutoRow(func(r *template.RowBuilder) {
r.Col(4, func(c *template.ColBuilder) {
c.Text("日本語")
})
r.Col(4, func(c *template.ColBuilder) {
c.Text("中文", template.FontFamily("NotoSansSC"))
})
r.Col(4, func(c *template.ColBuilder) {
c.Text("한국어", template.FontFamily("NotoSansKR"))
})
})
A unificação Han faz com que o japonês e o chinês simplificado compartilhem codepoints Unicode, mas os glifos são desenhados diferente. O mesmo codepoint renderiza com formato diferente dependendo da fonte — escolher fonte não é estética, é correção. Se você gera documentos para ambos os mercados, precisa das duas famílias registradas.
A armadilha do tofu
Se você escreve japonês mas esquece o WithFont, o gpdf cai nas fontes Base-14 do PDF — nenhuma delas cobre o range CJK. Os caracteres saem como retângulos vazios, o que o mundo Unicode chama de "caixas tofu":
□□□□□、□□。
Se ver isso na saída, a causa é sempre a mesma: não registrou uma fonte CJK, ou está escrevendo numa família que não inclui esses glifos. A correção também: adicione WithFont e use WithDefaultFont, ou passe template.FontFamily no c.Text.
Leitura relacionada
- gofpdf foi arquivado. Como migrar para o gpdf. — o mapa completo se você vem do
pdf.AddUTF8Font - Go PDF Library Showdown 2026 — como o gpdf se compara a gofpdf, gopdf, Maroto e unipdf em CJK
- Guia de fontes — referência completa de
WithFonte regras de nomeação de variantes
Experimente o gpdf
O gpdf é uma biblioteca Go para gerar PDFs. Licença MIT, zero dependências externas, suporte CJK nativo.
go get github.com/gpdf-dev/gpdf