数码相框_电子书之代码阅读及编写(7)

数码相框_电子书之代码阅读及编写(7)

在LCD显示任意编码的文本文件,类似电子书

怎样在LCD上显示文件:

需要哪几个文件?

1、顶部文件

通过main.c分析命令行的操作,然后初始化各个管理文件下的结构体,比如DisplayInit();

然后进入draw.c,在draw.c里按顺序调用3个管理文件,并控制显示。

2、encoding_manager.c管理文件

管理4个编码子文件:utf-8.c,utf-16be.c,utf-16le.c,ascii.c

比如utf-8.c:判断某个文件是否以0xEF,0xBB,0xBF开头, 若是,则接下来通过utf-8规律,来转换字节编码

3、font_maneger.c管理文件

管理3个字体子文件:ascii.c(英文点阵),gbk.c(中文点阵),freetype(矢量字体)

用来将获取的字符编码转换为点阵信息。

4、disp_manager.c管理文件

管理2个显示文件:fb.c(LCD显示) crt.c(串口显示)

主要负责将点阵信息发送到显存或串口上。

在3个管理.h头文件里,声明3个不同的结构体

T_DispOpr:显示操作结构体

T_FontOpr:字体操作结构体

T_EncodingOpr:编码操作结构体

5、首先来写显示部分

fb.c需要用到fb初始化函数,以及显示像素函数,当我们换页时,还需要一个清屏函数,所以有3个函数

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;

在disp_managet.c里

定义一个空链表:static PT_DispOpr g_ptDispOprHead = NULL;

写一个Register DispOpr()函数,子文件通过调用该函数来注册到链表g_ptDispOprHead里

在disp_manager.c里

定义一个DisplayInit()函数,用来被main.c初始化时调用。

在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了。

6、写字体部分

和显示部分思路一样,在fonts_managet.h里的声明了两个结构体,如下所示:

typedef struct FontBitMap {
       int iXLeft;         //文字最左边X坐标
       int iYTop;         //文字最顶部Y坐标
       int iXMax;        //文字的一行像素有多大
       int iYMax;       //文字的一列像素有多大
       int iBpp;       //像素格式
       int iPitch;   /* 对于单色位图, 两行象素之间的跨度,比如8*16,则跨度是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;

在fonts_manager.c里

定义一个空链表:static PT_FontOpr g_ptFontOprHead = NULL;

写一个RegisterFontOpr()函数,子文件通过调用该函数来注册到链表g_ptFontOprHead里

写一个GetFontOpr()函数,用来获取字体的name

写一个FontsInit()函数,用来被main.c初始化时调用:

int FontsInit(void)
{
       int iError;
       iError = ASCIIInit();              //调用./fonts/ascii.c里的ASCIIInit()函数
       if (iError)
       {
              DBG_PRINTF("ASCIIInit error!\n");
              return -1;
       }

       iError = GBKInit();   //调用./fonts/gbk.c里的GBKInit ()函数
       if (iError)
       {
              DBG_PRINTF("GBKInit error!\n");
              return -1;
       }

       iError = FreeTypeInit();  //调用./fonts/freetype.c里的FreeTypeInit ()函数
       if (iError)
       {
              DBG_PRINTF("FreeTypeInit error!\n");
              return -1;
       }
       return 0;
}

写一个GetFontOpr()函数,该函数被编码文件调用,来使编码文件与字体关联起来,比如通过utf-8编码找到对应的freetype字体。

写字体文件,以freetype.c(矢量字体)为例

首先定义一个T_FontOpr结构体

定义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, ptFontBitMap->iYMax, ptFontBitMap->iNextOriginX, ptFontBitMap->iNextOriginY);
       return 0;
}

int FreeTypeInit(void)
{
       return RegisterFontOpr(&g_tFreeTypeFontOpr);
}

7、写编码部分

在encoding_managet.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为例

比如:

对于ansi.c(编码文件),其实就是GBK编码,ascii占1字节,使用ascii点阵库,汉字占2字节,使用HZK16汉字库。

对于utf-8.c(编码文件),ascii只占1字节,使用ascii点阵库,而汉字占2~4字节,由于freetype字库默认支持的是utf-16格式,所以需要utf-8转换为utf-16后,再使用freetype字库,转换如下图所示:

8、写draw.c

8.1 首先定义一个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;

8.2 写一个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;
       }
}

8.3 写一个ShowOnePage()显示一页函数

首先设置原点xy为(0, fontsize),通过编码结构体的成员函数获取编码,判断编码是否为\r \n \t,然后通过字体结构体的成员函数将编码转换为位图,然后判断是否换行,满页,最后显示

8.4 写一个SetTextDetail()函数

通过支持的编码,来设置HZK,freetype,ascii字体文件,以及字体大小,供给main.c调用

9、写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:")来解析命令行,获取每个选项后的参数

2、然后调用管理文件的初始化函数,去初始化显示文件fb.c,字体文件freetype.c,gbk.c等,以及添加链表,比如:iError = DisplayInit();//最终调用FBInit();->RegisterDispOpr(&g_tFBOpr);

3、因为optind等于<text_file>位置,所以通过optind打开<text_file>文件:

strncpy(acTextFile, argv[optind], 128);
       acTextFile[127] = '\0';
iError = OpenTextFile(acTextFile);        //里面进行mmap(),并获取该文件所支持的编码结构体

4、设置文本细节(HZK库,freetype库,文字大小)

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退出

猜你喜欢

转载自blog.csdn.net/xiaodingqq/article/details/84861980