freetype 使用解析---矢量字体

屏幕显示字体(字体点阵):

在内核中有对应的文件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使用步骤进行讲解:

  1. 初始化库 — FT_Init_FreeType( &library );
  2. 创建字体文件对象 — FT_New_Face( library, “字体文件”, 0, &face );
  3. 设置矢量字体的字符大小 — FT_Set_Pixel_Sizes( face, 24, 0);
  4. 将glyph存入插槽 — slot = face->glyph;
  5. 确定坐标 — pen.x = var.xres/2 * 64; pen.y = (var.yres/2 + 16) * 64;
    字符宽度和高度以点的 1/64 指定。点是物理距离,等于 1/72 英寸。
  6. 设置角度 — angle = ( (1.0)strtoul(argv[2], NULL, 0) / 360 ) * 3.14159 * 2; / use 25 degrees */
  7. 初始化矩阵 —
    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 );
  8. 旋转字符设置 — FT_Set_Transform( face, &matrix, &pen );
  9. 将插槽中的glyph转换为位图 — FT_Load_Char( face, chinese_str[0], FT_LOAD_RENDER );
  10. 位图存放在插槽slot->bitmap下,点阵存放于bitmap->buffer下
  11. 将笛卡尔坐标转换为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]);
    }
  }
}

矢量字体显示

为了使字体美观

  1. 例如字母g的下界一般比a要低,为实现这种情况,原点坐标不在整个字体框图的最左下方,如下图所示:
  2. 字符的长宽也不相等,例如汉字与字母。
    在这里插入图片描述
    下一个原点坐标计算:
    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
在这里插入图片描述

居中多行显示

如何居中多行显示?

  1. 支持一次性字符串输出显示,将宽字符字符串的各个字符对应的glyph保存在一个数组中
  2. 居中显示,因为各个字符的显示框图大小不同,需要找出该行中最左和最右的坐标即最上和最低的坐标,从而得知该行的横向纵向长度,然后根据公式得到居中位置:
    mid_x = (LCD_x分辨率 - 行_x) / 2、mid_y = (LCD_y分辨率 - 行_y) / 2
  3. 多行打印时,因为各个字符并不是按照统一原点来描绘的,同一大小的框图大小可能不一样,需要找出上一行中字符串中最大的框长度。避免下一行覆盖,不过这里并未作讲解,读者可以自行添加。
  4. 将各存储的关键字描述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 );
	    }
	}
}

编译运行:

  1. 配置交叉编译环境:./configure --host=arm-linux
    注意:头文件库文件的路径相同时可指定 --prefix=xxx,即可将生成后的文件存放在该路径下,若不一样需手工将对应文件拷贝过去。prefix为前缀的意思。
  2. 编译生成交叉编译下的头文件、库文件:make、make DESTDIR=$PWD/tmp install
  3. 头文件存放路径:
    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
  4. 库文件存放路径:
    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
  5. 编译命令:
    arm-none-linux-gnueabi-gcc -finput-charset=GBK -fexec-charset=UTF-8 xxx.c -I /usr/local/include/freetype2/ -lfreetype -lm
  6. 移植到开发板,将生成动态库拷贝到开发板的根文件目录lib下,执行。

如何知道自己编译器所使用的头文件和库文件目录:
使用命令:平台-gcc -v
–prefix = xxx;//为指定前缀,所以文件都将位于此
–with-sysroot=xxx; //为指定当前使用的路径,头文件等位于该路径下的usr/include,库文件为该路径下的usr/lib/

  • 5
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值