#!/usr/bin/env python2

# Quick and dirty conversion script for BDF fonts 
# will produce fixed or proportional arduino tvout
# fonts.
#
# see
#
# http://code.google.com/p/arduino-tvout/wiki/Fonts
#
# for details
#
# (C) 2012 by G. Bartsch
# License: GPLv3
#


import sys
import fileinput
import re
import math
from PIL import Image, ImageDraw
from optparse import OptionParser

#
# command line argument parsing
#

parser = OptionParser("usage: %prog [options] font.bdf")
parser.add_option("-n", "--name", dest="fontname", default="font",
                  help="Font name (default: 'font')", action="store", type="string")
parser.add_option("-f", "--fixed",
                  action="store_true", dest="fixed", default=False,
                  help="Generate fix-width font (default: variable width)")
parser.add_option("-s", "--start", dest="start_code", default=32,
                  help="Start from this character code (default:32)", action="store", type="int")
parser.add_option("-t", "--stop", dest="stop_code", default=126,
                  help="Stop at this character code (default: 126)", action="store", type="int")

(options, args) = parser.parse_args()

if len(args) != 1:
  parser.error("incorrect number of arguments")

#
# compute fixed width (== maximum width), start code, height, font bounding box
#

fixedwidth = 0
fbbxw = 0
fbbxh = 0
fbbxx = 0
fbbxy = 0
start_code = 255
height = 0

f = open(str(args[0]), 'r')

for line in f:

  line = line.rstrip()

  res = re.match("DWIDTH\s+(\d+)\s+(\d+)", line)
  if res:
    w = int(res.group(1))
    if w > fixedwidth:
      fixedwidth = w
    continue

  res = re.match("BBX\s+(\d+)\s+(\d+)\s+([0-9\-+]+)\s+([0-9\-+]+)", line)
  if res:
    bbxw = int(res.group(1))
    bbxh = int(res.group(2))
    bbxx = int(res.group(3))
    bbxy = int(res.group(4))
    w = bbxx + bbxw - fbbxx + 1
    if w > fixedwidth:
      fixedwidth = w
    continue

  res = re.match("FONTBOUNDINGBOX\s+(\d+)\s+(\d+)\s+([0-9\-+]+)\s+([0-9\-+]+)", line)
  if res:
    fbbxw = int(res.group(1))
    fbbxh = int(res.group(2))
    fbbxx = int(res.group(3))
    fbbxy = int(res.group(4))
    continue

  res = re.match("ENCODING\s+(\d+)", line)
  if res:
    code = int(res.group(1))
    if code < start_code:
      start_code = code
    continue

  res = re.match("SIZE\s+(\d+)\s+(\d+)", line)
  if res:
    height = int(res.group(1))
    continue
f.close

if start_code < options.start_code:
  start_code = options.start_code

if fbbxh > height:
  height = fbbxh

fontname = options.fontname + str(height)

#
# generate header
#

fheader = open (fontname + ".h", "w")

hvar = fontname.upper() + "_H"

fheader.write ('#ifndef ' + hvar + "\n")
fheader.write ('#define ' + hvar + "\n\n")
fheader.write ('#include <avr/pgmspace.h>\n\n')
fheader.write ('extern const unsigned char ' + fontname + '[];\n\n');
fheader.write ('#endif\n')

fheader.close()

#
# conversion
#

fcode = open (fontname + ".c", "w")

fcode.write ("#include \"" + fontname +".h\"\n\n")
fcode.write ("PROGMEM const unsigned char " + fontname + "[] = {\n")

if options.fixed:
  fcode.write ("%d,%d,%d\n" % (fixedwidth, height, start_code))
else:
  fcode.write ("0,%d,%d\n" % (height, start_code))

f = open(str(args[0]), 'r')

inbitmap = False

width = 0
code = 0
data = []
row = 0
bbxw = 0
bbxh = 0
first = True
linewidth = 0

for line in f:

  line = line.rstrip()

  res = re.match("ENCODING\s+(\d+)", line)
  if res:
    code = int(res.group(1))
    if code >options.stop_code:
      break
    continue

  res = re.match("DWIDTH\s+(\d+)\s+(\d+)", line)
  if res:
    width = int(res.group(1))
    continue

  res = re.match("BBX\s+(\d+)\s+(\d+)\s+([0-9\-+]+)\s+([0-9\-+]+)", line)
  if res:
    bbxw   = int(res.group(1))
    bbxh   = int(res.group(2))
    bbxx  = int(res.group(3))
    bbxy = int(res.group(4))
    w = bbxx + bbxw - fbbxx + 1
    if w > width:
      width = w
    continue


  if re.match ("BITMAP", line):

    print "\nCode: %d '%s', width: %d, bbxx: %d, bbxy: %d, bbxw:%d, bbxh: %d, fbbxy: %d, height: %d" % (code, chr(code), width, bbxx, bbxy, bbxw, bbxh, fbbxy, height)

    linewidth = width
    if options.fixed:
      linewidth = fixedwidth

    im = Image.new("1", (linewidth, height), "white")

    row = 0

    inbitmap = True
    continue

  if re.match ("ENDCHAR", line):
    inbitmap = False

    if code >= start_code and code <= options.stop_code:
      fcode.write ("\n// Code: %d \n" % (code))
      if not options.fixed:
        fcode.write (", %d\n" % width)
  
      for y in range (0, height):
  
        bc = 0
        v = 0
        bit = 0x80
  
        for x in range (0, linewidth):
  
  
          px = im.getpixel((x,y)) == 0
  
          if px:
            v = v | bit
            print "X",
          else:
            print ".",
  
          bc = bc + 1
          bit = bit >> 1
          if bc == 8:
            fcode.write (", 0x%02x" % v)
            print (" 0x%02x " % v),
            bc = 0
            v = 0
	    bit = 0x80
  
        if bc > 0:
          fcode.write (", 0x%02x" % v)
          print (" 0x%02x " % v),
  
        fcode.write("\n")
        print

  if inbitmap:

    xcnt = bbxx
    bc = 0
    cc = 0
    v = 0

    while xcnt < bbxx + bbxw:

      if bc == 0:
        if cc < len(line):
          v = int (line[cc], 16)
        else:
          v = 0
        cc = cc + 1
        bc = 4

      if (v >> 3) & 1:
        x = xcnt - fbbxx
	y = row + height - bbxy + fbbxy - bbxh

        if (x>=0) and (x<=linewidth) and (y>=0) and (y<=height):
          im.putpixel ((x,y), 0) 

      v = v << 1

      bc = bc - 1
      xcnt = xcnt + 1
      
    row = row + 1

fcode.write ("};\n")
fcode.close()

print "\n\nAll done. Font height is %d. Generated files: %s.h and %s.c" % (height, fontname, fontname)

