024 嵌入式Linux应用开发——文字显示与freetype的使用显示

一、第 1 节:字符的编码方式(6.1)------ 计算机怎么存 "字"?(秋招必背概念)

1.1 核心概念:编码 vs 字体

  • 编码 :字符的「数字编号」,是文件里存的内容(比如字符 'A' 的编码是0x41),是唯一的
  • 字体 :字符的「显示形状」,是屏幕上画出来的样子,可以有无数种
  • 本质:TXT 文件里存的是编码,Notepad 根据编码 + 字体,把字符渲染出来

1.2 3 种核心编码标准(面试必问)

1. ASCII(美国信息交换标准代码)
  • 范围:0~127,仅支持英文、数字、符号,1 个字节(7 位有效,最高位恒为 0)
  • 特点:所有编码的基础,UTF-8、Unicode 都向下兼容 ASCII
  • 嵌入式场景:所有系统通用,英文显示必用
2. ANSI(ASCII 扩展,本地化编码)
  • 本质:ASCII 的扩展,向下兼容 ASCII,非 ASCII 字符用 2 字节表示
  • 问题:无统一标准,和地区强相关
    • 中国大陆默认:GB2312/GBK(简体中文)
    • 港澳台默认:BIG5(繁体中文)
  • 致命缺陷:同一个数值对应不同字符(比如0xd0d6,GB2312 是 "中",BIG5 是 "篦"),跨地区乱码
3. Unicode(统一编码,全球通用)
  • 核心目标:给全球所有字符一个唯一的数值编号,彻底解决乱码
  • 范围:0x0000 ~ 0x10FFFF,支持 100 多万个字符,完全覆盖全球语言
  • 向下兼容 ASCII:ASCII 字符的 Unicode 编号和原编码完全一致
  • 问题:Unicode 只是「编号标准」,不是存储格式,需要具体的编码实现来存到文件里

1.3 Unicode 的 4 种编码实现(嵌入式 Linux 核心用 UTF-8)

编码格式 字节数 特点 嵌入式场景
UTF-32 固定 4 字节 简单,直接存 Unicode 编号 浪费空间,极少用
UTF-16 LE(小端) 2/4 字节 用 BOM(0xFFFE)标识,小端字节序 Windows 常用,嵌入式少用
UTF-16 BE(大端) 2/4 字节 用 BOM(0xFEFF)标识,大端字节序 网络传输常用,嵌入式少用
UTF-8 变长 1~4 字节 无 BOM,兼容 ASCII,ASCII 字符 1 字节,中文 3 字节 嵌入式 Linux 标准! 国际化必用
UTF-8 核心优势(面试必背)
  1. 完全兼容 ASCII:ASCII 文件直接当 UTF-8 打开,无乱码
  2. 变长编码,节省空间:英文 1 字节,中文 3 字节,比 UTF-16/32 省空间
  3. 无 BOM,无字节序问题:不用处理大小端,跨平台无压力
  4. 容错性强:单个字节损坏,仅影响当前字符,不影响后续字符

1.4 秋招考点总结(编码部分)

  1. **ASCII、ANSI、Unicode、UTF-8 的区别?**答:ASCII 是英文基础编码;ANSI 是本地化扩展,跨地区乱码;Unicode 是全球统一编号;UTF-8 是 Unicode 的变长存储实现,兼容 ASCII,是嵌入式 Linux 标准。
  2. **为什么嵌入式 Linux 用 UTF-8,不用 GB2312?**答:UTF-8 是国际通用编码,支持多语言,兼容 ASCII,无乱码;GB2312 仅支持简体中文,国际化差。
  3. **UTF-8 和 UTF-16 的核心区别?**答:UTF-8 是变长编码,无 BOM,兼容 ASCII;UTF-16 是定长 2 字节,有 BOM,不兼容 ASCII。

二、第 2 节:ASCII 字符的点阵显示(6.2)------ 最简单的文字显示

2.1 核心原理:点阵字库

  • 本质:把每个字符的形状,用「像素点阵」存起来(比如 8×16 点阵,每个字符 16 字节,1 字节对应一行 8 个像素)
  • 规则:位为 1→点亮像素(白色),位为 0→熄灭像素(黑色)
  • 流程:根据字符的 ASCII 码,从字库数组中取出对应点阵→遍历点阵→调用lcd_put_pixel画点

所有编码的字符要显示出来,都得先转成点阵这类显示数据

2.2 完整开发流程(对应教材show_ascii.c

步骤 1:准备点阵字库数组
cpp 复制代码
#define FONTDATAMAX 4096
// 完整按ASCII顺序排列的 8x16 字库(从空格0x20开始,每个字符16字节)
// 高位在右,非常规字库从高位到低位,从左到右显示
const unsigned char fontdata_8x16[FONTDATAMAX] = {
    // 0x20 空格
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    // 0x21 !
    0x00,0x00,0x00,0x18,0x38,0x38,0x38,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00,
    // 0x22 "
    0x00,0x00,0x00,0x36,0x36,0x36,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    // 0x23 #
    0x00,0x00,0x00,0x36,0x36,0x7F,0x36,0x36,0x36,0x7F,0x36,0x36,0x00,0x00,0x00,0x00,
    // 0x24 $
    0x00,0x00,0x00,0x0C,0x3E,0x63,0x62,0x3C,0x06,0x03,0x63,0x3E,0x0C,0x00,0x00,0x00,
    // 0x25 %
    0x00,0x00,0x00,0x00,0x63,0x66,0x0C,0x18,0x30,0x63,0x63,0x00,0x00,0x00,0x00,0x00,
    // 0x26 &
    0x00,0x00,0x00,0x1C,0x36,0x36,0x1C,0x3E,0x66,0x66,0x66,0x3B,0x00,0x00,0x00,0x00,
    // 0x27 '
    0x00,0x00,0x00,0x06,0x06,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    // 0x28 (
    0x00,0x00,0x00,0x18,0x0C,0x06,0x06,0x06,0x06,0x06,0x06,0x0C,0x18,0x00,0x00,0x00,
    // 0x29 )
    0x00,0x00,0x00,0x06,0x0C,0x18,0x18,0x18,0x18,0x18,0x18,0x0C,0x06,0x00,0x00,0x00,
    // 0x2A *
    0x00,0x00,0x00,0x00,0x00,0x36,0x1C,0x7F,0x1C,0x36,0x00,0x00,0x00,0x00,0x00,0x00,
    // 0x2B +
    0x00,0x00,0x00,0x00,0x00,0x0C,0x0C,0x3F,0x0C,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,
    // 0x2C ,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x0C,0x06,0x00,0x00,0x00,0x00,
    // 0x2D -
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    // 0x2E .
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x0C,0x00,0x00,0x00,0x00,
    // 0x2F /
    0x00,0x00,0x00,0x00,0x00,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x00,0x00,0x00,0x00,
    // 0x30 0
    0x00,0x00,0x00,0x1C,0x36,0x63,0x63,0x63,0x63,0x63,0x36,0x1C,0x00,0x00,0x00,0x00,
    // 0x31 1
    0x00,0x00,0x00,0x0C,0x1C,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x3F,0x00,0x00,0x00,0x00,
    // 0x32 2
    0x00,0x00,0x00,0x1E,0x33,0x30,0x18,0x0C,0x06,0x03,0x33,0x3F,0x00,0x00,0x00,0x00,
    // 0x33 3
    0x00,0x00,0x00,0x1E,0x33,0x30,0x18,0x30,0x30,0x33,0x1E,0x0C,0x00,0x00,0x00,0x00,
    // 0x34 4
    0x00,0x00,0x00,0x18,0x38,0x30,0x36,0x33,0x33,0x7F,0x30,0x30,0x00,0x00,0x00,0x00,
    // 0x35 5
    0x00,0x00,0x00,0x3F,0x03,0x03,0x1F,0x30,0x30,0x33,0x33,0x1E,0x00,0x00,0x00,0x00,
    // 0x36 6
    0x00,0x00,0x00,0x1C,0x06,0x03,0x1F,0x33,0x33,0x33,0x33,0x1E,0x00,0x00,0x00,0x00,
    // 0x37 7
    0x00,0x00,0x00,0x3F,0x33,0x30,0x18,0x0C,0x06,0x06,0x06,0x06,0x00,0x00,0x00,0x00,
    // 0x38 8
    0x00,0x00,0x00,0x1C,0x36,0x33,0x36,0x1C,0x36,0x33,0x33,0x1C,0x00,0x00,0x00,0x00,
    // 0x39 9
    0x00,0x00,0x00,0x1C,0x36,0x33,0x33,0x3E,0x30,0x18,0x0C,0x1C,0x00,0x00,0x00,0x00,
    // 0x3A :
    0x00,0x00,0x00,0x00,0x00,0x0C,0x0C,0x00,0x00,0x0C,0x0C,0x00,0x00,0x00,0x00,0x00,
    // 0x3B ;
    0x00,0x00,0x00,0x00,0x00,0x0C,0x0C,0x00,0x00,0x0C,0x0C,0x06,0x00,0x00,0x00,0x00,
    // 0x3C <
    0x00,0x00,0x00,0x00,0x00,0x18,0x0C,0x06,0x03,0x06,0x0C,0x18,0x00,0x00,0x00,0x00,
    // 0x3D =
    0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,0x00,0x00,
    // 0x3E >
    0x00,0x00,0x00,0x00,0x00,0x06,0x0C,0x18,0x30,0x18,0x0C,0x06,0x00,0x00,0x00,0x00,
    // 0x3F ?
    0x00,0x00,0x00,0x1C,0x36,0x30,0x18,0x0C,0x0C,0x00,0x0C,0x0C,0x00,0x00,0x00,0x00,
    // 0x40 @
    0x00,0x00,0x00,0x1E,0x33,0x33,0x3B,0x3B,0x3B,0x30,0x1E,0x00,0x00,0x00,0x00,0x00,
    // 0x41 A
    0x00,0x00,0x00,0x0C,0x1E,0x33,0x33,0x3F,0x33,0x33,0x33,0x33,0x00,0x00,0x00,0x00,
    // 0x42 B
    0x00,0x00,0x00,0x3F,0x66,0x66,0x66,0x3E,0x66,0x66,0x66,0x3F,0x00,0x00,0x00,0x00,
    // 0x43 C
    0x00,0x00,0x00,0x3C,0x66,0x03,0x03,0x03,0x03,0x03,0x66,0x3C,0x00,0x00,0x00,0x00,
    // 0x44 D
    0x00,0x00,0x00,0x1F,0x36,0x66,0x66,0x66,0x66,0x66,0x36,0x1F,0x00,0x00,0x00,0x00,
    // 0x45 E
    0x00,0x00,0x00,0x7F,0x66,0x06,0x06,0x3E,0x06,0x06,0x66,0x7F,0x00,0x00,0x00,0x00,
    // 0x46 F
    0x00,0x00,0x00,0x7F,0x66,0x06,0x06,0x3E,0x06,0x06,0x06,0x0F,0x00,0x00,0x00,0x00,
    // 0x47 G
    0x00,0x00,0x00,0x3C,0x66,0x03,0x03,0x03,0x73,0x63,0x66,0x3C,0x00,0x00,0x00,0x00,
    // 0x48 H
    0x00,0x00,0x00,0x33,0x33,0x33,0x33,0x3F,0x33,0x33,0x33,0x33,0x00,0x00,0x00,0x00,
    // 0x49 I
    0x00,0x00,0x00,0x1E,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x1E,0x00,0x00,0x00,0x00,
    // 0x4A J
    0x00,0x00,0x00,0x3C,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x0C,0x00,0x00,0x00,0x00,
    // 0x4B K
    0x00,0x00,0x00,0x67,0x66,0x36,0x1E,0x36,0x66,0x66,0x66,0x67,0x00,0x00,0x00,0x00,
    // 0x4C L
    0x00,0x00,0x00,0x0F,0x06,0x06,0x06,0x06,0x06,0x06,0x66,0x7F,0x00,0x00,0x00,0x00,
    // 0x4D M
    0x00,0x00,0x00,0x63,0x77,0x7F,0x7F,0x6B,0x63,0x63,0x63,0x63,0x00,0x00,0x00,0x00,
    // 0x4E N
    0x00,0x00,0x00,0x63,0x67,0x6F,0x7B,0x73,0x63,0x63,0x63,0x63,0x00,0x00,0x00,0x00,
    // 0x4F O
    0x00,0x00,0x00,0x1C,0x36,0x63,0x63,0x63,0x63,0x63,0x36,0x1C,0x00,0x00,0x00,0x00,
    // 0x50 P
    0x00,0x00,0x00,0x3F,0x66,0x66,0x66,0x3E,0x06,0x06,0x06,0x0F,0x00,0x00,0x00,0x00,
    // 0x51 Q
    0x00,0x00,0x00,0x1C,0x36,0x63,0x63,0x63,0x63,0x63,0x36,0x1C,0x30,0x30,0x00,0x00,
    // 0x52 R
    0x00,0x00,0x00,0x3F,0x66,0x66,0x66,0x3E,0x36,0x66,0x66,0x67,0x00,0x00,0x00,0x00,
    // 0x53 S
    0x00,0x00,0x00,0x1E,0x33,0x03,0x06,0x0C,0x18,0x30,0x33,0x1E,0x00,0x00,0x00,0x00,
    // 0x54 T
    0x00,0x00,0x00,0x3F,0x2D,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x1E,0x00,0x00,0x00,0x00,
    // 0x55 U
    0x00,0x00,0x00,0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x3F,0x00,0x00,0x00,0x00,
    // 0x56 V
    0x00,0x00,0x00,0x33,0x33,0x33,0x33,0x33,0x33,0x36,0x1C,0x0C,0x00,0x00,0x00,0x00,
    // 0x57 W
    0x00,0x00,0x00,0x63,0x63,0x63,0x63,0x6B,0x7F,0x77,0x63,0x63,0x00,0x00,0x00,0x00,
    // 0x58 X
    0x00,0x00,0x00,0x63,0x63,0x36,0x1C,0x1C,0x36,0x63,0x63,0x63,0x00,0x00,0x00,0x00,
    // 0x59 Y
    0x00,0x00,0x00,0x33,0x33,0x33,0x33,0x33,0x36,0x1C,0x0C,0x0C,0x00,0x00,0x00,0x00,
    // 0x5A Z
    0x00,0x00,0x00,0x7F,0x63,0x60,0x30,0x18,0x0C,0x06,0x63,0x7F,0x00,0x00,0x00,0x00,
};
步骤 2:实现 ASCII 字符显示函数
cpp 复制代码
/* draw ascii text */
void draw_ascii_char(int x, int y, char c, char *text, int color, int bg_color)
{
    // 单个ASCII字符绘制(从字库取模)
    const unsigned char *font = (unsigned char *)text + (c - ' ') * 16;
    for (size_t j = 0; j < 16; j++)
    {
        unsigned char byte = font[j];
        for (size_t i = 0; i < 8; i++)  // 现在写的数组是:非常规字库------低位在左
        {
            if (byte & (1<<(i)) ) 
            {
                draw_point(x+i,y+j,color);
            }
            else
            {
                draw_point(x+i,y+j,bg_color);
            }
        }
    }
}

// 字符串绘制函数(封装单个字符)
void draw_ascii_text(int x, int y, char *text, char *font, int color, int bg_color)
{
    int i = 0;
    while (text[i] != '\0')
    {
        draw_ascii_char(x + i*8, y, text[i], font, color, bg_color);
        i++;
    }
}
步骤 3:主函数完整流程(复用 Framebuffer 代码)
cpp 复制代码
int main(int argc, char *argv[])
{
    /* open fb0 file */
    fd_fb = open("/dev/fb0", O_RDWR );
    if( fd_fb < 0 )
    {
        perror("open /dev/fb0 failed");
        return -1;
    }
    fd_zk = open("./HZK16", O_RDONLY);
    if( fd_zk < 0 )
    {
        perror("open ./HZK16 failed");
        return -1;
    }
    /* get lcd date */
    if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var) == -1)
    {
        perror("ioctl get failure");
        close(fd_fb);
        return -2;
    }
    /* calculation Video memory */
    screen_size =  var.xres * var.yres *var.bits_per_pixel/8;
    fb_base = mmap(NULL, screen_size, PROT_WRITE | PROT_READ, MAP_SHARED, fd_fb, 0);
    if (fb_base == MAP_FAILED)
    {
        perror("mmap failed");
        close(fd_fb);  // 修复:mmap失败必须关闭文件描述符
        close(fd_zk);
        return -3;
    }

    if (fstat(fd_zk,&zk_stat))
    {
        perror("get zk fuiled");
    }
    fb_zk_base = mmap(NULL, zk_stat.st_size, PROT_READ, MAP_PRIVATE, fd_zk, 0);
    if (fb_zk_base == MAP_FAILED)
    {
        perror("mmap failed");
        close(fd_fb);
        close(fd_zk);
        return -3;
    }
    /* Draw a picture */
    draw_ascii_char(200, 200, 'L', (char *)fontdata_8x16, 0x0, 0xFFFFFF);
    draw_ascii_text(20, 20, "CCCCCC", (char *)fontdata_8x16, 0x0, 0xFFFFFF);
    draw_chinese_char(60,60,"我",fb_zk_base, 0x0, 0xFFFFFF);
    draw_mixed_text(60,60,"中国",fontdata_8x16,fb_zk_base, 0x0, 0xFFFFFF);
    draw_mixed_text(60,60,"中国666GOOD",(char *)fontdata_8x16,fb_zk_base, 0x0, 0xFFFFFF);
    /* release memory and Handle */
    munmap(fb_base,screen_size);
    close(fd_fb);
    return 0;
}

2.3 秋招考点总结(点阵 ASCII)

  1. **点阵字库的原理?**答:用像素点阵存储字符形状,位为 1 点亮、0 熄灭,根据字符编码取对应点阵,遍历画点。
  2. **点阵字库的优缺点?**答:优点:简单,无需库,直接操作;缺点:放大模糊,不同字号需要不同字库,占用空间大。
  3. **8x16 点阵中,每个字符占多少字节?为什么?**答:16 字节。因为 8×16=128 个像素,1 字节 = 8 位,128/8=16 字节。

三、第 3 节:中文字符的点阵显示(6.3)------ 从英文到中文

3.1 核心难点:编码格式与 GB2312 区位码

1. 中文编码:GB2312(点阵字库常用)
  • 每个中文字符用2 字节表示,兼容 ASCII(ASCII 字符 1 字节,最高位为 0)
  • 区位码结构:
    • 第 1 字节:区码0xA1 ~ 0xF7,共 94 个区)
    • 第 2 字节:位码0xA1 ~ 0xFE,每个区 94 个位)
  • 公式:汉字在 HZK16 字库中的偏移 = (区码 - 0xA1) * 94 + (位码 - 0xA1) × 32(每个汉字 16×16 点阵,32 字节)
2. 编码格式坑(教材重点)
  • C 程序编码 vs 可执行程序编码:
    • -finput-charset指定源文件编码
    • -fexec-charset指定可执行程序编码
    • 嵌入式 Linux 默认:-finput-charset=UTF-8 -fexec-charset=UTF-8
    • 中文点阵字库(HZK16)常用 GB2312,需指定:-finput-charset=GB2312 -fexec-charset=GB2312

3.2 完整开发流程(对应教材show_chinese.c,HZK16 字库)

步骤 1:打开 HZK16 点阵字库文件,mmap 映射
复制代码
int fd_hzk16;
struct stat hzk_stat;
unsigned char *hzkmem;

// 1. 打开HZK16字库文件
fd_hzk16 = open("HZK16", O_RDONLY);
if (fd_hzk16 < 0) { perror("open HZK16"); return -1; }

// 2. fstat获取文件大小
if (fstat(fd_hzk16, &hzk_stat)) { close(fd_hzk16); return -1; }

// 3. mmap映射字库到用户态内存
hzkmem = mmap(NULL, hzk_stat.st_size, PROT_READ, MAP_SHARED, fd_hzk16, 0);
if (hzkmem == MAP_FAILED) { close(fd_hzk16); return -1; }

普通文件读写要经过用户缓冲区和内核缓冲区的多次数据拷贝,每次操作都得调用 read、write 这样的系统调用,上下文切换开销大。而 mmap 把文件直接映射成内存地址,访问内存就相当于访问文件,省去了数据拷贝步骤,尤其是像字库这种需要频繁随机读取的场景,速度提升非常明显

步骤 2:实现中文显示函数lcd_put_chinese
cpp 复制代码
/* draw chinese text */
void draw_chinese_char(int x, int y, char *text, uint8_t *font, int color, int bg_color)
{
    // 因为一个人中文字符两个byte
    unsigned int area  = text[0] - 0xA1;
	unsigned int where = text[1] - 0xA1;
	unsigned char *dots = font + (area * 94 + where)*32;
    for (size_t j = 0; j < 16; j++)
    {
        for (size_t n = 0; n < 2; n++)
        {
            // 修复:有符号char改为无符号unsigned char,位运算不异常
            unsigned char byte = dots[j*2+n];
            for (size_t i = 0; i < 8; i++)
            {
                if (byte & (1<<(7-i)) )  // 标准字库,高位在左
                {
                    draw_point(x+i+n*8,y+j,color);
                }
                else
                {
                    draw_point(x+i+n*8,y+j,bg_color);
                }
            }
        }
    }
}
/* 绘制中文/ASCII混合字符串 (GB2312编码) */
void draw_mixed_text(int x, int y, char *str, uint8_t *ascii_font, uint8_t *hzk, int color, int bg_color)
{
    int i = 0;
    int cur_x = x;
    while (str[i] != '\0') {
        if ((uint8_t)str[i] >= 0xA1) { // GB2312汉字(首字节>=0xA1)
            draw_chinese_char(cur_x, y, &str[i], hzk, color, bg_color);
            cur_x += 16; // 汉字宽16像素
            i += 2;      // 汉字占2字节
        } else { // ASCII字符
            draw_ascii_char(cur_x, y, str[i], ascii_font, color, bg_color);
            cur_x += 8;  // ASCII宽8像素
            i += 1;      // ASCII占1字节
        }
    }
}
步骤 3:主函数调用
cpp 复制代码
    draw_chinese_char(60,60,"我",fb_zk_base, 0x0, 0xFFFFFF);
    draw_mixed_text(60,60,"中国",fontdata_8x16,fb_zk_base, 0x0, 0xFFFFFF);
    draw_mixed_text(60,60,"中国666GOOD",(char *)fontdata_8x16,fb_zk_base, 0x0, 0xFFFFFF);

3.3 秋招考点总结(中文点阵)

  1. **GB2312 区位码的原理?**答:GB2312 用区码 + 位码定位汉字,区码 0xA1~0xF7,位码 0xA1~0xFE,每个区 94 个汉字,偏移 =(区码 - 0xA1)*94+(位码 - 0xA1)。
  2. **中文点阵显示和 ASCII 显示的区别?**答:ASCII 用 1 字节编码,8×16 点阵;中文用 2 字节 GB2312 编码,16×16 点阵,需要区位码计算偏移。
  3. **为什么中文程序会乱码?**答:源文件编码、可执行程序编码、字库编码不统一,比如源文件 UTF-8,字库 GB2312,未做编码转换。

四、第 4 节:交叉编译 freetype(6.4)------ 矢量字库的基础

4.1 交叉编译核心知识(嵌入式 Linux 必懂)

1. 头文件、库文件、运行时库的区别
类型 作用 位置 编译时是否需要 运行时是否需要
头文件(.h) 函数声明、结构体定义 工具链include目录 ✅ 必须 ❌ 不需要
库文件(.a/.so) 函数实现 工具链lib目录 ✅ 链接时需要 ✅ 运行时需要(.so)
运行时库 动态链接库(.so) 开发板/lib//usr/lib ❌ 不需要 ✅ 必须
2. 交叉编译万能命令(教材核心)

数组形式的字库很多也是在 PC 上开发的,但它本质是用 C 语言数组定义的静态数据,不涉及复杂的库依赖和架构相关代码。比如你在 PC 上写一个 16×16 点阵的数组字库,只要数组的存储格式符合嵌入式编译器的要求,直接把代码复制到嵌入式项目中编译就行。而 FreeType 这类矢量字库包含大量架构相关的优化代码和动态内存管理逻辑,必须针对目标嵌入式架构交叉编译才能正常工作。

复制代码
# 以freetype为例,工具链前缀arm-buildroot-linux-gnueabihf-
./configure --host=arm-buildroot-linux-gnueabihf --prefix=$PWD/tmp
make
make install
  • --host:指定目标架构(交叉编译)
  • --prefix:指定安装目录(生成bin/lib/include
3. 常见错误解决
  • 头文件找不到 :用-I指定头文件目录,或把头文件放到工具链include目录
  • 库文件找不到(undefined reference) :用-L指定库目录,-lxxx链接库(比如-lfreetype
  • 运行时找不到库(error while loading shared libraries) :把.so放到开发板/lib//usr/lib,或用LD_LIBRARY_PATH指定

4.2 freetype 交叉编译流程(IMX6ULL 示例)

freetype 依赖zliblibpng,工具链好像buildroot工具链自带无需编译,否则按顺序编译1和2后移到对应目录再编译3:

  1. 编译 zlib

    cpp 复制代码
    CC=arm-buildroot-linux-gnueabihf-gcc ./configure --prefix=$PWD/tmp #因为不支持host指定,所以请在环境变量指定交叉编译器
    make
    make install
  2. 编译 libpng

    复制代码
    ./configure --host=arm-buildroot-linux-gnueabihf --prefix=$PWD/tmp
    make
    make install
  3. 编译 freetype

    复制代码
    ./configure --host=arm-buildroot-linux-gnueabihf --prefix=$PWD/tmp
    make
    make install

头/库 文件问题

把头文件、库文件放到工具链对应目录(1/2/3都需要) ,后续编译直接链接-lfreetype

确定头文件目录和库文件目录:

bash 复制代码
echo 'main(){}' | arm-buildroot-linux-gnueabihf-gcc -E -v -

/usr/local/arm/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/../lib/gcc/arm-buildroot-linux-gnueabihf/7.5.0/include

/usr/local/arm/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/lib/


cp ./include/* -rf /usr/local/arm/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/../lib/gcc/arm-buildroot-linux-gnueabihf/7.5.0/include

cp ./lib/* -rf /usr/local/arm/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/lib/

运行可执行文件时、交叉编译应用freetype库的C文件时也需要确保++系统路径++、环境变量存在或者也可以自己指定:链接时用 " -L dir "选项指定,也是同理

开发板:

cpp 复制代码
cp  lib/libfreetype.so /usr/lib    
#或者export LD_LIBRARY_PATH=./tmp/lib:$LD_LIBRARY_PATH
#系统目录:就是板子上的/lib、/usr/lib 目录

4.3 秋招考点总结(交叉编译)

  1. 交叉编译的本质? 答:在 x86 主机上编译出能在 ARM 架构上运行的程序,核心是指定--host目标架构。
  2. **头文件和库文件的作用?**答:头文件用于编译时的函数声明,库文件用于链接时的函数实现,运行时仅需库文件。
  3. **动态库和静态库的区别?**答:静态库(.a)编译时嵌入程序,程序体积大,不依赖库;动态库(.so)编译时不嵌入,运行时加载,程序体积小,依赖库。

五、第 5 节:使用 freetype 显示单个文字(6.5)------ 矢量字库核心

先看看:Freetype 介绍

FreeType是一个开源的高质量字体渲染库,它提供了一个统一的接口来访问多种字体格式文件,包括TrueType、OpenType、Type1等

它的应用非常广泛,包括操作系统、浏览器、办公软件等多个领域。它不仅能够提高字体渲染的质量,还能通过其跨平台特性,为开发者提供一致的用户体验

what is 矢量字体:

矢量字体形成分三步:

第1步 确定关键点,

第2步 使用数学曲线(贝塞尔曲线)连接头键点,

第3步 填充闭合区线内部空间。

什么是关键点?以字母"A"为例,它的的关键点如图 6.16 中的黄色所示。

而为达到显示矢量字体的目的我们只需要移植这个字体引擎,调用对应的 API 接口,提供字体文件,就可以让 freetype 库帮我们取出关键点、实现闭合曲线,填充颜色。

矢量字库获取:

Windows 使 用 的 字 体 文 件 在 c:\Windows\Fonts 目录下,扩展名为 TTF 的都是矢量字库------教材用的simsun.ttc

有了以上基础,一个文字的显示过程可以概括如下:

  • 给定一个字符可以确定它的编码值(ASCII、UNICODE、GB2312);
  • 设置字体大小;
  • 根据编码值,从文件头部中通过 charmap(字符映射表) 找到对应的关键点(glyph),它会根据字体大小调整关键点;
  • 把关键点转换为位图点阵;
  • 在 LCD 上显示出来

5.1 核心原理:矢量字库 vs 点阵字库

特性 点阵字库 矢量字库(freetype+TrueType)
存储内容 像素点阵 字符轮廓(贝塞尔曲线)
缩放 放大模糊,有锯齿 任意缩放,无锯齿,清晰
字库大小 每个字号一个字库,体积大 一个字库支持所有字号,体积小
依赖 无需库,直接操作 需要 freetype 库解析
嵌入式场景 老款小屏、低功耗系统 现代嵌入式 Linux HMI、智能设备

5.2 freetype 核心流程(教材 9 步,面试必背)

一般官网有介绍文档,其次去看源码。调库的开发就是这样。当然常用的找开源或论坛也行

  1. 初始化 freetype 库FT_Init_FreeType(&library)
  2. 加载字体文件(.ttf/.ttc)FT_New_Face(library, font_path, 0, &face)
  3. 设置字体大小FT_Set_Pixel_Sizes(face, font_size, 0)
  4. 选择字符编码FT_Select_Charmap(face, FT_ENCODING_UNICODE)
  5. 根据编码获取 glyph 索引FT_Get_Char_Index(face, char_code)
  6. 加载 glyph(字符轮廓)FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT)
  7. 渲染 glyph 到位图FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL)
  8. (可选)设置变换 / 旋转FT_Set_Transform(face, &matrix, &pen)
  9. 遍历位图,调用 lcd_put_pixel 画点

5.3 完整代码实现(对应教材freetype_show_font.c

步骤 1:freetype 初始化与位图绘制函数

关于wchar_t:

wchar_t 用于存储单个宽字符常量,本质是unsigned short

L 是标准 C 语言,用于宽字符 / 宽字符串;用于告诉编译器,后面跟的字符串或字符是宽字符类型,每个字符占 2 字节或 4 字节,对应 wchar_t 类型

可用来直接赋值获得字符的 UNICODE

注意:如果 .c 文件 是以 ANSI(GB2312)格式保存,那么需要使用以下命

令来编译:

bash 复制代码
gcc -finput-charset=GB2312 -fexec-charset=UTF-8 -o test_wchar test_wchar.c

在 ++C 语言(VScode)++ 里,处理字符编码时,通常需要先重新加载目标编码方式,让程序能正确解析新的字符数据,之后再以这个编码方式保存文件,这样才能确保宽字符内容按预期编码格式存储。否则荣容易乱套

cpp 复制代码
#include <stdio.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <ft2build.h>
#include FT_FREETYPE_H

unsigned char *fb_base;
struct  fb_var_screeninfo var;

/* draw point 无修改,逻辑正确 */
void draw_point(int x, int y, int color)
{
    if (x < 0 || y < 0 || x >= var.xres || y >= var.yres)
    {
        return ;
    }
    uint32_t offset = (x+y*var.xres) * var.bits_per_pixel /8;
    uint8_t *point = fb_base + offset;
    switch (var.bits_per_pixel)
    {
        case 32:
            *(uint32_t *)point = color ;
            break;
        case 24:
            point[2] = (color>>16) & 0xff ;
            point[1] = (color>>8) & 0xff ;
            point[0] = color & 0xff ;
            break;
        case 16:
            *(uint16_t *)point = (((color >> 19) & 0x1F) << 11) | (((color >> 10) & 0x3F) << 5) | ((color >> 3) & 0x1F);
            break;
        default:
            break;
    }
}

void draw_bitmap(FT_Bitmap *bitmap, FT_Int x, FT_Int y, int color)
{
    // 遍历位图的 每一行(高度)
    for (FT_Int j = 0; j < bitmap->rows; j++)
    {
        // 遍历位图的 每一列(宽度)
        for (FT_Int i = 0; i < bitmap->width; i++)
        {
            // 计算真实屏幕坐标
            FT_Int screen_x = x + i;
            FT_Int screen_y = y + j;

            // 屏幕越界判断
            if (screen_x < 0 || screen_y < 0 || screen_x >= var.xres || screen_y >= var.yres)
                continue;

            // 【关键修复】FreeType位图索引:j * pitch + i
            unsigned char pixel = bitmap->buffer[j * bitmap->pitch + i];
            // 非0表示有像素,绘制
            if (pixel)
                draw_point(screen_x, screen_y, color);
        }
    }
}


int main(int argc, char *argv[])
{
    FT_Library library;
    FT_Face face;
    FT_Error error;
    int ret = 0;
    int fd_fb;

    if (argc != 2)
    {
        printf("用法: %s <字体文件路径>\n", argv[0]);
        printf("示例: %s ./simhei.ttf\n", argv[0]);
        ret = -1;
        goto exit_re;
    }

    fd_fb = open("/dev/fb0",O_RDWR);
    if (fd_fb < 0)
    {
        perror("open /dev/fb0 failed");
        ret = -1;
        goto exit_re;
    }

    if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
    {
        perror("ioctl FBIOGET_VSCREENINFO failed");
        ret = -2;
        goto exit_cl;
    }
    size_t screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
    fb_base = mmap(NULL, screen_size,  PROT_WRITE | PROT_READ ,MAP_SHARED,fd_fb,0 );
    if (fb_base == MAP_FAILED)
    {
        perror("mmap failed");
        ret = -2;
        goto exit_cl;
    }
    memset(fb_base, 0, screen_size);

    // 1. 初始化freetype库
    error = FT_Init_FreeType(&library);
    if (error) 
    { 
        printf("FT_Init_FreeType error\n"); 
        ret = -3;
        goto exit_mp;
    }

    // 2. 加载字体文件
    error = FT_New_Face(library, argv[1], 0, &face);
    if (error == FT_Err_Unknown_File_Format) 
    {
        printf("unsupported font format\n");
        ret = -4;
        goto exit_ft; // 统一释放资源
    } else if (error) 
    {
        printf("FT_New_Face error\n");
        ret = -4;
        goto exit_ft;
    }

    /***************************
    【修复】参数顺序规范:(0, 像素高度)
    ***************************/
    error = FT_Set_Pixel_Sizes(face, 24, 0);
    if (error) 
    { 
        printf("FT_Set_Pixel_Sizes error\n"); 
        ret = -5;
        goto exit_face; 
    }

    // 编码设置
    error = FT_Select_Charmap(face, FT_ENCODING_UNICODE);
    if (error) 
    { 
        printf("FT_Select_Charmap error\n"); 
        ret = -6;
        goto exit_face; 
    }

    // 绘制汉字:繁
    const wchar_t *w = L"我";

    FT_UInt glyph_index = FT_Get_Char_Index(face, *w);
    error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
    if (error) { printf("FT_Load_Glyph error\n"); ret = -7; goto exit_face; }

    error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
    if (error) { printf("FT_Render_Glyph error\n"); ret = -8; goto exit_face; }

    /***************************
    【修复】颜色改为白色 0xFFFFFF(之前传0=黑色,看不见)
    ***************************/
    draw_bitmap(&face->glyph->bitmap,
                var.xres/2 - face->glyph->bitmap_left,
                var.yres/2 - face->glyph->bitmap_top,
                0xFFFFFF);

    // 资源释放(统一出口,更规范)
exit_face:
    FT_Done_Face(face);
exit_ft:
    FT_Done_FreeType(library);
exit_mp:
    munmap(fb_base,screen_size);
exit_cl:
    close(fd_fb);
exit_re:
    return ret;
}
步骤 2:编译命令
复制代码
# 交叉编译,链接freetype库
arm-buildroot-linux-gnueabihf-gcc -o freetype_show_font freetype_show_font.c -lfreetype

5.4 秋招考点总结(freetype 单个文字)

  1. **freetype 的核心作用?**答:开源矢量字库解析库,支持 TrueType 等字体,把矢量轮廓转换成像素点阵,实现任意缩放的文字显示。
  2. **freetype 显示文字的完整流程?**答:初始化库→加载字体→设置大小→选择编码→获取 glyph 索引→加载 glyph→渲染位图→绘制到 LCD。
  3. **矢量字库为什么放大不模糊?**答:存储的是字符的贝塞尔曲线轮廓,放大时重新计算轮廓,生成新的点阵,无锯齿。

六、第 6 节:使用 freetype 显示一行文字(6.6)------ 从单个到多行

6.1 核心难点:坐标转换与字符排版

1. 坐标系统转换

纵向上LCD坐标系+笛卡尔刚好是屏幕高度

  • freetype 笛卡尔坐标系:原点在左下角,x 向右,y 向上
  • LCD 屏幕坐标系:原点在左上角,x 向右,y 向下
  • 转换公式:LCD_y = 屏幕高度 - 笛卡尔_y
2. 字符排版核心参数

因为有的字符占用的宽度不一样

  • pen(原点) :每个字符的绘制起点,后一个字符的原点 = 前一个字符的原点 + advance(字符间距)
  • FT_BBox :字符的外框(xMin/yMin/xMax/yMax),用于计算一行文字的整体外框,实现居中对齐
  • baseline:一行文字的基准线,所有字符基于 baseline 对齐

6.2 完整开发流程(对应教材show_line.c

单个字符外框有两个关键特点:一是以字符基线左下角为原点,坐标有正有负;二是每个字符外框独立,仅描述自身轮廓范围,不含与其他字符的间距信息

整合成行外框,你需要先获取每个字符的水平 advance 值,这个值是字符宽度加默认间距,用来确定下一个字符的起始位置。然后遍历所有字符,累加 advance 值得到行宽,同时比较所有字符的 yMin 和 yMax,取最小值和最大值作为行高范围。

步骤 1:计算一行文字的外框compute_string_bbox
cpp 复制代码
/* 计算字符串外框:修复内存泄漏 + 缓存字符串长度 */
int compute_string_bbox(FT_Face face, wchar_t *wstr, FT_BBox *abbox)
{
    int i;
    int error;
    FT_BBox bbox;
    FT_BBox glyph_bbox;
    FT_Vector pen;
    FT_Glyph glyph;
    FT_GlyphSlot slot = face->glyph;
    size_t str_len = wcslen(wstr);  // 缓存长度,避免重复计算

    // 初始化外框
    bbox.xMin = bbox.yMin = 32000;
    bbox.xMax = bbox.yMax = -32000;

    pen.x = 0;
    pen.y = 0;

    for (i = 0; i < str_len; i++)
    {
        FT_Set_Transform(face, 0, &pen);

        error = FT_Load_Char(face, wstr[i], FT_LOAD_RENDER);
        if (error)
        {
            printf("FT_Load_Char error\n");
            return -1;
        }

        error = FT_Get_Glyph(face->glyph, &glyph);
        if (error)
        {
            printf("FT_Get_Glyph error!\n");
            return -1;
        }

        FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &glyph_bbox);

        // 更新外框
        bbox.xMin = (glyph_bbox.xMin < bbox.xMin) ? glyph_bbox.xMin : bbox.xMin;
        bbox.yMin = (glyph_bbox.yMin < bbox.yMin) ? glyph_bbox.yMin : bbox.yMin;
        bbox.xMax = (glyph_bbox.xMax > bbox.xMax) ? glyph_bbox.xMax : bbox.xMax;
        bbox.yMax = (glyph_bbox.yMax > bbox.yMax) ? glyph_bbox.yMax : bbox.yMax;

        FT_Done_Glyph(glyph);  // 🔥 修复内存泄漏:必须释放!

        pen.x += slot->advance.x;
        pen.y += slot->advance.y;
    }

    *abbox = bbox;
    return 0;
}
步骤 2:显示一行文字display_string
cpp 复制代码
/* 显示字符串:修复边界逻辑 + 删除无用变量 + 优化矩阵位置 */
int display_string(FT_Face face, wchar_t *wstr, int lcd_x, int lcd_y, double angle, int* y_limit_p)
{
    int i;
    int error;
    FT_Vector pen;
    FT_BBox bbox;
    FT_GlyphSlot slot = face->glyph;
    FT_Matrix matrix;
    size_t str_len = wcslen(wstr);  // 缓存长度

    // 🔥 修复垂直边界保护逻辑(防止文字重叠)
    if (*y_limit_p < 0)
        *y_limit_p = var.yres;
    if (lcd_y > *y_limit_p)
        lcd_y = *y_limit_p;

    // 坐标转换:LCD → FreeType笛卡尔坐标
    int x = lcd_x;
    int y = var.yres - lcd_y;

    // 计算字符串总外框
    compute_string_bbox(face, wstr, &bbox);

    // 反推绘制原点(核心定位)
    pen.x = (x - bbox.xMin) * 64;
    pen.y = (y - bbox.yMax) * 64;

    // 旋转矩阵:只设置一次(放循环外,优化效率)
    matrix.xx = (FT_Fixed)(cos(angle) * 0x10000L);
    matrix.xy = (FT_Fixed)(-sin(angle) * 0x10000L);
    matrix.yx = (FT_Fixed)(sin(angle) * 0x10000L);
    matrix.yy = (FT_Fixed)(cos(angle) * 0x10000L);

    // 逐字符绘制
    for (i = 0; i < str_len; i++)
    {
        FT_Set_Transform(face, &matrix, &pen);

        error = FT_Load_Char(face, wstr[i], FT_LOAD_RENDER);
        if (error)
        {
            printf("FT_Load_Char error\n");
            return -1;
        }

        // 绘制到LCD
        draw_bitmap(&slot->bitmap,
                    slot->bitmap_left,
                    var.yres - slot->bitmap_top,
                    0xFFFFFF);

        // 移动画笔
        pen.x += slot->advance.x;
        pen.y += slot->advance.y;
    }

    // 更新垂直限制
    *y_limit_p = lcd_y - (bbox.yMax - bbox.yMin);
    return 0;
}
步骤 3:主函数调用
cpp 复制代码
int main(int argc, char *argv[])
{
    FT_Library library;
    FT_Face face;
    FT_Error error;
    wchar_t *wstr = L"百问网www.100ask.net";
    int ret = 0;
    int fd_fb = -1;
    int font_size = 24;

    int lcd_x, lcd_y;
    double angle;
    int y_limit;  // 原野值变量

    // 🔥 修复1:初始化y_limit(核心)
    y_limit = 0;

    // 🔥 修复2:修正参数提示
    if (argc < 5)
    {
        printf("usage: %s <字体文件> <X坐标> <Y坐标> <角度> [字号]\n", argv[0]);
        printf("example: %s ./simhei.ttf 100 100 0 24\n", argv[0]);
        return -1;
    }

    lcd_x = atoi(argv[2]);
    lcd_y = atoi(argv[3]);
    angle = strtod(argv[4], NULL);
    angle = angle / 360.0 * 2 * M_PI;  // 角度转弧度

    if (argc == 6)
        font_size = atoi(argv[5]);

    // 打开FrameBuffer
    fd_fb = open("/dev/fb0", O_RDWR);
    if (fd_fb < 0)
    {
        perror("open /dev/fb0 failed");
        ret = -1;
        goto exit_re;
    }

    if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
    {
        perror("ioctl failed");
        ret = -2;
        goto exit_cl;
    }

    size_t screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
    fb_base = mmap(NULL, screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
    if (fb_base == MAP_FAILED)
    {
        perror("mmap failed");
        ret = -2;
        goto exit_cl;
    }
    memset(fb_base, 0, screen_size);  // 清屏

    // FreeType初始化
    error = FT_Init_FreeType(&library);
    if (error)
    {
        printf("FT_Init_FreeType error\n");
        ret = -3;
        goto exit_mp;
    }

    error = FT_New_Face(library, argv[1], 0, &face);
    if (error)
    {
        printf("FT_New_Face error\n");
        ret = -4;
        goto exit_ft;
    }

    error = FT_Set_Pixel_Sizes(face, font_size, 0);
    if (error)
    {
        printf("FT_Set_Pixel_Sizes error\n");
        ret = -5;
        goto exit_face;
    }
    display_string(face, wstr, lcd_x, lcd_y, angle, &y_limit);

exit_face:
    FT_Done_Face(face);
exit_ft:
    FT_Done_FreeType(library);
exit_mp:
    munmap(fb_base, screen_size);
exit_cl:
    close(fd_fb);
exit_re:
    return ret;
}

6.3 秋招考点总结(一行文字)

  1. **freetype 坐标系统和 LCD 坐标的区别?如何转换?**答:freetype 原点在左下角,y 向上;LCD 原点在左上角,y 向下。转换公式:LCD_y = 屏幕高度 - 笛卡尔_y。
  2. **如何实现一行文字的居中对齐?**答:先计算整行文字的外框 FT_BBox,根据外框反推原点 pen,让文字中心对应屏幕中心。
  3. **advance 参数的作用?**答:字符间距,用于计算下一个字符的原点,实现文字的连续排列。

七、全章秋招考点总梳理(面试 / 笔试必背)

1. 概念题(面试必问)

  1. 编码、字体、点阵字库、矢量字库的区别?
  2. ASCII、Unicode、UTF-8 的关系?为什么嵌入式 Linux 用 UTF-8?
  3. freetype 的作用?显示文字的完整流程?
  4. 点阵字库和矢量字库的优缺点?嵌入式场景如何选择?
  5. 交叉编译的原理?头文件、库文件、动态库 / 静态库的区别?

2. 笔试题(手写代码)

  1. 手写 ASCII 点阵字符显示函数
  2. 手写 freetype 显示单个文字的核心流程
  3. 手写 freetype 显示一行文字的排版逻辑
  4. 手写 GB2312 区位码计算偏移的公式

3. 实战题(项目经验)

  1. 如何解决中文乱码问题?
  2. 如何实现文字的旋转、倾斜?
  3. 如何实现多行文字自动换行?
  4. 如何优化 freetype 的显示性能?

八、师傅给你的学习建议(效率优先,秋招导向)

  1. 学习顺序 :先吃透编码原理 →再学点阵字库(ASCII / 中文) →最后学freetype 矢量字库,循序渐进,不要跳
  2. 实践重点
    • 先跑通show_ascii.c,再跑通show_chinese.c,理解点阵的本质
    • 再跑通freetype_show_font.c,理解矢量字库的优势
    • 最后跑通show_line.c,掌握文字排版,为 GUI 开发打基础
  3. 时间分配
    • 编码 + 点阵:1-2 天(吃透原理,跑通代码)
    • freetype 交叉编译 + 单个文字:1-2 天(重点是流程和代码)
    • 一行文字 + 排版:1 天(重点是坐标转换和排版)
    • 总耗时:3-5 天,完全足够秋招备考
  4. 秋招优先级
    • 必背:编码原理、freetype 流程、点阵原理
    • 必写:ASCII 画点函数、freetype 单个文字代码
    • 加分:文字排版、旋转、多行换行

九、后续学习规划

  • 下一步:基于 freetype,实现多行文字自动换行、指定颜色显示、中英文混合显示(教材课后作业)
  • 再下一步:结合 Framebuffer,做一个简单的 GUI 界面(比如菜单、文本框)
  • 秋招前:把全章原理、代码背熟,面试能完整说清流程,笔试能手写核心函数
相关推荐
陳10305 小时前
Linux:进程的基本理解
linux·计算机外设·进程
七七powerful5 小时前
运维养龙虾--用Excalidraw Skill 手绘各种配图:从安装 Skill 到批量生成配图
运维
Hello World . .6 小时前
Linux驱动编程1:imxull上移植Linux系统
linux·运维·服务器
小夏子_riotous6 小时前
openstack的使用——5. Swift服务的基本使用
linux·运维·开发语言·分布式·云计算·openstack·swift
学Linux的语莫6 小时前
Hyper-V的安装使用
linux·windows·ubuntu·hyper-v
IMPYLH6 小时前
Linux 的 numfmt 命令
linux·运维·服务器·bash
proware6 小时前
海思3403与3559安全启动
linux·安全·tee
领尚6 小时前
openclaw 极简安装(Ubuntu 24.04 server)
linux·运维·ubuntu
Gofarlic_OMS6 小时前
Windchill的license合规使用报告自动化生成与审计追踪系统
大数据·运维·人工智能·云原生·自动化·云计算