02 使用freetype实现字体的显示

[toc]

***

# Q:Freetype是什么?

简单说,freetype 就是一个所谓的字体引擎,可以提供给我们针对 truetype 字体的统一接口来访问多种字体文件。

不同于我们之前使用点阵字模的方式,freetype 显示字体的原理是提取字体文件中对应的字模的关键点,然后用贝塞尔曲线对这些关键点进行对应的连接,形成一个闭合曲线,然后去填充闭合曲线。因为字体大小的改变对应到关键点位置的改变,而字体的改变也只是对应关键点和曲线连接方式的不同,因此通过这种方法可以实现动态调节字符大小和动态调节字符的字体的目的。

上述的内容仅仅是一个不严谨的概述,作为开发人员,实现的原理什么的不是我最关心的内容,可以拿来干嘛和怎么用才是我最关心的。可以拿来干嘛已经讲了,下面讲讲怎么用,非为两部分,显示一个字符和显示一行字符。

***

# Q: 如何使用 Freetype 进行字体的显示?

官方提供了一个例程,通过该例程可以实现在虚拟机串口控制台上打印任意大小任意字体的字符。但是,我们的最终目的是实现在开发板 LCD 上显示任意尺寸和字体的字符,因此在此就跳过在PC上分析的部分,直接分析在开发板上应该怎么用。

分为两个部分讲解:

- 在LCD上显示 1个 字符

- 在LCD上显示 1行 字符

首先需要进行环境的配置

##  Freetype 库的安装配置与编译

使用的文件请直接参考附件列表

1. **解压,配置,编译,安装**

```shell

tar xjf freetype-2.4.10.tar.bz2 

./configure --host=arm-linux

make

mkdir tmp

make DESTDIR=$PWD/tmp install

```

2. **拷贝相关的库到编译工具链的对应目录下**

- 首先查看编译工具链所在的目录,此处的 /usr/local/arm/4.3.2/bin 即为编译工具链所在的目录

```shell

book@book-desktop:/work/nfs_learning/ch3_01$ echo $PATH

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/arm/4.3.2/bin:/usr/local/arm/4.3.2/bin:/usr/local/arm/4.3.2/bin

```

- 把tmp/usr/local/lib/* 复制到 /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib

``` shell

sudo cp * /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib -d -rf

```

- 将动态库拷贝到开发板的根目录下

也就是这三个文件 <u>libfreetype.so libfreetype.so.6 libfreetype.so.6.9.0 </u>

```shell

book@book-desktop:~/100ask_learning/ch3_digtalpic/sample_code/02_freetype_siglechar_02_03_03/freetype-2.4.10/tmp/usr/local/lib$ ls *so*

libfreetype.so libfreetype.so.6 libfreetype.so.6.9.0

```

- 把tmp/usr/local/include/* 复制到 /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include

```shell

cp * /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include -rf

cd /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include

mv freetype2/freetype .

```

***

# Q:如何在开发板LCD上使用 freetype 显示 1个 字符?

参考官方提供的教程,总结出编写代码的步骤如下所示

1. 初始化 freetype 库

2. 加载字体的face

3. 设置字体的大小

4. 确定字符显示的位置以及角度

5. 加载字符的位图

6. 显示在LCD上

## 1. 初始化 freetype 库

使用函数 FT_Init_FreeType 进行初始化,函数原型如下所示

```c

FT_Init_FreeType( FT_Library *alibrary );

```

返回值如下

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

官方提供的例程如下所示

```c

FT_Library library; 

... 

error = FT_Init_FreeType( &library ); 

if ( error ) { 

    //... an error occurred during library initialization ... 

}

```

## 2. 加载字体的face

 这里的face 近似可以理解为字体的模型之类的东西,它从我们指定的字体文件中进行抽取。

使用函数 FT_New_Face 完成,其原型如下

```c

FT_New_Face( FT_Library library,

               const char* filepathname,

               FT_Long face_index,

               FT_Face *aface );

```

函数各个参数的含义如下

| 参数 | 含义 |

| ---- | - |

| FT_Library library | freetype库的描述符,在此face被创建 |

| const char* filepathname | 字体文件的路径(标准的c字符串形式) | 

| FT_Long face_index | face 的索引。官方对此的说明是,对于一些字体文件,其在单一的文件中嵌入了多个face,通过此处的索引来确定你想要加载的face是哪一个,在此,0总是可用的 | 

| FT_Face *aface | 一个指向描述符的指针,这个指针将用来描述一个新的face结构 | 

返回值如下

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

## 3. 设置字体的大小

 通过函数 FT_Set_Char_Size 或者 FT_Set_Pixel_Sizes 来完成

- **FT_Set_Char_Size** 设置字体的绝对大小,也就是显示的尺寸为物理尺寸,如x英寸

- **FT_Set_Pixel_Sizes **设置字体的像素大小,也就是占据多少个像素

- FT_Set_Char_Size

函数 FT_Set_Char_Size 的原型如下所示

```c

FT_Set_Char_Size( FT_Face     face,
                    FT_F26Dot6  char_width,
                    FT_F26Dot6  char_height,
                    FT_UInt     horz_resolution,
                    FT_UInt     vert_resolution );

```

参数含义如下所示
| 参数 | 含义 | 

| - | - | 

| FT_Face face face | 结构的描述符 | 

|  FT_F26Dot6 char_width  | 字体的宽度,以1/64point为单位,1point = 1/72inch,如果传入0的话,表示 char_width = char_height。允许char_width != char_height  | 

| FT_F26Dot6 char_height  | 同上,如果传入0的话,表示 char_width = char_height。允许char_width != char_height | 

|  FT_UInt horz_resolution  | 水平的分辨率,以像素每英寸,也就是dpi作为单位 | 

| FT_UInt vert_resolution  | 竖直方向分辨率,以像素每英寸为单位 | 

返回值如下

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

 官方提供的例程如下所示,需要注意的地方在于单位的转换

```c

/* use 50pt at 100dpi */

error = FT_Set_Char_Size( face, 50 * 64, 0,100, 0 );/* set character size */

```

- FT_Set_Pixel_Sizes

 函数 FT_Set_Pixel_Sizes 的原型如下所示

```c

  FT_Set_Pixel_Sizes( FT_Face face,

                    FT_UInt pixel_width,

                    FT_UInt pixel_height );

```

 其各个参数的含义如下所示

| 参数 | 含义 | 

| - | - | 

| FT_Face face | face结构的描述符 |

| FT_UInt pixel_width |  像素宽度,若传入0的话表示pixel_width = pixel_height |

| FT_UInt pixel_height  | 同上 |

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

 官方提供的例程如下所示,结果是将字符的像素尺寸设置为16*16

```c

error = FT_Set_Pixel_Sizes( face, /* handle to face object */ 

              0, /* pixel_width */ 

              16 ); /* pixel_height */

``` 

在LCD上,我们更加倾向于使用函数 FT_Set_Pixel_Sizes进行设置,因为这种设置方式更加符合我们的编程习惯,而且事实上,在LCD上显示我们并不需要固定的字体尺寸。

## 4. 确定字符显示的位置以及角度

位置的话很好理解,角度的话指的是对显示的字体进行一定角度的旋转,上述两个操作都通过函数 FT_Set_Transform 来实现,其原型如下所示

```c

FT_Set_Transform( FT_Face face,

                      FT_Matrix* matrix,

                      FT_Vector* delta );

```

 各个参数的含义概述如下所示

| 参数 | 含义 | 

| - | - | 

| FT_Face face | face结构的描述符 |

|  FT_Matrix* matrix | 指向FT_Matrix类型的结构体,该结构体描述了一个2*2的矩阵,用于描述字体旋转的角度,当设置为NULL 时代表不进行旋转 |

|   FT_Vector* delta | 指向转换向量的指针,用于描述字符显示的位置,详细可以参考脚注 |

  

- FT_Matrix* matrix

如果只是需要进行简单的角度(直角坐标系)旋转的话,可以使用类似下面的代码对矩阵进行设置

```c

 FT_Matrix matrix; /* transformation matrix */

   …

   double angle;

   …

   /* 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 );

```     

- FT_Vector* delta

其描述了字符显示的位置。需要注意的一点在于,开发板上使用的LCD坐标系与 freetype库 使用的坐标是是不同的,当在指定显示位置的时候,需要进行坐标的转换才可以保证字符在LCD的正确位置上进行显示。

如下图所示,freetype库 使用的坐标系称之为笛卡尔坐标系,也就是数学中最常用到的坐标系,原点在左下角;而在LCD上,我们认为原点在左上角。

所以对于LCD上某点(x',y')而言,其对应了笛卡尔坐标系中点(x', ymax'-y'),其中ymax'表示LCD的竖直分辨率。

另外需要注意的是,上图中的坐标是像素为单位进行描述的,但是在程序中,坐标是以 1/64 像素进行描述的,因此,在进行坐标转换的时候需要将目标像素乘上64再传入。  

```c

   FT_Vector pen;

   …

   /* 确定座标:

   * lcd_x = var.xres/2 + 8 + 16

   * lcd_y = var.yres/2 + 16

   * 笛卡尔座标系:

   * x = lcd_x = var.xres/2 + 8 + 16

   * y = var.yres - lcd_y = var.yres/2 - 16

   */

   pen.x = (var.xres/2 + 8 + 16) * 64;

   pen.y = (var.yres/2 - 16) * 64;

```   

 完整代码如下所示

```c

FT_Matrix matrix; /* transformation matrix */

FT_Vector pen; /* untransformed origin */

/* 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 );

/* 确定座标:

 * lcd_x = var.xres/2 + 8 + 16

 * lcd_y = var.yres/2 + 16

 * 笛卡尔座标系:

 * x = lcd_x = var.xres/2 + 8 + 16

 * y = var.yres - lcd_y = var.yres/2 - 16

 */

pen.x = (var.xres/2 + 8 + 16) * 64;

pen.y = (var.yres/2 - 16) * 64;

/* set transformation */

FT_Set_Transform( face, 0, &pen);

```  

  

## 5. 加载字符的位图

经过上面各个步骤的设定,这里开始加载字符的位图了,为倒数第二步

 加载通过函数 FT_Load_Char 实现,其原型如下所示

```c

    FT_Load_Char( FT_Face face,

                FT_ULong char_code,

                FT_Int32 load_flags );

```

 各个参数的含义如下所示

| 参数 | 含义 | 

| - | - | 

|  FT_Face face  | face结构的描述符 |

|  FT_ULong char_code[^3]  | 字符编码,根据当前加载到face结构中的字符集而定,一般而言是unicoe码 |

|  FT_Int32 load_flags  | 指定什么内容会被加载到这个字型中,例如是否拓展外边框,是否加载位图等,参数形如FT_LOAD_XXX,使用参数 FT_LOAD_RENDER 可以直接加载位图 |

- FT_ULong char_code

   关于字符编码的内容这里不在赘述了,百度相关内容即可了解。但是这里需要指出一点,对应混杂的字符串例如 char *str = "中abc"; 而言,汉字使用了两字节进行编码,而字母则仅仅使用了1字节进行编码,这样的字符串面临一个分割的问题,为了解决这个问题引入了所谓的宽字符的概念。

### 宽字符

所谓宽字符指的是一种数据类型,整体和 char* 类型定义字符串类似,不同的是,该类型存储的字符串统一使用 4字节 进行编码,存储字符的unicode码, 从而解决了分割的问题。

   

宽字符类型的字符串定义方式如下,其中 L 为前缀,不可省略。

```c

wchar_t *chinese_str = L"繁";

```

通过函数 wcslen 来获取字符串的长度,类似 strlen 的功能

```c

wcslen(chinese_str)

```

有了上述的概念,那么在显示一个汉字时就可以使用类似下面的模版来实现加载了

```c

    /* load glyph image into the slot (erase previous one) */

    error = FT_Load_Char( face, chinese_str[0], FT_LOAD_RENDER );

    if (error)

    {

        printf("FT_Load_Char error\n");

        return -1;

    }

```    

## 6. 显示在LCD上

这部分的内容完全是移植官方的例程。

官方例程在完成位图加载后,将位图数据存储在了一个二维数组中,然后通过打印该数组来查看效果。而事实上,我们的LCD也可以看作是一个二维数组,因此,修改官方例程,将数据存储到二维数组中转换为在对应位置打点就可以实现显示了

 代码如下所示

```c

 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;

     

     //printf("x = %d, y = %d\n", x, y);

     

     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;

             //image[j][i] |= bitmap->buffer[q * bitmap->width + p];

             lcd_put_pixel(i, j, bitmap->buffer[q * bitmap->width + p]);

         }

     }

 }

 …

 void main()

 {

          …

          FT_GlyphSlot slot;

          …

          slot = face->glyph;

          …

          draw_bitmap( &slot->bitmap,

                       slot->bitmap_left,

                       var.yres - slot->bitmap_top);

 …

 }

```

## 7. 编译

因为代码中有中文,所以在编译时需要指定字符集,另外还需要使用freetype的动态库和数学库。编译命令如下所示

```shell

arm-linux-gcc -finput-charset=GBK -fexec-charset=GBK -o show_font show_font.c -lfreetype -lm 

```

## 8. 测试

将编译好的可执行文件和字体文件拷贝到 NFS文件系统,开发板运行测试如下

```shell

/ # cd /mnt/nfs/

/mnt/nfs # ls

ch2 ch3_01 tslib

/mnt/nfs # cd ch3_01/

/mnt/nfs/ch3_01 # ls

HZK16 cfbfillrect.ko freetype_char show_font tmp

cfbcopyarea.ko cfbimgblt.ko lcd.ko simsun.ttc

/mnt/nfs/ch3_01 # ./show_font simsun.ttc

chinese code: d6 d0

/mnt/nfs/ch3_01 # ./freetype_char simsun.ttc

```

至此,显示一个中文字符的任务完成。

![](https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1534600712433&di=ec9758b41a77d7a530903cd32553c8c4&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F0127c0577e00620000012e7e12da0e.gif)

[toc]

***

# Q:Freetype是什么?

简单说,freetype 就是一个所谓的字体引擎,可以提供给我们针对 truetype 字体的统一接口来访问多种字体文件。

不同于我们之前使用点阵字模的方式,freetype 显示字体的原理是提取字体文件中对应的字模的关键点,然后用贝塞尔曲线对这些关键点进行对应的连接,形成一个闭合曲线,然后去填充闭合曲线。因为字体大小的改变对应到关键点位置的改变,而字体的改变也只是对应关键点和曲线连接方式的不同,因此通过这种方法可以实现动态调节字符大小和动态调节字符的字体的目的。

上述的内容仅仅是一个不严谨的概述,作为开发人员,实现的原理什么的不是我最关心的内容,可以拿来干嘛和怎么用才是我最关心的。可以拿来干嘛已经讲了,下面讲讲怎么用,非为两部分,显示一个字符和显示一行字符。

***

# Q: 如何使用 Freetype 进行字体的显示?

官方提供了一个例程,通过该例程可以实现在虚拟机串口控制台上打印任意大小任意字体的字符。但是,我们的最终目的是实现在开发板 LCD 上显示任意尺寸和字体的字符,因此在此就跳过在PC上分析的部分,直接分析在开发板上应该怎么用。

分为两个部分讲解:

- 在LCD上显示 1个 字符

- 在LCD上显示 1行 字符

首先需要进行环境的配置

##  Freetype 库的安装配置与编译

使用的文件请直接参考附件列表

1. **解压,配置,编译,安装**

```shell

tar xjf freetype-2.4.10.tar.bz2 

./configure --host=arm-linux

make

mkdir tmp

make DESTDIR=$PWD/tmp install

```

2. **拷贝相关的库到编译工具链的对应目录下**

- 首先查看编译工具链所在的目录,此处的 /usr/local/arm/4.3.2/bin 即为编译工具链所在的目录

```shell

book@book-desktop:/work/nfs_learning/ch3_01$ echo $PATH

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/arm/4.3.2/bin:/usr/local/arm/4.3.2/bin:/usr/local/arm/4.3.2/bin

```

- 把tmp/usr/local/lib/* 复制到 /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib

``` shell

sudo cp * /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib -d -rf

```

- 将动态库拷贝到开发板的根目录下

也就是这三个文件 <u>libfreetype.so libfreetype.so.6 libfreetype.so.6.9.0 </u>

```shell

book@book-desktop:~/100ask_learning/ch3_digtalpic/sample_code/02_freetype_siglechar_02_03_03/freetype-2.4.10/tmp/usr/local/lib$ ls *so*

libfreetype.so libfreetype.so.6 libfreetype.so.6.9.0

```

- 把tmp/usr/local/include/* 复制到 /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include

```shell

cp * /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include -rf

cd /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include

mv freetype2/freetype .

```

***

# Q:如何在开发板LCD上使用 freetype 显示 1个 字符?

参考官方提供的教程,总结出编写代码的步骤如下所示

1. 初始化 freetype 库

2. 加载字体的face

3. 设置字体的大小

4. 确定字符显示的位置以及角度

5. 加载字符的位图

6. 显示在LCD上

## 1. 初始化 freetype 库

使用函数 FT_Init_FreeType 进行初始化,函数原型如下所示

```c

FT_Init_FreeType( FT_Library *alibrary );

```

返回值如下

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

官方提供的例程如下所示

```c

FT_Library library; 

... 

error = FT_Init_FreeType( &library ); 

if ( error ) { 

    //... an error occurred during library initialization ... 

}

```

## 2. 加载字体的face

 这里的face 近似可以理解为字体的模型之类的东西,它从我们指定的字体文件中进行抽取。

使用函数 FT_New_Face 完成,其原型如下

```c

FT_New_Face( FT_Library library,

               const char* filepathname,

               FT_Long face_index,

               FT_Face *aface );

```

函数各个参数的含义如下

| 参数 | 含义 |

| ---- | - |

| FT_Library library | freetype库的描述符,在此face被创建 |

| const char* filepathname | 字体文件的路径(标准的c字符串形式) | 

| FT_Long face_index | face 的索引。官方对此的说明是,对于一些字体文件,其在单一的文件中嵌入了多个face,通过此处的索引来确定你想要加载的face是哪一个,在此,0总是可用的 | 

| FT_Face *aface | 一个指向描述符的指针,这个指针将用来描述一个新的face结构 | 

返回值如下

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

## 3. 设置字体的大小

 通过函数 FT_Set_Char_Size 或者 FT_Set_Pixel_Sizes 来完成

- **FT_Set_Char_Size** 设置字体的绝对大小,也就是显示的尺寸为物理尺寸,如x英寸

- **FT_Set_Pixel_Sizes **设置字体的像素大小,也就是占据多少个像素

- FT_Set_Char_Size

函数 FT_Set_Char_Size 的原型如下所示

```c

FT_Set_Char_Size( FT_Face     face,
                    FT_F26Dot6  char_width,
                    FT_F26Dot6  char_height,
                    FT_UInt     horz_resolution,
                    FT_UInt     vert_resolution );

```

参数含义如下所示
| 参数 | 含义 | 

| - | - | 

| FT_Face face face | 结构的描述符 | 

|  FT_F26Dot6 char_width  | 字体的宽度,以1/64point为单位,1point = 1/72inch,如果传入0的话,表示 char_width = char_height。允许char_width != char_height  | 

| FT_F26Dot6 char_height  | 同上,如果传入0的话,表示 char_width = char_height。允许char_width != char_height | 

|  FT_UInt horz_resolution  | 水平的分辨率,以像素每英寸,也就是dpi作为单位 | 

| FT_UInt vert_resolution  | 竖直方向分辨率,以像素每英寸为单位 | 

返回值如下

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

 官方提供的例程如下所示,需要注意的地方在于单位的转换

```c

/* use 50pt at 100dpi */

error = FT_Set_Char_Size( face, 50 * 64, 0,100, 0 );/* set character size */

```

- FT_Set_Pixel_Sizes

 函数 FT_Set_Pixel_Sizes 的原型如下所示

```c

  FT_Set_Pixel_Sizes( FT_Face face,

                    FT_UInt pixel_width,

                    FT_UInt pixel_height );

```

 其各个参数的含义如下所示

| 参数 | 含义 | 

| - | - | 

| FT_Face face | face结构的描述符 |

| FT_UInt pixel_width |  像素宽度,若传入0的话表示pixel_width = pixel_height |

| FT_UInt pixel_height  | 同上 |

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

 官方提供的例程如下所示,结果是将字符的像素尺寸设置为16*16

```c

error = FT_Set_Pixel_Sizes( face, /* handle to face object */ 

              0, /* pixel_width */ 

              16 ); /* pixel_height */

``` 

在LCD上,我们更加倾向于使用函数 FT_Set_Pixel_Sizes进行设置,因为这种设置方式更加符合我们的编程习惯,而且事实上,在LCD上显示我们并不需要固定的字体尺寸。

## 4. 确定字符显示的位置以及角度

位置的话很好理解,角度的话指的是对显示的字体进行一定角度的旋转,上述两个操作都通过函数 FT_Set_Transform 来实现,其原型如下所示

```c

FT_Set_Transform( FT_Face face,

                      FT_Matrix* matrix,

                      FT_Vector* delta );

```

 各个参数的含义概述如下所示

| 参数 | 含义 | 

| - | - | 

| FT_Face face | face结构的描述符 |

|  FT_Matrix* matrix | 指向FT_Matrix类型的结构体,该结构体描述了一个2*2的矩阵,用于描述字体旋转的角度,当设置为NULL 时代表不进行旋转 |

|   FT_Vector* delta | 指向转换向量的指针,用于描述字符显示的位置,详细可以参考脚注 |

  

- FT_Matrix* matrix

如果只是需要进行简单的角度(直角坐标系)旋转的话,可以使用类似下面的代码对矩阵进行设置

```c

 FT_Matrix matrix; /* transformation matrix */

   …

   double angle;

   …

   /* 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 );

```     

- FT_Vector* delta

其描述了字符显示的位置。需要注意的一点在于,开发板上使用的LCD坐标系与 freetype库 使用的坐标是是不同的,当在指定显示位置的时候,需要进行坐标的转换才可以保证字符在LCD的正确位置上进行显示。

如下图所示,freetype库 使用的坐标系称之为笛卡尔坐标系,也就是数学中最常用到的坐标系,原点在左下角;而在LCD上,我们认为原点在左上角。

所以对于LCD上某点(x',y')而言,其对应了笛卡尔坐标系中点(x', ymax'-y'),其中ymax'表示LCD的竖直分辨率。

另外需要注意的是,上图中的坐标是像素为单位进行描述的,但是在程序中,坐标是以 1/64 像素进行描述的,因此,在进行坐标转换的时候需要将目标像素乘上64再传入。  

```c

   FT_Vector pen;

   …

   /* 确定座标:

   * lcd_x = var.xres/2 + 8 + 16

   * lcd_y = var.yres/2 + 16

   * 笛卡尔座标系:

   * x = lcd_x = var.xres/2 + 8 + 16

   * y = var.yres - lcd_y = var.yres/2 - 16

   */

   pen.x = (var.xres/2 + 8 + 16) * 64;

   pen.y = (var.yres/2 - 16) * 64;

```   

 完整代码如下所示

```c

FT_Matrix matrix; /* transformation matrix */

FT_Vector pen; /* untransformed origin */

/* 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 );

/* 确定座标:

 * lcd_x = var.xres/2 + 8 + 16

 * lcd_y = var.yres/2 + 16

 * 笛卡尔座标系:

 * x = lcd_x = var.xres/2 + 8 + 16

 * y = var.yres - lcd_y = var.yres/2 - 16

 */

pen.x = (var.xres/2 + 8 + 16) * 64;

pen.y = (var.yres/2 - 16) * 64;

/* set transformation */

FT_Set_Transform( face, 0, &pen);

```  

  

## 5. 加载字符的位图

经过上面各个步骤的设定,这里开始加载字符的位图了,为倒数第二步

 加载通过函数 FT_Load_Char 实现,其原型如下所示

```c

    FT_Load_Char( FT_Face face,

                FT_ULong char_code,

                FT_Int32 load_flags );

```

 各个参数的含义如下所示

| 参数 | 含义 | 

| - | - | 

|  FT_Face face  | face结构的描述符 |

|  FT_ULong char_code[^3]  | 字符编码,根据当前加载到face结构中的字符集而定,一般而言是unicoe码 |

|  FT_Int32 load_flags  | 指定什么内容会被加载到这个字型中,例如是否拓展外边框,是否加载位图等,参数形如FT_LOAD_XXX,使用参数 FT_LOAD_RENDER 可以直接加载位图 |

- FT_ULong char_code

   关于字符编码的内容这里不在赘述了,百度相关内容即可了解。但是这里需要指出一点,对应混杂的字符串例如 char *str = "中abc"; 而言,汉字使用了两字节进行编码,而字母则仅仅使用了1字节进行编码,这样的字符串面临一个分割的问题,为了解决这个问题引入了所谓的宽字符的概念。

### 宽字符

所谓宽字符指的是一种数据类型,整体和 char* 类型定义字符串类似,不同的是,该类型存储的字符串统一使用 4字节 进行编码,存储字符的unicode码, 从而解决了分割的问题。

   

宽字符类型的字符串定义方式如下,其中 L 为前缀,不可省略。

```c

wchar_t *chinese_str = L"繁";

```

通过函数 wcslen 来获取字符串的长度,类似 strlen 的功能

```c

wcslen(chinese_str)

```

有了上述的概念,那么在显示一个汉字时就可以使用类似下面的模版来实现加载了

```c

    /* load glyph image into the slot (erase previous one) */

    error = FT_Load_Char( face, chinese_str[0], FT_LOAD_RENDER );

    if (error)

    {

        printf("FT_Load_Char error\n");

        return -1;

    }

```    

## 6. 显示在LCD上

这部分的内容完全是移植官方的例程。

官方例程在完成位图加载后,将位图数据存储在了一个二维数组中,然后通过打印该数组来查看效果。而事实上,我们的LCD也可以看作是一个二维数组,因此,修改官方例程,将数据存储到二维数组中转换为在对应位置打点就可以实现显示了

 代码如下所示

```c

 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;

     

     //printf("x = %d, y = %d\n", x, y);

     

     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;

             //image[j][i] |= bitmap->buffer[q * bitmap->width + p];

             lcd_put_pixel(i, j, bitmap->buffer[q * bitmap->width + p]);

         }

     }

 }

 …

 void main()

 {

          …

          FT_GlyphSlot slot;

          …

          slot = face->glyph;

          …

          draw_bitmap( &slot->bitmap,

                       slot->bitmap_left,

                       var.yres - slot->bitmap_top);

 …

 }

```

## 7. 编译

因为代码中有中文,所以在编译时需要指定字符集,另外还需要使用freetype的动态库和数学库。编译命令如下所示

```shell

arm-linux-gcc -finput-charset=GBK -fexec-charset=GBK -o show_font show_font.c -lfreetype -lm 

```

## 8. 测试

将编译好的可执行文件和字体文件拷贝到 NFS文件系统,开发板运行测试如下

```shell

/ # cd /mnt/nfs/

/mnt/nfs # ls

ch2 ch3_01 tslib

/mnt/nfs # cd ch3_01/

/mnt/nfs/ch3_01 # ls

HZK16 cfbfillrect.ko freetype_char show_font tmp

cfbcopyarea.ko cfbimgblt.ko lcd.ko simsun.ttc

/mnt/nfs/ch3_01 # ./show_font simsun.ttc

chinese code: d6 d0

/mnt/nfs/ch3_01 # ./freetype_char simsun.ttc

```

至此,显示一个中文字符的任务完成。

![](https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1534600712433&di=ec9758b41a77d7a530903cd32553c8c4&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F0127c0577e00620000012e7e12da0e.gif)

[toc]

***

# Q:Freetype是什么?

简单说,freetype 就是一个所谓的字体引擎,可以提供给我们针对 truetype 字体的统一接口来访问多种字体文件。

不同于我们之前使用点阵字模的方式,freetype 显示字体的原理是提取字体文件中对应的字模的关键点,然后用贝塞尔曲线对这些关键点进行对应的连接,形成一个闭合曲线,然后去填充闭合曲线。因为字体大小的改变对应到关键点位置的改变,而字体的改变也只是对应关键点和曲线连接方式的不同,因此通过这种方法可以实现动态调节字符大小和动态调节字符的字体的目的。

上述的内容仅仅是一个不严谨的概述,作为开发人员,实现的原理什么的不是我最关心的内容,可以拿来干嘛和怎么用才是我最关心的。可以拿来干嘛已经讲了,下面讲讲怎么用,非为两部分,显示一个字符和显示一行字符。

***

# Q: 如何使用 Freetype 进行字体的显示?

官方提供了一个例程,通过该例程可以实现在虚拟机串口控制台上打印任意大小任意字体的字符。但是,我们的最终目的是实现在开发板 LCD 上显示任意尺寸和字体的字符,因此在此就跳过在PC上分析的部分,直接分析在开发板上应该怎么用。

分为两个部分讲解:

- 在LCD上显示 1个 字符

- 在LCD上显示 1行 字符

首先需要进行环境的配置

##  Freetype 库的安装配置与编译

使用的文件请直接参考附件列表

1. **解压,配置,编译,安装**

```shell

tar xjf freetype-2.4.10.tar.bz2 

./configure --host=arm-linux

make

mkdir tmp

make DESTDIR=$PWD/tmp install

```

2. **拷贝相关的库到编译工具链的对应目录下**

- 首先查看编译工具链所在的目录,此处的 /usr/local/arm/4.3.2/bin 即为编译工具链所在的目录

```shell

book@book-desktop:/work/nfs_learning/ch3_01$ echo $PATH

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/arm/4.3.2/bin:/usr/local/arm/4.3.2/bin:/usr/local/arm/4.3.2/bin

```

- 把tmp/usr/local/lib/* 复制到 /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib

``` shell

sudo cp * /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib -d -rf

```

- 将动态库拷贝到开发板的根目录下

也就是这三个文件 <u>libfreetype.so libfreetype.so.6 libfreetype.so.6.9.0 </u>

```shell

book@book-desktop:~/100ask_learning/ch3_digtalpic/sample_code/02_freetype_siglechar_02_03_03/freetype-2.4.10/tmp/usr/local/lib$ ls *so*

libfreetype.so libfreetype.so.6 libfreetype.so.6.9.0

```

- 把tmp/usr/local/include/* 复制到 /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include

```shell

cp * /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include -rf

cd /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include

mv freetype2/freetype .

```

***

# Q:如何在开发板LCD上使用 freetype 显示 1个 字符?

参考官方提供的教程,总结出编写代码的步骤如下所示

1. 初始化 freetype 库

2. 加载字体的face

3. 设置字体的大小

4. 确定字符显示的位置以及角度

5. 加载字符的位图

6. 显示在LCD上

## 1. 初始化 freetype 库

使用函数 FT_Init_FreeType 进行初始化,函数原型如下所示

```c

FT_Init_FreeType( FT_Library *alibrary );

```

返回值如下

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

官方提供的例程如下所示

```c

FT_Library library; 

... 

error = FT_Init_FreeType( &library ); 

if ( error ) { 

    //... an error occurred during library initialization ... 

}

```

## 2. 加载字体的face

 这里的face 近似可以理解为字体的模型之类的东西,它从我们指定的字体文件中进行抽取。

使用函数 FT_New_Face 完成,其原型如下

```c

FT_New_Face( FT_Library library,

               const char* filepathname,

               FT_Long face_index,

               FT_Face *aface );

```

函数各个参数的含义如下

| 参数 | 含义 |

| ---- | - |

| FT_Library library | freetype库的描述符,在此face被创建 |

| const char* filepathname | 字体文件的路径(标准的c字符串形式) | 

| FT_Long face_index | face 的索引。官方对此的说明是,对于一些字体文件,其在单一的文件中嵌入了多个face,通过此处的索引来确定你想要加载的face是哪一个,在此,0总是可用的 | 

| FT_Face *aface | 一个指向描述符的指针,这个指针将用来描述一个新的face结构 | 

返回值如下

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

## 3. 设置字体的大小

 通过函数 FT_Set_Char_Size 或者 FT_Set_Pixel_Sizes 来完成

- **FT_Set_Char_Size** 设置字体的绝对大小,也就是显示的尺寸为物理尺寸,如x英寸

- **FT_Set_Pixel_Sizes **设置字体的像素大小,也就是占据多少个像素

- FT_Set_Char_Size

函数 FT_Set_Char_Size 的原型如下所示

```c

FT_Set_Char_Size( FT_Face     face,
                    FT_F26Dot6  char_width,
                    FT_F26Dot6  char_height,
                    FT_UInt     horz_resolution,
                    FT_UInt     vert_resolution );

```

参数含义如下所示
| 参数 | 含义 | 

| - | - | 

| FT_Face face face | 结构的描述符 | 

|  FT_F26Dot6 char_width  | 字体的宽度,以1/64point为单位,1point = 1/72inch,如果传入0的话,表示 char_width = char_height。允许char_width != char_height  | 

| FT_F26Dot6 char_height  | 同上,如果传入0的话,表示 char_width = char_height。允许char_width != char_height | 

|  FT_UInt horz_resolution  | 水平的分辨率,以像素每英寸,也就是dpi作为单位 | 

| FT_UInt vert_resolution  | 竖直方向分辨率,以像素每英寸为单位 | 

返回值如下

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

 官方提供的例程如下所示,需要注意的地方在于单位的转换

```c

/* use 50pt at 100dpi */

error = FT_Set_Char_Size( face, 50 * 64, 0,100, 0 );/* set character size */

```

- FT_Set_Pixel_Sizes

 函数 FT_Set_Pixel_Sizes 的原型如下所示

```c

  FT_Set_Pixel_Sizes( FT_Face face,

                    FT_UInt pixel_width,

                    FT_UInt pixel_height );

```

 其各个参数的含义如下所示

| 参数 | 含义 | 

| - | - | 

| FT_Face face | face结构的描述符 |

| FT_UInt pixel_width |  像素宽度,若传入0的话表示pixel_width = pixel_height |

| FT_UInt pixel_height  | 同上 |

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

 官方提供的例程如下所示,结果是将字符的像素尺寸设置为16*16

```c

error = FT_Set_Pixel_Sizes( face, /* handle to face object */ 

              0, /* pixel_width */ 

              16 ); /* pixel_height */

``` 

在LCD上,我们更加倾向于使用函数 FT_Set_Pixel_Sizes进行设置,因为这种设置方式更加符合我们的编程习惯,而且事实上,在LCD上显示我们并不需要固定的字体尺寸。

## 4. 确定字符显示的位置以及角度

位置的话很好理解,角度的话指的是对显示的字体进行一定角度的旋转,上述两个操作都通过函数 FT_Set_Transform 来实现,其原型如下所示

```c

FT_Set_Transform( FT_Face face,

                      FT_Matrix* matrix,

                      FT_Vector* delta );

```

 各个参数的含义概述如下所示

| 参数 | 含义 | 

| - | - | 

| FT_Face face | face结构的描述符 |

|  FT_Matrix* matrix | 指向FT_Matrix类型的结构体,该结构体描述了一个2*2的矩阵,用于描述字体旋转的角度,当设置为NULL 时代表不进行旋转 |

|   FT_Vector* delta | 指向转换向量的指针,用于描述字符显示的位置,详细可以参考脚注 |

  

- FT_Matrix* matrix

如果只是需要进行简单的角度(直角坐标系)旋转的话,可以使用类似下面的代码对矩阵进行设置

```c

 FT_Matrix matrix; /* transformation matrix */

   …

   double angle;

   …

   /* 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 );

```     

- FT_Vector* delta

其描述了字符显示的位置。需要注意的一点在于,开发板上使用的LCD坐标系与 freetype库 使用的坐标是是不同的,当在指定显示位置的时候,需要进行坐标的转换才可以保证字符在LCD的正确位置上进行显示。

如下图所示,freetype库 使用的坐标系称之为笛卡尔坐标系,也就是数学中最常用到的坐标系,原点在左下角;而在LCD上,我们认为原点在左上角。

所以对于LCD上某点(x',y')而言,其对应了笛卡尔坐标系中点(x', ymax'-y'),其中ymax'表示LCD的竖直分辨率。

另外需要注意的是,上图中的坐标是像素为单位进行描述的,但是在程序中,坐标是以 1/64 像素进行描述的,因此,在进行坐标转换的时候需要将目标像素乘上64再传入。  

```c

   FT_Vector pen;

   …

   /* 确定座标:

   * lcd_x = var.xres/2 + 8 + 16

   * lcd_y = var.yres/2 + 16

   * 笛卡尔座标系:

   * x = lcd_x = var.xres/2 + 8 + 16

   * y = var.yres - lcd_y = var.yres/2 - 16

   */

   pen.x = (var.xres/2 + 8 + 16) * 64;

   pen.y = (var.yres/2 - 16) * 64;

```   

 完整代码如下所示

```c

FT_Matrix matrix; /* transformation matrix */

FT_Vector pen; /* untransformed origin */

/* 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 );

/* 确定座标:

 * lcd_x = var.xres/2 + 8 + 16

 * lcd_y = var.yres/2 + 16

 * 笛卡尔座标系:

 * x = lcd_x = var.xres/2 + 8 + 16

 * y = var.yres - lcd_y = var.yres/2 - 16

 */

pen.x = (var.xres/2 + 8 + 16) * 64;

pen.y = (var.yres/2 - 16) * 64;

/* set transformation */

FT_Set_Transform( face, 0, &pen);

```  

  

## 5. 加载字符的位图

经过上面各个步骤的设定,这里开始加载字符的位图了,为倒数第二步

 加载通过函数 FT_Load_Char 实现,其原型如下所示

```c

    FT_Load_Char( FT_Face face,

                FT_ULong char_code,

                FT_Int32 load_flags );

```

 各个参数的含义如下所示

| 参数 | 含义 | 

| - | - | 

|  FT_Face face  | face结构的描述符 |

|  FT_ULong char_code[^3]  | 字符编码,根据当前加载到face结构中的字符集而定,一般而言是unicoe码 |

|  FT_Int32 load_flags  | 指定什么内容会被加载到这个字型中,例如是否拓展外边框,是否加载位图等,参数形如FT_LOAD_XXX,使用参数 FT_LOAD_RENDER 可以直接加载位图 |

- FT_ULong char_code

   关于字符编码的内容这里不在赘述了,百度相关内容即可了解。但是这里需要指出一点,对应混杂的字符串例如 char *str = "中abc"; 而言,汉字使用了两字节进行编码,而字母则仅仅使用了1字节进行编码,这样的字符串面临一个分割的问题,为了解决这个问题引入了所谓的宽字符的概念。

### 宽字符

所谓宽字符指的是一种数据类型,整体和 char* 类型定义字符串类似,不同的是,该类型存储的字符串统一使用 4字节 进行编码,存储字符的unicode码, 从而解决了分割的问题。

   

宽字符类型的字符串定义方式如下,其中 L 为前缀,不可省略。

```c

wchar_t *chinese_str = L"繁";

```

通过函数 wcslen 来获取字符串的长度,类似 strlen 的功能

```c

wcslen(chinese_str)

```

有了上述的概念,那么在显示一个汉字时就可以使用类似下面的模版来实现加载了

```c

    /* load glyph image into the slot (erase previous one) */

    error = FT_Load_Char( face, chinese_str[0], FT_LOAD_RENDER );

    if (error)

    {

        printf("FT_Load_Char error\n");

        return -1;

    }

```    

## 6. 显示在LCD上

这部分的内容完全是移植官方的例程。

官方例程在完成位图加载后,将位图数据存储在了一个二维数组中,然后通过打印该数组来查看效果。而事实上,我们的LCD也可以看作是一个二维数组,因此,修改官方例程,将数据存储到二维数组中转换为在对应位置打点就可以实现显示了

 代码如下所示

```c

 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;

     

     //printf("x = %d, y = %d\n", x, y);

     

     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;

             //image[j][i] |= bitmap->buffer[q * bitmap->width + p];

             lcd_put_pixel(i, j, bitmap->buffer[q * bitmap->width + p]);

         }

     }

 }

 …

 void main()

 {

          …

          FT_GlyphSlot slot;

          …

          slot = face->glyph;

          …

          draw_bitmap( &slot->bitmap,

                       slot->bitmap_left,

                       var.yres - slot->bitmap_top);

 …

 }

```

## 7. 编译

因为代码中有中文,所以在编译时需要指定字符集,另外还需要使用freetype的动态库和数学库。编译命令如下所示

```shell

arm-linux-gcc -finput-charset=GBK -fexec-charset=GBK -o show_font show_font.c -lfreetype -lm 

```

## 8. 测试

将编译好的可执行文件和字体文件拷贝到 NFS文件系统,开发板运行测试如下

```shell

/ # cd /mnt/nfs/

/mnt/nfs # ls

ch2 ch3_01 tslib

/mnt/nfs # cd ch3_01/

/mnt/nfs/ch3_01 # ls

HZK16 cfbfillrect.ko freetype_char show_font tmp

cfbcopyarea.ko cfbimgblt.ko lcd.ko simsun.ttc

/mnt/nfs/ch3_01 # ./show_font simsun.ttc

chinese code: d6 d0

/mnt/nfs/ch3_01 # ./freetype_char simsun.ttc

```

至此,显示一个中文字符的任务完成。

![](https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1534600712433&di=ec9758b41a77d7a530903cd32553c8c4&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F0127c0577e00620000012e7e12da0e.gif)

[toc]

***

# Q:Freetype是什么?

简单说,freetype 就是一个所谓的字体引擎,可以提供给我们针对 truetype 字体的统一接口来访问多种字体文件。

不同于我们之前使用点阵字模的方式,freetype 显示字体的原理是提取字体文件中对应的字模的关键点,然后用贝塞尔曲线对这些关键点进行对应的连接,形成一个闭合曲线,然后去填充闭合曲线。因为字体大小的改变对应到关键点位置的改变,而字体的改变也只是对应关键点和曲线连接方式的不同,因此通过这种方法可以实现动态调节字符大小和动态调节字符的字体的目的。

上述的内容仅仅是一个不严谨的概述,作为开发人员,实现的原理什么的不是我最关心的内容,可以拿来干嘛和怎么用才是我最关心的。可以拿来干嘛已经讲了,下面讲讲怎么用,非为两部分,显示一个字符和显示一行字符。

***

# Q: 如何使用 Freetype 进行字体的显示?

官方提供了一个例程,通过该例程可以实现在虚拟机串口控制台上打印任意大小任意字体的字符。但是,我们的最终目的是实现在开发板 LCD 上显示任意尺寸和字体的字符,因此在此就跳过在PC上分析的部分,直接分析在开发板上应该怎么用。

分为两个部分讲解:

- 在LCD上显示 1个 字符

- 在LCD上显示 1行 字符

首先需要进行环境的配置

##  Freetype 库的安装配置与编译

使用的文件请直接参考附件列表

1. **解压,配置,编译,安装**

```shell

tar xjf freetype-2.4.10.tar.bz2 

./configure --host=arm-linux

make

mkdir tmp

make DESTDIR=$PWD/tmp install

```

2. **拷贝相关的库到编译工具链的对应目录下**

- 首先查看编译工具链所在的目录,此处的 /usr/local/arm/4.3.2/bin 即为编译工具链所在的目录

```shell

book@book-desktop:/work/nfs_learning/ch3_01$ echo $PATH

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/arm/4.3.2/bin:/usr/local/arm/4.3.2/bin:/usr/local/arm/4.3.2/bin

```

- 把tmp/usr/local/lib/* 复制到 /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib

``` shell

sudo cp * /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib -d -rf

```

- 将动态库拷贝到开发板的根目录下

也就是这三个文件 <u>libfreetype.so libfreetype.so.6 libfreetype.so.6.9.0 </u>

```shell

book@book-desktop:~/100ask_learning/ch3_digtalpic/sample_code/02_freetype_siglechar_02_03_03/freetype-2.4.10/tmp/usr/local/lib$ ls *so*

libfreetype.so libfreetype.so.6 libfreetype.so.6.9.0

```

- 把tmp/usr/local/include/* 复制到 /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include

```shell

cp * /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include -rf

cd /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include

mv freetype2/freetype .

```

***

# Q:如何在开发板LCD上使用 freetype 显示 1个 字符?

参考官方提供的教程,总结出编写代码的步骤如下所示

1. 初始化 freetype 库

2. 加载字体的face

3. 设置字体的大小

4. 确定字符显示的位置以及角度

5. 加载字符的位图

6. 显示在LCD上

## 1. 初始化 freetype 库

使用函数 FT_Init_FreeType 进行初始化,函数原型如下所示

```c

FT_Init_FreeType( FT_Library *alibrary );

```

返回值如下

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

官方提供的例程如下所示

```c

FT_Library library; 

... 

error = FT_Init_FreeType( &library ); 

if ( error ) { 

    //... an error occurred during library initialization ... 

}

```

## 2. 加载字体的face

 这里的face 近似可以理解为字体的模型之类的东西,它从我们指定的字体文件中进行抽取。

使用函数 FT_New_Face 完成,其原型如下

```c

FT_New_Face( FT_Library library,

               const char* filepathname,

               FT_Long face_index,

               FT_Face *aface );

```

函数各个参数的含义如下

| 参数 | 含义 |

| ---- | - |

| FT_Library library | freetype库的描述符,在此face被创建 |

| const char* filepathname | 字体文件的路径(标准的c字符串形式) | 

| FT_Long face_index | face 的索引。官方对此的说明是,对于一些字体文件,其在单一的文件中嵌入了多个face,通过此处的索引来确定你想要加载的face是哪一个,在此,0总是可用的 | 

| FT_Face *aface | 一个指向描述符的指针,这个指针将用来描述一个新的face结构 | 

返回值如下

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

## 3. 设置字体的大小

 通过函数 FT_Set_Char_Size 或者 FT_Set_Pixel_Sizes 来完成

- **FT_Set_Char_Size** 设置字体的绝对大小,也就是显示的尺寸为物理尺寸,如x英寸

- **FT_Set_Pixel_Sizes **设置字体的像素大小,也就是占据多少个像素

- FT_Set_Char_Size

函数 FT_Set_Char_Size 的原型如下所示

```c

FT_Set_Char_Size( FT_Face     face,
                    FT_F26Dot6  char_width,
                    FT_F26Dot6  char_height,
                    FT_UInt     horz_resolution,
                    FT_UInt     vert_resolution );

```

参数含义如下所示
| 参数 | 含义 | 

| - | - | 

| FT_Face face face | 结构的描述符 | 

|  FT_F26Dot6 char_width  | 字体的宽度,以1/64point为单位,1point = 1/72inch,如果传入0的话,表示 char_width = char_height。允许char_width != char_height  | 

| FT_F26Dot6 char_height  | 同上,如果传入0的话,表示 char_width = char_height。允许char_width != char_height | 

|  FT_UInt horz_resolution  | 水平的分辨率,以像素每英寸,也就是dpi作为单位 | 

| FT_UInt vert_resolution  | 竖直方向分辨率,以像素每英寸为单位 | 

返回值如下

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

 官方提供的例程如下所示,需要注意的地方在于单位的转换

```c

/* use 50pt at 100dpi */

error = FT_Set_Char_Size( face, 50 * 64, 0,100, 0 );/* set character size */

```

- FT_Set_Pixel_Sizes

 函数 FT_Set_Pixel_Sizes 的原型如下所示

```c

  FT_Set_Pixel_Sizes( FT_Face face,

                    FT_UInt pixel_width,

                    FT_UInt pixel_height );

```

 其各个参数的含义如下所示

| 参数 | 含义 | 

| - | - | 

| FT_Face face | face结构的描述符 |

| FT_UInt pixel_width |  像素宽度,若传入0的话表示pixel_width = pixel_height |

| FT_UInt pixel_height  | 同上 |

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

 官方提供的例程如下所示,结果是将字符的像素尺寸设置为16*16

```c

error = FT_Set_Pixel_Sizes( face, /* handle to face object */ 

              0, /* pixel_width */ 

              16 ); /* pixel_height */

``` 

在LCD上,我们更加倾向于使用函数 FT_Set_Pixel_Sizes进行设置,因为这种设置方式更加符合我们的编程习惯,而且事实上,在LCD上显示我们并不需要固定的字体尺寸。

## 4. 确定字符显示的位置以及角度

位置的话很好理解,角度的话指的是对显示的字体进行一定角度的旋转,上述两个操作都通过函数 FT_Set_Transform 来实现,其原型如下所示

```c

FT_Set_Transform( FT_Face face,

                      FT_Matrix* matrix,

                      FT_Vector* delta );

```

 各个参数的含义概述如下所示

| 参数 | 含义 | 

| - | - | 

| FT_Face face | face结构的描述符 |

|  FT_Matrix* matrix | 指向FT_Matrix类型的结构体,该结构体描述了一个2*2的矩阵,用于描述字体旋转的角度,当设置为NULL 时代表不进行旋转 |

|   FT_Vector* delta | 指向转换向量的指针,用于描述字符显示的位置,详细可以参考脚注 |

  

- FT_Matrix* matrix

如果只是需要进行简单的角度(直角坐标系)旋转的话,可以使用类似下面的代码对矩阵进行设置

```c

 FT_Matrix matrix; /* transformation matrix */

   …

   double angle;

   …

   /* 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 );

```     

- FT_Vector* delta

其描述了字符显示的位置。需要注意的一点在于,开发板上使用的LCD坐标系与 freetype库 使用的坐标是是不同的,当在指定显示位置的时候,需要进行坐标的转换才可以保证字符在LCD的正确位置上进行显示。

如下图所示,freetype库 使用的坐标系称之为笛卡尔坐标系,也就是数学中最常用到的坐标系,原点在左下角;而在LCD上,我们认为原点在左上角。

所以对于LCD上某点(x',y')而言,其对应了笛卡尔坐标系中点(x', ymax'-y'),其中ymax'表示LCD的竖直分辨率。

另外需要注意的是,上图中的坐标是像素为单位进行描述的,但是在程序中,坐标是以 1/64 像素进行描述的,因此,在进行坐标转换的时候需要将目标像素乘上64再传入。  

```c

   FT_Vector pen;

   …

   /* 确定座标:

   * lcd_x = var.xres/2 + 8 + 16

   * lcd_y = var.yres/2 + 16

   * 笛卡尔座标系:

   * x = lcd_x = var.xres/2 + 8 + 16

   * y = var.yres - lcd_y = var.yres/2 - 16

   */

   pen.x = (var.xres/2 + 8 + 16) * 64;

   pen.y = (var.yres/2 - 16) * 64;

```   

 完整代码如下所示

```c

FT_Matrix matrix; /* transformation matrix */

FT_Vector pen; /* untransformed origin */

/* 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 );

/* 确定座标:

 * lcd_x = var.xres/2 + 8 + 16

 * lcd_y = var.yres/2 + 16

 * 笛卡尔座标系:

 * x = lcd_x = var.xres/2 + 8 + 16

 * y = var.yres - lcd_y = var.yres/2 - 16

 */

pen.x = (var.xres/2 + 8 + 16) * 64;

pen.y = (var.yres/2 - 16) * 64;

/* set transformation */

FT_Set_Transform( face, 0, &pen);

```  

  

## 5. 加载字符的位图

经过上面各个步骤的设定,这里开始加载字符的位图了,为倒数第二步

 加载通过函数 FT_Load_Char 实现,其原型如下所示

```c

    FT_Load_Char( FT_Face face,

                FT_ULong char_code,

                FT_Int32 load_flags );

```

 各个参数的含义如下所示

| 参数 | 含义 | 

| - | - | 

|  FT_Face face  | face结构的描述符 |

|  FT_ULong char_code[^3]  | 字符编码,根据当前加载到face结构中的字符集而定,一般而言是unicoe码 |

|  FT_Int32 load_flags  | 指定什么内容会被加载到这个字型中,例如是否拓展外边框,是否加载位图等,参数形如FT_LOAD_XXX,使用参数 FT_LOAD_RENDER 可以直接加载位图 |

- FT_ULong char_code

   关于字符编码的内容这里不在赘述了,百度相关内容即可了解。但是这里需要指出一点,对应混杂的字符串例如 char *str = "中abc"; 而言,汉字使用了两字节进行编码,而字母则仅仅使用了1字节进行编码,这样的字符串面临一个分割的问题,为了解决这个问题引入了所谓的宽字符的概念。

### 宽字符

所谓宽字符指的是一种数据类型,整体和 char* 类型定义字符串类似,不同的是,该类型存储的字符串统一使用 4字节 进行编码,存储字符的unicode码, 从而解决了分割的问题。

   

宽字符类型的字符串定义方式如下,其中 L 为前缀,不可省略。

```c

wchar_t *chinese_str = L"繁";

```

通过函数 wcslen 来获取字符串的长度,类似 strlen 的功能

```c

wcslen(chinese_str)

```

有了上述的概念,那么在显示一个汉字时就可以使用类似下面的模版来实现加载了

```c

    /* load glyph image into the slot (erase previous one) */

    error = FT_Load_Char( face, chinese_str[0], FT_LOAD_RENDER );

    if (error)

    {

        printf("FT_Load_Char error\n");

        return -1;

    }

```    

## 6. 显示在LCD上

这部分的内容完全是移植官方的例程。

官方例程在完成位图加载后,将位图数据存储在了一个二维数组中,然后通过打印该数组来查看效果。而事实上,我们的LCD也可以看作是一个二维数组,因此,修改官方例程,将数据存储到二维数组中转换为在对应位置打点就可以实现显示了

 代码如下所示

```c

 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;

     

     //printf("x = %d, y = %d\n", x, y);

     

     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;

             //image[j][i] |= bitmap->buffer[q * bitmap->width + p];

             lcd_put_pixel(i, j, bitmap->buffer[q * bitmap->width + p]);

         }

     }

 }

 …

 void main()

 {

          …

          FT_GlyphSlot slot;

          …

          slot = face->glyph;

          …

          draw_bitmap( &slot->bitmap,

                       slot->bitmap_left,

                       var.yres - slot->bitmap_top);

 …

 }

```

## 7. 编译

因为代码中有中文,所以在编译时需要指定字符集,另外还需要使用freetype的动态库和数学库。编译命令如下所示

```shell

arm-linux-gcc -finput-charset=GBK -fexec-charset=GBK -o show_font show_font.c -lfreetype -lm 

```

## 8. 测试

将编译好的可执行文件和字体文件拷贝到 NFS文件系统,开发板运行测试如下

```shell

/ # cd /mnt/nfs/

/mnt/nfs # ls

ch2 ch3_01 tslib

/mnt/nfs # cd ch3_01/

/mnt/nfs/ch3_01 # ls

HZK16 cfbfillrect.ko freetype_char show_font tmp

cfbcopyarea.ko cfbimgblt.ko lcd.ko simsun.ttc

/mnt/nfs/ch3_01 # ./show_font simsun.ttc

chinese code: d6 d0

/mnt/nfs/ch3_01 # ./freetype_char simsun.ttc

```

至此,显示一个中文字符的任务完成。

![](https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1534600712433&di=ec9758b41a77d7a530903cd32553c8c4&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F0127c0577e00620000012e7e12da0e.gif)

[toc]

***

# Q:Freetype是什么?

简单说,freetype 就是一个所谓的字体引擎,可以提供给我们针对 truetype 字体的统一接口来访问多种字体文件。

不同于我们之前使用点阵字模的方式,freetype 显示字体的原理是提取字体文件中对应的字模的关键点,然后用贝塞尔曲线对这些关键点进行对应的连接,形成一个闭合曲线,然后去填充闭合曲线。因为字体大小的改变对应到关键点位置的改变,而字体的改变也只是对应关键点和曲线连接方式的不同,因此通过这种方法可以实现动态调节字符大小和动态调节字符的字体的目的。

上述的内容仅仅是一个不严谨的概述,作为开发人员,实现的原理什么的不是我最关心的内容,可以拿来干嘛和怎么用才是我最关心的。可以拿来干嘛已经讲了,下面讲讲怎么用,非为两部分,显示一个字符和显示一行字符。

***

# Q: 如何使用 Freetype 进行字体的显示?

官方提供了一个例程,通过该例程可以实现在虚拟机串口控制台上打印任意大小任意字体的字符。但是,我们的最终目的是实现在开发板 LCD 上显示任意尺寸和字体的字符,因此在此就跳过在PC上分析的部分,直接分析在开发板上应该怎么用。

分为两个部分讲解:

- 在LCD上显示 1个 字符

- 在LCD上显示 1行 字符

首先需要进行环境的配置

##  Freetype 库的安装配置与编译

使用的文件请直接参考附件列表

1. **解压,配置,编译,安装**

```shell

tar xjf freetype-2.4.10.tar.bz2 

./configure --host=arm-linux

make

mkdir tmp

make DESTDIR=$PWD/tmp install

```

2. **拷贝相关的库到编译工具链的对应目录下**

- 首先查看编译工具链所在的目录,此处的 /usr/local/arm/4.3.2/bin 即为编译工具链所在的目录

```shell

book@book-desktop:/work/nfs_learning/ch3_01$ echo $PATH

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/arm/4.3.2/bin:/usr/local/arm/4.3.2/bin:/usr/local/arm/4.3.2/bin

```

- 把tmp/usr/local/lib/* 复制到 /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib

``` shell

sudo cp * /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib -d -rf

```

- 将动态库拷贝到开发板的根目录下

也就是这三个文件 <u>libfreetype.so libfreetype.so.6 libfreetype.so.6.9.0 </u>

```shell

book@book-desktop:~/100ask_learning/ch3_digtalpic/sample_code/02_freetype_siglechar_02_03_03/freetype-2.4.10/tmp/usr/local/lib$ ls *so*

libfreetype.so libfreetype.so.6 libfreetype.so.6.9.0

```

- 把tmp/usr/local/include/* 复制到 /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include

```shell

cp * /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include -rf

cd /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include

mv freetype2/freetype .

```

***

# Q:如何在开发板LCD上使用 freetype 显示 1个 字符?

参考官方提供的教程,总结出编写代码的步骤如下所示

1. 初始化 freetype 库

2. 加载字体的face

3. 设置字体的大小

4. 确定字符显示的位置以及角度

5. 加载字符的位图

6. 显示在LCD上

## 1. 初始化 freetype 库

使用函数 FT_Init_FreeType 进行初始化,函数原型如下所示

```c

FT_Init_FreeType( FT_Library *alibrary );

```

返回值如下

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

官方提供的例程如下所示

```c

FT_Library library; 

... 

error = FT_Init_FreeType( &library ); 

if ( error ) { 

    //... an error occurred during library initialization ... 

}

```

## 2. 加载字体的face

 这里的face 近似可以理解为字体的模型之类的东西,它从我们指定的字体文件中进行抽取。

使用函数 FT_New_Face 完成,其原型如下

```c

FT_New_Face( FT_Library library,

               const char* filepathname,

               FT_Long face_index,

               FT_Face *aface );

```

函数各个参数的含义如下

| 参数 | 含义 |

| ---- | - |

| FT_Library library | freetype库的描述符,在此face被创建 |

| const char* filepathname | 字体文件的路径(标准的c字符串形式) | 

| FT_Long face_index | face 的索引。官方对此的说明是,对于一些字体文件,其在单一的文件中嵌入了多个face,通过此处的索引来确定你想要加载的face是哪一个,在此,0总是可用的 | 

| FT_Face *aface | 一个指向描述符的指针,这个指针将用来描述一个新的face结构 | 

返回值如下

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

## 3. 设置字体的大小

 通过函数 FT_Set_Char_Size 或者 FT_Set_Pixel_Sizes 来完成

- **FT_Set_Char_Size** 设置字体的绝对大小,也就是显示的尺寸为物理尺寸,如x英寸

- **FT_Set_Pixel_Sizes **设置字体的像素大小,也就是占据多少个像素

- FT_Set_Char_Size

函数 FT_Set_Char_Size 的原型如下所示

```c

FT_Set_Char_Size( FT_Face     face,
                    FT_F26Dot6  char_width,
                    FT_F26Dot6  char_height,
                    FT_UInt     horz_resolution,
                    FT_UInt     vert_resolution );

```

参数含义如下所示
| 参数 | 含义 | 

| - | - | 

| FT_Face face face | 结构的描述符 | 

|  FT_F26Dot6 char_width  | 字体的宽度,以1/64point为单位,1point = 1/72inch,如果传入0的话,表示 char_width = char_height。允许char_width != char_height  | 

| FT_F26Dot6 char_height  | 同上,如果传入0的话,表示 char_width = char_height。允许char_width != char_height | 

|  FT_UInt horz_resolution  | 水平的分辨率,以像素每英寸,也就是dpi作为单位 | 

| FT_UInt vert_resolution  | 竖直方向分辨率,以像素每英寸为单位 | 

返回值如下

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

 官方提供的例程如下所示,需要注意的地方在于单位的转换

```c

/* use 50pt at 100dpi */

error = FT_Set_Char_Size( face, 50 * 64, 0,100, 0 );/* set character size */

```

- FT_Set_Pixel_Sizes

 函数 FT_Set_Pixel_Sizes 的原型如下所示

```c

  FT_Set_Pixel_Sizes( FT_Face face,

                    FT_UInt pixel_width,

                    FT_UInt pixel_height );

```

 其各个参数的含义如下所示

| 参数 | 含义 | 

| - | - | 

| FT_Face face | face结构的描述符 |

| FT_UInt pixel_width |  像素宽度,若传入0的话表示pixel_width = pixel_height |

| FT_UInt pixel_height  | 同上 |

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

 官方提供的例程如下所示,结果是将字符的像素尺寸设置为16*16

```c

error = FT_Set_Pixel_Sizes( face, /* handle to face object */ 

              0, /* pixel_width */ 

              16 ); /* pixel_height */

``` 

在LCD上,我们更加倾向于使用函数 FT_Set_Pixel_Sizes进行设置,因为这种设置方式更加符合我们的编程习惯,而且事实上,在LCD上显示我们并不需要固定的字体尺寸。

## 4. 确定字符显示的位置以及角度

位置的话很好理解,角度的话指的是对显示的字体进行一定角度的旋转,上述两个操作都通过函数 FT_Set_Transform 来实现,其原型如下所示

```c

FT_Set_Transform( FT_Face face,

                      FT_Matrix* matrix,

                      FT_Vector* delta );

```

 各个参数的含义概述如下所示

| 参数 | 含义 | 

| - | - | 

| FT_Face face | face结构的描述符 |

|  FT_Matrix* matrix | 指向FT_Matrix类型的结构体,该结构体描述了一个2*2的矩阵,用于描述字体旋转的角度,当设置为NULL 时代表不进行旋转 |

|   FT_Vector* delta | 指向转换向量的指针,用于描述字符显示的位置,详细可以参考脚注 |

  

- FT_Matrix* matrix

如果只是需要进行简单的角度(直角坐标系)旋转的话,可以使用类似下面的代码对矩阵进行设置

```c

 FT_Matrix matrix; /* transformation matrix */

   …

   double angle;

   …

   /* 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 );

```     

- FT_Vector* delta

其描述了字符显示的位置。需要注意的一点在于,开发板上使用的LCD坐标系与 freetype库 使用的坐标是是不同的,当在指定显示位置的时候,需要进行坐标的转换才可以保证字符在LCD的正确位置上进行显示。

如下图所示,freetype库 使用的坐标系称之为笛卡尔坐标系,也就是数学中最常用到的坐标系,原点在左下角;而在LCD上,我们认为原点在左上角。

所以对于LCD上某点(x',y')而言,其对应了笛卡尔坐标系中点(x', ymax'-y'),其中ymax'表示LCD的竖直分辨率。

另外需要注意的是,上图中的坐标是像素为单位进行描述的,但是在程序中,坐标是以 1/64 像素进行描述的,因此,在进行坐标转换的时候需要将目标像素乘上64再传入。  

```c

   FT_Vector pen;

   …

   /* 确定座标:

   * lcd_x = var.xres/2 + 8 + 16

   * lcd_y = var.yres/2 + 16

   * 笛卡尔座标系:

   * x = lcd_x = var.xres/2 + 8 + 16

   * y = var.yres - lcd_y = var.yres/2 - 16

   */

   pen.x = (var.xres/2 + 8 + 16) * 64;

   pen.y = (var.yres/2 - 16) * 64;

```   

 完整代码如下所示

```c

FT_Matrix matrix; /* transformation matrix */

FT_Vector pen; /* untransformed origin */

/* 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 );

/* 确定座标:

 * lcd_x = var.xres/2 + 8 + 16

 * lcd_y = var.yres/2 + 16

 * 笛卡尔座标系:

 * x = lcd_x = var.xres/2 + 8 + 16

 * y = var.yres - lcd_y = var.yres/2 - 16

 */

pen.x = (var.xres/2 + 8 + 16) * 64;

pen.y = (var.yres/2 - 16) * 64;

/* set transformation */

FT_Set_Transform( face, 0, &pen);

```  

  

## 5. 加载字符的位图

经过上面各个步骤的设定,这里开始加载字符的位图了,为倒数第二步

 加载通过函数 FT_Load_Char 实现,其原型如下所示

```c

    FT_Load_Char( FT_Face face,

                FT_ULong char_code,

                FT_Int32 load_flags );

```

 各个参数的含义如下所示

| 参数 | 含义 | 

| - | - | 

|  FT_Face face  | face结构的描述符 |

|  FT_ULong char_code[^3]  | 字符编码,根据当前加载到face结构中的字符集而定,一般而言是unicoe码 |

|  FT_Int32 load_flags  | 指定什么内容会被加载到这个字型中,例如是否拓展外边框,是否加载位图等,参数形如FT_LOAD_XXX,使用参数 FT_LOAD_RENDER 可以直接加载位图 |

- FT_ULong char_code

   关于字符编码的内容这里不在赘述了,百度相关内容即可了解。但是这里需要指出一点,对应混杂的字符串例如 char *str = "中abc"; 而言,汉字使用了两字节进行编码,而字母则仅仅使用了1字节进行编码,这样的字符串面临一个分割的问题,为了解决这个问题引入了所谓的宽字符的概念。

### 宽字符

所谓宽字符指的是一种数据类型,整体和 char* 类型定义字符串类似,不同的是,该类型存储的字符串统一使用 4字节 进行编码,存储字符的unicode码, 从而解决了分割的问题。

   

宽字符类型的字符串定义方式如下,其中 L 为前缀,不可省略。

```c

wchar_t *chinese_str = L"繁";

```

通过函数 wcslen 来获取字符串的长度,类似 strlen 的功能

```c

wcslen(chinese_str)

```

有了上述的概念,那么在显示一个汉字时就可以使用类似下面的模版来实现加载了

```c

    /* load glyph image into the slot (erase previous one) */

    error = FT_Load_Char( face, chinese_str[0], FT_LOAD_RENDER );

    if (error)

    {

        printf("FT_Load_Char error\n");

        return -1;

    }

```    

## 6. 显示在LCD上

这部分的内容完全是移植官方的例程。

官方例程在完成位图加载后,将位图数据存储在了一个二维数组中,然后通过打印该数组来查看效果。而事实上,我们的LCD也可以看作是一个二维数组,因此,修改官方例程,将数据存储到二维数组中转换为在对应位置打点就可以实现显示了

 代码如下所示

```c

 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;

     

     //printf("x = %d, y = %d\n", x, y);

     

     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;

             //image[j][i] |= bitmap->buffer[q * bitmap->width + p];

             lcd_put_pixel(i, j, bitmap->buffer[q * bitmap->width + p]);

         }

     }

 }

 …

 void main()

 {

          …

          FT_GlyphSlot slot;

          …

          slot = face->glyph;

          …

          draw_bitmap( &slot->bitmap,

                       slot->bitmap_left,

                       var.yres - slot->bitmap_top);

 …

 }

```

## 7. 编译

因为代码中有中文,所以在编译时需要指定字符集,另外还需要使用freetype的动态库和数学库。编译命令如下所示

```shell

arm-linux-gcc -finput-charset=GBK -fexec-charset=GBK -o show_font show_font.c -lfreetype -lm 

```

## 8. 测试

将编译好的可执行文件和字体文件拷贝到 NFS文件系统,开发板运行测试如下

```shell

/ # cd /mnt/nfs/

/mnt/nfs # ls

ch2 ch3_01 tslib

/mnt/nfs # cd ch3_01/

/mnt/nfs/ch3_01 # ls

HZK16 cfbfillrect.ko freetype_char show_font tmp

cfbcopyarea.ko cfbimgblt.ko lcd.ko simsun.ttc

/mnt/nfs/ch3_01 # ./show_font simsun.ttc

chinese code: d6 d0

/mnt/nfs/ch3_01 # ./freetype_char simsun.ttc

```

至此,显示一个中文字符的任务完成。

![](https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1534600712433&di=ec9758b41a77d7a530903cd32553c8c4&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F0127c0577e00620000012e7e12da0e.gif)

[toc]

***

# Q:Freetype是什么?

简单说,freetype 就是一个所谓的字体引擎,可以提供给我们针对 truetype 字体的统一接口来访问多种字体文件。

不同于我们之前使用点阵字模的方式,freetype 显示字体的原理是提取字体文件中对应的字模的关键点,然后用贝塞尔曲线对这些关键点进行对应的连接,形成一个闭合曲线,然后去填充闭合曲线。因为字体大小的改变对应到关键点位置的改变,而字体的改变也只是对应关键点和曲线连接方式的不同,因此通过这种方法可以实现动态调节字符大小和动态调节字符的字体的目的。

上述的内容仅仅是一个不严谨的概述,作为开发人员,实现的原理什么的不是我最关心的内容,可以拿来干嘛和怎么用才是我最关心的。可以拿来干嘛已经讲了,下面讲讲怎么用,非为两部分,显示一个字符和显示一行字符。

***

# Q: 如何使用 Freetype 进行字体的显示?

官方提供了一个例程,通过该例程可以实现在虚拟机串口控制台上打印任意大小任意字体的字符。但是,我们的最终目的是实现在开发板 LCD 上显示任意尺寸和字体的字符,因此在此就跳过在PC上分析的部分,直接分析在开发板上应该怎么用。

分为两个部分讲解:

- 在LCD上显示 1个 字符

- 在LCD上显示 1行 字符

首先需要进行环境的配置

##  Freetype 库的安装配置与编译

使用的文件请直接参考附件列表

1. **解压,配置,编译,安装**

```shell

tar xjf freetype-2.4.10.tar.bz2 

./configure --host=arm-linux

make

mkdir tmp

make DESTDIR=$PWD/tmp install

```

2. **拷贝相关的库到编译工具链的对应目录下**

- 首先查看编译工具链所在的目录,此处的 /usr/local/arm/4.3.2/bin 即为编译工具链所在的目录

```shell

book@book-desktop:/work/nfs_learning/ch3_01$ echo $PATH

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/arm/4.3.2/bin:/usr/local/arm/4.3.2/bin:/usr/local/arm/4.3.2/bin

```

- 把tmp/usr/local/lib/* 复制到 /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib

``` shell

sudo cp * /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib -d -rf

```

- 将动态库拷贝到开发板的根目录下

也就是这三个文件 <u>libfreetype.so libfreetype.so.6 libfreetype.so.6.9.0 </u>

```shell

book@book-desktop:~/100ask_learning/ch3_digtalpic/sample_code/02_freetype_siglechar_02_03_03/freetype-2.4.10/tmp/usr/local/lib$ ls *so*

libfreetype.so libfreetype.so.6 libfreetype.so.6.9.0

```

- 把tmp/usr/local/include/* 复制到 /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include

```shell

cp * /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include -rf

cd /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include

mv freetype2/freetype .

```

***

# Q:如何在开发板LCD上使用 freetype 显示 1个 字符?

参考官方提供的教程,总结出编写代码的步骤如下所示

1. 初始化 freetype 库

2. 加载字体的face

3. 设置字体的大小

4. 确定字符显示的位置以及角度

5. 加载字符的位图

6. 显示在LCD上

## 1. 初始化 freetype 库

使用函数 FT_Init_FreeType 进行初始化,函数原型如下所示

```c

FT_Init_FreeType( FT_Library *alibrary );

```

返回值如下

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

官方提供的例程如下所示

```c

FT_Library library; 

... 

error = FT_Init_FreeType( &library ); 

if ( error ) { 

    //... an error occurred during library initialization ... 

}

```

## 2. 加载字体的face

 这里的face 近似可以理解为字体的模型之类的东西,它从我们指定的字体文件中进行抽取。

使用函数 FT_New_Face 完成,其原型如下

```c

FT_New_Face( FT_Library library,

               const char* filepathname,

               FT_Long face_index,

               FT_Face *aface );

```

函数各个参数的含义如下

| 参数 | 含义 |

| ---- | - |

| FT_Library library | freetype库的描述符,在此face被创建 |

| const char* filepathname | 字体文件的路径(标准的c字符串形式) | 

| FT_Long face_index | face 的索引。官方对此的说明是,对于一些字体文件,其在单一的文件中嵌入了多个face,通过此处的索引来确定你想要加载的face是哪一个,在此,0总是可用的 | 

| FT_Face *aface | 一个指向描述符的指针,这个指针将用来描述一个新的face结构 | 

返回值如下

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

## 3. 设置字体的大小

 通过函数 FT_Set_Char_Size 或者 FT_Set_Pixel_Sizes 来完成

- **FT_Set_Char_Size** 设置字体的绝对大小,也就是显示的尺寸为物理尺寸,如x英寸

- **FT_Set_Pixel_Sizes **设置字体的像素大小,也就是占据多少个像素

- FT_Set_Char_Size

函数 FT_Set_Char_Size 的原型如下所示

```c

FT_Set_Char_Size( FT_Face     face,
                    FT_F26Dot6  char_width,
                    FT_F26Dot6  char_height,
                    FT_UInt     horz_resolution,
                    FT_UInt     vert_resolution );

```

参数含义如下所示
| 参数 | 含义 | 

| - | - | 

| FT_Face face face | 结构的描述符 | 

|  FT_F26Dot6 char_width  | 字体的宽度,以1/64point为单位,1point = 1/72inch,如果传入0的话,表示 char_width = char_height。允许char_width != char_height  | 

| FT_F26Dot6 char_height  | 同上,如果传入0的话,表示 char_width = char_height。允许char_width != char_height | 

|  FT_UInt horz_resolution  | 水平的分辨率,以像素每英寸,也就是dpi作为单位 | 

| FT_UInt vert_resolution  | 竖直方向分辨率,以像素每英寸为单位 | 

返回值如下

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

 官方提供的例程如下所示,需要注意的地方在于单位的转换

```c

/* use 50pt at 100dpi */

error = FT_Set_Char_Size( face, 50 * 64, 0,100, 0 );/* set character size */

```

- FT_Set_Pixel_Sizes

 函数 FT_Set_Pixel_Sizes 的原型如下所示

```c

  FT_Set_Pixel_Sizes( FT_Face face,

                    FT_UInt pixel_width,

                    FT_UInt pixel_height );

```

 其各个参数的含义如下所示

| 参数 | 含义 | 

| - | - | 

| FT_Face face | face结构的描述符 |

| FT_UInt pixel_width |  像素宽度,若传入0的话表示pixel_width = pixel_height |

| FT_UInt pixel_height  | 同上 |

| 代码 | 含义 |

| -- | -- |

| 0 | 初始化成功 |

| 其他 | 失败,代表错误代码 |

 官方提供的例程如下所示,结果是将字符的像素尺寸设置为16*16

```c

error = FT_Set_Pixel_Sizes( face, /* handle to face object */ 

              0, /* pixel_width */ 

              16 ); /* pixel_height */

``` 

在LCD上,我们更加倾向于使用函数 FT_Set_Pixel_Sizes进行设置,因为这种设置方式更加符合我们的编程习惯,而且事实上,在LCD上显示我们并不需要固定的字体尺寸。

## 4. 确定字符显示的位置以及角度

位置的话很好理解,角度的话指的是对显示的字体进行一定角度的旋转,上述两个操作都通过函数 FT_Set_Transform 来实现,其原型如下所示

```c

FT_Set_Transform( FT_Face face,

                      FT_Matrix* matrix,

                      FT_Vector* delta );

```

 各个参数的含义概述如下所示

| 参数 | 含义 | 

| - | - | 

| FT_Face face | face结构的描述符 |

|  FT_Matrix* matrix | 指向FT_Matrix类型的结构体,该结构体描述了一个2*2的矩阵,用于描述字体旋转的角度,当设置为NULL 时代表不进行旋转 |

|   FT_Vector* delta | 指向转换向量的指针,用于描述字符显示的位置,详细可以参考脚注 |

  

- FT_Matrix* matrix

如果只是需要进行简单的角度(直角坐标系)旋转的话,可以使用类似下面的代码对矩阵进行设置

```c

 FT_Matrix matrix; /* transformation matrix */

   …

   double angle;

   …

   /* 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 );

```     

- FT_Vector* delta

其描述了字符显示的位置。需要注意的一点在于,开发板上使用的LCD坐标系与 freetype库 使用的坐标是是不同的,当在指定显示位置的时候,需要进行坐标的转换才可以保证字符在LCD的正确位置上进行显示。

如下图所示,freetype库 使用的坐标系称之为笛卡尔坐标系,也就是数学中最常用到的坐标系,原点在左下角;而在LCD上,我们认为原点在左上角。

所以对于LCD上某点(x',y')而言,其对应了笛卡尔坐标系中点(x', ymax'-y'),其中ymax'表示LCD的竖直分辨率。

另外需要注意的是,上图中的坐标是像素为单位进行描述的,但是在程序中,坐标是以 1/64 像素进行描述的,因此,在进行坐标转换的时候需要将目标像素乘上64再传入。  

```c

   FT_Vector pen;

   …

   /* 确定座标:

   * lcd_x = var.xres/2 + 8 + 16

   * lcd_y = var.yres/2 + 16

   * 笛卡尔座标系:

   * x = lcd_x = var.xres/2 + 8 + 16

   * y = var.yres - lcd_y = var.yres/2 - 16

   */

   pen.x = (var.xres/2 + 8 + 16) * 64;

   pen.y = (var.yres/2 - 16) * 64;

```   

 完整代码如下所示

```c

FT_Matrix matrix; /* transformation matrix */

FT_Vector pen; /* untransformed origin */

/* 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 );

/* 确定座标:

 * lcd_x = var.xres/2 + 8 + 16

 * lcd_y = var.yres/2 + 16

 * 笛卡尔座标系:

 * x = lcd_x = var.xres/2 + 8 + 16

 * y = var.yres - lcd_y = var.yres/2 - 16

 */

pen.x = (var.xres/2 + 8 + 16) * 64;

pen.y = (var.yres/2 - 16) * 64;

/* set transformation */

FT_Set_Transform( face, 0, &pen);

```  

  

## 5. 加载字符的位图

经过上面各个步骤的设定,这里开始加载字符的位图了,为倒数第二步

 加载通过函数 FT_Load_Char 实现,其原型如下所示

```c

    FT_Load_Char( FT_Face face,

                FT_ULong char_code,

                FT_Int32 load_flags );

```

 各个参数的含义如下所示

| 参数 | 含义 | 

| - | - | 

|  FT_Face face  | face结构的描述符 |

|  FT_ULong char_code[^3]  | 字符编码,根据当前加载到face结构中的字符集而定,一般而言是unicoe码 |

|  FT_Int32 load_flags  | 指定什么内容会被加载到这个字型中,例如是否拓展外边框,是否加载位图等,参数形如FT_LOAD_XXX,使用参数 FT_LOAD_RENDER 可以直接加载位图 |

- FT_ULong char_code

   关于字符编码的内容这里不在赘述了,百度相关内容即可了解。但是这里需要指出一点,对应混杂的字符串例如 char *str = "中abc"; 而言,汉字使用了两字节进行编码,而字母则仅仅使用了1字节进行编码,这样的字符串面临一个分割的问题,为了解决这个问题引入了所谓的宽字符的概念。

### 宽字符

所谓宽字符指的是一种数据类型,整体和 char* 类型定义字符串类似,不同的是,该类型存储的字符串统一使用 4字节 进行编码,存储字符的unicode码, 从而解决了分割的问题。

   

宽字符类型的字符串定义方式如下,其中 L 为前缀,不可省略。

```c

wchar_t *chinese_str = L"繁";

```

通过函数 wcslen 来获取字符串的长度,类似 strlen 的功能

```c

wcslen(chinese_str)

```

有了上述的概念,那么在显示一个汉字时就可以使用类似下面的模版来实现加载了

```c

    /* load glyph image into the slot (erase previous one) */

    error = FT_Load_Char( face, chinese_str[0], FT_LOAD_RENDER );

    if (error)

    {

        printf("FT_Load_Char error\n");

        return -1;

    }

```    

## 6. 显示在LCD上

这部分的内容完全是移植官方的例程。

官方例程在完成位图加载后,将位图数据存储在了一个二维数组中,然后通过打印该数组来查看效果。而事实上,我们的LCD也可以看作是一个二维数组,因此,修改官方例程,将数据存储到二维数组中转换为在对应位置打点就可以实现显示了

 代码如下所示

```c

 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;

     

     //printf("x = %d, y = %d\n", x, y);

     

     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;

             //image[j][i] |= bitmap->buffer[q * bitmap->width + p];

             lcd_put_pixel(i, j, bitmap->buffer[q * bitmap->width + p]);

         }

     }

 }

 …

 void main()

 {

          …

          FT_GlyphSlot slot;

          …

          slot = face->glyph;

          …

          draw_bitmap( &slot->bitmap,

                       slot->bitmap_left,

                       var.yres - slot->bitmap_top);

 …

 }

```

## 7. 编译

因为代码中有中文,所以在编译时需要指定字符集,另外还需要使用freetype的动态库和数学库。编译命令如下所示

```shell

arm-linux-gcc -finput-charset=GBK -fexec-charset=GBK -o show_font show_font.c -lfreetype -lm 

```

## 8. 测试

将编译好的可执行文件和字体文件拷贝到 NFS文件系统,开发板运行测试如下

```shell

/ # cd /mnt/nfs/

/mnt/nfs # ls

ch2 ch3_01 tslib

/mnt/nfs # cd ch3_01/

/mnt/nfs/ch3_01 # ls

HZK16 cfbfillrect.ko freetype_char show_font tmp

cfbcopyarea.ko cfbimgblt.ko lcd.ko simsun.ttc

/mnt/nfs/ch3_01 # ./show_font simsun.ttc

chinese code: d6 d0

/mnt/nfs/ch3_01 # ./freetype_char simsun.ttc

```

至此,显示一个中文字符的任务完成。

![](https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1534600712433&di=ec9758b41a77d7a530903cd32553c8c4&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F0127c0577e00620000012e7e12da0e.gif)

Q:Freetype是什么?
Q: 如何使用 Freetype 进行字体的显示?
Freetype 库的安装配置与编译
Q:如何在开发板LCD上使用 freetype 显示 1个 字符?
1. 初始化 freetype 库
2. 加载字体的face
3. 设置字体的大小
4. 确定字符显示的位置以及角度
5. 加载字符的位图
宽字符
6. 显示在LCD上
7. 编译
8. 测试


Q:Freetype是什么?

简单说,freetype 就是一个所谓的字体引擎,可以提供给我们针对 truetype 字体的统一接口来访问多种字体文件。
不同于我们之前使用点阵字模的方式,freetype 显示字体的原理是提取字体文件中对应的字模的关键点,然后用贝塞尔曲线对这些关键点进行对应的连接,形成一个闭合曲线,然后去填充闭合曲线。因为字体大小的改变对应到关键点位置的改变,而字体的改变也只是对应关键点和曲线连接方式的不同,因此通过这种方法可以实现动态调节字符大小和动态调节字符的字体的目的。
上述的内容仅仅是一个不严谨的概述,作为开发人员,实现的原理什么的不是我最关心的内容,可以拿来干嘛和怎么用才是我最关心的。可以拿来干嘛已经讲了,下面讲讲怎么用,非为两部分,显示一个字符和显示一行字符。


Q: 如何使用 Freetype 进行字体的显示?

官方提供了一个例程,通过该例程可以实现在虚拟机串口控制台上打印任意大小任意字体的字符。但是,我们的最终目的是实现在开发板 LCD 上显示任意尺寸和字体的字符,因此在此就跳过在PC上分析的部分,直接分析在开发板上应该怎么用。

分为两个部分讲解:

  • 在LCD上显示 1个 字符
  • 在LCD上显示 1行 字符

首先需要进行环境的配置

Freetype 库的安装配置与编译

使用的文件请直接参考附件列表

  1. 解压,配置,编译,安装
tar xjf freetype-2.4.10.tar.bz2 
./configure --host=arm-linux
make
mkdir tmp
make DESTDIR=$PWD/tmp install
  1. 拷贝相关的库到编译工具链的对应目录下
  • 首先查看编译工具链所在的目录,此处的 /usr/local/arm/4.3.2/bin 即为编译工具链所在的目录
book@book-desktop:/work/nfs_learning/ch3_01$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/arm/4.3.2/bin:/usr/local/arm/4.3.2/bin:/usr/local/arm/4.3.2/bin
  • 把tmp/usr/local/lib/* 复制到 /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib
sudo cp * /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib -d -rf
  • 将动态库拷贝到开发板的根目录下
    也就是这三个文件 libfreetype.so libfreetype.so.6 libfreetype.so.6.9.0
book@book-desktop:~/100ask_learning/ch3_digtalpic/sample_code/02_freetype_siglechar_02_03_03/freetype-2.4.10/tmp/usr/local/lib$ ls *so*
libfreetype.so libfreetype.so.6 libfreetype.so.6.9.0
  • 把tmp/usr/local/include/* 复制到 /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include
cp * /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include -rf
cd /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include
mv freetype2/freetype .

Q:如何在开发板LCD上使用 freetype 显示 1个 字符?

参考官方提供的教程,总结出编写代码的步骤如下所示

  1. 初始化 freetype 库
  2. 加载字体的face
  3. 设置字体的大小
  4. 确定字符显示的位置以及角度
  5. 加载字符的位图
  6. 显示在LCD上

1. 初始化 freetype 库

使用函数 FT_Init_FreeType 进行初始化,函数原型如下所示

FT_Init_FreeType( FT_Library *alibrary );

返回值如下

代码 含义
0 初始化成功
其他 失败,代表错误代码

官方提供的例程如下所示

FT_Library library; 
... 
error = FT_Init_FreeType( &library ); 
if ( error ) { 
    //... an error occurred during library initialization ... 
}

2. 加载字体的face

这里的face 近似可以理解为字体的模型之类的东西,它从我们指定的字体文件中进行抽取。
使用函数 FT_New_Face 完成,其原型如下

FT_New_Face( FT_Library library,
               const char* filepathname,
               FT_Long face_index,
               FT_Face *aface );

函数各个参数的含义如下

参数 含义
FT_Library library freetype库的描述符,在此face被创建
const char* filepathname 字体文件的路径(标准的c字符串形式)
FT_Long face_index face 的索引。官方对此的说明是,对于一些字体文件,其在单一的文件中嵌入了多个face,通过此处的索引来确定你想要加载的face是哪一个,在此,0总是可用的
FT_Face *aface 一个指向描述符的指针,这个指针将用来描述一个新的face结构

返回值如下

代码 含义
0 初始化成功
其他 失败,代表错误代码

3. 设置字体的大小

通过函数 FT_Set_Char_Size 或者 FT_Set_Pixel_Sizes 来完成

  • FT_Set_Char_Size 设置字体的绝对大小,也就是显示的尺寸为物理尺寸,如x英寸
  • FT_Set_Pixel_Sizes 设置字体的像素大小,也就是占据多少个像素

  • FT_Set_Char_Size
    函数 FT_Set_Char_Size 的原型如下所示

FT_Set_Char_Size( FT_Face     face,
                    FT_F26Dot6  char_width,
                    FT_F26Dot6  char_height,
                    FT_UInt     horz_resolution,
                    FT_UInt     vert_resolution );

参数含义如下所示

参数 含义
FT_Face face face 结构的描述符
FT_F26Dot6 char_width 字体的宽度,以1/64point为单位,1point = 1/72inch,如果传入0的话,表示 char_width = char_height。允许char_width != char_height
FT_F26Dot6 char_height 同上,如果传入0的话,表示 char_width = char_height。允许char_width != char_height
FT_UInt horz_resolution 水平的分辨率,以像素每英寸,也就是dpi作为单位
FT_UInt vert_resolution 竖直方向分辨率,以像素每英寸为单位

返回值如下

代码 含义
0 初始化成功
其他 失败,代表错误代码

官方提供的例程如下所示,需要注意的地方在于单位的转换

/* use 50pt at 100dpi */
error = FT_Set_Char_Size( face, 50 * 64, 0,100, 0 );/* set character size */
  • FT_Set_Pixel_Sizes
    函数 FT_Set_Pixel_Sizes 的原型如下所示
  FT_Set_Pixel_Sizes( FT_Face face,
                    FT_UInt pixel_width,
                    FT_UInt pixel_height );

其各个参数的含义如下所示

参数 含义
FT_Face face face结构的描述符
FT_UInt pixel_width 像素宽度,若传入0的话表示pixel_width = pixel_height
FT_UInt pixel_height 同上
代码 含义
-- --
0 初始化成功
其他 失败,代表错误代码

官方提供的例程如下所示,结果是将字符的像素尺寸设置为16*16

error = FT_Set_Pixel_Sizes( face, /* handle to face object */ 
              0, /* pixel_width */ 
              16 ); /* pixel_height */

在LCD上,我们更加倾向于使用函数 FT_Set_Pixel_Sizes进行设置,因为这种设置方式更加符合我们的编程习惯,而且事实上,在LCD上显示我们并不需要固定的字体尺寸。

4. 确定字符显示的位置以及角度

位置的话很好理解,角度的话指的是对显示的字体进行一定角度的旋转,上述两个操作都通过函数 FT_Set_Transform 来实现,其原型如下所示

FT_Set_Transform( FT_Face face,
                      FT_Matrix* matrix,
                      FT_Vector* delta );

各个参数的含义概述如下所示

参数 含义
FT_Face face face结构的描述符
FT_Matrix* matrix 指向FT_Matrix类型的结构体,该结构体描述了一个2*2的矩阵,用于描述字体旋转的角度,当设置为NULL 时代表不进行旋转
FT_Vector* delta 指向转换向量的指针,用于描述字符显示的位置,详细可以参考脚注
  • FT_Matrix* matrix
    如果只是需要进行简单的角度(直角坐标系)旋转的话,可以使用类似下面的代码对矩阵进行设置
 FT_Matrix matrix; /* transformation matrix */
   …
   double angle;
   …
   /* 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 );
  • FT_Vector* delta
    其描述了字符显示的位置。需要注意的一点在于,开发板上使用的LCD坐标系与 freetype库 使用的坐标是是不同的,当在指定显示位置的时候,需要进行坐标的转换才可以保证字符在LCD的正确位置上进行显示。
    如下图所示,freetype库 使用的坐标系称之为笛卡尔坐标系,也就是数学中最常用到的坐标系,原点在左下角;而在LCD上,我们认为原点在左上角。

    所以对于LCD上某点(x',y')而言,其对应了笛卡尔坐标系中点(x', ymax'-y'),其中ymax'表示LCD的竖直分辨率。
    另外需要注意的是,上图中的坐标是像素为单位进行描述的,但是在程序中,坐标是以 1/64 像素进行描述的,因此,在进行坐标转换的时候需要将目标像素乘上64再传入。
   FT_Vector pen;
   …
   /* 确定座标:
   * lcd_x = var.xres/2 + 8 + 16
   * lcd_y = var.yres/2 + 16
   * 笛卡尔座标系:
   * x = lcd_x = var.xres/2 + 8 + 16
   * y = var.yres - lcd_y = var.yres/2 - 16
   */
   pen.x = (var.xres/2 + 8 + 16) * 64;
   pen.y = (var.yres/2 - 16) * 64;

完整代码如下所示

FT_Matrix matrix; /* transformation matrix */
FT_Vector pen; /* untransformed origin */
/* 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 );
/* 确定座标:
 * lcd_x = var.xres/2 + 8 + 16
 * lcd_y = var.yres/2 + 16
 * 笛卡尔座标系:
 * x = lcd_x = var.xres/2 + 8 + 16
 * y = var.yres - lcd_y = var.yres/2 - 16
 */
pen.x = (var.xres/2 + 8 + 16) * 64;
pen.y = (var.yres/2 - 16) * 64;
/* set transformation */
FT_Set_Transform( face, 0, &pen);

5. 加载字符的位图

经过上面各个步骤的设定,这里开始加载字符的位图了,为倒数第二步
加载通过函数 FT_Load_Char 实现,其原型如下所示

    FT_Load_Char( FT_Face face,
                FT_ULong char_code,
                FT_Int32 load_flags );

各个参数的含义如下所示

参数 含义
FT_Face face face结构的描述符
FT_ULong char_code[^3] 字符编码,根据当前加载到face结构中的字符集而定,一般而言是unicoe码
FT_Int32 load_flags 指定什么内容会被加载到这个字型中,例如是否拓展外边框,是否加载位图等,参数形如FT_LOAD_XXX,使用参数 FT_LOAD_RENDER 可以直接加载位图
  • FT_ULong char_code
    关于字符编码的内容这里不在赘述了,百度相关内容即可了解。但是这里需要指出一点,对应混杂的字符串例如 char *str = "中abc"; 而言,汉字使用了两字节进行编码,而字母则仅仅使用了1字节进行编码,这样的字符串面临一个分割的问题,为了解决这个问题引入了所谓的宽字符的概念。

宽字符

所谓宽字符指的是一种数据类型,整体和 char* 类型定义字符串类似,不同的是,该类型存储的字符串统一使用 4字节 进行编码,存储字符的unicode码, 从而解决了分割的问题。

宽字符类型的字符串定义方式如下,其中 L 为前缀,不可省略。

wchar_t *chinese_str = L"繁";

通过函数 wcslen 来获取字符串的长度,类似 strlen 的功能

wcslen(chinese_str)

有了上述的概念,那么在显示一个汉字时就可以使用类似下面的模版来实现加载了

    /* load glyph image into the slot (erase previous one) */
    error = FT_Load_Char( face, chinese_str[0], FT_LOAD_RENDER );
    if (error)
    {
        printf("FT_Load_Char error\n");
        return -1;
    }

6. 显示在LCD上

这部分的内容完全是移植官方的例程。
官方例程在完成位图加载后,将位图数据存储在了一个二维数组中,然后通过打印该数组来查看效果。而事实上,我们的LCD也可以看作是一个二维数组,因此,修改官方例程,将数据存储到二维数组中转换为在对应位置打点就可以实现显示了

代码如下所示

 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;
     //printf("x = %d, y = %d\n", x, y);
     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;
             //image[j][i] |= bitmap->buffer[q * bitmap->width + p];
             lcd_put_pixel(i, j, bitmap->buffer[q * bitmap->width + p]);
         }
     }
 }
 void main()
 {
          …
          FT_GlyphSlot slot;
          …
          slot = face->glyph;
          …
          draw_bitmap( &slot->bitmap,
                       slot->bitmap_left,
                       var.yres - slot->bitmap_top);
 }

7. 编译

因为代码中有中文,所以在编译时需要指定字符集,另外还需要使用freetype的动态库和数学库。编译命令如下所示

arm-linux-gcc -finput-charset=GBK -fexec-charset=GBK -o show_font show_font.c -lfreetype -lm 

8. 测试

将编译好的可执行文件和字体文件拷贝到 NFS文件系统,开发板运行测试如下

/ # cd /mnt/nfs/
/mnt/nfs # ls
ch2 ch3_01 tslib
/mnt/nfs # cd ch3_01/
/mnt/nfs/ch3_01 # ls
HZK16 cfbfillrect.ko freetype_char show_font tmp
cfbcopyarea.ko cfbimgblt.ko lcd.ko simsun.ttc
/mnt/nfs/ch3_01 # ./show_font simsun.ttc
chinese code: d6 d0
/mnt/nfs/ch3_01 # ./freetype_char simsun.ttc

至此,显示一个中文字符的任务完成。

猜你喜欢

转载自blog.csdn.net/sinat_32473259/article/details/81811967
02
今日推荐