RFZ 字形坐标语义深度分析
目的: 回答 "RFZ 字形的
box_x1/y1/x2/y2到底是图集像素坐标, 还是 PFR 源单元坐标?" 这是 "能否用 bmfont 资产生成全新 RFZ 字体" 的前提 —— 若坐标语义不通, 打出的字体游戏显示必然错位。 结论:box是 DDS 图集中的像素矩形坐标 (经三重证据坐实)。此前rfz_unpack_spec.md §3.4的 "PFR 源单元坐标, 非图集坐标" 说法是错误的, 已在本文档与该规范中更正。
0. 方法论
- 实测
font/RFO_SEGAKAKUGOTHIC_DB_14pt_unpacked/glyphs.csv的 box 数值分布。 - 实测
page0.dds(ARGB4444) 在 box 坐标处的像素墨迹。 - IDA 复核 大四应用.exe 字形加载/渲染链 (imagebase 0x400000)。
1. 数据证据 (glyphs.csv, 14pt, point=10)
连续字形的 box 在同一行内单调右移, box_y 固定:
| code | char | cell_inc_x | origin_x | origin_y | box_x1 | box_y1 | box_x2 | box_y2 |
|---|---|---|---|---|---|---|---|---|
| 33 | ! | 4 | 4 | 5 | 3 | 3 | 5 | 14 |
| 34 | " | 5 | 4 | 5 | 8 | 3 | 12 | 7 |
| 35 | # | 8 | 3 | 5 | 15 | 3 | 23 | 14 |
| 36 | $ | 8 | 3 | 7 | 26 | 3 | 33 | 16 |
| 37 | % | 12 | 4 | 6 | 36 | 3 | 47 | 14 |
实测全表 (21720 字形):
- max(box_x2) = 2047, max(box_y2) = 2037 —— 恰好贴合 tex_w=tex_h=2048。
- 165 次行换行: 当 box_x2 抵达 ~2043 时, 下一字形 box_x1 回到 3 (左边距), box_y1 增加 ~17 (行高+margin)。
例: code 957→958 时 (2036,3,2043,11) -> (3,20,10,32)。
- 1 次翻页: page 字段 0→1, 与 tex_page=2 一致。
这是典型的 图集 shelf-packing 行为。若 box 是字体源单元 (em 相对, 量级 0..point≈10), 绝不会在 2048 处换行、按 page 翻页。
2. 像素证据 (page0.dds, ARGB4444 16bpp)
直接解码 DDS 像素 (alpha = (u16>>12)&0xf), 统计 box 区域墨迹:
| 区域 | 坐标 | 含墨像素/总数 | 判定 |
|---|---|---|---|
# box |
[15,3]-[23,14] | 51 / 88 | 有字形墨迹 ✓ |
! box |
[3,3]-[5,14] | 22 / 22 | 满墨 (细长'!') ✓ |
| 行间隙 | [15,15]-[23,19] | 0 / 32 | 空白 ✓ |
box 矩形内有真实字形像素, 行与行之间的 margin 间隙为纯透明 → box 精确框住预渲染图集中的字形。 这是 box = 图集像素坐标的决定性证据。
3. 代码证据 (大四应用.exe)
3.1 加载链 (已在 IDB 重命名 + 注释)
| 函数 (新名) | 地址 | 作用 |
|---|---|---|
RFZ_Load |
0xF15940 | 校验 YS/v2 → 建 LZW 解码器 → .rfz 解码 → 校验 "RHFONTDB" → 调建表 |
RHFONTDB_BuildCodeIndexTable |
0xF157A0 | 建 table[code-start]=index (this+140/144/146/148) |
BitmapFont_GetGlyphByCode |
0xF00450 | 校验槽, 经虚表 (*(v5+4)+8)(font,code) 查字形 (读 +140 索引表) |
BitmapFont_BuildGlyphVertices |
0xF07900 | 取字形 → 生成顶点四边形 + UV |
3.2 运行时字形对象 (扩展结构, dword 索引)
文件字形 (10×u16) 在加载时被展开为运行时对象, 字段经换算后存于:
| 运行时偏移 | dword idx | 含义 | 来源 |
|---|---|---|---|
| +24 | glyph[6] | page (纹理页) | 文件 page |
| +40 | glyph[10] | origin_x (bearing) | 文件 origin_x |
| +44 | glyph[11] | origin_y (bearing) | 文件 origin_y |
| +48 | glyph[12] | 四边形宽 = box_x2-box_x1 | 文件 box |
| +52 | glyph[13] | 四边形高 = box_y2-box_y1 | 文件 box |
| +60 | glyph[15] | advance = cell_inc_x | 文件 cell_inc_x |
| +92..+120 | glyph[23..30] | 预计算 UV float ×8 (4 角) | box / 2048 |
3.3 渲染逻辑 (BitmapFont_BuildGlyphVertices @ 0xF07900)
- 顶点位置 = 笔位 + origin (bearing):
v53[4]=(glyph[10])+pen_x,v53[5]=(glyph[11])+pen_y(0xf0882f/0xf08853)。 - 四边形尺寸 = box 宽高 (+2*margin):
v18[6]=glyph[12]+2*glyph[9],v18[7]=glyph[13]+2*glyph[9](0xf07e45/0xf07e5d)。 - UV 四角直接拷贝预计算 float:
v18[19..26] = glyph[23..30](0xf07e68–0xf07ea9) —— 即 box 像素坐标 / 2048。 - 笔位前进: 水平
pen_x += cell_inc_x(glyph[15])(0xf07f73), 竖排累加 cell_inc_y。 - page (glyph[6]) 经
sub_43FB93作纹理键, 选择写入哪张 DDS 页 (0xf07fab/0xf07fc1)。
box→UV 的
/2048除法发生在加载时字形展开阶段 (未单独定位该行, 但 UV 已是 box 的归一化结果, 由 +92 预计算 float 与 §1/§2 的图集坐标性质共同确证)。渲染时只读预计算 UV, 不再现算。
4. 字段语义总表 (文件字形 → 语义)
| 文件字段 | 语义 | bmfont .fnt 对应 |
|---|---|---|
| code | 字符码 (Unicode) | char.id |
| cell_inc_x | 水平前进量 (advance) | char.xadvance |
| cell_inc_y | 竖排前进量 (横排=0) | (横排不用) |
| page | DDS 纹理页索引 | char.page |
| origin_x | 相对笔位的水平 bearing | char.xoffset |
| origin_y | 基线向上到字形顶 (上正下负, s16) | base - char.yoffset |
| box_x1 | 图集像素 左 | char.x |
| box_y1 | 图集像素 上 | char.y |
| box_x2 | 图集像素 右 | char.x + char.width |
| box_y2 | 图集像素 下 | char.y + char.height |
| kerning_info_cnt | 字距对数 (本字体恒 0) | (kernings, 通常空) |
四边形宽高 = box_x2-box_x1 / box_y2-box_y1 = bmfont 的 width/height。 origin_x 为水平 bearing (与 bmfont xoffset 同参考系, 直接拷贝)。 origin_y 的基线参考系已实测坐实, 见 §3.4。
3.4 origin_y 参考系 = 基线向上 (已实测, 2026-06-14)
此前 (旧 §4 末) 标注 "origin_y 是否以基线为参考待实测" —— 现以真实
RFO_SEGAKAKUGOTHIC_DB_32pt (point=24, max_ascent=32) 字形数据坐实:
| code | char | origin_y | box 高 | 判定 |
|---|---|---|---|---|
| 65 | A | 18 | 26 | 大写, 顶高于基线 18 |
| 66 | B | 18 | 26 | 同上 |
| 97 | a | 13 | 20 | 小写, 顶 = x 高 13 < 大写 ✓ |
| 103 | g | 13 | 28 | 小写带降部, 顶 13 / 底深入基线下 ✓ |
| 121 | y | 12 | 28 | 同上 |
| 44 | , | -2 | 9 | 逗号, 顶略低于基线 → 负值 |
| 46 | . | -2 | 5 | 句点, 同上 |
结论: origin_y = 字形墨迹顶相对基线的高度 (基线以上为正, 以下为负, s16)。
四边形自该顶点向下延伸 box 高 (降部因此越过基线)。
- 决定性证据: 逗号/句点
origin_y=-2(负)。若 origin_y 是 "行顶向下" (bmfont yoffset 那种, 恒 0..lineHeight), 绝不会出现负值; 负值只能解释为 "基线向上" 参考系下顶点落在基线之下。 - 大写 18 > 小写 13 > 降部字顶 12~13, 与字形学一致 (cap height > x-height)。
对 bmfont→RFZ 的换算 (修正"整行下沉"渲染 bug)
bmfont char.yoffset = "行顶向下到字形顶"; SEGA origin_y = "基线向上到字形顶"。
二者参考系相反, 必须换算:
origin_y = base - yoffset (base = common.base = RFO max_ascent)
- 症状: 早期
bmfont_to_rfz直接origin_y = yoffset, 游戏内整行字下沉约 base 像素 (字被画到原位置下方)。因游戏渲染pos_y = origin_y + pen_y把 origin_y 当基线向上量, 而 yoffset 在该语义下恒过小 → 字形顶贴到基线附近 → 整体下坠。 - 修正:
fnt_to_meta_glyphs改为origin_y = base - yoffset。实测 bmfont 32pt:Ayoffset=1→origin_y=28,ayoffset=9→20,,/.→7 (均基线以上, 符号正确)。 - 与真实 SEGA 同 pt 仍有约 +10px 系统差, 源于字体本身 (SEGAHUMMING size32 vs SEGA point24/ascent32) 而非参考系, 属字号差异; 参考系翻转已消除"整行下沉"主因, 像素级微调需游戏内实测。
5. 对 bmfont→RFZ 管线的影响
坐标语义缺口 (原"缺口 2") 已打通: bmfont 的 char.x/y/width/height 直接映射为 box 矩形,
xadvance/xoffset/yoffset/page 一一对应。bmfont 资产 (SEGAHUMMING_32pt, 2048² ARGB4444)
像素格式与 RFZ 内嵌 DDS 完全一致, 故字形度量 + 纹理像素层面可生成。
仍未解决的两个缺口 (与坐标无关, 属容器/框架):
1. 模板依赖: 现有 rfz_pack.py 整段复制 schema 框架 + 纹理段 (含字体名/页数/svo AVTS 目录/
__HmfToSvo__ 文件名/TextureResource 实例)。bmfont 是 4 页、字体名 SEGA-Humming, 无匹配模板,
换名/换页数需重写这些段 + svo 纹理容器写入器 + hash (见 rfz_pack_spec.md §6/§9)。
2. svo 纹理段按任意页数/字体名重新生成: 当前未实现 (纹理段整段模板复制)。
即: 同字体同页数的"度量+像素替换"已可行; bmfont 全新字体 (SEGAHUMMING 4 页) 还需补 svo 纹理容器写入器与 schema 框架生成。
6. 关键地址速查 (大四应用.exe, imagebase 0x400000)
| 函数 | 地址 | 说明 |
|---|---|---|
| RFZ_Load | 0xF15940 | RFZ 加载主函数 |
| RHFONTDB_BuildCodeIndexTable | 0xF157A0 | code→index 索引表 |
| BitmapFont_GetGlyphByCode | 0xF00450 | 按码查字形 (虚表 +8) |
| BitmapFont_BuildGlyphVertices | 0xF07900 | 顶点/UV 生成; box→UV, origin→位置, cell_inc→advance |