Skip to content

Commit 49bce31

Browse files
authored
Merge pull request #1755 from martinling/font-edit
Add script to extract font data to an image file for editing.
2 parents 06453e2 + 478a520 commit 49bce31

1 file changed

Lines changed: 107 additions & 0 deletions

File tree

firmware/tools/edit-font.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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

Comments
 (0)