Skip to content

Instantly share code, notes, and snippets.

@nooodl
Last active August 29, 2015 14:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nooodl/5f6f104fb8f0a4bdc862 to your computer and use it in GitHub Desktop.
Save nooodl/5f6f104fb8f0a4bdc862 to your computer and use it in GitHub Desktop.
packagable clone of Simon Tatham's dewinfont
#!/usr/bin/python
# dewinfont is copyright 2001 Simon Tatham. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import sys
import string
# Extract bitmap font data from a Windows .FON or .FNT file.
def frombyte(s):
return ord(s[0])
def fromword(s):
return frombyte(s[0:1]) + 256 * frombyte(s[1:2])
def fromdword(s):
return fromword(s[0:2]) | (fromword(s[2:4]) << 16)
def asciz(s):
i = string.find(s, "\0")
if i != -1:
s = s[:i]
return s
def bool(n):
if n:
return "yes"
else:
return "no"
class font:
pass
class char:
pass
def savefont(f, file):
"Write out a .fd form of an internal font description."
file.write("# .fd font description generated by dewinfont.\n\n")
file.write("facename " + f.facename + "\n")
file.write("copyright " + f.copyright + "\n\n")
file.write("height " + "%d"%f.height + "\n")
file.write("ascent " + "%d"%f.ascent + "\n")
if f.height == f.pointsize: file.write("# ")
file.write("pointsize " + "%d"%f.pointsize + "\n\n")
if not f.italic: file.write("# ")
file.write("italic " + bool(f.italic) + "\n")
if not f.underline: file.write("# ")
file.write("underline " + bool(f.underline) + "\n")
if not f.strikeout: file.write("# ")
file.write("strikeout " + bool(f.strikeout) + "\n")
if f.weight == 400: file.write("# ")
file.write("weight " + "%d"%f.weight + "\n\n")
if f.charset == 0: file.write("# ")
file.write("charset " + "%d"%f.charset + "\n\n")
for i in range(256):
file.write("char " + "%d"%i + "\nwidth " + "%d"%f.chars[i].width+"\n")
if f.chars[i].width != 0:
for j in range(f.height):
v = f.chars[i].data[j]
m = 1L << (f.chars[i].width-1)
for k in range(f.chars[i].width):
if v & m:
file.write("1")
else:
file.write("0")
v = v << 1
file.write("\n")
file.write("\n")
def dofnt(fnt):
"Create an internal font description from a .FNT-shaped string."
f = font()
f.chars = [None] * 256
version = fromword(fnt[0:])
ftype = fromword(fnt[0x42:])
if ftype & 1:
sys.stderr.write("This font is a vector font\n")
return None
off_facename = fromdword(fnt[0x69:])
if off_facename < 0 or off_facename > len(fnt):
sys.stderr.write("Face name not contained within font data")
return None
f.facename = asciz(fnt[off_facename:])
#print "Face name", f.facename
f.copyright = asciz(fnt[6:66] + "\0")
#print "Copyright", f.copyright
f.pointsize = fromword(fnt[0x44:])
#print "Point size", f.pointsize
f.ascent = fromword(fnt[0x4A:])
#print "Ascent", f.ascent
f.height = fromword(fnt[0x58:])
#print "Height", f.height
f.italic = frombyte(fnt[0x50:]) != 0
f.underline = frombyte(fnt[0x51:]) != 0
f.strikeout = frombyte(fnt[0x52:]) != 0
f.weight = fromword(fnt[0x53:])
f.charset = frombyte(fnt[0x55:])
#print "Attrs", f.italic, f.underline, f.strikeout, f.weight
#print "Charset", f.charset
# Read the char table.
if version == 0x200:
ctstart = 0x76
ctsize = 4
else:
ctstart = 0x94
ctsize = 6
maxwidth = 0
for i in range(256):
f.chars[i] = char()
f.chars[i].width = 0
f.chars[i].data = [0L] * f.height
firstchar = frombyte(fnt[0x5F:])
lastchar = frombyte(fnt[0x60:])
for i in range(firstchar,lastchar+1):
entry = ctstart + ctsize * (i-firstchar)
w = fromword(fnt[entry:])
f.chars[i].width = w
if ctsize == 4:
off = fromword(fnt[entry+2:])
else:
off = fromdword(fnt[entry+2:])
#print "Char", i, "width", w, "offset", off, "filelen", len(fnt)
widthbytes = (w + 7) / 8
for j in range(f.height):
for k in range(widthbytes):
bytepos = off + k * f.height + j
#print bytepos, "->", hex(frombyte(fnt[bytepos:]))
f.chars[i].data[j] = f.chars[i].data[j] << 8
f.chars[i].data[j] = f.chars[i].data[j] | frombyte(fnt[bytepos:])
f.chars[i].data[j] = f.chars[i].data[j] >> (8*widthbytes - w)
return f
def nefon(fon, neoff):
"Finish splitting up a NE-format FON file."
ret = []
# Find the resource table.
rtable = fromword(fon[neoff + 0x24:])
rtable = rtable + neoff
# Read the shift count out of the resource table.
shift = fromword(fon[rtable:])
# Now loop over the rest of the resource table.
p = rtable+2
while 1:
rtype = fromword(fon[p:])
if rtype == 0:
break # end of resource table
count = fromword(fon[p+2:])
p = p + 8 # type, count, 4 bytes reserved
for i in range(count):
start = fromword(fon[p:]) << shift
size = fromword(fon[p+2:]) << shift
if start < 0 or size < 0 or start+size > len(fon):
sys.stderr.write("Resource overruns file boundaries\n")
return None
if rtype == 0x8008: # this is an actual font
#print "Font at", start, "size", size
font = dofnt(fon[start:start+size])
if font == None:
sys.stderr.write("Failed to read font resource at %x" \
% start)
return None
ret = ret + [font]
p = p + 12 # start, size, flags, name/id, 4 bytes reserved
return ret
def pefon(fon, peoff):
"Finish splitting up a PE-format FON file."
dirtables=[]
dataentries=[]
def gotoffset(off,dirtables=dirtables,dataentries=dataentries):
if off & 0x80000000:
off = off &~ 0x80000000
dirtables.append(off)
else:
dataentries.append(off)
def dodirtable(rsrc, off, rtype, gotoffset=gotoffset):
number = fromword(rsrc[off+12:]) + fromword(rsrc[off+14:])
for i in range(number):
entry = off + 16 + 8*i
thetype = fromdword(rsrc[entry:])
theoff = fromdword(rsrc[entry+4:])
if rtype == -1 or rtype == thetype:
gotoffset(theoff)
# We could try finding the Resource Table entry in the Optional
# Header, but it talks about RVAs instead of file offsets, so
# it's probably easiest just to go straight to the section table.
# So let's find the size of the Optional Header, which we can
# then skip over to find the section table.
secentries = fromword(fon[peoff+0x06:])
sectable = peoff + 0x18 + fromword(fon[peoff+0x14:])
for i in range(secentries):
secentry = sectable + i * 0x28
secname = asciz(fon[secentry:secentry+8])
secrva = fromdword(fon[secentry+0x0C:])
secsize = fromdword(fon[secentry+0x10:])
secptr = fromdword(fon[secentry+0x14:])
if secname == ".rsrc":
break
if secname != ".rsrc":
sys.stderr.write("Unable to locate resource section\n")
return None
# Now we've found the resource section, let's throw away the rest.
rsrc = fon[secptr:secptr+secsize]
# Now the fun begins. To start with, we must find the initial
# Resource Directory Table and look up type 0x08 (font) in it.
# If it yields another Resource Directory Table, we stick the
# address of that on a list. If it gives a Data Entry, we put
# that in another list.
dodirtable(rsrc, 0, 0x08)
# Now process Resource Directory Tables until no more remain
# in the list. For each of these tables, we accept _all_ entries
# in it, and if they point to subtables we stick the subtables in
# the list, and if they point to Data Entries we put those in
# the other list.
while len(dirtables) > 0:
table = dirtables[0]
del dirtables[0]
dodirtable(rsrc, table, -1) # accept all entries
# Now we should be left with Resource Data Entries. Each of these
# describes a font.
ret = []
for off in dataentries:
rva = fromdword(rsrc[off:])
start = rva - secrva
size = fromdword(rsrc[off+4:])
font = dofnt(rsrc[start:start+size])
if font == None:
sys.stderr.write("Failed to read font resource at %x" % start)
return None
ret = ret + [font]
return ret
def dofon(fon):
"Split a .FON up into .FNTs and pass each to dofnt."
# Check the MZ header.
if fon[0:2] != "MZ":
sys.stderr.write("MZ signature not found\n")
return None
# Find the NE header.
neoff = fromdword(fon[0x3C:])
if fon[neoff:neoff+2] == "NE":
return nefon(fon, neoff)
elif fon[neoff:neoff+4] == "PE\0\0":
return pefon(fon, neoff)
else:
sys.stderr.write("NE or PE signature not found\n")
return None
def isfon(data):
"Determine if a file is a .FON or a .FNT format font."
if data[0:2] == "MZ":
return 1 # FON
else:
return 0 # FNT
if __name__ == "__main__":
a = sys.argv[1:]
options = 1
outfile = None
prefix = None
infile = None
if len(a) == 0:
print "usage: dewinfont [-o outfile | -p prefix] file"
sys.exit(0)
while len(a) > 0:
if a[0] == "--":
options = 0
a = a[1:]
elif options and a[0][0:1] == "-":
if a[0] == "-o":
try:
outfile = a[1]
a = a[2:]
except IndexError:
sys.stderr.write("option -o requires an argument\n")
sys.exit(1)
elif a[0] == "-p":
try:
prefix = a[1]
a = a[2:]
except IndexError:
sys.stderr.write("option -p requires an argument\n")
sys.exit(1)
else:
sys.stderr.write("ignoring unrecognised option "+a[0]+"\n")
a = a[1:]
else:
if infile != None:
sys.stderr.write("one input file at once, please\n")
sys.exit(1)
infile = a[0]
a = a[1:]
fp = open(infile, "rb")
data = fp.read()
fp.close()
if isfon(data):
fonts = dofon(data)
else:
fonts = [dofnt(data)]
if len(fonts) > 1 and prefix == None:
sys.stderr.write("more than one font in file; use -p prefix\n")
sys.exit(1)
if outfile == None and prefix == None:
sys.stderr.write("please specify -o outfile or -p prefix\n")
sys.exit(1)
for i in range(len(fonts)):
if len(fonts) == 1 and outfile != None:
fname = outfile
else:
fname = prefix + "%02d"%i + ".fd"
fp = open(fname, "w")
savefont(fonts[i], fp)
fp.close()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment