osmocom-bb/src/host/fb_tools/bdf_to_c.py

290 lines
8.3 KiB
Python
Executable File

#!/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)