6.6 使用freetype显示一行文字 使用GIT下载所有源码后,本节源码位于如下目录: 01_all_series_quickstart 04_嵌入式Linux应用开发基础知识source10_freetype 04_show_lineshow_line.c
Git下载地址: https://e.coding.net/weidongshan/01_all_series_quickstart.git
本节的目的: 在LCD上指定一个左上角坐标(x, y),把一行文字显示出来。下图中,文字的外框用虚线表示,外框的左上角坐标就是(x, y)。
6.6.1 笛卡尔坐标系 在LCD的坐标系中,原点在屏幕的左上角。对于笛卡尔坐标系,原点在左下角。freetype使用笛卡尔坐标系,在显示时需要转换为LCD坐标系。
从下图可知,X方向坐标值是一样的。 在Y方向坐标值需要换算,假设LCD的高度是V。 在LCD坐标系中坐标是(x, y),那么它在笛卡尔坐标系中的坐标值为(x, V-y)。 反过来也是一样的,在笛卡尔坐标系中坐标是(x, y),那么它在LCD坐标系中坐标值为(x, V-y)。
6.6.2 每个字符的大小可能不同 在使用FT_Set_Pixel_Sizes函数设置字体大小时,这只是“期望值”。比如“百问网www.100ask.net”,如果把“.”显示得跟其他汉字一样大,不好看。
所以在显示一行文字时,后面文字的位置会受到前面文字的影响。 幸好,freetype帮我们考虑到了这些影响。
对于freetype字体的尺寸(freetype Metrics),需要参考下图这个文档:
上述文档中列出了一个图,摘录如下:
在显示一行文字时,这些文字会基于同一个基线来绘制位图:baseline。 在baseline上,每一个字符都有它的原点(origin),比如上图中baseline左边的黑色圆点就是字母“g”的原点。当前origin加上advance就可以得到下一个字符的origin,比如上图中baseline右边的黑色圆点。在显示一行中多个文件字时,后一个文字的原点依赖于前一个文字的原点及advance。 字符的位图是有可能越过baseline的,比如上图中字母“g”在baseline下方还有图像。
上图中红色方框内就是字母“g”所点据的位图,它的四个角落不一定与原点重合。
上图中那些xMin、xMax、yMin、yMax如何获得?可以使用
FT_Glyph_Get_CBox函数获得一个字体的这些参数,将会保存在一个FT_BBox结构体中,以后想计算一行文字的外框时要用到这些信息:
6.6.3 怎么在指定位置显示一行文字 要显示一行文字时,每一个字符都有自己外框:xMin、xMax、yMin、yMax。把这些字符的xMin、yMin中的最小值取出来,把这些字符的xMax、yMax中的最大值取出来,就可以确定这行文字的外框了。 要想在指定位置(x, y)显示一行文字,步骤如下图所示:
① 先指定第1个字符的原点pen坐标为(0, 0),计算出它的外框 ② 再计算右边字符的原点,也计算出它的外框 把所有字符都处理完后就可以得到一行文字的整体外框:假设外框左上角坐标为(x’, y’)。 ③ 想在(x, y)处显示这行文字,调整一下pen坐标即可
怎么调整? pen为(0, 0)时对应左上角(x’, y’); 那么左上角为(x, y)时就可以算出pen为(x-x’, y-y’)。
6.6.4 freetype的几个重要数据结构 要想形象地理解程序,需要先介绍一下freetype中几个数据结构:
- FT_Library 对应freetype库,使用freetype之前要先调用以下代码:
FT_Library library; /* 对应freetype库 */
error = FT_Init_FreeType( &library ); /* 初始化freetype库 */
- FT_Face 它对应一个矢量字体文件,在源码中使用FT_New_Face函数打开字体文件后,就可以得到一个face。 为什么称之为face? 估计是文字都是写在二维平面上的吧,正对着人脸?不用管原因了,总之认为它对应一个字体文件就可以。 代码如下:
error = FT_New_Face(library, font_file, 0, &face ); /* 加载字体文件 */
- FT_GlyphSlot 插槽?用来保存字符的处理结果:比如转换后的glyph、位图,如下图:
一个face中有很多字符,生成一个字符的点阵位图时,位图保存在哪里?保存在插槽中:face->glyph。 生成第1个字符位图时,它保存在face->glyph中;生成第2个字符位图时,也会保存在face->glyph中,会覆盖第1个字符的位图。 代码如下:
代码语言:javascript复制FT_GlyphSlot slot = face->glyph; /* 插槽: 字体的处理结果保存在这里 */
- FT_Glyph 字体文件中保存有字符的原始关键点信息,使用freetype的函数可以放大、缩小、旋转,这些新的关键点保存在插槽中(注意:位图也是保存在插槽中)。 新的关键点使用FT_Glyph来表示,可以使用这样的代码从slot中获得glyph:
error = FT_Get_Glyph(slot , &glyph);
- FT_BBox FT_BBox结构体定义如下,它表示一个字符的外框,即新glyph的外框:
可以使用以下代码从glyph中获得这些信息:
代码语言:javascript复制FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &bbox );
针对上述流程,示例代码如下:
6.6.5 计算一行文字的外框 前面提到过,一行文字中:后一个字符的原点=前一个字符的原点 advance。 所以要计算一行文字的外框,需要按照排列顺序处理其中的每一个字符。 代码如下,注释写得很清楚了:
代码语言:javascript复制102 int compute_string_bbox(FT_Face face, wchar_t *wstr, FT_BBox *abbox)
103 {
104 int i;
105 int error;
106 FT_BBox bbox;
107 FT_BBox glyph_bbox;
108 FT_Vector pen;
109 FT_Glyph glyph;
110 FT_GlyphSlot slot = face->glyph;
111
112 /* 初始化 */
113 bbox.xMin = bbox.yMin = 32000;
114 bbox.xMax = bbox.yMax = -32000;
115
116 /* 指定原点为(0, 0) */
117 pen.x = 0;
118 pen.y = 0;
119
120 /* 计算每个字符的bounding box */
121 /* 先translate, 再load char, 就可以得到它的外框了 */
122 for (i = 0; i < wcslen(wstr); i )
123 {
124 /* 转换:transformation */
125 FT_Set_Transform(face, 0, &pen);
126
127 /* 加载位图: load glyph image into the slot (erase previous one) */
128 error = FT_Load_Char(face, wstr[i], FT_LOAD_RENDER);
129 if (error)
130 {
131 printf("FT_Load_Char errorn");
132 return -1;
133 }
134
135 /* 取出glyph */
136 error = FT_Get_Glyph(face->glyph, &glyph);
137 if (error)
138 {
139 printf("FT_Get_Glyph error!n");
140 return -1;
141 }
142
143 /* 从glyph得到外框: bbox */
144 FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &glyph_bbox);
145
146 /* 更新外框 */
147 if ( glyph_bbox.xMin < bbox.xMin )
148 bbox.xMin = glyph_bbox.xMin;
149
150 if ( glyph_bbox.yMin < bbox.yMin )
151 bbox.yMin = glyph_bbox.yMin;
152
153 if ( glyph_bbox.xMax > bbox.xMax )
154 bbox.xMax = glyph_bbox.xMax;
155
156 if ( glyph_bbox.yMax > bbox.yMax )
157 bbox.yMax = glyph_bbox.yMax;
158
159 /* 计算下一个字符的原点: increment pen position */
160 pen.x = slot->advance.x;
161 pen.y = slot->advance.y;
162 }
163
164 /* return string bbox */
165 *abbox = bbox;
166 }
6.6.6 调整原点并绘制 代码如下,也不复杂:
代码语言:javascript复制169 int display_string(FT_Face face, wchar_t *wstr, int lcd_x, int lcd_y)
170 {
171 int i;
172 int error;
173 FT_BBox bbox;
174 FT_Vector pen;
175 FT_Glyph glyph;
176 FT_GlyphSlot slot = face->glyph;
177
178 /* 把LCD坐标转换为笛卡尔坐标 */
179 int x = lcd_x;
180 int y = var.yres - lcd_y;
181
182 /* 计算外框 */
183 compute_string_bbox(face, wstr, &bbox);
184
185 /* 反推原点 */
186 pen.x = (x - bbox.xMin) * 64; /* 单位: 1/64像素 */
187 pen.y = (y - bbox.yMax) * 64; /* 单位: 1/64像素 */
188
189 /* 处理每个字符 */
190 for (i = 0; i < wcslen(wstr); i )
191 {
192 /* 转换:transformation */
193 FT_Set_Transform(face, 0, &pen);
194
195 /* 加载位图: load glyph image into the slot (erase previous one) */
196 error = FT_Load_Char(face, wstr[i], FT_LOAD_RENDER);
197 if (error)
198 {
199 printf("FT_Load_Char errorn");
200 return -1;
201 }
202
203 /* 在LCD上绘制: 使用LCD坐标 */
204 draw_bitmap( &slot->bitmap,
205 slot->bitmap_left,
206 var.yres - slot->bitmap_top);
207
208 /* 计算下一个字符的原点: increment pen position */
209 pen.x = slot->advance.x;
210 pen.y = slot->advance.y;
211 }
212
213 return 0;
214 }
6.6.7 上机实验 编译命令(如果你使用的交叉编译链前缀不是arm-buildroot-linux-gnueabihf,请自行修改命令):
代码语言:javascript复制$ arm-buildroot-linux-gnueabihf-gcc -o show_line show_line.c -lfreetype
将编译好的show_line文件与simsun.ttc字体文件拷贝至开发板,这2个文件放在同一个目录下,然后执行以下命令(其中的3个数字分别表示LCD的X坐标、Y坐标、字体大小):
代码语言:javascript复制[root@board:~]# ./show_line ./simsun.ttc 10 200 80