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

article/2025/8/30 14:16:40

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

在内核中有对应的文件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_HFT_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的sloterror = 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/


http://chatgpt.dhexx.cn/article/13onatnI.shtml

相关文章

freetype简介与测试

目录 一、简要 二、文字显示过程 三、freetype中的step1 四、在PC上测试freetype 4.1 分析main函数 4.2 修改main函数 4.3 在PC上测试 4.4 得到CBox 一、简要 从点阵文件中把字母或者汉字的字模取出来在LCD上显示这个方式有个缺点&#xff0c;这个文字的大小就固定了不…

Freetype 的安装与使用

4 交叉编译程序 4.1 程序运行的一些基础知识 编译程序时去哪找头文件&#xff1f; 系统目录&#xff1a;就是交叉编译工具链里的某个 include 目录&#xff1b; 也可以自己指定&#xff1a;编译时用 “ -I dir ” 选项指定。 链接时去哪找库文件&#xff1f; 系统目录&#x…

java和web哪个难_web前端和java哪个难学?哪个简单?

说到java很多都人都熟知&#xff0c;但是说到web前端可能是很多人并不知道&#xff0c;随着最近几年的发展&#xff0c;web前端开发人越来越吃香&#xff0c;无论是薪资待遇还是岗位的招聘数量比java差不了多少&#xff0c;最近有同学在咨询web前端和java哪个难学这个问题&…

js和java那个难_javascript与java哪个难?

javascript与java哪个难&#xff1f;答案是&#xff1a;JavaScript比Java更难。那么这是为什么&#xff1f;下面本篇文章就来给大家介绍一下&#xff0c;希望对大家有所帮助。 原因&#xff1a; JavaScript有太多东西需要你自己去理解&#xff0c;这些东西里有很多要么Java已经…

没学历学java很难找工作吗

说实话不是学Java很难找工作&#xff0c;是以后找工作基本都会看学历&#xff0c;学历这个东西说重要也重要&#xff0c;因为它决定了你的简历能不能呈现到面试官面前&#xff0c;如果你的学历不够的话很有可能在第一轮简历就被刷下去了&#xff0c;它根本就不会呈现在面试官面…

java最难的部分_java最难学的是那一块?

原标题&#xff1a;java最难学的是那一块&#xff1f; 对于那些打算通过学习java来找到一份工作的同学来说&#xff0c;可以分为三个学习阶段&#xff0c;初级&#xff0c;中级&#xff0c;高级。不同的学习阶段有不同的难点&#xff0c;下来我就简单的和大家仔细的聊聊&#x…

学习Java开发难不难?好学吗?

学习Java难不难&#xff1f;这是很多希望学习Java的人比较纠结的问题。实际上&#xff0c;Java语言是非常易学的&#xff0c;Java语言机遇&#xff23;语言&#xff0c;却又高于&#xff23;语言。Java语言简单易学的特性使得大多数程序员很容易学习和使用Java。 Java是个简单…

零基础Java难学吗?自学怎么样?

在零基础上学习Java难吗?自学呢?要回答这个问题,我们应该从多方面来回答。首先,谁更适合学习Java?   如果仅仅从兴趣上说那么人人都可以胜任,那就像姜子牙70多年的探险生涯。47岁的刘邦在沛县召集民众响应陈胜武广起义。古代的年龄相当于我们现在的六十岁。齐白石,一位…

java学习路线,一个初中生学java要多久,java难学吗

一门永不过时的编程语言——Java 软件开发 java难学吗 java不难&#xff0c;你可以做如下学习 一、到相应的培训机构付费学习 别在这说培训机构没用什么的&#xff0c;不过一定要找正规的培训机构&#xff0c;不然容易被坑。培训机构里面的课程都是现在工作中需要用到的&am…

学Java难吗

Java这门语言你要是单独拎出来&#xff0c;那还是挺复杂的&#xff0c;但你要是把它放到编程里面&#xff0c;那算不上难&#xff0c;因为编程这门行业本来就难&#xff0c;而且&#xff0c;现在大学计算机专业的学生&#xff0c;大学期间&#xff0c;会设置Java这门课程&#…

c语言难还是java难_C语言真的比Java难学吗?

原标题&#xff1a; C语言真的比Java难学吗&#xff1f; 千锋小编觉得C语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。C语言语法比较简单便捷&#xff0c;而且性能快速&#xff0c;只比汇编语言…

学习Java很难吗

没有简单的语言,编程语言各有各的优势,同时也各有各的难点,不过这些难点也可以是他们的特点。就像C++难的是语言的细节,Java难的是各种库,各种函数的调用,它的基本语法很简单,但只靠基础是做不出什么东西的,必须去熟悉各种轮子才能进行开发。 第一、 Java入门很简单…

Java难学吗

学习Java说难其实也不难&#xff01; 毕竟世上无难事&#xff0c;只怕有心人。 Java是编程语言中比较难学的一门语言&#xff0c;它的难度并不低&#xff0c;相对比于C语言来说&#xff0c;Java的学习难度要小一些。 Java的学习中最难得就是&#xff0c;各种各样的框架&#x…

java学起来难不难?

同学们问的最多的一个关于Java的问题就是java学起来到底难不难&#xff1f;java好不好学&#xff1f;小千综合过去毕业学员给大家介绍一下学Java到底难不难。 java语言 单单来说学Java这件事的话&#xff0c;小千认为是不难的&#xff0c;实际上Java经常会用到的代码结构无非就…

关于 高内聚 和 低耦合 的理解

随着一个程序逻辑越来越多&#xff0c;就难免需要微服务来保证程序的高可用性。一个服务宕机或者出问题了&#xff0c;不影响其他的服务。 只要有微服务的存在&#xff0c;两个服务之间有许多代码需要共用。 高内聚低耦合是一种程序设计的思想&#xff0c;高内聚的本质也就抽…

【架构基础】高内聚低耦合

软件设计目标&#xff1a;实现需求、易于重用、易于理解、没有冗余。 Dont reinvent the wheel, just realign it. --Anthony J D’ Angelo 高内聚低耦合&#xff0c;是软件工程中判断软件设计好坏的标准。主要评判模块或类的内聚性是否高&#xff0c;耦合度是否低。目的是使…

神秘的高内聚与低耦合举例

内聚类型&#xff1a; 1.巧合内聚 / 偶然内聚 模块的各成分之间没有关联&#xff0c;只是把分散的功能合并在一起。 例&#xff1a;A模块中有三条语句&#xff08;一条赋值&#xff0c;一条求和&#xff0c;一条传参&#xff09;&#xff0c;表面上看不出任何联系&#xff0c;但…

高内聚,低耦合的实现方式

高内聚低耦合&#xff0c;是软件工程中的概念&#xff0c;是判断软件设计好坏的标准&#xff0c;主要用于程序的面向对象的设计&#xff0c;主要看类的内聚性是否高&#xff0c;耦合度是否低。 目的是使程序模块的可重用性、移植性大大增强。通常程序结构中各模块的内聚程度越…

什么是高内聚,低耦合?

高内聚&#xff0c;低耦合是一个老生常谈的话题&#xff0c;所以拿出来说一下 我们在看Linux的一些资料&#xff0c;或者是在面试&#xff0c;又或者跟一个比较牛的大佬讨论技术的时候&#xff0c;可能会听到这个概念。 所以&#xff0c;什么是高内聚&#xff0c;低耦合呢&…

浅谈高内聚低耦合

前言 高内聚低耦合&#xff0c;是软件工程中的概念&#xff0c;是判断设计好坏的标准&#xff0c;主要是面向对象的设计&#xff0c;看类的内聚性是否高&#xff0c;耦合度是否低。 概念 内聚关注模块内部的元素结合程度&#xff0c;耦合关注模块之间的依赖程度。 内聚性&a…