Skip to content

Instantly share code, notes, and snippets.

@silbinarywolf
Last active June 6, 2023 07:26
Show Gist options
  • Save silbinarywolf/ef2cf324acf71e5e5930e4af513711d7 to your computer and use it in GitHub Desktop.
Save silbinarywolf/ef2cf324acf71e5e5930e4af513711d7 to your computer and use it in GitHub Desktop.
Example for using stb_truetype with SDL2 renderer. Only supports ASCII characters. For Zig.
//
// Based on C/C++ code from here: https://gist.github.com/benob/92ee64d9ffcaa5d3be95edbf4ded55f2
//
const std = @import("std");
const stb_truetype = @import("stb_truetype");
const SDL = @import("sdl2");
const Renderer = @import("renderer.zig").Renderer;
const Allocator = std.mem.Allocator;
var globalFormat: [*c]SDL.SDL_PixelFormat = null;
pub const Font = struct {
const Self = @This();
info: stb_truetype.stbtt_fontinfo,
chars: []stb_truetype.stbtt_packedchar,
atlas: ?*SDL.SDL_Texture,
texture_size: i32,
size: f32,
scale: f32,
ascent: i32,
baseline: f32,
pub fn draw(self: *Self, renderer: *Renderer, x: f32, y: f32, text: []const u8) void {
var r: u8 = 0;
var g: u8 = 0;
var b: u8 = 0;
var a: u8 = 0;
_ = SDL.SDL_SetRenderDrawColor(renderer._private.renderer, 0, 0, 0, 255);
_ = SDL.SDL_GetRenderDrawColor(renderer._private.renderer, &r, &g, &b, &a);
_ = SDL.SDL_SetTextureColorMod(self.atlas, r, g, b);
var i: usize = 0;
var xx: f32 = x;
var yy: f32 = y;
while (i < text.len) : (i += 1) {
const c = text[i];
if (c >= 32 and c < 128) {
var info: *stb_truetype.stbtt_packedchar = &self.chars[c - 32];
var src_rect: SDL.SDL_Rect = .{
.x = info.x0,
.y = info.y0,
.w = info.x1 - info.x0,
.h = info.y1 - info.y0,
};
var dst_rect: SDL.SDL_FRect = .{
.x = xx + info.xoff,
.y = yy + info.yoff,
.w = info.xoff2 - info.xoff,
.h = info.yoff2 - info.yoff,
};
_ = SDL.SDL_RenderCopyF(renderer._private.renderer, self.atlas, &src_rect, &dst_rect);
xx += info.xadvance;
if (i + 1 < text.len) {
const nextC = text[i + 1];
const kerning = @intToFloat(f32, stb_truetype.stbtt_GetCodepointKernAdvance(&self.info, c, nextC));
xx += kerning * self.scale;
}
}
}
}
pub fn open(allocator: Allocator, renderer: *Renderer, buffer: [*c]const u8, fontSize: f32) !*Font {
var font = try allocator.create(Font);
font.size = fontSize;
errdefer allocator.destroy(font);
if (stb_truetype.stbtt_InitFont(&font.info, buffer, 0) == 0) {
return error.UnableToInitSTBTrueType;
}
font.chars = try allocator.alloc(stb_truetype.stbtt_packedchar, 96);
errdefer allocator.free(font.chars);
// fill bitmap atlas with packed characters
var bitmap: []u8 = undefined;
defer allocator.free(bitmap);
font.texture_size = 32;
while (true) {
bitmap = try allocator.alloc(u8, @intCast(usize, font.texture_size * font.texture_size));
var pack_context: stb_truetype.stbtt_pack_context = undefined;
if (stb_truetype.stbtt_PackBegin(&pack_context, bitmap.ptr, font.texture_size, font.texture_size, 0, 1, null) == 0) {
return error.FailedToPackBegin;
}
stb_truetype.stbtt_PackSetOversampling(&pack_context, 2, 2);
if (stb_truetype.stbtt_PackFontRange(&pack_context, buffer, 0, fontSize, 32, 95, font.chars.ptr) == 0) {
// too small
allocator.free(bitmap);
stb_truetype.stbtt_PackEnd(&pack_context);
font.texture_size *= 2;
continue;
}
stb_truetype.stbtt_PackEnd(&pack_context);
break;
}
// convert bitmap to texture
font.atlas = SDL.SDL_CreateTexture(renderer._private.renderer, SDL.SDL_PIXELFORMAT_RGBA32, SDL.SDL_TEXTUREACCESS_STATIC, font.texture_size, font.texture_size);
if (font.atlas == null) {
return error.UnableToCreateFontAtlas;
}
_ = SDL.SDL_SetTextureBlendMode(font.atlas, SDL.SDL_BLENDMODE_BLEND);
var pixels: []u32 = try allocator.alloc(u32, @intCast(usize, font.texture_size * font.texture_size));
defer allocator.free(pixels);
var i: usize = 0;
if (globalFormat == null) {
// Returned structure may come from a shared global cache (i.e. not newly allocated), and
// hence should not be modified, especially the palette. Weird errors such as Blit combination not supported may occur.
globalFormat = SDL.SDL_AllocFormat(SDL.SDL_PIXELFORMAT_RGBA32);
}
while (i < font.texture_size * font.texture_size) : (i += 1) {
pixels[i] = SDL.SDL_MapRGBA(globalFormat, 0xff, 0xff, 0xff, bitmap[i]);
}
if (SDL.SDL_UpdateTexture(font.atlas, null, pixels.ptr, font.texture_size * @sizeOf(u32)) != 0) {
return error.FailedToUpdateTextureForFont;
}
// setup additional info
font.scale = stb_truetype.stbtt_ScaleForPixelHeight(&font.info, fontSize);
stb_truetype.stbtt_GetFontVMetrics(&font.info, &font.ascent, 0, 0);
font.baseline = @intToFloat(f32, font.ascent) * font.scale;
return font;
}
pub fn destroy(self: *Self, allocator: Allocator) void {
if (self.atlas == null) {
// do nothing if already freed
return;
}
allocator.free(self.chars);
SDL.SDL_DestroyTexture(self.atlas);
self.atlas = null;
allocator.destroy(self);
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment