¿Cómo añado una fuente TrueType personalizada en gpdf?
Lee los bytes del TTF, regístralo con gpdf.WithFont y referencia el nombre de la familia. Funciona con cualquier TrueType — Inter, Roboto, fuentes de iconos.
La pregunta, en otras palabras
Tengo un archivo .ttf — Inter para la marca, JetBrains Mono para bloques de código, una fuente de iconos para glifos. ¿Cómo lo meto en un documento de gpdf y lo referencio desde una llamada c.Text(...)?
TL;DR
Carga los bytes del TTF. Pasa gpdf.WithFont("TuFamilia", bytes) a NewDocument. Luego referencia "TuFamilia" desde template.FontFamily(...) o configúrala como predeterminada con gpdf.WithDefaultFont.
El nombre de la familia es arbitrario. No tiene relación con la tabla name interna de la fuente — es solo la clave de lookup que gpdf usa al resolver una opción FontFamily. Elige algo corto.
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)
}
}
Coloca Inter-Regular.ttf junto a main.go (descárgalo desde rsms.me/inter). go run main.go. Listo.
Qué hace gpdf con los bytes
Cuando se ejecuta Generate(), gpdf parsea las tablas TrueType (cmap, glyf, loca, hmtx …) en Go puro — sin FreeType, sin CGO. Recorre el texto renderizado, recoge los code points realmente usados y subsetea la tabla de glifos a ese conjunto. El PDF embebe una fuente Type0 / CIDFontType2 que solo lleva los glifos que necesitaste.
Efecto práctico: un Inter-Regular.ttf de 600 KB se convierte en un subset de unos 12 KB dentro del PDF si tu documento usó un par de párrafos. Aterriza la fuente de marca sin inflar el archivo.
Bold e italic necesitan sus propios archivos
Aquí es donde la gente tropieza. gpdf no sintetiza bold ni italic — no hay un paso algorítmico de "hazlo más grueso". Busca un ID de variante construido a partir de los flags de estilo:
Bold() | Italic() | Clave de lookup |
|---|---|---|
| no | no | Inter |
| sí | no | Inter-Bold |
| no | sí | Inter-Italic |
| sí | sí | Inter-BoldItalic |
Si no registraste Inter-Bold, el lookup hace fallback en silencio al Inter plano. El PDF se renderiza, pero todo queda en peso regular. No hay advertencia.
Registra las cuatro:
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),
)
Si una fuente solo trae un peso (muchas de iconos y display lo hacen), no llames template.Bold() ni template.Italic() para esa fuente. Saltarse una variante está bien. Hacer fallback a la variante equivocada es lo que produce los reportes de "¿por qué el bold no es bold?".
Embeber la fuente en el binario
os.ReadFile al arrancar funciona en desarrollo. En producción la fuente forma parte del programa — debería viajar dentro del binario:
import _ "embed"
//go:embed fonts/Inter-Regular.ttf
var interRegular []byte
doc := gpdf.NewDocument(
gpdf.WithFont("Inter", interRegular),
)
go build hornea los bytes dentro. Se acabó el debugging de "¿dónde está el .ttf en la imagen de despliegue?" un viernes por la tarde.
Las fuentes de iconos funcionan igual
Font Awesome, Material Symbols exportadas como TTF, IcoMoon, sets de glifos de marca personalizados — todos son archivos TrueType. Regístralos igual:
icons, _ := os.ReadFile("MaterialSymbols-Regular.ttf")
doc := gpdf.NewDocument(
gpdf.WithFont("Icons", icons),
gpdf.WithDefaultFont("Inter", 11), // predeterminado del cuerpo
)
// Dentro de una columna:
c.Text("", template.FontFamily("Icons"), template.FontSize(20)) // icono "home"
El escape Unicode es el que diga la documentación de la fuente. A gpdf no le importa que el glifo sea un icono — para él es un code point, y lo subsetea igual que las letras.
Errores comunes
- Typo en el nombre de la familia en la llamada.
template.FontFamily("Intr")hace fallback al predeterminado del documento. Sin error, sin warning. Si el texto de pronto parece Helvetica, este es el primer lugar a mirar. - No embeber con
//go:embeden contenedores. Un contexto Docker recortado deja caer el.ttf, el fallback de runtime se activa y te enteras por un email del cliente. Embebe. - Usar el nombre PostScript de la fuente como familia. "Inter-Regular" es el nombre PostScript. Si lo pasas a
WithFont, el lookup de bold intenta encontrar "Inter-Regular-Bold" — que no existe. Elige una raíz limpia ("Inter") y deja que el sufijo de variante maneje los estilos.
Recetas relacionadas
- ¿Cómo embebo una fuente japonesa en gpdf? — el mismo mecanismo, con notas específicas de CJK
- ¿Cómo uso bold e italic juntos? — el resolver de variantes en detalle
- ¿Por qué mi PDF muestra cuadritos vacíos? — qué pasa cuando la fuente no cubre los code points
Prueba gpdf
gpdf es una librería Go para generar PDFs. Licencia MIT, cero dependencias externas, manejo de TrueType en Go puro.
go get github.com/gpdf-dev/gpdf