¿Por qué mi PDF muestra rectángulos (tofu) en lugar de japonés?
Los rectángulos vacíos en lugar de caracteres japoneses significan que el PDF no encontró glifos para esos code points. Cuatro causas y sus soluciones.
La pregunta, en otras palabras
Escribí texto japonés con gpdf y el PDF resultante muestra rectángulos vacíos donde deberían estar los caracteres. ¿Qué es eso y cómo logro que los glifos japoneses reales aparezcan en el archivo?
La respuesta rápida
Eso es tofu — el visor de PDF dibuja un rectángulo como marcador porque la fuente embebida en el PDF no tiene glifo para el code point Unicode que pediste. Cuatro cosas lo causan, y una es mucho más frecuente que las otras.
Por orden de frecuencia:
- Ninguna fuente CJK registrada.
gpdf.NewDocumentno tiene ninguna llamada aWithFont, así que el documento cae en las fuentes Base-14 del PDF (Helvetica, Times, Courier). Ninguna cubre U+3040–U+9FFF. - Fuente CJK registrada, pero el nombre de familia en
c.Textes otro.WithFont("NotoSansJP", ...)está puesto, perotemplate.FontFamily("Arial")en el texto fuerza a gpdf a buscar japonés en una fuente latina. - El archivo de fuente no contiene glifos CJK. El TTF en disco es un subset latino (
NotoSans-Regular.ttfen lugar deNotoSansJP-Regular.ttf). El nombre parece correcto, la cobertura está vacía. - Los bytes estaban rotos antes de llegar a gpdf. La cadena se decodificó como Shift-JIS o Latin-1 aguas arriba y los code points ya no son japoneses. Si ves
縺ゅ→縺en vez de rectángulos, es esta.
La corrección canónica para la causa #1
Nueve de cada diez veces es esto:
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)
}
}
Dos líneas registran la fuente y la ponen por defecto. Sin CGO. Sin la contabilidad de AddUTF8Font. Si antes veías □□□□□、□□。 y corres este programa con un NotoSansJP-Regular.ttf real al lado, obtienes los glifos reales.
Descarga NotoSansJP-Regular.ttf desde Google Fonts.
Cómo saber qué causa tienes
La mayor parte consiste en mirar tres sitios: dónde construyes el documento, dónde escribes el texto, y el archivo TTF mismo.
Si la salida son □□□ (rectángulos idénticos), es causa 1, 2 o 3. El PDF embebió una fuente, pero no tiene los glifos. Abre el PDF en Acrobat, ve a Archivo → Propiedades → Fuentes y mira qué fuentes se embebieron. Si solo aparecen Helvetica / Times / Courier, causa 1. Si NotoSansJP aparece pero siguen los rectángulos, causa 2 o 3.
Si la salida es 縺ゅ→縺 o ã"ã‚"ã«ã¡ã¯ (latín revuelto), es causa 4. La cadena japonesa se re-codificó antes de que gpdf la recibiera. Culpable más común: un CSV guardado como Shift-JIS por Excel y leído con os.ReadFile como si fuera UTF-8, o un endpoint HTTP que no declaró charset=utf-8. Arregla el decodificador, no el PDF.
Salida mixta — algunos caracteres se ven, otros son rectángulos — significa cobertura parcial. Una fuente etiquetada "japonesa" puede incluir hiragana y katakana pero saltarse kanji poco comunes como 鬱 o 龠. Cambia a Noto Sans JP (cubre JIS X 0213) o Source Han Sans JP si caes en esto.
Causa 2 al detalle: fuente correcta, familia mal escrita
Esta es traicionera porque la fuente sí está embebida — solo que no se usa. Reproducción mínima:
doc := gpdf.NewDocument(
gpdf.WithFont("NotoSansJP", font),
// Sin WithDefaultFont.
)
page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Text("こんにちは") // Usa la fuente por defecto: Helvetica.
})
})
Solución: añade gpdf.WithDefaultFont("NotoSansJP", 12) a NewDocument, o pasa template.FontFamily("NotoSansJP") en cada c.Text que necesite japonés. El nombre de familia en WithFont y el de c.Text deben coincidir exactamente, incluyendo mayúsculas y minúsculas. Para gpdf, NotoSansJP y notosansjp son dos fuentes distintas.
Causa 3 al detalle: el TTF equivocado
NotoSans-Regular.ttf y NotoSansJP-Regular.ttf son dos archivos distintos. El primero es una fuente latina sin ninguna cobertura CJK. El segundo es la versión japonesa, con unos 17.000 glifos. Se ven casi idénticos en un listado de directorio, y el autocompletado fácilmente te pasa el equivocado.
gpdf no valida la cobertura de glifos al registrar. Si le das bytes, confía. El fallo aparece como tofu en tiempo de render.
Manera rápida de comprobar:
- macOS:
Font Book→ doble clic en el archivo → previsualización muestra una cuadrícula de glifos - Linux:
otfinfo -u NotoSans-Regular.ttfvuelca la cobertura Unicode - Multiplataforma: fontTools —
ttx -t cmap NotoSans-Regular.ttfexporta la tabla cmap como XML
Si U+3042 (あ) no está en la lista, tienes el subset latino.
Causa 4 al detalle: corrupción de codificación
Esta en realidad no involucra a gpdf. La cadena que le diste a c.Text ya traía los bytes mal. Imprímela antes de renderizar:
text := loadLabelFromSomewhere()
fmt.Printf("%q\n", text) // Muestra las runas reales
c.Text(text)
Si fmt.Printf("%q\n", text) imprime "縺ゅ→縺" en vez de "あいうえ", la corrupción ocurrió aguas arriba. gpdf no puede repararlo — encuentra el punto donde UTF-8 se decodificó mal.
Culpables habituales aguas arriba:
- Leer un CSV exportado desde Excel (Windows Shift-JIS) con
os.ReadFiley castearlo directo astring - Una columna de base de datos declarada
latin1outf8mb3(noutf8mb4) que ya guarda mojibake - Una respuesta HTTP sin
Content-Type: application/json; charset=utf-8y un cliente que adivinó Latin-1
Un caso límite que conviene nombrar
gpdf hace subset silenciosamente. El subset se congela en el momento de Generate(). Si durante la construcción del documento renderizas こんにちは y luego 鬱陶しい, el segundo también se añade al subset correctamente. Pero si generas el PDF, lo abres en Acrobat y escribes un kanji que no estaba en el texto original, ahí tendrás tofu — ese glifo nunca entró en el subset. No pospongas la edición al PDF; vuelve a ejecutar el programa en Go y llama a Generate() de nuevo.
Recetas relacionadas
- ¿Cómo incrusto una fuente japonesa en gpdf? — recorrido completo de
WithFontcon variantes bold/italic y documentos multi-CJK - ¿Cómo uso Noto Sans JP con gpdf? — qué archivo de Noto elegir y cómo
go:embedsimplifica la distribución - La guía definitiva para PDFs en japonés con Go (2026) — guía extensa que cubre fuentes, texto vertical, ruby y layout específico de JP
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