d2lang/d2 · error

failed to read font at %s: %v

Error message

failed to read font at %s: %v

What it means

After the extension check passes, loadFont reads the font file with os.ReadFile. If the file cannot be read — missing file, permission denied, path is a directory — the underlying OS error is wrapped and returned.

Source

Thrown at d2cli/main.go:1323

	return nil
}

func initPlaywright() error {
	pw, err := png.InitPlaywrightWithPrompt()
	if err != nil {
		return err
	}
	return pw.Cleanup()
}

func loadFont(ms *xmain.State, path string) ([]byte, error) {
	if filepath.Ext(path) != ".ttf" {
		return nil, fmt.Errorf("expected .ttf file but %s has extension %s", path, filepath.Ext(path))
	}
	ttf, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("failed to read font at %s: %v", path, err)
	}
	ms.Log.Info.Printf("font %s loaded", filepath.Base(path))
	return ttf, nil
}

func loadFonts(ms *xmain.State, pathToRegular, pathToItalic, pathToBold, pathToSemibold, pathToMono, pathToMonoBold, pathToMonoItalic, pathToMonoSemibold string) (*d2fonts.FontFamily, *d2fonts.FontFamily, error) {
	if pathToRegular == "" && pathToItalic == "" && pathToBold == "" && pathToSemibold == "" &&
		pathToMono == "" && pathToMonoBold == "" && pathToMonoItalic == "" && pathToMonoSemibold == "" {
		return nil, nil, nil
	}

	var regularTTF []byte
	var italicTTF []byte
	var boldTTF []byte
	var semiboldTTF []byte
	var monoTTF []byte
	var monoBoldTTF []byte
	var monoItalicTTF []byte

View on GitHub (pinned to 0d69dca6f5)

Solutions

  1. Verify the path exists with `ls -l <path>` and correct any typo
  2. Use an absolute path for the font file
  3. Check file permissions (chmod/chown) so the running user can read it

Example fix

// before
d2 --font-regular=./fonts/Inter.ttf sketch.d2   # run from wrong dir
// after
d2 --font-regular=/usr/share/fonts/Inter-Regular.ttf sketch.d2
Defensive patterns

Strategy: validation

Validate before calling

if _, err := os.Stat(fontPath); err != nil {
	// fail fast: path missing or inaccessible
	os.Exit(1)
}

Type guard

func fontReadable(path string) bool {
	f, err := os.Open(path)
	if err != nil { return false }
	f.Close()
	return true
}

Try / catch

ttf, err := loadFont(ms, path)
if err != nil {
	ms.Log.Error.Print(err)
	return fmt.Errorf("check font path %s exists and is readable", path)
}

Prevention

When it happens

Trigger: loadFont called with a .ttf path that does not exist, points to a directory, or is not readable by the current user.

Common situations: Typo in the font path, font file deleted or moved after configuring flags, relative path resolved from a different working directory, or restricted file permissions on shared systems.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of d2lang/d2@0d69dca6f5 (2026-08-31). Data as JSON: /api/errors/3f7a84dcd7f197f1. Report an issue: GitHub.