Embedded Unicode Bitmap Font Format (EUBF)

28,938字
122–184 分钟

Version 1.2

本规范定义了一种适用于嵌入式系统的 Unicode 位图字库文件格式。
该格式设计用于在 MCU + 外部存储(SD / USB / SPI Flash)环境中高效显示多语言文本。

典型应用场景:

  • 嵌入式 GUI
  • LCD / OLED / E-Paper 显示
  • 外部存储字体
  • 多语言 Unicode UI

设计目标:

  • Unicode 全兼容
  • O(1) 字符查找
  • 支持多字体、多字号
  • 支持比例字体(Proportional Font)
  • 支持基线排版(Baseline Layout)
  • MCU 端实现简单
  • 支持未来扩展

术语

术语说明
Glyph字符对应的字形
CodepointUnicode 编码
Font Instance某字体的某一字号
Bitmap字形像素数据
Page256 个 Unicode 字符组成的页
Bounding Box包围字符像素的最小矩形
Advance字符渲染后光标移动距离
Baseline文本排版基准线

文件结构

EUBF 文件由以下部分组成:


+---------------------------+
| Font Header               |
+---------------------------+
| Unicode Page Directory    |
+---------------------------+
| Unicode Page Tables       |
+---------------------------+
| Glyph Offset Table        |
+---------------------------+
| Glyph Data                |
| (Meta + Bitmap)           |
+---------------------------+

文件基本属性

属性说明
编码Unicode
字形Bitmap
字形排列Row-major
位序MSB-first
字节序Little Endian
查找复杂度O(1)

Font Header

Font Header 位于文件起始位置。

C
struct FontHeader
{
    char magic[4];          // "EUBF"

    uint16_t version;       // 版本号 (0x0102 = 1.2)

    char font_name[32];     // 字体名称

    uint16_t font_size;     // 字号 (pixel)

    uint16_t ascent;        // 基线以上最大高度
    uint16_t descent;       // 基线以下最大高度

    uint16_t line_height;   // 推荐行高

    uint16_t max_advance;   // 最大字符步进宽

    uint8_t  bpp;           // bits per pixel (1 / 4 / 8)

    uint16_t max_width;     // 最大包围盒宽
    uint16_t max_height;    // 最大包围盒高

    uint32_t glyph_count;   // 字形总数

    uint16_t missing_glyph; // 缺失字符 glyph id

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

    char source_ttf[64];    // 原始 TTF 文件名

    uint32_t ttf_crc;       // TTF 校验值
};

Unicode Page 机制

Unicode 最大值:

C
0x10FFFF

将 Unicode 按 256 字符一页划分。

计算方法:

C
page_id = unicode >> 8
index   = unicode & 0xFF

示例:

C
Unicode: 0x4E2D

page_id = 0x4E
index   = 0x2D

Page Directory

Page Directory 为固定长度数组。

Unicode 总页数:

C
0x110000 / 256 = 4352

结构:

C
uint32_t page_dir[4352]

含义:

C
page_dir[page_id] = page_table_offset

若该页不存在:

C
page_dir[page_id] = 0xFFFFFFFF

空间占用:

C
4352 × 4 = 17408 bytes

Page Table

每个 Page Table 包含 256 个字符入口。

结构:

C
uint16_t glyph_index[256]

含义:

C
glyph_index[index] = glyph_id

若字符不存在:

C
glyph_index[index] = 0xFFFF

Page Table 大小:

C
256 × 2 = 512 bytes

Glyph Offset Table

Glyph Offset Table 用于定位字形数据。

结构:

C
uint32_t glyph_offset[glyph_count]

含义:

C
glyph_data_address =
glyph_data_base + glyph_offset[glyph_id]

支持:

  • 变长 bitmap
  • 字模压缩
  • 可扩展数据

Glyph Data

Glyph Data 区顺序存储所有字形数据。

C
glyph0
glyph1
glyph2
...

每个 glyph 数据块由:

C
GlyphMeta + Bitmap

组成。


GlyphMeta

每个字符包含一个字形元数据头。

结构:

C
struct GlyphMeta
{
    uint8_t  box_width;     // 包围盒宽
    uint8_t  box_height;    // 包围盒高

    int8_t   x_offset;      // 绘制 X 偏移
    int8_t   y_offset;      // 绘制 Y 偏移

    uint16_t advance;       // 字符步进宽

    uint16_t reserved;      // 保留 (对齐)
};

结构大小:

C
8 bytes

说明:

字段说明
box_width字形宽
box_height字形高
x_offset相对光标水平偏移
y_offset相对基线垂直偏移
advance渲染后光标移动距离

Bitmap 数据

Bitmap 紧跟在 GlyphMeta 后。

计算方式:

C
bytes_per_row = ceil(box_width × bpp / 8)

bitmap_size =
bytes_per_row × box_height

排列规则:

  • row-major
  • MSB-first
  • 每行字节对齐

示例(1bpp):

C
bit7 = left pixel
bit0 = right pixel

字符查找算法

输入:

C
unicode

查找流程:

Step1

C
page_id = unicode >> 8

Step2

C
page_offset = page_dir[page_id]

若:

C
page_offset == 0xFFFFFFFF

使用:

C
missing_glyph

Step3

C
index = unicode & 0xFF
glyph_id = page_table[index]

若:

C
glyph_id == 0xFFFF

使用:

C
missing_glyph

Step4

C
glyph_offset =
glyph_offset_table[glyph_id]

Step5

读取 GlyphMeta:

C
glyph_address =
glyph_data_base + glyph_offset

Step6

计算 bitmap 大小并读取 bitmap。


渲染算法

假设当前光标:

C
cursor_x
cursor_y

其中:

C
cursor_y = baseline

绘制位置:

C
draw_x = cursor_x + x_offset
draw_y = cursor_y + y_offset

绘制:

C
draw_bitmap(
    draw_x,
    draw_y,
    box_width,
    box_height
)

更新光标:

C
cursor_x += advance

Missing Glyph

若字符不存在:

C
glyph_id = missing_glyph

常见 glyph:

C

?

Kerning

EUBF 不支持 Kerning。

原因:

  • Kerning 表巨大
  • MCU 查找复杂

嵌入式系统通常仅使用:

C
advance

进行排版。


空间估算

示例:

C
字符数 = 3000
平均包围盒 = 16×16
bpp = 1

Bitmap:

C
bytes_per_row = 2
bitmap = 32 bytes

单 glyph:

C
meta = 8
bitmap = 32
total = 40 bytes

Glyph Data:

C
3000 × 40 = 120 KB

Offset Table:

C
3000 × 4 = 12 KB

Page Tables:

C
40 × 512 = 20 KB

Directory:

C
17 KB

总计:

C
169 KB

字库生成流程

推荐生成流程:

TTF
 ↓
Python Script
 ↓
FreeType
 ↓
Glyph Render
 ↓
Unicode Page Builder
 ↓
EUBF Writer

步骤:

1 扫描字符集
2 FreeType 渲染 glyph
3 计算 GlyphMeta
4 构建 Page Table
5 写入文件结构


字体组织方式

推荐目录结构:

fonts/

    Roboto/
        12.eubf
        16.eubf
        24.eubf

    NotoSansCJK/
        16.eubf
        24.eubf

每个文件:

一个字体
一个字号

扩展能力

EUBF 可扩展支持:

  • 字模压缩
  • Glyph Cache
  • 多字体 fallback
  • 彩色字体

通过保留字段实现。


总结

EUBF 字库格式特点:

特性说明
Unicode完整支持
查找速度O(1)
比例字体支持
基线排版支持
实现复杂度
外部存储友好

该格式适用于需要 稳定、高效、多语言字体渲染 的嵌入式系统。

建议

  • 在MCU上使用EUBF字库时,建议使用缓存机制,避免频繁访问外部存储。即建立最近最少使用(LRU)缓存,将常用 glyph 缓存到内存中。缓存区大小视该芯片内存大小而定,建议足够存储100个glyph。

实现

将要打包的字体放在同目录下的needfont文件夹里,然后运行打包脚本即可。

您还可以传入 -whitelist 参数来读取whitelist.txt里的字符来按需生成。

生成的字库放在同目录下的asset/font/<fontname>/里。

生成完毕后,可运行检查脚本来检查字库是否正确。

打包脚本
Python
Version 1.2
本规范定义了一种适用于嵌入式系统的 Unicode 位图字库文件格式。
该格式设计用于在 MCU + 外部存储(SD / USB / SPI Flash)环境中高效显示多语言文本。
典型应用场景:
● 嵌入式 GUI
● LCD / OLED / E-Paper 显示
● 外部存储字体
● 多语言 Unicode UI
设计目标:
● Unicode 全兼容
● O(1) 字符查找
● 支持多字体、多字号
● 支持比例字体(Proportional Font)
● 支持基线排版(Baseline Layout)
● MCU 端实现简单
● 支持未来扩展

1 术语
术语	说明
Glyph	字符对应的字形
Codepoint	Unicode 编码
Font Instance	某字体的某一字号
Bitmap	字形像素数据
Page	256 个 Unicode 字符组成的页
Bounding Box	包围字符像素的最小矩形
Advance	字符渲染后光标移动距离
Baseline	文本排版基准线

2 文件结构
EUBF 文件由以下部分组成:

+---------------------------+
| Font Header               |
+---------------------------+
| Unicode Page Directory    |
+---------------------------+
| Unicode Page Tables       |
+---------------------------+
| Glyph Offset Table        |
+---------------------------+
| Glyph Data                |
| (Meta + Bitmap)           |
+---------------------------+


3 文件基本属性
属性	说明
编码	Unicode
字形	Bitmap
字形排列	Row-major
位序	MSB-first
字节序	Little Endian
查找复杂度	O(1)

4 Font Header
Font Header 位于文件起始位置。
struct FontHeader
{
    char magic[4];          // "EUBF"

    uint16_t version;       // 版本号 (0x0102 = 1.2)

    char font_name[32];     // 字体名称

    uint16_t font_size;     // 字号 (pixel)

    uint16_t ascent;        // 基线以上最大高度
    uint16_t descent;       // 基线以下最大高度

    uint16_t line_height;   // 推荐行高

    uint16_t max_advance;   // 最大字符步进宽

    uint8_t  bpp;           // bits per pixel (1 / 4 / 8)

    uint16_t max_width;     // 最大包围盒宽
    uint16_t max_height;    // 最大包围盒高

    uint32_t glyph_count;   // 字形总数

    uint16_t missing_glyph; // 缺失字符 glyph id

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

    char source_ttf[64];    // 原始 TTF 文件名

    uint32_t ttf_crc;       // TTF 校验值
};

5 Unicode Page 机制
Unicode 最大值:
0x10FFFF
将 Unicode 按 256 字符一页划分。
计算方法:
page_id = unicode >> 8
index   = unicode & 0xFF
示例:
Unicode: 0x4E2D

page_id = 0x4E
index   = 0x2D

6 Page Directory
Page Directory 为固定长度数组。
Unicode 总页数:
0x110000 / 256 = 4352
结构:
uint32_t page_dir[4352]
含义:
page_dir[page_id] = page_table_offset
若该页不存在:
page_dir[page_id] = 0xFFFFFFFF
空间占用:
4352 × 4 = 17408 bytes

7 Page Table
每个 Page Table 包含 256 个字符入口。
结构:
uint16_t glyph_index[256]
含义:
glyph_index[index] = glyph_id
若字符不存在:
glyph_index[index] = 0xFFFF
Page Table 大小:
256 × 2 = 512 bytes

8 Glyph Offset Table
Glyph Offset Table 用于定位字形数据。
结构:
uint32_t glyph_offset[glyph_count]
含义:
glyph_data_address =
glyph_data_base + glyph_offset[glyph_id]
支持:
● 变长 bitmap
● 字模压缩
● 可扩展数据

9 Glyph Data
Glyph Data 区顺序存储所有字形数据。
glyph0
glyph1
glyph2
...
每个 glyph 数据块由:
GlyphMeta + Bitmap
组成。

10 GlyphMeta
每个字符包含一个字形元数据头。
结构:
struct GlyphMeta
{
    uint8_t  box_width;     // 包围盒宽
    uint8_t  box_height;    // 包围盒高

    int8_t   x_offset;      // 绘制 X 偏移
    int8_t   y_offset;      // 绘制 Y 偏移

    uint16_t advance;       // 字符步进宽

    uint16_t reserved;      // 保留 (对齐)
};
结构大小:
8 bytes
说明:
字段	说明
box_width	字形宽
box_height	字形高
x_offset	相对光标水平偏移
y_offset	相对基线垂直偏移
advance	渲染后光标移动距离

11 Bitmap 数据
Bitmap 紧跟在 GlyphMeta 后。
计算方式:
bytes_per_row = ceil(box_width × bpp / 8)

bitmap_size =
bytes_per_row × box_height
排列规则:
● row-major
● MSB-first
● 每行字节对齐
示例(1bpp):
bit7 = left pixel
bit0 = right pixel

12 字符查找算法
输入:
unicode
查找流程:
Step1
page_id = unicode >> 8

Step2
page_offset = page_dir[page_id]
若:
page_offset == 0xFFFFFFFF
使用:
missing_glyph

Step3
index = unicode & 0xFF
glyph_id = page_table[index]
若:
glyph_id == 0xFFFF
使用:
missing_glyph

Step4
glyph_offset =
glyph_offset_table[glyph_id]

Step5
读取 GlyphMeta:
glyph_address =
glyph_data_base + glyph_offset

Step6
计算 bitmap 大小并读取 bitmap。

13 渲染算法
假设当前光标:
cursor_x
cursor_y
其中:
cursor_y = baseline
绘制位置:
draw_x = cursor_x + x_offset
draw_y = cursor_y + y_offset
绘制:
draw_bitmap(
    draw_x,
    draw_y,
    box_width,
    box_height
)
更新光标:
cursor_x += advance

14 Missing Glyph
若字符不存在:
glyph_id = missing_glyph
常见 glyph:

?

15 Kerning
EUBF 不支持 Kerning。
原因:
● Kerning 表巨大
● MCU 查找复杂
嵌入式系统通常仅使用:
advance
进行排版。

16 空间估算
示例:
字符数 = 3000
平均包围盒 = 16×16
bpp = 1
Bitmap:
bytes_per_row = 2
bitmap = 32 bytes
单 glyph:
meta = 8
bitmap = 32
total = 40 bytes
Glyph Data:
3000 × 40 = 120 KB
Offset Table:
3000 × 4 = 12 KB
Page Tables:
40 × 512 = 20 KB
Directory:
17 KB
总计:
169 KB

17 字库生成流程
推荐生成流程:
TTF

Python Script

FreeType

Glyph Render

Unicode Page Builder

EUBF Writer
步骤:
1 扫描字符集
2 FreeType 渲染 glyph
3 计算 GlyphMeta
4 构建 Page Table
5 写入文件结构

18 字体组织方式
推荐目录结构:
fonts/

    Roboto/
        12.eubf
        16.eubf
        24.eubf

    NotoSansCJK/
        16.eubf
        24.eubf
每个文件:
一个字体
一个字号

19 扩展能力
EUBF 可扩展支持:
● 字模压缩
● Glyph Cache
● 多字体 fallback
● 彩色字体
通过保留字段实现。

20 总结
EUBF 字库格式特点:
特性	说明
Unicode	完整支持
查找速度	O(1)
比例字体	支持
基线排版	支持
实现复杂度	低
外部存储	友好
该格式适用于需要 稳定、高效、多语言字体渲染 的嵌入式系统。
21 建议
● 在MCU上使用EUBF字库时,建议使用缓存机制,避免频繁访问外部存储。即建立最近最少使用(LRU)缓存,将常用 glyph 缓存到内存中。缓存区大小视该芯片内存大小而定,建议足够存储100个glyph。
22 实现
将要打包的字体放在同目录下的needfont文件夹里,然后运行打包脚本即可。
您还可以传入 -whitelist 参数来读取whitelist.txt里的字符来按需生成。
生成的字库放在同目录下的asset/font/<fontname>/里。
生成完毕后,可运行检查脚本来检查字库是否正确。
import os
import sys
import struct
import zlib
import freetype
import glob
import tempfile
import shutil
import uuid
import re

# ================= 全局配置 =================
TARGET_SIZES = [12, 16, 19, 25, 32]
BPP = 4
HEADER_SIZE = 146  # 严格匹配 C 语言 #pragma pack(1)


# ============================================

def get_whitelist_chars():
    whitelist_path = "whitelist.txt"
    chars = set()
    if os.path.exists(whitelist_path):
        with open(whitelist_path, 'r', encoding='utf-8') as f:
            chars = set(f.read())
        print(f"已加载 whitelist.txt,共包含 {len(chars)} 个字符。")
    else:
        print("警告: 未找到 whitelist.txt,将仅生成基础字符。")
    chars.add(' ')
    chars.add('?')
    return chars


def pack_4bpp_bitmap(bitmap, box_w, box_h):
    pitch = bitmap.pitch
    buffer = bitmap.buffer
    bytes_per_row = (box_w + 1) // 2
    packed_data = bytearray()
    for y in range(box_h):
        row_data = buffer[y * pitch: y * pitch + box_w]
        packed_row = bytearray(bytes_per_row)
        for x in range(box_w):
            val = row_data[x] >> 4
            if x % 2 == 0:
                packed_row[x // 2] |= (val << 4)
            else:
                packed_row[x // 2] |= val
        packed_data.extend(packed_row)
    return packed_data


def build_eubf_for_size(face, ttf_path, font_name, size, valid_chars, output_path):
    face.set_pixel_sizes(size, size)
    ascent, descent = face.size.ascender // 64, abs(face.size.descender // 64)
    line_height, max_advance = face.size.height // 64, face.size.max_advance // 64

    glyph_data_block, glyph_offsets, page_tables = bytearray(), [], {}
    max_w, max_h, missing_glyph_id = 0, 0, 0

    print(f"  正在渲染 {size}px 字号...")

    for glyph_id, charcode in enumerate(valid_chars):
        if charcode == ord('?'): missing_glyph_id = glyph_id
        page_id, index = (charcode >> 8) & 0xFF, charcode & 0xFF
        if page_id not in page_tables: page_tables[page_id] = {}
        page_tables[page_id][index] = glyph_id

        face.load_char(charcode, freetype.FT_LOAD_RENDER | freetype.FT_LOAD_TARGET_NORMAL)
        bitmap = face.glyph.bitmap
        w, h = bitmap.width, bitmap.rows
        max_w, max_h = max(max_w, w), max(max_h, h)

        glyph_offsets.append(len(glyph_data_block))
        meta = struct.pack('<BBbbHH', w, h, face.glyph.bitmap_left, -face.glyph.bitmap_top, face.glyph.advance.x // 64,
                           0)
        glyph_data_block.extend(meta)
        if w > 0 and h > 0:
            glyph_data_block.extend(pack_4bpp_bitmap(bitmap, w, h))

    # 构建索引
    page_dir_offset = HEADER_SIZE
    page_dir_data = bytearray([0xFF] * (256 * 4))
    page_table_offset = page_dir_offset + 1024
    page_tables_data, current_table_ptr = bytearray(), page_table_offset

    for page_id in range(256):
        if page_id in page_tables:
            struct.pack_into('<I', page_dir_data, page_id * 4, current_table_ptr)
            table = bytearray([0xFF] * 512)
            for index, gid in page_tables[page_id].items():
                struct.pack_into('<H', table, index * 2, gid)
            page_tables_data.extend(table)
            current_table_ptr += 512

    glyph_offset_offset = current_table_ptr
    glyph_offset_data = bytearray()
    for offset in glyph_offsets: glyph_offset_data.extend(struct.pack('<I', offset))
    glyph_data_offset = glyph_offset_offset + len(glyph_offset_data)

    # 计算源文件 CRC
    with open(ttf_path, 'rb') as f:
        ttf_crc = zlib.crc32(f.read()) & 0xFFFFFFFF

    name_bytes = font_name.encode('utf-8')[:32]
    ttf_filename = os.path.basename(ttf_path).encode('utf-8')[:64]

    header = struct.pack('<4sH32sHHHHHBxHH I H H I I I I 64s I',
                        b'EUBF', 0x0102, name_bytes, size, ascent, descent, line_height, max_advance, BPP,
                        max_w, max_h, len(valid_chars), missing_glyph_id, 0,
                        page_dir_offset, page_table_offset, glyph_offset_offset, glyph_data_offset,
                        ttf_filename, ttf_crc)

    with open(output_path, 'wb') as f:
        for b in [header, page_dir_data, page_tables_data, glyph_offset_data, glyph_data_block]: f.write(b)
    print(f"    -> 生成完毕: {output_path}")


def main():
    input_dir = "needfont"
    mode = "full" if len(sys.argv) < 2 else sys.argv[1]
    if not os.path.exists(input_dir):
        os.makedirs(input_dir)
        print(f"提示: 已创建 '{input_dir}' 文件夹,请放入字体后再运行。")
        return

    font_files = []
    for ext in ('*.ttf', '*.otf', '*.TTF', '*.OTF'):
        font_files.extend(glob.glob(os.path.join(input_dir, ext)))

    if not font_files:
        print(f"错误: '{input_dir}' 内无字体文件。")
        return

    target_chars = get_whitelist_chars() if mode == "whitelist" else set()
    temp_dir = tempfile.gettempdir()

    for ttf_path in font_files:
        # --- 核心修复:将中文路径文件复制到纯英文临时路径 ---
        _, ext = os.path.splitext(ttf_path)
        temp_ttf = os.path.join(temp_dir, f"eubf_tmp_{uuid.uuid4().hex}{ext}")

        try:
            shutil.copy2(ttf_path, temp_ttf)
            face = freetype.Face(temp_ttf)

            # 提取名称,防止 Windows 路径非法字符
            raw_name = face.family_name.decode('utf-8', errors='ignore') if face.family_name else ""
            font_name = re.sub(r'[\\/*?:"<>|]', '', raw_name).strip() or "font"
            output_dir = os.path.join("asset", "font", font_name)
            os.makedirs(output_dir, exist_ok=True)

            print(f"处理字体: {font_name}...")

            valid_chars = []
            if mode == "whitelist":
                for char in target_chars:
                    if face.get_char_index(ord(char)): valid_chars.append(ord(char))
            else:
                charcode, gindex = face.get_first_char()
                while gindex:
                    valid_chars.append(charcode);
                    charcode, gindex = face.get_next_char(charcode, gindex)

            valid_chars = sorted(list(set(valid_chars)))
            for size in TARGET_SIZES:
                build_eubf_for_size(face, ttf_path, font_name, size, valid_chars,
                                    os.path.join(output_dir, f"{size}.eubf"))

            del face  # 必须先释放句柄才能删除临时文件
        except Exception as e:
            print(f"失败 {ttf_path}: {e}")
        finally:
            if os.path.exists(temp_ttf): os.remove(temp_ttf)

    print("\n?? 全部打包完成!")


if __name__ == "__main__":
    main()
import struct
import os

# ================= 配置区 =================
FILE_PATH = "asset\\fount\\myfont\\16.eubf"


# =========================================

def analyze_eubf_robust(path):
    if not os.path.exists(path):
        print(f"错误: 找不到文件 {path}")
        return

    file_size = os.path.getsize(path)
    print(f"--- 文件系统检查 ---")
    print(f"文件路径: {path}")
    print(f"实际文件大小: {file_size} 字节")

    with open(path, 'rb') as f:
        data = f.read()

    # 1. 解析 Header (146 字节)
    try:
        header_fmt = '<4sH32sHHHHHBxHH I H H I I I I 64s I'
        header = struct.unpack('<4sH32sHHHHHBxHH I H H I I I I 64s I', data[:146])
    except Exception as e:
        print(f"Header 解析失败: {e}")
        return

    magic = header[0].decode('ascii', errors='ignore')
    font_name = header[2].split(b'\x00')[0].decode('utf-8', errors='ignore')
    glyph_count = header[12]
    pg_dir_off = header[14]

    print(f"\n--- Header 信息 ---")
    print(f"Magic: {magic} | 字体: {font_name} | 总字数: {glyph_count}")
    print(f"Page Directory 偏移: {pg_dir_off}")

    # 2. 扫描索引
    if pg_dir_off + 1024 > file_size:
        print(f"致命错误: Page Directory 偏移量已超出文件范围!")
        return

    page_dir = struct.unpack('<256I', data[pg_dir_off: pg_dir_off + 1024])

    found_pages = 0
    for page_id, page_offset in enumerate(page_dir):
        if page_offset != 0xFFFFFFFF:
            found_pages += 1
            # 【关键检查】:检查偏移量是否越界
            if page_offset + 512 > file_size:
                print(f"警告: Page {page_id} 偏移量(0x{page_offset:X})指向文件外部! (文件仅 {file_size} 字节)")
                continue

            page_table = struct.unpack('<256H', data[page_offset: page_offset + 512])
            for char_idx, glyph_id in enumerate(page_table):
                if glyph_id != 0xFFFF:
                    uni = (page_id << 8) | char_idx
                    print(f"发现字符: U+{uni:04X} ({chr(uni) if uni < 0xFFFF else '?'}) -> GID: {glyph_id}")

    if found_pages == 0:
        print("提示: 索引表为空,文件中可能没有定义任何字符。")


if __name__ == "__main__":
    analyze_eubf_robust(FILE_PATH)
检查脚本
Python
import struct
import os

# ================= 配置区 =================
FILE_PATH = "asset\\fount\\myfont\\16.eubf"


# =========================================

def analyze_eubf_robust(path):
    if not os.path.exists(path):
        print(f"错误: 找不到文件 {path}")
        return

    file_size = os.path.getsize(path)
    print(f"--- 文件系统检查 ---")
    print(f"文件路径: {path}")
    print(f"实际文件大小: {file_size} 字节")

    with open(path, 'rb') as f:
        data = f.read()

    # 1. 解析 Header (146 字节)
    try:
        header_fmt = '<4sH32sHHHHHBxHH I H H I I I I 64s I'
        header = struct.unpack('<4sH32sHHHHHBxHH I H H I I I I 64s I', data[:146])
    except Exception as e:
        print(f"Header 解析失败: {e}")
        return

    magic = header[0].decode('ascii', errors='ignore')
    font_name = header[2].split(b'\x00')[0].decode('utf-8', errors='ignore')
    glyph_count = header[12]
    pg_dir_off = header[14]

    print(f"\n--- Header 信息 ---")
    print(f"Magic: {magic} | 字体: {font_name} | 总字数: {glyph_count}")
    print(f"Page Directory 偏移: {pg_dir_off}")

    # 2. 扫描索引
    if pg_dir_off + 1024 > file_size:
        print(f"致命错误: Page Directory 偏移量已超出文件范围!")
        return

    page_dir = struct.unpack('<256I', data[pg_dir_off: pg_dir_off + 1024])

    found_pages = 0
    for page_id, page_offset in enumerate(page_dir):
        if page_offset != 0xFFFFFFFF:
            found_pages += 1
            # 【关键检查】:检查偏移量是否越界
            if page_offset + 512 > file_size:
                print(f"警告: Page {page_id} 偏移量(0x{page_offset:X})指向文件外部! (文件仅 {file_size} 字节)")
                continue

            page_table = struct.unpack('<256H', data[page_offset: page_offset + 512])
            for char_idx, glyph_id in enumerate(page_table):
                if glyph_id != 0xFFFF:
                    uni = (page_id << 8) | char_idx
                    print(f"发现字符: U+{uni:04X} ({chr(uni) if uni < 0xFFFF else '?'}) -> GID: {glyph_id}")

    if found_pages == 0:
        print("提示: 索引表为空,文件中可能没有定义任何字符。")


if __name__ == "__main__":
    analyze_eubf_robust(FILE_PATH)

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注


目录