Code cleanup for fonttool.py (#739, fixes #738)

This commit is contained in:
xcodz-dot 2020-10-24 12:39:55 +05:30 committed by GitHub
parent 02a3dcbaa2
commit 0df9743c9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,266 +1,282 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import math import math
import sys
import os
import re import re
import argparse
CP_MAX = 0x10FFFF CP_MAX = 0x10FFFF
FONT_CPP = "data/font.cpp" FONT_CPP = "data/font.cpp"
FONT_HEIGHT = 12 FONT_HEIGHT = 12
PTRS_PER_LINE = 8 PTRS_PER_LINE = 8
class ReadBDFError(RuntimeError):
def __init__(self, line_number, message):
super().__init__(self, 'line %i: %s' % (line_number, message))
class FontTool: class FontTool:
def __init__(self): def __init__(self):
with open(FONT_CPP) as font_cpp: with open(FONT_CPP) as font_cpp:
self.font_cpp_data = font_cpp.read() self.font_cpp_data = font_cpp.read()
font_data = ([ int(s, 16) for s in re.findall(r'\w+', re.search(r'font_data[^{]*{([^;]+);', self.font_cpp_data, re.MULTILINE | re.DOTALL)[1]) ]) font_data = ([int(s, 16) for s in re.findall(r'\w+', re.search(r'font_data[^{]*{([^;]+);', self.font_cpp_data,
font_ptrs = ([ int(s, 16) for s in re.findall(r'\w+', re.search(r'font_ptrs[^{]*{([^;]+);', self.font_cpp_data, re.MULTILINE | re.DOTALL)[1]) ]) re.MULTILINE | re.DOTALL)[1])])
font_ranges = ([ int(s, 16) for s in re.findall(r'\w+', re.search(r'font_ranges[^{]*{([^;]+);', self.font_cpp_data, re.MULTILINE | re.DOTALL)[1]) ]) font_ptrs = ([int(s, 16) for s in re.findall(r'\w+', re.search(r'font_ptrs[^{]*{([^;]+);', self.font_cpp_data,
self.code_points = [ False for _ in range(CP_MAX + 2) ] re.MULTILINE | re.DOTALL)[1])])
ptrs_ptr = 0 font_ranges = ([int(s, 16) for s in re.findall(r'\w+',
for i in range(len(font_ranges) // 2 - 1): re.search(r'font_ranges[^{]*{([^;]+);', self.font_cpp_data,
for cp in range(font_ranges[i * 2], font_ranges[i * 2 + 1] + 1): re.MULTILINE | re.DOTALL)[1])])
base = font_ptrs[ptrs_ptr] self.code_points = [False for _ in range(CP_MAX + 2)]
self.code_points[cp] = font_data[base : (base + math.ceil(font_data[base] * FONT_HEIGHT / 4) + 1)] ptrs_ptr = 0
ptrs_ptr += 1 for i in range(len(font_ranges) // 2 - 1):
for cp in range(font_ranges[i * 2], font_ranges[i * 2 + 1] + 1):
base = font_ptrs[ptrs_ptr]
self.code_points[cp] = font_data[base: (base + math.ceil(font_data[base] * FONT_HEIGHT / 4) + 1)]
ptrs_ptr += 1
def commit(self): def commit(self):
new_ranges = [] new_ranges = []
in_range = False in_range = False
for i, data in enumerate(self.code_points): for i, data in enumerate(self.code_points):
if in_range and not data: if in_range and not data:
new_ranges[-1].append(i - 1) new_ranges[-1].append(i - 1)
in_range = False in_range = False
elif not in_range and data: elif not in_range and data:
in_range = True in_range = True
new_ranges.append([ i ]) new_ranges.append([i])
font_data_lines_hex = [ [ '0x%02X' % v for v in d ] for d in filter(lambda x: x, self.code_points) ] font_data_lines_hex = [['0x%02X' % v for v in d] for d in filter(lambda x: x, self.code_points)]
font_data_lines = [ len(h) > 1 and h[0] + ', ' + ', '.join(h[1 :]) + ',' or '0x00, ' for h in font_data_lines_hex ] font_data_lines = [len(h) > 1 and h[0] + ', ' + ', '.join(h[1:]) + ',' or '0x00, ' for h in
font_cpp_data = re.sub(r'font_data[^{]*{([^;]+);', 'font_data[] = {\n ' + '\n '.join(font_data_lines) + '\n};', self.font_cpp_data) font_data_lines_hex]
font_ptrs_blocks = [] font_cpp_data = re.sub(r'font_data[^{]*{([^;]+);',
data_ptr = 0 'font_data[] = {\n ' + '\n '.join(font_data_lines) + '\n};', self.font_cpp_data)
for ran in new_ranges: font_ptrs_blocks = []
block = [] data_ptr = 0
for cp in range(ran[0], ran[1] + 1): for ran in new_ranges:
block.append(data_ptr) block = []
data_ptr += math.ceil(self.code_points[cp][0] * FONT_HEIGHT / 4) + 1 for cp in range(ran[0], ran[1] + 1):
font_ptrs_wrapped = [] block.append(data_ptr)
for i in range(0, len(block), PTRS_PER_LINE): data_ptr += math.ceil(self.code_points[cp][0] * FONT_HEIGHT / 4) + 1
font_ptrs_wrapped.append(', '.join([ '0x%08X' % v for v in block[i : (i + PTRS_PER_LINE)] ])) font_ptrs_wrapped = []
font_ptrs_blocks.append(',\n '.join(font_ptrs_wrapped)) for i in range(0, len(block), PTRS_PER_LINE):
font_cpp_data = re.sub(r'font_ptrs[^{]*{([^;]+);', 'font_ptrs[] = {\n ' + ',\n\n '.join(font_ptrs_blocks) + ',\n};', font_cpp_data) font_ptrs_wrapped.append(', '.join(['0x%08X' % v for v in block[i: (i + PTRS_PER_LINE)]]))
font_ranges_lines = [ '{ 0x%06X, 0x%06X },' % ( r[0], r[1] ) for r in new_ranges ] font_ptrs_blocks.append(',\n '.join(font_ptrs_wrapped))
font_cpp_data = re.sub(r'font_ranges[^{]*{([^;]+);', 'font_ranges[][2] = {\n ' + '\n '.join(font_ranges_lines) + '\n { 0, 0 },\n};', font_cpp_data) font_cpp_data = re.sub(r'font_ptrs[^{]*{([^;]+);',
with open(FONT_CPP, 'w') as font_cpp: 'font_ptrs[] = {\n ' + ',\n\n '.join(font_ptrs_blocks) + ',\n};', font_cpp_data)
font_cpp.write(font_cpp_data) font_ranges_lines = ['{ 0x%06X, 0x%06X },' % (r[0], r[1]) for r in new_ranges]
font_cpp_data = re.sub(r'font_ranges[^{]*{([^;]+);',
'font_ranges[][2] = {\n ' + '\n '.join(font_ranges_lines) + '\n { 0, 0 },\n};',
font_cpp_data)
with open(FONT_CPP, 'w') as font_cpp:
font_cpp.write(font_cpp_data)
def pack(cp_matrix): def pack(cp_matrix):
width = 0 width = 0
for row in cp_matrix: for row in cp_matrix:
if width < len(row): if width < len(row):
width = len(row) width = len(row)
cp_data = [ width ] cp_data = [width]
bits = 8 bits = 8
for row in cp_matrix: for row in cp_matrix:
padded = row + [ 0 ] * (width - len(row)) padded = row + [0] * (width - len(row))
for cv in padded: for cv in padded:
if bits == 8: if bits == 8:
cp_data.append(0) cp_data.append(0)
bits = 0 bits = 0
cp_data[-1] |= (cv & 3) << bits cp_data[-1] |= (cv & 3) << bits
bits += 2 bits += 2
return cp_data return cp_data
def unpack(cp_data):
ptr = 1
bits = 0
buf = 0
cp_matrix = []
for y in range(FONT_HEIGHT):
cp_matrix.append([])
for x in range(cp_data[0]):
if bits == 0:
buf = cp_data[ptr]
ptr += 1
bits = 8
cp_matrix[-1].append(buf & 3)
buf >>= 2
bits -= 2
return cp_matrix
def unpack(cp_data):
ptr = 1
bits = 0
buf = 0
cp_matrix = []
for y in range(FONT_HEIGHT):
cp_matrix.append([])
for x in range(cp_data[0]):
if bits == 0:
buf = cp_data[ptr]
ptr += 1
bits = 8
cp_matrix[-1].append(buf & 3)
buf >>= 2
bits -= 2
return cp_matrix
class RawReader: class RawReader:
def __init__(self, path): def __init__(self, path):
self.code_points = [ False for _ in range(CP_MAX + 2) ] self.code_points = [False for _ in range(CP_MAX + 2)]
with open(path) as raw: with open(path) as raw:
items = [ int(v) for v in re.findall(r'[0-9]+', raw.read()) ] items = [int(v) for v in re.findall(r'[0-9]+', raw.read())]
ptr = 0 ptr = 0
while ptr <= len(items) - 2: while ptr <= len(items) - 2:
cp = items[ptr] cp = items[ptr]
width = items[ptr + 1] width = items[ptr + 1]
ptr += 2 ptr += 2
matrix = [] matrix = []
for i in range(ptr, ptr + width * FONT_HEIGHT, width): for i in range(ptr, ptr + width * FONT_HEIGHT, width):
matrix.append(items[i : (i + width)]) matrix.append(items[i: (i + width)])
ptr += width * FONT_HEIGHT ptr += width * FONT_HEIGHT
self.code_points[cp] = FontTool.pack(matrix) self.code_points[cp] = FontTool.pack(matrix)
class BDFReader: class BDFReader:
class ReadBDFError:
def __init__(line_number, message):
super(RuntimeError, self).__init__('line %i: %s' % ( line_number, message ))
def __init__(self, path, xoffs, yoffs): def __init__(self, path, xoffs, yoffs):
self.code_points = [ False for _ in range(CP_MAX + 2) ] self.code_points = [False for _ in range(CP_MAX + 2)]
item_re = re.compile(r'[^ \n\r]+') item_re = re.compile(r'[^ \n\r]+')
with open(path) as bdf: with open(path) as bdf:
global_dw = False global_dw = False
startchar = False startchar = False
bitmap = False bitmap = False
char_dw = False char_dw = False
char_cp = False char_cp = False
char_bbx = False char_bbx = False
skip = 0 skip = 0
for line_number, line in enumerate(bdf): for line_number, line in enumerate(bdf):
if skip: if skip:
skip -= 1 skip -= 1
continue continue
items = re.findall(item_re, line) items = re.findall(item_re, line)
if startchar and items[0] == 'ENDCHAR': if startchar and items[0] == 'ENDCHAR':
if len(bitmap) != char_bbx[1]: if len(bitmap) != char_bbx[1]:
raise ReadBDFError(line_number, "invalid bitmap data") raise ReadBDFError(line_number, "invalid bitmap data")
cp_matrix = [] cp_matrix = []
for y in range(FONT_HEIGHT): for y in range(FONT_HEIGHT):
cp_matrix.append([]) cp_matrix.append([])
for x in range(char_dw): for x in range(char_dw):
cv = 0 cv = 0
xx = x + xoffs xx = x + xoffs
yy = FONT_HEIGHT - 1 - y + yoffs yy = FONT_HEIGHT - 1 - y + yoffs
if xx >= char_bbx[2] and xx < char_bbx[0] + char_bbx[2] and yy >= char_bbx[3] and yy < char_bbx[1] + char_bbx[3]: if char_bbx[2] <= xx < char_bbx[0] + char_bbx[2] and char_bbx[3] <= yy < char_bbx[1] + \
cv = bitmap[char_bbx[1] - 1 - (yy - char_bbx[3])][xx - char_bbx[2]] * 3 char_bbx[3]:
cp_matrix[-1].append(cv) cv = bitmap[char_bbx[1] - 1 - (yy - char_bbx[3])][xx - char_bbx[2]] * 3
self.code_points[char_cp] = FontTool.pack(cp_matrix) cp_matrix[-1].append(cv)
startchar = False self.code_points[char_cp] = FontTool.pack(cp_matrix)
bitmap = False startchar = False
char_dw = False bitmap = False
char_cp = False char_dw = False
char_bbx = False char_cp = False
elif bitmap != False: char_bbx = False
if len(items) != 1: elif bitmap != False:
raise ReadBDFError(line_number, "missing bitmap data") if len(items) != 1:
bits = [] raise ReadBDFError(line_number, "missing bitmap data")
for ch in items[0]: bits = []
cv = int(ch, 16) for ch in items[0]:
bits += [ cv & 8 and 1 or 0, cv & 4 and 1 or 0, cv & 2 and 1 or 0, cv & 1 and 1 or 0 ] cv = int(ch, 16)
bitmap.append(bits[ : char_bbx[0]]) bits += [cv & 8 and 1 or 0, cv & 4 and 1 or 0, cv & 2 and 1 or 0, cv & 1 and 1 or 0]
elif items[0] == 'SIZE': bitmap.append(bits[: char_bbx[0]])
if len(items) != 4: elif items[0] == 'SIZE':
raise ReadBDFError(line_number, "invalid directive") if len(items) != 4:
elif items[0] == 'FONTBOUNDINGBOX': raise ReadBDFError(line_number, "invalid directive")
if len(items) != 5: elif items[0] == 'FONTBOUNDINGBOX':
raise ReadBDFError(line_number, "invalid directive") if len(items) != 5:
elif not startchar and items[0] == 'STARTCHAR': raise ReadBDFError(line_number, "invalid directive")
startchar = True elif not startchar and items[0] == 'STARTCHAR':
char_dw = global_dw startchar = True
elif items[0] == 'STARTPROPERTIES': char_dw = global_dw
if len(items) != 2: elif items[0] == 'STARTPROPERTIES':
raise ReadBDFError(line_number, "invalid directive") if len(items) != 2:
skip = int(items[1]) + 1 raise ReadBDFError(line_number, "invalid directive")
elif startchar and items[0] == 'BITMAP': skip = int(items[1]) + 1
bitmap = [] elif startchar and items[0] == 'BITMAP':
elif startchar and items[0] == 'BBX': bitmap = []
if len(items) != 5: elif startchar and items[0] == 'BBX':
raise ReadBDFError(line_number, "invalid directive") if len(items) != 5:
char_bbx = [ int(items[1]), int(items[2]), int(items[3]), int(items[4]) ] raise ReadBDFError(line_number, "invalid directive")
elif startchar and items[0] == 'ENCODING': char_bbx = [int(items[1]), int(items[2]), int(items[3]), int(items[4])]
if len(items) != 2: elif startchar and items[0] == 'ENCODING':
raise ReadBDFError(line_number, "invalid directive") if len(items) != 2:
char_cp = int(items[1]) raise ReadBDFError(line_number, "invalid directive")
elif items[0] == 'METRICSSET': char_cp = int(items[1])
if len(items) != 2: elif items[0] == 'METRICSSET':
raise ReadBDFError(line_number, "invalid directive") if len(items) != 2:
if int(items[1]) == 1: raise ReadBDFError(line_number, "invalid directive")
raise ReadBDFError(line_number, "font does not support writing direction 0") if int(items[1]) == 1:
elif items[0] == 'DWIDTH': raise ReadBDFError(line_number, "font does not support writing direction 0")
if len(items) != 3: elif items[0] == 'DWIDTH':
raise ReadBDFError(line_number, "invalid directive") if len(items) != 3:
if int(items[2]) != 0: raise ReadBDFError(line_number, "invalid directive")
raise ReadBDFError(line_number, "vertical component of dwidth vector is non-zero") if int(items[2]) != 0:
char_dw = int(items[1]) raise ReadBDFError(line_number, "vertical component of dwidth vector is non-zero")
if not startchar: char_dw = int(items[1])
global_dw = char_dw if not startchar:
global_dw = char_dw
if __name__ == "__main__": if __name__ == "__main__":
def print_usage_and_exit():
print("""Usage:
* fonttool.py addbdf FIRST LAST BDFFILE [XOFFS YOFFS]
* fonttool.py addraw FIRST LAST RAWFILE
* fonttool.py remove FIRST [LAST]
* fonttool.py inspect FIRST [LAST]
LAST defaults to FIRST, XOFFS and YOFFS default to 0. BDF is an parser = argparse.ArgumentParser("fonttool.py", description="font tools for managing fonts, this script can be"
archaic bitmap font format; look it up. " imported as a module",
fromfile_prefix_chars="@")
command = parser.add_subparsers(dest="command", required=True)
"Raw" files are simply ASCII-encoded white-space delimited lists addbdf = command.add_parser("addbdf", help="Adds BDF Formated Font")
addbdf.add_argument("first", metavar="FIRST", type=int)
addbdf.add_argument("last", metavar="LAST", type=int)
addbdf.add_argument("bdffile", metavar="BDFFILE", help="BDF is an archaic bitmap font format")
addbdf.add_argument("xoffs", metavar="XOFFS", nargs="?", default=0, type=int, help="Defaults to 0")
addbdf.add_argument("yoffs", metavar="YOFFS", nargs="?", default=0, type=int, help="Defaults to 0")
addraw = command.add_parser("addraw", help="Adds a Raw Formated Font")
addraw.add_argument("first", metavar="FIRST", type=int)
addraw.add_argument("last", metavar="LAST", type=int)
addraw.add_argument("rawfile", metavar="RAWFILE", help=""""Raw" files are simply ASCII-encoded white-space delimited \
lists
of decimal integer constants. These lists of integers encode of decimal integer constants. These lists of integers encode
characters as any number of consecutive character description characters as any number of consecutive character description
structures laid out as follows: structures laid out as follows:
* the code point corresponding to the character being described; * the code point corresponding to the character being described;
* the width in pixels of the character being described; * the width in pixels of the character being described;
* width times %i brightness levels between 0 and 3, a row-major matrix. * width times %i brightness levels between 0 and 3, a row-major matrix.""")
This script is also an importable module.""" % FONT_HEIGHT) remove = command.add_parser("remove", help="Remove")
exit(1) remove.add_argument("first", metavar="FIRST", type=int)
remove.add_argument("last", metavar="LAST", type=int, default=None, nargs="?", help="Defaults to FIRST")
if len(sys.argv) < 3: inspect = command.add_parser("inspect", help="Inspect")
print_usage_and_exit() inspect.add_argument("first", metavar="FIRST", type=int)
inspect.add_argument("last", metavar="LAST", type=int, default=None, nargs="?", help="Defaults to FIRST")
cp_first = int(sys.argv[2]) args = parser.parse_args()
if len(sys.argv) < 4:
cp_last = cp_first
else:
cp_last = int(sys.argv[3])
if cp_first < 0 or cp_last > CP_MAX or cp_first > cp_last:
print('invalid range')
exit(1)
ft = FontTool() cp_first = args.first
if args.last is None:
cp_last = cp_first
else:
cp_last = args.last
if cp_first < 0 or cp_last > CP_MAX or cp_first > cp_last:
print('invalid range')
exit(1)
if sys.argv[1] == 'addbdf': ft = FontTool()
if len(sys.argv) < 5:
print_usage_and_exit() if args.command == "addbdf":
xoffs = 0 xoffs = args.xoffs
yoffs = 0 yoffs = args.yoffs
if len(sys.argv) >= 6: bdfr = BDFReader(args.bdffile, xoffs, yoffs)
xoffs = int(sys.argv[5]) for i in range(cp_first, cp_last + 1):
if len(sys.argv) >= 7: if bdfr.code_points[i] and not ft.code_points[i]:
yoffs = int(sys.argv[6]) ft.code_points[i] = bdfr.code_points[i]
bdfr = BDFReader(sys.argv[4], xoffs, yoffs) ft.commit()
for i in range(cp_first, cp_last + 1): elif args.command == 'addraw':
if bdfr.code_points[i] and not ft.code_points[i]: rr = RawReader(args.rawfile)
ft.code_points[i] = bdfr.code_points[i] for i in range(cp_first, cp_last + 1):
ft.commit() if rr.code_points[i] and not ft.code_points[i]:
elif sys.argv[1] == 'addraw': ft.code_points[i] = rr.code_points[i]
if len(sys.argv) < 5: ft.commit()
print_usage_and_exit() elif args.command == 'remove':
rr = RawReader(sys.argv[4]) for i in range(cp_first, cp_last + 1):
for i in range(cp_first, cp_last + 1): ft.code_points[i] = False
if rr.code_points[i] and not ft.code_points[i]: ft.commit()
ft.code_points[i] = rr.code_points[i] elif args.command == 'inspect':
ft.commit() lut = [' ', '░░', '▒▒', '▓▓']
elif sys.argv[1] == 'remove': for i in range(cp_first, cp_last + 1):
for i in range(cp_first, cp_last + 1): if ft.code_points[i]:
ft.code_points[i] = False print('code point %i (%c)' % (i, i))
ft.commit() print('')
elif sys.argv[1] == 'inspect': print('\n'.join([''.join([lut[ch] for ch in row]) for row in FontTool.unpack(ft.code_points[i])]))
lut = [ ' ', '░░', '▒▒', '▓▓' ] print('')
for i in range(cp_first, cp_last + 1): else:
if ft.code_points[i]: print('code point %i (%c) is not available' % (i, i))
print('code point %i (%c)' % ( i, i ))
print('')
print('\n'.join([ ''.join([ lut[ch] for ch in row ]) for row in FontTool.unpack(ft.code_points[i]) ]))
print('')
else:
print('code point %i (%c) is not available' % ( i, i ))
else:
print_usage_and_exit()