上下文:我正在开发一个当前使用位图字体的终端模拟器(在我的例子中,它只是一个 PNG 文件,其字形位于可以按照 ASCII 顺序计算的位置,字体分辨率为以像素提供,例如 8x16)。这是一个简单但有效的解决方案,非常容易实施。现在我想使用 TrueType 字体。我正在使用 libfreetype2 提取字形的位图数据。
所以我的想法是,我将从 TTF 中提取 ASCII 字符,将它们转换为可以直接传输到屏幕的 SDL_Surfaces(这就是我现在所做的,除了从 PNG 中提取像素数据)。
现在,TrueType 字体的问题当然是字符的宽度不固定,因此仅占用所需的空间。另一方面,终端显然期望字符是固定宽度的,因为我认为这通常是终端模拟器的工作方式(如果我错了,请纠正我)。
对于像我的用例这样的事情,最好的方法是什么?
所以我的解决方案是创建一个$n imes n$位图(这里$n$是字体的pixel大小,例如16),然后将所有位图数据传输到这个缓冲区,将其移动到水平居中,并将 FreeType 位图的底部与 $n imes n$ 网格的底部对齐。当然,这似乎在大多数情况下都有效,除了“g”、“y”、“p”、“q”等字母,这些字母看起来很尴尬。
我的尝试:
#define SCREEN_W 800
#define SCREEN_H 600
#include <iostream>
#include <string>
#include <SDL2/SDL.h>
#include <stdint.h>
#include <ft2build.h>
#include FT_FREETYPE_H
class TrueTypeFont
{
public:
TrueTypeFont(std::string filename, int mw, int mh)
{
err = FT_Init_FreeType(&library);
if (err)
{
std::cerr << "Failed to start freetype." << std::endl;
}
err = FT_New_Face(library, filename.c_str(), 0, &face);
this->mono_height = mh;
this->mono_width = mw;
/* Create 256 SDL_Surfaces that can be blitted at request */
err = FT_Set_Pixel_Sizes(face, mono_width, mono_height);
for (int i = 0; i < 256; i++)
{
FT_UInt glyph_index = FT_Get_Char_Index(face, i);
err = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
uint8_t *src = face->glyph->bitmap.buffer;
/* Allocate a fixed size buffer */
uint8_t* letter_buf = (uint8_t*)calloc(mono_height * mono_width, sizeof(uint8_t));
letter_buffers[i] = letter_buf;
int height = face->glyph->bitmap.rows;
int width = face->glyph->bitmap.width;
/* Center the bitmap horizontally into our fixed size buffer */
int offsetx = (mono_width - width) / 2;
/* Match the botom with the bottom of the fixed size buffer */
int offsety = (mono_height - height);
/* Copy data from source buffer into our fixed size buffer */
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
uint8_t val = src[i * width + j];
/* Remove anti-aliasing, just have 1 or 0. */
if(val > 128)
letter_buf[(offsety + i) * mono_width + (offsetx + j)] = 255;
}
}
/* Create SDL_Surface from bitmap */
letter_surfaces[i] = SDL_CreateRGBSurfaceFrom(letter_buf, mono_width, mono_height, 8, mono_width, 0, 0, 0, 0xff);
/* Set the palette colors accordingly 0 = black, 255 = white */
SDL_Color colors[256];
for (int j = 0; j < 256; j++)
{
colors[j].r = colors[j].g = colors[j].b = j;
colors[j].a = j;
}
SDL_SetPaletteColors(letter_surfaces[i]->format->palette, colors, 0, 256);
/* Make black transparent */
SDL_SetColorKey(letter_surfaces[i], SDL_TRUE, 0);
}
}
/* Draw a string at x,y */
void Puts(SDL_Surface* screen, int x, int y, std::string s)
{
SDL_Rect r;
r.x = x;
r.y = y;
r.w = this->mono_width;
r.h = this->mono_height;
for(int i = 0; i < s.length(); i++)
{
SDL_Surface* glyph = this->GetGlyph(s[i]);
SDL_BlitSurface(glyph, NULL, screen, &r);
r.x += this->mono_width / 2;
}
}
SDL_Surface* GetGlyph(char c)
{
return letter_surfaces[c];
}
/* Destructor */
~TrueTypeFont()
{
for(int i = 0; i < 256; i++)
{
SDL_FreeSurface(this->letter_surfaces[i]);
free(this->letter_buffers[i]);
}
}
private:
int mono_width, mono_height;
FT_Library library;
FT_Face face;
FT_Error err;
SDL_Surface* letter_surfaces[256];
uint8_t* letter_buffers[256];
};
int main(void)
{
SDL_Init(SDL_INIT_EVERYTHING);
SDL_Window *window = SDL_CreateWindow("FreeType Test", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_W, SCREEN_H, SDL_WINDOW_SHOWN);
SDL_Surface *window_surface = SDL_GetWindowSurface(window);
TrueTypeFont arial("arial.ttf", 16, 16);
TrueTypeFont proggy("ProggyClean.ttf", 16, 16);
bool running = true;
while (running)
{
SDL_Event ev;
while (SDL_PollEvent(&ev))
{
if (ev.type == SDL_QUIT)
{
running = false;
}
}
SDL_Rect r;
SDL_FillRect(window_surface, NULL, 0x0000A0);
arial.Puts(window_surface, 0, 0, "Hello World!");
proggy.Puts(window_surface, 200, 200, "The quick brown fox jumps over the lazy dog. {} [] @ # $ * &");
SDL_UpdateWindowSurface(window);
}
return 0;
}
结果(我需要声誉):
你可以看到‘p’和‘q’肯定是错误渲染的。
我不太确定我是否按照应有的方式做事。
您没有考虑字形的方位(请参阅:https://freetype.org/freetype2/docs/tutorial/glyph-metrics-3.svg)。
可以检索方位信息(在渲染字形位图之后),例如:
FT_FaceRec::FT_GlyphSlotRec::bitmap_left; //horizontal bearing
FT_FaceRec::FT_GlyphSlotRec::bitmap_top; //vertical bearing
//e.g.
face->glyph->bitmap_left; //x bearing
face->glyph->bitmap_top; //y bearing
另请参阅:https://freetype.org/freetype2/docs/reference/ft2-glyph_retrieval.html#ft_glyphslotrec
计算绘图位置:
x = pen_x + glyph->bitmap_left;
y = pen_y - glyph->bitmap_top;
//or
//y = pen_y - (glyph_height - glyph->bitmap_top);
//where glyph_height = glyph->bitmap.rows
另请参阅:https://freetype.org/freetype2/docs/tutorial/step1.html#section-7
计算出字形的“正确”位置后,即可将其调整到(固定大小)单元格中。