siyuan-note/siyuan · error

font file is invalid

Error message

font file is invalid

What it means

Returned by detectCustomFontExtension when io.ReadFull cannot read 4 bytes from the font file header. It means the reader was exhausted, closed, or seeked past the end before the magic-bytes check. This fires before the TTF/OTF signature switch.

Source

Thrown at kernel/util/custom_font.go:298

	entries, err := os.ReadDir(CustomFontDir())
	if err != nil {
		return
	}
	for _, entry := range entries {
		if entry.IsDir() || !strings.HasPrefix(entry.Name(), ".font-") {
			continue
		}
		tempPath := filepath.Clean(filepath.Join(CustomFontDir(), entry.Name()))
		if _, active := customFontTemps[tempPath]; !active {
			_ = os.Remove(tempPath)
		}
	}
}

func detectCustomFontExtension(reader io.Reader) (string, error) {
	header := make([]byte, 4)
	if _, err := io.ReadFull(reader, header); err != nil {
		return "", errors.New("font file is invalid")
	}

	switch string(header) {
	case "\x00\x01\x00\x00", "true":
		return ".ttf", nil
	case "OTTO":
		return ".otf", nil
	default:
		return "", errors.New("only TTF and OTF font files are supported")
	}
}

func customFontHash(reader io.Reader) (string, error) {
	hash := sha256.New()
	if _, err := io.Copy(hash, reader); err != nil {
		return "", err
	}
	return hex.EncodeToString(hash.Sum(nil)), nil

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Re-upload the font — the file is truncated or corrupt and cannot be parsed.
  2. If you added a new code path that reads the font file, always Seek(0, io.SeekStart) before calling detectCustomFontExtension.
  3. Confirm the upload handler streams the full body to the temp file and closes it cleanly.

Example fix

// before
ext, err := detectCustomFontExtension(fontFile)

// after
if _, err := fontFile.Seek(0, io.SeekStart); err != nil {
    return err
}
if info, _ := fontFile.Stat(); info.Size() < 4 {
    return errors.New("font file is truncated")
}
ext, err := detectCustomFontExtension(fontFile)
Defensive patterns

Strategy: validation

Validate before calling

func readableFontHeader(f *os.File) error {
    if _, err := f.Seek(0, io.SeekStart); err != nil { return err }
    info, _ := f.Stat()
    if info.Size() < 4 { return errors.New("font file is truncated") }
    return nil
}

Prevention

When it happens

Trigger: InstallCustomFont (or loadCustomFontsLocked) calls detectCustomFontExtension on a font file that is shorter than 4 bytes, or on an *os.File whose position is already at EOF (e.g. it was read without seeking back to 0).

Common situations: A truncated upload that is at least 1 byte (so it passes the empty check at custom_font.go:103) but under 4 bytes; a code path that forgot fontFile.Seek(0, io.SeekStart) between reads; a file truncated on disk after stat but before read.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/62f913affda47d0f. Report an issue: GitHub.