Last active
June 6, 2023 07:26
-
-
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.
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
// | |
// 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