Por que meu PDF mostra retângulos (tofu) no lugar de japonês?
Retângulos vazios em vez de caracteres japoneses significam que o PDF não encontrou glifos para esses code points. Quatro causas e como corrigir.
A pergunta, em outras palavras
Escrevi texto japonês com gpdf e o PDF resultante mostra retângulos vazios onde os caracteres deveriam estar. O que é isso e como faço para que os glifos japoneses reais apareçam no arquivo?
A resposta rápida
Isso é tofu — o visualizador de PDF desenha um retângulo de marcador porque a fonte embutida no PDF não tem glifo para o code point Unicode que você pediu. Quatro coisas causam isso, e uma é muito mais comum que o restante.
Por ordem de frequência:
- Nenhuma fonte CJK registrada.
gpdf.NewDocumentnão tem nenhuma chamada aWithFont, então o documento recai nas fontes Base-14 do PDF (Helvetica, Times, Courier). Nenhuma cobre U+3040–U+9FFF. - Fonte CJK registrada, mas o nome da família em
c.Textestá errado.WithFont("NotoSansJP", ...)está configurado, mastemplate.FontFamily("Arial")no texto força o gpdf a procurar japonês em uma fonte latina. - O arquivo de fonte não contém glifos CJK. O TTF em disco é um subset latino (
NotoSans-Regular.ttfem vez deNotoSansJP-Regular.ttf). O nome parece certo, a cobertura está vazia. - Os bytes foram corrompidos antes do gpdf recebê-los. A string foi decodificada como Shift-JIS ou Latin-1 em algum ponto anterior, e os code points já não são japoneses. Se você vê
縺ゅ→縺em vez de retângulos, é essa.
A correção canônica para a causa #1
Nove em cada dez vezes é isto:
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("こんにちは、世界。")
})
})
data, err := doc.Generate()
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile("hello.pdf", data, 0o644); err != nil {
log.Fatal(err)
}
}
Duas linhas registram a fonte e a definem como padrão. Sem CGO. Sem a burocracia do AddUTF8Font. Se você estava vendo □□□□□、□□。 e rodar este programa com um NotoSansJP-Regular.ttf real ao lado, os glifos reais aparecem.
Baixe NotoSansJP-Regular.ttf no Google Fonts.
Como saber qual causa é a sua
A maior parte é olhar três lugares: onde você constrói o documento, onde você escreve o texto e o próprio arquivo TTF.
Se a saída são □□□ (retângulos idênticos), é causa 1, 2 ou 3. O PDF embutiu uma fonte, mas ela não tem os glifos. Abra o PDF no Acrobat, vá em Arquivo → Propriedades → Fontes e veja quais fontes foram realmente embutidas. Se a lista só tem Helvetica / Times / Courier, causa 1. Se NotoSansJP está listada e ainda há retângulos, causa 2 ou 3.
Se a saída é 縺ゅ→縺 ou ã"ã‚"ã«ã¡ã¯ (latim embaralhado), é causa 4. Sua string japonesa foi recodificada antes de chegar ao gpdf. Culpado mais comum: um CSV salvo como Shift-JIS pelo Excel e lido com os.ReadFile como se fosse UTF-8, ou um endpoint HTTP que não declarou charset=utf-8. Conserte o decodificador, não o PDF.
Saída mista — alguns caracteres renderizam, outros viram retângulos — significa cobertura parcial da fonte. Uma fonte rotulada como "japonesa" pode incluir hiragana e katakana mas pular kanjis incomuns como 鬱 ou 龠. Troque para Noto Sans JP (cobre JIS X 0213) ou Source Han Sans JP se isso acontecer.
Causa 2 em detalhe: fonte certa, nome de família errado
Essa é traiçoeira porque a fonte está embutida — simplesmente não é usada. Reprodução mínima:
doc := gpdf.NewDocument(
gpdf.WithFont("NotoSansJP", font),
// Sem WithDefaultFont.
)
page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Text("こんにちは") // Usa a fonte padrão: Helvetica.
})
})
Correção: adicione gpdf.WithDefaultFont("NotoSansJP", 12) ao NewDocument, ou passe template.FontFamily("NotoSansJP") em cada c.Text que precisa de japonês. O nome de família em WithFont e o em c.Text devem bater exatamente, incluindo caixa. Para o gpdf, NotoSansJP e notosansjp são duas fontes diferentes.
Causa 3 em detalhe: o arquivo TTF errado
NotoSans-Regular.ttf e NotoSansJP-Regular.ttf são arquivos diferentes. O primeiro é uma fonte latina sem nenhuma cobertura CJK. O segundo é a versão japonesa, com cerca de 17.000 glifos. Eles ficam quase idênticos em um ls, e o autocomplete do editor pega o errado com facilidade.
O gpdf não valida cobertura de glifos no registro. Se você entrega bytes, ele confia. A falha só aparece como tofu no momento do render.
Maneira rápida de conferir:
- macOS:
Font Book→ duplo-clique no arquivo → a prévia mostra uma grade de glifos - Linux:
otfinfo -u NotoSans-Regular.ttflista a cobertura Unicode - Multiplataforma: fontTools —
ttx -t cmap NotoSans-Regular.ttfdespeja a tabela cmap como XML
Se U+3042 (あ) não está na lista, você está com o subset latino.
Causa 4 em detalhe: corrupção de encoding
Essa na verdade não envolve o gpdf. A string entregue ao c.Text já tinha os bytes errados. Imprima antes de renderizar:
text := loadLabelFromSomewhere()
fmt.Printf("%q\n", text) // Mostra as runas reais
c.Text(text)
Se fmt.Printf("%q\n", text) imprime "縺ゅ→縺" em vez de "あいうえ", a corrupção aconteceu antes. O gpdf não pode consertar — ache o ponto onde o UTF-8 foi decodificado errado.
Culpados habituais lá na frente:
- Ler um CSV exportado do Excel (Windows Shift-JIS) com
os.ReadFilee converter direto emstring - Uma coluna de banco declarada
latin1ouutf8mb3(nãoutf8mb4) já guardando mojibake - Uma resposta HTTP sem
Content-Type: application/json; charset=utf-8e um cliente que chutou Latin-1
Um caso de borda que vale mencionar
O gpdf faz subset silenciosamente. O subset congela no instante de Generate(). Se durante a construção do documento você renderiza こんにちは e depois 鬱陶しい, o segundo também entra no subset corretamente. Mas se você gerar o PDF, abrir no Acrobat e digitar um kanji que não estava no texto original, aquele caractere virá como tofu — aquele glifo nunca entrou no subset. Não edite o PDF depois; rode o programa Go de novo e chame Generate().
Receitas relacionadas
- Como incorporar uma fonte japonesa no gpdf? — guia completo do
WithFontcom variantes bold/italic e documentos multi-CJK - Como usar Noto Sans JP com o gpdf? — qual arquivo do Noto escolher e como o
go:embedsimplifica a distribuição - Guia definitivo de PDFs em japonês com Go (2026) — guia longo cobrindo fontes, texto vertical, ruby e layout específico de JP
Experimente o gpdf
gpdf é uma biblioteca Go para gerar PDFs. Licença MIT, zero dependências externas, suporte nativo a CJK.
go get github.com/gpdf-dev/gpdf