package draw import "core:log" import sdl "vendor:sdl3" import sdl_ttf "vendor:sdl3/ttf" Font_Id :: u16 Font_Key :: struct { id: Font_Id, size: u16, } Text_Cache :: struct { engine: ^sdl_ttf.TextEngine, font_bytes: [dynamic][]u8, sdl_fonts: map[Font_Key]^sdl_ttf.Font, cache: map[u32]^sdl_ttf.Text, } // Internal for fetching SDL TTF font pointer for rendering get_font :: proc(id: Font_Id, size: u16) -> ^sdl_ttf.Font { assert(int(id) < len(GLOB.text_cache.font_bytes), "Invalid font ID.") key := Font_Key{id, size} font := GLOB.text_cache.sdl_fonts[key] if font == nil { log.debug("Font with id:", id, "and size:", size, "not found. Adding..") font_bytes := GLOB.text_cache.font_bytes[id] if font_bytes == nil { log.panicf("Font must first be registered with register_font before using (id=%d)", id) } font_io := sdl.IOFromConstMem(raw_data(font_bytes[:]), len(font_bytes)) if font_io == nil { log.panicf("Failed to create IOStream for font id=%d: %s", id, sdl.GetError()) } sdl_font := sdl_ttf.OpenFontIO(font_io, true, f32(size)) if sdl_font == nil { log.panicf("Failed to create SDL font for font id=%d size=%d: %s", id, size, sdl.GetError()) } if !sdl_ttf.SetFontSizeDPI(sdl_font, f32(size), 72 * i32(GLOB.dpi_scaling), 72 * i32(GLOB.dpi_scaling)) { log.panicf("Failed to set font DPI for font id=%d size=%d: %s", id, size, sdl.GetError()) } GLOB.text_cache.sdl_fonts[key] = sdl_font return sdl_font } else { return font } } // Returns `false` if there are more than max(u16) fonts register_font :: proc(bytes: []u8) -> (id: Font_Id, ok: bool) #optional_ok { if GLOB.text_cache.engine == nil { log.panicf("Cannot register font: text system not initialized. Call init() first.") } if len(GLOB.text_cache.font_bytes) > int(max(Font_Id)) do return 0, false log.debug("Registering font...") append(&GLOB.text_cache.font_bytes, bytes) return Font_Id(len(GLOB.text_cache.font_bytes) - 1), true } Text :: struct { ref: ^sdl_ttf.Text, position: [2]f32, color: Color, } text :: proc( id: u32, txt: cstring, pos: [2]f32, font_id: Font_Id, font_size: u16 = 44, color: Color = {0, 0, 0, 255}, ) -> Text { sdl_text := GLOB.text_cache.cache[id] if sdl_text == nil { sdl_text = sdl_ttf.CreateText(GLOB.text_cache.engine, get_font(font_id, font_size), txt, 0) if sdl_text == nil { log.panicf("Failed to create SDL text: %s", sdl.GetError()) } GLOB.text_cache.cache[id] = sdl_text } else { //TODO if IDs are always unique and never change the underlying text // can get rid of this if !sdl_ttf.SetTextString(sdl_text, txt, 0) { log.panicf("Failed to update SDL text string: %s", sdl.GetError()) } } return Text{sdl_text, pos, color} } @(private, require_results) init_text_cache :: proc( device: ^sdl.GPUDevice, allocator := context.allocator, ) -> ( text_cache: Text_Cache, ok: bool, ) { log.debug("Initializing text state") if !sdl_ttf.Init() { log.errorf("Failed to initialize SDL_ttf: %s", sdl.GetError()) return text_cache, false } engine := sdl_ttf.CreateGPUTextEngine(device) if engine == nil { log.errorf("Failed to create GPU text engine: %s", sdl.GetError()) sdl_ttf.Quit() return text_cache, false } sdl_ttf.SetGPUTextEngineWinding(engine, .COUNTER_CLOCKWISE) text_cache = Text_Cache { engine = engine, cache = make(map[u32]^sdl_ttf.Text, allocator = allocator), } log.debug("Done initializing text cache") return text_cache, true } destroy_text_cache :: proc() { for _, font in GLOB.text_cache.sdl_fonts { sdl_ttf.CloseFont(font) } delete(GLOB.text_cache.sdl_fonts) delete(GLOB.text_cache.font_bytes) delete(GLOB.text_cache.cache) sdl_ttf.DestroyGPUTextEngine(GLOB.text_cache.engine) sdl_ttf.Quit() }