-
-
Save benob/92ee64d9ffcaa5d3be95edbf4ded55f2 to your computer and use it in GitHub Desktop.
#ifndef __STBTTF_H__ | |
#define __STBTTF_H__ | |
#include <SDL2/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. | |
*/ | |
typedef struct { | |
stbtt_fontinfo* info; | |
stbtt_packedchar* chars; | |
SDL_Texture* atlas; | |
int texture_size; | |
float size; | |
float scale; | |
int ascent; | |
int baseline; | |
} STBTTF_Font; | |
/* Release the memory and textures associated with a font */ | |
void STBTTF_CloseFont(STBTTF_Font* font); | |
/* 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). | |
*/ | |
STBTTF_Font* STBTTF_OpenFontRW(SDL_Renderer* renderer, SDL_RWops* rw, float size); | |
/* Open a TTF font given a filename, for a given renderer and a given font size. | |
* Convinience function which calls STBTTF_OpenFontRW. | |
*/ | |
STBTTF_Font* STBTTF_OpenFont(SDL_Renderer* renderer, const char* filename, float size); | |
/* 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. | |
*/ | |
void STBTTF_RenderText(SDL_Renderer* renderer, STBTTF_Font* font, float x, float y, const char *text); | |
/* Return the length in pixels of a text. | |
* You can get the height of a line by using font->baseline. | |
*/ | |
float STBTTF_MeasureText(STBTTF_Font* font, const char *text); | |
#ifdef STBTTF_IMPLEMENTATION | |
void STBTTF_CloseFont(STBTTF_Font* font) { | |
if(font->atlas) SDL_DestroyTexture(font->atlas); | |
if(font->info) free(font->info); | |
if(font->chars) free(font->chars); | |
free(font); | |
} | |
STBTTF_Font* STBTTF_OpenFontRW(SDL_Renderer* renderer, SDL_RWops* rw, float size) { | |
Sint64 file_size = SDL_RWsize(rw); | |
unsigned char* buffer = malloc(file_size); | |
if(SDL_RWread(rw, buffer, file_size, 1) != 1) return NULL; | |
SDL_RWclose(rw); | |
STBTTF_Font* font = calloc(sizeof(STBTTF_Font), 1); | |
font->info = malloc(sizeof(stbtt_fontinfo)); | |
font->chars = malloc(sizeof(stbtt_packedchar) * 96); | |
if(stbtt_InitFont(font->info, buffer, 0) == 0) { | |
free(buffer); | |
STBTTF_CloseFont(font); | |
return NULL; | |
} | |
// fill bitmap atlas with packed characters | |
unsigned char* bitmap = NULL; | |
font->texture_size = 32; | |
while(1) { | |
bitmap = malloc(font->texture_size * font->texture_size); | |
stbtt_pack_context pack_context; | |
stbtt_PackBegin(&pack_context, bitmap, font->texture_size, font->texture_size, 0, 1, 0); | |
stbtt_PackSetOversampling(&pack_context, 1, 1); | |
if(!stbtt_PackFontRange(&pack_context, buffer, 0, size, 32, 95, font->chars)) { | |
// too small | |
free(bitmap); | |
stbtt_PackEnd(&pack_context); | |
font->texture_size *= 2; | |
} else { | |
stbtt_PackEnd(&pack_context); | |
break; | |
} | |
} | |
// convert bitmap to texture | |
font->atlas = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, font->texture_size, font->texture_size); | |
SDL_SetTextureBlendMode(font->atlas, SDL_BLENDMODE_BLEND); | |
Uint32* pixels = malloc(font->texture_size * font->texture_size * sizeof(Uint32)); | |
static SDL_PixelFormat* format = NULL; | |
if(format == NULL) format = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA32); | |
for(int i = 0; i < font->texture_size * font->texture_size; i++) { | |
pixels[i] = SDL_MapRGBA(format, 0xff, 0xff, 0xff, bitmap[i]); | |
} | |
SDL_UpdateTexture(font->atlas, NULL, pixels, font->texture_size * sizeof(Uint32)); | |
free(pixels); | |
free(bitmap); | |
// setup additional info | |
font->scale = stbtt_ScaleForPixelHeight(font->info, size); | |
stbtt_GetFontVMetrics(font->info, &font->ascent, 0, 0); | |
font->baseline = (int) (font->ascent * font->scale); | |
free(buffer); | |
return font; | |
} | |
STBTTF_Font* STBTTF_OpenFont(SDL_Renderer* renderer, const char* filename, float size) { | |
SDL_RWops *rw = SDL_RWFromFile(filename, "rb"); | |
if(rw == NULL) return NULL; | |
return STBTTF_OpenFontRW(renderer, rw, size); | |
} | |
void STBTTF_RenderText(SDL_Renderer* renderer, STBTTF_Font* font, float x, float y, const char *text) { | |
Uint8 r, g, b, a; | |
SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); | |
SDL_SetTextureColorMod(font->atlas, r, g, b); | |
SDL_SetTextureAlphaMod(font->atlas, a); | |
for(int i = 0; text[i]; i++) { | |
if (text[i] >= 32 && text[i] < 128) { | |
//if(i > 0) x += stbtt_GetCodepointKernAdvance(font->info, text[i - 1], text[i]) * font->scale; | |
stbtt_packedchar* info = &font->chars[text[i] - 32]; | |
SDL_Rect src_rect = {info->x0, info->y0, info->x1 - info->x0, info->y1 - info->y0}; | |
SDL_Rect dst_rect = {x + info->xoff, y + info->yoff, info->x1 - info->x0, info->y1 - info->y0}; | |
SDL_RenderCopy(renderer, font->atlas, &src_rect, &dst_rect); | |
x += info->xadvance; | |
} | |
} | |
} | |
float STBTTF_MeasureText(STBTTF_Font* font, const char *text) { | |
float width = 0; | |
for(int i = 0; text[i]; i++) { | |
if (text[i] >= 32 && text[i] < 128) { | |
//if(i > 0) width += stbtt_GetCodepointKernAdvance(font->info, text[i - 1], text[i]) * font->scale; | |
stbtt_packedchar* info = &font->chars[text[i] - 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.h" | |
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 = STBTTF_OpenFont(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); | |
STBTTF_RenderText(renderer, font, 50, 50, "This is a test"); | |
// render the atlas to check its content | |
//SDL_Rect dest = {0, 0, font->texturesize, font->texturesize}; | |
//SDL_RenderCopy(renderer, font->atlas, &dest, &dest); | |
SDL_RenderPresent(renderer); | |
SDL_Delay(1000 / 60); | |
} | |
STBTTF_CloseFont(font); | |
SDL_Quit(); | |
} | |
*/ | |
#endif | |
#endif |
Thanks for writing this. It's great for getting up-and-running quickly.
I can't thank you enough! This is very helpful.
I've made a version of this code that moves it into a C++ class for anyone that prefers that. Its basically the same code as in this gist, but with malloc/free replaced by new/delete and the functions placed in a class so that you can do:
stbttf::Font font(renderer, "myfont.ttf", 32);
font.render(10, 25, "Hello");
It of course doesn't follow the SDL-style function naming the way this gist does, but someone using C++ might find it useful.
Thanks a lot!
Using this I put together a partial Zig port. Posting it here incase it helps someone else, but warning I barely know C or Zig so use at your own peril :)
Link: https://gist.github.com/silbinarywolf/ef2cf324acf71e5e5930e4af513711d7
Heya,
I noticed a bug where increasing the oversampling with your sample didn't work.
By investigating how stbtt_GetPackedQuad works, I realized that the dest_rect struct didn't use xoff/yoff values.
The fix:
if (text[i] >= 32 && text[i] < 128) {
//if(i > 0) x += stbtt_GetCodepointKernAdvance(font->info, text[i - 1], text[i]) * font->scale;
stbtt_packedchar* info = &font->chars[text[i] - 32];
SDL_Rect src_rect = {info->x0, info->y0, info->x1 - info->x0, info->y1 - info->y0};
SDL_FRect dst_rect = {x + info->xoff, y + info->yoff, info.xoff2 - info.xoff, info.yoff2 - info.yoff};
SDL_RenderCopyF(renderer, font->atlas, &src_rect, &dst_rect);
x += info->xadvance;
}
Allows you to increase the oversampling size, which can improve quality of the rendering for small fonts
stbtt_PackSetOversampling(&pack_context, 2, 2);
Thanks again for this! :)
Thanks this is very helpful.