#!/usr/bin/env python3
# -*- coding: utf-8 -*-

'''
This script converts a bdf-font to a c-source-file containing
selected glyphs in the format defined by the <fb/font.h> header.
'''

# (C) 2010 by Christian Vogel <vogelchr@vogel.cx>
#
# All Rights Reserved
#
# 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.

from optparse import OptionParser
import sys
import os

def unique_name(thisname,existingnames) :
	# return first of thisname, thisname_1, thisname_2, ...
	# that does not yet exist in existingnames. This is used
	# because somethings glyphs with non-unique names exist
	# in fonts!
	retname=thisname
	N=1
	while retname in existingnames :
		N=N+1
		retname='%s_%d'%(thisname,N)
	return retname


# return number N (for a character), optionally including
# the ascii character
def ascii_charnum(n) :
	if n >= 32 and n < 127 :
		if n != 34 : # """ looks stupid
			return '(%d, ASCII "%s")'%(n,chr(n))
		else :
			return '(%d, ASCII \'%s\')'%(n,chr(n))
	return '(%d)'%(n)

def is_zeroes(s) :
	# check if list s consists entirely of "00" strings
	# (used to detect empty lines in fonts)
	for x in s :
		if s != '00' :
			return False
	return True

def byte_to_bits(x) :
	# convert byte x to a string representing the bits #=1, .=0
	# used for drawing pretty pictures in the comments of the
	# generated C-file
	ret = ''
	for i in range(8) :
		if x & 1<<(7-i) :
			ret = ret + '#'
		else :
			ret = ret + '.'
	return ret

class BDF_Font(object) :
	# this class stores a read-in bdf font
	def __init__(self,filename) :
		self.filename = filename
		self.glyphs = dict()
		self.enc = dict()
		self.height = None
		self.registry = None
		self.encoding = None
		self.ascent = 0
		self.descent = 0
		self.read_font(filename)

	def add_header(self,data) :
		#print 'Header data: ',data
		self.registry = data.get('charset_registry','none')
		self.encoding = data.get('charset_encoding','unknown')
		self.ascent = int(data['font_ascent'])
		self.descent = int(data['font_descent'])
		bbx = data['fontboundingbox'].split(None,3)
		self.height = int(bbx[1])

	def add_glyph(self,charname,data,bitmap) :
		chnum = int(data['encoding'])
#		print 'add_glyph(%s) -> %s'%(charname,ascii_charnum(chnum))
		self.enc[chnum] = charname
		self.glyphs[charname] = data
		self.glyphs[charname]['bitmap']=bitmap

	def read_font(self,filename) :
		f = file(filename)

		hdr_data = dict()
		# read in header
		for l in f :
			l = l.strip()
			if l == '' :
				continue
			arr = l.split(None,1)
			if len(arr) > 1 :
				hdr_data[ arr[0].lower() ] = arr[1]
			if arr[0].lower() == 'chars' :
				break

		self.add_header(hdr_data)

		# now read in characters
		inchar = None
		data = dict() # store glyph data
		bitmap = None
		for l in f :
			l = l.strip()
			if l == '' :
				continue

			# waiting for next glyph
			if inchar == None :
				if l.lower() == 'endfont' :
					break # end of font :-)
				arr = l.split(None,1)
				if len(arr) < 2 and \
				    arr[0].lower() != 'STARTCHAR' :
					print('Not start of glyph: %s'%(l), file=sys.stderr)
					continue
				inchar = unique_name(arr[1],self.glyphs)
				continue

			# ENDCHAR always ends the glyph
			if l.lower() == 'endchar' :
				self.add_glyph(inchar,data,bitmap)
				inchar = None
				bitmap = None
				data = dict()
				continue

			# in bitmap
			if bitmap != None :
				bitmap.append(l)
				continue

			# else: metadata for this glyph
			arr = l.split(None,1)

			if arr[0].lower() == 'bitmap' :
				bitmap = list() # start collecting pixels
				continue

			if len(arr) < 2 :
				print('Bad line in font: %s'%(l), file=sys.stderr)
				continue
			data[arr[0].lower()] = arr[1]

if __name__ == '__main__' :
	P = OptionParser(usage='%prog [options] bdf-file')
	P.add_option('-o','--out',action='store', dest='out', default=None,
		metavar='FILE',help='write .c-code representing font to FILE')
	P.add_option('-b','--base',action='store',dest='base',default=None,
		metavar='base_symbol',help='prefix for all generated symbols')
	P.add_option('-f','--firstchar',action='store',dest='firstchar',type="int",
		metavar='N',default=None,help='numeric value of first char')
	P.add_option('-l','--lastchar',action='store',dest='lastchar',type="int",
		metavar='N',default=None,help='numeric value of last char')

	opts,args = P.parse_args()

	if len(args) != 1 :
		P.error('Please specify (exactly one) bdf input file.')

	font = BDF_Font(args[0])

	if opts.firstchar == None :
		opts.firstchar = min(font.enc)
		print('First character in font: %d, %s'%(opts.firstchar,
			font.enc[opts.firstchar]))

	if opts.lastchar == None :
		opts.lastchar = max(font.enc)
		print('Last character in font: %d, %s'%(opts.lastchar,
			font.enc[opts.lastchar]))

	if opts.base == None :
		opts.base = 'font_'+os.path.basename(args[0])
		if opts.base[-4:] == '.bdf' :
			opts.base = opts.base[:-4]
		print('Guessing symbol prefix to be %s.'%(opts.base), file=sys.stderr)

	if opts.out == None :
		opts.out = os.path.basename(args[0])
		if opts.out[-4:] == '.bdf' :
			opts.out = opts.out[:-4]
		opts.out = opts.out + '.c'
		print('Guessing output filename to be %s.'%(opts.out), file=sys.stderr)

		if os.path.exists(opts.out) :
			print('Will *NOT* overwrite existing file when guessing output!', file=sys.stderr)
			sys.exit(1)

	of = file(opts.out,'w')
	
	print('#include <fb/font.h>', file=of)
	print('/* file autogenerated by %s */' %(sys.argv[0]), file=of)

	offsets = list()
	glyphnames = list()

	print('static const uint8_t %s_data[] = {'%(opts.base), file=of)

	pos = 0

	# output font data, build up per-character information

	for i in range(opts.firstchar,opts.lastchar+1) :
		if not i in font.enc :
			offsets.append(0xffff)
			glyphnames.append('(no glyph)')
			continue

		charname = font.enc[i]
		glyphnames.append('%s %s'%(charname,ascii_charnum(i)))
		offsets.append(pos)
		glyph = font.glyphs[charname]
		bbx = map(int,glyph['bbx'].split(None,3))
		bitmap = glyph['bitmap']

		if bbx[1] != len(bitmap) :
			print('ERROR: glyph',charname,'has wrong number of lines of data!', file=sys.stderr)
			print(' want: ',bbx[1],'but have',len(bitmap), file=sys.stderr)
			sys.exit(1)

		removedrows = 0

		while len(bitmap) > 1 and is_zeroes(bitmap[0]) :
			removedrows = removedrows + 1
			bbx[1] = bbx[1] - 1 # decrease height
			bitmap = bitmap[1:]

		while len(bitmap) > 1 and is_zeroes(bitmap[-1]) :
			removedrows = removedrows + 1
			bbx[1] = bbx[1] - 1  # decrease height
			bbx[3] = bbx[3] + 1  # increase y0
			bitmap = bitmap[:-1]

		if removedrows > 0 :
			print("Glyph %s: removed %d rows."%(charname,removedrows))

		w = int(glyph['dwidth'].split(None,1)[0])

		print('/* --- new character %s %s starting at offset 0x%04x --- */'%(
				charname,ascii_charnum(i),pos), file=of)
		print('\t/*%04x:*/\t%d, %d, %d, %d, %d, /* width and bbox (w,h,x,y) */'%(
			pos,w,bbx[0],bbx[1],bbx[2],bbx[3]), file=of)

		pos += 5

		for k,l in enumerate(bitmap) :
			bytes = [ int(l[i:i+2],16) for i in range(0,len(l),2) ]
			if len(bytes) != (bbx[0]+7)/8 :
				print('ERROR: glyph',charname,'has wrong # of bytes', file=sys.stderr)
				print(' per line. Want',(bbx[0]+7)/8,'have',len(bytes), file=sys.stderr)
				sys.exit(1)
			cdata = ','.join([ '0x%02x'%v for v in bytes ])
			comment = ''.join([ byte_to_bits(b) for b in bytes ])
			print('\t/*%04x:*/\t'%(pos)+cdata+',  /* '+comment+' */', file=of)
			pos += len(bytes)

	print("};", file=of)

	x = ',\n\t'.join(['0x%04x /* %s */'%(w,n) for w,n in zip(offsets,glyphnames)])
	print('static const uint16_t %s_offsets[] = {\n\t%s\n};'%(opts.base,x), file=of)

	height = font.ascent + font.descent

	print('const struct fb_font %s = {'%(opts.base), file=of)
	print('\t.height = %d,'%(height), file=of)
	print('\t.ascent = %d,'%(font.ascent), file=of)
	print('\t.firstchar = %d, /* %s */'%(opts.firstchar,font.enc.get(opts.firstchar,"?")), file=of)
	print('\t.lastchar = %d, /* %s */'%(opts.lastchar,font.enc.get(opts.lastchar,"?")), file=of)
	print('\t.chardata = %s_data,'%(opts.base), file=of)
	print('\t.charoffs = %s_offsets,'%(opts.base), file=of)
	print('};', file=of)
