Skip to content

Instantly share code, notes, and snippets.

@Lain62
Last active April 16, 2025 16:26
Show Gist options
  • Save Lain62/2b8151e2eccc3cf2e609ef9aa0ce8c91 to your computer and use it in GitHub Desktop.
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
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)
}
}
@johnblat
Copy link

johnblat commented Apr 15, 2025

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"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment