跳转至

RFZ 字形坐标语义深度分析

目的: 回答 "RFZ 字形的 box_x1/y1/x2/y2 到底是图集像素坐标, 还是 PFR 源单元坐标?" 这是 "能否用 bmfont 资产生成全新 RFZ 字体" 的前提 —— 若坐标语义不通, 打出的字体游戏显示必然错位。 结论: box 是 DDS 图集中的像素矩形坐标 (经三重证据坐实)。此前 rfz_unpack_spec.md §3.4 的 "PFR 源单元坐标, 非图集坐标" 说法是错误的, 已在本文档与该规范中更正。


0. 方法论

  1. 实测 font/RFO_SEGAKAKUGOTHIC_DB_14pt_unpacked/glyphs.csv 的 box 数值分布。
  2. 实测 page0.dds (ARGB4444) 在 box 坐标处的像素墨迹。
  3. 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: A yoffset=1→origin_y=28, a yoffset=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

评论