Summary
Using a UTF-8 font and writing text that contains characters outside the Basic Multilingual Plane (code points >= U+10000, such as emoji) causes a panic during Output(). The panic occurs in generateCIDFontMap (fpdf.go:4600), which iterates up to LastRune+1 and indexes into font.Cw without bounds checking. font.Cw is backed by CharWidths, a fixed 65536-element slice allocated in parseHMTXTable.
A second crash site exists in parseHMTXTable itself (utf8fontfile.go:859-860), which can panic during font loading if the font's cmap table contains non-BMP character mappings.
Reproduction
Self-contained, no external font files needed:
packagemainimport("io""codeberg.org/go-pdf/fpdf""golang.org/x/image/font/gofont/goregular")funcmain(){pdf:=fpdf.New("P","mm","A4","")pdf.AddUTF8FontFromBytes("go","",goregular.TTF)pdf.SetFont("go","",12)pdf.AddPage()pdf.Cell(40,10,"\U0001F7E5")// U+1F7E5 LARGE RED SQUAREpdf.Output(io.Discard)}Panics with:
panic: runtime error: index out of range [65536] with length 65536
goroutine 1 [running]:
codeberg.org/go-pdf/fpdf.(*Fpdf).generateCIDFontMap(0x..., 0x..., 0x1f7e5)
fpdf.go:4600 +0xfbc
codeberg.org/go-pdf/fpdf.(*Fpdf).putfonts(0x...)
fpdf.go:4519 +0xafc
codeberg.org/go-pdf/fpdf.(*Fpdf).putresources(0x...)
fpdf.go:4985 +0x48
codeberg.org/go-pdf/fpdf.(*Fpdf).enddoc(0x...)
fpdf.go:5192 +0x13c
codeberg.org/go-pdf/fpdf.(*Fpdf).Close(0x...)
fpdf.go:767 +0x1d4
codeberg.org/go-pdf/fpdf.(*Fpdf).Output(0x..., {0x..., 0x...})
fpdf.go:3837 +0x48
main.main()
main.go:18 +0x190
generateCIDFontMap is called with LastRune = 0x1f7e5 (128997) and iterates sequentially from cid = 1; the panic fires at the first out-of-bounds index (65536).
Root Cause
There are two related bugs sharing the same root cause: CharWidths is allocated with only 65536 elements but code allows character values well beyond that range.
Bug 1: generateCIDFontMap (fpdf.go:4588)
When text is rendered via Cell/Write/etc., each character's code point is recorded in font.usedRunes:
// fpdf.go:2555-2556for_,uni:=rangetxtStr{f.currentFont.usedRunes[int(uni)]=int(uni)}Later, during makeSubset (called from Output), LastRune is set to the maximum used rune:
// utf8fontfile.go:544utf.LastRune=max(utf.LastRune,char)Then generateCIDFontMap iterates up to LastRune+1, indexing into font.Cw (which is CharWidths, length 65536) without bounds checking:
// fpdf.go:4596-4600cwLen:=LastRune+1forcid:=startCid;cid<cwLen;cid++{iffont.Cw[cid]==0x00{// PANIC when cid >= 65536Bug 2: parseHMTXTable (utf8fontfile.go:834)
CharWidths is allocated as make([]int, 256*256) (65536 elements), but the bounds guard allows values up to 196608:
// utf8fontfile.go:839utf.CharWidths=make([]int,256*256)// length 65536// ...// utf8fontfile.go:859-860ifchar<196608{utf.CharWidths[char]=widths// PANIC when 65536 <= char < 196608}This panics during font loading if the font's cmap contains mappings for non-BMP characters.
Suggested Fix
The most straightforward fix is to make CharWidths large enough for the range it guards (196608), or use a map instead of a slice. If the intent is to only support BMP characters, the guard should be char < 65536 (or char < len(utf.CharWidths)).
Additionally, generateCIDFontMap should cap its iteration to len(font.Cw):
cwLen:=LastRune+1ifcwLen>len(font.Cw){cwLen=len(font.Cw)}Environment
- fpdf version: v0.11.1
- Go version: go1.24
- OS: darwin/arm64