将 TrueType 字体渲染为固定大小(就像在终端模拟器中一样)

问题描述 投票:0回答:1

上下文:我正在开发一个当前使用位图字体的终端模拟器(在我的例子中,它只是一个 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;
}

结果(我需要声誉):

TrueType Output

你可以看到‘p’和‘q’肯定是错误渲染的。

我不太确定我是否按照应有的方式做事。

c++ fonts graphics truetype freetype
1个回答
0
投票

您没有考虑字形的方位(请参阅: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

计算出字形的“正确”位置后,即可将其调整到(固定大小)单元格中。

© www.soinside.com 2019 - 2024. All rights reserved.