package main import ( "encoding/binary" "flag" "fmt" "os" "strconv" "strings" ) // http://www.roysac.com/blog/2014/04/thedraw-fonts-file-tdf-specifications/ 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)) copy(Targets[pos], 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 { 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() { var usage func() = func() { fmt.Println("Usage: font-util [] fontfile.tdf...") fmt.Println(" list - List available fonts") fmt.Println(" show - Show fonts") fmt.Println(" extract - Extract to source.go file") } if len(os.Args) == 1 { usage() return } var fonts string var defaultPackage string = "main" var allFonts bool var convert arrayFlags var output string var width int 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") showCommand.IntVar(&width, "w", 0, "Width to Show fonts") 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 { listCommand.Usage() 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: usage() 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 var exit bool if len(fontList) == 0 { showCommand.Usage() fmt.Println("No Fonts selected.") exit = true } if len(showCommand.Args()) == 0 { if !exit { showCommand.Usage() } fmt.Println("No TDF Fonts files given.") exit = true } if exit { os.Exit(2) } for _, fontfile := range showCommand.Args() { fmt.Println("FILE:", fontfile) DisplayFonts(fontfile, fontList, width) } } if extractCommand.Parsed() { // Extract Fonts var exit bool if len(fontList) == 0 { extractCommand.Usage() fmt.Println("No Fonts selected.") exit = true } if len(extractCommand.Args()) == 0 { if !exit { extractCommand.Usage() } fmt.Println("No TDF Fonts files given.") exit = true } if exit { 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) } } }