Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/gen_ui_font.py
T
MeowOS Dev 245bee2946 MeowOS for HiBy R3 Pro II — 公共发布版本(历史已重置)
• 全新单提交:清除含敏感信息的旧历史(sudo 口令、内网 IP、SSH 密钥路径、热点 SSID、设备 MAC)
• 本地开发文档/测试记录/构建脚本全部移入 gitignored 的 devdocs/ 与 scripts/build/(不入库)
• 保留:MeowOS 源码(launcher/watchdog/bootmenu/meow_svcd/SDK)、项目规范文档、mips-repo
增补组件:SDK 统一日志+通知 API、meow_svcd 服务管理器、mips-repo 软件仓库
2026-08-30 01:12:34 +08:00

141 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# gen_ui_font.py — 预渲染 MeowOS UI 位图字库(替代 freetype,零依赖)
#
# 输入默认字体(本脚本用设备自带 default.otf,因为 ~/Fonts 属主为 10195 不可读;
# 如需切换思源黑体/MiSans,改 SRC 路径并把字体拷到可读处,或经 meow.conf font_file= 指定)。
# 输出 ui_font.dat:自定义二进制位图字库(8-bit alpha 灰度,白字用 alpha,blit 时着色)。
# 布局(小端):
# +0 uint32 magic "UIF1"
# +4 uint16 px (设计字号,用于行距/参考)
# +6 uint16 flags (0 = 8-bit alpha 灰度)
# +8 uint32 n (glyph 数)
# +12 uint16 atlas_w (位图集宽)
# +14 uint16 atlas_h
# +16 16*n entry: uint32 cp; uint16 x,y,w,h,adv
# +16+16*n bitmap: atlas_w*atlas_h 字节(8-bit alpha)
#
# 字符集:ASCII 0x20-0x7E + GB2312 一级汉字(0xB0-0xD7) + 常用 CJK 标点 + UI 显式字符串。
import struct, sys
from PIL import Image, ImageDraw, ImageFont
SRC = "fw_ssh/squashfs-root/usr/resource/fonts/default.otf"
PX = 24 # 设计像素高(大字,配合 40px 状态栏)
OUT = sys.argv[1] if len(sys.argv) > 1 else "/tmp/ftg/ui_font.dat"
# ---- 字符集 ----
chars = set()
for c in range(0x20, 0x7F):
chars.add(chr(c)) # ASCII 可打印
# GB2312 一级汉字: 0xB0A1(176,161) .. 0xD7F9(215,249),每行 94
for row in range(0xB0, 0xD8):
for col in range(0xA1, 0xFF):
b = bytes([row, col])
try:
ch = b.decode('gb2312')
except Exception:
continue
if ch and not ch.startswith(' '):
# 去掉 PUA/未定义区
if ord(ch[0]) >= 0x4E00 or (0xFF00 <= ord(ch[0]) <= 0xFFEF):
chars.add(ch[0])
# 常用 CJK 标点 / 全角
for ch in ",。、!?:;()《》【】「」『』—…¥%·×÷=+-·  〇":
chars.add(ch)
# UI 显式中文(电源菜单/状态栏/提示),确保必有
for ch in "关机重启回到取消电源菜单音量蓝牙电量充电返回播放器需网络设置我你的好中英文数分秒年月日应用没有存储卡请插入后开吃到找到放缺失导航打触摸选择无法启动以确认上下":
chars.add(ch)
chars = sorted(chars)
# 宽字符(CJK)用 PX 高,ASCII 用较小一点以保持基线一致;Pillow 自带 advance
font = ImageFont.truetype(SRC, PX)
# ---- 栅格化并打包进 atlas(行优先)----
# 先用足够大的空白画布测量每个字符 bitmap,再打包
tmp_img_cache = {}
maxw = PX * 2
packed = [] # (char, bbox_img, w, h, adv)
scale = 1
# 计算每个字符的 bitmap(grayscale alpha)
metas = []
for ch in chars:
if ch == ' ':
# 空格无 bitmap,只记 advance
adv = font.getlength(' ')
metas.append((ch, None, 0, 0, int(adv)))
continue
try:
bbox = font.getbbox(ch)
except Exception:
bbox = None
if not bbox:
adv = int(font.getlength(ch))
metas.append((ch, None, 0, 0, adv))
continue
x0, y0, x1, y1 = bbox
w = max(1, x1 - x0); h = max(1, y1 - y0)
# 渲染到 grayscale (alpha=灰度)
im = Image.new('L', (w, h), 0)
# 用 getmask 更精确;这里直接画到临时图再裁
mask = font.getmask(ch, mode='L')
im = Image.frombytes('L', mask.size, bytes(mask))
# 裁到 bbox 对应的部分
if 0 <= y0 < im.height and 0 <= x0 < im.width:
im = im.crop((max(0, x0), max(0, y0), min(im.width, x1), min(im.height, y1)))
adv = int(font.getlength(ch))
metas.append((ch, im, im.width, im.height, adv))
# 打包:放入 atlas(简单行打包,一行放满再换行)
atlas_w = 512
atlas_rows = []
x = 0; y = 0; maxY = 0
entry_pos = {} # char -> (x,y,w,h,adv)
# 先按宽高打包
pos_list = []
curx = 0; cury = 0; rowh = 0
entries = []
for ch, im, w, h, adv in metas:
if im is None:
entries.append((ch, 0, 0, 0, 0, adv)); continue
if curx + w > atlas_w:
curx = 0; cury += rowh; rowh = 0
entry_pos[ch] = (curx, cury, w, h)
entries.append((ch, curx, cury, w, h, adv))
curx += w + 1
rowh = max(rowh, h)
if cury + rowh > 4096:
raise RuntimeError("atlas too tall")
atlas_h = cury + rowh
atlas = bytearray(atlas_w * atlas_h)
for ch, im, w, h, adv in metas:
if im is None: continue
x, y, w, h = entry_pos[ch][0], entry_pos[ch][1], entry_pos[ch][2], entry_pos[ch][3]
for yy in range(h):
for xx in range(w):
v = im.getpixel((xx, yy))
atlas[(y + yy) * atlas_w + (x + xx)] = v
# ---- 写文件 ----
with open(OUT, 'wb') as f:
f.write(struct.pack('<I', 0x55494631)) # magic "UIF1"
f.write(struct.pack('<H', PX)) # px
f.write(struct.pack('<H', 0)) # flags 8-bit alpha
f.write(struct.pack('<I', len(entries))) # n
f.write(struct.pack('<H', atlas_w)) # atlas_w
f.write(struct.pack('<H', atlas_h)) # atlas_h
for ch, x, y, w, h, adv in entries:
f.write(struct.pack('<IHHHHH', ord(ch), x, y, w, h, adv))
f.write(bytes(atlas))
print(f"chars={len(chars)} entries={len(entries)} atlas={atlas_w}x{atlas_h} out={OUT}")
import os
print(f"size={os.path.getsize(OUT)} bytes")
# 自检:渲染取样图(含中文 + 英文)
img = Image.new('RGB', (480, 40), (0,0,0))
d = ImageDraw.Draw(img)
f = ImageFont.truetype(SRC, PX)
d.text((4, 4), "关机重启 回到HibyOS 取消 音量蓝牙 12:30:45 100%", font=f, fill=(255,255,255))
img.save('/tmp/ftg/ui_font_sample.png')
print("sample saved /tmp/ftg/ui_font_sample.png")