Linux应用项目之量产工具(三)——文字系统

前言

前面我们完成了量产工具的显示系统和输入系统,见
量产工具(一)------显示系统
量产工具(二)------输入系统

这节我们来实现文字系统,套路和前两节类似,先抽象出数据结构然后再对代码进行封装。


数据结构抽象

使用点阵绘制文字时:每个文字的大小一样,前后文件互不影响

使用Freetype绘制文字时:大小可能不同,前面文字会影响后面文字

既然我们能支持比较复杂的Freetype,自然能支持比较简单的点阵字体。

对于单个Freetype字符,格式如下:

我们要抽象出一个结构体 FontBitMap ,能描述一个"字符":位置、大小、位图;我们还要抽象出一个结构体 FontOpr ,能描述字体的操作,比如Freetype的操作、固定点阵字体的操作。

FontBitMap

结构如下:

为了让代码更加美观,这里的坐标信息就用了之前定义的 Region 结构体,它用来描述位图的区域信息。

值得注意的是 FontBitMap结构体里的带 Origin 后缀的参数,它用来描述Freetype里的基点,之所以Freetype会看起来比较紧凑,是因为下一个字符的基点是根据前一个字符的基点来计算的。

FontOpr

结构如下:

底层代码需要实现这三个函数,后面也会有管理层,底层需要把自己注册到管理链表里,上层代码就可以直接调用底层代码而无需关心细节。这就是面向对象的编程思想。

font_manager.h

cpp 复制代码
#ifndef _FONT_MANAGER_H
#define _FONT_MANAGER_H

/*对于一些要复用的结构体和定义,避免重复包含,放在一个通用头文件里*/
#include <common.h>

typedef struct FontBitMap{
    /*后面会将Region结构体从之前的头文件剪切到common.h里*/
	Region tRegion;
	int iCurOriginX;
	int iCurOriginY;
	int iNextOriginX;
	int iNextOriginY;
	unsigned char *pucBuffer;
}FontBitMap, *PFontBitMap;

typedef struct FontOpr{
	char *name;
	int (*FontInit)(char *aFineName);
	int (*SetFontSize)(int iFontSize);
	int (*GetFontBitMap)(unsigned char dwCode, PFontBitMap ptFontBitMap);
	struct FontOpr *ptNext;
}FontOpr, *PFontOpr;

void RegisterFont(PFontOpr ptFontOpr);
void FontsRegister(void);
int SelectAndInitFont(char *aFontOprName, char *aFontFileName);
int SetFontSize(int iFontSize);
int GetFontBitMap(unsigned char dwCode, PFontBitMap ptFontBitMap);

#endif

实现Freetype代码

现在我们来实现freetype的代码,从某个字体库文件里得到字符的点阵。freetype知识点比较多且相对复杂,可以先去看这篇博客:使用freetype显示文字

freetype.c

cpp 复制代码
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <wchar.h>
#include <sys/ioctl.h>
#include <font_manager.h>

#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H

//储存FreeType库中的字体面(face)
static FT_Face g_tFace;
//默认字体大小
static int g_iDefaultFontSize = 12;

/* 初始化FreeType字体库 */
static int FreeTypeFontInit(char *aFineName)
{
    FT_Library    library;
    int error;

    //初始化FreeType库
    error = FT_Init_FreeType( &library );                 /* initialize library */    
	if (error)
	{
		printf("FT_Init_FreeType err\n");
		return -1;
	}
	
	//从指定文件创建字体面
    error = FT_New_Face(library, aFineName, 0, &g_tFace ); /* create face object */
	if (error)
	{
		printf("FT_New_Face err\n");
		return -1;
	}
    //设置字体大小
    FT_Set_Pixel_Sizes(g_tFace, g_iDefaultFontSize, 0);

	return 0;
}

/* 设置字体大小 */
static int FreeTypeSetFontSize(int iFontSize)
{
    //设置字体大小
    FT_Set_Pixel_Sizes(g_tFace, iFontSize, 0);
	return 0;
}

/* 获取字体位图 */
static int FreeTypeGetFontBitMap(unsigned int dwCode, PFontBitMap ptFontBitMap)
{
	int error;
    FT_Vector pen;
    FT_GlyphSlot slot = g_tFace->glyph;

    //设置当前光标位置,单位为1/64像素
    pen.x = ptFontBitMap->iCurOriginX * 64; /* 单位: 1/64像素 */
    pen.y = ptFontBitMap->iCurOriginY * 64; /* 单位: 1/64像素 */

	/* 转换:transformation设置变换矩阵 */
	FT_Set_Transform(g_tFace, 0, &pen);

	/* 加载位图: load glyph image into the slot (erase previous one) */
	error = FT_Load_Char(g_tFace, dwCode, FT_LOAD_RENDER);
	if (error)
	{
		printf("FT_Load_Char error\n");
		return -1;
	}
    
    //获取字符位图缓冲区
	ptFontBitMap->pucBuffer = slot->bitmap.buffer;

    //设置字体位图的区域信息
	ptFontBitMap->tRegion.iLeftUpX = slot->bitmap_left;
	ptFontBitMap->tRegion.iLeftUpY = ptFontBitMap->iCurOriginY*2 - slot->bitmap_top;
	ptFontBitMap->tRegion.iWidth   = slot->bitmap.width;
	ptFontBitMap->tRegion.iHeigh   = slot->bitmap.rows;
	ptFontBitMap->iNextOriginX = ptFontBitMap->iCurOriginX + slot->advance.x / 64;
	ptFontBitMap->iNextOriginY = ptFontBitMap->iCurOriginY;

	return 0;
}

/* 定义一个字体操作结构体 */
static FontOpr g_tFreetypeOpr = {
	.name          = "freetype",
	.FontInit      = FreeTypeFontInit,
	.SetFontSize   = FreeTypeSetFontSize,
	.GetFontBitMap = FreeTypeGetFontBitMap,
};

//注册FreeType字体操作结构体
void FreetypeRegister(void)
{
	RegisterFont(&g_tFreetypeOpr);
}

文字管理

参照下图实现 font_manager.c ,向上层提供更简单的接口函数:

套路和我们之前的管理单元一样,都是对底层函数进一步封装,上层代码调用管理单元的代码而无需关心内部细节,这就是面向对象的编程思想。

font_manager.c

cpp 复制代码
#include <font_manager.h>
#include <string.h>

//指向注册链表的头
static PFontOpr g_ptFonts = NULL;
//指向当前默认的字体的操作系统
static PFontOpr g_ptDefaulFontOpr = NULL;

//注册一个新的字体操作结构体到链表中
void RegisterFont(PFontOpr ptFontOpr)
{
	ptFontOpr->ptNext = g_ptFonts;
	g_ptFonts = ptFontOpr;
}

//注册字体操作系统,这里显示注册了FreeType字体
void FontsRegister(void)
{
	extern void FreetypeRegister(void);
	FreetypeRegister();
}

//选择并初始一个字体操作结构体
int SelectAndInitFont(char *aFontOprName, char *aFontFileName)
{
	PFontOpr ptTmp = g_ptFonts;
	while (ptTmp)
	{
	    //比较操作结构体的名称,找到匹配的结构体
		if (strcmp(ptTmp->name, aFontOprName) == 0)
			break;
		ptTmp = ptTmp->ptNext;
	}

    //没找到,返回
	if (!ptTmp)
		return -1;
		
   //记录一下默认的字体操作结构体
	g_ptDefaulFontOpr = ptTmp;
	//调用结构体的初始化函数,初始化字体
	return ptTmp->FontInit(aFontFileName);
}

//设置当前默认字体的字体大小
int SetFontSize(int iFontSize)
{
	return g_ptDefaulFontOpr->SetFontSize(iFontSize);
}

//获取当前默认字体的字符位图
int GetFontBitMap(unsigned int dwCode, PFontBitMap ptFontBitMap)
{
	return g_ptDefaulFontOpr->GetFontBitMap(dwCode, ptFontBitMap);
}

单元测试

目录结构如下:

我们要将freetype生成的字体在LCD上显示出来,在 disp_manager.c 中还要实现一个绘制函数: DrawFontBitMap

DrawFontBitMap

cpp 复制代码
void DrawFontBitMap(PFontBitMap ptFontBitMap, unsigned int dwColor)
{
    int i, j, p, q;
	int x = ptFontBitMap->tRegion.iLeftUpX;
	int y = ptFontBitMap->tRegion.iLeftUpY;
    int x_max = x + ptFontBitMap->tRegion.iWidth;
    int y_max = y + ptFontBitMap->tRegion.iHeigh;
	int width = ptFontBitMap->tRegion.iWidth;
	unsigned char *buffer = ptFontBitMap->pucBuffer;//字符点阵数据

    for ( j = y, q = 0; j < y_max; j++, q++ )
    {
        for ( i = x, p = 0; i < x_max; i++, p++ )
        {
            if ( i < 0      || j < 0       ||
                i >= g_tDispBuff.iXres || j >= g_tDispBuff.iYres )
            continue;

            if (buffer[q * width + p])//根据字符点阵数据描点
	            PutPixel(i, j, dwColor);
        }
    }
}

接着在unittest.c目录下新建 font_test.c 文件,编写单元测试代码。

font_test.c

cpp 复制代码
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <stdlib.h>

#include <disp_manager.h>
#include <font_manager.h>

#define FONTDATAMAX 4096

/*此处忽略了点阵数据
static const unsigned char fontdata_8x16[FONTDATAMAX] = {};
*/

/**********************************************************************
 * 函数名称: lcd_put_ascii
 * 功能描述: 在LCD指定位置上显示一个8*16的字符
 * 输入参数: x坐标,y坐标,ascii码
 * 输出参数: 无
 * 返 回 值: 无
 * 修改日期        版本号     修改人	      修改内容
 * -----------------------------------------------
 * 2020/05/12	     V1.0	  zh(angenao)	      创建
 ***********************************************************************/ 
void lcd_put_ascii(int x, int y, unsigned char c)
{
	unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16];
	int i, b;
	unsigned char byte;

	for (i = 0; i < 16; i++)
	{
		byte = dots[i];
		for (b = 7; b >= 0; b--)
		{
			if (byte & (1<<b))
			{
				/* show */
				PutPixel(x+7-b, y+i, 0xffffff); /* 白 */
			}
			else
			{
				/* hide */
				PutPixel(x+7-b, y+i, 0); /* 黑 */
			}
		}
	}
}

int main(int argc, char **argv)
{
	PDispBuff ptBuffer;
	int error;

	FontBitMap tFontBitMap;
	char *str= "www.100ask.net";
	int i = 0;
	int lcd_x;
	int lcd_y;
	int font_size;
		
    //检查输入参数数目
	if (argc != 5)
	{
		printf("Usage: %s <font_file> <lcd_x> <lcd_y> <font_size>\n", argv[0]);
		return -1;
	}

	lcd_x = strtol(argv[2], NULL, 0);//将输入参数从字符串形式变成数字形式
	lcd_y = strtol(argv[3], NULL, 0);//将输入参数从字符串形式变成数字形式
	font_size  = strtol(argv[4], NULL, 0);//将输入参数从字符串形式变成数字形式
	
    //初始化显示设备
	DisplayInit();
	SelectDefaultDisplay("fb");
	InitDefaultDisplay();
	ptBuffer = GetDisplayBuffer();
    
    //注册初始化字体
	FontsRegister();
	error = SelectAndInitFont("freetype", argv[1]);
	if (error)
	{
		printf("SelectAndInitFont err\n");
		return -1;
	}
	 
	//设置字体大小
	SetFontSize(font_size);

    //循环各个字符
	while (str[i])
	{
		
		tFontBitMap.iCurOriginX = lcd_x;
		tFontBitMap.iCurOriginY = lcd_y;
		/* get bitmap 获得字符位图*/
		error = GetFontBitMap(str[i], &tFontBitMap);
		if (error)
		{
			printf("SelectAndInitFont err\n");
			return -1;
		}

		/* draw on buffer */		
		DrawFontBitMap(&tFontBitMap, 0xff0000);

		/* flush to lcd/web */		
		FlushDisplayRegion(&tFontBitMap.tRegion, ptBuffer);
		
		lcd_x = tFontBitMap.iNextOriginX;//移动光标,进行下一个字符的描绘
		lcd_y = tFontBitMap.iNextOriginY;//移动光标,进行下一个字符的描绘	
		i++;
	}
	return 0;	
}

在include目录下面新建一个我们之前提到的 common.h 头文件,里面定义了我们经常会用到的结构体和某些宏。

common.h

cpp 复制代码
#ifndef _COMMON_H
#define _COMMON_H

#ifndef NULL
#define NULL (void *)0
#endif

typedef struct Region {
	int iLeftUpX;  //区域左上角的X坐标
	int iLeftUpY;  //区域左上角的Y坐标
	int iWidth;    //区域宽度
	int iHeigh;    //区域高度
}Region, *PRegion;

#endif

unittest目录下的Makefile

EXTRA_CFLAGS :=

CFLAGS_file.o :=

#obj-y += disp_test.o

#obj-y += input_test.o
obj-y += font_test.o

font目录下的Makefile

EXTRA_CFLAGS :=

CFLAGS_file.o :=

obj-y += font_manager.o
obj-y += freetype.o

这里注意,顶层目录下的makefile还要链接freetype库,否则make会报错。

运行结果

Ubuntu交叉编译后,开发板共享文件系统。

下方彩色的是lvgl的页面,忽略即可,可以看到成功显示一行字体


至此量产工具第三部分------文字系统就完成了,下一篇我会更新第四部分------UI系统。希望大家多多点赞支持。

相关推荐
首席CEO16 分钟前
Shell基础2
linux·kali·shell编程
烟雨长虹,孤鹜齐飞17 分钟前
【苍穹外卖】学习日志-day1
学习
虾球xz22 分钟前
游戏引擎学习第12天
android·学习·游戏引擎
楚疏笃36 分钟前
鸿蒙学习基本概念
学习·华为·harmonyos
非概念1 小时前
STM32学习笔记----时钟体系
笔记·stm32·嵌入式硬件·学习
tt5555555555551 小时前
计算机网络学习笔记-3.3以太网和局域网
笔记·学习·计算机网络
怀澈1221 小时前
【golang学习笔记】新奇语法
笔记·学习
ZZZ_O^O1 小时前
动态规划-背包问题——[模版]完全背包问题
c++·学习·算法·leetcode·动态规划
Koishi_TvT1 小时前
蓝桥杯c++算法学习【3】之思维与贪心(重复字符串、翻硬币、乘积最大、皮亚诺曲线距离【难】:::非常典型的必刷例题!!!)
c++·学习·算法·游戏·贪心算法·蓝桥杯·c
TsengOnce1 小时前
Docker安装稳定版本nginx-1.26.2
linux·nginx·docker