-
Notifications
You must be signed in to change notification settings - Fork 21
/
stm32-font.py
179 lines (143 loc) · 5.53 KB
/
stm32-font.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
from PIL import Image, ImageFont, ImageDraw
import argparse
import math
import re
import time
import textwrap
import regex
# Greyscale threshold from 0 - 255
THRESHOLD = 128
# Font Character Set
CHAR_SET = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'
def get_charset_perceived():
# https://stackoverflow.com/questions/6805311/playing-around-with-devanagari-characters
return regex.findall(r'\X', CHAR_SET)
def get_max_width(font):
widths = []
for ch in get_charset_perceived():
w, h = font.getsize(ch)
widths.append(w)
return max(widths)
def bin_to_c_hex_array(bin_text, bytes_per_line, lsb_padding=0, msb_padding=0):
# create comment with preview of line
comment = bin_text.replace('0', ' ').replace('1', '#')
# pad the top or bottom remaining bits with 0's
bin_text = ("0" * msb_padding) + bin_text + ("0" * lsb_padding)
# ensure the length matches the number of bytes
assert len(bin_text) == (bytes_per_line * 8)
# split up into 8 digits each of bytes
bin_list = re.findall('.{8}', bin_text)
# convert to hex representation
bin_list = map(lambda a: "0x{:02X}".format(int(a, 2)), bin_list)
array = ', '.join(bin_list)
return f'{array}, /* |{comment}| */\r\n'
def generate_font_data(font, x_size, y_size):
data = ''
# find bytes per line needed to fit the font width
bytes_per_line = math.ceil(x_size / 8)
empty_bit_padding = (bytes_per_line * 8 - x_size)
for i, ch in enumerate(get_charset_perceived()):
# the starting array index of the current char
array_offset = i * (bytes_per_line * y_size)
assert data.count('0x') == array_offset
# comment separator for each char
data += '\r\n'
data += f"// @{array_offset} '{ch}' ({font_width} pixels wide)\r\n"
# Calculate size and margins for centered text
w, h = font.getsize(ch)
x_margin = (x_size - w) // 2
y_margin = (y_size - h) // 2
margin = (x_margin, y_margin)
im_size = (x_size, y_size)
# create image and write the char
im = Image.new("RGB", im_size)
drawer = ImageDraw.Draw(im)
drawer.text(margin, ch, font=font)
del drawer
# for each row, convert to hex representation
for y in range(y_size):
# get list of row pixels
x_coordinates = range(x_size)
pixels = map(lambda x: im.getpixel((x, y))[0], x_coordinates)
# convert to bin text
bin_text = map(lambda val: '1' if val > THRESHOLD else '0', pixels)
bin_text = ''.join(bin_text)
# convert to c-style hex array
data += bin_to_c_hex_array(bin_text, bytes_per_line,
lsb_padding=empty_bit_padding)
return data
def output_files(font, font_width, font_height, font_data, font_name):
generated_time = time.strftime("%Y-%m-%d %H:%M:%S")
# create filename, remove invalid chars
filename = f'Font{font_name}{font_height}'
filename = ''.join(c if c.isalnum() else '' for c in filename)
# C file template
output = f"""/**
* This file provides '{font_name}' [{font_height}px] text font
* for STM32xx-EVAL's LCD driver.
*
* Generated by zst123 on {generated_time}
*/
#pragma once
#include "../../../Utilities/Fonts/fonts.h"
#define {filename}_Name ("{font_name} {font_height}px")
// {font_data.count('0x')} bytes
const uint8_t {filename}_Table [] = {{{font_data}}};
sFONT {filename} = {{
{filename}_Table,
{font_width}, /* Width */
{font_height}, /* Height */
}};
"""
# Output font C header file
with open(f'{filename}.h', 'w') as f:
f.write(output)
# Output preview of font
size = font.getsize(CHAR_SET)
im = Image.new("RGB", size)
drawer = ImageDraw.Draw(im)
drawer.text((0, 0), CHAR_SET, font=font)
im.save(f'{filename}.png')
if __name__ == '__main__':
# Command-line arguments
parser = argparse.ArgumentParser(
description='Generate text font for STM32xx-EVAL\'s LCD driver')
parser.add_argument('-f', '--font',
type=str,
help='Font type [filename]',
required=True)
parser.add_argument('-s', '--size',
type=int,
help='Font size in pixels [int]',
default=16,
required=False)
parser.add_argument('-n', '--name',
type=str,
help='Custom font name [str]',
required=False)
parser.add_argument('-c', '--charset',
type=str,
help='Custom charset from file [filename]',
required=False)
args = parser.parse_args()
if args.charset:
with open(args.charset) as f:
CHAR_SET = f.read().splitlines()[0]
# create font type
font_type = args.font
font_height = args.size
myfont = ImageFont.truetype(font_type, size=font_height)
font_width = get_max_width(myfont)
if args.name:
font_name = args.name
else:
font_name = myfont.font.family
# generate the C file data
font_data = generate_font_data(myfont, font_width, font_height)
font_data = textwrap.indent(font_data, ' ' * 4)
# output everything
output_files(font=myfont,
font_width=font_width,
font_height=font_height,
font_data=font_data,
font_name=font_name)