-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathfontlib.py
388 lines (318 loc) · 10.7 KB
/
fontlib.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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
"""
The MIT License (MIT)
Copyright © 2021 Walkline Wang (https://walkline.wang)
Gitee: https://gitee.com/walkline/micropython-new-fontlib
"""
import os
import gc
import struct
import math
try:
import framebuf
MICROPYTHON = True
except ImportError:
MICROPYTHON = False
CURRENT_DIR = os.getcwd() if MICROPYTHON else os.path.dirname(__file__) + '/'
FONT_DIR = '/client/'
class FontLibHeaderException(Exception):
pass
class FontLibException(Exception):
pass
'''
Header Data Sample:
b'FMUX\xa4\xa1\x04\x00\x10\x10\xe4"\x01$E\x00\x00$Q\x00\x00\\\x00A\x00' # little-endian
[4] b'FMUX' - identify
[4] b'\xa4\xa1\x04\x00' - file length
[1] b'\x10' - font width
[1] b'\x10' - font height
[2] b'\xe4"' - character counts
[1] b'\x01' - has index table
[1] b'\x00' - scan mode
[1] b'\x00' - byte order
[4] b'$E\x00\x00' - ascii start address
[4] b'$Q\x00\x00' - gb2312 start address
[2] b'\x00\x00' - reserved
'''
class FontLibHeader(object):
LENGTH = 25
SCAN_MODE_HORIZONTAL = BYTE_ORDER_LSB = 0
SCAN_MODE_VERTICAL = BYTE_ORDER_MSB = 1
SCAN_MODE = {SCAN_MODE_HORIZONTAL: 'Horizontal', SCAN_MODE_VERTICAL: 'Vertical'}
BYTE_ORDER = {BYTE_ORDER_LSB: 'LSB', BYTE_ORDER_MSB: 'MSB'}
def __init__(self, header_data):
if len(header_data) != FontLibHeader.LENGTH:
raise FontLibHeaderException('Invalid header length')
self.identify,\
self.file_size,\
self.font_width,\
self.font_height,\
self.characters,\
self.has_index_table,\
self.scan_mode,\
self.byte_order,\
self.ascii_start,\
self.gb2312_start,\
_ = struct.unpack('<4sIBBHBBBII2s', header_data)
if self.identify not in (b'FMUX', b'FMUY'):
raise FontLibHeaderException('Invalid font file')
self.data_size = ((self.font_width - 1) // 8 + 1) * self.font_height
if self.has_index_table:
self.index_table_address = FontLibHeader.LENGTH
else:
self.index_table_address = 0
class FontLib(object):
ASCII_START = 0x20
ASCII_END = 0x7f
GB2312_START = 0x80
GB2312_END = 0xffef
def __init__(self, font_filename):
self.__font_filename = font_filename
self.__header = None
with open(self.__font_filename, 'rb') as font_file:
self.__header = FontLibHeader(memoryview(font_file.read(FontLibHeader.LENGTH)))
self.__placeholder_buffer = self.__get_character_unicode_buffer(font_file, {ord('?')}, True)[0][1]
gc.collect()
def __is_ascii(self, char_code):
return FontLib.ASCII_START <= char_code <= FontLib.ASCII_END
def __is_gb2312(self, char_code):
return FontLib.GB2312_START <= char_code <= FontLib.GB2312_END
def __get_character_unicode_buffer(self, font_file, unicode_set, is_placeholder=False):
buffer_list = []
ascii_list = []
gb2312_list = []
chunk_size = 1000
def __seek(offset, data, targets):
# target:
# 0: unicode
# 1: little endian bytes
# 2: charest index offset
seeked_count = 0
for target in targets:
if target[2] is not None:
seeked_count += 1
continue
seek_offset = -1
while True:
seek_offset = data.find(target[1], seek_offset + 1)
if seek_offset >= 0:
if seek_offset % 2 == 0:
target[2] = seek_offset + offset
break
else:
continue
else:
break
return seeked_count == len(targets)
gc.disable()
for unicode in unicode_set:
if self.__is_ascii(unicode):
char_offset = self.__header.ascii_start + (unicode - FontLib.ASCII_START) * self.__header.data_size
ascii_list.append([unicode, char_offset])
elif self.__is_gb2312(unicode):
gb2312_list.append([unicode, struct.pack('<H', unicode), None])
else:
buffer_list.append([unicode, memoryview(self.__placeholder_buffer)])
for ascii in ascii_list:
font_file.seek(ascii[1])
buffer_list.append([ascii[0], memoryview(font_file.read(self.__header.data_size))])
gc.enable()
if is_placeholder:
return buffer_list
if len(gb2312_list):
font_file.seek(self.__header.index_table_address)
for offset in range(self.__header.index_table_address, self.__header.ascii_start, chunk_size):
if __seek(offset, font_file.read(chunk_size), gb2312_list):
break
else:
__seek(self.__header.ascii_start - offset, font_file.read(chunk_size), gb2312_list)
gc.disable()
for gb2312 in gb2312_list:
if gb2312[2] is None:
buffer_list.append([gb2312[0], memoryview(self.__placeholder_buffer)])
else:
char_offset = int(self.__header.gb2312_start + (gb2312[2] - self.__header.index_table_address) / 2 * self.__header.data_size)
font_file.seek(char_offset)
buffer_list.append([gb2312[0], memoryview(font_file.read(self.__header.data_size))])
gc.enable()
gc.collect()
del gb2312_list
return buffer_list
def get_characters(self, characters: str):
result = {}
with open(self.__font_filename, 'rb') as font_file:
unicode_list = list(set(ord(char) for char in characters))
chunk = 30
for count in range(0, len(unicode_list) // chunk + 1):
for char in self.__get_character_unicode_buffer(font_file, unicode_list[count * chunk:count * chunk + chunk]):
result[char[0]] = char[1]
return result
@property
def scan_mode(self):
return self.__header.scan_mode
@property
def byte_order(self):
return self.__header.byte_order
@property
def data_size(self):
return self.__header.data_size
@property
def file_size(self):
return self.__header.file_size
@property
def font_width(self):
return self.__header.font_width
@property
def font_height(self):
return self.__header.font_height
@property
def characters(self):
return self.__header.characters
def info(self):
print('\
HZK Info: {}\n\
file size : {}\n\
font width : {}\n\
font height : {}\n\
data size : {}\n\
scan mode : {} ({})\n\
byte order : {} ({})\n\
characters : {}\n'.format(
self.__font_filename,
self.file_size,
self.font_width,
self.font_height,
self.data_size,
self.scan_mode,
FontLibHeader.SCAN_MODE[self.scan_mode],
self.byte_order,
FontLibHeader.BYTE_ORDER[self.byte_order],
self.characters
))
def reverseBits(n):
bits = "{:0>8b}".format(n)
return int(bits[::-1], 2)
def list_bin_files(root):
import os
def list_files(root):
files=[]
for dir in os.listdir(root):
fullpath = ('' if root=='/' else root)+'/'+dir
if os.stat(fullpath)[0] & 0x4000 != 0:
files.extend(list_files(fullpath))
else:
if dir.endswith('.bin'):
files.append(fullpath)
return files
return list_files(root)
def run_test():
font_files = list_bin_files(CURRENT_DIR + FONT_DIR)
if len(font_files) == 0:
print('No font file founded')
return
elif len(font_files) == 1:
if MICROPYTHON:
from utime import ticks_diff, ticks_us
start_time = ticks_us()
fontlib = FontLib(font_files[0])
if MICROPYTHON:
print('### load font file: {} ms'.format(ticks_diff(ticks_us(), start_time) / 1000))
fontlib.info()
else:
print('\nFont file list')
for index,file in enumerate(font_files, start=1):
print(' [{}] {}'.format(index, file))
selected=None
while True:
try:
selected=int(input('Choose a file: '))
print('')
assert type(selected) is int and 0 < selected <= len(font_files)
break
except KeyboardInterrupt:
break
except:
pass
if selected:
if MICROPYTHON:
from utime import ticks_diff, ticks_us
start_time = ticks_us()
fontlib = FontLib(font_files[selected - 1])
if MICROPYTHON:
print('### load font file: {} ms'.format(ticks_diff(ticks_us(), start_time) / 1000))
fontlib.info()
else:
return
if MICROPYTHON:
from machine import I2C, Pin
from drivers.ssd1306 import SSD1306_I2C
import framebuf
from utime import ticks_us, ticks_diff
i2c = I2C(0, scl=Pin(18), sda=Pin(19))
slave_list = i2c.scan()
if slave_list:
print('slave id: {}'.format(slave_list[0]))
oled = SSD1306_I2C(128, 64, i2c)
chars = '使用MicroPython开发板读取自定义字库并显示'
start_time = ticks_us()
buffer_list = fontlib.get_characters(chars)
diff_time = ticks_diff(ticks_us(), start_time) / 1000
print('### get {} chars: {} ms, avg: {} ms'.format(len(chars), diff_time, diff_time / len(chars)))
format = framebuf.MONO_VLSB
if fontlib.scan_mode == FontLibHeader.SCAN_MODE_HORIZONTAL:
format = framebuf.MONO_HMSB if fontlib.byte_order == FontLibHeader.BYTE_ORDER_MSB else framebuf.MONO_HLSB
def __fill_buffer(buffer, width, height, x, y):
fb = framebuf.FrameBuffer(bytearray(buffer), width, height, format)
oled.blit(fb, x, y)
x = y = 0
width = height = fontlib.font_height
start_time = ticks_us()
for char in chars:
buffer = memoryview(buffer_list[ord(char)])
if x > ((128 // width - 1) * width):
x = 0
y += height
__fill_buffer(buffer, width, height, x, y)
x += width
oled.show()
diff_time = ticks_diff(ticks_us(), start_time) / 1000
print('### show {} chars: {} ms, avg: {} ms'.format(len(chars), diff_time, diff_time / len(chars)))
else:
buffer_dict = fontlib.get_characters("Monitor") # '\ue900鼽爱我,中华!Hello⒉あβǚㄘB⑴■☆')
buffer_list = []
for unicode, buffer in buffer_dict.items():
buffer_list.append([unicode, buffer])
print("{}: {}\n".format(chr(unicode), bytes(buffer)))
data_size = fontlib.data_size
font_width = fontlib.font_width
font_height = fontlib.font_height
bytes_per_row = int(data_size / font_height)
chars_per_row = 150 // font_width
if fontlib.scan_mode == FontLibHeader.SCAN_MODE_VERTICAL:
for char in range(math.ceil(len(buffer_list) / chars_per_row)):
for count in range(bytes_per_row):
for col in range(8):
if count * 8 + col >= font_height: continue
for buffer in buffer_list[char * chars_per_row:char * chars_per_row + chars_per_row]:
if len(buffer[1]) < data_size: buffer[1] = bytes(data_size)
for index in range(count * font_width, count * font_width + font_width):
data = ''.join(reversed('{:08b}'.format(buffer[1][index])))
print('{}'.format(data[col].replace('0', '.').replace('1', '@')), end='')
print(' ', end='')
print('')
print('')
else:
for char in range(math.ceil(len(buffer_list) / chars_per_row)):
for row in range(font_height):
for buffer in buffer_list[char * chars_per_row:char * chars_per_row + chars_per_row]:
for index in range(bytes_per_row):
if len(buffer[1]) < data_size: buffer[1] = bytes(data_size)
data = buffer[1][row * bytes_per_row + index]
if fontlib.byte_order == FontLibHeader.BYTE_ORDER_MSB:
data = reverseBits(buffer[1][row * bytes_per_row + index])
offset = 8 if (index + 1) * 8 < font_width else 8 - ((index + 1) * 8 - font_width)
print('{:08b}'.format(data)[:offset].replace('0', '.').replace('1', '@'), end='')
print(' ', end='')
print('')
print('')
if __name__ == '__main__':
run_test()