|
| 1 | +# Tool to extract font data, save to bitmap file, and apply modifications |
| 2 | +# to source code after editing the bitmap. |
| 3 | + |
| 4 | +from __future__ import print_function |
| 5 | +from matplotlib.pyplot import imread, imsave, imshow, show |
| 6 | +import numpy as np |
| 7 | +import sys |
| 8 | +import re |
| 9 | + |
| 10 | +if len(sys.argv) != 3: |
| 11 | + print("Usage: %s <font name> { show | extract | insert}" % sys.argv[0]) |
| 12 | + sys.exit(1) |
| 13 | + |
| 14 | +font_name = sys.argv[1] |
| 15 | +mode = sys.argv[2] |
| 16 | + |
| 17 | +# Where to find the font data - may need updated if code has changed. |
| 18 | +source_file = 'firmware/common/ui_portapack.c' |
| 19 | +data_marker = f'static const uint8_t {font_name}_glyph_data[] = {{' |
| 20 | +data_offset = 1 # Data starts this number of lines after start marker. |
| 21 | +end_pattern = '(.*)};$' # Indicates end of font data. |
| 22 | +def_marker = f'static const ui_font_t {font_name} =' |
| 23 | +def_offset = 1 |
| 24 | +def_pattern = '^\t{{(?P<width>\\d+), (?P<height>\\d+)}' |
| 25 | + |
| 26 | +# Image filename used in extract and insert modes. |
| 27 | +image_file = f'{font_name}.png' |
| 28 | + |
| 29 | +# Get the literals used to populate the font array in the source file |
| 30 | +lines = [line.rstrip() for line in open(source_file).readlines()] |
| 31 | +try: |
| 32 | + start = lines.index(data_marker) + data_offset |
| 33 | +except ValueError: |
| 34 | + print(f"Could not find glyph data for font '{font_name}'") |
| 35 | + sys.exit(1) |
| 36 | + |
| 37 | +data = str() |
| 38 | +for line in lines[start:]: |
| 39 | + if match := re.match(end_pattern, line): |
| 40 | + data += match.group(1) |
| 41 | + break |
| 42 | + else: |
| 43 | + data += line |
| 44 | + |
| 45 | +# Get the width and height from the font definition. |
| 46 | +try: |
| 47 | + def_start = lines.index(def_marker) |
| 48 | +except ValueError: |
| 49 | + print(f"Could not find definition for font '{font_name}'") |
| 50 | + sys.exit(1) |
| 51 | + |
| 52 | +def_match = re.match(def_pattern, lines[def_start + def_offset]) |
| 53 | + |
| 54 | +if def_match is None: |
| 55 | + print(f"Failed to parse definition of '{font_name}'") |
| 56 | + |
| 57 | +width = int(def_match.group('width')) |
| 58 | +height = int(def_match.group('height')) |
| 59 | + |
| 60 | +# Eval the literals to get the values into a numpy array |
| 61 | +packed = eval("np.array([%s], np.uint8)" % data) |
| 62 | + |
| 63 | +# Reorganise into a monochrome image. |
| 64 | +chars_per_row = 5 |
| 65 | +unpacked = np.unpackbits(packed) |
| 66 | +bitmaps = (unpacked |
| 67 | + .reshape(-1, height, width // 8, 8)[:,:,::-1,:] |
| 68 | + .reshape(-1, height, width)[:,:,::-1] |
| 69 | +) |
| 70 | +indices = np.arange(len(bitmaps)).reshape(-1, chars_per_row) |
| 71 | +image = np.block([[bitmaps[idx] for idx in row] for row in indices]) |
| 72 | + |
| 73 | +if mode == 'show': |
| 74 | + # Display font image |
| 75 | + imshow(image, cmap='binary') |
| 76 | + show() |
| 77 | + |
| 78 | +elif mode == 'extract': |
| 79 | + # Save font image |
| 80 | + imsave(image_file, image, format='png', cmap='binary') |
| 81 | + |
| 82 | +elif mode == 'insert': |
| 83 | + # Read in modified font image |
| 84 | + image = imread(image_file)[:,:,0].astype(bool) |
| 85 | + |
| 86 | + # Reorganise back to original order |
| 87 | + rows = image.reshape(-1, height, image.shape[1]) |
| 88 | + single_row_image = np.hstack(rows) |
| 89 | + num_chars = single_row_image.shape[1] // width |
| 90 | + bitmaps = np.array(np.split(single_row_image, num_chars, 1)) |
| 91 | + unpacked = (bitmaps |
| 92 | + [:,:,::-1].reshape(-1, height, width // 8, 8) |
| 93 | + [:,:,::-1,:].reshape(-1) |
| 94 | + ) |
| 95 | + packed = ~np.packbits(unpacked) |
| 96 | + |
| 97 | + # Replace lines of file in same format as used before |
| 98 | + bytes_per_row = 13 |
| 99 | + split_indices = np.arange(bytes_per_row, len(packed), bytes_per_row) |
| 100 | + for i, group in enumerate(np.split(packed, split_indices)): |
| 101 | + line = ("\t0x%02x," + " 0x%02x," * (len(group) - 1)) % tuple(group) |
| 102 | + if len(group) < bytes_per_row: |
| 103 | + line = line[:-1] + "};" |
| 104 | + lines[start + i] = line |
| 105 | + |
| 106 | + # Write out modified source file |
| 107 | + open(source_file, 'w').writelines([line + "\n" for line in lines]) |
0 commit comments