如何用stb_truetype库计算SDF字体的基线垂直对齐?
我刚好之前在项目里处理过一模一样的问题,stb_truetype的坐标系和渲染系统的差异很容易让人踩坑,尤其是SDF的偏移参数和字体度量的结合。下面我把核心逻辑和计算公式拆解清楚,你直接套就行:
先明确核心概念和参数
首先你得搞懂两个关键部分:字体的全局垂直度量,以及stbtt_GetCodepointSDF返回的yoff到底代表什么。
1. 获取字体的全局垂直度量
你需要先调用stbtt_GetFontVMetrics拿到字体的基准线相关数据,这是所有垂直对齐的基础:
int ascent, descent, lineGap; stbtt_GetFontVMetrics(font, &ascent, &descent, &lineGap);
ascent:正数,代表基线到字形最高点的距离(stb坐标系里,y轴向下,所以最高点的y坐标是-ascent)descent:负数,代表基线到字形最低点的距离(最低点的y坐标是-descent,因为descent是负的,所以实际是向下的距离)lineGap:行与行之间的额外间距,用来计算行高(行高 =ascent - descent + lineGap)
2. 理解stbtt_GetCodepointSDF返回的yoff
这个参数最容易搞混:它是在stb的y向下坐标系中,SDF位图顶部相对于基线的偏移量。举个例子:
- 如果字形顶部在基线上方80像素,stb里的y坐标是-80,所以
yoff返回-80 - 如果字形底部在基线下方20像素,位图底部的y坐标是20,那么
height就是20 - (-80) = 100(加上可能的padding)
核心对齐计算公式
关键是根据你的渲染坐标系来转换yoff,因为不同渲染系统的y轴方向可能不一样:
情况1:渲染坐标系是y向下(比如HTML、SDL默认、Windows GDI)
这和stb的坐标系方向一致,直接用下面的公式:
// 位图顶部的y坐标 = 目标基线的y坐标 + stb返回的yoff bitmap_top_y = target_baseline_y + stb_yoff;
比如目标基线y是100,stb_yoff是-80,那么位图顶部y就是20(在基线上方80像素,符合y向下的逻辑)。
情况2:渲染坐标系是y向上(比如OpenGL正交投影、数学坐标系)
这时候y轴方向和stb相反,需要把yoff取反:
// 位图顶部的y坐标 = 目标基线的y坐标 - stb返回的yoff bitmap_top_y = target_baseline_y - stb_yoff;
比如目标基线y是100,stb_yoff是-80,那么位图顶部y就是180(在基线上方80像素,y向上的话数值越大越靠上)。
常见垂直对齐的具体计算
上面的公式是基于基线对齐的,如果你需要其他对齐方式,先算出目标基线的y坐标,再代入上面的公式即可:
顶部对齐
把字体的最高点和容器顶部对齐:
- y向下坐标系:
target_baseline_y = container_top_y + ascent; - y向上坐标系:
target_baseline_y = container_top_y - ascent;
底部对齐
把字体的最低点和容器底部对齐:
- y向下坐标系:
target_baseline_y = container_bottom_y + descent;(descent是负数,相当于减去它的绝对值) - y向上坐标系:
target_baseline_y = container_bottom_y + descent;(同样,descent是负数,相当于加上向下的距离)
垂直居中
让字体在容器中垂直居中:
先算字体的总高度:font_total_height = ascent - descent;(因为descent是负的,减去它等于加绝对值)
再计算基线相对于字体中心的偏移:baseline_offset_from_center = (ascent + descent) / 2.0f;
最后:
- y向下坐标系:
target_baseline_y = container_center_y + baseline_offset_from_center; - y向上坐标系:
target_baseline_y = container_center_y + baseline_offset_from_center;
注意事项
- SDF Padding的影响:如果你调用
stbtt_GetCodepointSDF时传入了padding参数,返回的yoff和height已经包含了padding,不需要额外调整(别裁剪padding,不然SDF渲染会出边缘错误)。 - 字体缩放:如果你用
stbtt_ScaleForPixelHeight缩放了字体,记得把stbtt_GetFontVMetrics返回的度量值乘以缩放因子;而stbtt_GetCodepointSDF如果传入了缩放参数,返回的width, height, xoff, yoff已经是缩放后的像素值,不用再手动乘。
内容的提问来源于stack exchange,提问作者Ildar Kasimov

