package fonts import ( "fmt" "path/filepath" "strconv" "strings" "code.ur.gs/lupine/ordoor/internal/util" "code.ur.gs/lupine/ordoor/internal/util/asciiscan" ) type Range struct { Min Point Max Point } type Point struct { Rune rune Sprite int } type Font struct { Name string // Contains the sprite data for the font ObjectFile string // Maps ASCII bytes to a sprite offset in the ObjectFile Ranges []Range Mapping map[rune]int } func (f *Font) Entries() int { return len(f.Mapping) } // Returns the offsets required to display a given string, returning an error if // some of the runes in the string are unknown to the font func (f *Font) Indices(s string) ([]int, error) { out := make([]int, 0, len(s)) for i, b := range []byte(s) { offset, ok := f.Mapping[rune(b)] if !ok { return nil, fmt.Errorf("Unknown codepoint %v at offset %v in string %s", b, i, s) } out = append(out, offset) } return out, nil } func LoadFont(filename string) (*Font, error) { scanner, err := asciiscan.New(filename) if err != nil { return nil, err } defer scanner.Close() // First, load the object file name objFile, err := scanner.ConsumeString() if err != nil { return nil, err } out := &Font{ Name: filepath.Base(filename), ObjectFile: objFile, Mapping: make(map[rune]int), } for { str, err := scanner.ConsumeString() if err != nil { return nil, err } parseErr := fmt.Errorf("Invalid entry in %v: %q", filename, str) fields := strings.Fields(str) switch fields[0] { case "done": goto out case "r": // A range of codepoints if len(fields) < 5 { return nil, parseErr } cpStart, _ := strconv.Atoi(fields[1]) cpEnd, _ := strconv.Atoi(fields[2]) idxStart, _ := strconv.Atoi(fields[3]) idxEnd, _ := strconv.Atoi(fields[4]) cpSize := cpEnd - cpStart idxSize := idxEnd - idxStart // FIXME: I'd love this to be an error but several .fnt files do it // Take the smallest range if cpSize != idxSize { fmt.Printf("WARNING: %v has mismatched codepoints (sz=%v) and indices (sz=%v): %q\n", filename, cpSize, idxSize, str) if cpSize < idxSize { idxEnd = idxStart + cpSize idxSize = cpSize } else { cpEnd = cpStart + idxSize cpSize = idxSize } } r := Range{ Min: Point{Rune: rune(cpStart), Sprite: idxStart}, Max: Point{Rune: rune(cpEnd), Sprite: idxEnd}, } out.Ranges = append(out.Ranges, r) for offset := 0; offset <= cpSize; offset++ { out.Mapping[rune(cpStart+offset)] = idxStart + offset } case "v": // A single codepoint, 4 fields if len(fields) < 3 { return nil, parseErr } cp, _ := strconv.Atoi(fields[1]) idx, _ := strconv.Atoi(fields[2]) pt := Point{Rune: rune(cp), Sprite: idx} out.Ranges = append(out.Ranges, Range{Min: pt, Max: pt}) out.Mapping[rune(cp)] = idx default: return nil, parseErr } } out: return out, nil } func LoadFonts(dir string) (map[string]*Font, error) { files, err := util.DirByExt(dir, ".fnt") if err != nil { return nil, err } out := make(map[string]*Font, len(files)) for _, file := range files { abs := filepath.Join(dir, file) base := filepath.Base(file) font, err := LoadFont(abs) if err != nil { return nil, fmt.Errorf("%s: %v", abs, err) } out[base] = font } return out, nil }