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 | 字符对应的字形 |
| Codepoint | Unicode 编码 |
| Font Instance | 某字体的某一字号 |
| Bitmap | 字形像素数据 |
| Page | 256 个 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 位于文件起始位置。
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 最大值:
0x10FFFF将 Unicode 按 256 字符一页划分。
计算方法:
page_id = unicode >> 8
index = unicode & 0xFF示例:
Unicode: 0x4E2D
page_id = 0x4E
index = 0x2DPage 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 bytesPage Table
每个 Page Table 包含 256 个字符入口。
结构:
uint16_t glyph_index[256]含义:
glyph_index[index] = glyph_id若字符不存在:
glyph_index[index] = 0xFFFFPage Table 大小:
256 × 2 = 512 bytesGlyph Offset Table
Glyph Offset Table 用于定位字形数据。
结构:
uint32_t glyph_offset[glyph_count]含义:
glyph_data_address =
glyph_data_base + glyph_offset[glyph_id]支持:
- 变长 bitmap
- 字模压缩
- 可扩展数据
Glyph Data
Glyph Data 区顺序存储所有字形数据。
glyph0
glyph1
glyph2
...每个 glyph 数据块由:
GlyphMeta + Bitmap组成。
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 | 渲染后光标移动距离 |
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字符查找算法
输入:
unicode查找流程:
Step1
page_id = unicode >> 8Step2
page_offset = page_dir[page_id]若:
page_offset == 0xFFFFFFFF使用:
missing_glyphStep3
index = unicode & 0xFF
glyph_id = page_table[index]若:
glyph_id == 0xFFFF使用:
missing_glyphStep4
glyph_offset =
glyph_offset_table[glyph_id]Step5
读取 GlyphMeta:
glyph_address =
glyph_data_base + glyph_offsetStep6
计算 bitmap 大小并读取 bitmap。
渲染算法
假设当前光标:
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 += advanceMissing Glyph
若字符不存在:
glyph_id = missing_glyph常见 glyph:
□
?Kerning
EUBF 不支持 Kerning。
原因:
- Kerning 表巨大
- MCU 查找复杂
嵌入式系统通常仅使用:
advance进行排版。
空间估算
示例:
字符数 = 3000
平均包围盒 = 16×16
bpp = 1Bitmap:
bytes_per_row = 2
bitmap = 32 bytes单 glyph:
meta = 8
bitmap = 32
total = 40 bytesGlyph Data:
3000 × 40 = 120 KBOffset Table:
3000 × 4 = 12 KBPage Tables:
40 × 512 = 20 KBDirectory:
17 KB总计:
≈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>/里。
生成完毕后,可运行检查脚本来检查字库是否正确。
打包脚本
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)检查脚本
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)

发表回复