zoukankan      html  css  js  c++  java
  • uCGUI字符串显示过程分析和uCGUI字库的组建

    为什么要分析字符串的显示过程?

      学习uCGUI主要是学习如何使用的,为何要深究到源码的层次呢?

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

    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 == '
    ') || (*s == '
    ')) {
    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 == '
    ')
    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 }

     字符串显示过程概括                                                

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

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

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

    <4> 如果不是只有一行,重新计算下一行显示的坐标,重复<1、2、3>的工作,直到将字符串显示完毕。

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

                       重要细节分析

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

     1、GUI运行的全局变量                                             

      GUI_Context是GUI保存运行环境的全局变量,它的类型GUI_CONTEXT在GUI.h中被定义。

    struct GUI_CONTEXT {
    /* Variables in LCD module */
      LCD_COLORINDEX_UNION LCD;
      LCD_RECT       ClipRect;
      U8             DrawMode;
      U8             SelLayer;
      U8             TextStyle;
    /* Variables in GL module */
      GUI_RECT* pClipRect_HL;                /* High level clip rectangle ... Speed optimization so drawing routines can optimize */
      U8        PenSize;
      U8        PenShape;
      U8        LineStyle;
      U8        FillStyle;
    /* Variables in GUICHAR module */
      const GUI_FONT           GUI_UNI_PTR * pAFont;  //指向当前选择的字体
      #if GUI_SUPPORT_UNICODE
        const GUI_UC_ENC_APILIST * pUC_API;    /* Unicode encoding API */
      #endif
      I16P LBorder;
      I16P DispPosX, DispPosY;
      I16P DrawPosX, DrawPosY;
      I16P TextMode, TextAlign;                       //对齐方式
      GUI_COLOR Color, BkColor;           /* Required only when changing devices and for speed opt (caching) */
    /* Variables in WM module */
      #if GUI_WINSUPPORT
        const GUI_RECT* WM__pUserClipRect;
        GUI_HWIN hAWin;                                   //当前激活的窗口 
        int xOff, yOff;
      #endif
    /* Variables in MEMDEV module (with memory devices only) */
      #if GUI_SUPPORT_DEVICES
        const tLCDDEV_APIList* pDeviceAPI;  /* function pointers only */
        GUI_HMEM    hDevData;
        GUI_RECT    ClipRectPrev;
      #endif
    /* Variables in Anitaliasing module */
      #if GUI_SUPPORT_AA
        const tLCD_HL_APIList* pLCD_HL;     /* Required to reroute drawing (HLine & Pixel) to the AA module */
        U8 AA_Factor;
        U8 AA_HiResEnable;
      #endif
    };

    2、GUI_FONT的定义                                                 

         一种字库想要被uCGUI所调用,需要将其定义成GUI_GONT类型的一个常量,当我们需要自己制作字库的时候,就需要这样做。GUI_FONT类型的定义在GUIType.h文件中。

    struct GUI_FONT {
      GUI_DISPCHAR*     pfDispChar;             //显示一个属于当前字库字符的函数
      GUI_GETCHARDISTX* pfGetCharDistX;         //获取字库中某字符的宽度
      GUI_GETFONTINFO*  pfGetFontInfo;          //获取字库信息
      GUI_ISINFONT*     pfIsInFont;             //查询字库中是否存在此字符
      const tGUI_ENC_APIList* pafEncode;        //
      U8 YSize;                        //高度
      U8 YDist;                        //对应的像素点
      U8 XMag;                         //X方向上的放大系数
      U8 YMag;                         //Y方向上的放大系数
      union {                          //此共用体主要是提供字库数据的访问地址,
                                       //对于不同的字库,其从字库中查找字模的方法,是不一样的
                                       //这里主要分成三种情况,对应三种类型的指针        
        const void          GUI_UNI_PTR * pFontData;
        const GUI_FONT_MONO GUI_UNI_PTR * pMono;
        const GUI_FONT_PROP GUI_UNI_PTR * pProp;
      } p;
      U8 Baseline;                                //
      U8 LHeight;     /* height of a small lower case character (a,x) */    //小写高度
      U8 CHeight;     /* height of a small upper case character (A,X) */    //大写高度
    };

    (1)单一分区的字库,例如GUI_Font6x8,使用的共用体指针是第二个

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

    这类字库的特点:

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

    ② 之所以是一个分区,那是因为所有的字符的索引码都是连续的,所以根据字符索引寻找字符的字模时,从字库的基地址开始找起就可以了。

    (2)多个分区的字库,比如汉字的字库,使用的共用体指针是第三个

    typedef struct GUI_FONT_PROP {
      U16P First;                                /* first character               */
      U16P Last;                                 /* last character                */
      const GUI_CHARINFO GUI_UNI_PTR * paCharInfo;           /* address of first character    */
      const struct GUI_FONT_PROP GUI_UNI_PTR * pNext;        /* pointer to next */
    } GUI_FONT_PROP;

        相较(1)结构体中的内容,GUI_FONT_PROP显得少了几个。由于字库是分区管理,每一个分区有自己特有的参数,通过paCharInfo指针来指向这个分区的特点,也会给出这个分区的第一个字符字模的地址。 

    typedef struct {
      U8 XSize;                                  //字符的宽度
      U8 XDist;                                  //显示所对应的像素点
      U8 BytesPerLine;                           //每行占据的字节数
      const unsigned char GUI_UNI_PTR * pData;   //指向字模的数据区
    } GUI_CHARINFO;

      可能有人会说:不使用GUI,将汉字字库下载到SD卡中,然后使用下边的函数GetGBKCode_from_sd()也可以获得字模,而且这种字库所有的分区都是连续排放的。为什么要与上一种情况单独分开呢?

    /*******************************************************************************
    * Function Name  : GetGBKCode_from_sd
    * Description    : 从SD卡字库中读取自摸数据到指定的缓冲区 
    * Input          : pBuffer---数据保存地址  
    *                                       c--汉字字符低字节码 
    * Output         : None
    * Return         : 0(success)  -1(fail)
    * Attention              : None
    *******************************************************************************/ 
    int GetGBKCode_from_sd(unsigned char* pBuffer,const unsigned char * c)
    { 
        unsigned char High8bit,Low8bit;
        unsigned int pos;
        High8bit=*c;     /* 取高8位数据 */
        Low8bit=*(c+1);  /* 取低8位数据 */
        
        pos = ((High8bit-0xa0-16)*94+Low8bit-0xa0-1)*2*16;
        
        f_mount(0, &myfs[0]);
        myres = f_open(&myfsrc , "0:/HZLIB.bin", FA_OPEN_EXISTING | FA_READ);
        
        if ( myres == FR_OK ) 
        {
            f_lseek (&myfsrc, pos);                               //指针偏移
            myres = f_read( &myfsrc, pBuffer, 32, &mybr );         //16*16大小的汉字 其字模 占用16*2个字节
            f_close(&myfsrc);
            
            return 0;  
        }    
        else
            return -1;    
    }

        虽然字库是连续存放的,但是根据汉字机内码从这样的字库中找到对应的字模存放的位置,这样的算法是不通用的。不通用的意思是,uCGUI支持多种外语,可它不可能为每一种外语都写这样的算法,那样的代码不具包容性。
       正确的做法是考虑到多种外语的共同点,字符索引都可以看做是分区排列的,而且在每个分区中都是连续的。每个分区中查找字模的算法肯定是一样的,而在创建字库的时候,将字模分区管理。

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

        为了减少ROM的消耗,裁剪字库是最为有效的方法。裁剪字库的基础就是将字库进行分区管理,一个分区甚至可以只有一个汉字。将字库进行了分割,但是所有的分区有都属于同一个字库,这就需要链表将它们连接起来。于是乎,就产生了第三种共用体指针。

    3、属于字库的特有的函数                                           

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

        不同的字库不仅有属于自己的数据库,而且还有自己特有的函数,这也是由于其特别的因素所决定的。

    4、构建自己需要的字库时需要的工作                                  

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

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

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

        还需要为字库创建诸多查询功能的管理函数,例如16*16点阵的汉字字库可以选用

    #define GUI_FONTTYPE_PROP_SJIS  
      GUIPROP_DispChar,             
        GUIPROP_GetCharDistX,         
        GUIPROP_GetFontInfo,          
        GUIPROP_IsInFont,             
      &GUI_ENC_APIList_SJIS

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

        对于只有一个分区的MONO英文字库,是不需要用链表的,只需要在数组中根据偏移量查找就行了。

  • 相关阅读:
    2021,6,10 xjzx 模拟考试
    平衡树(二)——Treap
    AtCoder Beginner Contest 204 A-E简要题解
    POJ 2311 Cutting Game 题解
    Codeforces 990G GCD Counting 题解
    NOI2021 SDPTT D2T1 我已经完全理解了 DFS 序线段树 题解
    第三届山东省青少年创意编程与智能设计大赛总结
    Luogu P6042 「ACOI2020」学园祭 题解
    联合省选2021 游记
    Codeforces 1498E Two Houses 题解 —— 如何用结论吊打标算
  • 原文地址:https://www.cnblogs.com/amanlikethis/p/4038569.html
Copyright © 2011-2022 走看看