电子书项目笔记

框架

将程序模块化,一个功能由一个.c文件完成,并且将这些功能分层。

  1. (上)主程序main.c、draw.c:完成主程序总流程,“初始化->关键变量设置->LCD显示操作”。
  2. (中)Display_manager部分、Fonts_manager部分、Encoding_manager部分、input_manager部分、debug_manager部分:各自管理维护一条链表,提供上层匹配以及初始化接口,提供下层注册。
  3. (底)各子功能的定义及初始化,注册到对应manager维护的链表中。

关键代码思路

一. Encoding部分:

文本的编码格式有:ANSI、unicode、unicode big endian、UTF-8 共四种。
在这里插入图片描述

  1. ANSI:英文用ASCII码表示(一字节),汉字用GBK国标码表示(两字节)。
    问题:如何通过码值知道该字符是英文还是汉字呢?
    可通过码值大小来确定,英文码值<0x80,汉字码值>=0x80。
  2. unicode:小端存储方式,两字节表示,文件头部用0xFF, 0xFE两字节来表示。
  3. unicode big endian:大端存储方式,两字节表示,文件头部用0xFE, 0xFF两字节来表示。
  4. UTF-8:小端存储方式,可变长字节表示,文件头部用0xEF, 0xBB, 0xBF三字节来表示。

从上可知,当我们打开一个未知编码格式的文本文件(电子书)时,可以根据头部信息来判断其编码格式,从而可以根据其编码格式,采取对应的解码方式及获取点阵方式。

解码方式:

  1. ANSI: 通过头部信息判断为ANSI编码格式,那么解码时读一个字节若码值小于0x80则直接通过该码值获取对应点阵,若大于0x80则继续读入下一个字节(两字节)。

对应代码:

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 = pucBuf[0] + (((unsigned int)pucBuf[1])<<8);
		return 2;
	}

	if (pucBuf < pucBufEnd)
	{
		/* 可能文件有损坏, 但是还是返回一个码, 即使它是错误的 */
		*pdwCode = (unsigned int)c;
		return 1;
	}
	else
	{
		/* 文件处理完毕 */
		return 0;
	}
}
  1. unicode:
static int Utf16leGetCodeFrmBuf(unsigned char *pucBufStart, unsigned char *pucBufEnd, unsigned int *pdwCode)
{
	if (pucBufStart + 1 < pucBufEnd)
	{
		*pdwCode = (((unsigned int)pucBufStart[1])<<8) + pucBufStart[0];
		return 2;
	}
	else
	{
		/* 文件结束 */
		return 0;
	}
}
  1. unicode big endian:
static int Utf16beGetCodeFrmBuf(unsigned char *pucBufStart, unsigned char *pucBufEnd, unsigned int *pdwCode)
{
	if (pucBufStart + 1 < pucBufEnd)
	{
		*pdwCode = (((unsigned int)pucBufStart[0])<<8) + pucBufStart[1];
		return 2;
	}
	else
	{
		/* 文件结束 */
		return 0;
	}
}
  1. UTF-8:较复杂
    对于UTF-8编码中的任意字节B,如果B的第一位为0,则B为ASCII码,并且B独立的表示一个字符;
    如果B的第一位为1,第二位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的一个字节,并且不为字符的第一个字节编码;
    如果B的前两位为1,第三位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由两个字节表示;
    如果B的前三位为1,第四位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由三个字节表示;
    如果B的前四位为1,第五位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由四个字节表示;
    因此,对UTF-8编码中的任意字节,根据第一位,可判断是否为ASCII字符;
    根据前二位,可判断该字节是否为一个字符编码的第一个字节;
    根据前四位(如果前两位均为1),可确定该字节为字符编码的第一个字节,并且可判断对应的字符由几个字节表示;
    根据前五位(如果前四位为1),可判断编码是否有错误或数据传输过程中是否有错误。
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)
	{
		/* ASCII */
		*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;
	}
}

获取点阵方式:

  1. ascii码: (unsigned char *)&fontdata_8x16[码值 * 16];
  2. HZK16:汉字库映射起始位置 + (高字节* 94 + 低字节)*32;
  3. freetype:g_tSlot->bitmap.buffer; // 通过FT_Load_Char将glyph关键点信息转换为位图

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

二. Fonts部分:

字体在显示时,需要确定显示的坐标和字体大小等参数,ANSI与GBK的每个字体点阵之间的距离固定,ascii为8 * 16,HZK16为16 * 16,而freetype的每个字体点阵大小不固定,间距是变化的,因此要构造一个通用结构体来表示会比较复杂。

问题1:为什么要构造一个通用结构体来描述他们呢?
答: 一个通用的结构体兼容具有差异性的功能,方便管理和使用,简化框架,可实现统一且通用的接口函数。
问题2:如何构造?

三种点阵存储方式如下图:
在这里插入图片描述
分析:A每一行的跨度为1字节,B的每一行的跨度为2字节,C的每一行跨度slot->bitmao->pich获取(结果向上取整,例如17位则用3字节表示),对于A\B来说十分简单,但是为兼容freetype的显示需引入其他成员。

关于freetype的显示:
第一需要进行LCD坐标转换;
第二字体点阵的原点坐标为(pen.x + advance.x, pen.y + advance.y),但是注意他所显示的坐标不一定为原点坐标,会有偏差;
第三,点阵的表示有两种情况,MONO位图:1bit表示一个像素;anti-color:1Byte表示一个像素。

知道了上述知识点,我们可以定义如下通用结构体:

typedef struct FontBitMap {
	int iXLeft;	
	int iYTop; //该字体显示的LCD坐标
	int iXMax;
	int iYMax; //该字体最大的长度
	int iBpp;	//freetype像素的表示方式
	int iPitch;   /* 对于单色位图, 两行象素之间的跨度 */
	int iCurOriginX;	
	int iCurOriginY;	//当前原点坐标
	int iNextOriginX;
	int iNextOriginY;	//下一个原点坐标
	unsigned char *pucBuffer;	//指向点阵的存储空间
}T_FontBitMap, *PT_FontBitMap;

问题三:如何设置freetype字体间距

实际LCD显示坐标原点计算:
在这里插入图片描述

  1. iPenX = iCurOriginX;
  2. iPenY = iCurOriginY;
  3. iXLeft = iPenX + slot->bitmap_left;
  4. iYTop = iPenY - slot->bitmap_top; //这里注意 y 轴方向相反了,用 - 号
  5. iXMax = iXLeft + slot->bitmap->width;
  6. iYMax = iYTop + slot->bitmap->rows;
  7. iNextOriginX = iPenX + slot->advance.x / 64;
  8. iNextOriginY = iPenY;

问题四:由于freetype字体大小无法确定,如何保证显示字体使不会溢出屏幕?

在这里插入图片描述
1点:将字体显示的当前x坐标与字体横向长度之和 iXMax,与屏幕的xres分辨率作比较,如果溢出则需换行。
2点:将字体显示的当前y坐标与字体纵向长度之和 iYMax,与屏幕的yres分辨率作比较,如果溢出则需换页。
iXMax > var.xres 则需换行
iYMax > var.yres 则需换页

换行操作代码:

			iDeltaX = 0 - iCurOriginX;
			iDeltaY = FontSize;

			iCurOriginX  += iDeltaX;
			iCurOriginY  += iDeltaY;

			iNextOriginX += iDeltaX;
			iNextOriginY += iDeltaY;

			iXLeft += iDeltaX;
			iXMax  += iDeltaX;

			iYTop  += iDeltaY;
			iYMax  += iDeltaY;;

换页操作:

  1. 清屏
  2. iCurOriginX = 0;
    iCurOriginY = FontSize;
    pucTextFileMemCurPos = 显示了多少字节 + 起始地址;
    pucBufStart = pucTextFileMemCurPos;
三.freetype的字体颜色显示方式

MONO位图:1bit表示一个像素;//单色图,底色为黑
anti-color:1Byte表示一个像素; //彩色(反色)位图,底色可以设置

区别:
单色说直白一点就是一种颜色,比如红,黄,蓝绿,而彩色的就有多重颜色混搭,这就更加具有视觉感官。
数据大小不同,用彩色位图表示的一个字体数据量为单色图的八倍。

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++)
		{
			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;
}
三. 文本的读取

通过指定的解码方式,按一定字节循环读取出,特殊字符:换行,制表符等。

  1. 普通字符:根据点阵显示对应字体。
  2. 文本换行有两种"\n\r",“\n”: 只处理“\n”,"\r"不做处理
  3. 制表符“\t”:打印空格
  4. 读取0字节:文本已读取完毕
四. 如何显示一页

int ShowOnePage(unsigned char *pucTextFileMemCurPos)
{
	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 (0 == iLen)
		{
			/* 文件结束 */
			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 (0 == tFontBitMap.iCurOriginY)
			{
				/* 显示完当前一屏了 */
				return 0;
			}
			else
			{
				continue;
			}
		}
		else if (dwCode == '\r')
		{
			continue;
		}
		else if (dwCode == '\t')
		{
			/* TAB键用一个空格代替 */
			dwCode = ' ';
		}

		DBG_PRINTF("dwCode = 0x%x\n", dwCode);
		
		ptFontOpr = g_ptEncodingOprForFile->ptFontOprSupportedHead;
		while (ptFontOpr)
		{
			DBG_PRINTF("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
			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;

				/* 继续取出下一个编码来显示 */
				break;
			}
			ptFontOpr = ptFontOpr->ptNext;
		}		
	}

	return 0;
}

五. 上一页与下一页的实现

通过双向链表实现上一页和下一页功能。
双向链表的结构:

typedef struct PageDesc {
	int iPage; //当前第几页
	unsigned char *pucLcdFirstPosAtFile; //当前页对应电子书的内容位置
	unsigned char *pucLcdNextPageFirstPosAtFile;	//下一页对应电子书的内容位置
	struct PageDesc *ptPrePage;
	struct PageDesc *ptNextPage;
} T_PageDesc, *PT_PageDesc;

将当前页的上一页与下一页对应的文件起始位置记录下来,使用尾插法添加到该双向链表中。(这里较难理解)

将每一页的页码即对应电子书的内容位置记录下来构建成结点,通过双向链表连接起来。

六. input_manager部分

支持标准输入及触摸屏。
问题1:通过fgetc从标准输入获取字节,输入完毕需按下Enter将内核缓冲区读出到用户空间,如何实现一按下无需按Enter键则立即读出?

关闭终端canonical模式,并且设置c_cc[VMIN]为1,表示缓冲区有一个字节则立即读出到用户空间缓冲区

	struct termios tTTYState;
 
    //get the terminal state
    tcgetattr(STDIN_FILENO, &tTTYState);
 
    //turn off canonical mode
    tTTYState.c_lflag &= ~ICANON;
    //minimum of number input read.
    tTTYState.c_cc[VMIN] = 1;   /* 有一个数据时就立刻返回 */

    //set the terminal attributes.
    tcsetattr(STDIN_FILENO, TCSANOW, &tTTYState);

反操作:

	struct termios tTTYState;
 
    //get the terminal state
    tcgetattr(STDIN_FILENO, &tTTYState);
 
    //turn on canonical mode
    tTTYState.c_lflag |= ICANON;
	
    //set the terminal attributes.
    tcsetattr(STDIN_FILENO, TCSANOW, &tTTYState);

问题2:输入系统IO模型如何选择:
非阻塞:采用轮询不断读取,大量占用CPU
多线程:主线程阻塞,通过子线程唤醒。
多路复用:主线程阻塞,通过内核唤醒。

若支持非阻塞标准输入的设置: 可利用select的超时设置来实现

struct timeval tTV;
fd_set tFDs;

tTV.tv_sec = 0;
tTV.tv_usec = 0;
FD_ZERO(&tFDs);

FD_SET(STDIN_FILENO, &tFDs);
select(STDIN_FILENO + 1, &tFDs, NULL, NULL, &tv);

if(FD_ISSET(STDIN_FILENO, &tFDs))
{
	..........
}

若支持触摸屏非阻塞访问,会导致按一下产生会多次事件,通过定义按键事件的timeval值,判断当前按下触摸屏距离上次事件发生超过0.5秒则再次触发input事件。

static int isOutOf500ms(struct timeval *ptPreTime, struct timeval *ptNowTime)
{
	int iPreMs;
	int iNowMs;
	
	iPreMs = ptPreTime->tv_sec * 1000 + ptPreTime->tv_usec / 1000;
	iNowMs = ptNowTime->tv_sec * 1000 + ptNowTime->tv_usec / 1000;

	return (iNowMs > iPreMs + 500);
}

若支持多线程访问,通过条件变量与互斥锁实现同步操作:
static pthread_mutex_t g_tMutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t g_tConVar = PTHREAD_COND_INITIALIZER;

static void *InputEventTreadFunction(void *pVoid)
{
	T_InputEvent tInputEvent;
	
	/* 定义函数指针 */
	int (*GetInputEvent)(PT_InputEvent ptInputEvent);
	GetInputEvent = (int (*)(PT_InputEvent))pVoid;

	while (1)
	{
		if(0 == GetInputEvent(&tInputEvent))
		{
			/* 唤醒主线程, 把tInputEvent的值赋给一个全局变量 */
			/* 访问临界资源前,先获得互斥量 */
			pthread_mutex_lock(&g_tMutex);
			g_tInputEvent = tInputEvent;

			/*  唤醒主线程 */
			pthread_cond_signal(&g_tConVar);

			/* 释放互斥量 */
			pthread_mutex_unlock(&g_tMutex);
		}
	}

	return NULL;
}

int AllInputDevicesInit(void)
{
	PT_InputOpr ptTmp = g_ptInputOprHead;
	int iError = -1;

	while (ptTmp)
	{
		if (0 == ptTmp->DeviceInit())
		{
			/* 创建子线程 */
			pthread_create(&ptTmp->tTreadID, NULL, InputEventTreadFunction, ptTmp->GetInputEvent);			
			iError = 0;
		}
		ptTmp = ptTmp->ptNext;
	}
	return iError;
}

int GetInputEvent(PT_InputEvent ptInputEvent)
{
	/* 休眠 */
	pthread_mutex_lock(&g_tMutex);
	pthread_cond_wait(&g_tConVar, &g_tMutex);	

	/* 被唤醒后,返回数据 */
	*ptInputEvent = g_tInputEvent;
	pthread_mutex_unlock(&g_tMutex);
	return 0;	
}
七. debug_manager部分

支持串口打印、网络远程打印:

  1. 打印协议:
#define	APP_EMERG	"<0>"	/* system is unusable			*/
#define	APP_ALERT	"<1>"	/* action must be taken immediately	*/
#define	APP_CRIT	"<2>"	/* critical conditions			*/
#define	APP_ERR	    "<3>"	/* error conditions			*/
#define	APP_WARNING	"<4>"	/* warning conditions			*/
#define	APP_NOTICE	"<5>"	/* normal but significant condition	*/
#define	APP_INFO	"<6>"	/* informational			*/
#define	APP_DEBUG	"<7>"	/* debug-level messages			*/
/*注意:打印级别 0 最高 */
#define DEFAULT_DBGLEVEL  4 //默认打印级别

typedef struct DebugOpr {
	char *name;	//名字
	int isCanUse;	//是否打开
	int (*DebugInit)(void);	//初始化
	int (*DebugExit)(void);	//释放,反操作
	int (*DebugPrint)(char *strData);	//打印方法
	struct DebugOpr *ptNext;	//链表
}T_DebugOpr, *PT_DebugOpr;
  1. 关于可变参及级别限制的打印功能统一接口函数实现:
int DebugPrint(const char *pcFormat, ...)
{
	char strTmpBuf[1000];
	char *pcTmp;
	va_list tArg;
	int iNum;
	PT_DebugOpr ptTmp = g_ptDebugOprHead;
	int dbglevel = DEFAULT_DBGLEVEL;
	
	va_start (tArg, pcFormat);
	iNum = vsprintf (strTmpBuf, pcFormat, tArg);
	va_end (tArg);
	strTmpBuf[iNum] = '\0';

	pcTmp = strTmpBuf;
	
	/* 根据打印级别决定是否打印 */
	if ((strTmpBuf[0] == '<') && (strTmpBuf[2] == '>'))
	{
		dbglevel = strTmpBuf[1] - '0';
		if (dbglevel >= 0 && dbglevel <= 9)
		{
			pcTmp = strTmpBuf + 3;
		}
		else
		{
			dbglevel = DEFAULT_DBGLEVEL;
		}
	}

	if (dbglevel > g_iDbgLevelLimit)
	{
		return -1;
	}
	/* 调用链表中所有isCanUse为1的结构体的DebugPrint函数 */
	while (ptTmp)
	{
		if (ptTmp->isCanUse)
		{
			ptTmp->DebugPrint(pcTmp);
		}
		ptTmp = ptTmp->ptNext;
	}
	return 0;
}

ptTmp->DebugPrint(pcTmp)调用各注册进来的对象DebugPrint方法:
对于串口:直接使用printf函数输出到终端。
对于网络打印:定义个环形缓冲区(通过(Pos + 1) % BUF_SIZE实现),将数据存放在该缓冲区中,文本发送数据量大推荐使用UDP发送,缓冲区大小尽量要大于一次数据包发送量,每次发送均从环形缓冲区取适当的数据量到临时缓冲区进行发送,保证传输效率高且丢包率低,通过sendto发送给客户端。

网络打印代码实现如下:

static int NetDbgPrint(char *strData)
{
	/* 把数据放入环形缓冲区 */
	int i;
	
	for (i = 0; i < strlen(strData); i++)
	{
		if (0 != PutData(strData[i]))
			break;
	}
	//唤醒休眠
	return i;
}

static void *NetDbgSendTreadFunction(void *pVoid)
{
	char strTmpBuf[512];
	char cVal;
	int i;
	int iAddrLen;
	int iSendLen;
	
	while (1)
	{
		/* 平时休眠 */

		while (g_iHaveConnected && !isEmpty())
		{
			i = 0;

			/* 把环形缓冲区的数据取出来, 最多取512字节 */
			while ((i < 512) && (0 == GetData(&cVal)))
			{
				strTmpBuf[i] = cVal;
				i++;
			}
			
			/* 执行到这里, 表示被唤醒 */
			/* 用sendto函数发送打印信息给客户端 */
			iAddrLen = sizeof(struct sockaddr);
			iSendLen = sendto(g_iSocketServer, strTmpBuf, i, 0,
			                      (const struct sockaddr *)&g_tSocketClientAddr, iAddrLen);

		}

	}
	return NULL;
}

总体思路

上层首先通过manager层初始化已注册链表上的设备,并将链表进行维护,即包含该框架底层支持的功能。
通过传入的电子书文件,判断其编码格式,从而通过Encoding中所维护的链表获取匹配的解码方式。
通过传入的字体类型来判断要使用的Fonts字体类型,从Fonts链表中获取对应的字体相关信息。
将所有对应信息赋值给draw.c中的关键变量,根据变量信息获取字体点阵、解码方式,根据传入的指令显示页面或退出。

Android电子书阅读器开发是一个非常有趣和挑战性的项目。在开发过程中,有几个关键点需要注意。 首先,需要选择合适的阅读器引擎。市面上有很多优秀的开源引擎可供选择,如MuPDF、FBReader等。这些引擎提供了文档解析、渲染、翻页等基础功能,能够大大减少开发工作量。 其次,界面设计也是重要的一环。用户界面应该简洁易用,同时提供一些常用的功能,如调整字体大小、屏幕亮度、书签等。还可以考虑添加一些个性化的功能,如夜间模式、自动跳转到上次阅读位置等,以提升用户体验。 另外,要考虑电子书的格式支持。常见的电子书格式有EPUB、PDF等,需要根据实际需求选择合适的格式进行解析和显示。在解析过程中,要注意处理异常情况,如特殊字符、布局问题等。 还需要考虑电子书的存储和管理。用户可以通过电子书阅读器导入、导出、删除等操作来管理自己的电子书库。可以使用SQLite数据库来存储电子书信息,如书名、作者、封面等,同时还要保证对电子书的读写操作高效可靠。 最后,要注意性能优化。电子书阅读器必须能够快速加载和显示大量文字和图片,同时还要保持较低的内存占用和电池消耗。可以使用一些性能优化的技巧,如缓存、异步加载、内存管理等来提升阅读器的性能。 总体来说,开发一个Android电子书阅读器需要充分考虑用户界面设计、阅读引擎选择、电子书格式支持、电子书存储管理以及性能优化等方面。只有综合考虑这些因素,才能开发出高质量、易用性强的电子书阅读器应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值