Last active
May 17, 2023 13:21
-
-
Save danielytics/840047bb51ce6bd1c8d9aae6529ab11c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef __STBTTF_HPP__ | |
#define __STBTTF_HPP__ | |
#include <SDL.h> | |
#include "stb_rect_pack.h" | |
#include "stb_truetype.h" | |
/* STBTTF: A quick and dirty SDL2 text renderer based on stb_truetype and stdb_rect_pack. | |
* Benoit Favre 2019 | |
* | |
* This header-only addon to the stb_truetype library allows to draw text with SDL2 from | |
* TTF fonts with a similar API to SDL_TTF without the bloat. | |
* The renderer is however limited by the integral positioning of SDL blit functions. | |
* It also does not parse utf8 text and only prints ASCII characters. | |
* | |
* This code is public domain. | |
*/ | |
namespace stbttf { | |
class Font { | |
public: | |
Font (SDL_Renderer* renderer) : | |
m_renderer(renderer), | |
m_info(nullptr), | |
m_chars(nullptr), | |
m_atlas(nullptr) | |
{} | |
Font (SDL_Renderer* renderer, const std::string& filename, float size) : Font(renderer) { | |
if (!open(filename, size)) { | |
throw std::runtime_error(std::string{"Could not open font file: "} + filename); | |
} | |
} | |
~Font (); | |
/* Open a TTF font given a filename, for a given renderer and a given font size. | |
* Convinience function which calls STBTTF_OpenFontRW. | |
*/ | |
bool open (const std::string& filename, float size); | |
/* Open a TTF font given a SDL abstract IO handler, for a given renderer and a given font size. | |
* Returns NULL on failure. The font must be deallocated with STBTTF_CloseFont when not used anymore. | |
* This function creates a texture atlas with prerendered ASCII characters (32-128). | |
*/ | |
bool open (SDL_RWops* rw, float size); | |
bool open () const { return m_info != nullptr; } | |
/* Release the memory and textures associated with a font */ | |
void close (); | |
/* Draw some text using the renderer draw color at location (x, y). | |
* Characters are copied from the texture atlas using the renderer SDL_RenderCopy function. | |
* Since that function only supports integral coordinates, the result is not great. | |
* Only ASCII characters (32 <= c < 128) are supported. Anything outside this range is ignored. | |
*/ | |
bool render (float x, float y, const std::string& text); | |
/* Return the length in pixels of a text. */ | |
float width (const std::string& text); | |
float height () const { return m_baseline; } | |
private: | |
SDL_Renderer* const m_renderer; | |
stbtt_fontinfo* m_info; | |
stbtt_packedchar* m_chars; | |
SDL_Texture* m_atlas; | |
int m_texture_size; | |
float m_size; | |
float m_scale; | |
int m_ascent; | |
int m_baseline; | |
static SDL_PixelFormat* s_format; | |
}; | |
} | |
#ifdef STBTTF_IMPLEMENTATION | |
SDL_PixelFormat* stbttf::Font::s_format = nullptr; | |
stbttf::Font::~Font () { | |
if (m_info) { | |
close(); | |
} | |
} | |
bool stbttf::Font::open (const std::string& filename, float size) { | |
SDL_RWops *rw = SDL_RWFromFile(filename.c_str(), "rb"); | |
if (rw == nullptr) { | |
return false; | |
} | |
return open(rw, size); | |
} | |
bool stbttf::Font::open (SDL_RWops* rw, float size) { | |
Sint64 file_size = SDL_RWsize(rw); | |
auto buffer = new unsigned char[file_size]; | |
if(SDL_RWread(rw, buffer, file_size, 1) != 1) { | |
delete [] buffer; | |
return false; | |
} | |
SDL_RWclose(rw); | |
m_info = new stbtt_fontinfo; | |
m_chars = new stbtt_packedchar[96]; | |
if(stbtt_InitFont(m_info, buffer, 0) == 0) { | |
delete [] buffer; | |
close(); | |
return false; | |
} | |
// fill bitmap atlas with packed characters | |
unsigned char* bitmap = nullptr; | |
m_texture_size = 32; | |
while (1) { | |
bitmap = new unsigned char[m_texture_size * m_texture_size]; | |
stbtt_pack_context pack_context; | |
stbtt_PackBegin(&pack_context, bitmap, m_texture_size, m_texture_size, 0, 1, 0); | |
stbtt_PackSetOversampling(&pack_context, 1, 1); | |
if(!stbtt_PackFontRange(&pack_context, buffer, 0, size, 32, 95, m_chars)) { | |
// too small | |
delete [] bitmap; | |
stbtt_PackEnd(&pack_context); | |
m_texture_size *= 2; | |
} else { | |
stbtt_PackEnd(&pack_context); | |
break; | |
} | |
} | |
// convert bitmap to texture | |
m_atlas = SDL_CreateTexture(m_renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, m_texture_size, m_texture_size); | |
SDL_SetTextureBlendMode(m_atlas, SDL_BLENDMODE_BLEND); | |
Uint32* pixels = new Uint32[m_texture_size * m_texture_size]; | |
if (s_format == nullptr) { | |
s_format = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA32); | |
} | |
for (int i = 0; i < m_texture_size * m_texture_size; i++) { | |
pixels[i] = SDL_MapRGBA(s_format, 0xff, 0xff, 0xff, bitmap[i]); | |
} | |
SDL_UpdateTexture(m_atlas, nullptr, pixels, m_texture_size * sizeof(Uint32)); | |
delete [] pixels; | |
delete [] bitmap; | |
// setup additional info | |
m_scale = stbtt_ScaleForPixelHeight(m_info, size); | |
stbtt_GetFontVMetrics(m_info, &m_ascent, 0, 0); | |
m_baseline = (int) (m_ascent * m_scale); | |
delete [] buffer; | |
return true; | |
} | |
void stbttf::Font::close () { | |
if(m_atlas) { | |
SDL_DestroyTexture(m_atlas); | |
m_atlas = nullptr; | |
} | |
if(m_info) { | |
delete m_info; | |
m_info = nullptr; | |
} | |
if(m_chars) { | |
delete [] m_chars; | |
m_chars = nullptr; | |
} | |
} | |
bool stbttf::Font::render (float x, float y, const std::string& text) { | |
if (m_info == nullptr) { | |
return false; | |
} | |
Uint8 r, g, b, a; | |
SDL_GetRenderDrawColor(m_renderer, &r, &g, &b, &a); | |
SDL_SetTextureColorMod(m_atlas, r, g, b); | |
SDL_SetTextureAlphaMod(m_atlas, a); | |
for (unsigned char ch : text) { | |
if (ch >= 32 && ch < 128) { | |
//if(i > 0) x += stbtt_GetCodepointKernAdvance(font->info, text[i - 1], text[i]) * font->scale; | |
stbtt_packedchar* info = &m_chars[ch - 32]; | |
SDL_Rect src_rect = {info->x0, info->y0, info->x1 - info->x0, info->y1 - info->y0}; | |
SDL_Rect dst_rect = {int(x + info->xoff), int(y + info->yoff), int(info->x1 - info->x0), int(info->y1 - info->y0)}; | |
SDL_RenderCopy(m_renderer, m_atlas, &src_rect, &dst_rect); | |
x += info->xadvance; | |
} | |
} | |
return true; | |
} | |
float stbttf::Font::width (const std::string& text) { | |
if (m_info == nullptr) { | |
return 0; | |
} | |
float width = 0; | |
for (unsigned char ch : text) { | |
if (ch >= 32 && ch < 128) { | |
//if(i > 0) width += stbtt_GetCodepointKernAdvance(font->info, text[i - 1], text[i]) * font->scale; | |
stbtt_packedchar* info = &m_chars[ch - 32]; | |
width += info->xadvance; | |
} | |
} | |
return width; | |
} | |
/******************* | |
* Example program * | |
******************* | |
#include <stdio.h> | |
#define STB_RECT_PACK_IMPLEMENTATION | |
#define STB_TRUETYPE_IMPLEMENTATION | |
#define STBTTF_IMPLEMENTATION | |
#include "stbttf.hpp" | |
int main(int argc, char** argv) { | |
if(argc != 2) { | |
fprintf(stderr, "usage: %s <font>\n", argv[0]); | |
exit(1); | |
} | |
SDL_Init(SDL_INIT_VIDEO); | |
SDL_Window* window = SDL_CreateWindow("stbttf", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL); | |
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); | |
SDL_RenderSetLogicalSize(renderer, 640, 480); | |
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); | |
stbttf::Font font(renderer, argv[1], 32); | |
while(1) { | |
SDL_Event event; | |
while(SDL_PollEvent(&event)) { | |
if(event.type == SDL_QUIT) exit(0); | |
} | |
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); | |
SDL_RenderClear(renderer); | |
// set color and render some text | |
SDL_SetRenderDrawColor(renderer, 128, 0, 0, 255); | |
font.render(50, 50, "This is a test"); | |
SDL_RenderPresent(renderer); | |
SDL_Delay(1000 / 60); | |
} | |
STBTTF_CloseFont(font); | |
SDL_Quit(); | |
} | |
*/ | |
#endif | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment