[电子书]项目储备二:用FreeType2绘制矢量字体、多行文字

0 矢量字体 VS 点阵字体
点阵字体:每一个字符都用点阵(8x8 / 8x16/ 16x16等)表示,然后用每个点的虚实来表示字符的轮廓
矢量字体:矢量字体(Vector font)中每一个字形是通过数学曲线来描述的,它包含了字形边界上的关键点,连线的导数信息等,
字体的渲染引擎(如FreeType2)通过读取这些数学矢量,然后进行一定的数学运算来进行渲染。
矢量字体主要包括 Type1、 TrueType、OpenType等几类。

两者的区别:
1)拓展名:矢量字体扩展名ttf。点阵字体的扩展名是fon.(在window10下,C盘windows/fonts/目录下有许多字体文件)
2)点阵字体组成的点阵字库 常用来保存显示字符点阵,这类点阵字库汉字最大的缺点是不能放大,一旦放大后就会
      发现文字边缘的锯齿。而 矢量字体组成的矢量字库只保存该字符关键点和其它一些描述信息
      (比如一个笔划的起始、终止坐标,半径、弧度等)。在显示、打印这一类字库时,要经过一系列的数学运算才能
      输出结果,但是这一类字库保存的汉字理论上可以被无限地放大,笔划轮廓仍然能保持圆滑,打印时使用的字库均为此类字库

两者的优劣势:
①点阵字体: 点阵字体优点是显示速度快,不像矢量字体需要计算;其最大的缺点是不能放大,一旦放大后就会发现文字边缘的锯齿。
②矢量字体:优点很明显显示效果比点阵字体好,且随意变换(放大、缩小、旋转等)不失真。缺点需要渲染方能显示、打印,
			因此相比点阵字体肯定要慢。

矢量字体与点阵字体对比图

矢量字体是后文要实现绘制,那如何使用矢量字体呢?
需要从矢量字库从提取字符的关键点,然后用若干条数学曲线(贝塞尔曲线)连接绘制(由FreeType2渲染完成)。
因此,实际编程时,只需要使用FreType2提供的高层API即可,此外,难点在于需要我们
把转换后的位图绘制到LCD屏幕上。
1 FreeType2的介绍与基本使用
FreeType库是一个完全免费(开源)的、高质量的且可移植的字体引擎,它提供统一的接口来访问多种字体格式文件,
包括TrueType, OpenType, Type1, CID, CFF, Windows FON/FNT, X11 PCF等

FreeType2的基本使用如下图

FreeType2的基本使用
后面绘制矢量字体、多行文字也是按此流程编程实现
用文字来描述FreeType的基本使用,如下所述
1)给定一个文字,可得到确定它的字符编码值
2)根据字符“编码值”从矢量字体文件中找到“glyph(字形/字的轮廓,其实就是字形的一些关键点)”
3)设置字体大小(因为是矢量字体,大小随意,固然需确定显示字符大小啦)
4)用某些函数吧Glyph里的关键点编码为指定字体大小
5)转换为位图点阵
6)在LCD上显示出来

废话不多说,先看看如果用FreeType2渲染一个矢量字体,并能随意旋转

2 在LCD显示一个矢量字体,且可旋转

方便起见,在LCD中央显示一个“繁”字,并根据命令行参数控制旋转角度
show_vector_font.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <math.h>
#include <wchar.h>
#include <ft2build.h>
#include FT_FREETYPE_H  // freetype/freetype.h
#include FT_GLYPH_H

extern const unsigned char fontdata_8x16[];
int fd_fb;
static line_width, pixel_width, screen_size;
static unsigned char* fbmem;
static struct fb_var_screeninfo var;
static struct fb_fix_screeninfo fix;

int error;
double angle;

FT_Library  library;
FT_Face     face;      /* handle to face object */
FT_GlyphSlot slot;
FT_Vector     pen;                 /* untransformed origin */
FT_Matrix   matrix;

void lcd_put_pixel(unsigned int x, unsigned y, unsigned int color);
void my_draw_bitmap( FT_Bitmap*  bitmap, FT_Int x, FT_Int  y);

int main(int argc, char** argv)
{
	wchar_t *str1 = L"繁";
 	if(argc != 3)
 	{
  		printf("Usage: ");
  		printf("%s <font_file> <angle>\n", argv[0]);
  		return 0;
 	}
 	fd_fb = open("/dev/fb0", O_RDWR); 
 	if(fd_fb < 0)
 	{
  		printf("can't open /dev/fb0\n");
  		return -1;
 	}
 	if(ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
 	{
  		printf("can't get var\n");
  		return -1;
 	}
 	if(ioctl(fd_fb, FBIOGET_FSCREENINFO, &fix))
 	{
  		printf("can't get fix\n");
  		return -1;
 	}
 	line_width = var.xres * var.bits_per_pixel /8;
 	pixel_width = var.bits_per_pixel / 8;
 	screen_size = var.xres * var.yres * pixel_width;
 	fbmem=(unsigned char*)mmap(NULL, screen_size,PROT_READ | PROT_WRITE, MAP_SHARED,fd_fb, 0);
 	if(fbmem == (unsigned char*)-1)
 	{
  		printf("can't map fbmem\n");b 		return -1;
 	}
 	memset(fbmem, 0, screen_size); // clear screen 
 
 	// show a vector font 
 	error = FT_Init_FreeType(&library);
 	error = FT_New_Face( library, argv[1], 0, &face );
 	slot = face->glyph;
        error = FT_Set_Pixel_Sizes(
            	face,   /* handle to face object */
            	24,      /* pixel_width           */
            	0);   /* pixel_height          */
            
    	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 );
    	
    	pen.x = (var.xres/2 ) * 64;
    	pen.y = (var.yres/2 ) * 64;
    	
    	FT_Set_Transform(
                face,       /* target face object    */
                &matrix,    /* pointer to 2x2 matrix */
                &pen);   /* pointer to 2d vector  */
    'aT_Load_Char( face, str1[0], FT_LOAD_RENDER );
    	my_draw_bitmap( &slot->bitmap,
                         slot->bitmap_left,
                         var.yres - slot->bitmap_top );
                              
        // 记得释放                      
 	munmap(fbmem, screen_size);
 	FT_Done_Face(face);
  	FT_Done_FreeType(library);
 	
 	return 0; 
}


void lcd_put_pixel(unsigned int x, unsigned y, unsigned int color)
{
	unsigned char* pen_8 = fbmem + y*line_width + x*pixel_width;
	unsigned ort* pen_16 = NULL;
 	unsigned int* pen_32 = NULL;
 	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:
  		{
   			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'z:
  		{
   			*pen_32 = color ;
  			break;
  		}
  		default:
  		{
   			printf("can't surport %dbpp\n",var.bits_per_pixel);
   			return ;
  		}
	}
}
void my_draw_bitmap( FT_Bitmap*  bitmap, FT_Int x, FT_Int  y)
{
	printf("x:%d, y:%d\n", x, 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   ||
  ao       			i >= var.xres || j >= var.yres )
        			continue;
      			lcd_put_pixel(i, j, bitmap->buffer[q * bitmap->width + p]);
    		}
  	}
}

好了,如何交叉编译并测试呢?

  1. tar xjf freetype-2.4.10.tar bz2
  2. .configure --host=arm-linux --prefix=xxxx
    (xxx是存放交叉编译工具链头文件和库的目录,一般来说是目录是相同的,我的就是。
    如果不是,则不需–prefix=xxx,在后面make install DESTDIR=$PWD/tmp 安装到临时目录
    然后再把freetype的库文件和库复制到交叉编译工具链对应的头文件和库目录下
    )
  3. make
  4. make DESTDIR=$PWD/tmp install (如果目录相同,DESTDIR不需要)

最后编译:
arm-linux-gcc -finput-charset=GBK -fexec-charset=GBK -o show_vector_font show_vector_font.c
-lfreetype -lm

将生成的show_vector_font测试文件和字体文件(可从C盘windows/fonts/目录下随便获取一个)复制
到开发板的根文件系统
另外,记得也要将freetype的动态库文件复制到根文件系统的/lib目录下

然后测试
./show_vector_font simsun.ttc 45

最后,看下面的测试结果图.

ok,这个简单例子只是掌握FreeType2的基本用法
下面来个更高级一些的用法,在LCD任意位置显示多行文字
测试效果:以逆时针旋转45°为例,其它角度也OK。要顺时针的话,要调整那个变换矩阵(我数学渣渣,留给你们:)
显示单个矢量字体

3 在LCD任意位置显示多行文字
有了前面显示单个矢量字体,对于显示多行文字就简单了,直接贴出代码,如下
备注:全部贴出太占篇幅了,也没必要。因此贴出关键代码,其它和显示单个矢量字体一样
   完整源码可从我的代码集合那篇博文获取
int main(int argc, char** argv)	
{	
	wchar_t *str = L"中国我爱你\n我爱你中国";
	/* ... */
	draw_lines(200, 100, str);
}
int draw_lines(unsigned int x, unsigned int y, wchar_t* str)
{
    if(x > var.xres || y > var.yres || !str)
       return -1;
    printf("xres:%d yres:%d\n",var.xres, var.yres);
    pen.x = x * 64;
    pen.y = (var.yres - y) * 64;
    
    int n;
    for(n = 0; n < wcslen(str)i; ++n)
    {
        FT_Set_Transform(
               face,       /* target face object    */
               0,
               &pen);   /* pointer to 2d vector  */
        FT_Load_Char( face, str[n], FT_LOAD_RENDER );
        my_draw_bitmap( &slot->bitmap,
                    slot->bitmap_left,
                    var.yres - slot->bitmap_top );
        pen.x += slot->advance.x;
        if(str[n] == '\n')
        {
            pen.y = (var.yres - y - 24)*64;
            pen.x = x * 64;
       b }
    }
}

测试效果:
显示多行文字
细心的朋友会发现,其实换行的时候,我给定了下一行文字的起点(即设置的pen.x pen.y)
打印汉字没问题,但是如果中英文参杂时,比如g,会不会出现混叠呢?
其实会的,这里有个解决办法,也是要讲解的重点——提取计算下一行(整行, 而不是单个字体)的最大宽和高
修改后的代码如下:

typedef struct TGlyph { 
 FT_UInt index; /* glyph index */ 
 FT_Vector pos; /* glyph origin on the baseline */ 
 FT_Glyph image; /* glyph image */ 
} TGlyph, *PGlyph; 

/* 从一行字符串中获取glyph保存到glyphs[]数组的image */
int Get_Glyphs_Frm_Wstr(FT_Face face, wchar_t *wstr, TGlyph glyphs[])          
{
    int n;
    PGlyph glyph = glyphs;
    int pen_x = 0;
    int pen_y = 0;
    int error;
    FT_GlyphSlot slot = face->glyph;
    for(n = 0; n < wcslen(wstr); n++)
    {
        glyph->index = FT_Get_Char_Index(face, wstr[n]);
        glyph->pos.x = pen_x;
        glyph->pos.y = pen_y;
        if(wstr[n] == '\n')  // 计算的字符串以换行符作为一行结束的标志
            break;
        error = FT_Load_Glyph(face, glyph->index, FT_LOAD_DEFAULT);
        if(error)
            continue;
        error = FT_Get_Glyph(face->glyph, &glyph->image);
        if(error)
            continue;
        FT_Glyph_Transform(glyph->image, 0, &glyph->pos);
        pen_x += slot->advance.x;
        glyph++;
    }
    return (glyph - glyphs);
}
/* 根据glyphs[]数组保存的glyph计算每个glyph的cbox,cbox里存有xMin xMax yMin yMax */
/* 然后依次比较每个glyph的cbox以获得xMin xMax yMin yMax,并把最终结果保存到abbox */
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 glyphs_bbox;
        FT_Glyph_Get_CBox(glyphs[n].image, FT_GLYPH_BBOX_TRUNCATE, &glyphs_bbox);
        if(glyphs_bbox.xMin < bbox.xMin)
            bbox.xMin = glyphs_bbox.xMin;
        if(glyphs_bbox.yMin < bbox.yMin)
            bbox.yMin = glyphs_bbox.yMin;
        if(glyphs_bbox.xMax > bbox.xMax)
            bbox.xMax = glyphs_bbox.xMax;
        if(glyphs_bbox.yMax > bbox.yMax)
            bbox.yMax = glyphs_bbox.yMax;
    }
    *abbox = bbox;
}
int draw_lines(unsigned int x, unsigned int y, wchar_t* str)
{
    FT_UInt num_glyphs;
    TGlyph glyphs[MAX_GLYPHS]; /* glyphs table */
    FT_BBox bbox;
    int line_box_width;
    int line_box_height;
    int count;
    
    if(x > var.xres || y > var.yres || !str)
       return -1;
    printf("xres:%d yres:%d\n",var.xres, var.yres);
    pen.x = x * 64;
    pen.y = (var.yres - y) * 64;
    int n;
    for(n = 0; n < wcslen(str); ++n)
    {
        if(str[n] == '\n')
        {
            n += 1;
            // 注意要获取上行的宽、高
            num_glyphs = Get_Glyphs_Frm_Wstr(face, &str[count], glyphs);
           
            compute_string_bbox(glyphs, num_glyphs, &bbox);
            line_box_height = bbox.yMax - bbox.yMin;
            line_box_width  = bbox.xMax - bbox.xMin;
           
            pen.y = pen.y -(line_box_height + 1) * 64; // 注意 -1 
            pen.x = x * 64;
            
	    count = num_glyphs;
        }
        FT_Set_Transform(
               face,       /* target face object    */
               0,
               &pen);   /* pointer to 2d vector  */
        FT_Load_Char( face, str[n], FT_LOAD_RENDER );
        my_draw_bitmap( &slot->bitmap,
                    slot->bitmap_left,
                    var.yres - slot->bitmap_top );
        pen.x += slot->advance.x;
    } 
}

注意在计算得到行高后,记得-1,不然会出现覆盖现象,如下图所示
覆盖现象,多行文字

-1之后,最终效果:
打印多行文字

至于为什么-1呢? 因为,比如说 23 至 8 差多少, 你不能说是 23-8=15吧,而应该是 23 -8 + 1 = 16才对,包括 8 本身。
因此,这里需要减一操作,不然覆盖了呀

再来看看,如果显示四行内容,能成功吗?看下图
LCD显示四行文字

好了,这篇文章告一段落,原本打算绘制图片的,考虑到篇幅问题,留给下篇博文吧 :)

如果有任何疑问、错误,欢迎指出来噢 :)
一起进步呀,加油!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值