#!/usr/bin/env python
"""pcf2fon -- convert X11 .pcf bitmap fonts into Windows .fon format"""
# (C) 2014 Martin J. Fiedler <keyj@emphy.de>
# uses major parts of mkwinfont, which is (C) 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, string, os, glob, zlib, gzip, struct, cStringIO, optparse

################################################################################
## mkwinfont                                                                  ##
################################################################################

def byte(i):
    return chr(i & 0xFF)
def word(i):
    return byte(i) + byte(i >> 8)
def dword(i):
    return word(i) + word(i >> 16)

def frombyte(s):
    return ord(s)
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

class sgt_font:
    pass

class sgt_char:
    pass

def fnt(font):
    "Generate the contents of a .FNT file, given a font description."

    # Average width is defined by Windows to be the width of 'X'.
    avgwidth = font.chars[ord('X')].width
    # Max width we calculate from the font. During this loop we also
    # check if the font is fixed-pitch.
    maxwidth = 0
    fixed = 1
    for i in range(0,256):
        if avgwidth != font.chars[i].width:
            fixed = 0
        if maxwidth < font.chars[i].width:
            maxwidth = font.chars[i].width
    # Work out how many 8-pixel wide columns we need to represent a char.
    widthbytes = (maxwidth+7)/8
    widthbytes = (widthbytes+1) &~ 1  # round up to multiple of 2
    # widthbytes = 3 # FIXME!

    file = ""
    file = file + word(0x0300) # file version
    file = file + dword(0)     # file size (come back and fix later)
    copyright = font.copyright + ("\0" * 60)
    copyright = copyright[0:60]
    file = file + copyright
    file = file + word(0)      # font type (raster, bits in file)
    file = file + word(font.pointsize) # nominal point size
    file = file + word(100)    # vertical resolution
    file = file + word(100)    # horizontal resolution
    file = file + word(font.ascent)   # top of font <--> baseline
    file = file + word(0)      # internal leading
    file = file + word(0)      # external leading
    file = file + byte(font.italic)
    file = file + byte(font.underline)
    file = file + byte(font.strikeout)
    file = file + word(font.weight)   # 1 to 1000; 400 is normal.
    file = file + byte(font.charset)
    if fixed:
        pixwidth = avgwidth
    else:
        pixwidth = 0
    file = file + word(pixwidth) # width, or 0 if var-width
    file = file + word(font.height) # height
    if fixed:
        pitchfamily = 0
    else:
        pitchfamily = 1
    file = file + byte(pitchfamily) # pitch and family
    file = file + word(avgwidth)
    file = file + word(maxwidth)
    file = file + byte(0)          # first char
    file = file + byte(255)        # last char
    file = file + byte(0)          # default char
    file = file + byte(32)         # break char
    file = file + word(widthbytes) # dfWidthBytes
    file = file + dword(0)         # device
    file = file + dword(0)         # face name
    file = file + dword(0)         # BitsPointer (used at load time)
    file = file + dword(0)         # pointer to bitmap data
    file = file + byte(0)          # reserved
    if fixed:
        dfFlags = 1
    else:
        dfFlags = 2
    file = file + dword(dfFlags)   # dfFlags
    file = file + word(0) + word(0) + word(0) # Aspace, Bspace, Cspace
    file = file + dword(0)         # colour pointer
    file = file + ("\0" * 16)      # dfReserved1

    # Now the char table.
    offset_chartbl = len(file)
    offset_bitmaps = offset_chartbl + 257 * 6
    # Fix up the offset-to-bitmaps at 0x71.
    file = file[:0x71] + dword(offset_bitmaps) + file[0x71+4:]
    bitmaps = ""
    for i in range(0,257):
        if i < 256:
            width = font.chars[i].width
        else:
            width = avgwidth
        file = file + word(width)
        file = file + dword(offset_bitmaps + len(bitmaps))
        for j in range(widthbytes):
            for k in range(font.height):
                if i < 256:
                    chardata = font.chars[i].data[k]
                else:
                    chardata = 0
                chardata = chardata << (8*widthbytes - width)
                bitmaps = bitmaps + byte(chardata >> (8*(widthbytes-j-1)))

    file = file + bitmaps
    # Now the face name. Fix up the face name offset at 0x69.
    file = file[:0x69] + dword(len(file)) + file[0x69+4:]
    file = file + font.facename + "\0"
    # And finally fix up the file size at 0x2.
    file = file[:0x2] + dword(len(file)) + file[0x2+4:]

    # Done.
    return file

def direntry(f):
    "Return the FONTDIRENTRY, given the data in a .FNT file."
    device = fromdword(f[0x65:])
    face = fromdword(f[0x69:])
    if device == 0:
        devname = ""
    else:
        devname = asciz(f[device:])
    facename = asciz(f[face:])
    return f[0:0x71] + devname + "\0" + facename + "\0"

stubcode = [
  0xBA, 0x0E, 0x00, # mov dx,0xe
  0x0E,             # push cs
  0x1F,             # pop ds
  0xB4, 0x09,       # mov ah,0x9
  0xCD, 0x21,       # int 0x21
  0xB8, 0x01, 0x4C, # mov ax,0x4c01
  0xCD, 0x21        # int 0x21
]
stubmsg = "This is not a program!\r\nFont library created by mkwinfont.\r\n"

def stub():
    "Create a small MZ executable."
    file = ""
    file = file + "MZ" + word(0) + word(0)
    file = file + word(0) # no relocations
    file = file + word(4) # 4-para header
    file = file + word(0x10) # 16 extra para for stack
    file = file + word(0xFFFF) # maximum extra paras: LOTS
    file = file + word(0) + word(0x100) # SS:SP = 0000:0100
    file = file + word(0) # no checksum
    file = file + word(0) + word(0) # CS:IP = 0:0, start at beginning
    file = file + word(0x40) # reloc table beyond hdr
    file = file + word(0) # overlay number
    file = file + 4 * word(0) # reserved
    file = file + word(0) + word(0) # OEM id and OEM info
    file = file + 10 * word(0) # reserved
    file = file + dword(0) # offset to NE header
    assert len(file) == 0x40
    for i in stubcode: file = file + byte(i)
    file = file + stubmsg + "$"
    n = len(file)
    pages = (n+511) / 512
    lastpage = n - (pages-1) * 512
    file = file[:2] + word(lastpage) + word(pages) + file[6:]
    # Now assume there will be a NE header. Create it and fix up the
    # offset to it.
    while len(file) % 16: file = file + "\0"
    file = file[:0x3C] + dword(len(file)) + file[0x40:]
    return file

def fon(name, fonts):
    "Create a .FON font library, given a bunch of .FNT file contents."

    # Construct the FONTDIR.
    fontdir = word(len(fonts))
    for i in range(len(fonts)):
        fontdir = fontdir + word(i+1)
        fontdir = fontdir + direntry(fonts[i])

    # The MZ stub.
    stubdata = stub()
    # Non-resident name table should contain a FONTRES line.
    nonres = "FONTRES 100,96,96 : " + name
    nonres = byte(len(nonres)) + nonres + "\0\0\0"
    # Resident name table should just contain a module name.
    mname = ""
    for c in name:
        if c in "0123546789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz":
            mname = mname + c
    res = byte(len(mname)) + mname + "\0\0\0"
    # Entry table / imported names table should contain a zero word.
    entry = word(0)

    # Compute length of resource table.
    # 12 (2 for the shift count, plus 2 for end-of-table, plus 8 for the
    #    "FONTDIR" resource name), plus
    # 20 for FONTDIR (TYPEINFO and NAMEINFO), plus
    # 8 for font entry TYPEINFO, plus
    # 12 for each font's NAMEINFO 

    # Resources are currently one FONTDIR plus n fonts.
    # TODO: a VERSIONINFO resource would be nice too.
    resrcsize = 12 + 20 + 8 + 12 * len(fonts)
    resrcpad = ((resrcsize + 15) &~ 15) - resrcsize

    # Now position all of this after the NE header.
    p = 0x40        # NE header size
    off_segtable = off_restable = p
    p = p + resrcsize + resrcpad
    off_res = p
    p = p + len(res)
    off_modref = off_import = off_entry = p
    p = p + len(entry)
    off_nonres = p
    p = p + len(nonres)

    pad = ((p+15) &~ 15) - p
    p = p + pad
    q = p + len(stubdata)

    # Now q is file offset where the real resources begin. So we can
    # construct the actual resource table, and the resource data too.
    restable = word(4) # shift count
    resdata = ""
    # The FONTDIR resource.
    restable = restable + word(0x8007) + word(1) + dword(0)
    restable = restable + word((q+len(resdata)) >> 4)
    start = len(resdata)
    resdata = resdata + fontdir
    while len(resdata) % 16: resdata = resdata + "\0"    
    restable = restable + word((len(resdata)-start) >> 4)
    restable = restable + word(0x0C50) + word(resrcsize-8) + dword(0)
    # The font resources.
    restable = restable + word(0x8008) + word(len(fonts)) + dword(0)
    for i in range(len(fonts)):
        restable = restable + word((q+len(resdata)) >> 4)
        start = len(resdata)
        resdata = resdata + fonts[i]
        while len(resdata) % 16: resdata = resdata + "\0"    
        restable = restable + word((len(resdata)-start) >> 4)
        restable = restable + word(0x1C30) + word(0x8001 + i) + dword(0)
    # The zero word.
    restable = restable + word(0)
    assert len(restable) == resrcsize - 8
    restable = restable + "\007FONTDIR"
    restable = restable + "\0" * resrcpad

    file = stubdata + "NE" + byte(5) + byte(10)
    file = file + word(off_entry) + word(len(entry))
    file = file + dword(0) # no CRC
    file = file + word(0x8308) # the Mysterious Flags
    file = file + word(0) + word(0) + word(0) # no autodata, no heap, no stk
    file = file + dword(0) + dword(0) # CS:IP == SS:SP == 0
    file = file + word(0) + word(0) # segment table len, modreftable len
    file = file + word(len(nonres))
    file = file + word(off_segtable) + word(off_restable)
    file = file + word(off_res) + word(off_modref) + word(off_import)
    file = file + dword(len(stubdata) + off_nonres)
    file = file + word(0) # no movable entries
    file = file + word(4) # seg align shift count
    file = file + word(0) # no resource segments
    file = file + byte(2) + byte(8) # target OS and more Mysterious Flags
    file = file + word(0) + word(0) + word(0) + word(0x300)

    # Now add in all the other stuff.
    file = file + restable + res + entry + nonres + "\0" * pad + resdata

    return file


################################################################################
## PCF parser                                                                 ##
################################################################################

f = cStringIO.StringIO(zlib.decompress("""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""".decode('base64')))
Charmaps = {}
cpc = struct.unpack('<H', f.read(2))[0]
for cp in struct.unpack('<%dH' % cpc, f.read(cpc * 2)):
    Charmaps[cp] = struct.unpack('<256H', f.read(512))

def DumpBlock(block, f=None, chars="-#"):
    if not f:  f = sys.stdout
    for row in block:
        print >>f, ''.join(chars[x] for x in row)
    print >>f

def DumpBlocks(blocks, f=None, chars="-#"):
    if not f:  f = sys.stdout
    for allrows in zip(*blocks):
        print >>f, chars[2:].join(''.join(chars[x] for x in row) for row in allrows).rstrip()
    print >>f

class Glyph(object):
    def __init__(self, lb, rb, w, asc, desc, dummy=0):
        self.left_bearing = lb
        self.right_bearing = rb
        self.width = w
        self.height = asc + desc
        self.ascent = asc
        self.descent = desc
        self.data = None

    def check(self):
        # some sanity checks
        assert self.data
        assert len(self.data) == self.height
        assert all(len(row) == self.width for row in self.data)

    def apply_bearing(self):
        while self.left_bearing > 0:
            if any(row[-1] for row in self.data):
                return  # there are pixels in the rightmost column -> stop here
            for i in xrange(self.height):
                self.data[i] = [0] + self.data[i][:-1]
            self.left_bearing -= 1

    def adjust_height(self, delta_asc=0, delta_desc=0):
        if (delta_asc > 0) or (delta_desc > 0):
            nullrow = [0] * self.width
        for y in xrange(0, delta_asc):
            self.data.insert(0, nullrow)
        if delta_asc < 0:
            self.data = self.data[-delta_asc:]
        for y in xrange(0, delta_desc):
            self.data.append(nullrow)
        if delta_desc < 0:
            self.data = self.data[:delta_desc]
        self.height += delta_asc + delta_desc
        self.ascent += delta_asc
        self.descent += delta_desc

    def dump(self, f=None):
        print "*** %dx%d, bearing=%d/%d, asc/desc=%d/%d" % (self.width, self.height, self.left_bearing, self.right_bearing, self.ascent, self.descent)
        if self.data:
            DumpBlock(self.data)


class PCFFont(object):
    def __init__(self, filename):
        # read the whole file into memory
        self.filename = filename
        try:
            f = gzip.open(filename, 'rb')
            data = f.read()
        except IOError:
            f = open(filename, 'rb')
            data = f.read()
        f.close()

        # read and parse the TOC
        f = cStringIO.StringIO(data)
        header, table_count = struct.unpack('<4si', f.read(8))
        assert header == "\x01fcp"
        toc = [struct.unpack('<4i', f.read(16)) for x in xrange(table_count)]
        toc = dict((tid, (data[offset+4:offset+size], fmt, '>' if (fmt & 4) else '<')) for tid, fmt, size, offset in toc)
    
        # read and parse the properties block
        block, fmt, endian = toc[1]  # PCF_PROPERTIES block
        f = cStringIO.StringIO(block)
        nprops = struct.unpack(endian + 'i', f.read(4))[0]
        props = [struct.unpack(endian + 'ibi', f.read(9)) for x in xrange(nprops)]
        if nprops & 3:
            f.read(4 - (nprops & 3))
        def read_string_table(f, endian):
            strings = {}
            offset = 0
            for s in f.read(struct.unpack(endian + 'i', f.read(4))[0]).split('\0'):
                strings[offset] = s
                offset += len(s) + 1
            return strings
        strings = read_string_table(f, endian)
        self.props = dict((strings[name_offset], strings[value] if is_string else value) for name_offset, is_string, value in props)
        self.charset = self.props.get('CHARSET_REGISTRY', '?').lower() + '-' + self.props.get('CHARSET_ENCODING', '?')
        assert self.charset in ('iso10646-1', 'iso8859-1')

        # read the accelerator table
        try:
            block, fmt, endian = toc[256]  # PCF_BDF_ACCELERATORS  block
        except KeyError:
            try:
                block, fmt, endian = toc[2]  # PCF_ACCELERATORS  block
            except KeyError:
                block = None
        if block:
            f = cStringIO.StringIO(block)
            self.props['noOverlap'], \
            self.props['constantMetrics'], \
            self.props['terminalFont'], \
            self.props['constantWidth'], \
            self.props['inkInside'], ink_metrics, \
            self.props['drawDirection'], padding, \
            self.props['fontAscent'], self.props['fontDescent'], \
            max_overlap = struct.unpack(endian + '8B3i', f.read(20))

        # get the character-to-glyph mapping
        block, fmt, endian = toc[32]  # PCF_BDF_ENCODINGS  block
        f = cStringIO.StringIO(block)
        min2, max2, min1, max1, self.default_glyph = struct.unpack(endian + '5h', f.read(10))
        def iterchar():
            for i1 in xrange(min1, max1+1):
                for i2 in xrange(min2, max2+1):
                    yield (max2 - min2 + 1) * (i1 + min1) + i2 + min2
        nchars = (max2 - min2 + 1) * (max1 - min1 + 1)
        chars = zip(iterchar(), struct.unpack("%s%dh" % (endian, nchars), f.read(nchars * 2)))
        self.charmap = dict((c, g) for c, g in chars if g >= 0)

        # get the glyph names
        try:
            block, fmt, endian = toc[128]  # PCF_GLYPH_NAMES block
        except KeyError:
            block = 8 * '\0'
        f = cStringIO.StringIO(block)
        glyph_count = struct.unpack(endian + 'i', f.read(4))[0]
        offsets = struct.unpack("%s%di" % (endian, glyph_count), f.read(glyph_count * 4))
        strings = read_string_table(f, endian)
        self.charmap.update(dict((strings[offsets[i]], i) for i in xrange(glyph_count)))
    
        # get the glyph metrics
        block, fmt, endian = toc[4]  # PCF_METRICS  block
        f = cStringIO.StringIO(block)
        metrics_count = struct.unpack(endian + 'h', f.read(2))[0]
        def read_metrics():
            if fmt & 0x100:  # PCF_COMPRESSED_METRICS
                return tuple([ord(x) - 128 for x in f.read(5)] + [0])
            else:
                return struct.unpack(endian + '5hH', f.read(12))
        self.glyphs = [Glyph(*read_metrics()) for i in xrange(metrics_count)]
    
        # get the actual bitmap data
        block, fmt, endian = toc[8]  # PCF_BITMAPS block
        f = cStringIO.StringIO(block)
        glyph_count = struct.unpack(endian + 'i', f.read(4))[0]
        offsets = sorted(zip(struct.unpack("%s%di" % (endian, glyph_count), f.read(glyph_count * 4)), xrange(glyph_count)))
        bmp_size = struct.unpack(endian + '4i', f.read(16))[fmt & 3]
        offsets.append((bmp_size, None))
        block = f.read(bmp_size)
        glyph_data = dict((offsets[i][1], block[offsets[i][0]:offsets[i+1][0]]) for i in xrange(glyph_count))
        rowpad = fmt & 3
        rowfmt = (fmt >> 4) & 3
        rowpad = [1, 2, 4][max(rowpad, rowfmt)]
        bits = [8, 16, 32][rowfmt]
        rowfmt = "BHI"[rowfmt]
        bitinv = -1 if (fmt & 8) else 0
        def decode_glyph(data, width):
            bytes_per_row = ((width + rowpad * 8 - 1) & ~(rowpad * 8 - 1)) >> 3
            used_bytes = ((width + bits - 1) & ~(bits - 1)) >> 3
            rows = len(data) / bytes_per_row
            assert len(data) == rows * bytes_per_row
            for row in xrange(0, len(data), bytes_per_row):
                row = struct.unpack("%s%d%s" % (endian, used_bytes * 8 / bits, rowfmt), data[row : row+used_bytes])
                row = [(row[x / bits] >> ((x ^ bitinv) & (bits - 1))) & 1 for x in xrange(width)]
                yield row
        for i in xrange(glyph_count):
            self.glyphs[i].data = list(decode_glyph(glyph_data[i], self.glyphs[i].width))

        # add ascents and descents
        self.ascent = self.props.get('fontAscent', max(g.ascent for g in self.glyphs))
        self.descent = self.props.get('fontDescent', max(g.descent for g in self.glyphs))
        for g in self.glyphs:
            g.check()
            g.apply_bearing()
            g.adjust_height(self.ascent - g.ascent, self.descent - g.descent)
            g.check()

        # check heights
        self.height = self.glyphs[0].height
        assert all((g.height == self.height) for g in self.glyphs)

    def getchar(self, c):
        g = self.charmap.get(c, None)
        if g is None:
            g = self.charmap.get(ord(c), None)
        if g is None:
            g = self.default_glyph
        return self.glyphs[g]

    def getchars(self, s):
        return map(self.getchar, s)

    def banner(self, s, f=None, chars="-#"):
        DumpBlocks([g.data for g in self.getchars(s)], f, chars)

    def _get_copyright_60b(self):
        try:
            copyright = self.props['COPYRIGHT'].strip()
            if len(copyright) >= 60:
                copyright = copyright.split('.', 1)[0].strip()
            if len(copyright) >= 60:
                copyright = copyright[:56] + "..."
            return copyright
        except KeyError:
            return None

    def generate_fnt(self, codepage=1252, name=None):
        assert codepage in Charmaps
        f = sgt_font()
        f.facename = name or self.props['FACE_NAME']
        f.copyright = self._get_copyright_60b()
        f.height = self.height
        f.ascent = self.ascent
        f.italic = int(self.props.get('SLANT', 'r').lower() != 'r')
        f.underline = 0
        f.strikeout = 0
        f.weight = {"bold":700}.get(self.props.get('WEIGHT_NAME', "").lower(), 400)
        f.charset = 0 if (codepage >= 1000) else 255
        f.pointsize = (self.props.get('POINT_SIZE', self.height * 110) + 5) / 10
        f.chars = [None] * 256
        for i, u in zip(xrange(256), Charmaps[codepage]):
            g = self.glyphs[self.charmap.get(u, self.default_glyph)]
            c = sgt_char()
            c.width = g.width
            c.data = [0] * g.height
            for y in xrange(g.height):
                c.data[y] = sum((1 << bit) for bit, x in zip(xrange(g.width), reversed(g.data[y])) if x)
            f.chars[i] = c
        return f.facename, fnt(f)


################################################################################
## main function                                                              ##
################################################################################

def GetMappings(args):
    import urllib, re
    cps = map(int, args) or Charmaps.keys()
    data = struct.pack("<%dH" % (len(cps) + 1), len(cps), *cps)
    for cp in cps:
        cmap = {}
        f = urllib.urlopen("http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/%s/CP%s.TXT" % ("WINDOWS" if cp >= 1000 else "PC", cp))
        for line in f:
            m = re.match(r'\s*(0x)?([0-9a-f]+)\s+(0x)?([0-9a-f]+)\s+', line, re.I)
            if not m:
                continue
            cmap[int(m.group(2), 16)] = int(m.group(4), 16)
        f.close()
        data += struct.pack('<256H', *(cmap.get(i, 0) for i in xrange(256)))
    data = zlib.compress(data, 9).encode('base64').replace('\n', '')
    data = 'f = cStringIO.StringIO(zlib.decompress("""' + data + '""".decode(\'base64\')))'
    for x in xrange(254, len(data) * 2, 255):
        data = data[:x] + '\n' + data[x:]
    print data.strip()

def ListCodepages():
    print "ANSI codepages:", ", ".join(sorted(str(cp) for cp in Charmaps if cp >= 1000))
    print "OEM  codepages:", ", ".join(sorted(str(cp) for cp in Charmaps if cp <  1000))

if __name__ == "__main__":
    parser = optparse.OptionParser(usage="%prog [OPTIONS] <input1.pcf[.gz]> [<input2.pcf[.gz]>...]")
    parser.add_option("-o", "--output",
                      help="output .fon file name")
    parser.add_option("-i", "--info", action='store_true',
                      help="don't convert, just output font info")
    parser.add_option("-b", "--banner", metavar='TEXT',
                      help="don't convert, just create an ASCII banner")
    parser.add_option("-n", "--face-name",
                      help="override the font name")
    parser.add_option("-c", "--codepage", type='int', default=1252,
                      help="codepage to generate [default: %default]")
    parser.add_option("--list-codepages", action='store_true',
                      help="show supported codepages and exit")
    parser.add_option("--get-mappings", action='store_true',
                      help="load codepage mappings from unicode.org")
    opts, args = parser.parse_args()
    if opts.get_mappings:
        sys.exit(GetMappings(args) or 0)
    if opts.list_codepages:
        sys.exit(ListCodepages() or 0)
    if not args:
        parser.error("no input files")
    f = None
    if opts.banner and opts.output:
        f = open(opts.output, 'w')
    do_output = not(opts.info) and not(opts.banner)
    if do_output and not opts.output:
        parser.error("no output file specified")

    fonts = []
    name = None
    for arg in args:
        for filename in glob.glob(arg):
            font = PCFFont(filename)
            if opts.info:
                print "*****", filename, "*****"
                maxlen = max(map(len, font.props))
                for prop, val in sorted(font.props.iteritems()):
                    print >>sys.stderr, prop.ljust(maxlen), ":", val
                print
            if opts.banner:
                font.banner(opts.banner, f, " #")
            if do_output:
                new_name, data = font.generate_fnt(opts.codepage, opts.face_name)
                if name and (name != new_name):
                    print >>sys.stderr, "Error: Fonts disagree on face name (e.g. %r <-> %r). Please use the --face-name option to specify a name." % (name, new_name)
                    sys.exit(1)
                name = new_name
                fonts.append(data)

    if fonts:
        f = open(opts.output, 'wb')
        f.write(fon(name, fonts))
        f.close()
    elif do_output:
        print >>sys.stderr, "no fonts."
