from:https://blog.csdn.net/xiaodingqq/article/details/84861980
任务:在LCD上显示任意编码的文本文件
以面向对象的思想编写程序[模块化设计]:
- 分配一个结构体
- 设置结构体
- 注册结构体
怎样在LCD上显示文件:
1、从文件获得“码” (ASCII[GBK]、UTF-8、UTF-16LE、UTF-16BE —>encording_manager.c
2、根据“码”得到“字体数据”(LCD上就是点阵)(ascii 、GBK[HZK16]、freetype) —>fonts_manager.c
3、把“点阵”在LCD上显示出来 —>disp_manager.c
框架(需要哪些文件):
1、disp_manager.c管理文件 T_DispOpr:显示操作结构体
管理2个显示文件:fb.c(LCD显示) crt.c(串口显示)
主要负责将点阵信息发送到显存或串口上。
2、 font_maneger.c管理文件 T_FontOpr:字体操作结构体
管理3个字体子文件:ascii.c(英文点阵),gbk.c(中文点阵),freetype(矢量字体)
用来将获取的字符编码转换为点阵信息。
3、encoding_manager.c管理文件 T_EncodingOpr:编码操作结构体
管理4个编码子文件:utf-8.c,utf-16be.c,utf-16le.c,ascii.c
比如utf-8.c:判断某个文件是否以0xEF,0xBB,0xBF开头, 若是,则接下来通过utf-8规律,来转换字节编码
4、顶部文件
通过main.c分析命令行的操作,然后初始化各个管理文件下的结构体,比如DisplayInit();
然后进入draw.c,在draw.c里按顺序调用3个管理文件,并控制显示:
1、使用encording_manager.c解析文件的获得编码
2、使用font_manager.c来处理编码获得字体数据
3、使用display_manager.c来输出点阵
4、显示的控制——分页、换行等
在3个管理.h头文件里,声明3个不同的结构体:
T_DispOpr:显示操作结构体
T_FontOpr:字体操作结构体
T_EncodingOpr:编码操作结构体
1、显示部分:
fb.c需要用到fb初始化函数,以及显示像素函数,当我们换页时,还需要一个清屏函数,所以有3个函数。
在disp_managet.h里
定义一个结构体:
typedef struct DispOpr {
char *name;
int iXres; //x像素个数
int iYres; //y像素个数
int iBpp; //每个像素多少位
int (*DeviceInit)(void); //该函数对于fb.c,是用来打开/dev/fb0,获取var和fix,然后mmap
int (*ShowPixel)(int iPenX, int iPenY, unsigned int dwColor); //显示一个像素点
int (*CleanScreen)(void); //清屏
struct DispOpr *ptNext; //指向下一个注册的T_DispOpr结构体
}T_DispOpr, *PT_DispOpr;
在fb.c里定义g_tFBOpr:
static T_DispOpr g_tFBOpr = {
.name = "fb",
.DeviceInit = FBDeviceInit, //该函数打开/dev/fb0,然后获取fix和var成员,来mmap()
.ShowPixel = FBShowPixel, //该函数根据x,y,color这3个函数参数,来显示一个像素点
.CleanScreen = FBCleanScreen, //该函数,通过memset来将显存清0
};
并定义一个FBInit()函数,将结构体g_tFBOpr注册到g_ptDispOprHead链表里:
int FBInit(void)
{
return RegisterDispOpr(&g_tFBOpr);
}
由于FBInit()被disp_managet.c文件的disp_manager.c文件的DisplayInit()调用,所以不能写static了。
在disp_managet.c里
定义一个空链表:static PT_DispOpr g_ptDispOprHead = NULL;
写一个RegisterDispOpr()函数,子文件通过调用该函数来注册到链表g_ptDispOprHead里。
定义一个DisplayInit()函数,用来被main.c初始化时调用。
在config.h里宏定义
把printf定义成宏,如果不想打印的时候,就把DBG_PRINTF设为空(…)
在fb.c里的清屏函数:
void *memset(void *s, int ch, size_t n);
函数解释:将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s 。
注意:
memset是按字节赋值的。memset(内存地址,1,字节数)这句的意思就是要把指定的内存空间的值设置成 0x1,对于int型,是四个字节,也就是将这四个字节设置成0x01010101, 转换成十进制就是16843009。
在static int FBCleanScreen(unsigned int dwBackColor)里,所以8位用memset清屏就行了,但是16位和32位不行。
switch (g_tFBVar.bits_per_pixel)
{
case 8:
{
memset(g_pucFBMem, dwBackColor, g_dwScreenSize);
break;
}
case 16:
{
iRed = (dwBackColor >> (16+3)) & 0x1f;
iGreen = (dwBackColor >> (8+2)) & 0x3f;
iBlue = (dwBackColor >> 3) & 0x1f;
wColor16bpp = (iRed << 11) | (iGreen << 5) | iBlue;
while (i < g_dwScreenSize)
{
*pwFB16bpp = wColor16bpp;
pwFB16bpp++;
i += 2;
}
break;
}
case 32:
{
while (i < g_dwScreenSize)
{
*pdwFB32bpp = dwBackColor;
pdwFB32bpp++;
i += 4;
}
break;
}
default :
{
DBG_PRINTF("can't support %d bpp\n", g_tFBVar.bits_per_pixel);
return -1;
}
}
2、得到字体数据[位图]部分:
注意:
1 结构体FontBitMap,为了抽取三种点阵(ascii、GBK、freetype)的共性,如上图ascii跨度为8,hzk跨度为16,freetype跨度不一定,所以我们这里定义一个变量pitch来区别点阵。
2 我们得到的ascii、GBK的点阵的宽度width和长度rows是固定的
而freetype的rows和width是不固定的,所以取的是间距advance
在fonts_managet.h里的声明了两个结构体,如下所示:
typedef struct FontBitMap {
int iXLeft; //文字最左边X坐标
int iYTop; //文字最顶部Y坐标
int iXMax; //文字的一行像素有多大
int iYMax; //文字的一列像素有多大
int iBpp; //像素格式
int iPitch; /* 对于单色位图, 两行象素之间的跨度,比如8*16,则跨度是8(文字的一行像素有8位),我加8就是下一个位图 */
int iCurOriginX; //当前原点x坐标
int iCurOriginY; //当前原点y坐标
int iNextOriginX; //下个文字的原点x坐标
int iNextOriginY; //下个文字的原点y坐标
unsigned char *pucBuffer; //存放文字的像素数据
}T_FontBitMap, *PT_FontBitMap;
typedef struct FontOpr {
char *name;
int (*FontInit)(char *pcFontFile, unsigned int dwFontSize); //初始化字体文件
int (*GetFontBitmap)(unsigned int dwCode, PT_FontBitMap ptFontBitMap); //根据dwCode编码获取字体位图,并将信息(坐标,数据,格式等)存到ptFontBitMap里
struct FontOpr *ptNext;
}T_FontOpr, *PT_FontOpr;
写字体文件,以freetype.c(矢量字体)为例:
首先定义一个T_FontOpr结构体
static T_FontOpr g_tFreeTypeFontOpr = {
.name = "freetype",
.FontInit = FreeTypeFontInit,
.GetFontBitmap = FreeTypeGetFontBitmap,
};
定义FreeTypeFontInit成员函数,初始化freetype库,设置字体大小等。
定义GetFontBitmap成员函数,FT_Load_Char()转换位图,保存在PT_FontBitMap里。
具体内容如下:
#include <config.h>
#include <fonts_manager.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
static int FreeTypeFontInit(char *pcFontFile, unsigned int dwFontSize);
static int FreeTypeGetFontBitmap(unsigned int dwCode, PT_FontBitMap ptFontBitMap);
static T_FontOpr g_tFreeTypeFontOpr = {
.name = "freetype",
.FontInit = FreeTypeFontInit,
.GetFontBitmap = FreeTypeGetFontBitmap,
};
static FT_Library g_tLibrary;
static FT_Face g_tFace;
static FT_GlyphSlot g_tSlot;
static int FreeTypeFontInit(char *pcFontFile, unsigned int dwFontSize)
{
int iError;
/* 显示矢量字体 */
iError = FT_Init_FreeType(&g_tLibrary ); /* initialize library */
/* error handling omitted */
if (iError)
{
DBG_PRINTF("FT_Init_FreeType failed\n");
return -1;
}
iError = FT_New_Face(g_tLibrary, pcFontFile, 0, &g_tFace); /* create face object */
/* error handling omitted */
if (iError)
{
DBG_PRINTF("FT_Init_FreeType failed\n");
return -1;
}
g_tSlot = g_tFace->glyph;
iError = FT_Set_Pixel_Sizes(g_tFace, dwFontSize, 0);
if (iError)
{
DBG_PRINTF("FT_Set_Pixel_Sizes failed : %d\n", dwFontSize);
return -1;
}
return 0;
}
static int FreeTypeGetFontBitmap(unsigned int dwCode, PT_FontBitMap ptFontBitMap)
{
int iError;
int iPenX = ptFontBitMap->iCurOriginX; //初始值为:0 dwFontSize
int iPenY = ptFontBitMap->iCurOriginY;
#if 0
FT_Vector tPen;
tPen.x = 0;
tPen.y = 0;
/* set transformation */
FT_Set_Transform(g_tFace, 0, &tPen);
#endif
/* load glyph image into the slot (erase previous one) */
//iError = FT_Load_Char(g_tFace, dwCode, FT_LOAD_RENDER );
iError = FT_Load_Char(g_tFace, dwCode, FT_LOAD_RENDER | FT_LOAD_MONOCHROME);
if (iError)
{
DBG_PRINTF("FT_Load_Char error for code : 0x%x\n", dwCode);
return -1;
}
//DBG_PRINTF("iPenX = %d, iPenY = %d, bitmap_left = %d, bitmap_top = %d, width = %d, rows = %d\n", iPenX, iPenY, g_tSlot->bitmap_left, g_tSlot->bitmap_top, g_tSlot->bitmap.width, g_tSlot->bitmap.rows);
/*笛卡尔坐标的左上角是(bitmap_left,bitmap_top),
对应LCD的左上角是(Y+bitmap_left,Y-bitmap_top)*/
ptFontBitMap->iXLeft = iPenX + g_tSlot->bitmap_left;
ptFontBitMap->iYTop = iPenY - g_tSlot->bitmap_top;
ptFontBitMap->iXMax = ptFontBitMap->iXLeft + g_tSlot->bitmap.width;
ptFontBitMap->iYMax = ptFontBitMap->iYTop + g_tSlot->bitmap.rows;
ptFontBitMap->iBpp = 1;
ptFontBitMap->iPitch = g_tSlot->bitmap.pitch;
ptFontBitMap->pucBuffer = g_tSlot->bitmap.buffer;
ptFontBitMap->iNextOriginX = iPenX + g_tSlot->advance.x / 64;
ptFontBitMap->iNextOriginY = iPenY;
//DBG_PRINTF("iXLeft = %d, iYTop = %d, iXMax = %d, iYMax = %d, iNextOriginX = %d, iNextOriginY = %d\n", ptFontBitMap->iXLeft, ptFontBitMap->iYTop, ptFontBitMap->iXMax, ptFont!
return 0;
}
int FreeTypeInit(void)
{
return RegisterFontOpr(&g_tFreeTypeFontOpr);
}
3、 取出编码部分:
在encoding_manager.h里的T_EncodingOpr结构体,声明如下:
typedef struct EncodingOpr {
char *name;
int iHeadLen; //文件以多少字节开头
PT_FontOpr aptFontOprSupported[4]; //指针数组,用来存放支持该编码的字体结构体,以后就通过这个来显示文字
int (*isSupport)(unsigned char *pucBufHead); //该函数判断要显示的文件是否支持XX格式
int (*GetCodeFrmBuf)(unsigned char *pucBufStart, unsigned char *pucBufEnd, unsigned int *pdwCode);
//将文件里的字节转为编码,存到*pdwCode里
struct EncodingOpr *ptNext; //链表
}T_EncodingOpr, *PT_EncodingOpr;
在encoding_manager.c里
定义一个空链表:static PT_EncodingOpr g_ptEncodingOprHeaad = NULL
写一个RegisterEncodingOpr()函数,子文件通过调用该函数来注册到链表g_ptEncodingOprHead里
写一个SelectEncodingOprForFile()函数,通过链表来找isSupport()成员函数,判断要显示的文字支持哪种格式
写一个EncodingInit()函数,调用每个编码文件的init()函数,里面会初始化编码T_EncodingOpr结构体,并添加编码所支持的文字结构体
写编码文件,以utf-8.c为例
4、 draw.c:
首先定义一个T_PageDesc结构体。
用来控制分页换行用,需要用到双向链表。
typedef struct PageDesc {
int iPage; //当前页数
unsigned char *pucLcdFirstPosAtFile; //在LCD上第一个字符位置位于在文件哪个位置
unsigned char *pucLcdNextPageFirstPosAtFile; //下一页的LCD上第一个字符位置位于文件哪位置
struct PageDesc *ptPrePage; //上一页链表,指向上一个T_PageDesc结构体
struct PageDesc *ptNextPage; //下一页链表,指向下一个T_PageDesc结构体
} T_PageDesc, *PT_PageDesc;
int DrawInit(void) 函数
调用manager里的初始化函数,让fb.c、gbk.c、utf-8.c等注册结构体,以及添加链表。比如:iError = DisplayInit();//最终调用FBInit();然后RegisterDispOpr(&g_tFBOpr);
int OpenTextFile()函数
用来打开文本文件,然后mmap(),并判断支持哪种编码,并获取文件第一个字符位置
static int g_iFdTextFile; //文件描述符
static unsigned char *g_pucTextFileMem; //内存映射基地址
static unsigned char *g_pucTextFileMemEnd; //内存映射结尾地址
static PT_EncodingOpr g_ptEncodingOprForFile; //用来指向该文件支持的编码EncodingOpr结构体
static unsigned char *g_pucLcdFirstPosAtFile; //第一个字符位于文件的位置
int OpenTextFile(char *pcFileName)
{
g_iFdTextFile = open(pcFileName, O_RDONLY);
... ...
if(fstat(g_iFdTextFile, &tStat))
{
DBG_PRINTF("can't get fstat\n");
return -1;
}
g_pucTextFileMem = (unsigned char *)mmap(NULL , tStat.st_size, PROT_READ, MAP_SHARED, g_iFdTextFile, 0);
g_pucTextFileMemEnd = g_pucTextFileMem + tStat.st_size;
g_ptEncodingOprForFile = SelectEncodingOprForFile(g_pucTextFileMem); //获取支持的编码格式
if (g_ptEncodingOprForFile)
{
g_pucLcdFirstPosAtFile = g_pucTextFileMem + g_ptEncodingOprForFile->iHeadLen; //去掉文件编码前缀的开头位置
return 0;
}
else
{
return -1;
}
}
SetTextDetail函数
int SetTextDetail(char * pcHZKFile, char * pcFileFreetype, unsigned int dwFontSize)
设置文本编码可以从freetype还是ascii还是gbk获得点阵。
以及设置字体大小。
ShowOnePage()显示一页函数
首先设置原点xy为(0, fontsize),
通过编码结构体的成员函数获取编码,判断编码是否为\r \n \t, GetCodeFrmBuf
然后通过字体结构体的成员函数将编码转换为位图, GetFontBitMap
然后判断是否换行,满页, 计算位置
最后显示
5、main.c:
main.c主要用来通过main.c分析命令行的操作,然后初始化各个管理文件下的结构体,比如DisplayInit();
命令行:
./show_file [-l] [-s Size] [-d Dispshow] [-f freetype_font_file] [-h HZK] <text_file>
//-l :列出选项
//-s :设置文字大小
//-d:选择显示到哪里,是fb还是crt
//-f :指定矢量文字文件位置
//-h :指定汉字库文件位置
//text_file:指定要显示哪个文件
main.c流程:
1、 通过getopt(argc, argv, “ls:f:h:d:”)来解析命令行,获取每个选项后的参数
有冒号就代表需要参数 l s f h d 这些都是option, l后面没有参数就代表不需要参数。
1、定义:
int getopt(int argc, char * const argv[], const char *optstring);
2、描述:getopt是用来解析命令行选项参数的,但是只能解析短选项【比如: -d 100】,不能解析长选项【比如:–prefix】
3、参数:argc:main()函数传递过来的参数的个数
argv:main()函数传递过来的参数的字符串指针数组
optstring:选项字符串,告知 getopt()可以处理哪个选项以及哪个选项需要参数
4、返回:
如果选项成功找到,返回选项字母;如果所有命令行选项都解析完毕,返回 -1;如果遇到选项字符不在 optstring 中,返回字符 ‘?’;如果遇到丢失参数,那么返回值依赖于 optstring 中第一个字符,如果第一个字符是 ‘:‘则返回’:’,否则返回’?'并提示出错误信息。
详解:https://www.cnblogs.com/qingergege/p/5914218.html
“:”表示后面又参数 “::”表示即可以有参数,也可以没有参数
while ((iError = getopt(argc, argv, "ls:f:h:d:")) != -1)
{
switch(iError)
{
case 'l':
{
bList = 1;
break;
}
case 's':
{
dwFontSize = strtoul(optarg, NULL, 0);
break;
}
case 'f':
{
strncpy(acFreetypeFile, optarg, 128);
acFreetypeFile[127] = '\0';
break;
}
case 'h':
{
strncpy(acHzkFile, optarg, 128);
acHzkFile[127] = '\0';
break;
}
case 'd':
{
strncpy(acDisplay, optarg, 128);
acDisplay[127] = '\0';
break;
}
default:
{
printf("Usage: %s [-s Size] [-d display] [-f font_file] [-h HZK] <text_file>\n", argv[0]);
printf("Usage: %s -l\n", argv[0]);
return -1;
break;
}
}
}
optind和opterr的初始值都为1
optind是下一次 进行选项搜索 的 开始索引
例如:
./show -s 16 textfile.txt
./show
、-s
、 16
、 textfile.txt
argc = 4
optindex = 3,因为下一次索引是从argv[3]开始找,然后argv[3]不符合就返回了
./show -s 16
./show
、-s
、 16
argc = 3
optindex = 3,因为下一次索引是从argv[3]开始找,然后argv[3]为空就返回了
./show -l -s 16 textile.txt
./show
、 -l
、-s
、 16
、 textfile.txt
argc = 5
optindex = 4
./show -l -s 16
./show
、 -l
、-s
、 16
argc = 4
optindex = 4
因此只要没有<text_file>这个参数, optindex就会等于argc
如果有<text_file> ,optind最后会等于argc-1。
if (!bList && (optind >= argc)) //说明没有<text_file>这个参数
{
printf("Usage: %s [-s Size] [-d display] [-f font_file] [-h HZK] <text_file>\n", argv[0]);
printf("Usage: %s -l\n", argv[0]);
return -1;
}
2、 因为optind等于<text_file>位置,所以通过optind打开<text_file>文件:
strncpy(acTextFile, argv[optind], 128);
acTextFile[127] = '\0';
iError = OpenTextFile(acTextFile); //OpenTextFile里面把文本文件进行mmap(),并获取该文件所支持的编码结构体
3、 调用OpenTextFile()去判断文本支持哪种编码格式,并且设置文本位置
4、 设置文本细节(HZK库,freetype库,文字大小)
设置用freetype还是ascii还是gbk去得到位图。
iError = SetTextDetail(acHzkFile, acFreetypeFile, dwFontSize); //该函数位于draw.c
5、 调用SelectAndInitDisplay()函数,通过[-d Dispshow]来从g_ptDisOprHead显示链表里,找到name相同的结构体,并放入g_ptDispOpr结构体,然后执行g_ptDispOpr->DeviceInit();来初始化LCD
iError = SelectAndInitDisplay(acDisplay);
6、 调用ShowNextPage()显示文本的第一页
7、 然后进入while(1)里,通过getchar()来获取命令行参数
输入n,则调用ShowNextPage(),显示下一页
输入u,则调用ShowNextPage(),显示上一页
输入q退出
编译:
当我们编译时遇到,未定义宏这种情况的解决方法:
去头文件里查找:
grep "FBIOGET_VSREENINFO" * -nR
头文件会自动去include目录里找,所以这里包含头文件 <linux/fb.h>就行了