怎么显示一个电子书? 获得电子书的编码格式,利用库解析该编码,转换为可显示的位图,将数据描画出来。
模块化思想:编码格式模块,库模块,显示模块,描画,输入模块。根据传入参数
面向对象思想:每个模块创建一个结构机构体链表,如编码模块,有一个编码链表,各编码格式设置为一个结构体,注册入链表
以后我们操作,只需要操作到链表,而不需要到操作到底层各编码格式的具体函数。
操作过程:根据传入参数,找到支持该编码格式的结构体,从该结构体中找到支持该编码格式转化为位图的库,调用该库转化为位图,描画到显示模块输出。以下是具体一步步的从源码分析:
一.解析传入参数
从main函数开始 ,一开始会根据传进来的参数进行解析,
./show_file [-s Size] [-f freetype_font_file] [-h HZK] <text_file> //输入信息
dwFontSize = strtoul(optarg, NULL, 0); //size存放的位置,optagr表示参数存在optagr
strncpy(acFreetypeFile, optarg, 128); //freetype(字体库)存放的位置
acFreetypeFile[127] = '\0';
strncpy(acHzkFile, optarg, 128); //汉子库存放位置(基本没用到)
acHzkFile[127] = '\0';
strncpy(acDisplay, optarg, 128); //显示设备名字存放的
acDisplay[127] = '\0';
二.解析完之后,iError = OpenTextFile(acTextFile); 进入OpenTextFile ,看做了什么
1.主要是获得文件首地址:g_pucTextFileMem,怎么计算?open文件后得到句柄,fstat获得文件属性,mmap地址空间到内存
2.计算得到文件结束地址:g_pucTextFileMemEnd,如何计算?首地址+fstat获得的size
3.获得支持文件编码的结构体g_ptEncodingOprForFile,如何获得? SelectEncodingOprForFile(g_pucTextFileMem),该函数会调用g_ptEncodingOprHead 即 编码链表 取出每个 isSupport 函数 判断哪一个编码支持,若支持则返回该编码的结构体。这里面isSupport是根据传入的参数,即文件首地址 g_pucTextFileMem 然后strncmp 得到的,因为这几个编码格式的头部不一样。
4.如果获得了编码结构体 ,则计算得到数据真正开始的地方:g_pucLcdFirstPosAtFile =首地址+结构体的头部 //即头部之后的才是数据
三 SetTextDetail(acHzkFile, acFreetypeFile, dwFontSize)
该函数里,根据传入的库文件,字体文件,字体大小,找到支持的字体库,并对该库初始化。怎么找的呢?进入函数发现,根据“二”中得到的支持编码结构体g_ptEncodingOprForFile 里的成员 ptFontOprSupportedHead 的name 找到该库,并调用其初始化函数。举个例子例如,在g_ptEncodingOprForFile 如果是 g_tUtf8EncodingOpr,即utf-8编码格式的文件,就会找到支持utf-8 的库,utf-8里有freetype库和ascii库,我们在为utf-8添加支持库时,把freetype放在 ptFontOprSupportedHead了头部,所以拿ptFontOprSupportedHead链表里的第一个成员去匹配时,会先找到freetype库,然后调用其初始化函数初始化这个库。
四 选择显示设备 以及 初始化所有输入设备
1.选择显示设备很简单,SelectAndInitDisplay(acDisplay);acDisplay里存放着我们传入的设备名(在“一”中以说明),然后根据设备名从显示设备链表中找到匹配的成员,取出该结构体并调用其初始化函数初始化设备。
2.初始化所有输入设备也简单:AllInputDevicesInit();该函数会去输入设备链表对每一个成员都初始化,并且创建子线程,等待数据传入后唤醒。一开始是如何休眠的?在初始化设备时(调用每个成员的初始化函数),我们open的方式为阻塞方式。iRet = ts_read(g_tTSDev, &tSamp, 1); /* 如果无数据则休眠 *,有数据传入,就会调用 InputEventTreadFunction 去唤醒主线程。
五 真正的显示函数ShowNextPage() 里的 ShowOnePage() 分析这两个函数即可知道如何显示
ShowNextPage(),该函数里会调用ShowOnePage()去显示一页,之后就会根据输入事件去判断显示哪一页了
1.分析ShowNextPage() 主要做了哪些事情?
1.1得到该页首地址(数据真正开始的地方),存放在 pucTextFileMemCurPos
//第一次先执行else,因为 g_ptCurPage一开始等于NULL
if (g_ptCurPage)
{
//以后再调用ShowNextPage 会得到下一页的地址
pucTextFileMemCurPos = g_ptCurPage->pucLcdNextPageFirstPosAtFile;
}
else
{
//根据OpenTextFile得到文件数据开始的地方 g_pucLcdFirstPosAtFile,在这里就可以使用了
pucTextFileMemCurPos = g_pucLcdFirstPosAtFile;
}
1.2.根据当前位置开始显示一页内容,显示完成返回 0 ;
iError = ShowOnePage(pucTextFileMemCurPos);
1.3.初始化下一页,申请空间,设置下一页地址,并且记录当前显示的页,将其放入Page链表。具体看代码
if (iError == 0)
{
//如果还有下一页,指定下一页地址,第一次没有下一页,不执行该if语句
if (g_ptCurPage && g_ptCurPage->ptNextPage)
{
g_ptCurPage = g_ptCurPage->ptNextPage;
return 0;
}
//为下一页申请内存,同时做一些初始化,ptPage是个双向链表成员
ptPage = malloc(sizeof(T_PageDesc));
if (ptPage)
{
//记录下该页位置,以及记录下一页位置,g_pucLcdNextPosAtFile怎么来的呢?在
showOnePage中得到的
ptPage->pucLcdFirstPosAtFile = pucTextFileMemCurPos;
ptPage->pucLcdNextPageFirstPosAtFile = g_pucLcdNextPosAtFile;
ptPage->ptPrePage = NULL;
ptPage->ptNextPage = NULL;
g_ptCurPage = ptPage;
DBG_PRINTF("%s %d, pos = 0x%x\n", __FUNCTION__, __LINE__, (unsigned int)ptPage->pucLcdFirstPosAtFile);
//将ptPage添加入g_ptPages链表中,RecordPage 中会对双链表操作,指定next和pre
RecordPage(ptPage);
return 0;
}
}
2.分析主要的显示函数ShowOnePage
2.1 得到了该编码用多少字节表示这个“字” ,得到dwCode 编码的内容,这就是GetCodeFrmBuf 里的功能(如何得到这些,需要相关编码知识)
iLen = g_ptEncodingOprForFile->GetCodeFrmBuf(pucBufStart, g_pucTextFileMemEnd, &dwCode)
pucBufStart = pucTextFileMemCurPos,pucTextFileMemCurPos 数据真正开始的地方,在 (五.1.1)中注明来源
g_pucTextFileMemEnd 即文本结束的地址,在(二.2)中得到
2.2 得到下一“字”(这里的字,表示要显示的一个字,如一个汉字“中”或)开始的地址 pucBufStart += iLen ,获得支持的库的结构体
ptFontOpr = g_ptEncodingOprForFile->ptFontOprSupportedHead;,准备开始转换位图
2.3 转换位图 得到位图存于tFontBitMap,并且重新计算显示位置,显示位图 ShowOneFont(&tFontBitMap),计算下一“字”的原点坐标以及数据开始的地方,break跳出又从(五.2.1)开始执行。具体代码注释如下:
while (ptFontOpr)
{
DBG_PRINTF("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
//根据编码开始转换位图,转换成功返回0 ,且位图存在 tFontBitMap
iError = ptFontOpr->GetFontBitmap(dwCode, &tFontBitMap);
DBG_PRINTF("%s %s %d, ptFontOpr->name = %s, %d\n", __FILE__, __FUNCTION__, __LINE__,
ptFontOpr->name, iError);
if (0 == iError)
{
DBG_PRINTF("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
//重新计算显示位置
if (RelocateFontPos(&tFontBitMap))
{
/* 剩下的LCD空间不能满足显示这个字符 */
return 0;
}
DBG_PRINTF("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
if (bHasNotClrSceen)
{
/* 首先清屏 */
g_ptDispOpr->CleanScreen(COLOR_BACKGROUND);
bHasNotClrSceen = 0;
}
DBG_PRINTF("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 显示一个字符 */
if (ShowOneFont(&tFontBitMap))
{
return -1;
}
//获得下一“字”的原点坐标以及数据开始的地方
tFontBitMap.iCurOriginX = tFontBitMap.iNextOriginX;
tFontBitMap.iCurOriginY = tFontBitMap.iNextOriginY;
g_pucLcdNextPosAtFile = pucBufStart;
/* 跳出while循环,继续取出下一个编码来显示 */
break;
}
ptFontOpr = ptFontOpr->ptNext;
}
关于转换位图 怎么转换呢?可以去一个库文件看看便知,如freetype.c 主要是设置 各种参数,Xtop,Ytop,buf地址等,这些值怎么来的呢?是初始化的时候,我们就去计算来的。
2.3.1这里面我们还要关心的两个函数是RelocateFontPos(&tFontBitMap) 重新计算位置 和 ShowOneFont(&tFontBitMap) 显示一个字符,重新计算位置这个函数都比较简单,根据位图信息,如果Xmax>Xres(x方向分辨率)就换行,同理Ymax>Yres换页,满页了,返回,不显示该字了。
2.3.2说一下ShowOneFont 会里面是个双层for循环,即y方向和x方向双层for,显示位图的每一个像素,如果该像素有值就显示,没有值就显示和背景色一样的颜色。显示每一个像素就调用到了 显示设备的 g_ptDispOpr->ShowPixel(x, y, unsigned int dwColor);
问题1. x,y的参数怎么来的?
答:去看ShowOneFont(&tFontBitMap),根据传入的位图,位图的x,y坐标等信息都存在了里面,我们从位图原点开始画每个像素即可。
问题2. 怎么显示一个像素的呢?
首先获得该像素的显示地址:
//得到该像素的显示地址,g_dwLineWidth * iY表示在哪一行,g_dwPixelWidth * iX 表示x值
pucFB = g_pucFBMem + g_dwLineWidth * iY + g_dwPixelWidth * iX;
然后根据传入的颜色值dwColor 这个颜色值 转换成 8bpp、16bpp或者32bpp 的 color值,再将其赋值到 显示地址,即可显示出来。
六.根据输入事件判断显示上一页还是下一页
我们在(四.2)时AllInputDevicesInit(),为输入事件创建了子线程函数并且让输入事件 ptTmp->GetInputEvent 休眠了
iRet = ts_read(g_tTSDev, &tSamp, 1); /* 如果无数据则休眠 */,它的唤醒函数 InputEventTreadFunction。有数据传来,处理数据,给
ptInputEvent->iVal赋值,然后返回0,GetInputEvent ==0 就会执行相应的if语句了。
七.总结
其实分析那么多,最主要的是想把细节说清楚,掌握这些细节的知识,如freetype是怎么获得位图的,才算是真正的懂。如果答不出来,说明只是半桶水而已。
所以,细节又涉及很多知识点,具体知识点不在这里说明,只说方法。我认为重要的细节点是
1.ascii、utf-8、utf-16be、utf-16le编码如何分辨?即如何分辨出文件的编码格式,很简单,根据数据头部信息判断格式
如何解码?主要是掌握utf-8, 因为它不固定,即表示一个字用的字节数不固定
2.各库freetype·,gdk,ascii如何 转换为位图?其中,以freetype最为重要
3.如何在lcd上显示?这个问题相当复杂,详细可以看第五大点,细分为以下几个问题
如何显示在lcd显示一个像素(提示:mmap显存空间,将数据以x,y计算所得位置),如何显示一个位图的数据,如何显示一页数据,怎么换行,怎么换页(提示:每次会计算下一页地址,并且该页会放入双链表中),输入事件怎么休眠的?