Linux 应用层开发入门(十八)| freetype显示一行汉字
·

在嵌入式Linux图形应用开发中,字体显示是一个绕不开的话题。本文基于FreeType字体引擎,详细讲解如何在LCD屏幕的指定位置显示一行汉字字符串,并重点解析字符基线、坐标系转换以及字符串外框计算等关键问题。
1 LCD 坐标系与 FreeType 坐标系的差异
1.1 LCD 使用的是“屏幕坐标系”
- 原点
(0,0):左上角 - X轴:向右
- Y轴:向下
1.2 FreeType使用的是“笛卡尔坐标系”
- 原点
(0,0):左下角 - X轴:向右
- Y轴:向上
假设LCD垂直分辨率为V,两种坐标系之间的转换关系为:
LCD坐标(x,y) <---> 笛卡尔坐标(x,V-y)

👉 在使用 FreeType 显示字体前,必须完成坐标系转换,否则显示位置一定是错的。
2 为什么“字体大小”不是绝对值?
在FreeType中,我们通常会这样设置字体大小:
FT_Set_Pixel_Sizes(face, 0, font_size);
⚠️ 注意:font_size只是一个“期望高度”,而不是强制像素高度。
例如字符串:
小程同学https://gitcode.com/xiaocheng/gitcode_knowledge
- 汉字
- 英文字母
- 数字
- 符号(
.)
如果所有字符都强制等高等宽,字形比例会被破坏,显示效果会非常难看。
👉 每个字符的实际宽度、高度、上下延展范围都是不同的。
3 FreeType 中最重要的三个概念
FreeType在绘制一行文字时,引入了几个非常重要的概念:
3.1 基线(Baseline)
- 基线是字符排版的参考线,所有字符都基于同一条基线绘制
- 字符可以:
- 向上超出基线(如大写字母)
- 向下超出基线(如
g、y)
3.2 原点(Origin / Pen)
FreeType使用一个“笔尖(pen)”来决定字符的位置:
pen_next = pen_current + advance
- 每个字符都有一个advance
- 用于决定下一个字符的原点位置
- advance的单位为1/64像素
2.3 字符外框(Bounding Box)
每个字符都有一个独立的外框:
xMin, xMax, yMin, yMax
⚠️ 关键点:
- 外框 不一定以原点为中心
- 不同字符的外框大小完全不同
4 FreeType 中的几个关键数据结构
4.1 FT_Library(FreeType 库句柄)
FT_Library library;
FT_Init_FreeType(&library);
4.2 FT_Face(字体文件)
一个FT_Face对应一个字体文件:
FT_New_Face(library, font_file, 0, &face);
4.3 FT_GlyphSlot(插槽)
FT_GlyphSlot slot = face->glyph;
- 保存字符的位图、度量信息
- 每次加载字符都会覆盖它
4.4 FT_Glyph 与 FT_BBox
FT_Get_Glyph(slot, &glyph);
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &bbox);
FT_Glyph:字符的几何描述FT_BBox:字符的外接矩形(边界框)
5 如何计算“一整行文字”的外框?
5.1 核心思想
- 每个字符都有自己的外框
- 一行文字的外框 = 所有字符外框的并集
5.2 实现步骤
- pen 从
(0, 0)开始(基线) - 对每个字符:
- 设置变换(平移到 pen)
- 加载字符
- 获取字符外框
- 更新整行的最小 / 最大边界
- 根据 advance 移动 pen
5.3 关键代码
/**
* @brief 计算一行字符串在 FreeType 坐标系中的整体外接矩形
* @param face FreeType 字体对象
* @param wstr 宽字符字符串(Unicode)
* @param abbox 返回的一行文字整体外框
* @return 0 成功,-1 失败
*/
int compute_string_bbox(FT_Face face, wchar_t *wstr, FT_BBox *abbox)
{
int i;
int error;
/* 整行文字的外框 */
FT_BBox bbox;
/* 单个字符的外框 */
FT_BBox glyph_bbox;
/* pen:字符原点(基线上的位置),单位是 1/64 像素 */
FT_Vector pen;
/* glyph:用于获取字符的几何信息 */
FT_Glyph glyph;
/* glyph slot:字符位图和度量信息都保存在这里 */
FT_GlyphSlot slot = face->glyph;
/* 初始化整体外框
* 使用一个很大的正负值,方便后续取 min / max
*/
bbox.xMin = bbox.yMin = 32000;
bbox.xMax = bbox.yMax = -32000;
/* 指定第一个字符的原点为 (0, 0) */
pen.x = 0;
pen.y = 0;
/* 依次处理字符串中的每一个字符 */
for (i = 0; i < wcslen(wstr); i++)
{
/* 设置字体变换矩阵
* 这里只使用平移(pen),不做旋转和缩放
*/
FT_Set_Transform(face, 0, &pen);
/* 加载字符并生成位图
* 注意:每次都会覆盖 face->glyph 中的内容
*/
error = FT_Load_Char(face, wstr[i], FT_LOAD_RENDER);
if (error)
{
printf("FT_Load_Char error\n");
return -1;
}
/* 从 glyph slot 中取出 glyph
* glyph 保存了字符的几何轮廓信息
*/
error = FT_Get_Glyph(face->glyph, &glyph);
if (error)
{
printf("FT_Get_Glyph error\n");
return -1;
}
/* 获取该字符的外接矩形(bounding box)
* 得到的是字符相对于当前 pen 的外框
*/
FT_Glyph_Get_CBox(glyph,
FT_GLYPH_BBOX_TRUNCATE,
&glyph_bbox);
/* 更新整行文字的外框 */
if (glyph_bbox.xMin < bbox.xMin)
bbox.xMin = glyph_bbox.xMin;
if (glyph_bbox.yMin < bbox.yMin)
bbox.yMin = glyph_bbox.yMin;
if (glyph_bbox.xMax > bbox.xMax)
bbox.xMax = glyph_bbox.xMax;
if (glyph_bbox.yMax > bbox.yMax)
bbox.yMax = glyph_bbox.yMax;
/* 根据 advance 移动到下一个字符的原点
* advance 的单位同样是 1/64 像素
*/
pen.x += slot->advance.x;
pen.y += slot->advance.y;
}
/* 返回整行字符串的外框 */
*abbox = bbox;
return 0;
}
6 在指定 (x,y) 位置显示一行文字
6.1 思路总结
- 先计算整行文字的外框
- 已知:pen=(0,0)时,外框左上角是(x', y')
- 若要外框左上角对齐到(
x,y):
pen.x = x - x'
pen.y = y - y'
⚠️ 注意:FreeType中pen的单位是 1/64 像素
6.2 显示函数核心代码
/**
* @brief 在 LCD 的指定位置显示一行文字
* @param face FreeType 字体对象
* @param wstr 宽字符字符串(Unicode)
* @param lcd_x LCD 坐标系中的 X 坐标(左上角)
* @param lcd_y LCD 坐标系中的 Y 坐标(左上角)
* @return 0 成功,-1 失败
*/
int display_string(FT_Face face, wchar_t *wstr, int lcd_x, int lcd_y)
{
int i;
int error;
/* 整行文字的外框 */
FT_BBox bbox;
/* pen:字符原点(基线上的位置) */
FT_Vector pen;
/* glyph slot:保存当前字符的位图和度量信息 */
FT_GlyphSlot slot = face->glyph;
/* 将 LCD 坐标转换为 FreeType 使用的笛卡尔坐标系
* LCD 原点在左上角
* FreeType 原点在左下角
*/
int x = lcd_x;
int y = var.yres - lcd_y;
/* 第一步:计算整行文字的外接矩形 */
compute_string_bbox(face, wstr, &bbox);
/* 第二步:反推第一个字符的原点(pen)
*
* 当 pen = (0,0) 时,文字外框左上角是 (bbox.xMin, bbox.yMax)
* 现在希望文字外框左上角对齐到 (x, y)
*/
pen.x = (x - bbox.xMin) * 64; /* FreeType 使用 1/64 像素 */
pen.y = (y - bbox.yMax) * 64;
/* 第三步:逐个字符加载并绘制 */
for (i = 0; i < wcslen(wstr); i++)
{
/* 设置当前字符的变换(只平移,不旋转) */
FT_Set_Transform(face, 0, &pen);
/* 加载并生成当前字符的位图 */
error = FT_Load_Char(face, wstr[i], FT_LOAD_RENDER);
if (error)
{
printf("FT_Load_Char error\n");
return -1;
}
/* 将字符位图绘制到 LCD 上
* 注意:draw_bitmap 使用的是 LCD 坐标系
*/
draw_bitmap(&slot->bitmap,
slot->bitmap_left,
var.yres - slot->bitmap_top);
/* 移动到下一个字符的原点 */
pen.x += slot->advance.x;
pen.y += slot->advance.y;
}
return 0;
}
7 实验与运行结果
7.1 编译
arm-buildroot-linux-gnueabihf-gcc -o show_line show_line.c -lfreetype
7.2 运行
./show_line ./simsun.ttc 10 200 80
参数说明:
10: LCD X 坐标200:LCD Y 坐标80:字体大小
7.3 显示效果
LCD 上成功显示:
小程同学https://gitcode.com/xiaocheng/gitcode_knowledge
文字左上角严格对齐到指定 (x, y) 坐标,字符间距与基线完全正确。
8 总结
本文重点解决了 FreeType 初学者最容易踩坑的三个问题:
- ✅ 坐标系不一致
- ✅ 字符大小不统一
- ✅ 一行文字整体定位困难
👉 如果本文对你有帮助,欢迎点赞、收藏、关注 小程同学>o< 🌱
更多推荐



所有评论(0)