#!/usr/bin/env python
# convert-font: convert a BDF font into an occam source fragment
#
# Copyright (C) 2008, 2009 Adam Sampson <ats@offog.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.

import sys, os, getopt

first = 0
last = 255
include_size = True
word_size = 32
sideways = False

def dump(data):
	print data
	for line in data:
		print "".join([str((line >> (word_size - i)) & 1) for i in range(word_size)])

def rotate(data, width, height):
	if width != height:
		print "Font is not square"
		sys.exit(1)
	out = []
	for y in range(height):
		val = 0
		for x in range(width):
			val |= ((data[x] >> (word_size - 1 - y)) & 1) << x
		out.append(val)
	return out

def convert(fn, name):
	width = None
	height = None
	comment_lines = []
	chars = {}

	f = open(fn)

	while 1:
		l = f.readline()
		if l == "":
			break
		l = l[:-1]

		fs = l.split()
		if fs == []:
			continue
		cmd = fs[0]
		if cmd in ("FONT", "COMMENT", "COPYRIGHT"):
			comment_lines.append(l)
		elif cmd == "FONTBOUNDINGBOX":
			width, height = int(fs[1]), int(fs[2])
		elif cmd == "STARTCHAR":
			code = None

			while cmd != "ENDCHAR":
				l = f.readline()
				if l == "":
					print "EOF in character"
					sys.exit(1)
				l = l[:-1]

				fs = l.split()
				cmd = fs[0]
				if cmd == "ENCODING":
					code = int(fs[1])
				elif cmd == "BBX":
					if int(fs[1]) != width or int(fs[2]) != height:
						print "Character %d has mismatched size" % code
						sys.exit(1)
				elif cmd == "BITMAP":
					# Bitmap data is MSB-aligned in a
					# variable number of hex digits; we
					# want it MSB-aligned in our word size.
					lines = [f.readline().strip() for i in range(height)]
					shift = word_size - (len(lines[0]) * 4)
					chars[code] = [int(line, 16) << shift for line in lines]

					if sideways:
						chars[code] = rotate(chars[code], width, height)
				elif cmd == "ENDCHAR":
					break

	f.close()

	if width > 32:
		print "Font is too wide (max 32 pixels)"
		sys.exit(1)

	def format_num(n):
		if n == 0:
			return "0"
		elif word_size == 8:
			return "#%02X" % n
		else:
			return "#%08X" % n
	def convert_char(code):
		char = chars.get(code, [0 for i in range(height)])
		return ", ".join(map(format_num, char))

	f = sys.stdout
	f.write("-- Font converted from %s by convert-font; details:\n" % os.path.basename(fn))
	for l in comment_lines:
		f.write("--   %s\n" % l)

	if word_size == 8:
		el_type = "BYTE"
	else:
		el_type = "INT"
	f.write("VAL []%s %s IS\n" % (el_type, name))
	if include_size:
		f.write("  [%d, %d,\n" % (width, height))
	for char in range(first, last + 1):
		if char == first and not include_size:
			prefix = "  ["
		else:
			prefix = "   "
		if char == last:
			suffix = "]:"
		else:
			suffix = ","
		if char >= 32 and char < 127:
			printable_char = chr(char)
		else:
			printable_char = " "
		f.write("%s%s%s -- %s %d\n"
		        % (prefix, convert_char(char), suffix,
		           printable_char, char))
	f.write("\n")

def usage(f):
	print >>f, """convert-font
Convert BDF fonts into occam constant definitions
Usage: convert-font [OPTIONS] BDF-FILE VARIABLE-NAME

Options:
  --first CHAR        Start from character CHAR (default 0)
  --last CHAR         End at character CHAR (default 255)
  --no-size           Don't include the width and height as the first two
                      elements of the array
  --bytes             Output a VAL []BYTE rather than VAL []INT
  --sideways          Rotate the characters anticlockwise 90 degrees

  --led-array         Equivalent to:
                        --first 65 --last 90 --no-size --bytes --sideways

  --help              Show usage

Report bugs to <kroc-bugs@kent.ac.uk>."""

if __name__ == "__main__":
	long_opts = [
		"first=", "last=", "no-size", "bytes", "sideways",
		"led-array",
		"help"
		]
	try:
		opts, args = getopt.getopt(sys.argv[1:], "", long_opts)
	except getopt.GetoptError:
		usage(sys.stderr)
		sys.exit(1)

	for o, a in opts:
		if o == "--help":
			usage(sys.stdout)
			sys.exit(0)
		elif o == "--first":
			first = int(a)
		elif o == "--last":
			last = int(a)
		elif o == "--no-size":
			include_size = False
		elif o == "--bytes":
			word_size = 8
		elif o == "--sideways":
			sideways = True
		elif o == "--led-array":
			first = 65
			last = 90
			include_size = False
			word_size = 8
			sideways = True

	if len(args) != 2:
		print "Usage: convert-font BDF-FILE NAME"
		sys.exit(1)

	convert(args[0], args[1])
