我如何将 Glyphs 沿基线与 Freetype 对齐?
How do I align Glyphs along the baseline with Freetype?
我编写了一个使用 Freetype2 从字体创建图像的函数。我的目标是创建一个包含字体中所有字符的 OpenGL 纹理。但首先,我想确保正确创建纹理,因此我暂时将其存储为带有 stb_image_write 的图像。这是我目前的进度:
如您所见,字形并未沿基线对齐,而是沿图像顶部对齐。所以我决定引入一个名为 "yOffset" 的变量,它描述了每个字形必须向下移动才能正确对齐的像素数量。
根据Render FreeType text with flipped ortho, difference between top and baseline of glyph中的一个答案,这个偏移量可以用
计算
yOffset = (Glyph with highest y-Bearing)->bitmap_top - glyph->bitmap_top
这个值存储在 "maxAscent" 中,第一个循环找出每个字形的度量。
这是字形度量的直观表示:
这是我的函数:
static void loadFontImage(const std::string& fontPath, unsigned int fontSize, const std::string& imagePath) {
FT_Library library;
FT_Face face;
unsigned int imageWidth = 0;
unsigned int imageHeight = 0;
unsigned int maxAscent = 0;
if (FT_Init_FreeType(&library)) {
std::cerr << "Could not initialize font library" << std::endl;
std::exit(-1);
}
if (FT_New_Face(library, fontPath.c_str(), 0, &face)) {
std::cerr << "Could not load font '" << fontPath << "'" << std::endl;
std::exit(-1);
}
FT_Set_Char_Size(face, 0, fontSize * 64, 300, 300);
FT_GlyphSlot glyph = face->glyph;
for (unsigned int c = 32; c < 256; c++) {
if (c == 127) continue;
if (FT_Load_Char(face, c, FT_LOAD_RENDER)) continue;
imageWidth += glyph->bitmap.width;
if (glyph->bitmap.rows > (int)imageHeight) {
imageHeight = glyph->bitmap.rows;
}
if (glyph->bitmap_top > (int)maxAscent) {
maxAscent = glyph->bitmap_top;
}
}
unsigned int size = imageWidth * imageHeight;
unsigned char* buffer = new unsigned char[size];
unsigned int xOffset = 0;
unsigned int yOffset = 0;
for (unsigned int c = 32; c < 256; c++) {
if (c == 127) continue;
if (FT_Load_Char(face, c, FT_LOAD_RENDER)) continue;
yOffset = maxAscent - glyph->bitmap_top;
for (unsigned int x = 0; x < glyph->bitmap.width; x++) {
for (unsigned int y = 0; y < glyph->bitmap.rows; y++) {
unsigned int imageIndex = (x + xOffset) + (y + yOffset) * imageWidth;
unsigned int bitmapIndex = x + y * glyph->bitmap.width;
buffer[imageIndex] = glyph->bitmap.buffer[bitmapIndex];
}
}
xOffset += glyph->bitmap.width;
}
stbi_write_png(imagePath.c_str(), imageWidth, imageHeight, 1, buffer, imageWidth);
delete[] buffer;
}
}
但是由于我引入了yOffset,"index out of bounds"类型的异常只出现在某些字形上,因为字形的高度加上yOffset大于图像高度。
我认为这是因为我的 "maxAscent" 公式不正确,因此我的 yOffset 对于某些字符来说太大了。因此我的问题是:这确实是正确的公式吗?如果是,我的算法还有什么问题?是否有更简单的方法来实现正确对齐?
在我注意到图像的实际高度计算不正确之后,我通过简单的修复解决了这个问题。这是因为 "tallest" 字形的高度并不代表图像的实际高度。例如,一个字符可能小于最高的字形,但高于基线,因此超出范围。
我引入了另一个变量"maxDescent",它存储基线以下的最大像素数。在第一个循环中计算如下:
if ((glyph->metrics.height >> 6) - glyph->bitmap_top > (int)maxDescent) {
maxDescent = (glyph->metrics.height >> 6) - glyph->bitmap_top;
}
与 "maxAscent" 一起存储基线以上的最大像素数量,我可以通过简单地将它们相加来计算图像所需的实际高度。
imageHeight = maxAscent + maxDescent
这是结果的一部分:
@user12678368
谢谢你 post。这对我帮助很大。然而,我发现了两个小错误,也许你或未来的读者会觉得有趣:
1.
由于您的代码会计算最高的 y 方位角,因此您的文本将以不同的高度显示,具体取决于您使用的字符。
例如:
如果您显示字符串“A______”,您就会得到准确的结果。但是,如果您只显示“_____”,它将被楔入图像的顶部。
您可以使用:
maxAscent = int(face->ascender * (face->size->metrics.y_scale / 65536.0)) >> 6;
maxDescent = int(abs(face->descender * (face->size->metrics.y_scale / 65536.0))) >> 6;
为您的字体获取全局 maxAscent/maxDescent(face->descender 为负数(对于大多数字体)因此在您的代码中为 abs())。这样,您也可以摆脱第一个 for 循环。
注意: 根据 FreeType 文档,这些值未以统一方式在字体中定义,可能会有所不同。所以你的里程数可能会有所不同。
2.
显示空格时,“xOffset += glyph->bitmap.width”会加一个零。意思是:“这是一个测试!”将显示为“Thisatest!”。您可以将该行更改为
xOffset += glyph->metrics.horiAdvance >> 6;
这会让你“这是一个测试!”。
我编写了一个使用 Freetype2 从字体创建图像的函数。我的目标是创建一个包含字体中所有字符的 OpenGL 纹理。但首先,我想确保正确创建纹理,因此我暂时将其存储为带有 stb_image_write 的图像。这是我目前的进度:
如您所见,字形并未沿基线对齐,而是沿图像顶部对齐。所以我决定引入一个名为 "yOffset" 的变量,它描述了每个字形必须向下移动才能正确对齐的像素数量。
根据Render FreeType text with flipped ortho, difference between top and baseline of glyph中的一个答案,这个偏移量可以用
计算yOffset = (Glyph with highest y-Bearing)->bitmap_top - glyph->bitmap_top
这个值存储在 "maxAscent" 中,第一个循环找出每个字形的度量。
这是字形度量的直观表示:
这是我的函数:
static void loadFontImage(const std::string& fontPath, unsigned int fontSize, const std::string& imagePath) {
FT_Library library;
FT_Face face;
unsigned int imageWidth = 0;
unsigned int imageHeight = 0;
unsigned int maxAscent = 0;
if (FT_Init_FreeType(&library)) {
std::cerr << "Could not initialize font library" << std::endl;
std::exit(-1);
}
if (FT_New_Face(library, fontPath.c_str(), 0, &face)) {
std::cerr << "Could not load font '" << fontPath << "'" << std::endl;
std::exit(-1);
}
FT_Set_Char_Size(face, 0, fontSize * 64, 300, 300);
FT_GlyphSlot glyph = face->glyph;
for (unsigned int c = 32; c < 256; c++) {
if (c == 127) continue;
if (FT_Load_Char(face, c, FT_LOAD_RENDER)) continue;
imageWidth += glyph->bitmap.width;
if (glyph->bitmap.rows > (int)imageHeight) {
imageHeight = glyph->bitmap.rows;
}
if (glyph->bitmap_top > (int)maxAscent) {
maxAscent = glyph->bitmap_top;
}
}
unsigned int size = imageWidth * imageHeight;
unsigned char* buffer = new unsigned char[size];
unsigned int xOffset = 0;
unsigned int yOffset = 0;
for (unsigned int c = 32; c < 256; c++) {
if (c == 127) continue;
if (FT_Load_Char(face, c, FT_LOAD_RENDER)) continue;
yOffset = maxAscent - glyph->bitmap_top;
for (unsigned int x = 0; x < glyph->bitmap.width; x++) {
for (unsigned int y = 0; y < glyph->bitmap.rows; y++) {
unsigned int imageIndex = (x + xOffset) + (y + yOffset) * imageWidth;
unsigned int bitmapIndex = x + y * glyph->bitmap.width;
buffer[imageIndex] = glyph->bitmap.buffer[bitmapIndex];
}
}
xOffset += glyph->bitmap.width;
}
stbi_write_png(imagePath.c_str(), imageWidth, imageHeight, 1, buffer, imageWidth);
delete[] buffer;
}
}
但是由于我引入了yOffset,"index out of bounds"类型的异常只出现在某些字形上,因为字形的高度加上yOffset大于图像高度。
我认为这是因为我的 "maxAscent" 公式不正确,因此我的 yOffset 对于某些字符来说太大了。因此我的问题是:这确实是正确的公式吗?如果是,我的算法还有什么问题?是否有更简单的方法来实现正确对齐?
在我注意到图像的实际高度计算不正确之后,我通过简单的修复解决了这个问题。这是因为 "tallest" 字形的高度并不代表图像的实际高度。例如,一个字符可能小于最高的字形,但高于基线,因此超出范围。
我引入了另一个变量"maxDescent",它存储基线以下的最大像素数。在第一个循环中计算如下:
if ((glyph->metrics.height >> 6) - glyph->bitmap_top > (int)maxDescent) {
maxDescent = (glyph->metrics.height >> 6) - glyph->bitmap_top;
}
与 "maxAscent" 一起存储基线以上的最大像素数量,我可以通过简单地将它们相加来计算图像所需的实际高度。
imageHeight = maxAscent + maxDescent
这是结果的一部分:
@user12678368
谢谢你 post。这对我帮助很大。然而,我发现了两个小错误,也许你或未来的读者会觉得有趣:
1.
由于您的代码会计算最高的 y 方位角,因此您的文本将以不同的高度显示,具体取决于您使用的字符。
例如:
如果您显示字符串“A______”,您就会得到准确的结果。但是,如果您只显示“_____”,它将被楔入图像的顶部。
您可以使用:
maxAscent = int(face->ascender * (face->size->metrics.y_scale / 65536.0)) >> 6;
maxDescent = int(abs(face->descender * (face->size->metrics.y_scale / 65536.0))) >> 6;
为您的字体获取全局 maxAscent/maxDescent(face->descender 为负数(对于大多数字体)因此在您的代码中为 abs())。这样,您也可以摆脱第一个 for 循环。
注意: 根据 FreeType 文档,这些值未以统一方式在字体中定义,可能会有所不同。所以你的里程数可能会有所不同。
2.
显示空格时,“xOffset += glyph->bitmap.width”会加一个零。意思是:“这是一个测试!”将显示为“Thisatest!”。您可以将该行更改为
xOffset += glyph->metrics.horiAdvance >> 6;
这会让你“这是一个测试!”。