Last active
October 7, 2024 09:47
-
-
Save benob/92ee64d9ffcaa5d3be95edbf4ded55f2 to your computer and use it in GitHub Desktop.
Example for using stb_truetype with SDL2 renderer. Only supports ASCII characters.
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_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 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! :)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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:
It of course doesn't follow the SDL-style function naming the way this gist does, but someone using C++ might find it useful.