目录
一、简要
从点阵文件中把字母或者汉字的字模取出来在LCD上显示这个方式有个缺点,这个文字的大小就固定了不能够缩放,我们浏览器就可以放大缩小字体,这些字体就成为矢量字体,比如字母A,在矢量字体文件中存放的是若干条闭合曲线的关键点,显示的时候使用数学曲线描绘并连接关键点,然后填充曲线内部空间,这样来实现缩放字体,关键点之间的相互位置是知晓的,而这种数学曲线就称为贝塞尔曲线,贝塞尔曲线在高等数学中有讲到,但不代表我们需要复习贝塞尔曲线,我们可以通过freetype库来处理这些矢量字体,在网上可以搜索到freetype的官网:https://www.freetype.org/,在里面可以下载到源码以及相当文档
在docs/reference目录有解释说明,不过都是英文的,但是有中国牛人的翻译:https://wenku.baidu.com/view/2d24be10cc7931b765ce155b.html
字体文件怎么构造的,构造字体前,需要先描绘出来,例如在1000*1000的细格子以点阵的方式描绘,由做美工的描绘,然后具备数字功底的程序员将关键点取出来及相对位置,例如我们PC上C:\Windows\Fonts存放这各种字库文件,例如文件simsun.ttc字体,存放着很多中文还兼容ASCII,存放着每个字符的关键点和关键点之间的相对位置,这些数据在术语上称为glyph(字形)
二、文字显示过程
字符编码参考:字符编码方式
- 给定一个文字'A':0x41,"中":可能有GBK、unicode 等编码,可以确定它的编码值
- 根据编码值从字体文件中找到"glyph" ,(由于字体文件可以是GBK、unicode等,在字库文件前还存有字符映射表charmaps,支持多种编码)
- 设置字体大小
- 用某些函数把glyph里的点缩放设置的大小
- 转换为位图点阵
- 在LCD等显示器中显示出来
三、freetype中的step1
在docs\tutorial\step1.html介绍了怎么使用相关函数,这里只截图小部分
- 首先包含头文件里面定义了一些宏例如FT_FREETYPE_H这个宏就在ft2build.h这个头文件中定义了
- 1、初始化:FT_Init_FreeType函数
- 2、初始化库:FT_Init_FreeType函数,加载字体Face,可以认为是一种平面,只可意会,a.可以从文件中加载,b也可以读到内存再从内存中加载,c其他方式加载,我们主要想从文件里面做,FT_New_Face(可以认为打开字体文件),术语上是构造了一个face,face是一个结构体
- 加载face之后会得到所谓的face结构体,从结构体可以确定字体文件的信息,比如num_glyphs(字形数)、units_per_EM(比如1000*1000的方框,这个框就是EM)
- 3、设置字体大小:有FT_Set_Char_Size和FT_Set_Pixel_Sizes两个函数
- 4、根据字母或者汉字的ASCLL或者其他编码,去文件中找到glyph,根据编码值加载glyph,在loading之前可以选择charmap,比如给定一个中字可能有 GBK、unicode、BIG5码,charmaps支持多种编码方式,可以根据FT_Select_CharMap选择字符编码方式,如果不选择的话默认是unicode码
- glyph_index = FT_Get_Char_Index( face, charcode ); 参数是face和字符编码,得到glyph的索引
- 根据索引取出glyph:FT_Load_Glyph(face, glyph_index, load_flags ); 转为位图:FT_Render_Glyph函数
- 5、变换:移动、旋转,FT_Set_Transform:matrix旋转的角度,delta 偏移位置
四、在PC上测试freetype
看再多的文档也比不上写代码或者看代码来得快,写代码的过程理解会更加深刻,在docs\tutorial里面有3个例子是C++,还有C语言的例子example1.c,各个函数可以参考文档中docs\reference\ft2-base_interface.html
4.1 分析main函数
main函数如下
int
main( int argc,
char** argv )
{
FT_Library library;
FT_Face face;
FT_GlyphSlot slot;
FT_Matrix matrix; /* transformation matrix */
FT_Vector pen; /* untransformed origin */
FT_Error error;
char* filename;
char* text;
double angle;
int target_height;
int n, num_chars;
if ( argc != 3 )
{
fprintf ( stderr, "usage: %s font sample-text\n", argv[0] );
exit( 1 );
}
filename = argv[1]; /* first argument */
text = argv[2]; /* second argument */
num_chars = strlen( text );
angle = ( 25.0 / 360 ) * 3.14159 * 2; /* use 25 degrees */
target_height = HEIGHT;
error = FT_Init_FreeType( &library ); /* initialize library */
/* error handling omitted */
error = FT_New_Face( library, filename, 0, &face );/* create face object */
/* error handling omitted */
/* use 50pt at 100dpi */
error = FT_Set_Char_Size( face, 50 * 64, 0,
100, 0 ); /* set character size */
/* error handling omitted */
/* cmap selection omitted; */
/* for simplicity we assume that the font contains a Unicode cmap */
slot = face->glyph;
/* set up matrix */
matrix.xx = (FT_Fixed)( cos( angle ) * 0x10000L );
matrix.xy = (FT_Fixed)(-sin( angle ) * 0x10000L );
matrix.yx = (FT_Fixed)( sin( angle ) * 0x10000L );
matrix.yy = (FT_Fixed)( cos( angle ) * 0x10000L );
/* the pen position in 26.6 cartesian space coordinates; */
/* start at (300,200) relative to the upper left corner */
pen.x = 300 * 64;
pen.y = ( target_height - 200 ) * 64;
for ( n = 0; n < num_chars; n++ )
{
/* set transformation */
FT_Set_Transform( face, &matrix, &pen );
/* load glyph image into the slot (erase previous one) */
error = FT_Load_Char( face, text[n], FT_LOAD_RENDER );
if ( error )
continue; /* ignore errors */
/* now, draw to our target surface (convert position) */
draw_bitmap( &slot->bitmap,
slot->bitmap_left,
target_height - slot->bitmap_top );
/* increment pen position */
pen.x += slot->advance.x;
pen.y += slot->advance.y;
}
show_image();
FT_Done_Face ( face );
FT_Done_FreeType( library );
return 0;
}
- 一开始FT_Init_FreeType、FT_New_Face、FT_Set_Char_Size,后面出现以下语句,FT_Load_Glyph函数会把图像存在face->glyph中,而slot称为插槽
slot = face->glyph;
- 对于FT_Set_Char_Size函数其中char_width、char_height:单位是1/64point,point相等于物理上的尺寸,等于1/72inch(英寸),如果char_width是100的话就是100 * 1/64 * 1/72 ,horz_resolution:单位是dpi,(dpi:dots像素,per每一个,inch英寸)即每英寸里面有多少个像素,因此字符像素为 100 * 1/64 * 1/72,假设horz_resolution为200,就是100 * 1/64 * 1/72 * 200,即每个字符的像素
- 对于lcd来说只需要关注像素的大小,因此我们用FT_Set_Pixel_Sizes
- 旋转角度方面的计算
/* set up matrix */
matrix.xx = (FT_Fixed)( cos( angle ) * 0x10000L );
matrix.xy = (FT_Fixed)(-sin( angle ) * 0x10000L );
matrix.yx = (FT_Fixed)( sin( angle ) * 0x10000L );
matrix.yy = (FT_Fixed)( cos( angle ) * 0x10000L );
- pen指示的是笛卡尔坐标,原点是在左下角,在lcd原点一般在左上角,在字体文件或者字体函数中用的都是笛卡尔坐标,这里需要进行转换为lcd的坐标,笛卡尔与lcd的x同向,因此x' = x(lcd),y坐标下,笛卡尔与lcd的y坐标相加等于lcd宽度,其中target_height相当于宽度,单位是1/64point,因此乘上64
/* the pen position in 26.6 cartesian space coordinates; */
/* start at (300,200) relative to the upper left corner */
pen.x = 300 * 64;
pen.y = ( target_height - 200 ) * 64;
- 这个函数旋转角度的时候用
FT_Set_Transform( face, &matrix, &pen );
- 这里只用了FT_Load_Char,在文档step1有解释,这个函数在这里替代了FT_Get_Char_Index、FT_Load_Glyph、FT_Render_Glyph三个函数,FT_LOAD_RENDER标志位可以立刻得到点阵,就不用FT_Render_Glyph函数来转换为点阵
error = FT_Load_Char( face, text[n], FT_LOAD_RENDER );
- 最后把点阵由buffer存在了image中,并打印出出来
void
draw_bitmap( FT_Bitmap* bitmap,
FT_Int x,
FT_Int y)
{
FT_Int i, j, p, q;
FT_Int x_max = x + bitmap->width;
FT_Int y_max = y + bitmap->rows;
for ( i = x, p = 0; i < x_max; i++, p++ )
{
for ( j = y, q = 0; j < y_max; j++, q++ )
{
if ( i < 0 || j < 0 ||
i >= WIDTH || j >= HEIGHT )
continue;
image[j][i] |= bitmap->buffer[q * bitmap->width + p];
}
}
}
void
show_image( void )
{
int i, j;
for ( i = 0; i < HEIGHT; i++ )
{
for ( j = 0; j < WIDTH; j++ )
putchar( image[i][j] == 0 ? ' '
: image[i][j] < 128 ? '+'
: '*' );
putchar( '\n' );
}
}
4.2 修改main函数
包含相关注释
...
#define WIDTH 80 //分辨率,在打印的时候体现
#define HEIGHT 80
...
int
main( int argc,
char** argv )
{
FT_Library library;
FT_Face face;
FT_GlyphSlot slot;
FT_Matrix matrix; /* transformation matrix */
FT_Vector pen; /* untransformed origin */
FT_Error error;
char* filename;
char* text;
double angle;
int target_height;
int n, num_chars;
if ( argc != 3 )
{
fprintf ( stderr, "usage: %s font sample-text\n", argv[0] );
exit( 1 );
}
filename = argv[1]; /* first argument */
text = argv[2]; /* second argument */
num_chars = strlen( text );
angle = ( 0.0 / 360 ) * 3.14159 * 2; /* use 25 degrees */ //角度设置为0不旋转
target_height = HEIGHT;
error = FT_Init_FreeType( &library ); /* initialize library */
/* error handling omitted */
error = FT_New_Face( library, argv[1], 0, &face ); /* create face object */
/* error handling omitted */
#if 0
/* use 50pt at 100dpi */
error = FT_Set_Char_Size( face, 50 * 64, 0,
100, 0 ); /* set character size */
/* pixels = 50 /72 * 100 = 69 */
#else
FT_Set_Pixel_Sizes(face, 24, 0); //设置为24*24的点阵大小
#endif
/* error handling omitted */
slot = face->glyph;
/* set up matrix */
matrix.xx = (FT_Fixed)( cos( angle ) * 0x10000L );
matrix.xy = (FT_Fixed)(-sin( angle ) * 0x10000L );
matrix.yx = (FT_Fixed)( sin( angle ) * 0x10000L );
matrix.yy = (FT_Fixed)( cos( angle ) * 0x10000L );
/* the pen position in 26.6 cartesian space coordinates; */
/* start at (0,40) relative to the upper left corner */ // 这里表示在0,40的地方显示
pen.x = 0 * 64;//单位是1/64像素 所以乘上64
pen.y = ( target_height - 40 ) * 64;
for ( n = 0; n < num_chars; n++ )
{
/* set transformation */
FT_Set_Transform( face, &matrix, &pen );
/* load glyph image into the slot (erase previous one) */
error = FT_Load_Char( face, text[n], FT_LOAD_RENDER ); //load_char之后就可以得到字符的点阵
if ( error )
continue; /* ignore errors */
/* now, draw to our target surface (convert position) */
draw_bitmap( &slot->bitmap,
slot->bitmap_left,
target_height - slot->bitmap_top );
/* increment pen position */
pen.x += slot->advance.x;
pen.y += slot->advance.y;
}
show_image();
FT_Done_Face ( face );
FT_Done_FreeType( library );
return 0;
}
draw_bitmap中参数的相关解释:
draw_bitmap( &slot->bitmap, //得到点阵,笛卡尔左上角的坐标
slot->bitmap_left, //转换为点阵lcd的x坐标,两者坐标相等
target_height - slot->bitmap_top );//lcd的高度减去笛卡尔高度就是点阵的y坐标
4.3 在PC上测试
- 先安装freetype环境
tar xjf freetype-2.4.10.tar.bz2
./configure
make
sudo make install
gcc -o example1 example1.c -I /usr/local/include/freetype2 -lfreetype -lm
- 显示字母的效果,参数二为字库,参数三只能输入字符,不能是中文,因为FT_Load_Char函数会把中文当成一个字节,需要后面引入宽字符
- 想显示中文怎么做,继续修改main函数,可以直接指定unicode码,然后FT_Load_Char得到位图,如果是这种写法:char *str = "小马";这样写需要分辨中文两个字节,字母一个字节,因此我们引入宽字符,每一个字/字母都用四个字节来表示,需要包含wchar.h头文件
#include <wchar.h>
...
double angle;
int target_height;
int n, num_chars;
//int chinese_str[] = {0x97e6, 0x4e1c, 0x5c71, 0x0067}; //这样写表示一个字/字母,需要我们手动去查找unicode码
//char *str = "小马"; 这样写需要分辨中文两个字节,字母一个字节
wchar_t *chinese_str = L"韦gif"; //每一个字/字母都用四个字节来表示
unsigned int *p = (wchar_t *)chinese_str;
int i;
printf("Uniocde: \n");
for (i = 0; i < wcslen(chinese_str); i++)
{
printf("0x%x ", p[i]);
}
printf("\n");
//return 0;
if ( argc != 2 )
{
fprintf ( stderr, "usage: %s font\n", argv[0] );
exit( 1 );
}
...
for ( n = 0; n < wcslen(chinese_str); n++ ) //宽字符长度
{
/* set transformation */
FT_Set_Transform( face, &matrix, &pen );
/* load glyph image into the slot (erase previous one) */
error = FT_Load_Char( face, chinese_str[n], FT_LOAD_RENDER ); //每个编码
if ( error )
continue; /* ignore errors */
/* now, draw to our target surface (convert position) */
draw_bitmap( &slot->bitmap,
slot->bitmap_left,
target_height - slot->bitmap_top );
/* increment pen position */
pen.x += slot->advance.x;
pen.y += slot->advance.y;
}
- 测试如下,指定输入字符集DBK,输出字符集为unicode,因此打印出来的为unicode码,FT_Load_Char根据unicode码就可以得到位图,然后打印显示
- 在打印函数中修改代码打印一下行数
void
show_image( void )
{
int i, j;
for ( i = 0; i < HEIGHT; i++ )
{
printf("%02d", i);
for ( j = 0; j < WIDTH; j++ )
putchar( image[i][j] == 0 ? ' '
: image[i][j] < 128 ? '+'
: '*' );
putchar( '\n' );
}
}
...
/* the pen position in 26.6 cartesian space coordinates; */
/* start at (0,40) relative to the upper left corner */
pen.x = 0 * 64;
pen.y = ( target_height - 40 ) * 64;
- 执行之后,发现"韦"字不在(0,40)的位置,反而超过了
4.4 得到CBox
- 去描绘一个字符的时候,有可能超过基线,参考文档中step2
- 因此在main函数最后,每次循环会重新指定pen的位置
/* increment pen position */
pen.x += slot->advance.x;
pen.y += slot->advance.y;
- 在文档中介绍了通过FT_Glyph_Get_CBox函数,可以得到xMax,xMin,yMax,yMin
- 修改代码,FT_Get_Glyph把插槽slot提取出来,因为每一次循环solt都不一样,FT_Glyph_Get_CBox根据glyph找到bbox,bbox就含有相关的参数
#include FT_GLYPH_H
....
int
main( int argc,
char** argv )
{
...
FT_BBox bbox;
FT_Glyph glyph;
...
for ( n = 0; n < wcslen(chinese_str); n++ )
{
/* set transformation */
FT_Set_Transform( face, &matrix, &pen );
/* load glyph image into the slot (erase previous one) */
error = FT_Load_Char( face, chinese_str[n], FT_LOAD_RENDER );
if ( error )
continue; /* ignore errors */
error = FT_Get_Glyph( face->glyph, &glyph ); //把插槽放在glyph中
if (error)
{
printf("FT_Get_Glyph error!\n");
return -1;
}
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &bbox );//根据glyph找到bbox
/* now, draw to our target surface (convert position) */
draw_bitmap( &slot->bitmap,
slot->bitmap_left,
target_height - slot->bitmap_top );
printf("Unicode: 0x%x\n", chinese_str[n]);
printf("origin.x/64 = %d, origin.y/64 = %d\n", pen.x/64, pen.y/64); //原点的位置,单位为1/64
printf("xMin = %d, xMax = %d, yMin = %d, yMax = %d\n", bbox.xMin, bbox.xMax, bbox.yMin, bbox.yMax);//可以描述方框
printf("slot->advance.x/64 = %d, slot->advance.y/64 = %d\n", slot->advance.x/64, slot->advance.y/64);
/* increment pen position */
pen.x += slot->advance.x;
pen.y += slot->advance.y;
}
编译之后得到相关的参数
- 根据打印把字符描绘出来,字/字母可能越过基线,根据CBox可以算出整个文本中的宽度和长度,如下图绿色的方框