英文:
Rendering TrueType fonts as fixed size (like in a terminal emulator)
问题
以下是代码的翻译部分:
#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;
/* 创建 256 个可以在需要时进行 blit 的 SDL_Surfaces */
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;
/* 分配一个固定大小的缓冲区 */
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;
/* 在固定大小的缓冲区中水平居中位图 */
int offsetx = (mono_width - width) / 2;
/* 使 FreeType 位图的底部与 n x n 网格的底部对齐 */
int offsety = (mono_height - height);
/* 将数据从源缓冲区复制到我们的固定大小缓冲区 */
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
uint8_t val = src[i * width + j];
/* 去除抗锯齿,只保留 1 或 0。 */
if(val > 128)
letter_buf[(offsety + i) * mono_width + (offsetx + j)] = 255;
}
}
/* 从位图创建 SDL_Surface */
letter_surfaces[i] = SDL_CreateRGBSurfaceFrom(letter_buf, mono_width, mono_height, 8, mono_width, 0, 0, 0, 0xff);
/* 根据颜色设置调色板,0 = 黑色,255 = 白色 */
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);
/* 使黑色透明 */
SDL_SetColorKey(letter_surfaces[i], SDL_TRUE, 0);
}
}
/* 在 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];
}
/* 析构函数 */
~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;
}
请注意,这只是代码的翻译部分,其中包含了注释和字符串。如果您需要有关代码的详细解释或修改建议,请提出具体问题。
英文:
Context: I'm working on a terminal emulator that currently uses a bitmap font (in my case, it is just a PNG file with the glyphs located at the positions that can be calculated as per their order in ASCII with the font resolution being provided in pixels such as 8x16). This was a simple but working solution that was very easy to implement. Now I want to work with TrueType fonts. I'm using the libfreetype2 to extract bitmap data for glyphs.
So my idea is that I'm going to extract ASCII characters from the TTF, turn them into SDL_Surfaces which can be blitted directly to the screen (this is what I do right now except I'm extracting pixel data from the PNG).
Now the issue with TrueType fonts of course is that the characters are not fixed width and therefore only occupy space only what they need. On the other hand, the terminal is obviously expecting the characters to be fixed width, as that I assume are generally how terminal emulators work (correct me if I am wrong).
What's the best way to do this for something like my usecase?
So my solution was to create an $n \times n$ bitmap (here $n$ is the pixel size of the font such as 16), and then transfer all the bitmap data to this buffer, shifting it to center it horizontally, and aligning the bottom of the FreeType bitmap with the bottom of the $n \times n$ grid. This of course seems to work for the most part except for letters such as 'g', 'y', 'p', 'q' where it looks awkward.
My attempt:
#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;
}
The result (I need reputation):
You can see that the 'p' and 'q' are definitely wrongly rendered.
I'm not really sure if I'm doing things the way they're supposed to be done.
答案1
得分: 0
你没有考虑字形的轴承(见:https://freetype.org/freetype2/docs/tutorial/glyph-metrics-3.svg)。
轴承信息可以在渲染字形位图后获取,如下所示:
FT_FaceRec::FT_GlyphSlotRec::bitmap_left; // 水平轴承
FT_FaceRec::FT_GlyphSlotRec::bitmap_top; // 垂直轴承
例如:
face->glyph->bitmap_left; // x轴承
face->glyph->bitmap_top; // y轴承
参见:https://freetype.org/freetype2/docs/reference/ft2-glyph_retrieval.html#ft_glyphslotrec
要计算绘制位置:
x = pen_x + glyph->bitmap_left;
y = pen_y - glyph->bitmap_top;
// 或者
// y = pen_y - (glyph_height - glyph->bitmap_top);
// 其中 glyph_height = glyph->bitmap.rows
参见:https://freetype.org/freetype2/docs/tutorial/step1.html#section-7
一旦计算出字形的“正确”位置,然后将其调整到(固定大小的)单元格中。
英文:
You don't take the bearing of the glyph into consideration (see: https://freetype.org/freetype2/docs/tutorial/glyph-metrics-3.svg).
The bearing information can be retrieved (after rendering the glyph bitmap) like:
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
See also: https://freetype.org/freetype2/docs/reference/ft2-glyph_retrieval.html#ft_glyphslotrec
To calculate the drawing position:
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
See also: https://freetype.org/freetype2/docs/tutorial/step1.html#section-7
Once you've calculated the 'correct' position of the glyph, then go and adjust it into the (fixed sized) cell.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论