font-show.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. package main
  2. import (
  3. "encoding/binary"
  4. "flag"
  5. "fmt"
  6. "os"
  7. "red-green/door"
  8. "regexp"
  9. "strings"
  10. )
  11. // Given a TheDrawFont file, display the fonts contained within.
  12. func ListFonts(filename string) {
  13. f, err := os.Open(filename)
  14. if err != nil {
  15. fmt.Printf("Open(%s): %s\n", filename, err)
  16. panic(err)
  17. }
  18. defer f.Close()
  19. tdfonts := make([]byte, 20)
  20. f.Read(tdfonts)
  21. for true {
  22. fontdef := make([]byte, 4)
  23. read, _ := f.Read(fontdef)
  24. if read != 4 {
  25. fmt.Println("*END*")
  26. break
  27. }
  28. fontname := make([]byte, 13)
  29. f.Read(fontname)
  30. Name := strings.Trim(string(fontname[1:]), "\x00")
  31. // fmt.Printf("Font: %s\n", Name)
  32. f.Read(fontdef)
  33. single := make([]byte, 1)
  34. var FontType int8
  35. binary.Read(f, binary.LittleEndian, &FontType)
  36. fmt.Printf("Font: %s (type %d)\n", Name, FontType)
  37. f.Read(single) // Spacing
  38. var BlockSize int16
  39. binary.Read(f, binary.LittleEndian, &BlockSize)
  40. // fmt.Printf("Size: %d / %x\n", BlockSize, BlockSize)
  41. letterOffsets := make([]int16, 94)
  42. binary.Read(f, binary.LittleEndian, &letterOffsets)
  43. if false {
  44. for idx, i := range letterOffsets {
  45. fmt.Printf(" %04X", i)
  46. if (idx+1)%10 == 0 {
  47. fmt.Println("")
  48. }
  49. }
  50. fmt.Println("")
  51. }
  52. // Or possibly, seek past the character data
  53. data := make([]byte, BlockSize)
  54. binary.Read(f, binary.LittleEndian, &data)
  55. }
  56. }
  57. // Properly convert bytes to string
  58. // This is easy to get wrong, and string([]byte) might not work.
  59. // (Work right, that is.)
  60. func byte_to_text(line []byte) string {
  61. var output string
  62. for _, ch := range line {
  63. output += fmt.Sprintf("0x%02x,", ch)
  64. }
  65. if len(output) > 0 {
  66. output = output[:len(output)-1]
  67. }
  68. return output
  69. }
  70. // Convert string to hex values for initializing []byte{}
  71. func text_to_hextext(line string) string {
  72. var output string
  73. // output = "\""
  74. for _, ch := range []byte(line) {
  75. output += fmt.Sprintf("0x%02x,", ch)
  76. }
  77. if len(output) > 1 {
  78. output = output[:len(output)-1]
  79. }
  80. // output += "\""
  81. return output
  82. }
  83. // Attempt to fix broken fonts.
  84. // This verifies that the character offsets are proceeded
  85. // by a null character.
  86. func FontFixup(offsets []uint16, data *[]byte) bool {
  87. fixed := false
  88. for _, offset := range offsets {
  89. if offset == 65535 {
  90. continue
  91. }
  92. if offset == 0 {
  93. continue
  94. }
  95. if (*data)[offset-1] != 0 {
  96. (*data)[offset-1] = 0
  97. fixed = true
  98. }
  99. }
  100. return fixed
  101. }
  102. func ExtractColor(name string, offsets []uint16, data []byte) (Font door.ColorFont) {
  103. defer func() {
  104. if r := recover(); r != nil {
  105. // Ok, this failed
  106. Font = door.ColorFont{}
  107. }
  108. }()
  109. var indexes []int
  110. var blocks [][][]byte
  111. var current [][]byte
  112. var line []byte
  113. pos := 0
  114. for pos < len(data) {
  115. indexes = append(indexes, pos)
  116. current = make([][]byte, 0)
  117. line = make([]byte, 0)
  118. // We don't use these.
  119. // w = data[pos]
  120. // h = data[pos+1]
  121. pos += 2
  122. // process this character
  123. for pos < len(data) {
  124. ch := data[pos]
  125. pos++
  126. if ch == 0x00 {
  127. // end of character
  128. current = append(current, line)
  129. blocks = append(blocks, current)
  130. current = make([][]byte, 0)
  131. line = make([]byte, 0)
  132. break
  133. }
  134. if ch == 0x0d {
  135. // end of this character line
  136. current = append(current, line)
  137. line = make([]byte, 0)
  138. continue
  139. }
  140. if ch == 0x26 {
  141. // & descender mark
  142. continue
  143. }
  144. line = append(line, ch)
  145. color := data[pos]
  146. pos++
  147. line = append(line, color)
  148. }
  149. }
  150. // offset optimization:
  151. var single []int
  152. for _, o := range offsets {
  153. if o == 65535 {
  154. single = append(single, -1)
  155. continue
  156. }
  157. found := false
  158. for idx, i := range indexes {
  159. if o == uint16(i) {
  160. single = append(single, idx)
  161. found = true
  162. break
  163. }
  164. }
  165. if !found {
  166. panic(fmt.Sprintf("Unable to locate index %d / %x (font appears corrupted)", o, o))
  167. }
  168. }
  169. font := door.ColorFont{}
  170. font.Characters = single
  171. font.Data = blocks
  172. return font
  173. }
  174. func ExtractBlock(name string, offsets []uint16, data []byte) (Font door.BlockFont) {
  175. defer func() {
  176. if r := recover(); r != nil {
  177. // Ok, this failed
  178. Font = door.BlockFont{}
  179. }
  180. }()
  181. // fmt.Printf("Extract Block Font: %s\n", name)
  182. var indexes []int
  183. var blocks [][][]byte
  184. var current [][]byte
  185. var line []byte
  186. pos := 0
  187. for pos < len(data) {
  188. indexes = append(indexes, pos)
  189. current = make([][]byte, 0)
  190. line = make([]byte, 0)
  191. // We don't use these
  192. // w = data[pos]
  193. // h = data[pos+1]
  194. pos += 2
  195. // process this character
  196. for pos < len(data) {
  197. ch := data[pos]
  198. pos++
  199. if ch == 0x00 {
  200. // end of character
  201. current = append(current, line)
  202. blocks = append(blocks, current)
  203. current = make([][]byte, 0)
  204. line = make([]byte, 0)
  205. break
  206. }
  207. if ch == 0x0d {
  208. // end of this character line
  209. current = append(current, line)
  210. line = make([]byte, 0)
  211. continue
  212. }
  213. if ch == 0x26 {
  214. // & descender mark
  215. continue
  216. }
  217. line = append(line, ch)
  218. }
  219. }
  220. // offset optimization:
  221. var single []int
  222. for _, o := range offsets {
  223. if o == 65535 {
  224. single = append(single, -1)
  225. continue
  226. }
  227. found := false
  228. for idx, i := range indexes {
  229. if o == uint16(i) {
  230. single = append(single, idx)
  231. found = true
  232. break
  233. }
  234. }
  235. if !found {
  236. panic(fmt.Sprintf("Unable to locate index %d / %x (font appears corrupted)", o, o))
  237. }
  238. }
  239. font := door.BlockFont{}
  240. font.Characters = single
  241. font.Data = blocks
  242. return font
  243. }
  244. func Show(parts [][]byte) {
  245. reset := "\x1b[0m"
  246. if len(parts) > 0 {
  247. for _, line := range parts {
  248. fmt.Printf("%s%s\n", door.CP437_to_Unicode(string(line)), reset)
  249. }
  250. fmt.Println("")
  251. }
  252. }
  253. // Examine character indexes, are there lower case letter? are they unique?
  254. // What are the available characters in this font?
  255. func FontInfo(characters []int) (lower bool, lowerUnique bool, available string) {
  256. // Does it have lowercase?
  257. // Is lowercase unique?
  258. // Available characters in font
  259. // 33 -126
  260. lower = characters[int(rune('a')-33)] != -1
  261. lowerUnique = characters[int(rune('A')-33)] != characters[int(rune('a')-33)]
  262. for idx, offset := range characters {
  263. char := idx + 33
  264. if offset != -1 {
  265. available += string(char)
  266. }
  267. }
  268. return
  269. }
  270. func ShowBlockFont(name string, bf *door.BlockFont) {
  271. low, uniq, avail := FontInfo(bf.Characters)
  272. fmt.Printf("Font: %s (LowerCase %t, Unique Lower %t, [%s]\n", name, low, uniq, avail)
  273. output, _ := bf.Output("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
  274. Show(output)
  275. if low && uniq {
  276. output, _ := bf.Output("abcdefghijklmnopqrstuvwxyz")
  277. Show(output)
  278. }
  279. leftovers := avail
  280. reg, _ := regexp.Compile("[a-zA-Z]+")
  281. left := reg.ReplaceAllString(leftovers, "")
  282. // output, _ = bf.Output("abcdef")
  283. // Show(output)
  284. if len(left) > 0 {
  285. output, _ = bf.Output(left)
  286. Show(output)
  287. }
  288. }
  289. func ShowColorFont(name string, cf *door.ColorFont) {
  290. low, uniq, avail := FontInfo(cf.Characters)
  291. fmt.Printf("Font: %s (LowerCase %t, Unique Lower %t, [%s]\n", name, low, uniq, avail)
  292. // fmt.Printf("Font: %s\n", name)
  293. output, _ := cf.Output("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
  294. Show(output)
  295. if low && uniq {
  296. output, _ := cf.Output("abcdefghijklmnopqrstuvwxyz")
  297. Show(output)
  298. }
  299. leftovers := avail
  300. reg, _ := regexp.Compile("[a-zA-Z]+")
  301. left := reg.ReplaceAllString(leftovers, "")
  302. // output, _ = bf.Output("abcdef")
  303. // Show(output)
  304. if len(left) > 0 {
  305. output, _ = cf.Output(left)
  306. Show(output)
  307. }
  308. }
  309. // Example using FontOutput interface
  310. /*
  311. func ShowFont(name string, f FontOutput) {
  312. var low, uniq bool
  313. var avail string
  314. v, ok := f.(BlockFont)
  315. if ok {
  316. low, uniq, avail := FontInfo(v.characters)
  317. }
  318. v, ok = f.(ColorFont)
  319. if ok {
  320. low, uniq, avail := FontInfo(v.characters)
  321. }
  322. // low, uniq, avail := FontInfo((*f).characters)
  323. fmt.Printf("Font: %s (LowerCase %t, Unique Lower %t, [%s]\n", name, low, uniq, avail)
  324. // fmt.Printf("Font: %s\n", name)
  325. output, _ := f.Output("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
  326. Show(output)
  327. if low && uniq {
  328. output, _ = f.Output("abcdefghijklmnopqrstuvwxyz")
  329. Show(output)
  330. }
  331. leftovers := avail
  332. reg, _ := regexp.Compile("[a-zA-Z]+")
  333. left := reg.ReplaceAllString(leftovers, "")
  334. // output, _ = bf.Output("abcdef")
  335. // Show(output)
  336. // left := "0123456789!@#$%^&*()-=_+[]{}~"
  337. if len(left) > 0 {
  338. output, _ = f.Output(left)
  339. Show(output)
  340. }
  341. }
  342. */
  343. func ExtractFonts(filename string, fonts []string) {
  344. f, err := os.Open(filename)
  345. if err != nil {
  346. fmt.Printf("Open(%s): %s\n", filename, err)
  347. panic(err)
  348. }
  349. defer f.Close()
  350. tdfonts := make([]byte, 20)
  351. f.Read(tdfonts)
  352. for true {
  353. fontdef := make([]byte, 4)
  354. read, _ := f.Read(fontdef)
  355. if read != 4 {
  356. break
  357. }
  358. fontname := make([]byte, 13)
  359. f.Read(fontname)
  360. Name := strings.Trim(string(fontname[1:]), "\x00")
  361. // fmt.Printf("Font: %s\n", Name)
  362. f.Read(fontdef)
  363. single := make([]byte, 1)
  364. var FontType int8
  365. binary.Read(f, binary.LittleEndian, &FontType)
  366. // fmt.Printf("Font: %s (type %d)\n", Name, FontType)
  367. f.Read(single) // Spacing
  368. var BlockSize int16
  369. binary.Read(f, binary.LittleEndian, &BlockSize)
  370. letterOffsets := make([]uint16, 94)
  371. binary.Read(f, binary.LittleEndian, &letterOffsets)
  372. if false {
  373. for idx, i := range letterOffsets {
  374. fmt.Printf(" %04X", i)
  375. if (idx+1)%10 == 0 {
  376. fmt.Println("")
  377. }
  378. }
  379. fmt.Println("")
  380. }
  381. data := make([]byte, BlockSize)
  382. binary.Read(f, binary.LittleEndian, &data)
  383. // The problem isn't that the offsets are > BlockSize
  384. // Detect "truncated" fonts...
  385. broken := false
  386. for idx, i := range letterOffsets {
  387. if i != 65535 {
  388. if i >= uint16(BlockSize) {
  389. broken = true
  390. // Mark character offset as not used
  391. letterOffsets[idx] = 65535
  392. // fmt.Printf("offset %d / %x is out of range %d / %x\n", i, i, BlockSize, BlockSize)
  393. }
  394. }
  395. }
  396. if broken {
  397. fmt.Println("FONT is corrupted/truncated. FIX attempted.")
  398. }
  399. if FontFixup(letterOffsets, &data) {
  400. fmt.Printf("Attempting to *FIX* Font %s\n", Name)
  401. }
  402. // Special case where they are asking for all fonts
  403. if len(fonts) == 1 && fonts[0] == "*" {
  404. switch FontType {
  405. case 1:
  406. bf := ExtractBlock(Name, letterOffsets, data)
  407. if len(bf.Characters) == 0 {
  408. fmt.Printf("%s : BLOCK FONT FAIL\n", Name)
  409. } else {
  410. // ShowFont(Name, &bf)
  411. ShowBlockFont(Name, &bf)
  412. }
  413. case 2:
  414. cf := ExtractColor(Name, letterOffsets, data)
  415. if len(cf.Characters) == 0 {
  416. fmt.Printf("%s : COLOR FONT FAIL\n", Name)
  417. } else {
  418. // ShowFont(Name, &cf)
  419. ShowColorFont(Name, &cf)
  420. }
  421. default:
  422. fmt.Printf("Sorry, I can't handle Font: %s Type %d!\n", Name, FontType)
  423. }
  424. } else {
  425. for _, f := range fonts {
  426. if Name == f {
  427. switch FontType {
  428. case 1:
  429. bf := ExtractBlock(Name, letterOffsets, data)
  430. if len(bf.Characters) == 0 {
  431. fmt.Printf("%s : BLOCK FONT FAIL\n", Name)
  432. } else {
  433. ShowBlockFont(Name, &bf)
  434. }
  435. case 2:
  436. cf := ExtractColor(Name, letterOffsets, data)
  437. if len(cf.Characters) == 0 {
  438. fmt.Printf("%s : COLOR FONT FAIL\n", Name)
  439. } else {
  440. ShowColorFont(Name, &cf)
  441. }
  442. default:
  443. fmt.Printf("Sorry, I can't handle Font: %s Type %d!\n", Name, FontType)
  444. }
  445. break
  446. }
  447. }
  448. }
  449. }
  450. }
  451. func main() {
  452. fmt.Println("Font-Show - A TDF (TheDraw Font) Viewer.")
  453. var fonts string
  454. var listFonts bool
  455. var allFonts bool
  456. flag.StringVar(&fonts, "f", "", "Font(s) to show")
  457. flag.BoolVar(&allFonts, "a", false, "Show All Fonts")
  458. flag.BoolVar(&listFonts, "l", false, "List Fonts")
  459. flag.Parse()
  460. if flag.NArg() == 0 {
  461. fmt.Println("I need a TDF filename.")
  462. flag.PrintDefaults()
  463. os.Exit(2)
  464. }
  465. var fontList []string
  466. if len(fonts) > 0 {
  467. fontList = strings.Split(fonts, ",")
  468. }
  469. if allFonts {
  470. fontList = make([]string, 0)
  471. fontList = append(fontList, "*")
  472. }
  473. for _, fontfile := range flag.Args() {
  474. if listFonts {
  475. ListFonts(fontfile)
  476. }
  477. if len(fontList) > 0 {
  478. ExtractFonts(fontfile, fontList)
  479. }
  480. }
  481. }