數碼相框_電子書之程式碼閱讀及編寫(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退出