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)), nilView on GitHub (pinned to 251596fc0d)
Solutions
- Re-upload the font — the file is truncated or corrupt and cannot be parsed.
- If you added a new code path that reads the font file, always Seek(0, io.SeekStart) before calling detectCustomFontExtension.
- 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
- Always Seek(0, io.SeekStart) before re-reading an *os.File in font code.
- Reject uploads smaller than 4 bytes at the boundary.
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
- font file is empty
- only TTF and OTF font files are supported
- font family is empty
- import path is not sub path of import dir
- not a valid workspace: %s
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/62f913affda47d0f.
Report an issue: GitHub.