Files
STM32F4-Base/tools/font_gen/make_font.py
T

146 lines
6.4 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
# -*- coding: utf-8 -*-
"""生成中文子集位图字体 ui_font_cn_12.c(LVGL 格式,输出到 Drivers/BSP/UI/)。
前置依赖:
npm i -g lv_font_conv # 离线 TTF/OTF -> LVGL 字体 转换器
pip install fonttools # 仅当字体源是 .ttc 集合时需要(用于抽取单字重)
用法:
python tools/font_gen/make_font.py # 默认:宋体, 12px, 1bpp
python tools/font_gen/make_font.py --bpp 2 # 宋体细笔画发虚时改用 2bpp
python tools/font_gen/make_font.py --font C:/path/to/font.ttf
"""
import os
import sys
import argparse
import tempfile
import subprocess
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
OUT = os.path.join(ROOT, 'Drivers', 'BSP', 'UI', 'ui_font_cn_12.c')
CHARS_FILE = os.path.join(ROOT, 'ui_cn_chars.txt')
FONT_NAME = 'ui_font_cn_12'
def resolve_lv_font_conv():
# 1) 全局 node 模块路径(npm i -g)
try:
groot = subprocess.check_output(['npm', 'root', '-g'], text=True).strip()
p = os.path.join(groot, 'lv_font_conv', 'lv_font_conv.js')
if os.path.exists(p):
return ['node', p]
except Exception:
pass
# 2) PATH 上的可执行
for cmd in ('lv_font_conv.cmd', 'lv_font_conv'):
if subprocess.run(['where', cmd], capture_output=True, shell=True).returncode == 0:
return [cmd]
sys.exit('ERROR: 找不到 lv_font_conv。请先执行: npm i -g lv_font_conv')
def resolve_font(path):
if path:
if not os.path.exists(path):
sys.exit('ERROR: --font 指定的文件不存在: ' + path)
return path
# 默认字体:Windows 宋体集合 simsun.ttc(lv_font_conv 不支持 .ttc,需先抽取 face0)
ttc = r'C:\Windows\Fonts\simsun.ttc'
if os.path.exists(ttc):
try:
from fontTools.ttLib import TTCollection
coll = TTCollection(ttc)
tmp = os.path.join(tempfile.gettempdir(), 'simsun_regular.ttf')
coll.fonts[0].save(tmp)
print('extracted SimSun regular (face0) ->', tmp)
return tmp
except Exception as e:
sys.exit('ERROR: 抽取 simsun.ttc 失败,请 pip install fonttools,或用 --font 指定 ttf: ' + str(e))
sys.exit('ERROR: 未找到 C:\\Windows\\Fonts\\simsun.ttc,请用 --font 指定字体 ttf/otf 路径')
# ---------------------------------------------------------------------------
# 箭头字形合并
# 宋体(simsun.ttc)没有 U+25B6(▶)字形,lv_font_conv 会静默跳过 → 真机显示成方框。
# 该字形从 Segoe UI Symbol(seguisym.ttf,Windows 自带)提取,全量重生成字体后
# 自动补回,避免"重生成字体箭头又变方框"。
# ---------------------------------------------------------------------------
ARROW_CP_DEC = 9654 # U+25B6 ▶
ARROW_BITMAP = [0x80, 0xe0, 0xf0, 0xfc, 0xff, 0xfe, 0xf8, 0xf0, 0xc0, 0x0]
ARROW_ADV_W, ARROW_BOX_W, ARROW_BOX_H, ARROW_OFS_X, ARROW_OFS_Y = 165, 8, 10, 1, -1
def merge_arrow_glyph(font_path):
"""若字体缺 U+25B6(▶),把预提取的箭头字形合并进去(位图 + glyph_dsc + cmap)。"""
import re
raw = open(font_path, 'rb').read()
has_bom = raw.startswith(b'\xef\xbb\xbf')
src = raw.decode('utf-8-sig')
if re.search(r'\.range_start\s*=\s*9654\s*,', src):
print('arrow U+25B6 已存在,跳过合并')
return
# 当前位图总字节数(新字形 bitmap_index 的取值)
bm = re.search(r'glyph_bitmap\[\]\s*=\s*\{', src)
end_bm = src.find('\n};', bm.end())
body_bm = re.sub(r'/\*.*?\*/', '', src[bm.end():end_bm], flags=re.S)
total = len(re.findall(r'0x[0-9a-fA-F]+', body_bm))
glyph_count = len(re.findall(r'\.bitmap_index\s*=\s*\d+', src))
# 1) 追加位图
bmp = ',\n\n /* U+25B6 "▶"(来源:Segoe UI Symbol;宋体无此字形) */\n ' + \
', '.join('0x%02x' % b for b in ARROW_BITMAP)
src = src[:end_bm] + bmp + src[end_bm:]
# 2) 追加 glyph_dsc
gd = re.search(r'glyph_dsc\[\]\s*=\s*\{', src)
end_gd = src.find('\n};', gd.end())
entry = ',\n {.bitmap_index = %d, .adv_w = %d, .box_w = %d, .box_h = %d, .ofs_x = %d, .ofs_y = %d} /* U+25B6 */' % (
total, ARROW_ADV_W, ARROW_BOX_W, ARROW_BOX_H, ARROW_OFS_X, ARROW_OFS_Y)
src = src[:end_gd] + entry + src[end_gd:]
# 3) 追加 cmap(单码位映射)
cm = re.search(r'cmaps\[\]\s*=\s*\{', src)
end_cm = src.find('\n};', cm.end())
cmap = ',\n {\n .range_start = %d, .range_length = 1, .glyph_id_start = %d,\n' \
' .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY\n }' % (
ARROW_CP_DEC, glyph_count)
src = src[:end_cm] + cmap + src[end_cm:]
# 4) cmap_num +1
src = re.sub(r'\.cmap_num = (\d+),',
lambda m: '.cmap_num = %d,' % (int(m.group(1)) + 1), src, count=1)
out = b'\xef\xbb\xbf' if has_bom else b''
open(font_path, 'wb').write(out + src.encode('utf-8'))
print('已合并箭头 U+25B6:位图+%d 字节,glyph_id=%d' % (len(ARROW_BITMAP), glyph_count))
def main():
ap = argparse.ArgumentParser()
ap.add_argument('--bpp', type=int, default=1, help='字体位深 1/2/3(默认 1)')
ap.add_argument('--size', type=int, default=12, help='字号(默认 12)')
ap.add_argument('--font', default=None, help='自定义字体 ttf/otf 路径(默认宋体)')
args = ap.parse_args()
if not os.path.exists(CHARS_FILE):
sys.exit('ERROR: 请先运行 extract_cn.py 生成 ui_cn_chars.txt')
chars = open(CHARS_FILE, encoding='utf-8').read().strip()
cmd = resolve_lv_font_conv()
font = resolve_font(args.font)
cmd += ['--font', font,
'--size', str(args.size),
'--bpp', str(args.bpp),
'-r', '0x20-0x7E', # 含 ASCII,使字体自包含
'--symbols', chars, # 本工程实际显示的汉字子集
'--no-compress', # 关闭 RLE 压缩,保证兼容
'--format', 'lvgl',
'--lv-font-name', FONT_NAME,
'-o', OUT]
print('RUN:', ' '.join(cmd))
r = subprocess.run(cmd)
if r.returncode != 0:
sys.exit('FAILED rc=%d' % r.returncode)
merge_arrow_glyph(OUT) # 补回宋体缺失的 ▶ 字形(幂等)
print('OK %d bytes -> %s' % (os.path.getsize(OUT), OUT))
if __name__ == '__main__':
main()