Como adiciono uma fonte TrueType personalizada ao gpdf?
Carregue os bytes do TTF, registre com gpdf.WithFont e referencie o nome da família. Funciona com qualquer TrueType — Inter, Roboto, fontes de ícones.
A pergunta, em outras palavras
Tenho um arquivo .ttf — Inter para a marca, JetBrains Mono para blocos de código, uma fonte de ícones para glifos. Como coloco isso num documento gpdf e referencio numa chamada c.Text(...)?
TL;DR
Carregue os bytes do TTF. Passe gpdf.WithFont("SuaFamilia", bytes) para NewDocument. Depois referencie "SuaFamilia" em template.FontFamily(...) ou defina como padrão com gpdf.WithDefaultFont.
O nome da família é arbitrário. Não tem relação com a tabela name interna da fonte — é só a chave de lookup que o gpdf usa ao resolver uma opção FontFamily. Escolha algo curto.
Código funcional
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)
}
}
Coloque Inter-Regular.ttf ao lado de main.go (baixe em rsms.me/inter). go run main.go. Pronto.
O que o gpdf faz com os bytes
Quando Generate() roda, o gpdf parseia as tabelas TrueType (cmap, glyf, loca, hmtx …) em Go puro — sem FreeType, sem CGO. Ele percorre o texto renderizado, coleta os code points realmente usados e subsetiza a tabela de glifos para esse conjunto. O PDF embute uma fonte Type0 / CIDFontType2 carregando apenas os glifos necessários.
Efeito prático: um Inter-Regular.ttf de 600 KB vira um subset de cerca de 12 KB dentro do PDF se o documento usou só alguns parágrafos. A fonte de marca entra sem inflar o arquivo.
Bold e italic precisam dos próprios arquivos
Aqui é onde as pessoas tropeçam. O gpdf não sintetiza bold nem italic — não há um passo algorítmico de "deixa mais grosso". Ele busca um ID de variante montado a partir das flags de estilo:
Bold() | Italic() | Chave de lookup |
|---|---|---|
| não | não | Inter |
| sim | não | Inter-Bold |
| não | sim | Inter-Italic |
| sim | sim | Inter-BoldItalic |
Se você não registrou Inter-Bold, o lookup faz fallback silencioso para o Inter puro. O PDF é renderizado, mas tudo fica em peso regular. Sem aviso.
Registre as quatro:
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),
)
Se uma fonte só vem em um peso (muitas de ícones e display são assim), não chame template.Bold() nem template.Italic() para ela. Pular uma variante está ok. Fazer fallback para a variante errada é o que gera os reportes de "por que o bold não está em bold?".
Embeba a fonte no binário
os.ReadFile no startup funciona em desenvolvimento. Em produção a fonte é parte do programa — deveria viajar dentro do binário:
import _ "embed"
//go:embed fonts/Inter-Regular.ttf
var interRegular []byte
doc := gpdf.NewDocument(
gpdf.WithFont("Inter", interRegular),
)
go build cozinha os bytes ali dentro. Adeus debugging de "onde está o .ttf na imagem de deploy?" numa sexta à tarde.
Fontes de ícones funcionam do mesmo jeito
Font Awesome, Material Symbols exportada como TTF, IcoMoon, sets de glifos de marca customizados — são todos arquivos TrueType. Registre da mesma forma:
icons, _ := os.ReadFile("MaterialSymbols-Regular.ttf")
doc := gpdf.NewDocument(
gpdf.WithFont("Icons", icons),
gpdf.WithDefaultFont("Inter", 11), // padrão do corpo
)
// Dentro de uma coluna:
c.Text("", template.FontFamily("Icons"), template.FontSize(20)) // ícone "home"
O escape Unicode é o que a documentação da fonte disser. Pro gpdf não importa que o glifo seja um ícone — pra ele é um code point, e ele subsetiza igual às letras.
Erros comuns
- Typo no nome da família na chamada.
template.FontFamily("Intr")faz fallback para o padrão do documento. Sem erro, sem warning. Se o texto de repente parece Helvetica, é o primeiro lugar a checar. - Não embeber com
//go:embedem containers. Um contexto Docker enxuto solta o.ttf, o fallback de runtime entra, e você descobre por email do cliente. Embeba. - Usar o nome PostScript da fonte como família. "Inter-Regular" é o nome PostScript. Passe isso para
WithFonte o lookup de bold tenta achar "Inter-Regular-Bold" — que não existe. Escolha uma raiz limpa ("Inter") e deixe o sufixo de variante cuidar dos estilos.
Receitas relacionadas
- Como embuto uma fonte japonesa no gpdf? — mesmo mecanismo, com notas específicas de CJK
- Como uso bold e italic juntos? — o resolver de variantes em detalhe
- Por que meu PDF mostra quadradinhos vazios? — o que acontece quando a fonte não cobre os code points
Experimente o gpdf
O gpdf é uma biblioteca Go para gerar PDFs. Licença MIT, zero dependências externas, manipulação TrueType em Go puro.
go get github.com/gpdf-dev/gpdf