Browse Source

Font-Util. This handles TDF Fonts.

This list fonts, shows fonts, and exports fonts.
Also!  The exported code is clean.  (It matches gofmt!)
Steve Thielemann 2 năm trước cách đây
mục cha
commit
99d872e885
4 tập tin đã thay đổi với 1151 bổ sung0 xóa
  1. 580 0
      font-util/font-out.go
  2. 316 0
      font-util/font-show.go
  3. 7 0
      font-util/go.mod
  4. 248 0
      font-util/utils.go

+ 580 - 0
font-util/font-out.go

@@ -0,0 +1,580 @@
+package main
+
+import (
+	"encoding/binary"
+	"flag"
+	"fmt"
+	"os"
+	"strconv"
+	"strings"
+)
+
+var Conversion [][2]int
+
+// copied from tdfont.go, and modified
+
+func thedraw_to_ansi(c int) int {
+	trans := []int{0, 4, 2, 6, 1, 5, 3, 7}
+	//             0, 1, 2, 3, 4, 5, 6, 7
+	return trans[c]
+}
+
+func MatchStyle(color byte, look byte) int {
+	var match int = 0
+	if ((color >> 4) & 0x07) == look {
+		// Top
+		match |= 1
+	}
+	if (color & 0x07) == look {
+		// Bottom
+		match |= 2
+	}
+	return match
+}
+
+func PatchColor(color byte, new_color byte, style int) byte {
+	var c byte = color
+	if style&1 == 1 {
+		c = (c & 0x8f) | new_color<<4
+	}
+	if style&2 == 2 {
+		c = (c & 0xf8) | new_color
+	}
+	return c
+}
+
+type ColorMap map[[2]int][][2]int
+
+func Scan(block [][][]byte, find_color int) ColorMap {
+	var Targets ColorMap = make(ColorMap, 0)
+	// Scan the font looking for the given color FG/BG
+	// Covert color code to TheDraw Color
+	actual := byte(thedraw_to_ansi(find_color))
+
+	for charIndex := range block {
+		for lineIndex := range block[charIndex] {
+			var found bool = false
+			var patches [][2]int = make([][2]int, 0)
+			for offset := 1; offset < len(block[charIndex][lineIndex]); offset += 2 {
+				color := block[charIndex][lineIndex][offset]
+				style := MatchStyle(color, actual)
+				if style != 0 {
+					// log.Printf("color: %x actual %x style: %d\n", color, actual, style)
+					patches = append(patches, [2]int{offset, style})
+					found = true
+				}
+			}
+			if found {
+				pos := [2]int{charIndex, lineIndex}
+				Targets[pos] = make([][2]int, len(patches))
+				for i := range patches {
+					Targets[pos][i] = patches[i]
+				}
+				// Targets[pos] = patches
+			}
+		}
+	}
+	return Targets
+}
+
+func Modify(block [][][]byte, new_color int, Targets ColorMap) {
+	// Covert color code to TheDraw Color
+	actual := byte(thedraw_to_ansi(new_color))
+	for pos, patch := range Targets {
+		for _, p := range patch {
+			block[pos[0]][pos[1]][p[0]] = PatchColor(block[pos[0]][pos[1]][p[0]], actual, p[1])
+		}
+	}
+}
+
+// Use ExtractColor to get the font -- then output that?
+
+func OutputColor(writer *os.File, name string, offsets []uint16, data []byte) {
+	// fmt.Printf("Extract Color Font: %s\n", name)
+	var indexes []int
+	var blocks [][][]byte
+	var current [][]byte
+	var line []byte
+
+	pos := 0
+	for pos < len(data) {
+		indexes = append(indexes, pos)
+		current = make([][]byte, 0)
+		line = make([]byte, 0)
+
+		// We don't use these.
+		// w = data[pos]
+		// h = data[pos+1]
+		pos += 2
+
+		// process this character
+		for pos < len(data) {
+			ch := data[pos]
+			pos++
+			if ch == 0x00 {
+				// end of character
+				current = append(current, line)
+				blocks = append(blocks, current)
+				current = make([][]byte, 0)
+				line = make([]byte, 0)
+				break
+			}
+			if ch == 0x0d {
+				// end of this character line
+				current = append(current, line)
+				line = make([]byte, 0)
+				continue
+			}
+			if ch == 0x26 {
+				// & descender mark
+				continue
+			}
+			line = append(line, ch)
+			color := data[pos]
+			pos++
+			line = append(line, color)
+		}
+	}
+
+	// offset optimization:
+	var single []int
+	for _, o := range offsets {
+		if o == 65535 {
+			single = append(single, -1)
+			continue
+		}
+		for idx, i := range indexes {
+			if o == uint16(i) {
+				single = append(single, idx)
+				break
+			}
+		}
+	}
+
+	/*
+		// Handle Names with spaces
+		filename := fmt.Sprintf("%s_font.go", strings.Replace(name, " ", "", -1))
+
+		fp, err := os.Create(filename)
+		if err != nil {
+			panic(err)
+		}
+		fmt.Printf("Writing: %s\n", filename)
+
+		defer fp.Close()
+		writer := bufio.NewWriter(fp)
+	*/
+	// writer := bufio.NewWriter(os.Stdout)
+
+	writer.WriteString("\n// " + name + "\n\n")
+
+	// Name := strings.ToUpper(name)
+	Name := strings.Replace(name, " ", "", -1)
+
+	writer.WriteString("func Font" + Name + "() door.ColorFont {\n")
+	var output string
+	output = "\treturn door.ColorFont{Characters: []int{"
+	for _, s := range single {
+		output += strconv.Itoa(s) + ", "
+	}
+	output = output[:len(output)-2] + "},\n"
+	writer.WriteString(output)
+	// writer.Flush()
+
+	if len(Conversion) > 0 {
+		// Color Convert time!
+		var Maps []map[[2]int][][2]int = make([]map[[2]int][][2]int, len(Conversion))
+		for idx, codes := range Conversion {
+			Maps[idx] = Scan(blocks, codes[0])
+		}
+		for idx, codes := range Conversion {
+			Modify(blocks, codes[1], Maps[idx])
+		}
+	}
+
+	output = "\t\tData: [][][]byte{"
+	for _, blk := range blocks {
+		output += "{"
+		if len(blk) == 0 {
+			output += "{},"
+		} else {
+			for _, inner := range blk {
+				// output += text_to_hextext(b) + ","
+				output += "{" + byte_to_text(inner) + "}, "
+			}
+			output = output[:len(output)-2]
+		}
+
+		output += "},\n"
+		writer.WriteString(output)
+		output = "\t\t\t"
+	}
+	writer.WriteString("\t\t}}\n")
+	writer.WriteString("}\n")
+	// writer.Flush()
+}
+
+// Use ExtractBlock to get the Font, and then output that?
+
+func OutputBlock(writer *os.File, name string, offsets []uint16, data []byte) {
+	// fmt.Printf("Extract Block Font: %s\n", name)
+	var indexes []int
+	var blocks [][][]byte
+	var current [][]byte
+	var line []byte
+
+	pos := 0
+	for pos < len(data) {
+		indexes = append(indexes, pos)
+		current = make([][]byte, 0)
+		line = make([]byte, 0)
+
+		// We don't use these
+		// w = data[pos]
+		// h = data[pos+1]
+		pos += 2
+
+		// process this character
+		for pos < len(data) {
+			ch := data[pos]
+			pos++
+			if ch == 0x00 {
+				// end of character
+				current = append(current, line)
+				blocks = append(blocks, current)
+				current = make([][]byte, 0)
+				line = make([]byte, 0)
+				break
+			}
+			if ch == 0x0d {
+				// end of this character line
+				current = append(current, line)
+				line = make([]byte, 0)
+				continue
+			}
+			if ch == 0x26 {
+				// & descender mark
+				continue
+			}
+			line = append(line, ch)
+		}
+	}
+
+	// offset optimization:
+	var single []int
+	for _, o := range offsets {
+		if o == 65535 {
+			single = append(single, -1)
+			continue
+		}
+		for idx, i := range indexes {
+			if o == uint16(i) {
+				single = append(single, idx)
+				break
+			}
+		}
+	}
+
+	/*
+		// Handle Names with spaces
+		filename := fmt.Sprintf("%s_font.go", strings.Replace(name, " ", "", -1))
+
+		fp, err := os.Create(filename)
+		if err != nil {
+			panic(err)
+		}
+		fmt.Printf("Writing: %s\n", filename)
+
+		defer fp.Close()
+		writer := bufio.NewWriter(fp)
+	*/
+	// writer := bufio.NewWriter(os.Stdout)
+
+	// Should this output routine be part of the BlockFont?
+	// I think so!
+
+	// writer.WriteString("package main\n")
+	writer.WriteString("// " + name + "\n\n")
+
+	// Name := strings.ToUpper(name)
+	Name := strings.Replace(name, " ", "", -1)
+
+	writer.WriteString("func Font" + Name + "() door.BlockFont {\n")
+	var output string
+	output = "  return door.BlockFont{Characters: []int{"
+	for _, s := range single {
+		output += strconv.Itoa(s) + ", "
+	}
+	output = output[:len(output)-2] + "},\n"
+	writer.WriteString(output)
+	// writer.Flush()
+
+	output = "    Data: [][][]byte{"
+	for _, blk := range blocks {
+		output += "{"
+		if len(blk) == 0 {
+			output += "{},"
+		} else {
+			for _, inner := range blk {
+				output += "{" + byte_to_text(inner) + "},"
+			}
+			output = output[:len(output)-1]
+		}
+
+		output += "},\n"
+
+		// output = output[:len(output)-1]
+		// output += "},\n"
+		writer.WriteString(output)
+		output = "      "
+	}
+	writer.WriteString("    }}\n")
+	writer.WriteString("}\n")
+	// writer.Flush()
+}
+
+// outputs fonts
+func OutputFonts(writer *os.File, filename string, fonts []string) {
+	f, err := os.Open(filename)
+	if err != nil {
+		fmt.Printf("Open(%s): %s\n", filename, err)
+		panic(err)
+	}
+	defer f.Close()
+	tdfonts := make([]byte, 20)
+	f.Read(tdfonts)
+	for true {
+		fontdef := make([]byte, 4)
+		read, _ := f.Read(fontdef)
+		if read != 4 {
+			break
+		}
+		fontname := make([]byte, 13)
+		f.Read(fontname)
+		Name := strings.Trim(string(fontname[1:]), "\x00")
+		// fmt.Printf("Font: %s\n", Name)
+		f.Read(fontdef)
+		single := make([]byte, 1)
+
+		var FontType int8
+		binary.Read(f, binary.LittleEndian, &FontType)
+
+		// fmt.Printf("Font: %s (type %d)\n", Name, FontType)
+		f.Read(single) // Spacing
+
+		var BlockSize int16
+		binary.Read(f, binary.LittleEndian, &BlockSize)
+
+		letterOffsets := make([]uint16, 94)
+		binary.Read(f, binary.LittleEndian, &letterOffsets)
+
+		if false {
+			for idx, i := range letterOffsets {
+				fmt.Printf(" %04X", i)
+				if (idx+1)%10 == 0 {
+					fmt.Println("")
+				}
+			}
+			fmt.Println("")
+		}
+
+		data := make([]byte, BlockSize)
+		binary.Read(f, binary.LittleEndian, &data)
+
+		// Special case where they are asking for all fonts
+		if len(fonts) == 1 && fonts[0] == "*" {
+			switch FontType {
+			case 1:
+				OutputBlock(writer, Name, letterOffsets, data)
+			case 2:
+				OutputColor(writer, Name, letterOffsets, data)
+			default:
+				fmt.Printf("// Sorry, I can't handle Font: %s Type %d!\n", Name, FontType)
+			}
+		} else {
+			for _, f := range fonts {
+				if Name == f {
+					switch FontType {
+					case 1:
+						OutputBlock(writer, Name, letterOffsets, data)
+					case 2:
+						OutputColor(writer, Name, letterOffsets, data)
+					default:
+						fmt.Printf("// Sorry, I can't handle Font: %s Type %d!\n", Name, FontType)
+					}
+					break
+				}
+			}
+		}
+	}
+}
+
+// Created so that multiple inputs can be accepted
+type arrayFlags []string
+
+func (i *arrayFlags) String() string {
+	// change this, this example is just to satisfy the interface
+	result := ""
+	for _, str := range *i {
+		if result != "" {
+			result += ", "
+		}
+		result += str
+	}
+	return result
+}
+
+func (i *arrayFlags) Set(value string) error {
+	*i = append(*i, strings.TrimSpace(value))
+	return nil
+}
+
+func ParseColorConvert(convert arrayFlags) {
+	Conversion = make([][2]int, 0)
+	if len(convert) > 0 {
+		// Something to do
+		for _, color := range convert {
+			split := strings.Split(color, ",")
+			v1, _ := strconv.Atoi(split[0])
+			v2, _ := strconv.Atoi(split[1])
+			Conversion = append(Conversion, [2]int{v1, v2})
+		}
+	}
+}
+
+/*
+I could envision something like this:
+
+https://blog.ralch.com/articles/golang-subcommands/
+https://stackoverflow.com/questions/23725924/can-gos-flag-package-print-usage
+
+list   (font files)
+show -a -f (font files)
+extract -all -font -color -package -output (font files)
+
+show - under windows ?  :cat_scream:
+
+*/
+
+func main() {
+	if len(os.Args) == 1 {
+		fmt.Println("Usage: font-out <command> [<args>] fontfile.tdf...")
+		fmt.Println("  list    - List available fonts")
+		fmt.Println("  show    - Show fonts")
+		fmt.Println("  extract - Extract to source.go file")
+		return
+	}
+
+	var fonts string
+	var defaultPackage string = "main"
+	var allFonts bool
+	var convert arrayFlags
+	var output string
+
+	var listCommand *flag.FlagSet = flag.NewFlagSet("list", flag.ExitOnError)
+	var showCommand *flag.FlagSet = flag.NewFlagSet("show", flag.ExitOnError)
+	showCommand.BoolVar(&allFonts, "a", false, "Show All Fonts")
+	showCommand.StringVar(&fonts, "f", "", "Fonts to Show font1,font2,font3")
+
+	var extractCommand *flag.FlagSet = flag.NewFlagSet("extract", flag.ExitOnError)
+	extractCommand.BoolVar(&allFonts, "a", false, "Extract All Fonts")
+	extractCommand.StringVar(&fonts, "f", "", "Fonts to Extract font1,font2,font3")
+	extractCommand.StringVar(&defaultPackage, "p", "main", "Package name to use")
+	extractCommand.Var(&convert, "c", "Convert Color to Color n,n")
+	extractCommand.StringVar(&output, "o", "", "Output to file")
+
+	switch os.Args[1] {
+	case "list":
+		listCommand.Parse(os.Args[2:])
+		if listCommand.Parsed() {
+			if len(listCommand.Args()) == 0 {
+				fmt.Println("No TDF Font files given.")
+				os.Exit(2)
+			}
+
+			for _, fontfile := range listCommand.Args() {
+				ListFonts(fontfile)
+			}
+		}
+		os.Exit(0)
+
+	case "show":
+		showCommand.Parse(os.Args[2:])
+
+	case "extract":
+		extractCommand.Parse(os.Args[2:])
+
+	default:
+		fmt.Printf("%q is not a valid command.\n", os.Args[1])
+		os.Exit(2)
+	}
+
+	var fontList []string
+	if len(fonts) > 0 {
+		fontList = strings.Split(fonts, ",")
+	}
+
+	if allFonts {
+		fontList = make([]string, 0)
+		fontList = append(fontList, "*")
+	}
+
+	var err error
+
+	if showCommand.Parsed() {
+		// Show Fonts
+		if len(fontList) == 0 {
+			fmt.Println("No Fonts selected.")
+			showCommand.Usage()
+			os.Exit(2)
+		}
+
+		if len(showCommand.Args()) == 0 {
+			fmt.Println("No TDF Fonts files given.")
+			showCommand.Usage()
+			os.Exit(2)
+		}
+
+		for _, fontfile := range showCommand.Args() {
+			fmt.Println("FILE:", fontfile)
+			DisplayFonts(fontfile, fontList)
+		}
+	}
+
+	if extractCommand.Parsed() {
+		// Extract Fonts
+		if len(fontList) == 0 {
+			fmt.Println("No Fonts selected.")
+			extractCommand.Usage()
+			os.Exit(2)
+		}
+
+		if len(extractCommand.Args()) == 0 {
+			fmt.Println("No TDF Fonts files given.")
+			extractCommand.Usage()
+			os.Exit(2)
+		}
+
+		var saveTo *os.File
+
+		ParseColorConvert(convert)
+
+		// Setup saveTo
+		if output == "" {
+			saveTo = os.Stdout
+		} else {
+			saveTo, err = os.Create(output)
+			if err != nil {
+				fmt.Println("Create", output, "error:", err)
+				os.Exit(2)
+			}
+		}
+
+		fmt.Fprintf(saveTo, "package %s\n\n", defaultPackage)
+		fmt.Fprintf(saveTo, "import (\n\t\"red-green/door\"\n)\n")
+
+		for _, fontfile := range extractCommand.Args() {
+			OutputFonts(saveTo, fontfile, fontList)
+		}
+	}
+}

+ 316 - 0
font-util/font-show.go

@@ -0,0 +1,316 @@
+package main
+
+import (
+	"encoding/binary"
+	"fmt"
+	"os"
+	"red-green/door"
+	"regexp"
+	"strings"
+)
+
+// Best used with | less -RS
+
+// Attempt to fix broken fonts.
+// This verifies that the character offsets are proceeded
+// by a null character.
+func FontFixup(offsets []uint16, data *[]byte) bool {
+	fixed := false
+	for _, offset := range offsets {
+		if offset == 65535 {
+			continue
+		}
+		if offset == 0 {
+			continue
+		}
+		if (*data)[offset-1] != 0 {
+			(*data)[offset-1] = 0
+			fixed = true
+		}
+	}
+	return fixed
+}
+
+func Show(parts []string) {
+	reset := "\x1b[0m"
+
+	if len(parts) > 0 {
+		for _, line := range parts {
+			fmt.Printf("%s%s\n", door.CP437_to_Unicode(string(line)), reset)
+			// fmt.Printf("%s%s\n", line, reset)
+		}
+		fmt.Println("")
+	}
+}
+
+// Examine character indexes, are there lower case letter? are they unique?
+// What are the available characters in this font?
+func FontInfo(characters []int) (lower bool, lowerUnique bool, available string) {
+	// Does it have lowercase?
+	// Is lowercase unique?
+	// Available characters in font
+
+	// 33 -126
+	lower = characters[int(rune('a')-33)] != -1
+	lowerUnique = characters[int(rune('A')-33)] != characters[int(rune('a')-33)]
+
+	for idx, offset := range characters {
+		char := idx + 33
+		if offset != -1 {
+			available += string(char)
+		}
+	}
+	return
+}
+
+func ShowBlockFont(name string, bf *door.BlockFont) {
+	low, uniq, avail := FontInfo(bf.Characters)
+	fmt.Printf("Font: %s (LowerCase %t, Unique Lower %t, [%s]\n", name, low, uniq, avail)
+
+	output, _ := bf.Output("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
+	Show(output)
+
+	if low && uniq {
+		output, _ := bf.Output("abcdefghijklmnopqrstuvwxyz")
+		Show(output)
+	}
+
+	leftovers := avail
+	reg, _ := regexp.Compile("[a-zA-Z]+")
+	left := reg.ReplaceAllString(leftovers, "")
+	// output, _ = bf.Output("abcdef")
+	// Show(output)
+	if len(left) > 0 {
+		output, _ = bf.Output(left)
+		Show(output)
+	}
+}
+
+func ShowColorFont(name string, cf *door.ColorFont) {
+	low, uniq, avail := FontInfo(cf.Characters)
+	fmt.Printf("Font: %s (LowerCase %t, Unique Lower %t, [%s]\n", name, low, uniq, avail)
+
+	// fmt.Printf("Font: %s\n", name)
+	output, _ := cf.Output("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
+	Show(output)
+
+	if low && uniq {
+		output, _ := cf.Output("abcdefghijklmnopqrstuvwxyz")
+		Show(output)
+	}
+
+	leftovers := avail
+	reg, _ := regexp.Compile("[a-zA-Z]+")
+	left := reg.ReplaceAllString(leftovers, "")
+	// output, _ = bf.Output("abcdef")
+	// Show(output)
+	if len(left) > 0 {
+		output, _ = cf.Output(left)
+		Show(output)
+	}
+}
+
+// Example using FontOutput interface
+/*
+func ShowFont(name string, f FontOutput) {
+	var low, uniq bool
+	var avail string
+
+	v, ok := f.(BlockFont)
+	if ok {
+		low, uniq, avail := FontInfo(v.characters)
+	}
+
+	v, ok = f.(ColorFont)
+	if ok {
+		low, uniq, avail := FontInfo(v.characters)
+	}
+
+	// low, uniq, avail := FontInfo((*f).characters)
+	fmt.Printf("Font: %s (LowerCase %t, Unique Lower %t, [%s]\n", name, low, uniq, avail)
+
+	// fmt.Printf("Font: %s\n", name)
+	output, _ := f.Output("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
+	Show(output)
+
+	if low && uniq {
+		output, _ = f.Output("abcdefghijklmnopqrstuvwxyz")
+		Show(output)
+	}
+
+	leftovers := avail
+	reg, _ := regexp.Compile("[a-zA-Z]+")
+	left := reg.ReplaceAllString(leftovers, "")
+	//  output, _ = bf.Output("abcdef")
+	// Show(output)
+
+	// left := "0123456789!@#$%^&*()-=_+[]{}~"
+
+	if len(left) > 0 {
+		output, _ = f.Output(left)
+		Show(output)
+	}
+}
+*/
+
+// displays fonts
+func DisplayFonts(filename string, fonts []string) {
+	f, err := os.Open(filename)
+	if err != nil {
+		fmt.Printf("Open(%s): %s\n", filename, err)
+		panic(err)
+	}
+	defer f.Close()
+	tdfonts := make([]byte, 20)
+	f.Read(tdfonts)
+	for true {
+		fontdef := make([]byte, 4)
+		read, _ := f.Read(fontdef)
+		if read != 4 {
+			break
+		}
+		fontname := make([]byte, 13)
+		f.Read(fontname)
+		Name := strings.Trim(string(fontname[1:]), "\x00")
+		// fmt.Printf("Font: %s\n", Name)
+		f.Read(fontdef)
+		single := make([]byte, 1)
+
+		var FontType int8
+		binary.Read(f, binary.LittleEndian, &FontType)
+
+		// fmt.Printf("Font: %s (type %d)\n", Name, FontType)
+		f.Read(single) // Spacing
+
+		var BlockSize int16
+		binary.Read(f, binary.LittleEndian, &BlockSize)
+
+		letterOffsets := make([]uint16, 94)
+		binary.Read(f, binary.LittleEndian, &letterOffsets)
+
+		if false {
+			for idx, i := range letterOffsets {
+				fmt.Printf(" %04X", i)
+				if (idx+1)%10 == 0 {
+					fmt.Println("")
+				}
+			}
+			fmt.Println("")
+		}
+
+		data := make([]byte, BlockSize)
+		binary.Read(f, binary.LittleEndian, &data)
+
+		// The problem isn't that the offsets are > BlockSize
+		// Detect "truncated" fonts...
+		broken := false
+		for idx, i := range letterOffsets {
+			if i != 65535 {
+				if i >= uint16(BlockSize) {
+					broken = true
+					// Mark character offset as not used
+					letterOffsets[idx] = 65535
+					// fmt.Printf("offset %d / %x is out of range %d / %x\n", i, i, BlockSize, BlockSize)
+				}
+			}
+		}
+
+		if broken {
+			fmt.Println("FONT is corrupted/truncated.  FIX attempted.")
+		}
+
+		if FontFixup(letterOffsets, &data) {
+			fmt.Printf("Attempting to *FIX* Font %s\n", Name)
+		}
+
+		// Special case where they are asking for all fonts
+		if len(fonts) == 1 && fonts[0] == "*" {
+
+			switch FontType {
+			case 1:
+				bf := ExtractBlock(Name, letterOffsets, data)
+				if len(bf.Characters) == 0 {
+					fmt.Printf("%s : BLOCK FONT FAIL\n", Name)
+				} else {
+					// ShowFont(Name, &bf)
+					ShowBlockFont(Name, &bf)
+				}
+			case 2:
+				cf := ExtractColor(Name, letterOffsets, data)
+				if len(cf.Characters) == 0 {
+					fmt.Printf("%s : COLOR FONT FAIL\n", Name)
+				} else {
+					// ShowFont(Name, &cf)
+					ShowColorFont(Name, &cf)
+				}
+			default:
+				fmt.Printf("Sorry, I can't handle Font: %s Type %d!\n", Name, FontType)
+			}
+		} else {
+			for _, f := range fonts {
+				if Name == f {
+					switch FontType {
+					case 1:
+						bf := ExtractBlock(Name, letterOffsets, data)
+						if len(bf.Characters) == 0 {
+							fmt.Printf("%s : BLOCK FONT FAIL\n", Name)
+						} else {
+							ShowBlockFont(Name, &bf)
+						}
+					case 2:
+						cf := ExtractColor(Name, letterOffsets, data)
+						if len(cf.Characters) == 0 {
+							fmt.Printf("%s : COLOR FONT FAIL\n", Name)
+						} else {
+							ShowColorFont(Name, &cf)
+						}
+					default:
+						fmt.Printf("Sorry, I can't handle Font: %s Type %d!\n", Name, FontType)
+					}
+					break
+				}
+			}
+		}
+	}
+}
+
+/*
+func main() {
+	fmt.Println("Font-Show - A TDF (TheDraw Font) Viewer.")
+	var fonts string
+	var listFonts bool
+	var allFonts bool
+	door.Unicode = true
+
+	flag.StringVar(&fonts, "f", "", "Font(s) to show")
+	flag.BoolVar(&allFonts, "a", false, "Show All Fonts")
+	flag.BoolVar(&listFonts, "l", false, "List Fonts")
+	flag.Parse()
+
+	if flag.NArg() == 0 {
+		fmt.Println("I need a TDF filename.")
+		flag.PrintDefaults()
+		os.Exit(2)
+	}
+
+	var fontList []string
+	if len(fonts) > 0 {
+		fontList = strings.Split(fonts, ",")
+	}
+
+	if allFonts {
+		fontList = make([]string, 0)
+		fontList = append(fontList, "*")
+	}
+
+	for _, fontfile := range flag.Args() {
+		if listFonts {
+			ListFonts(fontfile)
+		}
+
+		if len(fontList) > 0 {
+			DisplayFonts(fontfile, fontList)
+		}
+	}
+}
+*/

+ 7 - 0
font-util/go.mod

@@ -0,0 +1,7 @@
+module red-green/font-util
+
+go 1.18
+
+replace red-green/door => ../door
+
+require red-green/door v0.0.0-00010101000000-000000000000

+ 248 - 0
font-util/utils.go

@@ -0,0 +1,248 @@
+package main
+
+import (
+	"encoding/binary"
+	"fmt"
+	"os"
+	"red-green/door"
+	"strings"
+)
+
+func ListFonts(filename string) {
+
+	f, err := os.Open(filename)
+	if err != nil {
+		fmt.Printf("Open(%s): %s\n", filename, err)
+		panic(err)
+	}
+	defer f.Close()
+	fmt.Println(filename, ":")
+
+	tdfonts := make([]byte, 20)
+	f.Read(tdfonts)
+	for true {
+		fontdef := make([]byte, 4)
+		read, _ := f.Read(fontdef)
+		if read != 4 {
+			break
+		}
+		fontname := make([]byte, 13)
+		f.Read(fontname)
+		Name := strings.Trim(string(fontname[1:]), "\x00")
+		// fmt.Printf("Font: %s\n", Name)
+		f.Read(fontdef)
+		single := make([]byte, 1)
+
+		var FontType int8
+		binary.Read(f, binary.LittleEndian, &FontType)
+
+		// f.Read(single) // FontType
+		// FontType := int(single[0])
+		fmt.Printf("Font: %s (type %d)\n", Name, FontType)
+		f.Read(single) // Spacing
+
+		// blocksize := make([]byte, 2)
+		// f.Read(blocksize)
+
+		var BlockSize int16
+		binary.Read(f, binary.LittleEndian, &BlockSize)
+		// fmt.Printf("Size: %d / %x\n", BlockSize, BlockSize)
+
+		letterOffsets := make([]int16, 94)
+		binary.Read(f, binary.LittleEndian, &letterOffsets)
+
+		if false {
+			for idx, i := range letterOffsets {
+				fmt.Printf(" %04X", i)
+				if (idx+1)%10 == 0 {
+					fmt.Println("")
+				}
+			}
+			fmt.Println("")
+		}
+
+		data := make([]byte, BlockSize)
+		binary.Read(f, binary.LittleEndian, &data)
+	}
+}
+
+func byte_to_text(line []byte) string {
+	var output string
+
+	for _, ch := range line {
+		output += fmt.Sprintf("0x%02x, ", ch)
+	}
+	if len(output) > 0 {
+		output = output[:len(output)-2]
+	}
+	return output
+}
+
+func text_to_hextext(line string) string {
+	var output string
+	// output = "\""
+	for _, ch := range []byte(line) {
+		// output += fmt.Sprintf("\\x%02x", ch)
+		output += fmt.Sprintf("0x%02x,", ch)
+	}
+	if len(output) > 1 {
+		output = output[:len(output)-1]
+	}
+	// output += "\""
+	return output
+}
+
+func ExtractColor(name string, offsets []uint16, data []byte) (Font door.ColorFont) {
+
+	defer func() {
+		if r := recover(); r != nil {
+			// Ok, this failed
+			Font = door.ColorFont{}
+		}
+	}()
+
+	var indexes []int
+	var blocks [][][]byte
+	var current [][]byte
+	var line []byte
+
+	pos := 0
+	for pos < len(data) {
+		indexes = append(indexes, pos)
+		current = make([][]byte, 0)
+		line = make([]byte, 0)
+
+		// We don't use these.
+		// w = data[pos]
+		// h = data[pos+1]
+
+		pos += 2
+
+		// process this character
+		for pos < len(data) {
+			ch := data[pos]
+			pos++
+			if ch == 0x00 {
+				// end of character
+				current = append(current, line)
+				blocks = append(blocks, current)
+				current = make([][]byte, 0)
+				line = make([]byte, 0)
+				break
+			}
+			if ch == 0x0d {
+				// end of this character line
+				current = append(current, line)
+				line = make([]byte, 0)
+				continue
+			}
+			if ch == 0x26 {
+				// & descender mark
+				continue
+			}
+			line = append(line, ch)
+			color := data[pos]
+			pos++
+			line = append(line, color)
+		}
+
+	}
+
+	// offset optimization:
+	var single []int
+	for _, o := range offsets {
+		if o == 65535 {
+			single = append(single, -1)
+			continue
+		}
+		found := false
+		for idx, i := range indexes {
+			if o == uint16(i) {
+				single = append(single, idx)
+				found = true
+				break
+			}
+		}
+		if !found {
+			panic(fmt.Sprintf("Unable to locate index %d / %x (font appears corrupted)", o, o))
+		}
+	}
+
+	return door.ColorFont{Characters: single, Data: blocks}
+}
+
+func ExtractBlock(name string, offsets []uint16, data []byte) (Font door.BlockFont) {
+
+	defer func() {
+		if r := recover(); r != nil {
+			// Ok, this failed
+			Font = door.BlockFont{}
+		}
+	}()
+
+	// fmt.Printf("Extract Block Font: %s\n", name)
+	var indexes []int
+	var blocks [][][]byte
+	var current [][]byte
+	var line []byte
+
+	pos := 0
+	for pos < len(data) {
+		indexes = append(indexes, pos)
+		current = make([][]byte, 0)
+		line = make([]byte, 0)
+
+		// We don't use these
+		// w = data[pos]
+		// h = data[pos+1]
+		pos += 2
+
+		// process this character
+		for pos < len(data) {
+			ch := data[pos]
+			pos++
+
+			if ch == 0x00 {
+				// end of character
+				current = append(current, line)
+				blocks = append(blocks, current)
+				current = make([][]byte, 0)
+				line = make([]byte, 0)
+				break
+			}
+			if ch == 0x0d {
+				// end of this character line
+				current = append(current, line)
+				line = make([]byte, 0)
+				continue
+			}
+			if ch == 0x26 {
+				// & descender mark
+				continue
+			}
+			line = append(line, ch)
+		}
+	}
+
+	// offset optimization:
+	var single []int
+	for _, o := range offsets {
+		if o == 65535 {
+			single = append(single, -1)
+			continue
+		}
+		found := false
+		for idx, i := range indexes {
+			if o == uint16(i) {
+				single = append(single, idx)
+				found = true
+				break
+			}
+		}
+		if !found {
+			panic(fmt.Sprintf("Unable to locate index %d / %x (font appears corrupted)", o, o))
+		}
+	}
+
+	return door.BlockFont{Characters: single, Data: blocks}
+}