[ 附件影像 RECORD ]
EUBF管理器

EUBF管理器

[ SCAN_URL ]
[ 归档时间 ]:2026-03-24 21:11 [ 课题责任人 ]:文止墨 [ 档案分类 ]:嵌入式, 文章, 编程
*1774386678*

EUBF管理器通过提供方便的字体请求接口来链接上层 LCD 等渲染程序和下层存储介质(USB 或 SD)。

除此之外,EUBF 管理器还对于每个快速槽位维护一个最近最少使用(LRU)缓存,进一步减少读取文件产生的 IO 开销。

不过,由于缓存机制可能会很大,所以缓存池开辟在外载的 SDRAM 中。

为了更好的展示缓存区配置,将 sdram 的接口一并展示:

sdram.h
C
#ifndef __SDRAM_W9825G6KH_H
#define __SDRAM_W9825G6KH_H

#include "stm32f4xx_hal.h"

/* * W9825G6KH 容量为 32MB (256Mbit) 
 * 原理图片选连接至 FMC_SDNE0,映射到 STM32 的 SDRAM Bank 1
 */
#define SDRAM_BANK_ADDR     ((uint32_t)0xC0000000)
#define SDRAM_SIZE          ((uint32_t)0x2000000) // 32MB

/* FMC 命令超时时间 */
#define SDRAM_TIMEOUT       ((uint32_t)0xFFFF)

/* W9825G6KH 模式寄存器配置定义 */
#define SDRAM_MODEREG_BURST_LENGTH_1             ((uint16_t)0x0000)
#define SDRAM_MODEREG_BURST_LENGTH_2             ((uint16_t)0x0001)
#define SDRAM_MODEREG_BURST_LENGTH_4             ((uint16_t)0x0002)
#define SDRAM_MODEREG_BURST_LENGTH_8             ((uint16_t)0x0004)

#define SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL      ((uint16_t)0x0000)
#define SDRAM_MODEREG_BURST_TYPE_INTERLEAVED     ((uint16_t)0x0008)

#define SDRAM_MODEREG_CAS_LATENCY_2              ((uint16_t)0x0020)
#define SDRAM_MODEREG_CAS_LATENCY_3              ((uint16_t)0x0030)

#define SDRAM_MODEREG_OPERATING_MODE_STANDARD    ((uint16_t)0x0000)

#define SDRAM_MODEREG_WRITEBURST_MODE_PROGRAMMED ((uint16_t)0x0000)
#define SDRAM_MODEREG_WRITEBURST_MODE_SINGLE     ((uint16_t)0x0200)

/* 外部引入 CubeMX 生成的 SDRAM 句柄 */
extern SDRAM_HandleTypeDef hsdram1;


/* ======================================  其它外设的配置统一在这里管理   =========================================*/
/* ------------------------------------------------------------------------------------------
 * [显存内存映射配置]
 * 依赖于 sdram.h 中的 SDRAM_BANK_ADDR (如 0xC0000000)
 * ------------------------------------------------------------------------------------------*/
/* 定义全屏显存首地址 (默认存放在 SDRAM 的起始位置) */
#define LCD_FRAME_BUFFER        SDRAM_BANK_ADDR
#define LCD_FRAME_BUFFER_SIZE    (320 * 480 * 2) // 320x480 分辨率,RGB565 格式每像素 2 字节,共 307200 字节
/* 定义字库/图片缓存首地址 (跳过全屏显存的大小) */
#define FONT_CACHE_BUFFER       (SDRAM_BANK_ADDR + LCD_FRAME_BUFFER_SIZE)
#define FONT_CACHE_BUFFER_SIZE  (320 * 480 * 2) 



/* * EUBF 字库 LRU 缓存区域 - 分配 6MB 
 * 紧接着显存区域之后。供我们定义的 10 个超级槽位使用。
 */
#define EUBF_CACHE_BASE_ADDR    (FONT_CACHE_BUFFER + FONT_CACHE_BUFFER_SIZE)
#define EUBF_CACHE_SIZE         (6 * 1024 * 1024)




/* 函数声明 */
void SDRAM_InitSequence(void);
void SDRAM_WriteBuffer8(uint32_t addr, uint8_t *data, uint32_t len);
void SDRAM_ReadBuffer8(uint32_t addr, uint8_t *data, uint32_t len);
void SDRAM_WriteBuffer16(uint32_t addr, uint16_t *data, uint32_t len);
void SDRAM_ReadBuffer16(uint32_t addr, uint16_t *data, uint32_t len);
void SDRAM_WriteBuffer32(uint32_t addr, uint32_t *data, uint32_t len);
void SDRAM_ReadBuffer32(uint32_t addr, uint32_t *data, uint32_t len);

uint8_t SDRAM_Test(void);

#endif /* __SDRAM_W9825G6KH_H */

eubf_manager.h
C
/**
 ******************************************************************************
 * @file    eubf_manager.h
 * @brief   EUBF 字库管理器 (无状态架构版)
 * @author  文止墨
 * @date    2026-3-16
 * @note    https://github.com/S-R-Afraid/Embedded-Unicode-Bitmap-Font-Format
 ******************************************************************************
 */
#ifndef __EUBF_MANAGER_H
#define __EUBF_MANAGER_H

#include <stdint.h>
//#include "ff.h"            
#include "../../ThirdParty/MyLib/SDRAM/sdram.h" 

#define EUBF_MAX_SLOTS        10    
#define EUBF_MAX_CACHE_NODES  256   
#define EUBF_MAX_BITMAP_SIZE  2048  

#define EUBF_OK               0
#define EUBF_ERR_NO_FILE      -1    
#define EUBF_ERR_FORMAT       -2    

#pragma pack(push, 1)
/**
 * @brief 单个字符的 LRU 缓存节点 (存放在 SDRAM)
 */
typedef struct {
    uint16_t unicode;        
    uint32_t last_used_tick; 
    uint8_t  box_w;
    uint8_t  box_h;
    int8_t   x_offset;
    int8_t   y_offset;
    uint16_t advance;
    uint8_t  bitmap_data[EUBF_MAX_BITMAP_SIZE]; 
} EUBF_GlyphCache;

/**
 * @brief 单个字库槽位管理结构 (存放在 SDRAM)
 */
typedef struct {
    uint8_t  is_occupied;    
    char     font_name[32];  
    uint8_t  font_size;      
    uint16_t line_height;
    uint16_t bpp;
    uint16_t missing_glyph;
    uint32_t page_dir_offset;
    uint32_t page_table_offset;
    uint32_t glyph_offset_offset;
    uint32_t glyph_data_offset;
    EUBF_GlyphCache cache_pool[EUBF_MAX_CACHE_NODES]; 
} EUBF_Slot;
#pragma pack(pop)

/* 指向 SDRAM 的基地址指针 */
#define g_eubf_slots ((EUBF_Slot *)EUBF_CACHE_BASE_ADDR)


/* ========================================================================= *
 * 底层 IO 移植接口定义 (由用户实现并注册)
 * ========================================================================= */
typedef struct {
    /** * @brief 打开文件接口
     * @return 返回文件描述符(fd)或者槽位号(>=0),失败返回 -1
     */
    int8_t  (*Open)(const char *path);
    
    /** * @brief 关闭文件接口
     */
    void    (*Close)(int8_t fd);
    
    /** * @brief 绝对偏移量读取接口 (对齐你的 FastSlot_Read 逻辑)
     * @return 0: 成功,其他: 失败
     */
    uint8_t (*ReadAt)(int8_t fd, uint32_t offset, uint8_t *buf, uint32_t len);
    
    /**
     * @brief 根目录路径 (例如 "0:/" 代表 SD卡,"1:/" 代表 U盘)
     */
    const char *RootPath; 
} EUBF_Port_Config_t;


/* ========================================================================= *
 * 核心对外 API
 * ========================================================================= */

/**
 * @brief 初始化管理器并注册底层 IO 接口
 * @param config 用户配置的底层 IO 函数指针结构体
 */
void EUBF_Init(EUBF_Port_Config_t *config);



/**
 * @brief 直接获取字形信息 (底层自动完成加载、切换、LRU 淘汰与 U 盘读取)
 */
const EUBF_GlyphCache* EUBF_Get_Glyph(const char *font_name, uint8_t size, uint16_t unicode);

/**
 * @brief 获取指定字库的行高
 */
uint16_t EUBF_Get_Line_Height(const char *font_name, uint8_t size);

/**
 * @brief 获取字符串在指定字库下的总渲染宽度 (像素)
 */
uint16_t EUBF_Get_Text_Width(const char *font_name, uint8_t size, const char *str);

/**
 * @brief U 盘断开时的紧急清理与安全切断
 */
void EUBF_Notify_Disconnected(void);

#endif /* __EUBF_MANAGER_H */

eubf_manager.c
C
/**
 ******************************************************************************
 * @file    eubf_manager.c
 * @brief   EUBF 字库管理器 (无状态架构 & 工业级硬件容错版)
 ******************************************************************************
 */

#include "eubf_manager.h"
#include <string.h>
#include <stdio.h>


/* 外部系统资源 */

extern uint32_t HAL_GetTick(void);



/* ========================================================================= *
 * 内部函数声明
 * ========================================================================= */

static uint32_t read_u32_le_safe(int8_t fd, uint32_t offset);
static uint16_t read_u16_le_safe(int8_t fd, uint32_t offset);
static const char* Translate_Font_Alias(const char *request_name);
static int8_t Find_Available_Slot(void);
static uint8_t UTF8_To_Unicode(const char *utf8_str, uint16_t *unicode);
static int8_t EUBF_Read_Glyph_From_Disk(EUBF_Slot *slot, uint16_t unicode, EUBF_GlyphCache *target_node);
static int8_t EUBF_Get_Or_Load_Slot(const char *font_name, uint8_t size);

/* ========================================================================= *
 * 内部资源
 * ========================================================================= */

static EUBF_Port_Config_t s_io_port;  // 保存用户传入的底层接口

static int8_t s_file_fds[EUBF_MAX_SLOTS]; // 存储底层返回的 fd (如 FastSlot 的槽位号)

static uint8_t s_meta_temp_buf[16] __attribute__((aligned(4)));

static uint32_t s_slot_last_used_tick[EUBF_MAX_SLOTS] = {0};

static int8_t s_last_matched_slot = -1;

/* ========================================================================= *
 * EUBF Header
 * ========================================================================= */

#pragma pack(push,1)
typedef struct {

    char     magic[4];
    uint16_t version;

    char     font_name[32];
    uint16_t font_size;

    uint16_t ascent;
    uint16_t descent;
    uint16_t line_height;
    uint16_t max_advance;

    uint8_t  bpp;
    uint8_t  padding1;

    uint16_t max_width;
    uint16_t max_height;

    uint32_t glyph_count;

    uint16_t missing_glyph;
    uint16_t padding2;

    uint32_t page_dir_offset;
    uint32_t page_table_offset;
    uint32_t glyph_offset_offset;
    uint32_t glyph_data_offset;

    char     source_ttf[64];
    uint32_t ttf_crc;

} EUBF_RawHeader;
#pragma pack(pop)

/* ========================================================================= *
 * 字体别名
 * ========================================================================= */

typedef struct {
    const char *alias;
    const char *real_dir;
} EUBF_AliasMap;

static const EUBF_AliasMap g_font_aliases[] = {

    {"华康金文体", "DFJinWenW3-GB"},
    {"白路彤彤手写体", "bailutongtongshouxieti"},
    {"字酷堂板桥体", "zktbqt"},
    {"峄山碑篆体", "ysbzt"},
    {"余繁新语","yufanxinyu"}

};

#define ALIAS_TABLE_SIZE (sizeof(g_font_aliases) / sizeof(g_font_aliases[0]))


/* ========================================================================= *
 * 初始化 (依赖注入)
 * ========================================================================= */
void EUBF_Init(EUBF_Port_Config_t *config)
{
    if (config != NULL) {
        s_io_port = *config; // 拷贝用户配置
    }

    for (int i = 0; i < EUBF_MAX_SLOTS; i++)
    {
        g_eubf_slots[i].is_occupied = 0;
        s_slot_last_used_tick[i] = 0;
        s_file_fds[i] = -1; 
    }
    s_last_matched_slot = -1;
}


/* ========================================================================= *
 * 安全读取辅助函数 (改为 Offset-based 偏移量读取)
 * ========================================================================= */
static uint32_t read_u32_le_safe(int8_t fd, uint32_t offset)
{
    if (s_io_port.ReadAt(fd, offset, s_meta_temp_buf, 4) == 0) { // 0表示成功
        return (uint32_t)(s_meta_temp_buf[0] | (s_meta_temp_buf[1] << 8) | 
                          (s_meta_temp_buf[2] << 16) | (s_meta_temp_buf[3] << 24));
    }
    return 0xFFFFFFFF;
}

static uint16_t read_u16_le_safe(int8_t fd, uint32_t offset)
{
    if (s_io_port.ReadAt(fd, offset, s_meta_temp_buf, 2) == 0) {
        return (uint16_t)(s_meta_temp_buf[0] | (s_meta_temp_buf[1] << 8));
    }
    return 0xFFFF;
}
/* ========================================================================= *
 * Slot 自动调度
 * ========================================================================= */

static int8_t EUBF_Get_Or_Load_Slot(const char *font_name, uint8_t size)
{
    if (font_name == NULL) return -1;

    const char *actual_dir = Translate_Font_Alias(font_name);

    /* 快速路径 */

    if (s_last_matched_slot >= 0 &&
        g_eubf_slots[s_last_matched_slot].is_occupied)
    {
        if (g_eubf_slots[s_last_matched_slot].font_size == size &&
            strcmp(g_eubf_slots[s_last_matched_slot].font_name, actual_dir) == 0)
        {
            s_slot_last_used_tick[s_last_matched_slot] = HAL_GetTick();
            return s_last_matched_slot;
        }
    }

    /* 查找已加载字体 */

    for (int8_t i = 0; i < EUBF_MAX_SLOTS; i++)
    {
        if (g_eubf_slots[i].is_occupied &&
            g_eubf_slots[i].font_size == size &&
            strcmp(g_eubf_slots[i].font_name, actual_dir) == 0)
        {
            s_slot_last_used_tick[i] = HAL_GetTick();
            s_last_matched_slot = i;
            return i;
        }
    }

    /* 查找可用 slot */
    int8_t slot_idx = Find_Available_Slot();
    EUBF_Slot *slot = &g_eubf_slots[slot_idx];

    if (slot->is_occupied && s_file_fds[slot_idx] >= 0)
    {
        s_io_port.Close(s_file_fds[slot_idx]); // 使用用户接口关闭旧文件
        s_file_fds[slot_idx] = -1;
        slot->is_occupied = 0;
    }

    // 拼接路径,使用动态注册的 RootPath
    static char path_buf[128];
    snprintf(path_buf, sizeof(path_buf), "%sasset/font/%s/%d.eubf", 
             s_io_port.RootPath, actual_dir, size);

    // 调用底层 Open 接口 (比如 USB_FastSlot_Open_UTF8)
    int8_t fd = s_io_port.Open(path_buf);
    if (fd < 0) return -1;
    
    s_file_fds[slot_idx] = fd; // 保存底层返回的句柄/槽位号

    // 读取 Header (偏移量 0)
    EUBF_RawHeader head;
    if (s_io_port.ReadAt(fd, 0, (uint8_t*)&head, sizeof(EUBF_RawHeader)) != 0 ||
        memcmp(head.magic, "EUBF", 4) != 0)
    {
        s_io_port.Close(fd);
        s_file_fds[slot_idx] = -1;
        return -1;
    }


    strcpy(slot->font_name, actual_dir);

    slot->font_size = size;
    slot->line_height = head.line_height;
    slot->bpp = head.bpp;
    slot->missing_glyph = head.missing_glyph;

    slot->page_dir_offset = head.page_dir_offset;
    slot->glyph_offset_offset = head.glyph_offset_offset;
    slot->glyph_data_offset = head.glyph_data_offset;

    for (uint16_t c = 0; c < EUBF_MAX_CACHE_NODES; c++)
    {
        slot->cache_pool[c].unicode = 0xFFFF;
        slot->cache_pool[c].last_used_tick = 0;
    }

    slot->is_occupied = 1;

    s_slot_last_used_tick[slot_idx] = HAL_GetTick();
    s_last_matched_slot = slot_idx;

    return slot_idx;
}

/* ========================================================================= *
 * 获取 Glyph
 * ========================================================================= */

const EUBF_GlyphCache*
EUBF_Get_Glyph(const char *font_name,
               uint8_t size,
               uint16_t unicode)
{
    int8_t slot_idx = EUBF_Get_Or_Load_Slot(font_name, size);

    if (slot_idx < 0)
        return NULL;

    EUBF_Slot *slot = &g_eubf_slots[slot_idx];

    int16_t target_index = -1;
    int16_t oldest_index = 0;

    uint32_t oldest_tick = 0xFFFFFFFF;
    uint32_t current_tick = HAL_GetTick();

    for (int i = 0; i < EUBF_MAX_CACHE_NODES; i++)
    {
        if (slot->cache_pool[i].unicode == unicode)
        {
            slot->cache_pool[i].last_used_tick = current_tick;
            return &slot->cache_pool[i];
        }

        if (slot->cache_pool[i].unicode == 0xFFFF)
        {
            target_index = i;
        }
        else if (target_index == -1 &&
                 slot->cache_pool[i].last_used_tick < oldest_tick)
        {
            oldest_tick = slot->cache_pool[i].last_used_tick;
            oldest_index = i;
        }
    }

    if (target_index == -1)
        target_index = oldest_index;

    EUBF_GlyphCache *node = &slot->cache_pool[target_index];

    if (EUBF_Read_Glyph_From_Disk(slot, unicode, node) == 0)
    {
        node->unicode = unicode;
        node->last_used_tick = current_tick;
        return node;
    }

    return NULL;
}

/* ========================================================================= *
 * 获取行高
 * ========================================================================= */

uint16_t EUBF_Get_Line_Height(const char *font_name, uint8_t size)
{
    int8_t slot_idx = EUBF_Get_Or_Load_Slot(font_name, size);

    if (slot_idx < 0)
        return 0;

    return g_eubf_slots[slot_idx].line_height;
}

/* ========================================================================= *
 * 计算字符串宽度
 * ========================================================================= */

uint16_t EUBF_Get_Text_Width(const char *font_name,
                             uint8_t size,
                             const char *str)
{
    if (!str)
        return 0;

    if (EUBF_Get_Or_Load_Slot(font_name, size) < 0)
        return 0;

    uint16_t total = 0;

    const char *p = str;

    while (*p)
    {
        uint16_t code;
        uint8_t len = UTF8_To_Unicode(p, &code);

        if (len == 0)
            break;

        const EUBF_GlyphCache *glyph =
            EUBF_Get_Glyph(font_name, size, code);

        if (glyph)
            total += glyph->advance;

        p += len;
    }

    return total;
}

/* ========================================================================= *
 * USB 断开
 * ========================================================================= */

void EUBF_Notify_Disconnected(void)
{
    for (int i = 0; i < EUBF_MAX_SLOTS; i++)
        g_eubf_slots[i].is_occupied = 0;

    s_last_matched_slot = -1;
}



/* ========================================================================= *
 * 从磁盘读取 glyph (全面使用 ReadAt 接口)
 * ========================================================================= */
static int8_t EUBF_Read_Glyph_From_Disk(EUBF_Slot *slot, uint16_t unicode, EUBF_GlyphCache *target_node)
{
    int8_t s_idx = (slot - g_eubf_slots);
    int8_t fd = s_file_fds[s_idx]; // 获取绑定的底层文件槽位号

    // 1. 读 Page 偏移
    uint32_t page_offset = read_u32_le_safe(fd, slot->page_dir_offset + ((unicode >> 8) * 4));
    uint16_t glyph_id = 0xFFFF;

    // 2. 读 Glyph ID
    if (page_offset != 0xFFFFFFFF) {
        glyph_id = read_u16_le_safe(fd, page_offset + ((unicode & 0xFF) * 2));
    }
    if (page_offset == 0xFFFFFFFF || glyph_id == 0xFFFF) {
        glyph_id = slot->missing_glyph;
    }

    // 3. 读数据偏移
    uint32_t rel_addr = read_u32_le_safe(fd, slot->glyph_offset_offset + (glyph_id * 4));

    // 4. 读 Glyph 头部信息 (8个字节)
    if (s_io_port.ReadAt(fd, slot->glyph_data_offset + rel_addr, s_meta_temp_buf, 8) != 0) {
        return -1;
    }

    target_node->box_w    = s_meta_temp_buf[0];
    target_node->box_h    = s_meta_temp_buf[1];
    target_node->x_offset = (int8_t)s_meta_temp_buf[2];
    target_node->y_offset = (int8_t)s_meta_temp_buf[3];
    target_node->advance  = (uint16_t)(s_meta_temp_buf[4] | (s_meta_temp_buf[5] << 8));

    if (target_node->box_h == 0 && target_node->advance == 0) return -1;

    // 5. 读点阵数据
    if (target_node->box_h > 0 && target_node->box_w > 0)
    {
        uint32_t b_size = ((target_node->box_w * slot->bpp + 7) / 8) * target_node->box_h;
        if (b_size > 0 && b_size <= EUBF_MAX_BITMAP_SIZE)
        {
            // 继续从上面读完8字节的偏移量往后读数据
            if (s_io_port.ReadAt(fd, slot->glyph_data_offset + rel_addr + 8, target_node->bitmap_data, b_size) != 0) {
                return -1;
            }
        }
    }
    return 0;
}

/* ========================================================================= *
 * 字体别名转换
 * ========================================================================= */

static const char*
Translate_Font_Alias(const char *request_name)
{
    for (int i = 0; i < ALIAS_TABLE_SIZE; i++)
    {
        if (strcmp(request_name, g_font_aliases[i].alias) == 0)
            return g_font_aliases[i].real_dir;
    }

    return request_name;
}

/* ========================================================================= *
 * Slot LRU
 * ========================================================================= */

static int8_t Find_Available_Slot(void)
{
    int8_t oldest_slot = 0;
    uint32_t oldest_tick = 0xFFFFFFFF;

    for (int8_t i = 0; i < EUBF_MAX_SLOTS; i++)
    {
        if (g_eubf_slots[i].is_occupied == 0)
            return i;

        if (s_slot_last_used_tick[i] < oldest_tick)
        {
            oldest_tick = s_slot_last_used_tick[i];
            oldest_slot = i;
        }
    }

    return oldest_slot;
}

/* ========================================================================= *
 * UTF8 → Unicode
 * ========================================================================= */

static uint8_t UTF8_To_Unicode(const char *utf8_str,
                               uint16_t *unicode)
{
    uint8_t first = (uint8_t)utf8_str[0];

    if (first < 0x80)
    {
        *unicode = first;
        return 1;
    }
    else if ((first & 0xE0) == 0xC0)
    {
        *unicode =
            ((first & 0x1F) << 6) |
            (utf8_str[1] & 0x3F);

        return 2;
    }
    else if ((first & 0xF0) == 0xE0)
    {
        *unicode =
            ((first & 0x0F) << 12) |
            ((utf8_str[1] & 0x3F) << 6) |
            (utf8_str[2] & 0x3F);

        return 3;
    }
    else if ((first & 0xF8) == 0xF0)
    {
        /* 4字节UTF8 → 截断为16bit */
        *unicode =
            ((utf8_str[2] & 0x3F) << 6) |
            (utf8_str[3] & 0x3F);

        return 4;
    }

    return 0;
}

概述

EUBF 字体管理器用于在嵌入式系统中动态加载和管理 .eubf 字库文件,并为上层显示驱动提供字符字形数据。该管理器负责:

  • 字库文件加载
  • 字库槽位(Slot)管理
  • 字形缓存(Glyph Cache)
  • UTF-8 字符解析
  • 字形磁盘读取
  • 字符串宽度计算

设计目标:

  • 上层 只需提供字体名称、字号、字符编码
  • 字库加载与缓存完全由 EUBF 管理层自动完成
  • 支持 多字体、多字号并行
  • 降低 LCD 驱动与字库系统耦合

系统架构

整体结构如下:

Plaintext
应用层 / LCD驱动

        │ (font_name + size + unicode)

EUBF Font Manager

        ├── Slot Manager
        │       ├── 字库加载
        │       └── LRU淘汰

        ├── Glyph Cache
        │       └── LRU字形缓存

        ├── UTF8解析器

        └── FATFS 文件读取


            .eubf 字库文件

字库文件结构

.eubf 文件由以下部分组成:

Plaintext
┌──────────────┐
│ Header       │
├──────────────┤
│ Page Dir     │
├──────────────┤
│ Page Tables  │
├──────────────┤
│ Glyph Offset │
├──────────────┤
│ Glyph Data   │
└──────────────┘

Header

C
typedef struct {

    char     magic[4];      // "EUBF"
    uint16_t version;

    char     font_name[32];
    uint16_t font_size;

    uint16_t ascent;
    uint16_t descent;
    uint16_t line_height;
    uint16_t max_advance;

    uint8_t  bpp;

    uint16_t max_width;
    uint16_t max_height;

    uint32_t glyph_count;

    uint16_t missing_glyph;

    uint32_t page_dir_offset;
    uint32_t page_table_offset;
    uint32_t glyph_offset_offset;
    uint32_t glyph_data_offset;

    char     source_ttf[64];
    uint32_t ttf_crc;

} EUBF_RawHeader;

字库槽位(Slot)管理

系统维护多个字体槽位:

C
EUBF_Slot g_eubf_slots[EUBF_MAX_SLOTS]

每个 Slot 表示一个 已加载的字体文件

Slot 内容

Plaintext
EUBF_Slot

├─ font_name
├─ font_size
├─ line_height
├─ bpp

├─ page_dir_offset
├─ glyph_offset_offset
├─ glyph_data_offset

└─ cache_pool[]

Slot 调度策略

使用 LRU(Least Recently Used)策略

Plaintext
如果存在空Slot
    使用空Slot
否则
    淘汰最久未使用字体

时间戳来源:

C
HAL_GetTick()

字形缓存(Glyph Cache)

每个字体 Slot 内部维护:

C
EUBF_MAX_CACHE_NODES

个字形缓存节点。

结构:

C
typedef struct {

    uint16_t unicode;

    uint8_t box_w;
    uint8_t box_h;

    int8_t x_offset;
    int8_t y_offset;

    uint16_t advance;

    uint32_t last_used_tick;

    uint8_t bitmap_data[EUBF_MAX_BITMAP_SIZE];

} EUBF_GlyphCache;

缓存流程

Plaintext
请求 glyph


检查 cache_pool

 ┌────┴────┐
 │         │
命中     未命中
 │         │
返回     读取磁盘
 │         │
更新LRU   写入缓存

缓存淘汰策略:

LRU

字形读取流程

读取 glyph 的完整流程:

Plaintext
Unicode


PageDir 查找


PageTable 查找


GlyphID


GlyphOffset


GlyphData

读取步骤

  1. 计算 Unicode 页:
C
page = unicode >> 8
  1. 查找 page_dir:
C
page_offset = page_dir[page]
  1. 查找 glyph id:
C
glyph_id = page_table[unicode & 0xFF]
  1. 读取 offset:
C
glyph_offset = glyph_offset_table[glyph_id]
  1. 读取 glyph 数据

UTF-8 字符解析

支持:

UTF8长度Unicode范围
1字节ASCII
2字节扩展拉丁
3字节CJK

函数:

C
UTF8_To_Unicode()

返回:

C
返回值 = UTF8长度

示例:

C
UTF8: E4 B8 AD
Unicode: 0x4E2D

对外接口

初始化

C
/* 定义 SD卡 的 EUBF 底层 IO 接口配置 */
EUBF_Port_Config_t font_sd_config = {
    .Open     = SD_FastSlot_Open_UTF8,  // 完美的接口匹配!
    .Close    = (void (*)(int8_t))SD_FastSlot_Close, // 强转一下避免警告
    .ReadAt   = SD_FastSlot_Read,       // 完美的参数匹配!
    .RootPath = "0:/"                   // SD卡的根目录
};
EUBF_Init(&font_sd_config);
C
/* 定义 U 盘的 EUBF 底层 IO 接口配置 */
EUBF_Port_Config_t font_usb_config = {
    .Open     = USB_FastSlot_Open_UTF8,
    .Close    = (void (*)(int8_t))USB_FastSlot_Close, // 强转以消除编译警告
    .ReadAt   = USB_FastSlot_Read,
    .RootPath = "1:/"  // 注意:在 FatFs 中,U 盘的根目录通常是 "1:/" 或 "USBHPath"
};
EUBF_Init(&font_usb_config);

初始化字体管理器。通过修改初始化结构体,您只需要提供读写接口就可以很方便的适配 USB、SD、或 flash 等存储介质。


获取字符字形

C
const EUBF_GlyphCache*
EUBF_Get_Glyph(
    const char *font_name,
    uint8_t size,
    uint16_t unicode
);

输入:

Plaintext
font_name   字体名称
size        字号
unicode     字符编码

返回:

GlyphCache 指针

获取行高

C
uint16_t EUBF_Get_Line_Height(
    const char *font_name,
    uint8_t size
);

计算字符串宽度

C
uint16_t EUBF_Get_Text_Width(
    const char *font_name,
    uint8_t size,
    const char *str
);

输入:

UTF8字符串

返回:

字符串像素宽度

存储介质断开通知

C
void EUBF_NotifyDisconnected(void)

功能:

清空所有Slot

避免 FATFS 句柄失效。


字体路径规则

字体文件路径:

C
USB:/asset/font/{font}/{size}.eubf

示例:

C
USB:/asset/font/DFJinWenW3-GB/24.eubf

字体别名系统

为了让上层使用 中文字体名称

"华康金文体"

系统会自动映射:

DFJinWenW3-GB

别名表:

C
static const EUBF_AliasMap g_font_aliases[]

示例:

C
static const EUBF_AliasMap g_font_aliases[] = {

    {"华康金文体", "DFJinWenW3-GB"},
    {"白路彤彤手写体", "bailutongtongshouxieti"},
    {"字酷堂板桥体", "zktbqt"},
    {"峄山碑篆体", "ysbzt"},
    {"余繁新语","yufanxinyu"}

};

典型使用方式

LCD 驱动只需:

C
const EUBF_GlyphCache *glyph;

glyph = EUBF_Get_Glyph("华康金文体", 24, unicode);

if (glyph)
{
    draw_bitmap(
        glyph->bitmap_data,
        glyph->box_w,
        glyph->box_h
    );
}

上层 无需管理

  • 字库加载
  • 文件句柄
  • 缓存
  • Slot

性能特征

当前版本复杂度:

操作复杂度
Slot查找O(N)
Glyph缓存查找O(N)
Glyph磁盘读取O(1)

典型性能:

Plaintext
ASCII缓存命中: < 5µs
中文缓存命中: < 5µs
磁盘读取:     0.5 ~ 2ms

内存占用

假设:

C
EUBF_MAX_SLOTS = 3
EUBF_MAX_CACHE_NODES = 32
EUBF_MAX_BITMAP_SIZE = 256

RAM 使用:

Plaintext
Slot结构       ≈ 200B
GlyphCache     ≈ 300B × 32
总计/Slot      ≈ 9KB

3 Slots        ≈ 27KB

当前限制

  1. Unicode 仅支持 16bit
  2. GlyphCache 使用 线性查找
  3. PageDir 每次读取磁盘
  4. 不支持 kerning
  5. 不支持字形压缩

后续优化方向

推荐优化:

PageDir 预加载

减少一次 FAT seek。

性能提升:

约 30%

GlyphCache 哈希表

当前:

O(N)

优化:

O(1)

Unicode 32bit

支持:

Emoji
扩展汉字

Glyph 压缩

RLE / LZ4。


总结

EUBF Font Manager 提供:

  • 自动字体加载
  • LRU Slot 管理
  • LRU Glyph Cache
  • UTF8 支持
  • FATFS 读取

特点:

上层零管理
低耦合
可扩展
适合嵌入式GUI系统

[ 发起通讯连接 / INITIATE COMM-LINK ]

[SYS]: 您的回传节点(邮箱)将被严格保密。带有 * 的字段为必填项。


> 终止读取并返回主控制台 <