Skip to content

Instantly share code, notes, and snippets.

@danielytics
Last active May 17, 2023 13:21
Show Gist options
  • Save danielytics/840047bb51ce6bd1c8d9aae6529ab11c to your computer and use it in GitHub Desktop.
Save danielytics/840047bb51ce6bd1c8d9aae6529ab11c to your computer and use it in GitHub Desktop.
#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