{"record":{"id":"1d633d533a44a44e","repo":"d2lang/d2","slug":"invalid-kern-table-no-subtables","errorCode":null,"errorMessage":"invalid kern table: no subtables","messagePattern":"invalid kern table: no subtables","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/textmeasure/fontface.go","lineNumber":165,"sourceCode":"\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\n\tpairs := make([]kernPair, n)\n\tfor i := range pairs {\n\t\toffset := 18 + 6*i\n\t\tpairs[i] = kernPair{\n\t\t\tkey:   binary.BigEndian.Uint32(table[offset : offset+4]),\n\t\t\tvalue: int16(binary.BigEndian.Uint16(table[offset+4 : offset+6])),","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/d2lang/d2/blob/0d69dca6f532ceaeacd615d35d1eaa41a238ffdb/lib/textmeasure/fontface.go#L147-L183","documentation":"After the kern table header, nSubtables (bytes 2–4) must be non-zero; a version-0 kern table with zero subtables contains no kerning data at all, which the parser treats as malformed rather than empty.","triggerScenarios":"parseFont loads a font whose kern table declares version 0 but nSubtables == 0, typically from a broken subsetting or editing operation.","commonSituations":"Fonts processed by buggy subsetting tools; fonts where GPOS kerning was added and the legacy kern table emptied but not removed.","solutions":["Remove the empty kern table entirely with fonttools so the parser skips it gracefully.","Regenerate/re-download the font from the original source.","Verify with fonttools (f['kern'].kernTables) that the table actually contains subtables; if kerning lives in GPOS, accept reduced kerning fidelity or pre-convert the font."],"exampleFix":"# python fonttools: drop the empty kern table\nfrom fontTools.ttLib import TTFont\nf = TTFont('broken.ttf')\nif 'kern' in f and not f['kern'].kernTables:\n    del f['kern']\nf.save('fixed.ttf')","handlingStrategy":"fallback","validationCode":"// nSubtables := binary.BigEndian.Uint16(kern[2:4]); usable := nSubtables != 0","typeGuard":null,"tryCatchPattern":"pairs, err := parseLegacyKern(fontData)\nif err != nil {\n    log.Printf(\"kerning unavailable (%v), continuing without\", err)\n    pairs = nil\n}","preventionTips":["Inspect fonts with fonttools before bundling; delete empty kern tables","Regenerate fonts from source rather than editing binaries in place","Keep GPOS-based fonts as primary assets; legacy kern is best-effort","Fail fast in CI by parsing every bundled font at test time"],"tags":["font","kern","malformed-font"],"backgroundTag":"invalid-font-kern-table","analyzedSha":"0d69dca6f532ceaeacd615d35d1eaa41a238ffdb","analyzedAt":"2026-08-31T12:19:21.182Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}