Files
xiaozhi-esp32/managed_components/78__xiaozhi-fonts/generate_fonts.ipynb
2025-09-13 23:40:38 +08:00

820 lines
59 KiB
Plaintext
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.
{
"cells": [
{
"cell_type": "markdown",
"id": "3036ad45",
"metadata": {},
"source": [
"# 生成字体步骤\n",
"\n",
"- 获取字符列表\n",
"- 从字体中提取子集,合并成一个完整的字体文件\n",
"- 生成不同 size 与 bpp 的点阵字体"
]
},
{
"cell_type": "markdown",
"id": "93205a30",
"metadata": {},
"source": [
"## 获取字符列表\n",
"\n",
"这里使用 DeepSeek 的词表来提取常用字符"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "45820776",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Requirement already satisfied: modelscope in /home/terrence/miniconda3/envs/dev/lib/python3.12/site-packages (1.28.1)\n",
"Requirement already satisfied: transformers in /home/terrence/miniconda3/envs/dev/lib/python3.12/site-packages (4.54.1)\n",
"Requirement already satisfied: requests>=2.25 in /home/terrence/miniconda3/envs/dev/lib/python3.12/site-packages (from modelscope) (2.32.3)\n",
"Requirement already satisfied: setuptools in /home/terrence/miniconda3/envs/dev/lib/python3.12/site-packages (from modelscope) (75.8.0)\n",
"Requirement already satisfied: tqdm>=4.64.0 in /home/terrence/miniconda3/envs/dev/lib/python3.12/site-packages (from modelscope) (4.67.1)\n",
"Requirement already satisfied: urllib3>=1.26 in /home/terrence/miniconda3/envs/dev/lib/python3.12/site-packages (from modelscope) (2.4.0)\n",
"Requirement already satisfied: filelock in /home/terrence/miniconda3/envs/dev/lib/python3.12/site-packages (from transformers) (3.18.0)\n",
"Requirement already satisfied: huggingface-hub<1.0,>=0.34.0 in /home/terrence/miniconda3/envs/dev/lib/python3.12/site-packages (from transformers) (0.34.3)\n",
"Requirement already satisfied: numpy>=1.17 in /home/terrence/miniconda3/envs/dev/lib/python3.12/site-packages (from transformers) (2.2.6)\n",
"Requirement already satisfied: packaging>=20.0 in /home/terrence/miniconda3/envs/dev/lib/python3.12/site-packages (from transformers) (25.0)\n",
"Requirement already satisfied: pyyaml>=5.1 in /home/terrence/miniconda3/envs/dev/lib/python3.12/site-packages (from transformers) (6.0.2)\n",
"Requirement already satisfied: regex!=2019.12.17 in /home/terrence/miniconda3/envs/dev/lib/python3.12/site-packages (from transformers) (2025.7.34)\n",
"Requirement already satisfied: tokenizers<0.22,>=0.21 in /home/terrence/miniconda3/envs/dev/lib/python3.12/site-packages (from transformers) (0.21.4)\n",
"Requirement already satisfied: safetensors>=0.4.3 in /home/terrence/miniconda3/envs/dev/lib/python3.12/site-packages (from transformers) (0.5.3)\n",
"Requirement already satisfied: fsspec>=2023.5.0 in /home/terrence/miniconda3/envs/dev/lib/python3.12/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (2025.7.0)\n",
"Requirement already satisfied: typing-extensions>=3.7.4.3 in /home/terrence/miniconda3/envs/dev/lib/python3.12/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (4.14.1)\n",
"Requirement already satisfied: hf-xet<2.0.0,>=1.1.3 in /home/terrence/miniconda3/envs/dev/lib/python3.12/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (1.1.5)\n",
"Requirement already satisfied: charset-normalizer<4,>=2 in /home/terrence/miniconda3/envs/dev/lib/python3.12/site-packages (from requests>=2.25->modelscope) (3.4.2)\n",
"Requirement already satisfied: idna<4,>=2.5 in /home/terrence/miniconda3/envs/dev/lib/python3.12/site-packages (from requests>=2.25->modelscope) (3.10)\n",
"Requirement already satisfied: certifi>=2017.4.17 in /home/terrence/miniconda3/envs/dev/lib/python3.12/site-packages (from requests>=2.25->modelscope) (2025.8.3)\n"
]
}
],
"source": [
"# 安装 modelscope\n",
"!pip install modelscope transformers"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "c3acd714",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/home/terrence/miniconda3/envs/dev/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
" from .autonotebook import tqdm as notebook_tqdm\n",
"None of PyTorch, TensorFlow >= 2.0, or Flax have been found. Models won't be available and only tokenizers, configuration and file/data utilities can be used.\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Downloading Model from https://www.modelscope.cn to directory: /home/terrence/.cache/modelscope/hub/models/deepseek-ai/DeepSeek-R1-0528\n"
]
}
],
"source": [
"from tqdm import tqdm\n",
"from modelscope import AutoTokenizer\n",
"\n",
"model_name = \"deepseek-ai/DeepSeek-R1-0528\"\n",
"\n",
"# load the tokenizer and the model\n",
"tokenizer = AutoTokenizer.from_pretrained(model_name)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "e4ab8eb6",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
" 0%| | 0/128815 [00:00<?, ?it/s]"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"100%|██████████| 128815/128815 [00:00<00:00, 360022.20it/s]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Total unique chars: 7404\n",
"Wrote 7404 unique characters to unique_chars.txt (sorted by token ID)\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"\n"
]
}
],
"source": [
"import os\n",
"\n",
"# 创建临时目录\n",
"if not os.path.exists(\"build\"):\n",
" os.makedirs(\"build\")\n",
"\n",
"unique_chars = {} # 存储字符和对应的token ID\n",
"\n",
"for id in tqdm(range(len(tokenizer.get_vocab()))):\n",
" token = tokenizer.decode(id)\n",
" if token.startswith(\"<\"):\n",
" continue\n",
" # 对于每个字符记录最小的token ID\n",
" for char in token:\n",
" if char not in unique_chars:\n",
" unique_chars[char] = id\n",
"\n",
"# count total unique chars\n",
"print(f\"Total unique chars: {len(unique_chars)}\")\n",
"\n",
"sorted_chars = sorted(unique_chars.items(), key=lambda x: x[1])\n",
"# write to file - 按照token ID排序\n",
"with open(\"./build/chars.txt\", \"w\") as f:\n",
" # 按照token ID排序输出\n",
" for char, token_id in sorted_chars:\n",
" f.write(char + \"\\n\")\n",
"\n",
"print(f\"Wrote {len(unique_chars)} unique characters to unique_chars.txt (sorted by token ID)\")"
]
},
{
"cell_type": "markdown",
"id": "82281aaa",
"metadata": {},
"source": [
"## 从字体中提取子集\n",
"\n",
"字体文件可以从 https://www.alibabafonts.com/ 或 https://fonts.google.com/noto 下载到 fonts 目录下"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "031a7a93",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Requirement already satisfied: fonttools in /home/terrence/miniconda3/envs/dev/lib/python3.12/site-packages (4.59.0)\n"
]
}
],
"source": [
"!pip install fonttools"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "ac8117b0",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Building subsetter\n",
"basic_unicodes: Added 810 chars from assets\n",
"common_unicodes: Added chars: 7422\n",
"Subsetting 7 fonts\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"100%|██████████| 7/7 [00:01<00:00, 4.38it/s]\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Merging 7 fonts\n",
"Merged font saved to build/puhui-basic.ttf\n",
"Subsetting 7 fonts\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"100%|██████████| 7/7 [00:06<00:00, 1.03it/s]\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Merging 7 fonts\n",
"Merged font saved to build/puhui-common.ttf\n"
]
}
],
"source": [
"import os\n",
"import shutil\n",
"import json\n",
"from fontTools.subset import Subsetter, Options, load_font, save_font\n",
"from fontTools.merge import Merger\n",
"from fontTools.ttLib.scaleUpem import scale_upem\n",
"\n",
"print(f\"Building subsetter\")\n",
"basic_unicodes = set(range(0x20, 0x7f)) | set(range(0xA1, 0x100))\n",
"for parent, dirs, files in os.walk(\"../../main/assets/locales\"):\n",
" for filename in files:\n",
" if filename == 'language.json':\n",
" with open(os.path.join(parent, filename), 'r') as f:\n",
" strings = json.load(f)[\"strings\"]\n",
" # Add all values to unicodes\n",
" for value in strings.values():\n",
" for char in value:\n",
" basic_unicodes.add(ord(char))\n",
"print(f\"basic_unicodes: Added {len(basic_unicodes)} chars from assets\")\n",
"\n",
"common_unicodes = basic_unicodes.copy()\n",
"for char, token_id in sorted_chars:\n",
" common_unicodes.add(ord(char))\n",
"print(f\"common_unicodes: Added chars: {len(common_unicodes)}\")\n",
"\n",
"font_list = []\n",
"\n",
"def build_ttf(font_type, font_style, font_name, unicodes):\n",
" global font_list\n",
" # font_list = [\n",
" # f\"fonts/Noto_Sans/static/NotoSans-{font_style}.ttf\",\n",
" # f\"fonts/Noto_Emoji/static/NotoEmoji-{font_style}.ttf\",\n",
" # f\"fonts/Noto_Sans_JP/static/NotoSansJP-{font_style}.ttf\",\n",
" # f\"fonts/Noto_Sans_KR/static/NotoSansKR-{font_style}.ttf\",\n",
" # f\"fonts/Noto_Sans_SC/static/NotoSansSC-{font_style}.ttf\",\n",
" # f\"fonts/Noto_Sans_TC/static/NotoSansTC-{font_style}.ttf\",\n",
" # f\"fonts/Noto_Sans_Thai/static/NotoSansThai-{font_style}.ttf\"\n",
" # ]\n",
" font_list = [\n",
" f\"fonts/AlibabaSans/AlibabaSans-{font_style}/AlibabaSans-{font_style}.ttf\",\n",
" f\"fonts/AlibabaPuHuiTi-3/AlibabaPuHuiTi-3-55-{font_style}/AlibabaPuHuiTi-3-55-{font_style}.ttf\",\n",
" f\"fonts/AlibabaSansJP/AlibabaSansJP-{font_style}/AlibabaSansJP-{font_style}.ttf\",\n",
" f\"fonts/AlibabaSansKR/AlibabaSansKR-{font_style}/AlibabaSansKR-{font_style}.ttf\",\n",
" f\"fonts/AlibabaSansTC/AlibabaSansTC-55/AlibabaSansTC-55.ttf\",\n",
" f\"fonts/AlibabaSansThai/AlibabaSansThai-Rg/AlibabaSansThai-Rg.ttf\",\n",
" f\"fonts/AlibabaSansViet/AlibabaSansViet-Rg/AlibabaSansViet-Rg.ttf\",\n",
" ]\n",
"\n",
" if not os.path.exists(f\"build/subsets\"):\n",
" os.makedirs(f\"build/subsets\")\n",
"\n",
" print(f\"Subsetting {len(font_list)} fonts\")\n",
" subset_fonts = []\n",
" subsetter = Subsetter(Options())\n",
" subsetter.populate(unicodes=list(unicodes))\n",
" # 遍历 font_list提取每个字体中的字符\n",
" for font_path in tqdm(font_list):\n",
" if not os.path.exists(font_path):\n",
" print(f\"Font {font_path} not found\")\n",
" continue\n",
" font = load_font(font_path, Options())\n",
" font_file = font_path.split('/')[-1].split('.')[0]\n",
" subsetter.subset(font)\n",
" scale_upem(font, 1000)\n",
" save_path = f\"build/subsets/{font_file}.ttf\"\n",
" font.save(save_path)\n",
" subset_fonts.append(save_path)\n",
"\n",
" # 合并所有字体,保存到 build/puhui-{len(unicodes)}-{style}.ttf\n",
" print(f\"Merging {len(subset_fonts)} fonts\")\n",
" output_path = f\"build/{font_type}-{font_name}.ttf\"\n",
" cmd = f\"fonttools merge {\" \".join(subset_fonts)} --output-file={output_path} --drop-tables=vhea,vmtx\"\n",
" os.system(cmd)\n",
" print(f\"Merged font saved to {output_path}\")\n",
"\n",
"\n",
"build_ttf(\"puhui\", \"Regular\", \"basic\", basic_unicodes)\n",
"build_ttf(\"puhui\", \"Regular\", \"common\", common_unicodes)"
]
},
{
"cell_type": "markdown",
"id": "5f1cb872",
"metadata": {},
"source": [
"## 生成不同 size 与 bpp 的点阵字体"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "49c783db",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"字体: build/puhui-common.ttf, 大小: 20px, BPP: 1\n"
]
},
{
"data": {
"image/png": "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",
"text/plain": [
"<PIL.Image.Image image mode=RGBA size=695x30>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"字体: build/puhui-common.ttf, 大小: 20px, BPP: 2\n"
]
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAuYAAAAeCAYAAABgx9sOAAAUXUlEQVR4Ae2dW5ZcN3JFSS3NxnPwb3sATQ3G/rD8YQ+G7AG4f+0x2NORcxe5S6eCAVzcm1mskgysdQkgHidOHCCzsh6t/vjbbXzYYyuwFdgKbAW2AluBrcBWYCuwFXhTBX5+0+q7+FZgK7AV2ApsBbYCW4GtwFbgkQr8+vdjtF//chzzyIgVTrd6Pz2y5sbaCmwFtgJbga3AVmArsBXYCmwFrimwP5hf021nbQW2AluBrcBWYCuwFdgKbAUeqsD+YP5QOTfYVmArsBXYCmwFtgJbga3AVuCaAvuD+TXddtZWYCuwFdgKbAW2AluBrcBW4KEK7A/mD5Vzg20FtgJbga3AVmArsBXYCry1Ar98+fcPH//tnz4wv6cBJx+5ffmf/3qyMe8P5u/ptF6Jy8ePH18g//LLLx+q7UXAwebLly935R/Av4n7Xk060o/U6ZFYHddt++MowF34ow3eb3iNPWI88rX6KC3BSaxcP6LnjbEV2AqcU4APuF/+978//Pav//k0n8t+vWg+kDM+//Wfnx7W2ODq2P+5RJX4k87dF0O+aHz69OnhHXe1apHPnz9X03R/7xe41T5fQxNro8vZvqei/IGcfCBDh67/me+9tcgZ0odnusLPu3smZwVXLp2mo3y4kHf0f1vBmYC7whk8e7QueWd4kQcGz9k8a45m+c16PqOlvXbagMOw1gqufYtLPtg+7Pf48ynAeXM/fJ25v/LaeUt1zr5/E8/wNfKW3PnQzvj04Qf/5xK/Nc2H8k//8I9fOXyb/cCO8fmDuZfjW97Tm0P3RumbCLNrL5i5XDp92maX7my8mM6Z3x06XIjpRhefcZnb9eBly5y67mokZ+OrjtqZa3zHJeNdw59YR55LrvU7Z44251nezDfDFLvOo3OrcaN91b7jl7ZcjzBrH0ccwRzFgFXxOg7anCu3iqHfe1N1OHpTNW92J63xnmZ4pxa5licadnb9dVaLMzlgUIenal/xr+yvcLlSZ5Tj+x485GK/+M70jL6zYa1ZzI/w2d+Z3jpe4GTPVT/8jNlrr2JYByzy9ni/CviaqXfgLc/N97iq2uwO1tjZ3l7vfe3MalQfH3w/3X4K7QdeZ+P4cPxW45e//ceHDzyD8fTBPA8l3yS6N1gFHuA9m8XB4Btah2dCxpszizdPbPfd7BtdrdHFaqtvfOR2+Z1NDGZrp80vNGLaA9p2L4SMF4ecI32snRy1WVO8OncvIHPEyLsA79Ebi/xH/lo79x0P/fCAwyzGWOfkrM3Z/tyP5qy3mqNmFRN7xZtxHPkSo9b4/7RfPQ9eE6v30bPL19FVTcHKM0xM68zO0hjqu875TF+1B3Gqve6Nq7XY46M/5uytYpzZVxzrVwzfZ47slXeNf829+lBjdM7GoOMsBozUhjyeo68L5O3xtgqsvvf8CJb5uqn3yfeq0T08w+8qBneakdxW637+9C/PofyU3J9SPxsXF/dwqCX405rDcRPrt1vQb7emWb4YI7tB5BBzu2SahjMxR3iZbPwRtphySQzXM58xdQaX557RYcz6msWPzmfGseub+JmmXY4ayK+bR5jGivHIWS3vxTzS5Ar+1b5HPY3OZWSX88gPP3yjYd7oXEd51T6rM/NVnLN7eM+eVTx1WI3PuNEdEFN/nRODdfWP9uCOzsucxDZWneTF7ENeHcZVu3tyiMnR2fR33PR186i+nI9m+wZ7xqvWHtUVByzHCHeGYS4zHMFIrukfrcU/mzfC2/Y/twJH90U/cx2jO17jVvbedzB9qGn9ep+7+Bpz+wD82+ET5DrMGQdTyZNncv+ODwkrnG4xfBcyfAOwiATqPMutsezBI2d1EN829w3A+mxzXfGv1CXn3tHxn/HsfJ1NXjMfMdTPPrr4qi/xxM1G7avuzcXO81oD7vfijzhiP9Jh1NcMs+pdMUY9nbWL2505vqP+zOv4ysU+mbu4ozpHHLo6V89EPc7M1r9a86i/VS7w8FFz9mdG5eL5ijeau97NHdUXK/3a7CP568v4XFMvn6N4c7ta+pzB6nrUn3OtO+M0wq0YiV/XI4wal3t6Ji/1TT+c5VDnjHMtB3EzZ1SD3C5+pHNi5noUv4o/69W+7JM5a+e64yF25ud65O/q1todTvLJdcet0350VmJlzboexWDv6pvf8RjFqxezj3W7HGOcjX3R58qHYMneZrDsqeKy70b2aI44YL3gA8AKp1vMz7Mf0d8KPP1qjBjWP3LIa1QTP8+t8VHIXfZ7cf0VUNVNTTtyXc+drcutNutrH+lFHL3Cy5wrvdc+5X27ilK4a+bXbdS4wm1UWI6PxFTDrm81Vu8Rr/ds99ee9OKZoyN9P/J8wKvnw57nR/2q3rO8536o0T1nWjHYV9sKfuZ0PXmOq/eT+MRMDiO7muIfxSQOa+qcGXl3Mm/Ul30Tu8LLvs/yEn8lbyUme1tdi5vn75mMMMjhyRz25KFX2sVQR89YjNlrN3GMp0bawc/zFR87OTnkoA9/2jI211lvxIMYeun4WS8xZ+vKO2OTr1zSlrGsz7w/Wxe82cBv7Cyu+uQpvvy7O5CaJw6xXe0u3vPovvYm5mjdYRI74oCP82d0Ne/l8/Rdwg34u3Ej+uRj7sZN8Kk/c46wMlZc5tFAi/Sb08UTWx/iu77EkW/mdfFdPWzkjeLFTH/WTUxj0+ZajomDT3vmgs9Tx4xnjWUvtr66P7LrPzvDs/K3tr3V+ahGh2nOzGdMnal/pCf+WYw9Gbc6Vy7u5eTeGVx8o2EefBxy6/KMN9Z5Vmfkm9WZ+az5iNk6zFfGvfmjmmh2ltMZLqMzqXyI46nDe1A5znBHWBXbvTXc56yP2WH/1Ol4Wd/ZvJwTI7GNqbnsuzhxOp9YzBUvfaO12OSORofb2cy/6jM/Z3oGrzuDTo+utj128Vmrrs2rtTOOeh1ux4O8kX1Wq6uhTX2SU65nuMTp73rosI1nng1za5y8Z7nVN8Kqce5HGuvP+bvYlZ9OJ8Bg/R1uxM00sNcIX/6J+Q/775j7ncWN7K2X7wffXfjwHRJxt4vwfeDNQhxj5H9yxj/E5QM2NfI77wh/WuKTg3Vm8Zl/1OvtoJ5rZM/UqfrU/Uqdrr79Z/6Vdf3u1T017YVZDmlL+5Xaoxw4dM8oHjtcGJ5tzcdXbexHAzz9VYvUwHz1cV9nz8vZe+DeWXvNX9nDN7nl2l4SRxu165CHMdV/Zi/Ga9eZcfJ86As+7mc5nU9dOt9ZmxwSU2741G2Em3mjmFW7WNwZavOwhgM+/at4xImR95D1maEGeXfg4nuu/sTUT4xx6WdtHrGua8zKnnwx7FP9sn+wsoeKDQfzncm3lxqfe2LOjFG89lU9jF+t3cVba6bNKv5qXMeDXDnISTz3ozzjmI0VK31n1zMsuRiTtY/qmHsUt+J/JNZKvR8RY0++fpnvHc//ucR7gWb5Ep1dvvRxeXh4w6lvlCtYlYvCaXcPPnhZw4sLH+PII6aLFzNnMDI3fazBYdSe4UJe2lkTz5Oc1OEJqPwjRheDDX7Zs+m1hvaca2/uu371ZX4Xl/4r69RlJV/9jYVnpxX+ap/Vyt7sHRvr3IsLj+4c8CcWe4YYX3e//4v96ujqgNVhVlvuc32Vy2qemtZ4OPBwRvcMe6FO3pXZedV6YlT7PXsw7b3eS3CtObuj99TPXGpQzwcf3Hwy9mhtT/Jnz3A+yq/+WZ41as7Rnjxw6Zt7gP5X75l5YKR+cKCGz4xTFyPWPdxmNasPDlf1rFh1by/UeMsx4yE3YlzDte5n/DmrzJ3FHvmomyP3uc6Y97L2tZB8fJ2k7TXXZzmkpqx5/JyYvqucn34lcwP8btyEaX/1ZODtQk39xBnDfGZYu+bdmmx/3WSdMzW6HPB5uiGnzqetw9THPPOPfNaVmxq4T/xcd3jkgOfIfRdvnHOtmfnGMMs5bfesqQO/HFdqyP8ot6uXtWdrsZkdVaf0GcNsbtqu2M0fnelRf+YlT7WbzRkPh1mdkU98e6iz3Kr9KK/Gj/biMNuP5+K+5lb7jCO+KyN5gVFrgmlM4o+4ZIzrLl/fPTO4V/uudel7hDWr0/U2i7duzVPP5FBjjnDFsIYzvZHbna0xs1nc5Gb8CLtyN555xmWEp13cOtfeqj/3yUU++M8OOdXaiZN16zrjcq3e2o7qgOvZ1Ny6F9N5Bbvyrvvs/wjPuvLKXHzZi7HOYtf67hPLWOqw9jGWeWUY/xx74k9ZrnAwJ/lSGx7Y1O2ZD4sVTvyPP28gw3Hvp36/A7kRPP3TBXIYycE1c/4kKxvAfqVeYtyzhpvcOxx76Hzk4a8Y2L+e98sse31pHe+sPeJ3u0xPutb6iUgM55q1R3iZ9x7W9k8PP4LzrMbMh1ZyVTf3ztXuvs7UqTk1ZnUvVncXVzFW4qyzEltjjnSt8XWfWmWfR7i8Jo7uldhHWJUTe/Adszpw5rUpH3Nms7xmMW/ho4fKDe1m/cOz5mBTv7PaoyWDmg7WcqPWWUxxXmOGG5w7DTrbCoczPaoLmlRdwBlx6OLBopf6OhxhrPRyFLPKQ5zUm1y51d6Nz5nYvFfpu7K2fuq1ggOPFb4rMdQ7ewfUrGrhe9lKD/fG3MtBbXy/8Cyu8vpZgO5wJGvRM0UkiNhX8rta4IywkusopmKak3ZyOzsxI7v5XEhGvWD6HznL5Uwt+B1pg5+40Ysbvy8YOBzhPbLne7Hkfi/OUb5ncxQ383uXaszIXuPc0/OZO2Lee54foe+oP/UdadbdeXNe87Vgz6PXZe3HeOzwyr2xvke7z5meRhrUOLBXeWXuaA3eSFN88IZbpzd2cu2t9r7SU+XV1bJOx6Hmv5e9d+AsZ/NqH51d20hn/RWLfeWlxuRUX5f/KFuttcKDO8drYJXr6H4/qocVHPucnQk4R/5ay/jVO2C8r9mz7yXkq2flsrq/lwN1vCdn+Xccf1I8idUgD6/aZ3sFBvtKPtjyqflgdo9xqzVH+OJ0B23OqHf85o9iZvYjfHOJg9+ZWmKjz2ysYhoHrtgz3CNfp/dRznv1o4f6HHGs2pHHC7s+4lW7e+rwuqt4dX/EZ+aXw2uf1ayO/RhT+eI3pvqO9uaBPcKvGNZbjSffOhVrts9znsXpO+LjGYrrbD4cu/uk/+zse/ZKXnIzzxmeDGMqHn0TY//0wZp8c2vObJ9YNQ7M1QEPOPt4B9w7ax/FH9Uz3/5r/Mhe4+pe3LRrW8WkxzOjw7fWWawzdWtsxyNjvAfGuc+YXBPHcxSXOSvrK9qYI/daR52Nq/6ze/G6PF+fs5guj3i0fATHqxzQTx4dx9O2G5Hnv4W5JT+tbw0+/Z0M+9kw7ibKcxg5iUNMfZ6Dbwtyq1+Mo/qJI5e0uT6Ln/XN1Za9is88q59x5IsltrnYWXfD2Mzt4tImLrYRNvba0yhWbHGdOwz7NGc2G1t5ZE7HybyMO7MGczS6eqPYtKtJ2lh3Gs3sNX+l144ztq52F5s17aOeiXbz2actMeyvq3/kS0zW8EhbrcNejaiX/LCbnxjYcpiXtlxXf2JlHGu5jGrIDz8PWPlUvNV9V7ezyb3iygu7McyjYczIf8WeHLr8R9Y8qtXV72zg8Dgqrv6rcz0D8bH7JLY8mD3/Lkct9RGbQ7sz/syp8fqY8fmQ71Nz0m68OPjqSB9rny7WXHDx19r6mc/y6HJnHLJGF2dfiZvrlR7EAF9d0pZ4ru17lNNxJde8qqn1mPH5GM+cOcaDaQ5rhjlfd/2/4iXOU+TK33N/g8zcVQ70QG1zsydgtX8r8XVa4XSLeb71FlEIQI+GhZOQ+bM542td81bqJz+5pM21mDknB+NyFs+cIz7GJcZsXfHJn3ES/4hH1rSGGqeP9chuXo3PnOQht4yfYWQc61Es/Hys4Z7ZPOa013Wt517NxV6dza9z8qk+a63aaxw9gTEbXQ17Ij8HdviOhr3UPOLlIrbzUWz1J071jerMONuL3OU1mo13Jm6G3+HO4kd4HU5ynGHKtZsTt/orl9SevMzNs9BX8dyb5/4Rc+VaMR9Z86hWrT3ae376H4UrXp3VwLrO2OsYxZqTc83Hhy3vi/F5T7LmLJbcmidenSuXoxor8bV2Ytb67me45hND/AyfWDG7ODHErDM5KzWMs5ZzV9Ma1jbWeZaTdWpc+ioW+xpvDHMO7Wmr6+T+AnflQ3CAWWuVQ/b4ou43THlFieX/8edHkm5E9vgTKsCvVvgVSz1ibD60Xf0jKcS7Xbjvfg0HHnZxmVex+ZV5h+mfRI34rNg7XPPAv72g3C7PYOagV7RhjOrZS9YzZ0X/rJEY1FRz1itYxO3xvQLoWM+2RqXWxM7iR/e6Yp7de2/Mg1OO7g50XMirWNytWU9ZhzX5K7oRe6QXMYx8rVQuybnr8yvC+r+dLuvZv0dWnLr/PfLHrzyjI706zp3tx3fw/iuuavz+O/kTMfz178fN/PqX45hHRqxwutWb/ldZHslnY70vBfxiXj/kzVj6BbjL8QtofqHv4ir+jMfRF5KKdWUv7yu55PiGzHr2oYY6+aGCeMaKRsSRP8LAz1jF+hq9/60KrNwFz6HmdvvXur++ZrKmvEY9dFyI7eyJu7ruONVcYlbqEcMHwnwvqVgrODXnNffvjc9r9rqxXyrAveYZvfZeRu/dVuBYgf3B/FijP2zE6IPa1S/IK198VmJSUN7QRjwz7jXWZ7l2HODOB4ijHo78HXa1PQKjYu79H0+BR9zbR3X9WneSHv3Ak1x573rkB6DX0vK1cFOLvX5bBbyfzIzXei28bZe7+lsosP+U5S1U3zW3AluBrcBWYCvwzhXYf8oyPiB/W8o3ivtD+VinN/Os/NnI/lOWNzueXXgrsBXYCmwFtgJbgZMK7J/8jwXbH8bH2mzPfQrsn5jfp9/O3gpsBbYCW4GtwFZgK7AV2Ao8RIGfHoKyQbYCW4GtwFZgK7AV2ApsBbYCW4G7FPg/ivwLBT6LEE8AAAAASUVORK5CYII=",
"text/plain": [
"<PIL.Image.Image image mode=RGBA size=742x30>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"字体: build/puhui-common.ttf, 大小: 20px, BPP: 3\n"
]
},
{
"data": {
"image/png": "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",
"text/plain": [
"<PIL.Image.Image image mode=RGBA size=742x30>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"字体: build/puhui-common.ttf, 大小: 20px, BPP: 4\n"
]
},
{
"data": {
"image/png": "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",
"text/plain": [
"<PIL.Image.Image image mode=RGBA size=742x30>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"import os\n",
"from PIL import Image\n",
"import json\n",
"from IPython.display import display\n",
"\n",
"# https://github.com/78/lv_font_conv (Support cbin format)\n",
"lv_font_conv = \"node ./tmp/lv_font_conv/lv_font_conv.js\"\n",
"flags = \"--force-fast-kern-format --no-compress --no-prefilter \"\n",
"\n",
"def preview_font(font_path, size, bpp):\n",
" symbols_str = \"0123456789你好正在充电Helloこんにちは안녕하세요ЗдравствуйтеOláสวัสดี\"\n",
" output = \"./build/preview\"\n",
" if os.path.exists(output):\n",
" shutil.rmtree(output)\n",
" os.makedirs(output)\n",
" cmd = f\"{lv_font_conv} {flags} --font {font_path} --format dump --bpp {bpp} -o {output} --size {size} --symbols {symbols_str} \"\n",
" os.system(cmd)\n",
" \n",
" # 读取字体信息\n",
" font_info_path = os.path.join(output, \"font_info.json\")\n",
" if not os.path.exists(font_info_path):\n",
" print(\"未找到font_info.json文件\")\n",
" return\n",
" \n",
" with open(font_info_path, 'r', encoding='utf-8') as f:\n",
" font_info = json.load(f)\n",
" \n",
" # 获取字体高度信息\n",
" font_height = font_info.get('size', size)\n",
" ascent = font_info.get('ascent', font_height)\n",
" descent = font_info.get('descent', 0)\n",
" \n",
" # 收集所有PNG文件并按照字符顺序排序\n",
" png_files = []\n",
" char_positions = {} # 存储每个字符在原始字符串中的位置\n",
" \n",
" for i, char in enumerate(symbols_str):\n",
" unicode_hex = f\"{ord(char):x}.png\"\n",
" png_path = os.path.join(output, unicode_hex)\n",
" if os.path.exists(png_path):\n",
" png_files.append((char, png_path, i))\n",
" char_positions[char] = i\n",
" \n",
" # 按照原始字符串顺序排序\n",
" png_files.sort(key=lambda x: x[2])\n",
" \n",
" if not png_files:\n",
" print(\"未找到任何PNG文件\")\n",
" return\n",
" \n",
" # 加载所有图片并计算总尺寸\n",
" images = []\n",
" total_width = 0\n",
" max_height = 0\n",
" \n",
" for char, png_path, _ in png_files:\n",
" try:\n",
" img = Image.open(png_path).convert('RGBA')\n",
" images.append((char, img))\n",
" total_width += img.width + 2 # 添加2像素间距\n",
" max_height = max(max_height, img.height)\n",
" except Exception as e:\n",
" print(f\"无法加载图片 {png_path}: {e}\")\n",
" continue\n",
" \n",
" if not images:\n",
" print(\"没有成功加载任何图片\")\n",
" return\n",
" \n",
" # 移除最后一个字符的间距\n",
" total_width -= 2\n",
" \n",
" # 创建预览图画布,使用字体高度作为画布高度\n",
" canvas_height = max(max_height, font_height + 10) # 添加一些padding\n",
" canvas = Image.new('RGBA', (total_width, canvas_height), (255, 255, 255, 255))\n",
" \n",
" # 拼接图片\n",
" x_offset = 0\n",
" for char, img in images:\n",
" # 计算垂直居中位置\n",
" y_offset = (canvas_height - img.height) // 2\n",
" canvas.paste(img, (x_offset, y_offset), img)\n",
" x_offset += img.width + 2\n",
" \n",
" # 打印字符信息\n",
" print(f\"字体: {font_path}, 大小: {size}px, BPP: {bpp}\")\n",
" \n",
" # 在 notebook 中直接显示图片\n",
" display(canvas)\n",
" \n",
" return canvas\n",
"\n",
"# 调用函数并显示图片\n",
"font_size = 20\n",
"for bpp in [1, 2, 3, 4]:\n",
" preview_image = preview_font(\"build/puhui-common.ttf\", font_size, bpp)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "44c7121f",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Generating src/font_puhui_basic_14_1.c...\n",
"Generating src/font_puhui_basic_16_4.c...\n",
"Generating src/font_puhui_basic_20_4.c...\n",
"Generating src/font_puhui_basic_30_4.c...\n"
]
}
],
"source": [
"\n",
"flags = \"--force-fast-kern-format --no-compress --no-prefilter \"\n",
"\n",
"def generate_font_source(font_path, name, size, bpp):\n",
" output = f\"src/font_{name}_{size}_{bpp}.c\"\n",
" print(f\"Generating {output}...\")\n",
" cmd = f\"{lv_font_conv} {flags} --font {font_path} --format lvgl --lv-include lvgl.h --bpp {bpp} -o {output} --size {size} -r 0x0-0xfffff\"\n",
" os.system(cmd)\n",
"\n",
"generate_font_source(\"build/puhui-basic.ttf\", \"puhui_basic\", 14, 1)\n",
"generate_font_source(\"build/puhui-basic.ttf\", \"puhui_basic\", 16, 4)\n",
"generate_font_source(\"build/puhui-basic.ttf\", \"puhui_basic\", 20, 4)\n",
"generate_font_source(\"build/puhui-basic.ttf\", \"puhui_basic\", 30, 4)"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "0aa3e502",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Generating build/font_puhui_common_14_1.bin...\n",
"Forced faster kerning format (via classes). Size increase is 708 bytes.\n",
"Generating build/font_puhui_common_16_4.bin...\n",
"Forced faster kerning format (via classes). Size increase is 708 bytes.\n",
"Generating build/font_puhui_common_20_4.bin...\n",
"Forced faster kerning format (via classes). Size increase is 708 bytes.\n",
"Generating build/font_puhui_common_30_4.bin...\n",
"Forced faster kerning format (via classes). Size increase is 708 bytes.\n"
]
}
],
"source": [
"def generate_font_bin(font_path, name, size, bpp):\n",
" output = f\"build/font_{name}_{size}_{bpp}.bin\"\n",
" print(f\"Generating {output}...\")\n",
" cmd = f\"{lv_font_conv} {flags} --font {font_path} --format cbin --bpp {bpp} -o {output} --size {size} -r 0x0-0xfffff\"\n",
" os.system(cmd)\n",
"\n",
"# Generate cbin font\n",
"generate_font_bin(\"build/puhui-common.ttf\", \"puhui_common\", 14, 1)\n",
"generate_font_bin(\"build/puhui-common.ttf\", \"puhui_common\", 16, 4)\n",
"generate_font_bin(\"build/puhui-common.ttf\", \"puhui_common\", 20, 4)\n",
"generate_font_bin(\"build/puhui-common.ttf\", \"puhui_common\", 30, 4)"
]
},
{
"cell_type": "code",
"execution_count": 36,
"id": "fb65161e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"=== 开始并行生成字体bin文件 ===\n",
"开始并行生成 28 个字体文件,使用 8 个进程...\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"生成字体文件: 0%| | 0/28 [00:00<?, ?it/s]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Generating build/full/AlibabaSans-Regular_30_4.bin...Generating build/full/AlibabaPuHuiTi-3-55-Regular_30_4.bin...Generating build/full/AlibabaPuHuiTi-3-55-Regular_14_1.bin...Generating build/full/AlibabaSans-Regular_16_4.bin...Generating build/full/AlibabaPuHuiTi-3-55-Regular_16_4.bin...Generating build/full/AlibabaPuHuiTi-3-55-Regular_20_4.bin...Generating build/full/AlibabaSans-Regular_14_1.bin...Generating build/full/AlibabaSans-Regular_20_4.bin...\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"生成字体文件: 4%|▎ | 1/28 [00:01<00:29, 1.09s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Generating build/full/AlibabaSansJP-Regular_14_1.bin...\n",
"Generating build/full/AlibabaSansJP-Regular_16_4.bin...\n",
"Generating build/full/AlibabaSansJP-Regular_20_4.bin...\n",
"Generating build/full/AlibabaSansJP-Regular_30_4.bin...\n",
"Generating build/full/AlibabaSansKR-Regular_14_1.bin...\n",
"Generating build/full/AlibabaSansKR-Regular_16_4.bin...\n",
"Generating build/full/AlibabaSansKR-Regular_20_4.bin...\n",
"Generating build/full/AlibabaSansKR-Regular_30_4.bin...\n",
"Generating build/full/AlibabaSansTC-55_14_1.bin...\n",
"Generating build/full/AlibabaSansTC-55_16_4.bin...\n",
"Generating build/full/AlibabaSansTC-55_20_4.bin...\n",
"Generating build/full/AlibabaSansTC-55_30_4.bin...\n",
"Generating build/full/AlibabaSansThai-Rg_14_1.bin...\n",
"Forced faster kerning format (via classes). Size increase is 9024 bytes.\n",
"Generating build/full/AlibabaSansThai-Rg_16_4.bin...\n",
"Generating build/full/AlibabaSansThai-Rg_20_4.bin...\n",
"Generating build/full/AlibabaSansThai-Rg_30_4.bin...\n",
"Generating build/full/AlibabaSansViet-Rg_14_1.bin...\n",
"Generating build/full/AlibabaSansViet-Rg_16_4.bin...\n",
"Generating build/full/AlibabaSansViet-Rg_20_4.bin...\n",
"Generating build/full/AlibabaSansViet-Rg_30_4.bin...\n",
"Forced faster kerning format (via classes). Size increase is 9024 bytes.\n",
"Forced faster kerning format (via classes). Size increase is 9024 bytes.\n",
"Forced faster kerning format (via classes). Size increase is 9024 bytes.\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"生成字体文件: 18%|█▊ | 5/28 [03:36<17:36, 45.94s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Forced faster kerning format (via classes). Size increase is 12716 bytes.\n",
"Forced faster kerning format (via classes). Size increase is 12716 bytes.\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"生成字体文件: 21%|██▏ | 6/28 [03:37<13:01, 35.52s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Forced faster kerning format (via classes). Size increase is 12716 bytes.\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"生成字体文件: 100%|██████████| 28/28 [03:53<00:00, 8.34s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Forced faster kerning format (via classes). Size increase is 12716 bytes.\n",
"完成生成 28 个字体文件\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"\n"
]
},
{
"data": {
"text/plain": [
"['build/full/AlibabaSans-Regular_14_1.bin',\n",
" 'build/full/AlibabaSans-Regular_16_4.bin',\n",
" 'build/full/AlibabaSans-Regular_20_4.bin',\n",
" 'build/full/AlibabaSans-Regular_30_4.bin',\n",
" 'build/full/AlibabaPuHuiTi-3-55-Regular_14_1.bin',\n",
" 'build/full/AlibabaPuHuiTi-3-55-Regular_16_4.bin',\n",
" 'build/full/AlibabaPuHuiTi-3-55-Regular_20_4.bin',\n",
" 'build/full/AlibabaPuHuiTi-3-55-Regular_30_4.bin',\n",
" 'build/full/AlibabaSansJP-Regular_14_1.bin',\n",
" 'build/full/AlibabaSansJP-Regular_16_4.bin',\n",
" 'build/full/AlibabaSansJP-Regular_20_4.bin',\n",
" 'build/full/AlibabaSansJP-Regular_30_4.bin',\n",
" 'build/full/AlibabaSansKR-Regular_14_1.bin',\n",
" 'build/full/AlibabaSansKR-Regular_16_4.bin',\n",
" 'build/full/AlibabaSansKR-Regular_20_4.bin',\n",
" 'build/full/AlibabaSansKR-Regular_30_4.bin',\n",
" 'build/full/AlibabaSansTC-55_14_1.bin',\n",
" 'build/full/AlibabaSansTC-55_16_4.bin',\n",
" 'build/full/AlibabaSansTC-55_20_4.bin',\n",
" 'build/full/AlibabaSansTC-55_30_4.bin',\n",
" 'build/full/AlibabaSansThai-Rg_14_1.bin',\n",
" 'build/full/AlibabaSansThai-Rg_16_4.bin',\n",
" 'build/full/AlibabaSansThai-Rg_20_4.bin',\n",
" 'build/full/AlibabaSansThai-Rg_30_4.bin',\n",
" 'build/full/AlibabaSansViet-Rg_14_1.bin',\n",
" 'build/full/AlibabaSansViet-Rg_16_4.bin',\n",
" 'build/full/AlibabaSansViet-Rg_20_4.bin',\n",
" 'build/full/AlibabaSansViet-Rg_30_4.bin']"
]
},
"execution_count": 36,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import multiprocessing as mp\n",
"from functools import partial\n",
"import os\n",
"import shutil\n",
"\n",
"build_dir = \"build/full\"\n",
"if os.path.exists(build_dir):\n",
" shutil.rmtree(build_dir)\n",
"os.makedirs(build_dir)\n",
"\n",
"def generate_font_full_bin(args):\n",
" \"\"\"单个字体生成任务\"\"\"\n",
" font_path, name, size, bpp = args\n",
" output = f\"{build_dir}/{name}_{size}_{bpp}.bin\"\n",
" print(f\"Generating {output}...\")\n",
" cmd = f\"lv_font_conv {flags} --font {font_path} --format bin --bpp {bpp} -o {output} --size {size} -r 0x0-0xfffff\"\n",
" os.system(cmd)\n",
" return output\n",
"\n",
"# 创建任务列表\n",
"def create_font_tasks(font_list, task_type=\"bin\"):\n",
" \"\"\"创建字体生成任务列表\"\"\"\n",
" tasks = []\n",
" sizes_bpps = [(14, 1), (16, 4), (20, 4), (30, 4)]\n",
" \n",
" for font_path in font_list:\n",
" font_name = font_path.split('/')[-1].split('.')[0]\n",
" for size, bpp in sizes_bpps:\n",
" tasks.append((font_path, font_name, size, bpp))\n",
" \n",
" return tasks\n",
"\n",
"\n",
"# 多进程生成字体文件\n",
"def generate_fonts_parallel(font_list, task_type=\"bin\", max_workers=None):\n",
" \"\"\"使用进程池并行生成字体文件\"\"\"\n",
" if max_workers is None:\n",
" max_workers = min(mp.cpu_count(), len(font_list) * 4) # 每个字体4个任务\n",
" \n",
" tasks = create_font_tasks(font_list, task_type)\n",
" \n",
" print(f\"开始并行生成 {len(tasks)} 个字体文件,使用 {max_workers} 个进程...\")\n",
" \n",
" with mp.Pool(processes=max_workers) as pool:\n",
" if task_type == \"bin\":\n",
" results = list(tqdm(\n",
" pool.imap(generate_font_full_bin, tasks),\n",
" total=len(tasks),\n",
" desc=\"生成字体文件\"\n",
" ))\n",
" else:\n",
" results = list(tqdm(\n",
" pool.imap(generate_font_source_mp, tasks),\n",
" total=len(tasks),\n",
" desc=\"生成字体源码\"\n",
" ))\n",
" \n",
" print(f\"完成生成 {len(results)} 个字体文件\")\n",
" return results\n",
"\n",
"# 并行生成bin文件\n",
"print(\"=== 开始并行生成字体bin文件 ===\")\n",
"generate_fonts_parallel(font_list, task_type=\"bin\", max_workers=8)\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "dev",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}