wailsapp/wails · error

CreateFontIndirect failed

Error message

CreateFontIndirect failed

What it means

After validating the style, NewFont measures screen DPI, builds a LOGFONT (height = -MulDiv(pointSize, dpi, 72), weight/family/italic from style) and calls CreateFontIndirect via createForDPI; it panics when the returned HFONT is NULL. CreateFontIndirect fails only under GDI handle exhaustion or with a malformed LOGFONT; any family string is accepted (Windows performs font substitution), so the family name itself is not the trigger.

Source

Thrown at v2/internal/frontend/desktop/windows/winc/font.go:52

func NewFont(family string, pointSize int, style byte) *Font {
	if style > FontBold|FontItalic|FontUnderline|FontStrikeOut {
		panic("Invalid font style")
	}

	//Retrive screen DPI
	hDC := w32.GetDC(0)
	defer w32.ReleaseDC(0, hDC)
	screenDPIY := w32.GetDeviceCaps(hDC, w32.LOGPIXELSY)

	font := Font{
		family:    family,
		pointSize: pointSize,
		style:     style,
	}

	font.hfont = font.createForDPI(screenDPIY)
	if font.hfont == 0 {
		panic("CreateFontIndirect failed")
	}

	return &font
}

func (fnt *Font) createForDPI(dpi int) w32.HFONT {
	var lf w32.LOGFONT

	lf.Height = int32(-w32.MulDiv(fnt.pointSize, dpi, 72))
	if fnt.style&FontBold > 0 {
		lf.Weight = w32.FW_BOLD
	} else {
		lf.Weight = w32.FW_NORMAL
	}
	if fnt.style&FontItalic > 0 {
		lf.Italic = 1
	}
	if fnt.style&FontUnderline > 0 {

View on GitHub (pinned to 0e754b1b40)

Solutions

  1. Cache fonts: create them once (package-level or per-window) and reuse across controls
  2. Delete HFONTs of replaced fonts with w32.DeleteObject when a control's font is swapped
  3. Watch GDI object count in Task Manager to confirm exhaustion; find the true leak with GDIView

Example fix

// before
for _, row := range rows {
    row.SetFont(winc.NewFont("MS Shell Dlg 2", 8, 0)) // new HFONT per row -> exhaustion -> panic
}

// after
var uiFont = winc.NewFont("MS Shell Dlg 2", 8, 0) // once
for _, row := range rows {
    row.SetFont(uiFont)
}
Defensive patterns

Strategy: validation

Validate before calling

// Cache fonts; only create when the GDI budget allows.
var fontCache = map[fontKey]*winc.Font{}

type fontKey struct { fam string; size int; style byte }

func getFont(fam string, size int, style byte) *winc.Font {
    k := fontKey{fam, size, style}
    if f, ok := fontCache[k]; ok { return f }
    f := winc.NewFont(fam, size, style)
    fontCache[k] = f
    return f
}

Prevention

When it happens

Trigger: Creating Font objects per-control/per-repaint and never deleting the HFONTs until the 10000 GDI handle quota is reached; creating fonts during shutdown while GDI is unavailable.

Common situations: Apps applying fonts in loops (e.g. styling every list item with a fresh NewFont); the DefaultFont-style package var pattern broken by per-call creation; GDI leak elsewhere in the process.

Related errors


AI-assisted analysis of wailsapp/wails@0e754b1b40 (2026-08-15). Data as JSON: /api/errors/b784956ce5e560b0. Report an issue: GitHub.