siyuan-note/siyuan · error
font file is invalid
Error message
font file is invalid
What it means
detectCustomFontExtension sniffs the first 4 bytes of the font to detect its type. This error is thrown when reading the 4-byte magic header fails via io.ReadFull — the file is shorter than 4 bytes, unreadable, or an I/O error occurred, so the format cannot even be classified.
Solutions
- Verify the file is at least 4 bytes and re-upload it completely
- Check the upload pipeline for truncation (Content-Length, temp-file flush/close)
- Compare local file size with the source font size to confirm the transfer completed
- Re-export or re-download the font from a trusted source
Example fix
// before
font, _, err := util.InstallCustomFont(tempPath)
// after
if info, _ := os.Stat(tempPath); info != nil && info.Size() < 4 {
return fmt.Errorf("upload truncated (%d bytes)", info.Size())
}
font, _, err := util.InstallCustomFont(tempPath) Defensive patterns
Strategy: validation
Validate before calling
if info, err := os.Stat(tempPath); err == nil && info.Size() < 4 {
return fmt.Errorf("file too small to be a font: %d bytes", info.Size())
} Try / catch
font, _, err := util.InstallCustomFont(tempPath)
if err != nil && err.Error() == "font file is invalid" {
// re-upload / verify transfer completeness
} Prevention
- Verify uploaded size matches source size
- Keep uploads binary-safe end to end
- Reject obviously tiny files before staging
When it happens
Trigger: Calling InstallCustomFont with a temp file of 1-3 bytes (passes the size>0 check but is too small to sniff); a read error on the underlying file; loadCustomFontsLocked encountering a truncated font on disk.
Common situations: Truncated uploads (connection cut mid-transfer); an empty-ish placeholder file a few bytes long; disk/IO errors on the fonts directory; a text file containing a single short line mistakenly uploaded as a font.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- 106
- 199
- active tab [ ] is not a direct child of [ ]
- AI editor action must not be empty
- AI editor input is empty
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/62f913affda47d0f.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/custom_font.go:303
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 9f775e8a12)