如何将 Glyphs 沿基线与 Freetype 对齐?

Posted

技术标签:

【中文标题】如何将 Glyphs 沿基线与 Freetype 对齐?【英文标题】:How do I align Glyphs along the baseline with Freetype? 【发布时间】:2020-10-04 01:19:48 【问题描述】:

我编写了一个函数,它使用 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,“索引超出范围”类型的异常只发生在某些字形上,因为字形的高度加上 yOffset 大于图像高度。

我认为这是因为我的“maxAscent”公式不正确,因此我的 yOffset 对于某些字符来说太大了。因此我的问题是:这确实是正确的公式,如果是,我的算法还有什么问题?是否有更简单的方法来实现正确对齐?

【问题讨论】:

【参考方案1】:

在我注意到图像的实际高度计算不正确后,我通过简单的修复解决了这个问题。这是因为“最高”字形的高度并不代表图像的实际高度。例如,一个字符可能小于最高字形,但位于基线之上,因此超出了界限。

我引入了另一个名为“maxDescent”的变量,它存储了基线以下的最大像素量。在第一个循环中计算如下:

if ((glyph->metrics.height >> 6) - glyph->bitmap_top > (int)maxDescent) 
    maxDescent = (glyph->metrics.height >> 6) - glyph->bitmap_top;

与“maxAscent”一起存储基线以上的最大像素数量,我可以通过简单地将它们相加来计算图像所需的实际高度。

imageHeight = maxAscent + maxDescent

这是结果的一部分:

【讨论】:

【参考方案2】:

@user12678368

感谢您的这篇文章。这对我帮助很大。但是,我发现了两个小错误,也许您或未来的读者可能会觉得有趣:

1.

由于您的代码会计算最高 y-Bearing,因此您的文本将显示在不同的高度,具体取决于您使用的字符。

例如:

如果您显示字符串“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" 将添加一个零。意思是:“这是一个测试!”将显示为“Thisistest!”。您可以将该行更改为

xOffset += glyph->metrics.horiAdvance >> 6;

这会让你“这是一个测试!”。

【讨论】:

如果您有新问题,请点击 按钮提出问题。如果有助于提供上下文,请包含指向此问题的链接。 - From Review

以上是关于如何将 Glyphs 沿基线与 Freetype 对齐?的主要内容,如果未能解决你的问题,请参考以下文章

《转载》深入理解 CSS 中的行高与基线

深入理解 CSS 中的行高与基线

使用 GDI+,沿公共基线对齐文本(以几种不同字体绘制)的最简单方法是啥?

基线概念

vertical-align和text-align

use glyphs icons