欢迎来到现代Linux文本渲染的第一部分。 在本系列的每篇文章中,我们将开发一个独立的C程序以可视化一个字符或字符序列。 这些程序中的每一个都将实现我认为现代文本呈现所必需的功能。
在第一部分中,我们将配置FreeType并在控制台中编写一个简单的符号渲染器。

这就是我们要写的。
这是代码。
系统设定
- 我的操作系统:
Ubuntu 18.04.2 LTS (bionic)
- C编译器:
clang version 6.0.0-1ubuntu2
安装FreeType
在Ubuntu上,您需要安装FreeType和libpng。
$ sudo apt install libfreetype6 libfreetype6-dev $ sudo apt install libpng16-16 libpng-dev
- 我有FreeType版本
2.8.1-2ubuntu2
,尽管在撰写本文时, FreeType-2.10.1
的最新版本FreeType-2.10.1
,它也可以使用。
- libpng版本
(1.6.34-1ubuntu0.18.04.2)
控制台渲染器
创建一个C文件(在我的情况下为main.c
)
#include <stdio.h> int main() { printf("Hello, world\n"); return 0; }
$ clang -Wall -Werror -o main main.c $ ./main Hello, world
我们连接FreeType库
要搜索FreeType的包含路径(即,编译器在
#include
搜索文件时通过的目录),请运行:
$ pkg-config --cflags freetype2 -I/usr/include/freetype2 -I/usr/include/libpng16
-I/usr/include/freetype2 -I/usr/include/libpng16
包含在C程序中启用FreeType所需的编译标志。
#include <stdio.h> #include <freetype2/ft2build.h> #include FT_FREETYPE_H int main() { printf("Hello, world\n"); return 0; }
$ clang -I/usr/include/freetype2 \ -I/usr/include/libpng16 \ -Wall -Werror \ -o main \ main.c $ ./main Hello, world
我们打印FreeType的版本
在
main()
使用
FT_Init_FreeType(&ft)
初始化FreeType并检查错误(如果成功,FreeType函数将返回0)。
(从现在开始,我将使用的所有功能都来自
FreeType API的
帮助 )。
FT_Library ft; FT_Error err = FT_Init_FreeType(&ft); if (err != 0) { printf("Failed to initialize FreeType\n"); exit(EXIT_FAILURE); }
然后使用FT_Library_Version获得版本号。
FT_Int major, minor, patch; FT_Library_Version(ft, &major, &minor, &patch); printf("FreeType's version is %d.%d.%d\n", major, minor, patch);
如果使用最后一条命令进行编译,则会弹出链接器错误:
/tmp/main-d41304.o: In function `main': main.c:(.text+0x14): undefined reference to `FT_Init_FreeType' main.c:(.text+0x54): undefined reference to `FT_Library_Version' clang: error: linker command failed with exit code 1 (use -v to see invocation)
要解决此问题,请添加
-lfreetype
。
$ clang -I/usr/include/freetype2 \ -I/usr/include/libpng16 \ -Wall -Werror \ -o main \ -lfreetype \ main.c $ ./main FreeType's version is 2.8.1
字体下载
渲染字符的第一步是下载字体文件。 我正在使用
ubuntu mono 。
要了解字体结构,字体系列和单个字体之间的确切区别,请参阅
FreeType文档 。
第三个参数称为
面部索引 。 它旨在允许字体创建者将多个面插入相同的字体大小。 由于每种字体至少具有一个面,因此选择第一个选项时,始终使用0值。
FT_Face face; err = FT_New_Face(ft, "./UbuntuMono.ttf", 0, &face); if (err != 0) { printf("Failed to load face\n"); exit(EXIT_FAILURE); }
设置面部像素大小
使用此指令,我们告诉FreeType显示的字符所需的宽度和高度。
如果您为宽度传递零,则FreeType会将其解释为“与其他宽度相同”,在这种情况下为32px。 例如,它可以用于显示一个字符,其宽度为10px,高度为16px。
像emoji表情一样,此操作可能对固定大小的字体失败。
err = FT_Set_Pixel_Sizes(face, 0, 32); if (err != 0) { printf("Failed to set pixel size\n"); exit(EXIT_FAILURE); }
获取人物索引
首先,返回
FreeType文档并建立命名约定。 符号与
字形不同 。 字符是
char
所说的,字形是与该字符相关联的图像。 这种关系相当复杂,因为char可以对应几个字形:即重音。 一个字形可以对应许多字符:即连字,其中->表示为单个图像。
为了获得与字符相对应的字形索引,我们使用
FT_Get_Char_Index
。 如您所知,这只需要一对一地匹配字符和字形。 在本系列的后续文章中,我们将使用
HarfBuzz库解决该问题。
FT_UInt glyph_index = FT_Get_Char_Index(face, 'a');
从脸上加载字形
收到glyph_index后,我们可以从脸上加载相应的字形。
在以后的文章中,我们将详细讨论各种下载标志以及它们如何使您使用提示和位图字体等功能。
FT_Int32 load_flags = FT_LOAD_DEFAULT; err = FT_Load_Glyph(face, glyph_index, load_flags); if (err != 0) { printf("Failed to load glyph\n"); exit(EXIT_FAILURE); }
在其容器中显示字形(字形插槽)
现在,我们终于可以在
face->glyph
指定的容器(插槽)中显示
face->glyph
。
将来,我们还将讨论渲染标记,因为它们允许使用LCD(或子像素)渲染和灰度抗锯齿。
FT_Int32 render_flags = FT_RENDER_MODE_NORMAL; err = FT_Render_Glyph(face->glyph, render_flags); if (err != 0) { printf("Failed to render the glyph\n"); exit(EXIT_FAILURE); }
字符输出到控制台
可以从
face->glyph->bitmap.buffer
获得渲染的字形的位图,该位图以无符号char值的数组形式显示,因此其值的范围为0到255。
缓冲区以一维数组形式返回,但它是2D图像。 要访问第j列的第i行,我们计算
column * row_width + row
,就像在
bitmap.buffer[i * face->glyph->bitmap.pitch + j]
。
您可以看到,在访问数组时,我们在循环和
bitmap.pitch
使用了
bitmap.pitch
,因为每行像素的长度等于
bitmap.width
,但是缓冲区的“宽度”是
bitmap.pitch
。
在以下代码中,对所有行和列进行了排序,并根据像素的亮度绘制了不同的字符。
for (size_t i = 0; i < face->glyph->bitmap.rows; i++) { for (size_t j = 0; j < face->glyph->bitmap.width; j++) { unsigned char pixel_brightness = face->glyph->bitmap.buffer[i * face->glyph->bitmap.pitch + j]; if (pixel_brightness > 169) { printf("*"); } else if (pixel_brightness > 84) { printf("."); } else { printf(" "); } } printf("\n"); }
控制台输出。
$ clang -I/usr/include/freetype2 \ -I/usr/include/libpng16 \ -Wall -Werror \ -o main \ -lfreetype \ main.c && ./main FreeType's version is 2.8.1 .*****. .********. .********* . ***. *** *** .******** *********** .**. *** *** *** *** *** ***. *** .*********** *********** .*******..
→完整的代码可以在
这里看到
结论
我们已经在控制台中创建了一个基本的角色渲染器。 可以(并且将)扩展此示例以将字符渲染为OpenGL纹理,以支持表情符号,子像素渲染,连字等。 在下一部分中,我们将讨论与灰色阴影相比的LCD亚像素平滑,它们的优缺点。
待会儿见。