Ejemplo n.º 1
0
def mainProgram(optind):
    # NON-OPTIONS
    if len(sys.argv) - optind < 4:
        raise Exception('invalid number of arguments, try --help')

    input = sys.argv[optind]
    st = fnio.InputStream(input)
    registry = sys.argv[optind + 1]
    encoding = sys.argv[optind + 2]
    newCodes = []

    if re.match('^[A-Za-z][\w.:()]*$', registry) == None or re.match(
            '^[\w.:()]+$', encoding) == None:
        raise Exception('invalid registry or encoding')

    # READ INPUT
    try:
        oldFont = bdf.Font.read(st)
        st.close()
    except Exception as e:
        raise Exception(st.location() + str(e))

    # READ TABLES
    optind += 3
    while optind < len(sys.argv):
        st = fnio.InputStream(sys.argv[optind])
        optind += 1

        def loadCode(line):
            newCodes.append(fnutil.parseHex('unicode', line))

        try:
            st.readLines(loadCode)
            st.close()
        except Exception as e:
            raise Exception(st.location() + str(e))

    if not newCodes:
        raise Exception('no characters in the output font')

    # CREATE GLYPHS
    newFont = bdf.Font()
    charMap = {char.code: char for char in oldFont.chars}
    index = 0
    unstart = 0

    if filter:
        unstart = 32 if registry == 'ISO10646' else 1114111

    for code in newCodes:
        if filter and code == 0xFFFF:
            index += 1
            continue

        if code in charMap:
            oldChar = charMap[code]
            uniFFFF = False
        else:
            missing = hex(code)[2:].upper()
            uniFFFF = True

            if code != 0xFFFF:
                raise Exception('{0} does not contain {1}'.format(
                    input, missing))

            if oldFont.defaultCode != None:
                oldChar = charMap[oldFont.defaultCode]
            elif 0xFFFD in charMap:
                oldChar = charMap[0xFFFD]
            else:
                raise Exception(
                    '{0} does not contain {1}, and no stub found'.format(
                        input, missing))

        newChar = copy.copy(oldChar)
        newChar.props = oldChar.props.copy()
        newChar.code = code if index >= unstart else index
        index += 1
        newChar.props['ENCODING'] = str(newChar.code)
        newFont.chars.append(newChar)

        if uniFFFF:
            newChar.props['STARTCHAR'] = 'uniFFFF'
        elif oldChar.code == oldFont.defaultCode or (
                oldChar.code == 0xFFFD and newFont.defaultCode == None):
            newFont.defaultCode = newChar.code

    # CREATE HEADER
    propertyCount = None
    propertyDelta = 0

    for name, value in oldFont.props.items():
        if name == 'FONT':
            newFont.xlfd = oldFont.xlfd[:]
            newFont.xlfd[13] = registry
            newFont.xlfd[14] = encoding
            value = '-'.join(newFont.xlfd)
        elif name == 'STARTPROPERTIES':
            propertyCount = fnutil.parseDec(name, value, 1, 250)
        elif name == 'CHARSET_REGISTRY':
            value = '"' + registry + '"'
        elif name == 'CHARSET_ENCODING':
            value = '"' + encoding + '"'
        elif name == 'DEFAULT_CHAR':
            if newFont.defaultCode != None:
                value = str(newFont.defaultCode)
            else:
                propertyDelta = -1
                continue
        elif name == 'ENDPROPERTIES':
            if propertyCount == None:
                raise Exception(input +
                                ': ENDPROPERTIES without STARTPROPERTIES')

            if newFont.defaultCode != None and not 'DEFAULT_CHAR' in newFont.props:
                newFont.props['DEFAULT_CHAR'] = str(newFont.defaultCode)
                propertyDelta += 1

            newFont.props['STARTPROPERTIES'] = str(propertyCount +
                                                   propertyDelta)
        elif name == 'CHARS':
            value = str(len(newFont.chars))

        newFont.props[name] = value

    newFont.version = oldFont.version
    newFont.bbx = oldFont.bbx

    # WRITE OUTPUT
    st = fnio.OutputStream(output)

    try:
        newFont.write(st)
        st.close()
    except Exception as e:
        st.destroy()
        raise Exception(st.fileName + ': ' + str(e))
Ejemplo n.º 2
0
def main_program(nonopt, parsed):
	version = parsed.version
	exchange = parsed.exchange
	bdfile = len(nonopt) > 0 and nonopt[0].lower().endswith('.bdf')
	ver1_unicodes = True

	# READ INPUT
	ifs = fnio.InputStream(nonopt[0] if bdfile else None)

	try:
		font = bmpf.Font.read(ifs)
		ifs.close()

		for char in font.chars:
			prefix = 'char %d: ' % char.code

			if char.width != font.bbx.width:
				raise Exception(prefix + 'output width not equal to maximum output width')

			if char.code == 65534:
				raise Exception(prefix + 'not a character, use 65535 for empty position')

			if char.code >= 65536:
				if version == 1:
					raise Exception(prefix + '-1 requires unicodes <= 65535')
				ver1_unicodes = False

		# VERSION
		ver1_num_chars = len(font.chars) == 256 or len(font.chars) == 512

		if version == 1:
			if not ver1_num_chars:
				raise Exception('-1 requires a font with 256 or 512 characters')

			if font.bbx.width != 8:
				raise Exception('-1 requires a font with width 8')

		# EXCHANGE
		vga_num_chars = len(font.chars) >= 224 and len(font.chars) <= 512
		vga_text_size = font.bbx.width == 8 and font.bbx.height in [8, 14, 16]

		if exchange is True:
			if not vga_num_chars:
				raise Exception('-g/--vga requires a font with 224...512 characters')

			if not vga_text_size:
				raise Exception('-g/--vga requires an 8x8, 8x14 or 8x16 font')

	except Exception as ex:
		raise Exception(ifs.location() + str(ex))

	# READ EXTRAS
	tables = dict()

	def load_extra(line):
		nonlocal ver1_unicodes

		words = re.split(br'\s+', line)

		if len(words) < 2:
			raise Exception('invalid format')

		uni = fnutil.parse_hex('unicode', words[0])

		if uni == 0xFFFE:
			raise Exception('FFFE is not a character')

		if next((char for char in font.chars if char.code == uni), None):
			if uni >= 0x10000:
				ver1_unicodes = False

			if uni not in tables:
				tables[uni] = []

			table = tables[uni]

			for word in words[1:]:
				dup = fnutil.parse_hex('extra code', word)

				if dup == 0xFFFF:
					raise Exception('FFFF is not a character')

				if dup >= 0x10000:
					ver1_unicodes = False

				if not dup in table or 0xFFFE in table:
					tables[uni].append(dup)

			if version == 1 and not ver1_unicodes:
				raise Exception('-1 requires unicodes <= FFFF')

	for name in nonopt[int(bdfile):]:
		ifs = fnio.InputStream(name)

		try:
			ifs.read_lines(load_extra)
			ifs.close()
		except Exception as ex:
			raise Exception(ifs.location() + str(ex))

	# VERSION
	if version == -1:
		version = 1 if ver1_num_chars and ver1_unicodes and font.bbx.width == 8 else 2

	# EXCHANGE
	if exchange == -1:
		exchange = vga_text_size and version >= 1 and vga_num_chars and font.chars[0].code == 0x00A3

	if exchange:
		font.chars = font.chars[192:224] + font.chars[32:192] + font.chars[0:32] + font.chars[224:]

	# WRITE
	ofs = fnio.OutputStream(parsed.output)

	if ofs.file.isatty():
		raise Exception('binary output may not be send to a terminal, use -o or redirect/pipe it')

	try:
		# HEADER
		if version == 1:
			ofs.write8(0x36)
			ofs.write8(0x04)
			ofs.write8((len(font.chars) >> 8) + 1)
			ofs.write8(font.bbx.height)
		elif version == 2:
			ofs.write32(0x864AB572)
			ofs.write32(0x00000000)
			ofs.write32(0x00000020)
			ofs.write32(0x00000001)
			ofs.write32(len(font.chars))
			ofs.write32(len(font.chars[0].data))
			ofs.write32(font.bbx.height)
			ofs.write32(font.bbx.width)

		# GLYPHS
		for char in font.chars:
			ofs.write(char.data)

		# UNICODES
		if version > 0:
			def write_unicode(code):
				if version == 1:
					ofs.write16(code)
				elif code <= 0x7F:
					ofs.write8(code)
				elif code in [0xFFFE, 0xFFFF]:
					ofs.write8(code & 0xFF)
				else:
					if code <= 0x7FF:
						ofs.write8(0xC0 + (code >> 6))
					else:
						if code <= 0xFFFF:
							ofs.write8(0xE0 + (code >> 12))
						else:
							ofs.write8(0xF0 + (code >> 18))
							ofs.write8(0x80 + ((code >> 12) & 0x3F))

						ofs.write8(0x80 + ((code >> 6) & 0x3F))

					ofs.write8(0x80 + (code & 0x3F))

			for char in font.chars:
				if char.code != 0xFFFF:
					write_unicode(char.code)

				if char.code in tables:
					for extra in tables[char.code]:
						write_unicode(extra)

				write_unicode(0xFFFF)

		# FINISH
		ofs.close()

	except Exception as ex:
		raise Exception(ofs.location() + str(ex) + ofs.destroy())
Ejemplo n.º 3
0
def mainProgram(optind):
    WIN_FONTHEADERSIZE = 118

    global charset
    global minChar

    # READ INPUT
    if len(sys.argv) - optind > 1:
        raise Exception('invalid number of arguments, try --help')

    st = fnio.InputStream(sys.argv[optind] if optind < len(sys.argv) else None)

    try:
        font = bdf.Font.read(st)
        st.close()
    except Exception as e:
        raise Exception(st.location() + str(e))

    # PRE-COMPUTE
    if charset == None:
        encoding = font.xlfd[bdf.XLFD.CHARSET_ENCODING]

        if re.match('(cp)?125[0-8]', encoding.lower()):
            FNT_CHARSETS = [238, 204, 0, 161, 162, 177, 178, 186, 163]
            charset = FNT_CHARSETS[int(encoding[-1:])]
        else:
            charset = 255

    try:
        # CHECK INPUT
        if font.bbx.yoff > 0 or font.bbx.height + font.bbx.yoff < 0:
            raise Exception('FONTBOUNDINGBOX yoff must be between 0 and -' +
                            str(font.bbx.height))

        for char in font.chars:
            prefix = 'char {0}: '.format(char.code)

            if char.bbx.width > font.bbx.width:
                raise Exception(prefix + 'BBX width exceeds FONTBOUNDINGBOX')

            if char.bbx.height != font.bbx.height or char.bbx.yoff != font.bbx.yoff:
                raise Exception(
                    prefix +
                    'BBX height and yoff must be identical to FONTBOUNDINGBOX')

        numChars = len(font.chars)

        if numChars > 256:
            raise Exception('too many characters, the maximum is 256')

        if minChar == None:
            if numChars == 192 or numChars == 256:
                minChar = 256 - numChars
            else:
                minChar = font.chars[0].code

        maxChar = minChar + numChars - 1

        if maxChar >= 256:
            raise Exception('the maximum char is too big, (re)specify -m')

        # HEADER
        vTell = WIN_FONTHEADERSIZE + (numChars + 1) * 4
        bitsOffset = vTell
        cTable = []
        widthBytes = 0

        # CTABLE/GLYPHS
        for char in font.chars:
            rowSize = char.bbx.rowSize()

            cTable.append(char.bbx.width)
            cTable.append(vTell)
            vTell += rowSize * font.bbx.height
            widthBytes += rowSize

        if vTell > 0xFFFF:
            raise Exception('too much character data')

        # SENTINEL
        sentinel = 2 - widthBytes % 2
        cTable.append(sentinel * 8)
        cTable.append(vTell)
        vTell += sentinel * font.bbx.height
        widthBytes += sentinel

        if widthBytes > 0xFFFF:
            raise Exception('the total character width is too big')

    except Exception as e:
        raise Exception(st.fileName + str(e))

    # WRITE
    st = fnio.OutputStream(output)

    if st.file.isatty():
        raise Exception(
            'binary output may not be send to a terminal, use -o or redirect/pipe it'
        )

    def IntEql(index, value):
        return 1 if font.xlfd[index].upper() == value else 0

    try:
        # HEADER
        family = font.xlfd[bdf.XLFD.FAMILY_NAME]
        copyright = fnutil.unQuote(
            font.props['COPYRIGHT'])[:60] if 'COPYRIGHT' in font.props else ''
        proportional = IntEql(bdf.XLFD.SPACING, 'P')

        st.write16(0x0200)  # font version
        st.write32(vTell + len(family) + 1)  # total size
        st.writeZStr(copyright, 60 - len(copyright))
        st.write16(0)  # gdi, device type
        st.write16(round(font.bbx.height * 72 / 96))
        st.write16(96)  # vertical resolution
        st.write16(96)  # horizontal resolution
        st.write16(font.bbx.height + font.bbx.yoff)
        st.write16(0)  # internal leading
        st.write16(0)  # external leading
        st.write8(IntEql(bdf.XLFD.SLANT, 'I'))
        st.write8(0)  # underline
        st.write8(0)  # strikeout
        st.write16(400 + 300 * IntEql(bdf.XLFD.WEIGHT_NAME, 'BOLD'))
        st.write8(charset)
        st.write16(font.bbx.width * (1 - proportional))
        st.write16(font.bbx.height)
        st.write8((fntFamily << 4) + proportional)
        st.write16(font.getAvgWidth())
        st.write16(font.bbx.width)  # max width
        st.write8(minChar)
        st.write8(maxChar)

        defaultIndex = maxChar - minChar
        breakIndex = 0

        if font.defaultCode != None:
            defaultIndex = next(index for index, char in enumerate(font.chars)
                                if char.code == font.defaultCode)

        if minChar <= 0x20 and maxChar >= 0x20:
            breakIndex = 0x20 - minChar

        st.write8(defaultIndex)
        st.write8(breakIndex)
        st.write16(widthBytes)
        st.write32(0)  # device name
        st.write32(vTell)
        st.write32(0)  # gdi bits pointer
        st.write32(bitsOffset)
        st.write8(0)  # reserved
        # CTABLE
        for value in cTable:
            st.write16(value)
        # GLYPHS
        data = bytearray(font.bbx.rowSize() * font.bbx.height)

        for char in font.chars:
            rowSize = char.bbx.rowSize()
            counter = 0
            # MS coordinates
            for n in range(0, rowSize):
                for y in range(0, font.bbx.height):
                    data[counter] = char.data[rowSize * y + n]
                    counter += 1
            st.write(data, counter)
        st.write(bytes(sentinel * font.bbx.height))
        # FAMILY
        st.writeZStr(family, 1)
        st.close()
    except Exception as e:
        st.destroy()
        raise Exception(st.fileName + str(e))
Ejemplo n.º 4
0
def main_program(nonopt, parsed):
    bstr = lambda number: bytes(str(number), 'ascii')

    # NON-OPTIONS
    if len(nonopt) < 4:
        raise Exception('invalid number of arguments, try --help')

    input = nonopt[0]
    registry = nonopt[1]
    encoding = nonopt[2]
    new_codes = []

    if not re.fullmatch(r'[A-Za-z][\w.:()]*', registry) or not re.fullmatch(
            r'[\w.:()]+', encoding):
        raise Exception('invalid registry or encoding')

    # READ INPUT
    ifs = fnio.InputStream(input)

    try:
        old_font = bdf.Font.read(ifs)
        ifs.close()
    except Exception as ex:
        raise Exception(ifs.location() + str(ex))

    # READ TABLES
    def load_code(line):
        new_codes.append(fnutil.parse_hex('unicode', line))

    for name in nonopt[3:]:
        ifs = fnio.InputStream(name)

        try:
            ifs.read_lines(load_code)
            ifs.close()
        except Exception as ex:
            raise Exception(ifs.location() + str(ex))

    if not new_codes:
        raise Exception('no characters in the output font')

    # CREATE GLYPHS
    new_font = bdf.Font()
    charmap = {char.code: char for char in old_font.chars}
    index = 0
    unstart = 0
    family = parsed.family if parsed.family is not None else old_font.xlfd[
        bdf.XLFD.FAMILY_NAME]

    if parsed.filter:
        unstart = 32 if registry == 'ISO10646' else bdf.CHARS_MAX

    for code in new_codes:
        if code == 0xFFFF and parsed.filter:
            index += 1
            continue

        if code in charmap:
            old_char = charmap[code]
            uni_ffff = False
        else:
            uni_ffff = True

            if code != 0xFFFF:
                raise Exception('%s does not contain U+%04X' % (input, code))

            if old_font.default_code != -1:
                old_char = charmap[old_font.default_code]
            elif 0xFFFD in charmap:
                old_char = charmap[0xFFFD]
            else:
                raise Exception(
                    '%s does not contain U+FFFF, and no replacement found' %
                    input)

        new_char = copy.copy(old_char)
        new_char.code = code if index >= unstart else index
        index += 1
        new_char.props = new_char.props.clone()
        new_char.props.set('ENCODING', bstr(new_char.code))
        new_font.chars.append(new_char)

        if uni_ffff:
            new_char.props.set('STARTCHAR', b'uniFFFF')
        elif old_char.code == old_font.default_code or (
                old_char.code == 0xFFFD and new_font.default_code == -1):
            new_font.default_code = new_char.code

    # CREATE HEADER
    registry = bytes(registry, 'ascii')
    encoding = bytes(encoding, 'ascii')

    for [name, value] in old_font.props:
        if name == 'FONT':
            new_font.xlfd = old_font.xlfd[:]
            new_font.xlfd[bdf.XLFD.FAMILY_NAME] = family
            new_font.xlfd[bdf.XLFD.CHARSET_REGISTRY] = registry
            new_font.xlfd[bdf.XLFD.CHARSET_ENCODING] = encoding
            value = b'-'.join(new_font.xlfd)
        elif name == 'STARTPROPERTIES':
            num_props = fnutil.parse_dec(name, value, 1)
        elif name == 'FAMILY_NAME':
            value = fnutil.quote(family)
        elif name == 'CHARSET_REGISTRY':
            value = fnutil.quote(registry)
        elif name == 'CHARSET_ENCODING':
            value = fnutil.quote(encoding)
        elif name == 'DEFAULT_CHAR':
            if new_font.default_code != -1:
                value = bstr(new_font.default_code)
            else:
                num_props -= 1
                continue
        elif name == 'ENDPROPERTIES':
            if new_font.default_code != -1 and new_font.props.get(
                    'DEFAULT_CHAR') is None:
                new_font.props.add('DEFAULT_CHAR', bstr(new_font.default_code))
                num_props += 1

            new_font.props.set('STARTPROPERTIES', bstr(num_props))
        elif name == 'CHARS':
            value = bstr(len(new_font.chars))

        new_font.props.add(name, value)

    # COPY FIELDS
    new_font.bbx = old_font.bbx
    new_font.finis = old_font.finis

    # WRITE OUTPUT
    ofs = fnio.OutputStream(parsed.output)

    try:
        new_font.write(ofs)
        ofs.close()
    except Exception as ex:
        raise Exception(ofs.location() + str(ex) + ofs.destroy())
Ejemplo n.º 5
0
def main_program(nonopt, parsed):
	if len(nonopt) > 1:
		raise Exception('invalid number of arguments, try --help')

	char_set = parsed.char_set
	min_char = parsed.min_char

	# READ INPUT
	ifs = fnio.InputStream(nonopt[0] if nonopt else None)

	try:
		font = bmpf.Font.read(ifs)
		ifs.close()
	except Exception as ex:
		raise Exception(ifs.location() + str(ex))

	# COMPUTE
	if char_set == -1:
		encoding = font.xlfd[bdf.XLFD.CHARSET_ENCODING]

		if re.fullmatch(b'(cp)?125[0-8]', encoding.lower()):
			char_set = FNT_CHARSETS[int(encoding[-1:])]
		else:
			char_set = 255

	try:
		num_chars = len(font.chars)

		if num_chars > 256:
			raise Exception('too many characters, the maximum is 256')

		if min_char == -1:
			if num_chars in [192, 256]:
				min_char = 256 - num_chars
			else:
				min_char = font.chars[0].code

		max_char = min_char + num_chars - 1

		if max_char >= 256:
			raise Exception('the maximum character code is too big, (re)specify -m')

		# HEADER
		vtell = WIN_FONTHEADERSIZE + (num_chars + 1) * 4
		bits_offset = vtell
		ctable = []
		width_bytes = 0

		# CTABLE/GLYPHS
		for char in font.chars:
			row_size = char.row_size()
			ctable.append(char.width)
			ctable.append(vtell)
			vtell += row_size * font.bbx.height
			width_bytes += row_size

		if vtell > 0xFFFF:
			raise Exception('too much character data')

		# SENTINEL
		sentinel = 2 - width_bytes % 2
		ctable.append(sentinel * 8)
		ctable.append(vtell)
		vtell += sentinel * font.bbx.height
		width_bytes += sentinel

		if width_bytes > 0xFFFF:
			raise Exception('the total character width is too big')

	except Exception as ex:
		raise Exception(ifs.location() + str(ex))

	# WRITE
	ofs = fnio.OutputStream(parsed.output)

	if ofs.file.isatty():
		raise Exception('binary output may not be send to a terminal, use -o or redirect/pipe it')

	try:
		# HEADER
		family = font.xlfd[bdf.XLFD.FAMILY_NAME]
		copyright = font.props.get('COPYRIGHT')
		copyright = fnutil.unquote(copyright)[:60] if copyright is not None else b''
		proportional = font.get_proportional()

		ofs.write16(0x0200)                                              # font version
		ofs.write32(vtell + len(family) + 1)                             # total size
		ofs.write_zstr(copyright, 60 - len(copyright))
		ofs.write16(0)                                                   # gdi, device type
		ofs.write16(round(font.bbx.height * 72 / 96))
		ofs.write16(96)                                                  # vertical resolution
		ofs.write16(96)                                                  # horizontal resolution
		ofs.write16(font.get_ascent())                                   # base line
		ofs.write16(0)                                                   # internal leading
		ofs.write16(0)                                                   # external leading
		ofs.write8(font.get_italic())
		ofs.write8(0)                                                    # underline
		ofs.write8(0)                                                    # strikeout
		ofs.write16(400 + 300 * font.get_bold())
		ofs.write8(char_set)
		ofs.write16(0 if proportional else font.bbx.width)
		ofs.write16(font.bbx.height)
		ofs.write8((parsed.fnt_family << 4) + proportional)
		ofs.write16(font.avg_width)
		ofs.write16(font.bbx.width)
		ofs.write8(min_char)
		ofs.write8(max_char)

		default_index = max_char - min_char
		break_index = 0

		if font.default_code != -1:
			default_index = next(index for index, char in enumerate(font.chars) if char.code == font.default_code)

		if min_char <= 0x20 <= max_char:
			break_index = 0x20 - min_char

		ofs.write8(default_index)
		ofs.write8(break_index)
		ofs.write16(width_bytes)
		ofs.write32(0)            # device name
		ofs.write32(vtell)
		ofs.write32(0)            # gdi bits pointer
		ofs.write32(bits_offset)
		ofs.write8(0)             # reserved

		# CTABLE
		for value in ctable:
			ofs.write16(value)

		# GLYPHS
		data = bytearray(font.bbx.height * font.bbx.row_size())

		for char in font.chars:
			row_size = char.row_size()
			counter = 0
			# MS coordinates
			for n in range(0, row_size):
				for y in range(0, font.bbx.height):
					data[counter] = char.data[row_size * y + n]
					counter += 1
			ofs.write(data[:counter])
		ofs.write(bytes(sentinel * font.bbx.height))

		# FAMILY
		ofs.write_zstr(family, 1)
		ofs.close()

	except Exception as ex:
		raise Exception(ofs.location() + str(ex) + ofs.destroy())
Ejemplo n.º 6
0
def mainProgram(optind):
	global version
	global exchange

	# READ INPUT
	if optind < len(sys.argv) and sys.argv[optind].lower().endswith('.bdf'):
		st = fnio.InputStream(sys.argv[optind])
		optind += 1
	else:
		st = fnio.InputStream(None)

	try:
		font = bdf.Font.read(st)
		st.close()
	except Exception as e:
		raise Exception(st.location() + str(e))

	try:
		if font.xlfd[bdf.XLFD.SPACING] != 'C':
			raise Exception('SPACING "C" required')

		keys = ['width', 'height', 'xoff', 'yoff']

		for char in font.chars:
			for name in keys:
				if getattr(char.bbx, name) != getattr(font.bbx, name):
					raise Exception('char {0}: BBX must be identical to FONTBOUNDINGBOX'.format(char.code))

			if char.code == 65534:
				raise Exception('65534 is not a character, use 65535 for empty position')

		ver1NumChars = len(font.chars) == 256 or len(font.chars) == 512

		if version == None:
			if ver1NumChars and font.bbx.width == 8:
				version = 1
			else:
				version = 2
		elif version == 1:
			if not ver1NumChars:
				raise Exception('-1 requires a font with 256 or 512 characters')

			if font.bbx.width != 8:
				raise Exception('-1 requires a font with width 8')

		vgaNumChars = len(font.chars) >= 224 and len(font.chars) <= 512
		vgaTextSize = font.bbx.width == 8 and font.bbx.height in [8, 14, 16]

		if exchange == None:
			exchange = vgaTextSize and version >= 1 and vgaNumChars and font.chars[0].code == 0x00A3
		elif exchange:
			if not vgaNumChars:
				raise Exception('-g/--vga requires a font with 224...512 characters')

			if not vgaTextSize:
				raise Exception('-g/--vga requires an 8x8, 8x14 or 8x16 font')
	except Exception as e:
		raise Exception(st.fileName + ': ' + str(e))

	# READ EXTRAS
	tables = dict()

	def loadExtra(line):
		words = re.split('\s+', line)

		if len(words) < 2:
			raise Exception('invalid format')

		uni = fnutil.parseHex('unicode', words[0])

		if uni == 0xFFFE:
			raise Exception('FFFE is not a character')

		if not uni in tables:
			tables[uni] = []

		table = tables[uni]

		del words[0]
		for word in words:
			dup = fnutil.parseHex('extra code', word)

			if dup == 0xFFFF:
				raise Exception('FFFF is not a character')

			if not dup in table or 0xFFFE in table:
				tables[uni].append(dup)

	while optind < len(sys.argv):
		st = fnio.InputStream(sys.argv[optind])
		optind += 1

		try:
			st.readLines(loadExtra)
			st.close()
		except Exception as e:
			raise Exception(st.location() + str(e))

	# REMAP
	newChars = font.chars[:]

	if exchange:
		for index in range(0, 32):
			newChars[index] = font.chars[0xC0 + index]
			newChars[0xC0 + index] = font.chars[index]

	# WRITE
	st = fnio.OutputStream(output)

	if st.file.isatty():
		raise Exception('binary output may not be send to a terminal, use -o or redirect/pipe it')

	try:
		# HEADER
		if version == 1:
			st.write8(0x36)
			st.write8(0x04)
			st.write8((len(font.chars) >> 8) + 1)
			st.write8(font.bbx.height)
		elif version == 2:
			st.write32(0x864AB572)
			st.write32(0x00000000)
			st.write32(0x00000020)
			st.write32(0x00000001)
			st.write32(len(font.chars))
			st.write32(len(font.chars[0].data))
			st.write32(font.bbx.height)
			st.write32(font.bbx.width)
		# GLYPHS
		for char in newChars:
			st.write(char.data, None)
		# UNICODES
		if version > 0:
			def writeUnicode(code):
				if version == 1:
					st.write16(code)
				elif code <= 0x7F:
					st.write8(code)
				elif code == 0xFFFE or code == 0xFFFF:
					st.write8(code & 0xFF)
				else:
					if code <= 0x7FF:
						st.write8(0xC0 + (code >> 6))
					else:
						if code <= 0xFFFF:
							st.write8(0xE0 + (code >> 12))
						else:
							st.write8(0xF0 + (code >> 18))
							st.write8(0x80 + ((code >> 12) & 0x3F))

						st.write8(0x80 + ((code >> 6) & 0x3F))

					st.write8(0x80 + (code & 0x3F))

			for char in newChars:
				if char.code != 0xFFFF:
					writeUnicode(char.code)

				if char.code in tables:
					for extra in tables[char.code]:
						writeUnicode(extra)

				writeUnicode(0xFFFF)
		# FINISH
		st.close()
	except Exception as e:
		st.destroy()
		raise Exception(st.fileName + ': ' + str(e))