Last active
April 16, 2025 16:26
-
-
Save Lain62/2b8151e2eccc3cf2e609ef9aa0ce8c91 to your computer and use it in GitHub Desktop.
A quick guide on how to set up stb truetype(stbtt) with sdl3 on odin
This file contains hidden or 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
package sdl3stbtruetypeexample | |
import SDL "vendor:sdl3" | |
import stbtt "vendor:stb/truetype" | |
import "core:strings" | |
Font :: struct { | |
bitmap: []u8, | |
packed_char: []stbtt.packedchar, | |
atlas: ^SDL.Texture, | |
texture_size: i32, | |
line_height: f32 | |
} | |
CharAtStart :: 32 | |
CharAmount :: 95 | |
load_font_to_data :: proc(renderer: ^SDL.Renderer, path: string, data: ^Font, texture_size: i32, line_height: f32) { | |
// dont forget to free temp allocator | |
stream := new(SDL.IOStream, context.temp_allocator) | |
stream = SDL.IOFromFile(strings.clone_to_cstring(path), "rb") | |
size := SDL.GetIOSize(stream) | |
font := make([]u8, size) | |
defer delete(font) | |
if SDL.ReadIO(stream, &font[0], uint(size)) == 0 { | |
SDL.Log("ERROR: Cannot Read file %s properly: %s", path, SDL.GetError()) | |
} | |
data.texture_size = texture_size | |
data.line_height = line_height | |
data.bitmap = make([]u8, data.texture_size * data.texture_size) | |
data.packed_char = make([]stbtt.packedchar, CharAmount) | |
// free allocator from this too | |
pack_context := new(stbtt.pack_context, context.temp_allocator) | |
stbtt.PackBegin(pack_context, &data.bitmap[0], data.texture_size, data.texture_size, 0, 1, nil) | |
stbtt.PackSetOversampling(pack_context, 1, 1) | |
stbtt.PackFontRange(pack_context, &font[0], 0, line_height, CharAtStart, CharAmount, &data.packed_char[0]) | |
stbtt.PackEnd(pack_context) | |
data.atlas = SDL.CreateTexture(renderer, .RGBA32, .STATIC, data.texture_size, data.texture_size) | |
SDL.SetTextureBlendMode(data.atlas, {.BLEND}) | |
pixels := make([]u32, data.texture_size*data.texture_size) | |
defer delete(pixels) | |
format := SDL.GetPixelFormatDetails(.RGBA32) | |
for i := 0; i < int(data.texture_size*data.texture_size); i += 1 { | |
pixels[i] = SDL.MapRGBA(format, nil, 0xFF, 0xFF, 0xFF, data.bitmap[i]) | |
} | |
SDL.UpdateTexture(data.atlas, nil, &pixels[0], data.texture_size * size_of(u32)) | |
} | |
render_font :: proc(renderer: ^SDL.Renderer, font: ^Font, x: f32, y: f32, font_size: f32, text: string) { | |
xpos: f32 = x | |
// + font size here because if not then the thing is off screen for some reason | |
ypos: f32 = y + font_size | |
for i := 0; i < len(text); i += 1 { | |
// +1 here cuz char amount doesnt count the last char | |
if text[i] >= CharAtStart && text[i] < CharAtStart + CharAmount + 1 { | |
info := font.packed_char[text[i] - CharAtStart] | |
src := SDL.FRect{f32(info.x0), f32(info.y0), f32(info.x1) - f32(info.x0), f32(info.y1) - f32(info.y0)} | |
font_scale := font_size / font.line_height | |
dest_w :f32 = f32(info.x1 - info.x0) * font_scale | |
dest_h :f32 = f32(info.y1 - info.y0) * font_scale | |
dest_x :f32 = xpos + f32(info.xoff) * font_scale | |
dest_y :f32 = ypos + f32(info.yoff) * font_scale | |
dst := SDL.FRect{dest_x, dest_y, dest_w, dest_h} | |
SDL.RenderTexture( renderer, | |
font.atlas, | |
&src, | |
&dst ) | |
xpos += f32(info.xadvance) * font_scale | |
} | |
} | |
} | |
main :: proc() { | |
if !SDL.Init({.VIDEO}) { | |
SDL.Log("ERROR: Could not init video %s", SDL.GetError()) | |
} | |
window: ^SDL.Window | |
renderer: ^SDL.Renderer | |
font_data: Font | |
if !SDL.CreateWindowAndRenderer("sdl3stbtruetype-example", 1280, 720, {.OPENGL}, &window, &renderer) { | |
SDL.Log("ERROR: Could not init window and renderer %s", SDL.GetError()) | |
} | |
// put your own font here | |
load_font_to_data(renderer, INSERT_FONT_PATH_HERE, &font_data, 1024, 64) | |
text := "the quick brown fox jumped over the fence" | |
running := true | |
event: SDL.Event | |
for running { | |
for SDL.PollEvent(&event) { | |
#partial switch event.type { | |
case .QUIT: | |
running = false | |
} | |
} | |
SDL.SetRenderDrawColor(renderer, 0xFF,0xFF,0xFF,0xFF) | |
SDL.RenderClear(renderer) | |
SDL.SetTextureColorMod(font_data.atlas, 0, 0, 0) | |
SDL.SetTextureAlphaMod(font_data.atlas, 255) | |
// Renders text | |
render_font(renderer, &font_data, 0, 0, 32, text) | |
// Renders atlas | |
SDL.RenderTexture( renderer, | |
font_data.atlas, | |
nil, | |
&SDL.FRect{0, 45, 512, 512}) | |
SDL.RenderPresent(renderer) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://gist.github.com/johnblat/9e7006315724a03d52f972f139e3587a
I believe this fixes the issue u mentioned in https://gist.github.com/Lain62/2b8151e2eccc3cf2e609ef9aa0ce8c91#file-sdl3stbtruetype-example-odin-L63
// dest_y :f32 = ypos + f32(info.yoff) * font_scale dest_y :f32 = ypos
Edit:
Nevermind, i realized that this will render characters that are smaller (like lowercase letters and periods) all "top-justified"