1
This commit is contained in:
180
font.py
180
font.py
@@ -4,6 +4,11 @@ import time
|
||||
import binascii
|
||||
import gc
|
||||
|
||||
try:
|
||||
import static_font_data
|
||||
except ImportError:
|
||||
static_font_data = None
|
||||
|
||||
class Font:
|
||||
def __init__(self, ws=None):
|
||||
self.ws = ws
|
||||
@@ -11,6 +16,8 @@ class Font:
|
||||
self.pending_requests = set()
|
||||
self.retry_count = {}
|
||||
self.max_retries = 3
|
||||
# Pre-allocate buffer for row drawing (16 pixels * 2 bytes = 32 bytes)
|
||||
self.row_buf = bytearray(32)
|
||||
|
||||
def set_ws(self, ws):
|
||||
self.ws = ws
|
||||
@@ -24,7 +31,40 @@ class Font:
|
||||
"""获取当前缓存的字体数量"""
|
||||
return len(self.cache)
|
||||
|
||||
def text(self, tft, text, x, y, color, bg=0x0000):
|
||||
def handle_message(self, msg):
|
||||
"""处理字体相关消息,更新缓存
|
||||
返回: 是否为字体消息
|
||||
"""
|
||||
if not isinstance(msg, str):
|
||||
return False
|
||||
|
||||
if msg.startswith("FONT_BATCH_END:"):
|
||||
# 批处理结束消息,目前主要用于阻塞等待时的退出条件
|
||||
return True
|
||||
|
||||
elif msg.startswith("FONT_DATA:"):
|
||||
parts = msg.split(":")
|
||||
if len(parts) >= 3:
|
||||
try:
|
||||
key_str = parts[1]
|
||||
if key_str.startswith("0x"):
|
||||
c = int(key_str, 16)
|
||||
else:
|
||||
c = int(key_str)
|
||||
|
||||
d = binascii.unhexlify(parts[2])
|
||||
self.cache[c] = d
|
||||
# 清除重试计数(如果有)
|
||||
if c in self.retry_count:
|
||||
del self.retry_count[c]
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Font data parse error: {e}")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def text(self, tft, text, x, y, color, bg=0x0000, wait=True):
|
||||
"""在ST7789显示器上绘制文本"""
|
||||
if not text:
|
||||
return
|
||||
@@ -32,17 +72,23 @@ class Font:
|
||||
color_bytes = struct.pack(">H", color)
|
||||
bg_bytes = struct.pack(">H", bg)
|
||||
|
||||
lut = [bytearray(16) for _ in range(256)]
|
||||
for i in range(256):
|
||||
for bit in range(8):
|
||||
val = (i >> bit) & 1
|
||||
idx = (7 - bit) * 2
|
||||
# Create a mini-LUT for 4-bit chunks (16 entries * 8 bytes = 128 bytes)
|
||||
# Each entry maps 4 bits (0-15) to 4 pixels (8 bytes)
|
||||
mini_lut = []
|
||||
for i in range(16):
|
||||
chunk = bytearray(8)
|
||||
for bit in range(4):
|
||||
# bit 0 is LSB of nibble, corresponds to rightmost pixel of the 4 pixels
|
||||
# Assuming standard MSB-first bitmap
|
||||
val = (i >> (3 - bit)) & 1
|
||||
idx = bit * 2
|
||||
if val:
|
||||
lut[i][idx] = color_bytes[0]
|
||||
lut[i][idx+1] = color_bytes[1]
|
||||
chunk[idx] = color_bytes[0]
|
||||
chunk[idx+1] = color_bytes[1]
|
||||
else:
|
||||
lut[i][idx] = bg_bytes[0]
|
||||
lut[i][idx+1] = bg_bytes[1]
|
||||
chunk[idx] = bg_bytes[0]
|
||||
chunk[idx+1] = bg_bytes[1]
|
||||
mini_lut.append(bytes(chunk))
|
||||
|
||||
initial_x = x
|
||||
|
||||
@@ -50,6 +96,9 @@ class Font:
|
||||
for char in text:
|
||||
if ord(char) > 127:
|
||||
code = ord(char)
|
||||
# Check static font data first
|
||||
if static_font_data and hasattr(static_font_data, 'FONTS') and code in static_font_data.FONTS:
|
||||
continue
|
||||
if code not in self.cache:
|
||||
missing_codes.add(code)
|
||||
|
||||
@@ -57,10 +106,13 @@ class Font:
|
||||
missing_list = list(missing_codes)
|
||||
|
||||
req_str = ",".join([str(c) for c in missing_list])
|
||||
print(f"Batch requesting fonts: {req_str}")
|
||||
# Only print if waiting, to reduce log spam in async mode
|
||||
if wait:
|
||||
print(f"Batch requesting fonts: {req_str}")
|
||||
try:
|
||||
self.ws.send(f"GET_FONTS_BATCH:{req_str}")
|
||||
self._wait_for_fonts(missing_codes)
|
||||
if wait:
|
||||
self._wait_for_fonts(missing_codes)
|
||||
except Exception as e:
|
||||
print(f"Batch font request failed: {e}")
|
||||
|
||||
@@ -78,28 +130,64 @@ class Font:
|
||||
|
||||
is_chinese = False
|
||||
buf_data = None
|
||||
code = ord(char)
|
||||
|
||||
if ord(char) > 127:
|
||||
code = ord(char)
|
||||
if code in self.cache:
|
||||
if code > 127:
|
||||
if static_font_data and hasattr(static_font_data, 'FONTS') and code in static_font_data.FONTS:
|
||||
buf_data = static_font_data.FONTS[code]
|
||||
is_chinese = True
|
||||
elif code in self.cache:
|
||||
buf_data = self.cache[code]
|
||||
is_chinese = True
|
||||
else:
|
||||
if code in self.pending_requests:
|
||||
retry = self.retry_count.get(code, 0)
|
||||
if retry < self.max_retries:
|
||||
self.retry_count[code] = retry + 1
|
||||
self._request_single_font(code)
|
||||
# Missing font data
|
||||
if not wait:
|
||||
# In async mode, draw a placeholder or space
|
||||
# We use '?' for now so user knows something is missing
|
||||
char = '?'
|
||||
is_chinese = False
|
||||
else:
|
||||
if code in self.pending_requests:
|
||||
retry = self.retry_count.get(code, 0)
|
||||
if retry < self.max_retries:
|
||||
self.retry_count[code] = retry + 1
|
||||
self._request_single_font(code)
|
||||
|
||||
if is_chinese and buf_data:
|
||||
self._draw_bitmap(tft, buf_data, x, y, 16, 16, lut)
|
||||
self._draw_bitmap_optimized(tft, buf_data, x, y, mini_lut)
|
||||
x += 16
|
||||
else:
|
||||
if ord(char) > 127:
|
||||
if code > 127:
|
||||
char = '?'
|
||||
self._draw_ascii(tft, char, x, y, color, bg)
|
||||
x += 8
|
||||
|
||||
def _draw_bitmap_optimized(self, tft, bitmap, x, y, mini_lut):
|
||||
"""使用优化方式绘制位图,减少内存分配"""
|
||||
# Bitmap is 32 bytes (16x16 pixels)
|
||||
# 2 bytes per row
|
||||
|
||||
for row in range(16):
|
||||
# Get 2 bytes for this row
|
||||
# Handle case where bitmap might be different length (safety)
|
||||
if row * 2 + 1 < len(bitmap):
|
||||
b1 = bitmap[row * 2]
|
||||
b2 = bitmap[row * 2 + 1]
|
||||
|
||||
# Process b1 (Left 8 pixels)
|
||||
# High nibble
|
||||
self.row_buf[0:8] = mini_lut[(b1 >> 4) & 0x0F]
|
||||
# Low nibble
|
||||
self.row_buf[8:16] = mini_lut[b1 & 0x0F]
|
||||
|
||||
# Process b2 (Right 8 pixels)
|
||||
# High nibble
|
||||
self.row_buf[16:24] = mini_lut[(b2 >> 4) & 0x0F]
|
||||
# Low nibble
|
||||
self.row_buf[24:32] = mini_lut[b2 & 0x0F]
|
||||
|
||||
tft.blit_buffer(self.row_buf, x, y + row, 16, 1)
|
||||
|
||||
def _request_single_font(self, code):
|
||||
"""请求单个字体"""
|
||||
if self.ws:
|
||||
@@ -134,10 +222,10 @@ class Font:
|
||||
if msg is None:
|
||||
continue
|
||||
|
||||
if isinstance(msg, str):
|
||||
if self.handle_message(msg):
|
||||
# 如果是批处理结束,检查是否有失败的
|
||||
if msg.startswith("FONT_BATCH_END:"):
|
||||
parts = msg[15:].split(":")
|
||||
success = int(parts[0]) if len(parts) > 0 else 0
|
||||
failed = int(parts[1]) if len(parts) > 1 else 0
|
||||
|
||||
if failed > 0:
|
||||
@@ -145,34 +233,26 @@ class Font:
|
||||
for c in temp_missing:
|
||||
if c not in self.cache:
|
||||
print(f"Font failed after retries: {c}")
|
||||
self.cache[c] = None
|
||||
self.cache[c] = None # 标记为 None 避免死循环
|
||||
if c in target_codes:
|
||||
target_codes.remove(c)
|
||||
|
||||
# 清除所有剩余的目标,因为批处理结束了
|
||||
# 但实际上可能只需要清除 failed 的。
|
||||
# 无论如何,收到 BATCH_END 意味着本次请求处理完毕。
|
||||
# 如果还有没收到的,可能是丢包了。
|
||||
# 为了简单起见,我们认为结束了。
|
||||
target_codes.clear()
|
||||
|
||||
elif msg.startswith("FONT_DATA:"):
|
||||
parts = msg.split(":")
|
||||
if len(parts) >= 3:
|
||||
try:
|
||||
key_str = parts[1]
|
||||
if key_str.startswith("0x"):
|
||||
c = int(key_str, 16)
|
||||
else:
|
||||
c = int(key_str)
|
||||
|
||||
# 检查是否有新缓存的字体满足了 target_codes
|
||||
temp_target = list(target_codes)
|
||||
for c in temp_target:
|
||||
if c in self.cache:
|
||||
target_codes.remove(c)
|
||||
if c in self.retry_count:
|
||||
del self.retry_count[c]
|
||||
|
||||
d = binascii.unhexlify(parts[2])
|
||||
self.cache[c] = d
|
||||
if c in target_codes:
|
||||
target_codes.remove(c)
|
||||
if c in self.retry_count:
|
||||
del self.retry_count[c]
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
self.local_deferred.append(msg)
|
||||
|
||||
elif msg is not None:
|
||||
else:
|
||||
self.local_deferred.append(msg)
|
||||
|
||||
except Exception as e:
|
||||
@@ -183,12 +263,6 @@ class Font:
|
||||
self.ws.unread_messages = self.local_deferred + self.ws.unread_messages
|
||||
self.local_deferred = []
|
||||
|
||||
def _draw_bitmap(self, tft, bitmap, x, y, w, h, lut):
|
||||
"""绘制位图"""
|
||||
chunks = [lut[b] for b in bitmap]
|
||||
rgb_buf = b''.join(chunks)
|
||||
tft.blit_buffer(rgb_buf, x, y, w, h)
|
||||
|
||||
def _draw_ascii(self, tft, char, x, y, color, bg):
|
||||
"""绘制ASCII字符"""
|
||||
w, h = 8, 8
|
||||
|
||||
Reference in New Issue
Block a user