Todas as publicações

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.

por gpdf team

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

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

⭐ Star no GitHub · Ler a documentação