[应用相关] uCGUI字符串显示过程分析和uCGUI字库的组建

[复制链接]
 楼主| decoding 发表于 2019-8-4 17:51 | 显示全部楼层 |阅读模式
为什么要分析字符串的显示过程?
学习ucgui主要是学习如何使用的,为何要深究到源码的层次呢?

就分析字符串显示过程的原因来说,是因为移植汉字字库的需要。ucgui并么有合适的汉字字库,而且完整的汉字字库非常庞大,消耗单片机的flash资源。如果想要移植一个合适的字库,分析字符串显示的过程以及ucgui字库数据结构,还是很有必要的。

 楼主| decoding 发表于 2019-8-4 17:52 | 显示全部楼层
GUI_DispString()函数源码

  1. void GUI_DispString(const char GUI_UNI_PTR *s) {
  2.   int xAdjust, yAdjust, xOrg;
  3.   int FontSizeY;
  4.   if (!s)
  5.     return;
  6.   GUI_LOCK();
  7.   FontSizeY = GUI_GetFontDistY();         //获取字体的高度
  8.   xOrg = GUI_Context.DispPosX;            //获取当前显示的x坐标
  9. /* Adjust vertical position */
  10.   yAdjust = GUI_GetYAdjust();                    
  11.   GUI_Context.DispPosY -= yAdjust;        //根据Y方向上的对齐方式对y进行调整
  12.   for (; *s; s++) {
  13.     GUI_RECT r;
  14.     int LineNumChars = GUI__GetLineNumChars(s, 0x7fff);           //当前一行要显示几个字符
  15.     int xLineSize    = GUI__GetLineDistX(s, LineNumChars);        //当前一行在x方向上的像素数
  16.   /* Check if x-position needs to be changed due to h-alignment */
  17.     switch (GUI_Context.TextAlign & GUI_TA_HORIZONTAL) {
  18.       case GUI_TA_CENTER: xAdjust = xLineSize / 2; break;
  19.       case GUI_TA_RIGHT:  xAdjust = xLineSize; break;
  20.       default:            xAdjust = 0;
  21.     }
  22.     /* 计算出每一行显示内容的矩形区域 */
  23.     r.x0 = GUI_Context.DispPosX -= xAdjust;          //根据水平方向的对齐方式对x坐标进行调整
  24.     r.x1 = r.x0 + xLineSize - 1;   
  25.     r.y0 = GUI_Context.DispPosY;
  26.     r.y1 = r.y0 + FontSizeY - 1;
  27.         
  28.     GUI__DispLine(s, LineNumChars, &r);              //以计算好的矩形区域显示当前行字符
  29.     GUI_Context.DispPosY = r.y0;
  30.     s += GUI_UC__NumChars2NumBytes(s, LineNumChars); //从第一个字符开始,地址加1,可以遍历整行字符串
  31.     if ((*s == '\n') || (*s == '\r')) {
  32.       switch (GUI_Context.TextAlign & GUI_TA_HORIZONTAL) {
  33.       case GUI_TA_CENTER:
  34.       case GUI_TA_RIGHT:
  35.         GUI_Context.DispPosX = xOrg;
  36.         break;
  37.       default:
  38.         GUI_Context.DispPosX = GUI_Context.LBorder;
  39.         break;
  40.       }
  41.       if (*s == '\n')
  42.         GUI_Context.DispPosY += FontSizeY;
  43.     } else {
  44.       GUI_Context.DispPosX = r.x0 + xLineSize;
  45.     }
  46.     if (*s == 0)    /* end of string (last line) reached ? */
  47.       break;
  48.   }
  49.   GUI_Context.DispPosY += yAdjust;                //
  50.   GUI_Context.TextAlign &= ~GUI_TA_HORIZONTAL;    //
  51.   GUI_UNLOCK();
  52. }
 楼主| decoding 发表于 2019-8-4 17:52 | 显示全部楼层
字符串显示过程概括                                                

<1> 获取选择字体的高度、宽度

<2> 从所传字符串参数中,依次读出一行的字符数,最终得到一行显示所对应的矩形区域

<3> 将一行所得到的详细信息传给行显示函数,行显示函数会从字库中找到匹配的字依次将一行的字符进行显示,完成一行的显示

<4> 如果不是只有一行,重新计算下一行显示的坐标,重复<1、2、3>的工作,直到将字符串显示完毕。
 楼主| decoding 发表于 2019-8-4 17:53 | 显示全部楼层
============================================================================================

                   重要细节分析

============================================================================================

1、GUI运行的全局变量                                             

  GUI_Context是GUI保存运行环境的全局变量,它的类型GUI_CONTEXT在GUI.h中被定义。
  1. struct GUI_CONTEXT {
  2. /* Variables in LCD module */
  3.   LCD_COLORINDEX_UNION LCD;
  4.   LCD_RECT       ClipRect;
  5.   U8             DrawMode;
  6.   U8             SelLayer;
  7.   U8             TextStyle;
  8. /* Variables in GL module */
  9.   GUI_RECT* pClipRect_HL;                /* High level clip rectangle ... Speed optimization so drawing routines can optimize */
  10.   U8        PenSize;
  11.   U8        PenShape;
  12.   U8        LineStyle;
  13.   U8        FillStyle;
  14. /* Variables in GUICHAR module */
  15.   const GUI_FONT           GUI_UNI_PTR * pAFont;  //指向当前选择的字体
  16.   #if GUI_SUPPORT_UNICODE
  17.     const GUI_UC_ENC_APILIST * pUC_API;    /* Unicode encoding API */
  18.   #endif
  19.   I16P LBorder;
  20.   I16P DispPosX, DispPosY;
  21.   I16P DrawPosX, DrawPosY;
  22.   I16P TextMode, TextAlign;                       //对齐方式
  23.   GUI_COLOR Color, BkColor;           /* Required only when changing devices and for speed opt (caching) */
  24. /* Variables in WM module */
  25.   #if GUI_WINSUPPORT
  26.     const GUI_RECT* WM__pUserClipRect;
  27.     GUI_HWIN hAWin;                                   //当前激活的窗口 
  28.     int xOff, yOff;
  29.   #endif
  30. /* Variables in MEMDEV module (with memory devices only) */
  31.   #if GUI_SUPPORT_DEVICES
  32.     const tLCDDEV_APIList* pDeviceAPI;  /* function pointers only */
  33.     GUI_HMEM    hDevData;
  34.     GUI_RECT    ClipRectPrev;
  35.   #endif
  36. /* Variables in Anitaliasing module */
  37.   #if GUI_SUPPORT_AA
  38.     const tLCD_HL_APIList* pLCD_HL;     /* Required to reroute drawing (HLine & Pixel) to the AA module */
  39.     U8 AA_Factor;
  40.     U8 AA_HiResEnable;
  41.   #endif
  42. };

 楼主| decoding 发表于 2019-8-4 17:53 | 显示全部楼层
2、GUI_FONT的定义                                                

     一种字库想要被uCGUI所调用,需要将其定义成GUI_GONT类型的一个常量,当我们需要自己制作字库的时候,就需要这样做。GUI_FONT类型的定义在GUIType.h文件中。
  1. struct GUI_FONT {
  2.   GUI_DISPCHAR*     pfDispChar;             //显示一个属于当前字库字符的函数
  3.   GUI_GETCHARDISTX* pfGetCharDistX;         //获取字库中某字符的宽度
  4.   GUI_GETFONTINFO*  pfGetFontInfo;          //获取字库信息
  5.   GUI_ISINFONT*     pfIsInFont;             //查询字库中是否存在此字符
  6.   const tGUI_ENC_APIList* pafEncode;        //
  7.   U8 YSize;                        //高度
  8.   U8 YDist;                        //对应的像素点
  9.   U8 XMag;                         //X方向上的放大系数
  10.   U8 YMag;                         //Y方向上的放大系数
  11.   union {                          //此共用体主要是提供字库数据的访问地址,
  12.                                    //对于不同的字库,其从字库中查找字模的方法,是不一样的
  13.                                    //这里主要分成三种情况,对应三种类型的指针        
  14.     const void          GUI_UNI_PTR * pFontData;
  15.     const GUI_FONT_MONO GUI_UNI_PTR * pMono;
  16.     const GUI_FONT_PROP GUI_UNI_PTR * pProp;
  17.   } p;
  18.   U8 Baseline;                                //
  19.   U8 LHeight;     /* height of a small lower case character (a,x) */    //小写高度
  20.   U8 CHeight;     /* height of a small upper case character (A,X) */    //大写高度
  21. };

 楼主| decoding 发表于 2019-8-4 17:54 | 显示全部楼层
(1)单一分区的字库,例如GUI_Font6x8,使用的共用体指针是第二个

  1. /* MONO字库代表的是单一分区 */
  2. typedef struct {
  3.   const unsigned char GUI_UNI_PTR * pData;        //字库的起始地址
  4.   const U8 GUI_UNI_PTR * pTransData;              //
  5.   const GUI_FONT_TRANSINFO GUI_UNI_PTR * pTrans;  //
  6.   U16P FirstChar;                                 //第一个字符的索引
  7.   U16P LastChar;                                  //最后一个字符的索引
  8.   U8 XSize;                                       //宽度
  9.   U8 XDist;                                       //宽度对应的像素点
  10.   U8 BytesPerLine;                                //每行需要几个字节
  11. } GUI_FONT_MONO;


这类字库的特点:

① 字符不多,对应的字模也很少,不会很浪费Flash

② 之所以是一个分区,那是因为所有的字符的索引码都是连续的,所以根据字符索引寻找字符的字模时,从字库的基地址开始找起就可以了。
 楼主| decoding 发表于 2019-8-4 17:54 | 显示全部楼层
(2)多个分区的字库,比如汉字的字库,使用的共用体指针是第三个
  1. typedef struct GUI_FONT_PROP {
  2.   U16P First;                                /* first character               */
  3.   U16P Last;                                 /* last character                */
  4.   const GUI_CHARINFO GUI_UNI_PTR * paCharInfo;           /* address of first character    */
  5.   const struct GUI_FONT_PROP GUI_UNI_PTR * pNext;        /* pointer to next */
  6. } GUI_FONT_PROP;


相较(1)结构体中的内容,GUI_FONT_PROP显得少了几个。由于字库是分区管理,每一个分区有自己特有的参数,通过paCharInfo指针来指向这个分区的特点,也会给出这个分区的第一个字符字模的地址。
  1. typedef struct {
  2.   U8 XSize;                                  //字符的宽度
  3.   U8 XDist;                                  //显示所对应的像素点
  4.   U8 BytesPerLine;                           //每行占据的字节数
  5.   const unsigned char GUI_UNI_PTR * pData;   //指向字模的数据区
  6. } GUI_CHARINFO;

 楼主| decoding 发表于 2019-8-4 17:55 | 显示全部楼层
可能有人会说:不使用GUI,将汉字字库下载到SD卡中,然后使用下边的函数GetGBKCode_from_sd()也可以获得字模,而且这种字库所有的分区都是连续排放的。为什么要与上一种情况单独分开呢?

  1. /*******************************************************************************
  2. * Function Name  : GetGBKCode_from_sd
  3. * Description    : 从SD卡字库中读取自摸数据到指定的缓冲区
  4. * Input          : pBuffer---数据保存地址  
  5. *                                       c--汉字字符低字节码
  6. * Output         : None
  7. * Return         : 0(success)  -1(fail)
  8. * Attention              : None
  9. *******************************************************************************/
  10. int GetGBKCode_from_sd(unsigned char* pBuffer,const unsigned char * c)
  11. {
  12.     unsigned char High8bit,Low8bit;
  13.     unsigned int pos;
  14.     High8bit=*c;     /* 取高8位数据 */
  15.     Low8bit=*(c+1);  /* 取低8位数据 */
  16.    
  17.     pos = ((High8bit-0xa0-16)*94+Low8bit-0xa0-1)*2*16;
  18.    
  19.     f_mount(0, &myfs[0]);
  20.     myres = f_open(&myfsrc , "0:/HZLIB.bin", FA_OPEN_EXISTING | FA_READ);
  21.    
  22.     if ( myres == FR_OK )
  23.     {
  24.         f_lseek (&myfsrc, pos);                               //指针偏移
  25.         myres = f_read( &myfsrc, pBuffer, 32, &mybr );         //16*16大小的汉字 其字模 占用16*2个字节
  26.         f_close(&myfsrc);
  27.         
  28.         return 0;  
  29.     }   
  30.     else
  31.         return -1;   
  32. }
 楼主| decoding 发表于 2019-8-4 17:56 | 显示全部楼层
虽然字库是连续存放的,但是根据汉字机内码从这样的字库中找到对应的字模存放的位置,这样的算法是不通用的。不通用的意思是,uCGUI支持多种外语,可它不可能为每一种外语都写这样的算法,那样的代码不具包容性。
   正确的做法是考虑到多种外语的共同点,字符索引都可以看做是分区排列的,而且在每个分区中都是连续的。每个分区中查找字模的算法肯定是一样的,而在创建字库的时候,将字模分区管理。

    这种做法最为重要的意义在于:可以裁剪字库,自然节省了Flash的消耗。16*16点阵的汉字字库,它的大小是200多kB,对于小容量Flash的单片机来说难以承受。有人说可以外置SD卡,可是实际的项目中怎么可能将字库放在SD卡中,怎么可能就为了字库而添加SD卡这样的硬件资源,而且SD卡的读速度跟内部Flash是不可比的。

    为了减少ROM的消耗,裁剪字库是最为有效的方法。裁剪字库的基础就是将字库进行分区管理,一个分区甚至可以只有一个汉字。将字库进行了分割,但是所有的分区有都属于同一个字库,这就需要链表将它们连接起来。于是乎,就产生了第三种共用体指针。
 楼主| decoding 发表于 2019-8-4 17:57 | 显示全部楼层
3、属于字库的特有的函数

  1. struct GUI_FONT {
  2.   GUI_DISPCHAR*     pfDispChar;             //显示一个属于当前字库字符的函数
  3.   GUI_GETCHARDISTX* pfGetCharDistX;         //获取字库中某字符的宽度
  4.   GUI_GETFONTINFO*  pfGetFontInfo;          //获取字库信息
  5.   GUI_ISINFONT*     pfIsInFont;             //查询字库中是否存在此字符
  6. ......
  7. };


不同的字库不仅有属于自己的数据库,而且还有自己特有的函数,这也是由于其特别的因素所决定的。
 楼主| decoding 发表于 2019-8-4 17:58 | 显示全部楼层
4、构建自己需要的字库时需要的工作

     英文ASCII的字库,uCGUI提供的已经非常完备了,通常无需多虑。而uCGUI所欠缺的是属于我们中国的汉字字库。

    庞大的字库使我们只有选择“const GUI_FONT_PROP GUI_UNI_PTR * pProp;”这种办法才是光明之道。

    我们需要将自己需要显示的汉字字模分区建立GUI_FONT_PROP类型的结构体,然后使用指针pProp将它们连接成单项链表构成一个完整的字库。这样字库的数据区就做好了。

    还需要为字库创建诸多查询功能的管理函数,例如16*16点阵的汉字字库可以选用
  1. #define GUI_FONTTYPE_PROP_SJIS  \
  2.   GUIPROP_DispChar,             \
  3.     GUIPROP_GetCharDistX,         \
  4.     GUIPROP_GetFontInfo,          \
  5.     GUIPROP_IsInFont,             \
  6.   &GUI_ENC_APIList_SJIS


    显然上边的这些函数都是基于链表的,有什么样的数据结构就有什么样的算法的一种体现。

    对于只有一个分区的MONO英文字库,是不需要用链表的,只需要在数组中根据偏移量查找就行了。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

30

主题

469

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部