{"record":{"id":"17db7a160c72e170","repo":"siyuan-note/siyuan","slug":"parse-font-failed-w","errorCode":null,"errorMessage":"parse font failed: %w","messagePattern":"parse font failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/custom_font.go","lineNumber":329,"sourceCode":"func customFontHash(reader io.Reader) (string, error) {\n\thash := sha256.New()\n\tif _, err := io.Copy(hash, reader); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn hex.EncodeToString(hash.Sum(nil)), nil\n}\n\nfunc parseCustomFontFile(fontFile *os.File) (ret *Font, err error) {\n\tdefer func() {\n\t\tif recovered := recover(); recovered != nil {\n\t\t\tret = nil\n\t\t\terr = fmt.Errorf(\"parse font failed: %v\", recovered)\n\t\t}\n\t}()\n\n\tparsed, err := sfnt.Parse(fontFile)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"parse font failed: %w\", err)\n\t}\n\tret, err = parseFontInfo(parsed)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"parse font metadata failed: %w\", err)\n\t}\n\treturn ret, nil\n}\n\nfunc validCustomFontID(id string) bool {\n\tif len(id) != sha256.Size*2 || strings.ToLower(id) != id {\n\t\treturn false\n\t}\n\tdecoded, err := hex.DecodeString(id)\n\treturn err == nil && len(decoded) == sha256.Size\n}\n\nfunc newCustomFont(id, fontPath string, font *Font) *CustomFont {\n\tweight := font.Weight","sourceCodeStart":311,"sourceCodeEnd":347,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/util/custom_font.go#L311-L347","documentation":"Returned by parseCustomFontFile when sfnt.Parse returns a non-nil error (without panicking). Unlike the panic branch (errorIndex 1089), this is the normal error path: the parser recognized the input as invalid and returned an error, which is wrapped with %w so the underlying cause is preserved for errors.Is/As.","triggerScenarios":"InstallCustomFont or loadCustomFontsLocked passes a file whose header looks like TTF/OTF but whose internal structure (offset table, table directory, required tables) is invalid, so sfnt.Parse rejects it.","commonSituations":"A partially downloaded or truncated font (correct header, missing tables); a file that was concatenated or patched; an OTF whose CFF data is malformed.","solutions":["Inspect the wrapped error (errors.Unwrap or %w chain) to see the specific sfnt parse failure, then address that cause.","Replace the font from a known-good source.","Validate with fonttools/ttx to confirm the table structure before re-uploading."],"exampleFix":"// before\nif _, _, err := util.InstallCustomFont(tempPath); err != nil {\n    log.Println(err)\n}\n\n// after\nif _, _, err := util.InstallCustomFont(tempPath); err != nil {\n    var parseErr *sfnt.ParseError // if the underlying type is exposed\n    if errors.As(err, &parseErr) {\n        log.Printf(\"sfnt parse failure: %v\", parseErr)\n    }\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if _, _, err := util.InstallCustomFont(tempPath); err != nil {\n    var inner = err\n    for errors.Unwrap(inner) != nil { inner = errors.Unwrap(inner) }\n    // inner now holds the underlying sfnt.Parse cause; branch on it\n}","preventionTips":["Unwrap these errors to find the real sfnt cause before acting.","Keep a known-good source for each custom font so a corrupt copy can be replaced."],"tags":["fonts","parsing","sfnt","error-wrapping"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}