u8g2原理解析
u8g2_font_info_t 结构体用于存储 U8g2 图形库中字体的相关信息.
1. 字符计数与编码模式相关信息(偏移量 0)
uint8_t glyph_cnt;
uint8_t bbx_mode;
uint8_t bits_per_0;
uint8_t bits_per_1;
glyph_cnt:
含义:表示字体中可用字符(字形,glyph)的数量。这有助于确定字体所能支持的字符范围。
用途:在遍历字体中的字符时,可根据此值来控制循环范围。
bbx_mode:
含义:边界框(bounding box)模式。边界框用于定义字符的最小矩形区域,此模式决定了如何计算和使用这些边界框。
用途:在进行字符布局和对齐时,根据不同的模式来确定字符的位置和大小。
bits_per_0 和 bits_per_1:
含义:分别表示编码中 “0” 和 “1” 所占用的位数。在字体的点阵数据编码中,不同的编码方式可能对 “0” 和 “1” 的表示位数有不同要求。
用途:在解码字体点阵数据时,根据这些位数信息来正确解析数据。
2. 字符尺寸编码位数信息(偏移量 4)
uint8_t bits_per_char_width;
uint8_t bits_per_char_height;
uint8_t bits_per_char_x;
uint8_t bits_per_char_y;
uint8_t bits_per_delta_x;
bits_per_char_width:
含义:表示字符宽度编码所占用的位数。
用途:在读取字体数据中字符宽度信息时,根据此位数进行解析。
bits_per_char_height:
含义:表示字符高度编码所占用的位数。
用途:同理,用于解析字符高度信息。
bits_per_char_x 和 bits_per_char_y:
含义:分别表示字符在 X 轴和 Y 轴上位置编码所占用的位数。
用途:用于确定字符在字体中的具体位置。
bits_per_delta_x:
含义:表示字符在 X 轴上的偏移量编码所占用的位数。
用途:在进行字符布局时,根据此偏移量来调整字符之间的间距。
3. 字符最大尺寸与偏移信息(偏移量 9)
int8_t max_char_width;
int8_t max_char_height;
int8_t x_offset;
int8_t y_offset;
max_char_width:
含义:字体中字符的最大宽度。
用途:在进行文本布局时,可根据此值来估算一行文本所需的最大宽度。
max_char_height:
含义:字体中字符的最大高度,注意这里是整体高度,而不是上升高度(ascent)。上升高度等于 max_char_height + y_offset。
用途:用于确定文本行的高度。
x_offset 和 y_offset:
含义:分别表示字符在 X 轴和 Y 轴上的偏移量。
用途:在绘制字符时,根据这些偏移量来调整字符的位置。
4. 字符上升和下降高度信息(偏移量 13)
int8_t ascent_A;
int8_t descent_g;
int8_t ascent_para;
int8_t descent_para;
ascent_A:
含义:大写字母 “A” 的上升高度,即字符从基线(baseline)到最高点的距离。
用途:在文本布局中,用于确定文本行的顶部位置。
descent_g:
含义:小写字母 “g” 的下降高度,通常为负值,表示字符从基线到最低点的距离。
用途:用于确定文本行的底部位置。
ascent_para 和 descent_para:
含义:分别表示段落的上升高度和下降高度,可能用于处理多行文本的布局。
用途:在进行段落排版时,根据这些值来调整行间距和段落的整体布局。
5. 字符起始位置信息(偏移量 17)
uint16_t start_pos_upper_A;
uint16_t start_pos_lower_a;
start_pos_upper_A:
含义:大写字母 “A” 在字体数据中的起始位置。
用途:在查找大写字母 “A” 的点阵数据时,根据此位置进行定位。
start_pos_lower_a:
含义:小写字母 “a” 在字体数据中的起始位置。
用途:同理,用于查找小写字母 “a” 的点阵数据。
6. Unicode 字符起始位置信息(偏移量 21,可选)
#ifdef U8G2_WITH_UNICODE
uint16_t start_pos_unicode;
#endif
start_pos_unicode:
含义:如果启用了 Unicode 支持(U8G2_WITH_UNICODE 宏定义),此成员表示 Unicode 字符在字体数据中的起始位置。
用途:在处理 Unicode 字符时,根据此位置来查找相应的点阵数据。
综上所述,u8g2_font_info_t 结构体通过存储字体的各种信息,为 U8g2 库在文本渲染和布局过程中提供了必要的参数,确保能够正确地显示和排版文本。
u8g2_draw_string
函数定义和参数
static u8g2_uint_t u8g2_draw_string(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, const char *str)
static:表示该函数是一个静态函数,其作用域仅限于当前文件。
返回值:u8g2_uint_t 类型,返回绘制整个字符串所占用的水平或垂直总长度(根据字体旋转方向而定)。
参数:
u8g2_t *u8g2:指向 u8g2_t 结构体的指针,该结构体包含了 U8g2 库的上下文信息,如显示屏设置、字体信息等。
u8g2_uint_t x 和 u8g2_uint_t y:指定字符串绘制的起始坐标。
const char *str:指向要绘制的字符串的指针。
变量声明
uint16_t e;
u8g2_uint_t delta, sum;
e:用于存储从字符串中解码出的 Unicode 字符编码。
delta:表示绘制单个字符所占用的水平或垂直长度。
sum:用于累加绘制整个字符串所占用的总长度。
UTF - 8 初始化
u8x8_utf8_init(u8g2_GetU8x8(u8g2));
调用 u8x8_utf8_init 函数对 UTF - 8 解码进行初始化,确保后续能正确处理 UTF - 8 编码的字符串。u8g2_GetU8x8(u8g2) 用于获取 u8g2 结构体中关联的 u8x8 上下文。
初始化总长度
sum = 0;
将 sum 初始化为 0,用于后续累加字符绘制的长度。
字符串处理循环
for(;;)
{
e = u8g2->u8x8.next_cb(u8g2_GetU8x8(u8g2), (uint8_t)*str);
if ( e == 0x0ffff )
break;
str++;
u8g2->u8x8.next_cb 是一个回调函数,用于从字符串中解码出下一个 Unicode 字符编码。
如果解码结果为 0x0ffff,表示已经到达字符串的末尾,此时跳出循环。
每次解码后,将字符串指针 str 向后移动一位。
绘制字符
if ( e != 0x0fffe )
{
delta = u8g2_DrawGlyph(u8g2, x, y, e);
如果解码结果不是 0x0fffe(表示无效字符),则调用 u8g2_DrawGlyph 函数在指定位置 (x, y) 绘制该 Unicode 字符。该函数返回绘制该字符所占用的水平或垂直长度,存储在 delta 中。
处理字体旋转
#ifdef U8G2_WITH_FONT_ROTATION
switch(u8g2->font_decode.dir)
{
case 0:
x += delta;
break;
case 1:
y += delta;
break;
case 2:
x -= delta;
break;
case 3:
y -= delta;
break;
}
/*
// requires 10 bytes more on avr
x = u8g2_add_vector_x(x, delta, 0, u8g2->font_decode.dir);
y = u8g2_add_vector_y(y, delta, 0, u8g2->font_decode.dir);
*/
#else
x += delta;
#endif
启用字体旋转支持(U8G2_WITH_FONT_ROTATION 宏定义):
根据 u8g2->font_decode.dir 的值(表示字体旋转方向),更新下一个字符的绘制位置。
case 0:正常水平方向,x 坐标增加 delta。
case 1:顺时针旋转 90 度,y 坐标增加 delta。
case 2:顺时针旋转 180 度,x 坐标减少 delta。
case 3:顺时针旋转 270 度,y 坐标减少 delta。
注释掉的代码 u8g2_add_vector_x 和 u8g2_add_vector_y 是另一种实现方式,但会增加 AVR 平台的代码大小。
未启用字体旋转支持:默认情况下,水平方向绘制,x 坐标增加 delta。
累加总长度
sum += delta;
将当前字符绘制所占用的长度 delta 累加到 sum 中。
返回总长度
return sum;
循环结束后,返回绘制整个字符串所占用的总长度。
UFT8解码函数(将中文变成UFT8解码)
uint16_t u8x8_utf8_next(u8x8_t *u8x8, uint8_t b)
{
if ( b == 0 || b == '\n' ) /* '\n' terminates the string to support the string list procedures */
return 0x0ffff; /* end of string detected, pending UTF8 is discarded */
if ( u8x8->utf8_state == 0 )
{
if ( b >= 0xfc ) /* 6 byte sequence */
{
u8x8->utf8_state = 5;
b &= 1;
}
else if ( b >= 0xf8 )
{
u8x8->utf8_state = 4;
b &= 3;
}
else if ( b >= 0xf0 )
{
u8x8->utf8_state = 3;
b &= 7;
}
else if ( b >= 0xe0 )
{
u8x8->utf8_state = 2;
b &= 15;
}
else if ( b >= 0xc0 )
{
u8x8->utf8_state = 1;
b &= 0x01f;
}
else
{
/* do nothing, just use the value as encoding */
return b;
}
u8x8->encoding = b;
return 0x0fffe;
}
else
{
u8x8->utf8_state--;
/* The case b < 0x080 (an illegal UTF8 encoding) is not checked here. */
u8x8->encoding<<=6;
b &= 0x03f;
u8x8->encoding |= b;
if ( u8x8->utf8_state != 0 )
return 0x0fffe; /* nothing to do yet */
}
return u8x8->encoding;
}
一个例子**“世界” 的 Unicode 编码及解析过程**
- Unicode 编码结果
世:U+4E16
界:U+754C - UTF-8 编码解析过程
根据 UTF-8 编码规则,将 Unicode 代码点转换为字节序列的步骤如下:
“世”(U+4E16)的编码
代码点转换为二进制
U+4E16 → 二进制 0100 1110 0001 0110。
按三字节格式拆分
UTF-8 三字节格式为:
plaintext
1110xxxx 10xxxxyy 10yyyyyy
将二进制位分配到各字段:
第一部分(4 位):0100
第二部分(6 位):111000
第三部分(6 位):010110
生成字节序列
第一字节:1110 0100 → 0xE4
第二字节:10 111000 → 0xB8
第三字节:10 010110 → 0x96
最终 UTF-8 编码:E4 B8 96。
“界”(U+754C)的编码
代码点转换为二进制
U+754C → 二进制 0111 0101 0100 1100。
按三字节格式拆分
第一部分(4 位):0111
第二部分(6 位):010101
第三部分(6 位):001100
生成字节序列
第一字节:1110 0111 → 0xE7
第二字节:10 010101 → 0x95
第三字节:10 001100 → 0x8C
最终 UTF-8 编码:E7 95 8C。
3. 结合u8x8_utf8_next函数的处理逻辑
当函数依次处理 “世界” 的 UTF-8 字节E4 B8 96 E7 95 8C时:
处理E4(0xE4)
b = 0xE4,判断为三字节序列(0xE0 ≤ b < 0xF0)。
utf8_state设为 2,encoding保存0x16(E4的低 4 位),返回0x0fffe。
处理B8(0xB8)
utf8_state减为 1,encoding左移 6 位,合并B8的低 6 位(0x18)。
encoding = 0x16 << 6 | 0x18 = 0x1618,返回0x0fffe。
处理96(0x96)
utf8_state减为 0,encoding左移 6 位,合并96的低 6 位(0x16)。
encoding = 0x1618 << 6 | 0x16 = 0x4E16,返回0x4E16(“世” 的 Unicode 编码)。
处理E7(0xE7)
重复上述步骤,最终返回0x754C(“界” 的 Unicode 编码)。