¿Cómo incrusto una fuente japonesa en gpdf?
Pasa los bytes del TTF a gpdf.WithFont al construir el documento. Tres líneas, subset embedding automático y sin CGO.
La pregunta, en otras palabras
¿Cómo se renderiza texto japonés (o CJK en general) en un PDF creado con gpdf — sin la ceremonia de AddUTF8Font, sin CGO, sin incrustar una fuente de cinco megabytes en cada documento?
Respuesta rápida
Lee los bytes del TTF. Pasa gpdf.WithFont("NotoSansJP", fontBytes) a NewDocument. Si quieres, márcala como predeterminada. Tres líneas de configuración, y gpdf incrusta automáticamente sólo los glifos que realmente usaste — no los 5 MB enteros.
El ejemplo 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)
}
}
Descarga NotoSansJP-Regular.ttf desde Google Fonts, ponlo junto a main.go y ejecuta go run main.go. Obtienes un PDF de una página con texto japonés.
Qué hacen esas tres líneas en realidad
Dos cosas ocurren bajo el capó, y ninguna necesita tu intervención.
Subset embedding. Noto Sans JP trae unos 17.000 glifos — el peso Regular ocupa unos 5 MB en disco. Si embebieras la fuente completa, un recibo con cuatro líneas de japonés acarrearía cinco megabytes de datos tipográficos. gpdf recorre el texto renderizado, identifica qué IDs de glifo usaste y escribe sólo ese subconjunto en el PDF. Una factura corta suele terminar con 20–40 KB de datos de fuente, no 5 MB.
gofpdf también sabía hacer subset, pero exigía que AddUTF8Font recibiera una ruta de archivo y un flag UTF-8, con la carga ocurriendo mientras el cursor se movía; cambiar de fuente a mitad de documento era incómodo. gpdf registra la fuente una sola vez al construir el documento; a partir de ahí, cada c.Text la referencia por nombre de familia. No hay preparación por llamada.
Sin CGO. Esto importa más de lo que suena. En otros ecosistemas el manejo de fuentes suele pasar por FreeType o HarfBuzz — eso significa una dependencia en C, cachés de build que se invalidan distinto, capas extra en tus imágenes Docker y un cross-compile de macOS a linux/arm64 que deja de ser trivial. gpdf parsea las tablas TrueType en Go puro. go build sigue produciendo un binario estático. Mete en un contenedor distroless el binario Go y el archivo TTF; no necesitas más.
Variantes Bold e Italic
Las familias Noto japonesas entregan un archivo por peso. Para usar negrita, registra el TTF Bold con el sufijo -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),
)
Ahora template.Bold() toma la variante -Bold. Misma convención para -Italic y -BoldItalic. Si no registras la variante, la negrita se sintetiza — legible en pantalla, pero no tipográficamente honesto. Para facturas en producción, registra el peso real.
Varios idiomas CJK en el mismo documento
Registrar más de una familia está bien — gpdf las administra independientemente. Cambia por texto con 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"))
})
})
La unificación Han hace que japonés y chino simplificado compartan codepoints Unicode, pero sus glifos se dibujan distinto. El mismo codepoint se renderiza con forma distinta según la fuente — elegir fuente no es un tema estético, es un tema de corrección. Si generas facturación electrónica para ambos mercados, necesitas ambas fuentes registradas.
La trampa del tofu
Si escribes japonés pero olvidas WithFont, gpdf hace fallback a las fuentes Base-14 del PDF — ninguna cubre el rango CJK. Los caracteres salen como rectángulos vacíos: lo que en el mundo Unicode llamamos "cajas tofu":
□□□□□、□□。
Si ves eso, la causa siempre es la misma: no registraste una fuente CJK, o estás escribiendo en una familia que no incluye esos glifos. La solución también: agrega WithFont y usa WithDefaultFont, o pasa template.FontFamily en el c.Text.
Lectura relacionada
- gofpdf está archivado. Cómo migrar a gpdf. — el mapa completo si vienes de
pdf.AddUTF8Font - Go PDF Library Showdown 2026 — cómo se compara gpdf con gofpdf, gopdf, Maroto y unipdf en CJK
- Guía de fuentes — referencia completa de
WithFonty reglas de nombre de variante
Prueba gpdf
gpdf es una librería Go para generar PDFs. Licencia MIT, cero dependencias externas, soporte CJK nativo.
go get github.com/gpdf-dev/gpdf