package fonts import ( "fmt" "path/filepath" "strconv" "strings" "code.ur.gs/lupine/ordoor/internal/util" "code.ur.gs/lupine/ordoor/internal/util/asciiscan" ) type Font struct { Name string // Contains the sprite data for the font. FIXME: load this? ObjectFile string // Maps ASCII bytes to a sprite offset in the ObjectFile mapping map[int]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[int(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[int]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]) size := idxEnd - idxStart // FIXME: I'd love this to be an error but several .fnt files do it if cpEnd-cpStart != size { fmt.Printf("WARNING: %v has mismatched codepoints and indices: %q\n", filename, str) } for offset := 0; offset < size; offset++ { out.mapping[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]) out.mapping[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 }