同一个 EPUB 阅读器,换一块芯片、换一套 SDK,究竟要改哪些东西?
核心方法论:第一性原理,从"为什么"出发
「上一季我们用一块十几美元的 ESP32 把 EPUB 电子书"榨"上了墨水屏。这季换平台了:思澈科技的 SF32、RT-Thread、SCons……名字全是新的,但请记住,移植的第一步永远是问"为什么"——为什么书还是那本书,代码却不能直接搬过来?把平台的差异想清楚,后面每一章都是在给这个问题填空。」
先回答一个很多人会问的问题:SiFli(思澈科技,官网 sifli.com)是谁?它是一家总部在南京的芯片设计公司,专注物联网与低功耗场景,产品主打"一颗芯片把 CPU、内存、蓝牙、各种外设全包了"。而 SF32 就是思澈的 MCU 平台(微控制器平台,即一套以低功耗 SoC 为核心的芯片家族)。这套平台旗下有 SF32LB52X、SF32LB55X、SF32LB56X、SF32LB57X、SF32LB58X 五条产品线——从 SDK 里内置的蓝牙、音频、DFU 升级等中间件能看出,它是面向蓝牙低功耗物联网应用的平台。本项目用到的芯片,在板级配置里写得很清楚:CHIP = 'SF32LB52X'。如果你完全没碰过思澈,只把它想成"一个国产的、类似乐鑫 ESP32 的 MCU 品牌"就够了——上季的乐鑫(Espressif)做 ESP32,这季的思澈(SiFli)做 SF32,两家都在做"小芯片、大生态"。
那么 "移植"(porting)是什么意思?一句话说人话:把跑在 A 平台上的项目,改造到 B 平台上去跑。注意关键词是"改造"——不是"重写",也不是"照抄"。因为下面三样东西一旦换了平台,几乎必然跟着变:芯片不同(寄存器、内存布局、外设引脚都换了)、SDK 不同(SDK 是"软件开发工具包",芯片厂商提供的驱动与函数库集合;ESP32 用乐鑫的 ESP-IDF,SF32 用思澈的 SiFli SDK)、构建方式不同(上季用 PlatformIO + CMake,这季用 SCons)。而业务逻辑——EPUB 怎么解析、文字怎么换行分页、翻页怎么渲染——这些跟芯片无关的代码,恰恰是移植时最希望能"原样搬走"的部分。
本教程的主角,就是这个问题的活样本:EPD_Reader-main/ 仓库里的 epdiy-epub/ 项目,就是 diy-esp32-epub-reader(上季那条「ESP32 电子墨水屏」技术线讲过)在 SF32-OED-EPD 系列开发板上的适配版。SF32-OED-EPD 是思澈配套的电子墨水屏(EPD)开发板系列,本仓库就带了 V1.1、V1.2 等多块板配置。它和你上一季用的 M5 Paper / LilyGo 一样,都是"单片机 + 墨水屏 + 电池"的阅读器硬件,只是大脑从 ESP32 换成了 SF32。
最后把两边的差异表列出来。这张表是全书的地基——后面每一章(构建、板级、显示、输入、电池……)都在处理这张表里的某一行。
| 维度 | 上季:ESP32 平台 | 本季:SiFli SF32 平台 |
|---|---|---|
| 芯片厂商 | 乐鑫科技(Espressif) | 思澈科技(SiFli) |
| 芯片型号 | ESP32(双核 Xtensa) | SF32LB52X(Cortex-M33 系) |
| 开发框架 | ESP-IDF | SiFli SDK(基于 RT-Thread 定制) |
| 操作系统 | FreeRTOS | RT-Thread(可选 FreeRTOS) |
| 构建系统 | PlatformIO / CMake | SCons(Python) |
| 配置方式 | sdkconfig / menuconfig | Kconfig + proj.conf / board.conf |
| 目标板 | M5 Paper / LilyGo T5 等 | SF32-OED-EPD V1.1 / V1.2 系列 |
# SiFli-SDK/…/version.txt(SDK 版本号,本仓库所用)
v2.5.0
# epdiy-epub/sf32-oed-epd_v11/hcpu/rtconfig.py(芯片 / 核 / 目标名,节选)
CHIP = 'SF32LB52X'
CORE = "HCPU"
TARGET_NAME = 'bf0_ap'
JLINK_DEVICE = 'SF32LB52X_NOR'
给"移植"画一张最容易懂的图:把一套房子从北京搬到上海——户型(芯片)不同,得改水电(驱动);物业(SDK)不同,得改缴费方式(API);家具(业务逻辑)只要能塞进新户型,就尽量原样搬。所以移植的第一步不是动手改代码,而是先盘点:哪些是"家具"(能搬),哪些是"水电"(必须重排)。后面 1.3 的仓库解剖,本质上就是在做这份盘点。
这个项目不是从零开始写的。它的 README「项目简介」用一句话交代了身世:基于 atomic14 的 diy-esp32-epub-reader 进行适配,运行于 SiFli SF32-OED-EPD 系列开发板,用于 EPUB 电子书阅读与低功耗场景。也就是说,它是一位"移植者"(不是原项目作者)拿上季那个开源阅读器,在思澈平台上重新落地。为什么选它来移植?因为它的"家具"质量足够好:EPUB 解析、zip 解包、HTML 解析、换行分页、渲染抽象,这些核心模块都放在独立的 lib/Epub/ 里,和硬件平台解耦得很干净——这正是适合移植的仓库长相。
那到底继承了什么、改了什么?先说继承的"家具":lib/Epub/ 四个子模块原样搬了过来——EpubList(书目 / 阅读器 / 目录,顺带内含 tinyxml2)、RubbishHtmlParser(XHTML 解析,产出 TextBlock / ImageBlock)、Renderer(渲染抽象与图像合成)、ZipFile(包内文件寻址)。配套的第三方库也基本沿用了:miniz-2.2.0(zip 解压)、tjpgd3(JPEG 解码)、png/PNGdec(PNG 解码)、epdiy(墨水屏驱动)。连示例书 disk/pg43-images.epub 都和上季仓库里的那本古登堡书同名同源。
再说"水电"级别的改动,这才是移植的精华。第一,操作系统换了:上季用 FreeRTOS + ESP-IDF 的消息队列,这季用 RT-Thread,主循环里从 rt_mq_recv(ui_queue, …) 接收按键/触控事件(消息队列是 RTOS 里任务间通信的通道,相当于"任务的邮箱")。第二,渲染器重写了:新增 src/boards/SF32PaperRenderer.h,对接 SF32 的墨水屏控制器(EPIC/LCDC),而不是 epdiy 的并行驱动。第三,板级抽象整个换了血:src/boards/ 下不再是 LilyGo / Epdiy / M5Paper,而是 battery(电池)、controls(按键/触控)、display_dbi、display_spi、touch(FT5446U / FT6336U / GT967 三款触控芯片)。第四,追加了大量上季没有的功能:中英文显示、动态字体加载(font_manager.c)、阅读设置页(reading_settings.cpp)、触控区域管理(UIRegionsManager.cpp)、电量/充电/低功耗状态机。
| 类别 | 直接继承(能搬的家具) | 重写 / 新增(必须重排的水电) |
|---|---|---|
| EPUB 解析 | lib/Epub/ 全部四个子模块 | — |
| 第三方库 | miniz、tjpgd3、PNGdec、epdiy | — |
| 操作系统 / 框架 | — | FreeRTOS → RT-Thread;ESP-IDF → SiFli SDK |
| 渲染 | Renderer 抽象接口 | SF32PaperRenderer(对接 EPIC/LCDC) |
| 板级 / 输入 | — | boards/ 重写:电池、按键+触控、DBI/SPI 屏驱 |
| 新功能 | — | 中英文显示、动态字体、阅读设置、状态机 |
# epdiy-epub/README.md · 项目简介
本项目基于 atomic14/diy-esp32-epub-reader 进行适配,
运行于 SiFli SF32-OED-EPD 系列开发板,用于 EPUB 电子书阅读与低功耗场景。
# lib/Epub/(继承自上游的核心解析库,四个子模块)
lib/Epub/
EpubList/ 书目 / 阅读器 / 目录(内含 tinyxml2)
Renderer/ 渲染抽象与图像合成
RubbishHtmlParser/ XHTML 解析(TextBlock / ImageBlock)
ZipFile/ 包内文件寻址(配合 miniz)
判断"一个移植算成功还是算重写",只要数一件东西:上游的 lib/Epub 还在不在。它还在,说明 EPUB 解析这条主线被完整保住了,后续所有章节都在讲"新平台怎么喂数据给它、怎么把结果画出来"。它要是没了,那就不叫移植,叫借鉴思想重写——难度完全是两个量级。看到 lib/Epub 原样躺着,这本移植教程的地基就稳了。
打开仓库根目录 EPD_Reader-main/,它只有两层,像一门"必修课 + 教科书":epdiy-epub/ 是项目代码(12 个一级目录,固件、脚本、板级配置都在这),SiFli-SDK/ 是思澈平台的 SDK(官方提供的固件开发框架,内含一个带 40 字节哈希长名的子目录,是克隆下来的 SDK 本体)。搞不清"哪些是作者写的、哪些是厂商给的",读这个仓库就会乱——记住口诀:业务逻辑在 epdiy-epub,平台能力在 SiFli-SDK。
先看 epdiy-epub/。它按功能分成几摊:src/ 是核心业务逻辑,lib/ 是解析库与第三方依赖,project/ 是构建脚本(SCons + Kconfig),sf32-oed-epd_base/ 与 sf32-oed-epd_v11/、sf32-oed-epd_v12/、sf32-oed-epd_v12_spi/ 是四套板级配置(后三套是不同硬件版本,base 是它们共用的公共配置),disk/ 是内置 Flash 里打包的样书,font/ 是内置中文字体(DroidSansFallback.ttf),waveform/ 是墨水屏波形文件与解码库,scripts/ 与 tools/ 是开发期用的字体/图片/波形转换工具。
再聚焦到 src/,这是后面十几章的主战场,先认几个"门面"文件:main.cpp 是程序入口,跑一个 UI 状态机主循环;epub_screen.cpp 负责各页面(主页、书库、目录、阅读、设置…)的绘制与事件处理;reading_settings.cpp 是阅读设置页(字体/字号/字重/行距/边距 + 持久化到 /settings.cfg);font_manager.c 是字体管理器,扫描 TF 卡 /fonts 目录动态加载外部字体;UIRegionsManager.cpp 管理触控区域(点击区域动态注册与命中检测);boards/ 是硬件抽象层,下面分 battery(电池)、controls(输入)、display_dbi(8-bit 并行墨水屏)、display_spi(SPI 墨水屏)、touch(触控芯片)五个子目录。
至于 SiFli-SDK/ 顶层,你只要知道四大件就够了:drivers/(HAL 硬件抽象层与芯片寄存器定义,类似 ESP-IDF 的 soc/ 与 hal/)、rtos/(RT-Thread / FreeRTOS 与 OS 抽象层)、middleware/(思澈自研组件:蓝牙、音频、DFU 升级等)、external/(第三方组件,SDK 里甚至自带 LVGL、mbedTLS、FFmpeg 等)。普通应用开发基本碰不到它——构建时 SCons 会自动把需要的部分编进来。
| 仓库 / 目录 | 职责 | 是谁写的 |
|---|---|---|
epdiy-epub/src/ | 核心业务逻辑(状态机、页面、字体、设置) | 本项目作者 |
epdiy-epub/lib/ | EPUB 解析库 + 第三方依赖(miniz/epdiy…) | 上游 + 开源库 |
epdiy-epub/project/ | SCons 构建脚本 + Kconfig.proj 配置 | 本项目作者 |
epdiy-epub/sf32-oed-epd_*/ | 四套板级配置(v11 / v12 / v12_spi / base) | 本项目作者 |
epdiy-epub/disk/ font/ waveform/ | 样书、内置字体、波形文件等资源 | 素材 + 工具生成 |
SiFli-SDK/ | 思澈固件开发框架(HAL / RTOS / 中间件) | 芯片厂商 |
# epdiy-epub/ 目录结构(节选)
epdiy-epub/
disk/ 内置 Flash 样书(3 个 EPUB)
font/ 内置字体 DroidSansFallback.ttf
lib/ EPUB 解析 + 图像解码 + epdiy
project/ SCons 构建脚本 + Kconfig.proj
sf32-oed-epd_base/ V1.1 / V1.2 公用板级配置
sf32-oed-epd_v11/ V1.1 板级配置
sf32-oed-epd_v12/ V1.2 板级配置
sf32-oed-epd_v12_spi/ V1.2 + SPI 墨水屏配置
scripts/ 字体 / 图片生成工具
src/ 核心业务逻辑
tools/ 波形 xlsx → C 数组工具
waveform/ 墨水屏波形文件与解码库
# epdiy-epub/src/ 关键文件一览
src/
main.cpp 程序入口:UI 状态机主循环
epub_screen.cpp 各页面绘制与事件处理
reading_settings.cpp 阅读设置 + 持久化 /settings.cfg
font_manager.c 字体管理器(扫描 TF 卡 /fonts)
UIRegionsManager.cpp 触控区域管理
boards/ 硬件抽象:battery/controls/display_dbi/display_spi/touch
读任何嵌入式仓库,先问三件事:入口在哪(main)、状态在哪(状态机)、硬件差异藏在哪(boards)。这个仓库分别对应 src/main.cpp、src/type.h 的 AppUIState、src/boards/。把这三个锚点钉进脑子里,剩下的目录都是它们的"补给"。
这个移植版相比上游最大的进步,是中文支持。上季的 ESP32 版本只内置英文字体,而这版用 FreeType 加载了内置的 DroidSansFallback(一款开源中文字体,存放在 Flash XIP 区),开箱就能显示中英文。为了控制资源占用,README 特意说明默认未启用粗体/斜体中文字库——中文全字库动辄几 MB,粗斜体再翻倍,对嵌入式存储是巨大负担,所以只保底、不铺张。配合 font_manager.c,它甚至支持动态字体加载:往 TF 卡 /fonts 目录丢任意 .ttf / .otf 字体文件(最多 64 个),开机就会出现在阅读设置的字体列表里,选中后由 FreeType 直接读文件渲染。
阅读设置 UI 是另一大亮点。从"功能设置 → 阅读设置"或阅读页覆盖操作层进入,可以调字体、字号(24/28/32/36/40/44/48 px)、字重(正常/中粗/粗体)、行距(1.0x–2.0x)、边距(5–20 px),保存后以 key=value 文本格式持久化到 TF 卡 /settings.cfg,下次开机自动加载;指定字体不可用时自动回退内置字体。这在上季的三按键极简版里是完全不敢想的。
输入也从"三个按键"升级成"按键 + 触控"双通道。按键保留了 UP / DOWN / SELECT 语义,并新增 UPGLIDE(上滑)呼出阅读页覆盖操作层;触控芯片支持 FT5446U / FT6336U / GT967 三款,采用"先选中再确认"机制降低误触。操作说明里主页面点击左右区域切换选项、点击中间确认,书库页点击书籍项再点击确认进入目录,阅读页左右区域翻页、上滑呼出覆盖层——一套完整的触控导航。
最值得先打个照面的是 UI 状态机与低功耗。AppUIState 枚举定义了从主页面、书库、目录、阅读、阅读设置、功能设置、欢迎页,到低电量页、充电页、关机页共 12 个状态(还有天气相关状态,1.5 会讲到)。主循环 5 小时无交互进入关机页(常量 TIMEOUT_SHUTDOWN_TIME);无操作达到设置超时后进入欢迎页(类似熄屏);电量低于阈值进低电量页并抑制普通操作;充电状态变化只在百分比或状态真正变化时才刷屏,避免无效刷新。
| 设置项 | 可选值 | 说明 |
|---|---|---|
| 字体 | Default(内置)及 TF 卡 /fonts 全部 .ttf / .otf | 最多 64 个外部字体,FreeType 动态加载 |
| 字号 | 24 / 28 / 32 / 36 / 40 / 44 / 48 px | 像素单位 |
| 字重 | 正常 / 中粗 / 粗体 | — |
| 行距 | 1.0x / 1.2x / 1.4x / 1.6x / 1.8x / 2.0x | — |
| 边距 | 5 / 8 / 11 / 14 / 17 / 20 px | 页面边距 |
| 保存退出 | 确认后应用并持久化 | 写入 /settings.cfg |
/* src/type.h:AppUIState 状态枚举(节选) */
typedef enum {
MAIN_PAGE, // 主页面
SELECTING_EPUB, // 书库
SELECTING_TABLE_CONTENTS, // 目录
READING_EPUB, // 阅读
READING_SETTINGS, // 阅读设置
SETTINGS_PAGE, // 功能设置
WELCOME_PAGE, // 欢迎(超时熄屏)
LOW_POWER_PAGE, // 低电量
CHARGING_PAGE, // 充电
SHUTDOWN_PAGE // 关机
} AppUIState;
/* src/epub_screen.cpp:超时与全刷周期选项(设置页可选值) */
static const int kTimeoutOptions[] = {5, 10, 30, 60, 0}; // 单位:分钟,0 为不关机
static const int kFullRefreshOptions[] = {5, 10, 20, 0}; // 全刷周期(次)
static int full_refresh_idx = 1; // 默认 10 次全刷后做一次整屏刷新
电子墨水屏有个物理特性叫"残影"——同一块区域反复局部刷新会留灰影,所以读屏器行业有个惯例:隔几次局刷就整屏"洗"一次。这个项目把"隔几次"做成了设置项 kFullRefreshOptions(5/10/20 次或每次全刷)。理解"状态机 + 低功耗 + 刷新策略"这三件套,你就抓住了墨水屏设备的灵魂——它们在后半本(第 6、11 章)会反复登场。
这个项目最诚实的地方,是它没把自己包装成"已完成"。README 的「当前功能」清单,是已经实现的部分——中英文显示、双输入、阅读设置、动态字体、电量状态机,上面 1.4 讲的都在清单里。但仓库里同时躺着一批"未完成痕迹",它们是这本书最珍贵的第一手教材:比如 src/wheather.c 的天气功能(拼写还是 wheather),代码里已经定义了天气页、城市选择页两个状态,甚至接了"心知天气"的 HTTP API,但 README 的「当前功能」里一个字都没提——因为这套功能还没有完整接入主流程。
证据不止一处。打开 src/epub_screen.h,主页面的选项枚举 MainOption 有 4 项(打开书库 / 进入设置 / 查看天气 / 继续阅读),而 README「主页面」一节只写了 3 个入口(打开书库 / 继续阅读 / 进入设置)——文档与代码出现了"漂移",漂的恰恰是天气。再看 src/boards/display_dbi/epd_display.c,墨水屏驱动里有一段 CopyToMixedGrayBuffer,当数据格式是单色 LCDC_PIXEL_FORMAT_MONO 时直接 RT_ASSERT(0); //Not implemented yet——即"单色混合灰"这条路径还没写。源代码里的 TODO 注释也不止一处。这些都是"在途开发"的铁证。
这里引出一条贯穿全书的阅读原则:当 README 与代码冲突时,以代码为准。README 是作者"想让你看到的",代码是"它现在真实的状态";一个诚实作者写 README 也会漏掉半成品。所以本教程从第 3 章起,每讲完一块,都会做一次「已实现 / 未实现」盘点(第 14、15 章会集中清算),让你亲眼看到"一个开源移植项目到底完成到什么程度"。这不只是知识,更是一种读源码的职业习惯。
最后是路线图。全书 16 章分五个分部:本季前两章(第 1-2 章)讲认识项目与平台构建;接着「硬件平台」(第 3-6 章)讲板级配置、显示驱动、输入、电池低功耗;「解析与渲染」(第 7-10 章)讲 EPUB 继承与中文适配、动态字体、阅读设置、渲染绘制;「UI 与状态机」(第 11-13 章)讲状态机总览、书库目录阅读流程、覆盖操作层与触控区域;最后「现状与展望」(第 14-16 章)做已实现盘点、未实现清单、二次开发与新屏移植。每走完一段,你都会更接近"自己动手改这个项目"。
| 类型 | 内容 | 证据 / 位置 |
|---|---|---|
| 已实现 | 中英文显示、双输入、阅读设置、动态字体、电量状态机 | README「当前功能」 |
| 未完成(天气) | 天气页 / 城市选择页状态、HTTP 获取已写,但未列入功能清单 | src/wheather.c、epub_screen.h 的 WEATHER_PAGE |
| 未完成(驱动) | 单色数据格式的混合灰拷贝路径 | epd_display.c 中 RT_ASSERT(0); //Not implemented yet |
| 文档漂移 | README 主页面写 3 入口,代码 MainOption 有 4 项(含查看天气) | README「主页面」 vs epub_screen.h |
| 其他痕迹 | 源码中多处 TODO / FIXME 注释 | grep TODO src/ |
/* src/epub_screen.h:主页面选项——代码 4 项 vs README 3 项 */
typedef enum {
OPTION_OPEN_LIBRARY = 0, // 打开书库
OPTION_ENTER_SETTINGS, // 进入设置
OPTION_WEATHER, // 查看天气(README 未列出)
OPTION_CONTINUE_READING, // 继续阅读
OPTION_COUNT
} MainOption;
/* src/boards/display_dbi/epd_display.c:未实现分支 */
if (hlcdc->Layer[HAL_LCDC_LAYER_DEFAULT].data_format == LCDC_PIXEL_FORMAT_MONO)
{
RT_ASSERT(0); //Not implemented yet
}
README 说主循环"默认 5 小时无交互进入关机页",代码里 TIMEOUT_SHUTDOWN_TIME 5 同时被两处使用:关机循环用 60 * 1000 * 60 * TIMEOUT_SHUTDOWN_TIME(小时级),而欢迎页超时经 screen_init(TIMEOUT_SHUTDOWN_TIME) 走的是 screen_get_timeout_shutdown_minutes()(分钟级)——同一个常量身兼"5 小时关机"与"5 分钟熄屏"两个角色。这不一定是 bug,但足以说明:移植项目的常量语义要对着 main.cpp 逐行核实,别只信 README 一句话。
用一句话分别解释:SiFli、SF32、移植、SDK。要求不看本页,先自己写,再对照 1.1 检查有没有把"芯片品牌 / 芯片平台 / 改造动作 / 厂商工具包"四个概念搞混。
SiFli 是公司名,SF32 是产品线名,两者一个"人"一个"姓";SDK 是工具,移植是动作。
SiFli(思澈科技)是南京的芯片设计公司;SF32 是思澈的 MCU 平台,旗下有 SF32LB52X/55X/56X/57X/58X 五条产品线;SDK 是芯片厂商提供的软件开发工具包(驱动与函数库集合),ESP32 用 ESP-IDF,SF32 用 SiFli SDK;移植是把跑在 A 平台的代码改造到 B 平台——芯片、SDK、构建方式几乎必然要换,业务逻辑尽量原样搬。
打开 EPD_Reader-main/,指出以下五处的位置:(a) 固件程序入口在哪个文件?(b) UI 状态枚举定义在哪个文件、共几个状态?(c) 内置样书放在哪个目录?(d) 构建脚本在哪个目录?(e) 思澈平台 SDK 在哪个目录?
对照 1.3 的仓库地图:入口在 src/,状态枚举在 src/type.h,书在 disk/,构建在 project/,SDK 在顶层。
(a) src/main.cpp(int main() → main_task,UI 状态机主循环);(b) src/type.h 的 AppUIState,主流程 10 个状态 + 天气相关 2 个,共 12 个;(c) disk/(no_oebps.epub、oebps.epub、pg43-images.epub);(d) project/(SConstruct / SConscript / Kconfig.proj);(e) SiFli-SDK/。
对照 1.5,到源码里亲自核验两件事:(1) 主页面选项 MainOption 到底有几项、在哪定义、第 2 项是什么?(2) 用 grep -rn "Not implemented" src/ lib/ 找未实现注释,看它落在哪个函数、哪种数据格式的分支里。
枚举在 src/epub_screen.h;未实现注释在 src/boards/display_dbi/epd_display.c 的 CopyToMixedGrayBuffer 里,分支条件是 LCDC_PIXEL_FORMAT_MONO。
(1) MainOption 有 4 项:OPTION_OPEN_LIBRARY、OPTION_ENTER_SETTINGS、OPTION_WEATHER(查看天气,README 未列入)、OPTION_CONTINUE_READING,定义在 src/epub_screen.h。(2) 未实现注释位于 epd_display.c 的 CopyToMixedGrayBuffer 函数:当图层数据格式为 LCDC_PIXEL_FORMAT_MONO(单色)时 RT_ASSERT(0),即"单色→混合灰"的拷贝路径尚未实现,只有 LCDC_PIXEL_FORMAT_A4(4-bit 灰)路径是通的。这正是"在途开发"的现场。
用第一性原理回答:为什么移植到 SF32 后,第一个被重点解决的是中文字体,而不是别的功能?提示从"目标市场 / 字体体积 / 渲染链路"三个角度各想一层,并给出你的推理链。
想三点:思澈的客户主要在哪、英文 95 个字符 vs 中文常用 6-7 千字的体积差、FreeType 在这种体积下还能不能塞进嵌入式存储与内存。
(1) 市场:思澈主打国内物联网与消费电子,中文是基本盘,一个不显示中文的阅读器在国内没有意义——所以这是"能用"的底线而非可选项。(2) 体积:英文只需 ASCII 的 95 个字符,而常用中文按 GB2312 一级字表就约 3755 字,全字库通常数 MB;粗体/斜体再翻倍,嵌入式 Flash 扛不住,所以 README 明确"默认未启用粗体/斜体中文字库",只做单字重兜底。(3) 渲染链路:中文字形无法像英文字母那样简单拼宽,需要 FreeType 按字形描边,再叠加动态字体加载(TF 卡 /fonts)来缓解内置体积。结论:中文字体不是一个"加个功能",而是把字体体积、存储、渲染性能三者重新做平衡的系统工程——这正是移植中"业务逻辑之外的平台难题"的典型代表。