Эх сурвалжийг харах

add: easylvgl, 支持hzfont字库加载

zengeshuai 1 сар өмнө
parent
commit
e5277d0994

+ 46 - 1
components/easylvgl/binding/luat_lib_easylvgl.c

@@ -32,6 +32,7 @@ static int l_easylvgl_deinit(lua_State *L);
 static int l_easylvgl_refresh(lua_State *L);
 static int l_easylvgl_indev_bind_touch(lua_State *L);
 static int l_easylvgl_keyboard_enable_system(lua_State *L);
+static int l_easylvgl_font_load(lua_State *L);
 
 // Button 模块声明
 extern void easylvgl_register_button_meta(lua_State *L);
@@ -71,10 +72,14 @@ extern int easylvgl_keyboard_create(lua_State *L);
 
 // 模块注册表
 static const rotable_Reg_t reg_easylvgl[] = {
+    // 基础设置
     {"init", ROREG_FUNC(l_easylvgl_init)},
     {"deinit", ROREG_FUNC(l_easylvgl_deinit)},
     {"refresh", ROREG_FUNC(l_easylvgl_refresh)},
     {"indev_bind_touch", ROREG_FUNC(l_easylvgl_indev_bind_touch)},
+    {"keyboard_enable_system", ROREG_FUNC(l_easylvgl_keyboard_enable_system)},
+    {"font_load", ROREG_FUNC(l_easylvgl_font_load)},
+    // 组件注册
     {"button", ROREG_FUNC(easylvgl_button_create)},
     {"label", ROREG_FUNC(easylvgl_label_create)},
     {"image", ROREG_FUNC(easylvgl_image_create)},
@@ -84,7 +89,6 @@ static const rotable_Reg_t reg_easylvgl[] = {
     {"msgbox", ROREG_FUNC(easylvgl_msgbox_create)},
     {"textarea", ROREG_FUNC(easylvgl_textarea_create)},
     {"keyboard", ROREG_FUNC(easylvgl_keyboard_create)},
-    {"keyboard_enable_system", ROREG_FUNC(l_easylvgl_keyboard_enable_system)},
     // 颜色格式常量
     {"COLOR_FORMAT_RGB565", ROREG_INT(EASYLVGL_COLOR_FORMAT_RGB565)},
     {"COLOR_FORMAT_ARGB8888", ROREG_INT(EASYLVGL_COLOR_FORMAT_ARGB8888)},
@@ -296,3 +300,44 @@ lv_obj_t *easylvgl_check_component(lua_State *L, int index, const char *mt) {
     return ud->obj;
 }
 
+/**
+ * 加载字体
+ * @api easylvgl.font_load(type, path, ...)
+ * @string type 字体类型,"hzfont" 或 "bin"
+ * @string path 字体路径,对于 "hzfont",传 nil 则使用内置字库
+ * @int size 可选,TTF 字体大小,默认 16
+ * @int cache_size 可选,TTF 缓存数量,默认 256
+ * @int antialias 可选,TTF 抗锯齿等级,默认 -1(自动)
+ * @bool is_default 可选,是否设置为全局默认字体
+ * @return userdata 字体指针
+ */
+static int l_easylvgl_font_load(lua_State *L) {
+    const char *type = luaL_checkstring(L, 1);
+    bool is_default = false;
+    lv_font_t *font = NULL;
+
+    if (strcmp(type, "hzfont") == 0) {
+        const char *path = luaL_optstring(L, 2, NULL);
+        uint16_t size = luaL_optinteger(L, 3, 16);
+        uint32_t cache_size = luaL_optinteger(L, 4, 256);
+        int antialias = luaL_optinteger(L, 5, -1);
+        is_default = lua_toboolean(L, 6);
+        font = easylvgl_font_hzfont_create(path, size, cache_size, antialias);
+    } else if (strcmp(type, "bin") == 0) {
+        const char *path = luaL_checkstring(L, 2);
+        is_default = lua_toboolean(L, 3);
+        font = lv_binfont_create(path);
+    } else {
+        LLOGE("font_load: unsupported type %s", type);
+        return 0;
+    }
+
+    if (font) {
+        // 设置为全局默认字体
+        lv_obj_set_style_text_font(lv_screen_active(), font, 0);
+        lua_pushlightuserdata(L, font);
+        return 1;
+    }
+    return 0;
+}
+

+ 10 - 0
components/easylvgl/inc/luat_easylvgl.h

@@ -221,6 +221,16 @@ void *easylvgl_buffer_alloc(easylvgl_ctx_t *ctx, size_t size, easylvgl_buffer_ow
  */
 void easylvgl_buffer_free_all(easylvgl_ctx_t *ctx);
 
+/**
+ * 创建 HZFont(TTF)字体,用于 EasyLVGL
+ * @param path TTF 文件路径,为 NULL 则使用内置字库
+ * @param size 字号
+ * @param cache_size 缓存容量
+ * @param antialias 抗锯齿等级
+ * @return lv_font_t 字体对象,失败返回 NULL
+ */
+lv_font_t * easylvgl_font_hzfont_create(const char * path, uint16_t size, uint32_t cache_size, int antialias);
+
 /**
  * 设置显示缓冲
  * @param ctx 上下文指针

+ 203 - 0
components/easylvgl/src/font/lv_font_hzfont.c

@@ -0,0 +1,203 @@
+// 负责:LVGL 适配 HZFont (TTF) 渲染驱动实现
+
+#include "lvgl9/lvgl.h"
+#include "luat_base.h"
+#include "luat_hzfont.h"
+#include "ttf_parser.h"
+
+#define LUAT_LOG_TAG "easylvgl.hzfont"
+#include "luat_log.h"
+
+/** 
+ * HZFont 字体描述私有数据结构 
+ */
+typedef struct {
+    uint16_t font_size; /**< 当前字号 */
+    uint8_t antialias;  /**< 抗锯齿等级 (1, 2, 4) */
+    TtfFont *ttf;       /**< 底层 TTF 句柄引用 */
+} lv_font_hzfont_dsc_t;
+
+static uint16_t hzfont_default_ascent(uint16_t font_size) {
+    if (font_size == 0) {
+        return 0;
+    }
+    uint32_t asc = (uint32_t)font_size * 4u; // 0.8 = 4/5
+    uint32_t value = (asc + 4u) / 5u;
+    if (value == 0) {
+        value = 1;
+    }
+    return (uint16_t)value;
+}
+
+/**
+ * LVGL 获取字符描述回调 (Metrics)
+ * @param font 字体指针
+ * @param dsc_out 输出字符描述(宽高、偏移等)
+ * @param letter 当前字符的 Unicode 码点
+ * @param letter_next 下一个字符(用于 Kerning,暂未支持)
+ * @return bool 是否成功获取
+ */
+static bool hzfont_get_glyph_dsc(const lv_font_t * font, lv_font_glyph_dsc_t * dsc_out, uint32_t letter, uint32_t letter_next) {
+    // 获取私有数据
+    lv_font_hzfont_dsc_t * dsc = (lv_font_hzfont_dsc_t *)font->dsc;
+    if (!dsc || !dsc->ttf) return false;
+
+    // 重要:【修复点】必须先清空结构体,防止垃圾数据导致 LVGL 内部缓存系统崩溃
+    // LVGL 9 的 dsc_out 包含 entry 指针,如果是随机值,回调一返回就会崩
+    memset(dsc_out, 0, sizeof(lv_font_glyph_dsc_t));
+    
+    // 1. 码点转索引
+    uint16_t glyph_index = 0;
+    if (ttf_lookup_glyph_index(dsc->ttf, letter, &glyph_index) != TTF_OK) {
+        return false;
+    }
+
+    // 2. 获取位图以获取精确度量 (Metrics)
+    // 优先从底层引擎的 LRU 缓存中获取,避免重复渲染
+    const TtfBitmap *bitmap = luat_hzfont_get_bitmap(glyph_index, dsc->font_size, dsc->antialias);
+    // 如果获取不到位图,则进行处理
+    if (!bitmap) {
+        // 空格和制表符特殊处理
+        if (letter == ' ' || letter == '\t') {
+            dsc_out->adv_w = dsc->font_size/2;
+            dsc_out->box_w = 0;
+            dsc_out->box_h = 0;
+            dsc_out->format = LV_FONT_GLYPH_FORMAT_NONE;
+            dsc_out->resolved_font = font;
+            dsc_out->gid.index = glyph_index;
+            return true;
+        }else{
+            LLOGW("hzfont get bitmap failed");
+            return false;
+        }
+    }
+
+    // 3. 填充 LVGL 描述信息
+    dsc_out->adv_w = (uint16_t)bitmap->width; 
+    dsc_out->box_w = (uint16_t)bitmap->width;
+    dsc_out->box_h = (uint16_t)bitmap->height;
+    dsc_out->ofs_x = 0;
+    
+    // 设置每行字节数。对于 A8 格式,stride 等于像素宽度
+    dsc_out->stride = dsc_out->box_w;
+
+    // 设置解析字体,防止 Fallback 逻辑失效
+    dsc_out->resolved_font = font;
+
+    // 计算 Y 偏移。ofs_y 是字符顶端相对于基线的偏移。
+    // 计算公式:字号 - 内部 originY (基线位置) - 位图高度
+    dsc_out->ofs_y = (int16_t)((int32_t)bitmap->originY - (int32_t)bitmap->height);
+    
+    dsc_out->format = LV_FONT_GLYPH_FORMAT_A8; // HZFont 输出 A8 格式灰度图
+    dsc_out->gid.index = glyph_index;
+
+    return true;
+}
+
+/**
+ * LVGL 获取字符点阵回调 (Bitmap)
+ * @param dsc_out 字符描述(包含 gid)
+ * @param draw_buf 绘图缓冲区(暂未使用)
+ * @return const void* 点阵数据指针
+ */
+static const void * hzfont_get_glyph_bitmap(lv_font_glyph_dsc_t * dsc_out, lv_draw_buf_t * draw_buf) {
+    const lv_font_t * font = dsc_out->resolved_font;
+    lv_font_hzfont_dsc_t * dsc = (lv_font_hzfont_dsc_t *)font->dsc;
+    if (!dsc || !dsc->ttf) return NULL;
+
+    uint32_t glyph_index = dsc_out->gid.index;
+    const TtfBitmap *bitmap = luat_hzfont_get_bitmap((uint16_t)glyph_index, dsc->font_size, dsc->antialias);
+    if (!bitmap) return NULL;
+
+    if (draw_buf && draw_buf->data) {
+        uint32_t stride = draw_buf->header.stride;
+        if (stride == 0) {
+            stride = (uint32_t)bitmap->width;
+        }
+        if (stride < (uint32_t)bitmap->width) {
+            stride = (uint32_t)bitmap->width;
+        }
+
+        uint32_t draw_rows = draw_buf->header.h;
+        if (bitmap->height < (int32_t)draw_rows) {
+            draw_rows = (uint32_t)bitmap->height;
+        }
+
+        for (uint32_t y = 0; y < draw_rows; ++y) {
+            uint8_t *dst = draw_buf->data + (size_t)y * stride;
+            memcpy(dst, bitmap->pixels + (size_t)y * bitmap->width, bitmap->width);
+            if (stride > (uint32_t)bitmap->width) {
+                memset(dst + bitmap->width, 0, stride - bitmap->width);
+            }
+        }
+
+        if (draw_buf->header.h > draw_rows) {
+            uint8_t *tail = draw_buf->data + (size_t)draw_rows * stride;
+            size_t tail_size = (size_t)(draw_buf->header.h - draw_rows) * stride;
+            memset(tail, 0, tail_size);
+        }
+
+        draw_buf->header.w = bitmap->width;
+        draw_buf->header.h = bitmap->height;
+        draw_buf->header.cf = LV_COLOR_FORMAT_A8;
+        draw_buf->header.magic = LV_IMAGE_HEADER_MAGIC;
+        draw_buf->header.stride = stride;
+
+        return draw_buf;
+    }
+
+    return bitmap->pixels;
+}
+
+/**
+ * 创建 HZFont (TTF) 驱动字体对象
+ * @param path TTF 文件路径(传 NULL 则尝试加载内置字库)
+ * @param size 字号
+ * @param cache_size 点阵缓存上限数量
+ * @param antialias 抗锯齿等级 (-1: 自动, 1: 无, 2: 2x2, 4: 4x4)
+ * @return lv_font_t* 字体对象指针,失败返回 NULL
+ */
+lv_font_t * easylvgl_font_hzfont_create(const char * path, uint16_t size, uint32_t cache_size, int antialias) {
+    // 1. 初始化底层引擎(单例模式)
+    if (luat_hzfont_get_state() == LUAT_HZFONT_STATE_UNINIT) {
+        if (!luat_hzfont_init(path, cache_size)) {
+            LLOGE("hzfont init failed: %s", path ? path : "builtin");
+            return NULL;
+        }
+    }
+
+    // 2. 分配 LVGL 字体对象
+    lv_font_t * font = lv_malloc(sizeof(lv_font_t));
+    if (!font) return NULL;
+    memset(font, 0, sizeof(lv_font_t));
+
+    // 3. 构造私有描述上下文
+    lv_font_hzfont_dsc_t * dsc = lv_malloc(sizeof(lv_font_hzfont_dsc_t));
+    if (!dsc) {
+        lv_free(font);
+        LLOGI("hzfont malloc dsc failed");
+        return NULL;
+    }
+    dsc->font_size = size;
+    
+    // 自动选择 AA 等级:小号字体关闭以提高清晰度,大号开启
+    if (antialias < 0) {
+        dsc->antialias = (size <= 12) ? 1 : 2;
+    } else {
+        dsc->antialias = (uint8_t)antialias;
+    }
+    (void)ttf_set_supersample_rate(dsc->antialias);
+    LLOGI("hzfont antialias: %d", dsc->antialias);
+    // 关联底层句柄
+    dsc->ttf = luat_hzfont_get_ttf();
+    // 4. 绑定 LVGL 回调
+    font->dsc = dsc;
+    font->get_glyph_dsc = hzfont_get_glyph_dsc;
+    font->get_glyph_bitmap = hzfont_get_glyph_bitmap;
+    uint16_t ascent = hzfont_default_ascent(size);
+    // 额外增加5像素行高,防止字体显示不全
+    const int extra_leading = 5;
+    font->line_height = size + extra_leading;
+    font->base_line = (int32_t)font->line_height > ascent ? (int32_t)font->line_height - ascent : 0;
+    return font;
+}

+ 4 - 1
components/hzfont/inc/luat_hzfont.h

@@ -3,6 +3,7 @@
 
 #include "luat_base.h"
 #include <stdint.h>
+#include "ttf_parser.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -20,7 +21,9 @@ luat_hzfont_state_t luat_hzfont_get_state(void);
 uint32_t luat_hzfont_get_str_width(const char *utf8, unsigned char font_size);
 /* antialias = -1(自动), 1(无AA), 2(2x2), 4(4x4) */
 int luat_hzfont_draw_utf8(int x, int y, const char *utf8, unsigned char font_size, uint32_t color, int antialias);
-
+// 用于easylvgl的hzfont兼容接口
+TtfFont * luat_hzfont_get_ttf(void);
+const TtfBitmap * luat_hzfont_get_bitmap(uint16_t glyph_index, uint8_t font_size, uint8_t supersample);
 #ifdef __cplusplus
 }
 #endif

+ 58 - 0
components/hzfont/src/luat_hzfont.c

@@ -577,6 +577,64 @@ luat_hzfont_state_t luat_hzfont_get_state(void) {
     return g_ft_ctx.state;
 }
 
+/**
+ * 用于easylvgl,获取指定 glyph 的缓存位图(如不存在则实时渲染)
+ * @param glyph_index 目标 glyph 的索引
+ * @param font_size   渲染字号
+ * @param supersample 超采样等级(1/2/4)
+ * @return 指向缓存中的 TtfBitmap,失败返回 NULL
+ * @note 本函数内部会先查 Cache 结构,未命中时调 glyph 加载 + rasterize,
+ *       渲染完成后将结果插入缓存并返回;若缓存已满或渲染失败则返回 NULL。
+ */
+TtfFont * luat_hzfont_get_ttf(void) {
+    if (g_ft_ctx.state == LUAT_HZFONT_STATE_READY) {
+        return &g_ft_ctx.font;
+    }
+    return NULL;
+}
+
+/**
+ * 用于easylvgl,获取指定 glyph 的缓存位图(如不存在则实时渲染)
+ * @param glyph_index 目标 glyph 的索引
+ * @param font_size   渲染字号
+ * @param supersample 超采样等级(1/2/4)
+ * @return 指向缓存中的 TtfBitmap,失败返回 NULL
+ * @note 本函数内部会先查 Cache 结构,未命中时调 glyph 加载 + rasterize,
+ *       渲染完成后将结果插入缓存并返回;若缓存已满或渲染失败则返回 NULL。
+ */
+const TtfBitmap * luat_hzfont_get_bitmap(uint16_t glyph_index, uint8_t font_size, uint8_t supersample) {
+    if (g_ft_ctx.state != LUAT_HZFONT_STATE_READY) return NULL;
+    
+    hzfont_cache_entry_t *entry = hzfont_cache_get(glyph_index, font_size, supersample);
+    if (entry) {
+        return &entry->bitmap;
+    }
+    
+    // 如果未命中,尝试加载并渲染
+    TtfGlyph glyph;
+    if (ttf_load_glyph(&g_ft_ctx.font, glyph_index, &glyph) != TTF_OK) {
+        return NULL;
+    }
+    
+    TtfBitmap bitmap;
+    memset(&bitmap, 0, sizeof(TtfBitmap));
+    if (ttf_rasterize_glyph(&g_ft_ctx.font, &glyph, font_size, &bitmap) != TTF_OK) {
+        ttf_free_glyph(&glyph);
+        return NULL;
+    }
+    ttf_free_glyph(&glyph);
+    
+    hzfont_cache_entry_t *new_entry = hzfont_cache_insert(glyph_index, font_size, supersample, &bitmap);
+    if (new_entry) {
+        return &new_entry->bitmap;
+    }
+    
+    // 插入失败(可能缓存已满且无法替换),则需要释放并返回 NULL,或者直接返回临时的?
+    // 实际上 hzfont_cache_insert 会处理淘汰
+    ttf_free_bitmap(&bitmap);
+    return NULL;
+}
+
 static uint32_t hzfont_glyph_estimate_width_px(const TtfGlyph *glyph, float scale) {
     if (!glyph || glyph->pointCount == 0 || glyph->contourCount == 0) {
         return 0;