font-show.go 12 KB

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