Todas as publicações

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ãonãoInter
simnãoInter-Bold
nãosimInter-Italic
simsimInter-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:embed em 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 WithFont e 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

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

⭐ Star no GitHub · Ler a documentação