数码相框(六、在LCD上显示任意编码的文本文件)

注:本人已购买韦东山第三期项目视频,内容来源《数码相框项目视频》,只用于学习记录,如有侵权,请联系删除。

1. 怎么在LCD上显示文件:

(1) 去文件中获得编码,例如:ASCII、GBK、UTF-8、UFT16LE、UTF16BE等;
(2) 根据该编码得到字体数据(即字体点阵数据)
(3) 把点阵在LCD上显示出来;

2. 以面向对象的思想编写程序,即模块化

(1) 以前写驱动的时候,步骤:

  • 分配一个结构体;
  • 设置结构体;
  • 注册;

(2) 以面向对象的思想,设计应用程序框架:
在这里插入图片描述

3. 模块分析

3.1 显示模块分析

(1) 在显示模块中,主要实现的功能是LCD的初始化、像素显示、清屏。以面向对象的思想,声明一个显示设备的结构体,代码如下。实现一个显示设备,我们需填充该结构体结构(对应上面的fb.c)。

typedef struct DispOpr{
	char *name;  /*显示设备的设备名称*/
	int iXres;   /*LCD水平分辨率*/
	int iYres;   /*LCD垂直分辨率*/
	int iBpp;     /*LCD每个像素的位数,通常有8bit、16bit、24bit*/
	int (*DeviceInit)(void);  /*设备初始化*/
	int (*ShowPixel)(int iPenX, int iPenY, unsigned int dwColor);  /*像素显示*/
	int (*CleanScreen)(unsigned int dwBackColor); /*清屏*/
}T_DispOpr, *PT_DispOpr;

(2) 同一模块,可能有很多不同的设备,为了更好的管理这些设备,每个模块都应该有一个管理者,该模块的管理者是disp_manager.c。disp_manager.c与fb.c的关系如下图所示:
在这里插入图片描述
(3) disp_manager.c的代码如下:

#include <string.h>
#include <disp_manager.h>
#include <config.h>

static PT_DispOpr g_aptDispOprs[DISP_OPR_NUM];  /*定义disp_manager.c维护的全局数组,其中DISP_OPR_NUM在config.h被定义为4*/
static int g_iDispOprUsing;   

/* @brief	   向g_aptDispOprs数组注册一个PT_DispOpr结构体
 * @param[in]  ptDispOpr为需要向g_aptDispOprs注册的结构体
 * @retval     0:注册成功; -1:注册失败
 */
int RegisterDispOpr(PT_DispOpr ptDispOpr)
{
	int i;

	for (i = 0; i < DISP_OPR_NUM; i++)
	{
		if (g_aptDispOprs[i] == NULL)
			break;
	}

	if (i == DISP_OPR_NUM)
	{
		DBG_PRINTF("can't RegisterDispOpr, it's full\n");
		return -1;
	}
	else
	{
		g_aptDispOprs[i] = ptDispOpr;
	}

	return 0;
}

/* @brief   显示已注册结构体的名字   
 * @param   none
 * @retval  none
 */
void ShowDispOpr(void)
{
	int i;

	for (i = 0; i < DISP_OPR_NUM; i++)
	{
		if (g_aptDispOprs[i])
		{
			printf("%02d %s\n", i, g_aptDispOprs[i]->name);
		}
	}
}


/* @brief      根据显示设备的名称,选择对应的DispOpr结构体
 * @param[in]  显示设备的名称
 * @retval     0:成功;-1:失败
 */
int SelectDispOpr(char *pcName)
{
	int i;
	
	g_iDispOprUsing = -1;
	for (i = 0; i < DISP_OPR_NUM; i++)
	{
		if (g_aptDispOprs[i] && (strcmp(g_aptDispOprs[i]->name, pcName) == 0))
		{
			g_iDispOprUsing = i;
			return 0;
		}
	}

	return -1;
}

/* @brief      根据显示设备的名称,获取对应的DispOpr结构体
 * @param[in]  显示设备的名称
 * @retval     成功获取返回DispOpr结构体指针,否则返回NULL
 */
PT_DispOpr GetDispOpr(char *pcName)
{
	int i;

	for (i = 0; i < DISP_OPR_NUM; i++)
	{
		if (g_aptDispOprs[i] && (strcmp(g_aptDispOprs[i]->name, pcName) == 0))
		{
			return g_aptDispOprs[i];
		}
	}
	return NULL;

}

/* @brief    显示设备初始化
 * @param    none
 * @retval   0:成功; -1:失败  
 */
int DisplayInit(void)
{
	return FBInit();
}

(4) fb.c的代码如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <string.h>
#include <disp_manager.h>
#include <config.h>
#include <linux/fb.h>

static int g_iFBFb;
static struct fb_var_screeninfo g_tVar;
static struct fb_fix_screeninfo g_tFix;
static unsigned int g_dwScreenSize;
static unsigned char *g_pucFbMem;
static int g_iLineWidth;
static int g_iPixelWidth;

static int FBDeviceInit(void);
static int FBShowPixel(int iPenX, int iPenY, unsigned int dwColor);  
static int FBCleanScreen(unsigned int dwBackColor); 

static T_DispOpr g_tFBDispOpr = {
	.name        = "fb",
	.DeviceInit  = FBDeviceInit,
	.ShowPixel   = FBShowPixel,
	.CleanScreen = FBCleanScreen,
};
	
static int FBDeviceInit(void)
{
	int ret;
	
	g_iFBFb = open(FB_DEVICE_NAME, O_RDWR);
	if (g_iFBFb < 0)
	{
		DBG_PRINTF("can't open %s\n", FB_DEVICE_NAME);
		return -1;
	}

	ret = ioctl(g_iFBFb, FBIOGET_VSCREENINFO, &g_tVar);
	if (ret < 0)
	{
		DBG_PRINTF("can't get fb's var\n");
		return -1;
	}

	ret = ioctl(g_iFBFb, FBIOGET_FSCREENINFO, &g_tFix);
	if (ret < 0)
	{
		DBG_PRINTF("can't get fb's fix\n");
		return -1;
	}

	g_tFBDispOpr.iXres = g_tVar.xres;
	g_tFBDispOpr.iYres = g_tVar.yres;
	g_tFBDispOpr.iBpp  = g_tVar.bits_per_pixel;

	g_dwScreenSize = g_tVar.xres * g_tVar.yres * g_tVar.bits_per_pixel / 8;
	g_iLineWidth   = g_tVar.xres * g_tVar.bits_per_pixel / 8;
	g_iPixelWidth  = g_tVar.bits_per_pixel / 8;

	g_pucFbMem = (unsigned char *)mmap(NULL, g_dwScreenSize, PROT_READ | PROT_WRITE, MAP_SHARED, g_iFBFb, 0);
	if (g_pucFbMem == (unsigned char*)-1)
	{
		DBG_PRINTF("can't mmap\n");
		return -1;
	}	

	return 0;
}

static int FBShowPixel(int iPenX, int iPenY, unsigned int dwColor)
{
    unsigned char  *pucPen8 = g_pucFbMem + iPenY * g_iLineWidth + iPenX * g_iPixelWidth;
    unsigned short *pwPen16;
    unsigned int   *pdwPen32;
    int iRed, iGreen, iBlue;

    pwPen16  = (unsigned short *)pucPen8;
    pdwPen32 = (unsigned int *)pucPen8;

    switch(g_tFBDispOpr.iBpp)
    {
        case 8:  
        {
			*pucPen8 = dwColor; /*对于8BPP:color 为调色板的索引值,其颜色取决于调色板的数值*/
			break; 
        }
        case 16:
        {
        	/* 对于RGB888,red、green、blue分别使用8bit表示,
        	 * 把RGB888转为RGB565,red去掉低3位,green去掉低2位,blue去掉低3位
        	 */
            iRed   = (dwColor >> 16) & 0xff;
            iGreen = (dwColor >> 8)  & 0xff;
            iBlue  = (dwColor >> 0)  & 0xff;
            dwColor = ((iRed >> 3) << 11) | ((iGreen >> 2) << 5) | (iBlue >> 3); /*格式:RGB565*/
            *pwPen16 = dwColor;
            break;
        }
        case 32: 
		{
			*pdwPen32 = dwColor;
			break;
        }
        default:
        {
			DBG_PRINTF("can't surport %dbpp", g_tFBDispOpr.iBpp);
			return -1;
			break;
        }
    }

	return 0;
}

static int FBCleanScreen(unsigned int dwBackColor)
{
    unsigned char  *pucPen8 = g_pucFbMem;
    unsigned short *pwPen16;
    unsigned int   *pdwPen32;
    int iRed, iGreen, iBlue;
	int i = 0;
	
    pwPen16  = (unsigned short *)pucPen8;
    pdwPen32 = (unsigned int *)pucPen8;
	
	switch(g_tFBDispOpr.iBpp)
	 {
		 case 8:  
		 {
		 	memset(pucPen8, dwBackColor, g_dwScreenSize);
		 	break; 
		 }
		 case 16:
		 {
			 iRed	= (dwBackColor >> 16) & 0xff;
			 iGreen = (dwBackColor >> 8)  & 0xff;
			 iBlue	= (dwBackColor >> 0)  & 0xff;
			 dwBackColor = ((iRed >> 3) << 11) | ((iGreen >> 2) << 5) | (iBlue >> 3); /*格式:RGB565*/

			 while (i < g_dwScreenSize)
			 {
			 	*pwPen16 = dwBackColor;
				pwPen16++;  /* 每一次pwPen16++,地址加2 */
				i += 2;
			 }
			 
			 break;
		 }
		 case 32: 
		 {
		 	while (i < g_dwScreenSize)
			{
				*pdwPen32 = dwBackColor;
				pdwPen32++; /* 每一次pwPen16++,地址加4 */
				i += 4;
			}

			break;
		 }
		 default:
		 {
			 DBG_PRINTF("can't surport %dbpp", g_tFBDispOpr.iBpp);
			 return -1;
			 break;
		 }
	 }
	
	 return 0;
}


int FBInit(void)
{
	return RegisterDispOpr(&g_tFBDispOpr);
}
3.2 字体点阵模块分析

(1) 字体点阵模块如下图所示:
在这里插入图片描述
(2) 从上图可知,fonts_manager.c 维护的是FontOpr结构体,该结构体的成员:

  • name: 字体名称;
  • FontInit: 字体初始化;
  • GetFontBitmap:根据字体的编码获取字体的点阵(位图)。

fonts_manager.c的代码如下:

#include <string.h>
#include <fonts_manager.h>
#include <config.h>

static PT_FontOpr g_aptFontOprs[FONT_OPR_NUM];
static int g_iFontOprUsing;

int RegisterFontOpr(PT_FontOpr ptFontOpr)
{
	int i;

	for (i = 0; i < FONT_OPR_NUM; i++)
	{
		if (g_aptFontOprs[i] == NULL)
			break;
	}

	if (i == FONT_OPR_NUM)
	{
		DBG_PRINTF("can't RegisterFontOpr, it's full\n");
		return -1;
	}
	else
	{
		g_aptFontOprs[i] = ptFontOpr;
	}

	return 0;
}
void ShowFontOpr(void)
{
	int i;

	for (i = 0; i < FONT_OPR_NUM; i++)
	{
		if (g_aptFontOprs[i])
		{
			printf("%02d %s\n", i, g_aptFontOprs[i]->name);
		}
	}
}
int SelectFontOpr(char *pcName)
{
	int i;
	g_iFontOprUsing = -1;
	for (i = 0; i < FONT_OPR_NUM; i++)
	{
		if (g_aptFontOprs[i] && (strcmp(g_aptFontOprs[i]->name, pcName) == 0))
		{
			g_iFontOprUsing = i;
			return 0;
		}
	}

	return -1;
}
PT_FontOpr GetFontOpr(char *pcName)
{
	int i;

	for (i = 0; i < FONT_OPR_NUM; i++)
	{
		if (g_aptFontOprs[i] && (strcmp(g_aptFontOprs[i]->name, pcName) == 0))
		{
			return g_aptFontOprs[i];
		}
	}
	return NULL;	
}

int FontsInit(void)
{
	int iError;
	
	iError = AsciiInit();
	if (iError)
	{
		DBG_PRINTF("AsciiInit error!\n");
		return -1;
	}
	iError = GBKInit();
	if (iError)
	{
		DBG_PRINTF("GBKInit error!\n");
		return -1;
	}
	
	iError = FreeTypeInit();
	if (iError)
	{
		DBG_PRINTF("FreeTypeInit error!\n");
		return -1;
	}

	return 0;
}

(3) 其中,为了兼容ascii、gbk、freetype三种字体,还定义了一个用于描述字体位图的结构体FontBitmap,该结构体的代码与注释如下:

typedef struct FontBitMap{
    int iXLeft;       /* 位图左上角x轴坐标 */
    int iYTop;        /* 位图左上角y轴坐标 */
    int iXMax;        /* x方向的最大值 */
    int iYMax;        /* y方向的最大值 */
    int iBpp;         /* 用于表示位图的一个像素有多少个bit */
    int iPitch;       /* 表示对于单色位图,两行像素之间的跨度 */
    int iCurOriginX;  /* 记录当前位图原点的x轴位置 */
    int iCurOriginY;  /* 记录当前位图原点的y轴位置 */
    int iNextOriginX; /* 记录下一个位图原点的x轴位置 */
    int iNextOriginY; /* 记录下一个位图原点的y轴位置 */
    unsigned char *pucBuffer; /* 记录字体点阵存放的位置 */
}T_FontBitMap, *PT_FontBitMap;

(4) 字体点阵分析:
① ascii点阵: 对于8x16大小的ascii,它的点阵如下图所示。每一个8x16大小的ascii点阵占据16字节。它是一个单色位图,用1、0分别表示显示与不显示,所以该位图的Bpp=1。假设该位图的原点坐标为(iCurOriginX,iCurOriginY),则坐上角坐标(letf,top)为 (iCurOriginX,iCurOriginY - 16),下一个字体原点的坐标(iNextOriginX,iNextOriginY)为 (iNextOriginX + 8,iNextOriginY),位图x、y方向的的最大值分别为(iCurOriginX + 8)、iCurOriginY。
在这里插入图片描述

ascii.c代码如下:

#include <fonts_manager.h>
#include <config.h>
#include "font_8x16.h"

static int AsciiFontInit(char *pcFontFile, unsigned int dwFontSize)
{
	if (dwFontSize != 16)
	{
		DBG_PRINTF("ASCII can't support %d font size\n", dwFontSize);
		return -1;
	}
	
	return 0;
}

static int AsciiGetFontBitmap(unsigned int dwCode, PT_FontBitMap ptFontBitMap)
{
	int iPenX = ptFontBitMap->iCurOriginX;
	int iPenY = ptFontBitMap->iCurOriginY;

	if (dwCode > (unsigned int)0x80)
	{
		DBG_PRINTF("don't support this code : 0x%x\n", dwCode);
		return -1;
	}

	ptFontBitMap->iXLeft    = iPenX;
	ptFontBitMap->iYTop     = iPenY - 16;
	ptFontBitMap->iXMax     = iPenX + 8;
	ptFontBitMap->iYMax     = iPenY;
	ptFontBitMap->iBpp      = 1;
	ptFontBitMap->iPitch    = 1;
	ptFontBitMap->pucBuffer = (unsigned char *)&fontdata_8x16[dwCode * 16];

	ptFontBitMap->iNextOriginX = iPenX + 8;
	ptFontBitMap->iNextOriginY = iPenY;

	return 0;
}

static T_FontOpr g_tAsciiFontOpr = {
	.name          = "ascii",
	.FontInit      = AsciiFontInit,
	.GetFontBitmap = AsciiGetFontBitmap,
};

int AsciiInit(void)
{
	return RegisterFontOpr(&g_tAsciiFontOpr);
}

② gbk点阵: 下图是一个16*16的汉字点阵,它也是一个单色位图,每个位图占据32字节。假设该位图的原点坐标为(iCurOriginX,iCurOriginY),则坐上角坐标(letf,top)为 (iCurOriginX,iCurOriginY - 16),下一个字体原点的坐标(iNextOriginX,iNextOriginY)为 (iNextOriginX + 16,iNextOriginY),位图x、y方向的的最大值分别为(iCurOriginX + 16)、iCurOriginY。

在这里插入图片描述
gbk.c 代码如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

#include <fonts_manager.h>
#include <config.h>

static int g_iFdHZK;
static unsigned char *g_pucHZKMem;

static int GBKFontInit(char *pcFontFile, unsigned int dwFontSize)
{
	struct stat tStat;

	if (dwFontSize != 16)
	{
		DBG_PRINTF("GBK can't support %d fontsize\n", dwFontSize);
		return -1;
	}

	g_iFdHZK = open(pcFontFile, O_RDONLY);
	if (g_iFdHZK < 0)
	{
		DBG_PRINTF("can't open %s\n", pcFontFile);
		return -1;
	}

	if (fstat(g_iFdHZK, &tStat))
	{
		DBG_PRINTF("can't get fstat\n");
		return -1;
	}

	g_pucHZKMem = (unsigned char *)mmap(NULL, tStat.st_size, PROT_READ, MAP_SHARED, g_iFdHZK, 0);
	if (g_pucHZKMem == (unsigned char*)-1)
	{
		DBG_PRINTF("can't mmap for %s", pcFontFile);
		return -1;
	}

	return 0;
}

static int GBKGetFontBitmap(unsigned int dwCode, PT_FontBitMap ptFontBitMap)
{
	unsigned int dwArea;
	unsigned int dwWhere;
	
	int iPenX = ptFontBitMap->iCurOriginX;
	int iPenY = ptFontBitMap->iCurOriginY;

	if (g_pucHZKMem == NULL)
	{
		DBG_PRINTF("has not init GBK\n");
		return -1;
	}

	if (dwCode & 0xffff0000)
	{
		DBG_PRINTF("GBK don't support this code:0x%x\n", dwCode);
		return -1;
	}
	/* arm为小端模式:区码存在低字节位,位码存在高字节位 */
	dwArea  = (dwCode & 0xff) -0xA1;
	dwWhere = ((dwCode >> 8) & 0xff) - 0xA1;

	ptFontBitMap->iXLeft    = iPenX;
	ptFontBitMap->iYTop     = iPenX - 16;
	ptFontBitMap->iXMax     = iPenX + 16;
	ptFontBitMap->iYMax     = iPenY;
	ptFontBitMap->iBpp      = 1;
	ptFontBitMap->iPitch    = 2;
	ptFontBitMap->pucBuffer = g_pucHZKMem + (dwArea * 94 + dwWhere) * 32; /*每一个16*16汉字点阵占据32字节*/

	ptFontBitMap->iNextOriginX = iPenX + 16;
	ptFontBitMap->iNextOriginY = iPenY;

	return 0;
}

static T_FontOpr g_tGBKFontOpr = {
	.name          = "gbk",
	.FontInit      = GBKFontInit,
	.GetFontBitmap = GBKGetFontBitmap,
};

int GBKInit(void)
{
	return RegisterFontOpr(&g_tGBKFontOpr);
}

③ freetype点阵:
在这里插入图片描述
对于freetype来说,点阵的大小是可以变化的,点阵之间的距离也会改变,所以对于每一个字体点阵,我们都需要单独操作,单独记录它们的当前字体原点和下一字体原点坐标。
在这里插入图片描述
前面定义的FontBitMap结构体,描述的是LCD坐标系的位图。而Freeytpe获得的位图是基于笛卡尔坐标系坐标(0,0)获得的位图,把该位图从笛卡尔坐标系转换为LCD坐标系的过程如上图所示。获取一个LCD坐标系位图的代码如下:

static int FreeTypeGetFontBitmap(unsigned int dwCode, PT_FontBitMap ptFontBitMap)
{
	FT_Error iError;
	
	int iPenX = ptFontBitMap->iCurOriginX;
	int iPenY = ptFontBitMap->iCurOriginY;

	/* load glyph image into the slot (erase previous one) */
	/* FT_LOAD_RENDER表示转换为位图,要转换为单色位图需要添加参数FT_LOAD_MONOCHROME*/
	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;		
	}
	
   /* 笛卡尔坐标的左上角是(bitmap_left,bitmap_top),
    * 对应LCD的左上角是(iPenX+bitmap_left,iPenY-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;  /* g_tSlot->advance.x 的单位为1/64像素 */
	ptFontBitMap->iNextOriginY = iPenY;

	return 0;
}

freetype.c代码如下:

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

static FT_Library g_tLibrary;
static FT_Face g_tFace;
static FT_GlyphSlot g_tSlot;

static int FreeTypeFontInit(char *pcFontFile, unsigned int dwFontSize)
{
	FT_Error iError;
		
	/* 1.初始化library库 */
	iError = FT_Init_FreeType(&g_tLibrary);
	if (iError)
	{
		DBG_PRINTF("FT_Init_FreeType failed\n");
		return -1;
	}

	/* 2.创建face字体文件 */
	iError = FT_New_Face(g_tLibrary, pcFontFile, 0, &g_tFace);
	if (iError)
	{
		DBG_PRINTF("FT_New_Face failed\n");
		return -1;
	}

	g_tSlot = g_tFace->glyph;
	
	/* 3.设置字体大小 */
	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)
{
	FT_Error iError;
	
	int iPenX = ptFontBitMap->iCurOriginX;
	int iPenY = ptFontBitMap->iCurOriginY;

	/* load glyph image into the slot (erase previous one) */
	/* FT_LOAD_RENDER表示转换为位图,要转换为单色位图需要添加参数FT_LOAD_MONOCHROME*/
	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;		
	}

   /* 笛卡尔坐标的左上角是(bitmap_left,bitmap_top),
    * 对应LCD的左上角是(iPenX+bitmap_left,iPenY-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;  /* g_tSlot->advance.x 的单位为1/64像素 */
	ptFontBitMap->iNextOriginY = iPenY;

	return 0;
}

static T_FontOpr g_tFreeTypeFontOpr = {
	.name          = "freetype",
	.FontInit      = FreeTypeFontInit,
	.GetFontBitmap = FreeTypeGetFontBitmap,
};

int FreeTypeInit(void)
{
	return RegisterFontOpr(&g_tFreeTypeFontOpr);
}
3.3 编码模块分析

(1) 字体编码模块如下图所示:
在这里插入图片描述
(2) 从上图可知encoding_manager.c通过维护全局数组g_aptEncodingOpr负责管理ascii、utf-8、uft-16等不同字符编码的模块,其中EncodingOpr结构体的成员如下:

  • name:字符编码的名称;
  • iHeadLen:文件开头的字节数;(不同字符编码的文件iHeadLen长度:ascii/gbk:0,utf-8:3,utf-16be:2,utf-16le:2)
  • aptFontOprSupported:指针数组,用来存放支持该编码的字体结构体,以后就通过这个来显示文字;
  • isSupport:该函数判断要显示的文件是否支持xx字符编码格式;
  • GetCodeFrmBuf:将文件里的字节转为编码,存到*pdwCode里。

(3) 对于EncodingOpr结构体的成员isSupport是如何判断要显示的文件是否支持xx字符编码格式,我们需要知道以该xx字符编码的文件标志,一般一个文件的开头会有标志:

  • utf-8编码文件开头标志:EF BB BF
  • utf-16be编码文件开头标志:FE FF
  • utf-16le编码文件开头标志:FF FE
  • ascii/gbk编码的文件开头没有前缀

因此,我们可以通过判断文件的前iHeadLen字节来判断文件支持哪一种字符编码格式

utf-8的isSupport代码如下:

static int isUtf8Coding(unsigned char *pucBufHead)
{
	const char aStrUtf8[] = {0xEF, 0xBB, 0xBF, 0};

	if (strncmp((const char*)pucBufHead, aStrUtf8, 3) == 0)
	{
		/* UTF-8 */
		return 1;
	}
	else
	{
		return 0;
	}
}

utf-16be的isSupport代码如下:

static int isUtf16beCoding(unsigned char *pucBufHead)
{
	const char aStrUtf16be[] = {0xFE, 0xFF, 0};

	if (strncmp((const char*)pucBufHead, aStrUtf16be, 2) == 0)
	{
		/* UTF-16be */
		return 1;
	}
	else
	{
		return 0;
	}
}

utf-16ble的isSupport代码如下:

int isUtf16leCoding(unsigned char *pucBufHead)
{
	const char aStrUtf16le[] = {0xFF, 0xFE, 0};

	if (strncmp((const char*)pucBufHead, aStrUtf16le, 2) == 0)
	{
		/* UTF-16le */
		return 1;
	}
	else
	{
		return 0;
	}

}

ascii/gbk的isSupport代码如下:

/* 返回1表示支持Ascii编码 */
static int isAsciiCoding(unsigned char *pucBufHead)
{
	const char aStrUtf8[]    = {0xEF, 0xBB, 0xBF, 0};
	const char aStrUtf16le[] = {0xFF, 0xFE, 0};
	const char aStrUtf16be[] = {0xFE, 0xFF, 0};
	
	if (strncmp((const char*)pucBufHead, aStrUtf8, 3) == 0)
	{
		/* UTF-8 */
		return 0;
	}
	else if (strncmp((const char*)pucBufHead, aStrUtf16le, 2) == 0)
	{
		/* UTF-16 little endian */
		return 0;
	}
	else if (strncmp((const char*)pucBufHead, aStrUtf16be, 2) == 0)
	{
		/* UTF-16 big endian */
		return 0;
	}
	else
	{
		return 1;
	}

}

(4) 接下来就是如何获取文件中的字符编码,想要获取到文件的字符编码,我们需要根据该文件所支持的字符编码的格式来进行字符编码的解码并获得字符编码的值,不同字符编码的文件解码方法如下:

① ascii/gbk字符编码文件:
我们可以通过判断读到的编码值是否大于0x80得知该编码是ascii(码值小于0x80)还是gbk(码值大于等于0x80),如果是ascii就直接返回读到的编码值;如果是gbk编码则继续读取下一个编码值,把读到的编码值作为gbk编码的高字节,把之前读到的编码值作为gbk编码的低字节,计算gbk编码值后返回。代码如下:

static int AsciiGetCodeFrmBuf(unsigned char *pucBufStart, unsigned char *pucBufEnd, unsigned int *pdwCode)
{
	unsigned char *pucBuf = pucBufStart;
	unsigned char c = *pucBuf;
	
	if ((pucBuf < pucBufEnd) && (c < (unsigned char)0x80))
	{
		/* 返回ASCII码 */
		*pdwCode = (unsigned int)c;
		return 1;
	}

	if (((pucBuf +1) < pucBufEnd) && (c >= (unsigned char)0x80))
	{
		/* 返回GBK码 */
		*pdwCode = ((unsigned int)pucBuf[1] << 8) + pucBuf[0];
		return 2;
	}

	if (pucBuf < pucBufEnd)
	{
		/* 可能文件有损坏,但是还是返回一个码,即使它是错误的 */
		*pdwCode = (unsigned int)c;
	}
	else
	{
		/* 文件处理完毕 */
		return 0;
	}
	
}

② utf-8字符编码文件:
根据读到的第一个字节前导1的个数判断一个utf-8编码的字节长度,如果前导1的个数为0,说明该编码值是ascii;否则,按照utf-8的编码规则去解码。关于utf-8的编码规则可以查看数码相框(二、字符的编码方式)中utf-8字符编码部分的内容。 utf-8字符编码文件解码代码如下:

static  int Utf8GetCodeFrmBuf(unsigned char *pucBufStart, unsigned char *pucBufEnd, unsigned int *pdwCode)
{
	int i;
	int iNum;
	unsigned char ucVal;
	unsigned int dwSum = 0;
	
	if (pucBufStart >= pucBufEnd)
	{
		/* 文件结束 */
		return 0;
	}

	ucVal = pucBufStart[0];
	iNum = GetPreOneBits(pucBufStart[0]);

	if ((pucBufStart + iNum) >= pucBufEnd)
	{
		/* 文件结束 */
		return 0;
	}

	if (iNum == 0)
	{
		*pdwCode = pucBufStart[0];
		return 1;
	}
	else
	{
		ucVal = ucVal << iNum;
		ucVal = ucVal >> iNum;
		dwSum += ucVal;
		for (i = 1; i < iNum; i++)
		{
			ucVal = pucBufStart[i] & 0x3f;
			dwSum = dwSum << 6;
			dwSum += ucVal;
		}
		*pdwCode = dwSum;

		return iNum;
	}
}

③ utf-16be字符编码文件:
utf-16字符编码占据2字节空间,utf-16be中的“be”表字符编码的存储方式采用的是大字节序,即高地址存储低字节,低地址存储高字节。下面的utf-16le中的“le”表示字符编码的存储方式采用的是小字节序,即高地址存储高字节,低地址存储低字节。utf-16be字符编码文件解码代码如下:

static int Utf16beGetCodeFrmBuf(unsigned char *pucBufStart, unsigned char *pucBufEnd, unsigned int *pdwCode)
{
	if ((pucBufStart + 1) < pucBufEnd)
	{
		*pdwCode = (pucBufStart[0] << 8) + pucBufStart[1];
		return 2;
	}
	else
	{
		/* 文件结束 */
		return 0;
	}
}

④ utf-16le字符编码文件: utf-16le字符编码文件解码代码如下:

int Utf16leGetCodeFrmBuf(unsigned char *pucBufStart, unsigned char *pucBufEnd, unsigned int *pdwCode)
{
	if ((pucBufStart + 1) < pucBufEnd)
	{
		*pdwCode = (pucBufStart[1] << 8) + pucBufStart[0];
		return 2;
	}
	else
	{
		/* 文件结束 */
		return 0;
	}
}

4. draw.c分析

4.1 PageDesc结构分析

在编写draw.c之前,需要定义PageDesc结构体用于分页、换行控制,PageDesc结构体定义如下:

typedef struct PageDesc {
	int iPage;  /* 当前页数 */
	unsigned char *pucLcdFirstPosAtFile;   /* 在LCD上第一个字符位于文件的位置*/
	unsigned char *pucLcdNextPageFirstPosAtFile; /* 下一页的LCD上第一个字符位于文件的位置 */
	struct PageDesc *ptPrePage;  /* 上一页链表,指向上一个PageDesc结构体 */
	struct PageDesc *ptNextPage; /* 下一页链表,指向下一个PageDesc结构体 */
} T_PageDesc, *PT_PageDesc;
4.2 OpenTextFile函数分析

OpenTextFile函数的功能:① 打开文本文件;② 使用mmap对文本文件进行内存映射;③ 判断文本文件支持哪种编码;④ 获取文本文件第一个字符的位置。

static int g_iFdTextFile;                     /* 文件描述符 */
static unsigned char *g_pucTextFileMem;       /* 内存映射基地址 */
static unsigned char *g_pucTextFileMemEnd;    /* 内存映射结尾地址 */
static PT_EncodingOpr g_ptEncodingOprForFile; /* 用来指向该文件支持的编码EncodingOpr结构体 */

int OpenTextFile(char *pcFileName)
{
	struct stat tStat;

	g_iFdTextFile = open(pcFileName, O_RDONLY);
	if (g_iFdTextFile < 0)
	{
		DBG_PRINTF("can't open text file %s\n", pcFileName);
		return -1;
	}

	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);
	if (g_pucTextFileMem == (unsigned char *)-1)
	{
		DBG_PRINTF("can't mmap for text file\n");
		return -1;
	}

    /* 获取文件的结束地址 */
	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;
	}	
}
4.3 SetTextDetail函数分析

SetTextDetail函数的功能是通过文本文件所支持的编码,设置HZK、freetype、ascii字体文件,以及设置字体大小,提供给main函数调用。

int SetTextDetail(char *pcHZKFile, char *pcFileFreetype, unsigned int dwFontSize)
{
	int iError = 0;
	int i = 0;
	PT_FontOpr ptFontOpr;
	int iRet = -1;

	g_dwFontSize = dwFontSize;

	ptFontOpr = g_ptEncodingOprForFile->aptFontOprSupported[i];

	while(ptFontOpr)
	{
		if (strcmp(ptFontOpr->name, "ascii") == 0)
		{
			iError = ptFontOpr->FontInit(NULL, dwFontSize);
		}
		else if (strcmp(ptFontOpr->name, "gbk") == 0)
		{
			iError = ptFontOpr->FontInit(pcHZKFile, dwFontSize);
		}
		else
		{
			iError = ptFontOpr->FontInit(pcFileFreetype, dwFontSize);
		}
		
		DBG_PRINTF("%s, %d\n", ptFontOpr->name, iError);
		if (iError == 0)
		{
			/* 比如对于ascii编码的文件, 可能用ascii字体也可能用gbk字体, 
			 * 所以只要有一个FontInit成功, SetTextDetail最终就返回成功
			 */
			iRet = 0;
		}
		i++;
		ptFontOpr = g_ptEncodingOprForFile->aptFontOprSupported[i];
	}

	return iRet;
}
4.4 ShowOneFont函数分析

ShowOneFont函数的功能是根据字体的位图,在LCD上描绘文字。

int ShowOneFont(PT_FontBitMap ptFontBitMap)
{
	int x;
	int y;
	unsigned char ucByte =0;
	int i = 0;
	int bit;
	
	if (ptFontBitMap->iBpp == 1)
	{
	    /* 控制绘制换行 */
		for (y = ptFontBitMap->iYTop; y < ptFontBitMap->iYMax; y++)
		{
		    /* 根据 iPitch,获取下一行的buffer索引*/
			i = (y - ptFontBitMap->iYTop) * ptFontBitMap->iPitch;
			
			/* 绘制位图的一行像素 */
			for (x = ptFontBitMap->iXLeft, bit = 7; x < ptFontBitMap->iXMax; x++) 
			{
				if (bit == 7)
				{
					ucByte = ptFontBitMap->pucBuffer[i++];
				}

				if (ucByte & (1 << bit))
				{
					g_ptDispOpr->ShowPixel(x, y, COLOR_FOREGROUND); /* 字体颜色 */
				}
				else
				{
					/* 使用背景色, 不用描画 */
					//g_ptDispOpr->ShowPixel(x, y, 0); /*显示黑色*/
				}

				bit--;
				if (bit == -1)
				{
					bit = 7;
				}
			}
		}
	}
	else if (ptFontBitMap->iBpp == 8)
	{
		for (y = ptFontBitMap->iYTop; y < ptFontBitMap->iYMax; y++)
		{
			for (x = ptFontBitMap->iXLeft; x < ptFontBitMap->iXMax; x++)
			{
				//g_ptDispOpr->ShowPixel(x, y, ptFontBitMap->pucBuffer[i++]);
				if (ptFontBitMap->pucBuffer[i++])
					g_ptDispOpr->ShowPixel(x, y, COLOR_FOREGROUND);
			}
		}
	}
	else
	{
		DBG_PRINTF("ShowOneFont error, can't support %d bpp\n", ptFontBitMap->iBpp);
		return -1;
	}

	return 0;
}
4.5 ShowOnePage函数分析

ShowOnePage函数的功能:
① 设置位图的原点(iCurOriginX ,iCurOriginY )为 (0,fontsize);
② 获取文本文件里字体的编码;
③ 判断编码是否为\r \n \t,如果为\n就回车换行重新设置原点(iCurOriginX ,iCurOriginY ),跳出本次while循环,执行下一次while循环重新获取下文本文件里下一个字符的编码;如果为\t就不做处理直接获取下一个字符编码;如果为\t就使用空格代替;
④ 根据字符编码值获取文字的位图,然后判断是否换行、满页;
⑤ 在LCD显示位图,然后获取下一个位图的原点坐标(iCurOriginX ,iCurOriginY ),以便下一个字符显示。

int ShowOnePage(unsigned char *pucTextFileMemCurPos)
{
	int i;
	int iLen;
	int iError;
	unsigned char *pucBufStart;
	unsigned int dwCode;
	PT_FontOpr ptFontOpr;
	T_FontBitMap tFontBitMap;
	
	int bHasNotClrSceen = 1;
	int bHasGetCode = 0;

	tFontBitMap.iCurOriginX = 0;
	tFontBitMap.iCurOriginY = g_dwFontSize;
	pucBufStart = pucTextFileMemCurPos;

	while(1)
	{
		iLen = g_ptEncodingOprForFile->GetCodeFrmBuf(pucBufStart, g_pucTextFileMemEnd, &dwCode);
		if (iLen == 0)
		{
			/* 文件结束 */
			if (!bHasGetCode)
			{
				return -1;
			}
			else
			{
				return 0;
			}
		}

		bHasGetCode = 1;
		pucBufStart += iLen;
		
		/* 有些文本, \n\r两个一起才表示回车换行
		 * 碰到这种连续的\n\r, 只处理一次
		 */
		if (dwCode == '\n')
		{
			g_pucLcdNextPosAtFile = pucBufStart;

			/* 回车换行 */
			tFontBitMap.iCurOriginX = 0;
			tFontBitMap.iCurOriginY = IncLcdY(tFontBitMap.iCurOriginY);

			if (tFontBitMap.iCurOriginY == 0)
			{
				/* 显示完当前一屏了 */
				return 0;
			}
			else
			{
				continue;  /* 执行该语句后跳出本次循环,进入下一循环 */
			}
		}
		else if (dwCode == '\r')
		{
			continue; /* 执行该语句后跳出本次循环,进入下一循环 */
		}
		else if (dwCode == '\t')
		{
			/* TAB键使用一个空格代替 */
			dwCode = ' ';
		}

		//DBG_PRINTF("dwCode = 0x%x\n", dwCode);

		i = 0;
		ptFontOpr = g_ptEncodingOprForFile->aptFontOprSupported[i++];
		while(ptFontOpr)
		{
			iError = ptFontOpr->GetFontBitmap(dwCode, &tFontBitMap);
			if (iError == 0)
			{
				if (RelocateFontPos(&tFontBitMap))
				{
					/* 剩下的LCD空间不能满足显示这个字符 */
					return 0;
				}

				if (bHasNotClrSceen)
				{
					g_ptDispOpr->CleanScreen(COLOR_BACKGROUND);
					bHasNotClrSceen = 0;
				}

				/* 显示一个字符 */
				if (ShowOneFont(&tFontBitMap))
				{
					return -1;
				}

				tFontBitMap.iCurOriginX = tFontBitMap.iNextOriginX;
				tFontBitMap.iCurOriginY = tFontBitMap.iNextOriginY;
				g_pucLcdNextPosAtFile = pucBufStart;

				/* 跳出本层while循环,继续取出下一个编码显示 */
				break;
			}
			ptFontOpr = g_ptEncodingOprForFile->aptFontOprSupported[i++];
		}
	}
	
	return 0;
}
4.6 ShowNextPage函数分析

ShowNextPage函数的功能主要负责:①在LCD显示下一页文字;② 为PageDesc结构体申请空间,设置PageDesc结构体记录当前页、下一页的地址,并将其放入g_ptPages链表中。

int ShowNextPage(void)
{
	int iError;
	PT_PageDesc ptPage;
	unsigned char *pucTextFileMemCurPos;
    /* 第一次执行else分支,当g_ptCurPage为空,我们可以得知当前显示的是第一页*/
	if (g_ptCurPage)
	{
		/* 以后再调用ShowNextPage 会得到下一页的地址 */
		pucTextFileMemCurPos = g_ptCurPage->pucLcdNextPageFirstPosAtFile;
	}
	else
	{
		/* 根据OpenTextFile得到文件数据开始的地方 g_pucLcdFirstPosAtFile,在这里就可以使用了 */
		pucTextFileMemCurPos = g_pucLcdFirstPosAtFile;
	}
    /* 显示一页 */
	iError = ShowOnePage(pucTextFileMemCurPos);
	DBG_PRINTF("%s %d, %d\n", __FUNCTION__, __LINE__, iError);
	if (iError == 0)
	{
		/* 如果还有下一页,指定下一页地址,第一次执行时没有下一页,不执行if里的语句 */
		if (g_ptCurPage && g_ptCurPage->ptNextPage)
		{
			g_ptCurPage = g_ptCurPage->ptNextPage;
			return 0;
		}
		
		ptPage = malloc(sizeof(T_PageDesc));
		if (ptPage)
		{
			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;
		}
		else
		{
			return -1;
		}
	}
	return iError;
}
4.7 ShowPrePage 函数分析

ShowPrePage 函数的功能:① 通过双向链表得到上一页的文件位置,并显示上一页的文字;② 更新当前页g_ptCurPage = g_ptCurPage->ptPrePage。

int ShowPrePage(void)
{
	int iError;
	
	DBG_PRINTF("%s %d\n", __FUNCTION__, __LINE__);
	if (!g_ptCurPage || !g_ptCurPage->ptPrePage)
	{
		return -1;
	}

	DBG_PRINTF("%s %d, pos = 0x%x\n", __FUNCTION__, __LINE__, (unsigned int)g_ptCurPage->ptPrePage->pucLcdFirstPosAtFile);
	iError = ShowOnePage(g_ptCurPage->ptPrePage->pucLcdFirstPosAtFile);
	if (iError == 0)
	{
		DBG_PRINTF("%s %d\n", __FUNCTION__, __LINE__);
		g_ptCurPage = g_ptCurPage->ptPrePage;
	}
	
	return iError;

}

5. main函数处理流程

在这里插入图片描述

6. Makefile编写

对于不同功能模块的.c文件,我们把它放到了不同的文件夹,方便工程管理。因此,该工程的Makefile代码如下:

CROSSCOMPILE := arm-linux-

CFLAGS 	:= -Wall -O2 -c
CFLAGS  += -I$(PWD)/include

LDFLAGS := -lm -lfreetype

CC 	:= $(CROSSCOMPILE)gcc
LD 	:= $(CROSSCOMPILE)ld

OBJS := main.o \
		display/disp_manager.o        \
		display/fb.o                  \
		encoding/ascii.o              \
		encoding/encoding_manager.o   \
		encoding/utf-8.o              \
		encoding/utf-16be.o           \
		encoding/utf-16le.o           \
		draw/draw.o                   \
		fonts/ascii.o                 \
		fonts/fonts_manager.o         \
		fonts/freetype.o              \
		fonts/gbk.o              
		
all: $(OBJS)
	$(CC) $(LDFLAGS) -o show_file $^

clean:
	rm -f show_file
	rm -f $(OBJS)

%.o:%.c
	$(CC) $(CFLAGS) -o $@ $<

7. 代码运行效果

把编译好的show_file应用程序拷贝到JZ2440开发板上,执行以下命令运行程序:

./show_file -s 32 -f MSYH.TTF -h HZK16 utf16be.txt

运行效果如下图所示,在串口终端输入n显示下一页,输入u显示上一页,输入q退出程序。
在这里插入图片描述 在这里插入图片描述

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Louis@L.M.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值