如何在SDL2中呈现文本?

57

我正在使用一个SDL_WindowSDL_Renderer

是否可以将SDL_TTFSDL_Render/SDL_Window一起使用?如果可以,应该怎么做?

7个回答

80

是的,只要你有一个渲染器和窗口,并且你不打算涉及表面操作,那么你可能想着创建纹理,这里有一个样例代码:

//this opens a font style and sets a size
TTF_Font* Sans = TTF_OpenFont("Sans.ttf", 24);

// this is the color in rgb format,
// maxing out all would give you the color white,
// and it will be your text's color
SDL_Color White = {255, 255, 255};

// as TTF_RenderText_Solid could only be used on
// SDL_Surface then you have to create the surface first
SDL_Surface* surfaceMessage =
    TTF_RenderText_Solid(Sans, "put your text here", White); 

// now you can convert it into a texture
SDL_Texture* Message = SDL_CreateTextureFromSurface(renderer, surfaceMessage);

SDL_Rect Message_rect; //create a rect
Message_rect.x = 0;  //controls the rect's x coordinate 
Message_rect.y = 0; // controls the rect's y coordinte
Message_rect.w = 100; // controls the width of the rect
Message_rect.h = 100; // controls the height of the rect

// (0,0) is on the top left of the window/screen,
// think a rect as the text's box,
// that way it would be very simple to understand

// Now since it's a texture, you have to put RenderCopy
// in your game loop area, the area where the whole code executes

// you put the renderer's name first, the Message,
// the crop size (you can ignore this if you don't want
// to dabble with cropping), and the rect which is the size
// and coordinate of your texture
SDL_RenderCopy(renderer, Message, NULL, &Message_rect);

// Don't forget to free your surface and texture
SDL_FreeSurface(surfaceMessage);
SDL_DestroyTexture(Message);

我尝试逐行解释代码,你在那里没有看到任何窗口,因为我已经假设你知道如何初始化渲染器,这会让我知道你也知道如何初始化窗口,然后你所需要的就是如何初始化纹理的想法。
这里有一些小问题,请问你的窗口打开了吗?它是黑色的吗?如果是,那么我的想法是正确的;如果不是,那么你可以问我,我可以更改此代码以实现由渲染器和窗口组成的整个部分。

68
请问能否让您的表面空出来? - Leonardo
4
Message_rect.w = 100; //程序员需要知道文本的宽度吗? - engineerX
11
您可以使用TTF_SizeText(TTF_Font *font, const char *text, int *w, int *h)函数获取渲染文本的尺寸。 - Unicorn
3
TTF_Font和TTF_OpenFont未定义...能否解释一下它们的来源会很有帮助。 - Phil
6
如果这不起作用,请尝试在此代码之前运行 TTF_Init() - Keatinge
显示剩余10条评论

24

SDL_ttf 最简单可运行示例

图片描述

虽然不是很高效,但易于集成。如果要提高效率,请参阅:如何高效地使用 SDL2 渲染字体和文本?

这个库与主要的 SDL 源代码分开存储在一个单独的仓库中,但是托管在同一个官方服务器上,所以应该没问题:http://hg.libsdl.org/SDL_ttf/

换行符不起作用。您需要使用行高来处理。

编译和运行:

sudo apt-get install -y libsdl2-dev
gcc -lSDL2 -lSDL2_ttf -o ttf ttf.c
./ttf /usr/share/fonts/truetype/freefont/FreeMonoOblique.ttf

你需要向程序传递一个TTF字体文件的路径。

ttf.c

#include <stdlib.h>

#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>

#define WINDOW_WIDTH 300
#define WINDOW_HEIGHT (WINDOW_WIDTH)

/*
- x, y: upper left corner.
- texture, rect: outputs.
*/
void get_text_and_rect(SDL_Renderer *renderer, int x, int y, char *text,
        TTF_Font *font, SDL_Texture **texture, SDL_Rect *rect) {
    int text_width;
    int text_height;
    SDL_Surface *surface;
    SDL_Color textColor = {255, 255, 255, 0};

    surface = TTF_RenderText_Solid(font, text, textColor);
    *texture = SDL_CreateTextureFromSurface(renderer, surface);
    text_width = surface->w;
    text_height = surface->h;
    SDL_FreeSurface(surface);
    rect->x = x;
    rect->y = y;
    rect->w = text_width;
    rect->h = text_height;
}

int main(int argc, char **argv) {
    SDL_Event event;
    SDL_Rect rect1, rect2;
    SDL_Renderer *renderer;
    SDL_Texture *texture1, *texture2;
    SDL_Window *window;
    char *font_path;
    int quit;

    if (argc == 1) {
        font_path = "FreeSans.ttf";
    } else if (argc == 2) {
        font_path = argv[1];
    } else {
        fprintf(stderr, "error: too many arguments\n");
        exit(EXIT_FAILURE);
    }

    /* Inint TTF. */
    SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO);
    SDL_CreateWindowAndRenderer(WINDOW_WIDTH, WINDOW_WIDTH, 0, &window, &renderer);
    TTF_Init();
    TTF_Font *font = TTF_OpenFont(font_path, 24);
    if (font == NULL) {
        fprintf(stderr, "error: font not found\n");
        exit(EXIT_FAILURE);
    }
    get_text_and_rect(renderer, 0, 0, "hello", font, &texture1, &rect1);
    get_text_and_rect(renderer, 0, rect1.y + rect1.h, "world", font, &texture2, &rect2);

    quit = 0;
    while (!quit) {
        while (SDL_PollEvent(&event) == 1) {
            if (event.type == SDL_QUIT) {
                quit = 1;
            }
        }
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
        SDL_RenderClear(renderer);

        /* Use TTF textures. */
        SDL_RenderCopy(renderer, texture1, NULL, &rect1);
        SDL_RenderCopy(renderer, texture2, NULL, &rect2);

        SDL_RenderPresent(renderer);
    }

    /* Deinit TTF. */
    SDL_DestroyTexture(texture1);
    SDL_DestroyTexture(texture2);
    TTF_Quit();

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
    return EXIT_SUCCESS;
}

GitHub官方源代码.

在Ubuntu 16.04和SDL 2.0.4下测试通过。


15

没错。你可以用想要的文本创建一个表面(surface),然后将其转换为纹理(texture),以便进行渲染。

以下是我其中一个项目的示例代码:

std::string score_text = "score: " + std::to_string(score);        
SDL_Color textColor = { 255, 255, 255, 0 };
SDL_Surface* textSurface = TTF_RenderText_Solid(font, score_text.c_str(), textColor);
SDL_Texture* text = SDL_CreateTextureFromSurface(renderer, textSurface);
int text_width = textSurface->w;
int text_height = textSurface->h;
SDL_FreeSurface(textSurface);
SDL_Rect renderQuad = { 20, win_height - 30, text_width, text_height };
SDL_RenderCopy(renderer, text, NULL, &renderQuad);
SDL_DestroyTexture(text);

假设你已经正确地初始化了SDL_ttf并加载了字体。 在示例中,score是一个整数。屏幕被清除并渲染到其他地方(我没有包括这部分)。

有关完整的工作示例,请查看Lazy Foo上SDL2中SDL_ttf的教程


这个出现未处理的内存异常是出于某个原因吗? - Ethan Webster
@EthanWebster 噢,这段代码只是一个(不可运行的)示例,缺少必要的内容,比如初始化代码、错误检查等等。 - jpw
即使文本每帧都会改变,这是否高效? - Damian Yerrick
并不真正兼容我现有的使用SDL_BlitScaled的代码,而且字体渲染得很糟糕。 - Phil

5

为了更易于理解,我们进行了编辑。使用Roboto.ttf (https://fonts.google.com/specimen/Roboto) 替换 Verdana.ttf,并添加了not2qubit建议。请记住,这完全不遵循任何C++类约定。我只是想确保这个内容容易复制/粘贴并运行。

要构建此代码,您需要添加库SDL_ttf (https://www.libsdl.org/projects/SDL_ttf/)。

g++ demo.cpp -o demo -Wall -I include -lsdl2 -lsdl2_ttf

由于有些人在处理更复杂的代码时遇到困难,我在这里提供我的代码片段以帮助像我一样的初学者。这将只显示一个红色屏幕和黑色的“Hello World”。不要忘记在构建过程中添加 -lsdl2 和 -lsdl2_ttf,并在相同的文件夹中包含Verdana.ttf字体。

#include <iostream>
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h> //This is an sample library not included with stock SDL2. https://www.libsdl.org/projects/SDL_ttf/release-1.2.html

const char* WINDOW_TITLE = "Hello World SDL2 + TTF";
const char* FONT_NAME = "roboto.ttf";
const int FONT_SIZE = 128;
const int WINDOW_WIDTH = 1280, WINDOW_HEIGHT = 720;

SDL_Window* Window; // Window created by SDL.
SDL_Renderer* Renderer; // The renderer that shows our textures.
SDL_Event WindowEvent; // Event capturer from SDL Window.
SDL_Color TextColor = { 255, 0, 0, 255}; // Red SDL color.
TTF_Font* Font; // The font to be loaded from the ttf file.
SDL_Surface* TextSurface; // The surface necessary to create the font texture.
SDL_Texture* TextTexture; // The font texture prepared for render.
SDL_Rect TextRect; // Text rectangle area with the position for the texture text.

void CreateWindow() {
    Window = SDL_CreateWindow(WINDOW_TITLE, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_ALLOW_HIGHDPI);
    if (!Window)
        std::cout << "There was a problem creating the window.";
    Renderer = SDL_CreateRenderer(Window, -1, 0);
    if (!Renderer)
        std::cout << "There was a problem creating the renderer.";
}

void CreateText(const char* Message) {
    TTF_Init();
    TTF_Font *font = TTF_OpenFont(FONT_NAME, FONT_SIZE);
    if (!font)
        std::cout << "Couldn't find/init open ttf font." << std::endl;
    TextSurface = TTF_RenderText_Solid(font, Message, TextColor);
    TextTexture = SDL_CreateTextureFromSurface(Renderer, TextSurface);
    TextRect.x = WINDOW_WIDTH - TextSurface->w * 0.5; // Center horizontaly
    TextRect.y = WINDOW_HEIGHT - TextSurface->h * 0.5; // Center verticaly
    TextRect.w = TextSurface->w;
    TextRect.h = TextSurface->h;
    // After you create the texture you can release the surface memory allocation because we actually render the texture not the surface.
    SDL_FreeSurface(TextSurface);
    TTF_Quit();
}

bool IsPollingEvent() {
    while(SDL_PollEvent(&WindowEvent)) {
        switch (WindowEvent.type) {
            case SDL_QUIT: return false;
        }
    }
    return true;
}

void RenderText() {
    SDL_SetRenderDrawColor(Renderer, 0, 0, 0, 255); // Make window bg black.
    SDL_RenderClear(Renderer); // Paint screen black.
    SDL_RenderCopy(Renderer, TextTexture, NULL, &TextRect); // Add text to render queue.
    SDL_RenderPresent(Renderer); // Render everything that's on the queue.
    SDL_Delay(10); // Delay to prevent CPU overhead as suggested by the user `not2qubit`
}

void ClearMemory() {
    SDL_DestroyTexture(TextTexture);
    SDL_DestroyRenderer(Renderer);
    SDL_DestroyWindow(Window);
    SDL_Quit();
    std::cout << "Clear proccess done." << std::endl;
}

int main() {
    CreateWindow();
    CreateText("Hello SDL_Ttf");
    while (IsPollingEvent()) {
        RenderText();
    }
    ClearMemory();
    return EXIT_SUCCESS;
}

1

如果您想使用class实现:

// Include somewhere
#include <SDL2/SDL_ttf.h>

// Do not forget to init TTF once somewhere main.cpp
if (TTF_Init()==-1) {
    printf("Failed to TTF: %s", SDL_GetError());
    return 1;
}

// Have a font instance ready
TTF_Font* font = TTF_OpenFont("assets/fonts/ugly.ttf", 72);
if (font==NULL){
    printf("Failed to load font: %s", SDL_GetError());
}

// Have a Text instance ready
// (Class file is below this code)
// Also you need to provide SDL_Renderer* renderer to init
Text* fps_tracker = new Text(renderer, font);

// Somewhere in while true
fps_tracker.setText("FPS: 232");

// Render it
fps_tracker.render();

这是一个类:

using namespace std;
#include <string>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_ttf.h>

class Text {
    SDL_Texture* texture = NULL;
    SDL_Rect position;
    TTF_Font* font;
    SDL_Renderer* renderer;
    SDL_Color color;
    string text;
    public: 
    Text(SDL_Renderer* renderer, TTF_Font* font, string text="", int x=0, int y=0, SDL_Color color={255, 255, 255}) {
        position.x = x;
        position.y = y;
        this->color = color;
        this->font = font;
        this->renderer = renderer;
    }

    void setText(string text) {
        if (this->text==text){
            return;
        }
        this->text = text;
        SDL_DestroyTexture(texture);
        SDL_Surface* surface = TTF_RenderText_Solid(font, text.c_str(), color);
        if (surface==NULL) {
            printf("Failed to render text: %s", SDL_GetError());            
        }
        position.w = surface->w;
        position.h = surface->h;
        texture = SDL_CreateTextureFromSurface(renderer, surface);
        SDL_FreeSurface(surface);
    }
    
    void render() {
        SDL_RenderCopy(renderer, texture, NULL, &position);
    }  

    ~Text() {
        SDL_DestroyTexture(texture);
    }
    
};

1

针对Windows上的Powershell

如果你正在尝试从Windows上的Powershell执行此操作,你很快就会发现这真是个麻烦事。直到现在...

当你想要坚持使用Clang++SDL2渲染一个本地的Windows窗口和文本时,我刚刚花了几个小时来调试所有细节使其正常工作。

有三件事情你需要安装:LLVM, SDL2, SDL2_ttf。然后你必须确保你的程序可以找到你的库、头文件和字体。这基本上总结在以下程序here中:

//---------------------------------------------------------------------
//  Name:       HelloSDL2.cpp
//  Author:     EAML
//  Date:       2021-05-16
// 
//  Description:    
//      A minimal PoC for producing a native SDL2 Windows app that can
//      be ran from either Windows Explorer or from Powershell console.
//      It's designed to use minimal command line, compiler options, 
//      and dependencies... It will display a gray window for 2 sec's.
//
//  Dependencies:
//      [1] LLVM Clang++ compiler package
//      [2] SDL2 Libraries (DLL's) and Header files (*.h)
//      [3] TTF Libraries (DLL's) and Header files (*.h)
// 
//  Notes: 
//      There is a slight variation in the bahaviour, depending on: 
//      (a) if you compile as a Windows GUI:  the text will not show.
//      (b) if you compile as a console CLI:  text will show in both terminal and/or in a 2nd new window
//      (c) You may need to use "main()" for console and "WinMain()" for GUI...
//      (c) to install on Linux, use packages:  clang, libsdl2-dev
//      (d) Someone said: #define SDL_MAIN_HANDLED ...
//
//  To Run: 
//      cp .\SDL2\lib\x64\SDL2.dll C:\Windows\.     # For SDL2
//      cp .\SDL2_ttf\lib\x64\*.dll C:\Windows\.    # For SDL2 TTF
//      cp C:\Windows\Fonts\arial.ttf .             # Get a font...
// 
//  For a CLI version, with console output in 2nd Window:
//  # clang++.exe -std=c++11 main.cpp -o main.exe -L .\SDL2\lib\x64\ -L .\SDL2_ttf\lib\x64\ -I .\SDL2_ttf\include\ -I .\SDL2\include\ -lShell32 -lSDL2main -lSDL2 -lSDL2_ttf -Wno-narrowing -Xlinker /subsystem:console
//
//  For a GUI version, without any console output:
//  # clang++.exe -std=c++11 main.cpp -o main.exe -L .\SDL2\lib\x64\ -L .\SDL2_ttf\lib\x64\ -I .\SDL2_ttf\include\ -I .\SDL2\include\ -lShell32 -lSDL2main -lSDL2 -lSDL2_ttf -Wno-narrowing -Xlinker /subsystem:windows
// 
//  References:
//      [1] https://github.com/llvm/llvm-project/releases
//      [2] http://www.libsdl.org/release/SDL2-devel-2.0.14-VC.zip
//      [3] https://www.libsdl.org/projects/SDL_ttf/release/SDL2_ttf-devel-2.0.15-VC.zip
//      [4] https://www.libsdl.org/projects/SDL_ttf/docs/SDL_ttf.html
//      [5] http://www.sdltutorials.com/sdl-ttf
//---------------------------------------------------------------------
//#include <SDL2/SDL.h>
#include "SDL2/include/SDL.h"
#include "SDL2_ttf/include/SDL_ttf.h"
#include <stdio.h>

#define SCREEN_WIDTH    640
#define SCREEN_HEIGHT   480

#define WINDOW_TITLE    "Hello SDL2!"
//#define WINDOW_TEXT   "Hello World!"

void drawText ( SDL_Surface* screen, char* string, int size, int x, int y, SDL_Color fgC, SDL_Color bgC) {
    // Remember to call TTF_Init(), TTF_Quit(), before/after using this function.
    TTF_Font* font = TTF_OpenFont("arial.ttf", size);
    if(!font) {
        printf("[ERROR] TTF_OpenFont() Failed with: %s\n", TTF_GetError());
        exit(2);
    }
    TTF_SetFontStyle(font, TTF_STYLE_BOLD);
    //SDL_Surface* textSurface = TTF_RenderText_Solid(font, string, fgC);
    SDL_Surface* textSurface = TTF_RenderText_Shaded(font, string, fgC, bgC);
    SDL_Rect textLocation = { x, y, 0, 0 };
    SDL_BlitSurface(textSurface, NULL, screen, &textLocation);
    SDL_FreeSurface(textSurface);
    TTF_CloseFont(font);
    //printf("Oh My Goodness, an error : %s\n", TTF_GetError()); return 1;
}

int main(int argc, char* args[]) {
    SDL_Window* window = NULL;                      // The window we are rendering to
    SDL_Surface* screenSurface = NULL;              // The surface contained by the window
    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        printf( "SDL could not initialize! SDL Error: %s\n", SDL_GetError());
        return 1;
    }
    
    window = SDL_CreateWindow(WINDOW_TITLE, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
    if (window == NULL) {
        printf( "Window could not be created! SDL Error: %s\n", SDL_GetError());
        return 1;
    }
    
    screenSurface = SDL_GetWindowSurface(window);
    SDL_FillRect(screenSurface, NULL, SDL_MapRGB(screenSurface->format, 0x80, 0x80, 0x80)); // Set a gray background canvas
    SDL_UpdateWindowSurface(window);

    //-----------------------------------------------------
    // Draw the Text
    //-----------------------------------------------------
    if(TTF_Init() == -1) {
        printf("[ERROR] TTF_Init() Failed with: %s\n", TTF_GetError());
        exit(2);
    }
    SDL_Color fgC1 = { 0xff,0xff,0xff }, bgC1 = {0x00,0x00,0xa0};                               // white text on blue background
    SDL_Color fgC2 = { 0x00,0x00,0x00 }, bgC2 = {0xff,0x00,0xff};                               // black text on magenta background
    drawText( screenSurface, (char*) "Hello World! @ (x=50, y=100)", 18,  50,100, fgC1, bgC1);  // 18 pt @ (x=100,y=150)
    drawText( screenSurface, (char*) "arial.ttf @ (x=200, y=150)",   16, 200,150, fgC2, bgC2);  // 16 pt @ (x=100,y=150)
    SDL_UpdateWindowSurface(window);
    TTF_Quit();
    
    //-----------------------------------------------------
    // Get some info...
    //-----------------------------------------------------
    SDL_version compiled;
    SDL_version linked;
    SDL_version ttfv;

    SDL_VERSION(&compiled);
    SDL_GetVersion(&linked);
    SDL_TTF_VERSION(&ttfv);
    
    printf("Compiled using SDL version  : %d.%d.%d \n", compiled.major, compiled.minor, compiled.patch);
    printf("and linked with SDL version : %d.%d.%d \n", linked.major, linked.minor, linked.patch);
    printf("and using SDL_TTF version   : %d.%d.%d \n", ttfv.major, ttfv.minor, ttfv.patch);

    SDL_Delay(3000);  // Wait 3 seconds
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}


上述代码的运行结果如下:

enter image description here


如何达到这一点?

  1. 安装Windows上的LLVM

    • 勾选 [x] 添加到Windows PATH
  2. 如果您没有将LLVM添加到Windows PATH中,则至少暂时手动添加它。

    • 打开PowerShell并键入:$env:path += ";C:\Program Files\LLVM\bin"
  3. 为Windows安装SDL2: 下载并提取SDL2SDL2_ttf运行时二进制文件(*.dll)和头文件库(在[2,3]中找到) 并将它们放入与C++文件相同的目录中的单独的SDL文件夹中。

    现在您应该有类似于以下内容:

# tree --dirsfirst ./SDL2{,_ttf} -P *.h
./SDL2
├── include
│   ├── begin_code.h
│   ├── close_code.h
│   ├── SDL.h
│   ├── SDL_assert.h
...
│   ├── SDL_version.h
│   ├── SDL_video.h
│   └── SDL_vulkan.h
└── lib

./SDL2_ttf
└── include
    └── SDL_ttf.h

# tree --dirsfirst ./SDL2{,_ttf}/lib -P *.dll
./SDL2/lib
├── x64
│   └── SDL2.dll
└── x86
    └── SDL2.dll
./SDL2_ttf/lib
├── x64
│   ├── libfreetype-6.dll
│   ├── SDL2_ttf.dll
│   └── zlib1.dll
└── x86
    ├── libfreetype-6.dll
    ├── SDL2_ttf.dll
    └── zlib1.dll


将所有相关的已下载DLL文件复制到C:\Windows\目录下,除非你知道如何让clang++.exe能够找到它们。(我没有成功...)
cd C:\path\to\main.cpp
cp .\SDL2\lib\x64\SDL2.dll C:\Windows\.     # For SDL2
cp .\SDL2_ttf\lib\x64\*.dll C:\Windows\.    # For SDL2 TTF
cp C:\Windows\Fonts\arial.ttf .             # Get a font...
  1. 下载上述SDL2“Hello World” Windows程序。

    • 使用这里Minimal PoC代码。
  2. 使用以下命令编译程序:

clang++.exe -std=c++11 main2.cpp -o main.exe -L .\SDL2\lib\x64\ -L .\SDL2_ttf\lib\x64\ -I .\SDL2_ttf\include\ -I .\SDL2\include\ -lShell32 -lSDL2main -lSDL2 -lSDL2_ttf -Wno-narrowing -Xlinker /subsystem:windows

库的放置顺序似乎很重要。请确保按照上述顺序进行。

另外,注意两个不同的-Xlinker选项:

/subsystem:windows   # This give you only one window but no console output
/subsystem:console   # This give you console output, but in a 2nd window when in GUI

要查看其他链接器选项,请使用:

link.exe /link
link.exe /lib

# The most relevant are:
    /DLL
    /ENTRY:symbol
    /LIBPATH:dir
    /MACHINE:{ARM|ARM64|EBC|X64|X86}
    /SUBSYSTEM:{CONSOLE | NATIVE | POSIX | WINDOWS | WINDOWSCE |...}
    /VERBOSE

你现在可以开始了!


下载参考资料


0

如果您不想或无法使用SDL2 TTF库,您可以仅使用freetype库轻松实现它。

包含freetype

#include <ft2build.h>
#include FT_FREETYPE_H

创建纹理类

class texture
{
public:
    texture() : t{nullptr} {}

    texture(const texture &) = delete;
    texture &operator=(const texture &) = delete;

    texture(texture &&o) : t{o.t}
    {
        o.t = nullptr;
    }

    inline texture &operator=(texture &&o)
    {
        release();
        t = o.t;
        o.t = nullptr;
        return *this;
    }

    inline texture(SDL_Renderer *renderer, void *image, int width, int height, int depth, int pitch, uint32_t rmask, uint32_t gmask, uint32_t bmask, uint32_t amask) : t{nullptr}
    {
        attach_image(renderer, image, width, height, depth, pitch, rmask, gmask, bmask, amask);
    }

    inline void attach_image(SDL_Renderer *renderer, void *image, int width, int height, int depth, int pitch, uint32_t rmask, uint32_t gmask, uint32_t bmask, uint32_t amask)
    {
        release();

        SDL_Surface *s = SDL_CreateRGBSurfaceFrom(image, width, height, depth, pitch, rmask, gmask, bmask, amask);

        t = SDL_CreateTextureFromSurface(renderer, s);

        SDL_FreeSurface(s);
    }

    inline void draw(SDL_Renderer *renderer, const SDL_Rect *src, const SDL_Rect *dest) const
    {
        if (t)
            SDL_RenderCopyEx(renderer, t, src, dest, 0, nullptr, SDL_FLIP_NONE);
    }

    int width() const
    {
        if(!t) return 0;

        int w;

        SDL_QueryTexture(t, nullptr, nullptr, &w, nullptr);

        return w;
    }

    int height() const {
        if(!t) return 0;
        
        int h;

        SDL_QueryTexture(t, nullptr, nullptr, nullptr, &h);

        return h;
    }

    ~texture()
    {
        release();
    }

private:
    SDL_Texture *t;

    inline void release()
    {
        if (t)
            SDL_DestroyTexture(t);
        t = nullptr;
    }
};

创建字形类

struct character : texture
{
    using texture::texture;
    unsigned int advance;
    int bearing_x;
    int bearing_y;
};

确定字节序并设置掩码

#if SDL_BYTEORDER == SDL_BIG_ENDIAN

#define rmask 0x000000ff
#define gmask 0x0000ff00
#define bmask 0x00ff0000
#define amask 0xff000000

#else

#define rmask 0xff000000
#define gmask 0x00ff0000
#define bmask 0x0000ff00
#define amask 0x000000ff

#endif

创建从FreeType的8位像素深度到SDL2的32位转换器

void convert_8_to_32_depth(std::vector<uint32_t> &res, unsigned char *image, int width, int height)
{
    res.clear();
    res.reserve(width * height);
    for (int y = 0; y < height; ++y)
    {
        for (int x = 0; x < width; ++x)
        {
            if (image[y * width + x])
                res.push_back(amask);
            else
                res.push_back(0);
        }
    }
}

创建字体存储纹理类

struct lib
{
    lib() = default;

    lib(SDL_Renderer *renderer, int height)
    {
        init(renderer, height);
    }

    void init(SDL_Renderer *renderer, int height)
    {
        FT_Library ft;
        if (FT_Init_FreeType(&ft))
        {
            std::cout << "Can't init freetype lib\n";
        }

        FT_Face face;

        //use if you have font data in array
        // if (FT_New_Memory_Face(ft, font_array, std::size(font_array), 0, &face))
        // {
        //  std::cout << "Failed to load font\n";
        // }

        if (FT_New_Face(ft, "location/to/my/font.ttf", 0, &face))
        {
            std::cout << "Failed to load font\n";
        }

        //set size of future glyphs
        FT_Set_Pixel_Sizes(face, 0, height);

        std::vector<uint32_t> image;

        for (unsigned int c = 0; c < 256; ++c)
        {
            //load freetype glyph
            if (FT_Load_Char(face, c, FT_LOAD_RENDER))
            {
                std::cout << "failed to load glyph\n";
            }

            if (face->glyph->bitmap.width)
            {
                ///get image data that works for sdl2
                convert_8_to_32_depth(image, face->glyph->bitmap.buffer, face->glyph->bitmap.width, face->glyph->bitmap.rows);
                chars[c].attach_image(renderer, image.data(), face->glyph->bitmap.width, face->glyph->bitmap.rows,
                                      32, face->glyph->bitmap.width * sizeof(decltype(image)::value_type),
                                      rmask, gmask, bmask, amask);
            }

            chars[c].bearing_x = face->glyph->bitmap_left;
            chars[c].bearing_y = face->glyph->bitmap_top;
            chars[c].advance = face->glyph->advance.x;
        }

        FT_Done_Face(face);
        FT_Done_FreeType(ft);
    }
    character chars[256];
};

打印文本函数

void print_text(SDL_Renderer *renderer, int x, int y, int height, std::string_view text)
{
    static constexpr int default_height = 50;
    
    //store map of each renderer used to avoid creating more libs than neccesary
    static std::map<SDL_Renderer *, lib> l;
    const lib& ts = l.try_emplace(renderer, renderer, default_height).first->second;

    float scale = height / default_height;

    SDL_Rect dest;

    for (auto c : text)
    {

        dest.x = x + ts.chars[c].bearing_x * scale;
        dest.y = y - ts.chars[c].bearing_y * scale;

        dest.w = ts.chars[c].width() * scale;
        dest.h = ts.chars[c].height() * scale;

        ts.chars[c].draw(renderer, nullptr, &dest);

        x += (ts.chars[c].advance >> 6) * scale;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接