2019-01-02 06:16:15 +00:00
|
|
|
package fonts
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"path/filepath"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
2019-12-31 01:55:58 +00:00
|
|
|
"code.ur.gs/lupine/ordoor/internal/util"
|
|
|
|
"code.ur.gs/lupine/ordoor/internal/util/asciiscan"
|
2019-01-02 06:16:15 +00:00
|
|
|
)
|
|
|
|
|
2020-03-30 00:15:19 +01:00
|
|
|
type Range struct {
|
|
|
|
Min Point
|
|
|
|
Max Point
|
|
|
|
}
|
|
|
|
|
|
|
|
type Point struct {
|
|
|
|
Rune rune
|
|
|
|
Sprite int
|
|
|
|
}
|
|
|
|
|
2019-01-02 06:16:15 +00:00
|
|
|
type Font struct {
|
|
|
|
Name string
|
2020-03-30 00:15:19 +01:00
|
|
|
// Contains the sprite data for the font
|
2019-01-02 06:16:15 +00:00
|
|
|
ObjectFile string
|
|
|
|
|
|
|
|
// Maps ASCII bytes to a sprite offset in the ObjectFile
|
2020-03-30 00:15:19 +01:00
|
|
|
Ranges []Range
|
|
|
|
Mapping map[rune]int
|
2019-01-02 06:16:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (f *Font) Entries() int {
|
2020-03-30 00:15:19 +01:00
|
|
|
return len(f.Mapping)
|
2019-01-02 06:16:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
|
|
|
|
2020-03-30 00:15:19 +01:00
|
|
|
offset, ok := f.Mapping[rune(b)]
|
2019-01-02 06:16:15 +00:00
|
|
|
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,
|
2020-03-30 00:15:19 +01:00
|
|
|
Mapping: make(map[rune]int),
|
2019-01-02 06:16:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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])
|
2020-03-30 00:15:19 +01:00
|
|
|
cpSize := cpEnd - cpStart
|
|
|
|
idxSize := idxEnd - idxStart
|
2019-01-02 06:16:15 +00:00
|
|
|
|
|
|
|
// FIXME: I'd love this to be an error but several .fnt files do it
|
2020-03-30 00:15:19 +01:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2019-01-02 06:16:15 +00:00
|
|
|
}
|
|
|
|
|
2020-03-30 00:15:19 +01:00
|
|
|
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
|
2019-01-02 06:16:15 +00:00
|
|
|
}
|
|
|
|
case "v": // A single codepoint, 4 fields
|
|
|
|
if len(fields) < 3 {
|
|
|
|
return nil, parseErr
|
|
|
|
}
|
|
|
|
|
|
|
|
cp, _ := strconv.Atoi(fields[1])
|
|
|
|
idx, _ := strconv.Atoi(fields[2])
|
2020-03-30 00:15:19 +01:00
|
|
|
pt := Point{Rune: rune(cp), Sprite: idx}
|
|
|
|
|
|
|
|
out.Ranges = append(out.Ranges, Range{Min: pt, Max: pt})
|
2019-01-02 06:16:15 +00:00
|
|
|
|
2020-03-30 00:15:19 +01:00
|
|
|
out.Mapping[rune(cp)] = idx
|
2019-01-02 06:16:15 +00:00
|
|
|
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
|
|
|
|
}
|