{"record":{"id":"30b1d52db2d3b438","repo":"d2lang/d2","slug":"invalid-kern-table-too-short","errorCode":null,"errorMessage":"invalid kern table: too short","messagePattern":"invalid kern table: too short","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/textmeasure/fontface.go","lineNumber":159,"sourceCode":"}\n\nfunc roundScale(scale, units, unitsPerEm fixed.Int26_6) fixed.Int26_6 {\n\tx := int64(scale) * int64(units)\n\tif x >= 0 {\n\t\tx += int64(unitsPerEm) / 2\n\t} else {\n\t\tx -= int64(unitsPerEm) / 2\n\t}\n\treturn fixed.Int26_6(x / int64(unitsPerEm))\n}\n\nfunc parseLegacyKern(src []byte) ([]kernPair, error) {\n\ttable, err := sfntTable(src, \"kern\")\n\tif err != nil || table == nil {\n\t\treturn nil, err\n\t}\n\tif len(table) < 18 {\n\t\treturn nil, fmt.Errorf(\"invalid kern table: too short\")\n\t}\n\tif binary.BigEndian.Uint16(table[0:2]) != 0 {\n\t\treturn nil, fmt.Errorf(\"unsupported kern table version\")\n\t}\n\tif binary.BigEndian.Uint16(table[2:4]) == 0 {\n\t\treturn nil, fmt.Errorf(\"invalid kern table: no subtables\")\n\t}\n\n\tlength := int(binary.BigEndian.Uint16(table[6:8]))\n\tcoverage := binary.BigEndian.Uint16(table[8:10])\n\tif coverage != 0x0001 {\n\t\treturn nil, fmt.Errorf(\"unsupported kern table coverage 0x%04x\", coverage)\n\t}\n\tn := int(binary.BigEndian.Uint16(table[10:12]))\n\tif length < 14 || 6*n != length-14 || 4+length > len(table) {\n\t\treturn nil, fmt.Errorf(\"invalid kern table length\")\n\t}\n","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/d2lang/d2/blob/0d69dca6f532ceaeacd615d35d1eaa41a238ffdb/lib/textmeasure/fontface.go#L141-L177","documentation":"parseLegacyKern reads the legacy 'kern' table of an sfnt (TrueType/OpenType) font to extract kerning pairs. A valid kern table header plus subtable header needs at least 18 bytes; a shorter table cannot be parsed safely, so the font is rejected.","triggerScenarios":"Calling fontface/parseFont (via NewFontFace initialization) on a font whose 'kern' table exists but is truncated to fewer than 18 bytes — typically a corrupted or partially downloaded font file.","commonSituations":"Truncated font files from interrupted downloads; fonts embedded in PDFs and extracted incompletely; hand-trimmed/subsetted fonts where the kern table was cut short.","solutions":["Re-obtain the font file from a trusted source and verify its size/checksum.","Validate the font loads with a reference parser (e.g. gofont sfnt or fonttools) before feeding it to the library.","If the font is intentionally subsetted, re-subset with kern data included or stripped entirely (a missing kern table is fine; a truncated one is not).","Remove the corrupt 'kern' table with fonttools (ttLib) so sfntTable returns nil and kerning parsing is skipped."],"exampleFix":"# before: corrupt font loads with truncated kern table\n# after: strip the broken table so the library skips kerning\nfonttools ttLib.woff2 compress -o fixed.woff2 broken.ttf\n# or in python:\n# from fontTools.ttLib import TTFont\n# f = TTFont('broken.ttf'); del f['kern']; f.save('fixed.ttf')","handlingStrategy":"validation","validationCode":"func kernTablePlausible(font []byte) bool {\n    t, err := sfntTable(font, \"kern\")\n    return err == nil && (t == nil || len(t) >= 18)\n}","typeGuard":null,"tryCatchPattern":"pairs, err := parseLegacyKern(fontData)\nif err != nil {\n    log.Printf(\"kerning disabled: %v\", err)\n    pairs = nil // proceed without kerning\n}","preventionTips":["Verify font file integrity (size/checksum) after download and before embedding","Never commit truncated fonts; use go:embed on verified assets","Test all bundled fonts at startup so corruption is caught early","Strip rather than truncate unused tables when subsetting fonts"],"tags":["font","kern","truncated-file"],"backgroundTag":"invalid-font-kern-table","analyzedSha":"0d69dca6f532ceaeacd615d35d1eaa41a238ffdb","analyzedAt":"2026-08-31T12:19:21.182Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}