屏幕显示字体(字体点阵):
在内核中有对应的文件fontdata_8x16.c,将字体通过数组8*16来描述,0表示为空,1表示描点,通过通过各个点形成一个字体点阵显示在屏幕上,而在我们所有终端中显示的字体也可以是通过点阵进行显示的,对应的字体点阵数据在字体文件中,各字体文件均含有编码表、字体数据(不同字体对应不同的点阵)。其中编码表用于对应字符码值找到字体数据的索引,其支持编码方式不同字体文件支持的不同,一般在选择字体文件时会相应的提示支持的编码方式。
PC中字体文件:c:/window/fonts目录下。
问题:他们怎么进行索引的?
这里举例讲解一下,HZK16,即16x16的字体点阵,通过GB2312编码进行索引中,而GB2312中用2字节表示一个汉字,其中第一个字节表示区码,第二个字节表示位码,一般区的大小为94,含94个文字,位码为16*16大小及2 * 8 * 16=32字节,每一位占32字节,一个区占 94 * 32字节,并且区号和位号都是从0xA1开始编号,所以可以通过公式获得坐标:
unsigned char str[] = "中";
unsigned int area = str[0] - 0xA1;
unsigned int where = str[1] - 0xA1;
pos = 字体文件base_addr + (area * 94 + where) * 32;
注意:(area * 94 + where) * 32,可以拆分为 area * 94 * 32表示当前位于哪个区,where * 32表示在哪个位
字体点阵的使用与分析:
使用:字体点阵的使用需要通过对应的字体文件,根据字体文件的编码表所支持的编码方法,传入对应编码格式的数据,通过编码表获取到对应的字体点阵数据,在根据点阵进行描点,0位空,1描点。
注意:每个字体文件前包含多个charmaps,即支持多种编码值索引,一般均支持unicode码。
分析:他只能以固定大小进行显示,即只能在该屏幕的固定空间上进行显示,不能再屏幕上进行缩放,有一点局限性,因此引入了矢量字体。
矢量字体
在矢量字体中,坐标使用的是笛卡尔坐标,原点位于左下方,而LCD屏幕不是,原点位于左上方,
如下图:
那么我们可以发现,如果我们要讲矢量字体运用在LCD屏幕上,首先需要对坐标进行转换,即笛卡尔->LCD坐标轴:
LCD_y = LCD垂直高 - 笛卡尔_y;
LCD_x = LCDx;
在矢量字体中,glyph为每个字体的关键点描述信息,某一条曲线由这些关键点确定,关键点由贝塞尔曲线连接,并在贝塞尔曲线内进行填充,构成一个矢量字体,像我们使用艺术字那种填充字体。
那么我们可以大胆猜想要得到一个矢量字体,需要通过码值索引获得该字体的glyph,并将该glyph转换为位图,通过位图我们便可以知道该关键点的点阵,并在LCD进行显示。
freetype库
freetype是矢量字体的一个库
头文件包含以对应变量声明:
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
FT_Library library; /* 库 */
FT_Face face; /* 字体文件对象 */
FT_Vector pen; /* 矢量字体的原点 */
FT_Error error; /* 错误码 */
FT_GlyphSlot slot; /* glyph插槽 */
FT_Matrix matrix; /* 矩阵,通过矩阵转置实现字体旋转 */
double angle; /* 旋转的角度 */
首先先对freetype使用步骤进行讲解:
- 初始化库 — FT_Init_FreeType( &library );
- 创建字体文件对象 — FT_New_Face( library, “字体文件”, 0, &face );
- 设置矢量字体的字符大小 — FT_Set_Pixel_Sizes( face, 24, 0);
- 将glyph存入插槽 — slot = face->glyph;
- 确定坐标 — pen.x = var.xres/2 * 64; pen.y = (var.yres/2 + 16) * 64;
字符宽度和高度以点的 1/64 指定。点是物理距离,等于 1/72 英寸。 - 设置角度 — angle = ( (1.0)strtoul(argv[2], NULL, 0) / 360 ) * 3.14159 * 2; / use 25 degrees */
- 初始化矩阵 —
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 ); - 旋转字符设置 — FT_Set_Transform( face, &matrix, &pen );
- 将插槽中的glyph转换为位图 — FT_Load_Char( face, chinese_str[0], FT_LOAD_RENDER );
- 位图存放在插槽slot->bitmap下,点阵存放于bitmap->buffer下
- 将笛卡尔坐标转换为LCD坐标,根据点阵打印,该函数需自行实现
注意:若无需旋转功能,可忽略 6-8步骤。
打印位图的函数
draw_bitmap( &slot->bitmap, slot->bitmap_left, var.yres - slot->bitmap_top );
其中:
slot->bitmap:位图
slot->bitmap_left:笛卡尔_x坐标
var.yres:LCD的屏幕高度
slot->bitmap_top:笛卡尔_y坐标。
void lcd_put_pixel(int x,int y, unsigned int color)
{
unsigned char *pen_8 = fbmem + y*line_width + x*pixel_width;
unsigned short *pen_16;
unsigned int *pen_32;
unsigned int red,green,blue;
pen_16 = (unsigned short *)pen_8;
pen_32 = (unsigned int *)pen_8;
switch(var.bits_per_pixel)
{
case 8:
{
*pen_8 = color;
break;
}
case 16:
{
/* 565 */
red = (color >> 16) & 0xff;
green = (color >> 8) & 0xff;
blue = (color >> 0) & 0xff;
color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
*pen_16 = color;
break;
}
case 32:
{
*pen_32 = color;
break;
}
default:
{
printf("can't support %dbpp\n",var.bits_per_pixel);
break;
}
}
}
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 >= var.xres || j >= var.yres )
continue;
lcd_put_pixel(i, j, bitmap->buffer[q * bitmap->width + p]);
}
}
}
矢量字体显示
为了使字体美观
- 例如字母g的下界一般比a要低,为实现这种情况,原点坐标不在整个字体框图的最左下方,如下图所示:
- 字符的长宽也不相等,例如汉字与字母。
下一个原点坐标计算:
pen.x += slot->advance.x;
pen.y += slot->advance.y;
各个字体的原点,框图信息等均保存在FT_BBox成员中,但是信息在FT_Load_Char步骤后获取。
使用:
FT_BBox bbox;
FT_Glyph glyph;
...
FT_Load_Char
...
error = FT_Get_Glyph( face->glyph,&glyph );
if(error)
{
printf("FT_Get_Glyph error!\n");
return -1;
}
FT_Glyph_Get_CBox( glyph, FT_GLYPH_BBOX_TRUNCATE, &bbox );
详细可看FreeType 2 Tutorial Step 2 — managing glyphs
居中多行显示
如何居中多行显示?
- 支持一次性字符串输出显示,将宽字符字符串的各个字符对应的glyph保存在一个数组中
- 居中显示,因为各个字符的显示框图大小不同,需要找出该行中最左和最右的坐标即最上和最低的坐标,从而得知该行的横向纵向长度,然后根据公式得到居中位置:
mid_x = (LCD_x分辨率 - 行_x) / 2、mid_y = (LCD_y分辨率 - 行_y) / 2 - 多行打印时,因为各个字符并不是按照统一原点来描绘的,同一大小的框图大小可能不一样,需要找出上一行中字符串中最大的框长度。避免下一行覆盖,不过这里并未作讲解,读者可以自行添加。
- 将各存储的关键字描述glyph转换为位图,打印显示到LCD。
一、将矢量字体字符串中各字符的glyph存储在glyphs数组中。
思想:
指定一个指针指向该glyphs数组,在用for循环遍历宽字符数组中的每一个宽字符,将glyphs数组中各元素进行初始化,首先根据宽字符的Unicode编码值调用FT_Get_Char_Index获取对应的glyph对应序号,在通过调用FT_Load_Glyph将slot中的数据拷贝到glyph->image中存储,设置对应pen坐标值,在调用FT_Glyph_Transform使glyph->image含位置信息,x轴位置右移advance.x,指针指向下一个元素进行初始化,直到宽字符遍历结束。
Get_Glyph_Frm_Wstr(FT_Face face, wchar_t *wstl, TGlyph glyphs[])
{
int n;
PGlyph glyph = glyphs;
int pen_x=0, pen_y=0;
FT_Error error;
FT_GlyphSlot slot = face->glyph;
for(n=0; n<wcslen(wstl); n++)
{
glyph->index = FT_Get_Char_Index( face, wstl[n] );
//每次都把glyph覆盖face的slot
error = FT_Load_Glyph( face, glyph->index, FT_LOAD_DEFAULT );
if ( error ) continue;
//将slot中的数据拷贝到glyph->image存储
error = FT_Get_Glyph( face->glyph, &glyph->image );
if ( error ) continue;
/* 使glyph->image含位置信息 */
glyph->pos.x = pen_x;
glyph->pos.y = pen_y;
FT_Glyph_Transform( glyph->image, 0, &glyph->pos );
pen_x += slot->advance.x; // 1/64point
/* increment number of glyphs */
glyph++;
}
return (glyph - glyphs);
}
二、居中坐标获取
void compute_string_bbox( TGlyph glyphs[], FT_UInt num_glyphs, FT_BBox *abbox )
{
FT_BBox bbox;
int n;
bbox.xMin = bbox.yMin = 32000;
bbox.xMax = bbox.yMax = -32000;
for ( n = 0; n < num_glyphs; n++ )
{
FT_BBox glyph_bbox;
FT_Glyph_Get_CBox( glyphs[n].image, 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;
}
*abbox = bbox;
}
line_box_width = bbox.xMax - bbox.xMin;
line_box_height = bbox.yMax - bbox.yMin;
三、 多行打印
注:这里只是简单的将y坐标向下平移了指定框大小的长度,实际上会有偏差,例如g之类的字体。
pen.y -= 24 * 64;
四、转换为位图,打印显示到LCD
注:打印函数draw_bitmap,上面有提到。
void Draw_Glyphs(TGlyph glyphs[], FT_UInt num_glyphs, FT_Vector pen)
{
int n;
FT_Error error;
for(n=0;n<num_glyphs;n++)
{
/* transform copy (this will also translate it to the */
/* correct position */
FT_Glyph_Transform( glyphs[n].image, NULL, &pen );
/* convert glyph image to bitmap (destroy the glyph copy!) */
error = FT_Glyph_To_Bitmap(&glyphs[n].image,FT_RENDER_MODE_NORMAL,0,/* no additional translation */1 );
/* destroy copy in "image" */
if ( !error )
{
FT_BitmapGlyph bit = (FT_BitmapGlyph)glyphs[n].image;
draw_bitmap( &bit->bitmap,bit->left, var.yres - bit->top );
FT_Done_Glyph( glyphs[n].image );
}
}
}
编译运行:
- 配置交叉编译环境:./configure --host=arm-linux
注意:头文件库文件的路径相同时可指定 --prefix=xxx,即可将生成后的文件存放在该路径下,若不一样需手工将对应文件拷贝过去。prefix为前缀的意思。 - 编译生成交叉编译下的头文件、库文件:make、make DESTDIR=$PWD/tmp install
- 头文件存放路径:
cp usr/local/lib/include/ /usr/local/arm/gcc-4.6.2-glibc-2.13-linaro-multilib-2011.12/fsl-linaro-toolchain/arm-fsl-linux-gnueabi/multi-libs/usr/include - 库文件存放路径:
cp usr/local/lib/ /usr/local/arm/gcc-4.6.2-glibc-2.13-linaro-multilib-2011.12/fsl-linaro-toolchain/arm-fsl-linux-gnueabi/multi-libs/usr/lib/ -d - 编译命令:
arm-none-linux-gnueabi-gcc -finput-charset=GBK -fexec-charset=UTF-8 xxx.c -I /usr/local/include/freetype2/ -lfreetype -lm - 移植到开发板,将生成动态库拷贝到开发板的根文件目录lib下,执行。
如何知道自己编译器所使用的头文件和库文件目录:
使用命令:平台-gcc -v
–prefix = xxx;//为指定前缀,所以文件都将位于此
–with-sysroot=xxx; //为指定当前使用的路径,头文件等位于该路径下的usr/include,库文件为该路径下的usr/lib/