d2lang/d2 · error

invalid sfnt: too short

Error message

invalid sfnt: too short

What it means

sfntTable locates a named table (here 'kern') inside an sfnt container. An sfnt header is at least 12 bytes; anything shorter cannot even hold the table directory header, so the font data is rejected outright.

Source

Thrown at lib/textmeasure/fontface.go:191

	n := int(binary.BigEndian.Uint16(table[10:12]))
	if length < 14 || 6*n != length-14 || 4+length > len(table) {
		return nil, fmt.Errorf("invalid kern table length")
	}

	pairs := make([]kernPair, n)
	for i := range pairs {
		offset := 18 + 6*i
		pairs[i] = kernPair{
			key:   binary.BigEndian.Uint32(table[offset : offset+4]),
			value: int16(binary.BigEndian.Uint16(table[offset+4 : offset+6])),
		}
	}
	return pairs, nil
}

func sfntTable(src []byte, tag string) ([]byte, error) {
	if len(src) < 12 {
		return nil, fmt.Errorf("invalid sfnt: too short")
	}

	base := 0
	if string(src[:4]) == "ttcf" {
		if len(src) < 16 || binary.BigEndian.Uint32(src[8:12]) == 0 {
			return nil, fmt.Errorf("invalid font collection")
		}
		base = int(binary.BigEndian.Uint32(src[12:16]))
		if base < 0 || base+12 > len(src) {
			return nil, fmt.Errorf("invalid font collection offset")
		}
	}

	n := int(binary.BigEndian.Uint16(src[base+4 : base+6]))
	records := base + 12
	if n > (len(src)-records)/16 {
		return nil, fmt.Errorf("invalid sfnt table directory")
	}

View on GitHub (pinned to 0d69dca6f5)

Solutions

  1. Check the font file is non-empty and starts with a valid sfnt signature (0x00010000, 'true', 'OTTO', or 'ttcf').
  2. Re-download or re-copy the font and verify size/checksum before embedding.
  3. If fonts are loaded at runtime, validate len(data) >= 12 and the magic bytes before calling the parser.

Example fix

// before
data, _ := os.ReadFile(fontPath)
face := parseFont(data)
// after
data, err := os.ReadFile(fontPath)
if err != nil || len(data) < 12 {
    return fmt.Errorf("font %s missing or truncated", fontPath)
}
face := parseFont(data)
Defensive patterns

Strategy: validation

Validate before calling

func isSfnt(data []byte) bool {
    if len(data) < 12 { return false }
    sig := string(data[:4])
    return sig == "\x00\x01\x00\x00" || sig == "true" || sig == "OTTO" || sig == "ttcf"
}

Type guard

func looksLikeFont(b []byte) bool { return len(b) >= 12 && isSfnt(b) }

Try / catch

if err := loadAndParseFont(path); err != nil {
    log.Printf("skipping font %s: %v", path, err)
    return defaultFace // fallback font
}

Prevention

When it happens

Trigger: parseFont is given font bytes shorter than 12 bytes — an empty file, an HTTP error page saved as .ttf, a placeholder string, or a nil/blank embedded asset.

Common situations: embed directives picking up empty placeholder files; download failures writing 0-byte or HTML error responses into font assets; wrong env path resolving to the wrong file.

Related errors


AI-assisted analysis of d2lang/d2@0d69dca6f5 (2026-08-31). Data as JSON: /api/errors/8f49fbdc65f821cb. Report an issue: GitHub.