package main import ( "encoding/binary" "flag" "fmt" "os" "red-green/door" "regexp" "strings" ) // Best used with | less -RS // Given a TheDrawFont file, display the fonts contained within. 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() tdfonts := make([]byte, 20) f.Read(tdfonts) for true { fontdef := make([]byte, 4) read, _ := f.Read(fontdef) if read != 4 { fmt.Println("*END*") 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) // 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("") } // Or possibly, seek past the character data data := make([]byte, BlockSize) binary.Read(f, binary.LittleEndian, &data) } } // Properly convert bytes to string // This is easy to get wrong, and string([]byte) might not work. // (Work right, that is.) 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)-1] } return output } // Convert string to hex values for initializing []byte{} func text_to_hextext(line string) string { var output string // output = "\"" for _, ch := range []byte(line) { output += fmt.Sprintf("0x%02x,", ch) } if len(output) > 1 { output = output[:len(output)-1] } // output += "\"" return output } // 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 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)) } } font := door.ColorFont{} font.Characters = single font.Data = blocks return font } 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)) } } font := door.BlockFont{} font.Characters = single font.Data = blocks return font } 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) } } */ func ExtractFonts(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 { ExtractFonts(fontfile, fontList) } } }