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 []byteView on GitHub (pinned to 0d69dca6f5)
Solutions
- Verify the path exists with `ls -l <path>` and correct any typo
- Use an absolute path for the font file
- 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
- Verify font paths exist before running d2
- Use absolute paths for fonts
- Check permissions on shared/CI systems
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
- expected .ttf file but %s has extension %s
- cannot read a directory
- %s is not a supported format. Supported formats are: %s
- failed to fully compile (partial render written) %s: %w
- failed to compile %s: %w
AI-assisted analysis of d2lang/d2@0d69dca6f5 (2026-08-31).
Data as JSON: /api/errors/3f7a84dcd7f197f1.
Report an issue: GitHub.