...
This commit is contained in:
104
ui/font.sx
Normal file
104
ui/font.sx
Normal file
@@ -0,0 +1,104 @@
|
||||
#import "modules/std.sx";
|
||||
#import "ui/types.sx";
|
||||
|
||||
FIRST_CHAR :s32: 32;
|
||||
NUM_CHARS :s32: 96;
|
||||
ATLAS_W :s32: 512;
|
||||
ATLAS_H :s32: 512;
|
||||
|
||||
// Matches stbtt_bakedchar memory layout
|
||||
BakedChar :: struct {
|
||||
x0, y0, x1, y1: u16;
|
||||
xoff, yoff, xadvance: f32;
|
||||
}
|
||||
|
||||
// Matches stbtt_aligned_quad memory layout
|
||||
AlignedQuad :: struct {
|
||||
x0, y0, s0, t0: f32;
|
||||
x1, y1, s1, t1: f32;
|
||||
}
|
||||
|
||||
FontAtlas :: struct {
|
||||
texture_id: u32;
|
||||
font_size: f32;
|
||||
char_data: [*]BakedChar;
|
||||
bitmap: [*]u8;
|
||||
ascent: f32;
|
||||
descent: f32;
|
||||
line_height: f32;
|
||||
|
||||
// Bake font glyphs into a bitmap. Call upload_texture() after GL is ready.
|
||||
init :: (self: *FontAtlas, path: [:0]u8, size: f32) {
|
||||
file_size : s32 = 0;
|
||||
font_data := read_file_bytes(path, @file_size);
|
||||
if xx font_data == 0 {
|
||||
out("Failed to load font: ");
|
||||
out(path);
|
||||
out("\n");
|
||||
return;
|
||||
}
|
||||
|
||||
self.font_size = size;
|
||||
|
||||
// Allocate baked char data (96 entries for ASCII 32..127)
|
||||
self.char_data = xx context.allocator.alloc(xx NUM_CHARS * size_of(BakedChar));
|
||||
|
||||
// Bake font bitmap (512x512 single-channel alpha)
|
||||
bitmap_size : s64 = xx ATLAS_W * xx ATLAS_H;
|
||||
self.bitmap = xx context.allocator.alloc(bitmap_size);
|
||||
stbtt_BakeFontBitmap(font_data, 0, size, self.bitmap, ATLAS_W, ATLAS_H, FIRST_CHAR, NUM_CHARS, xx self.char_data);
|
||||
|
||||
// Get font vertical metrics
|
||||
fontinfo : [256]u8 = ---;
|
||||
stbtt_InitFont(xx @fontinfo, font_data, 0);
|
||||
ascent_i : s32 = 0;
|
||||
descent_i : s32 = 0;
|
||||
linegap_i : s32 = 0;
|
||||
stbtt_GetFontVMetrics(xx @fontinfo, @ascent_i, @descent_i, @linegap_i);
|
||||
scale := stbtt_ScaleForPixelHeight(xx @fontinfo, size);
|
||||
self.ascent = xx ascent_i * scale;
|
||||
self.descent = xx descent_i * scale;
|
||||
self.line_height = self.ascent - self.descent + xx linegap_i * scale;
|
||||
|
||||
font_data_ptr : *void = xx font_data;
|
||||
free(font_data_ptr);
|
||||
|
||||
out("Font loaded: ");
|
||||
out(path);
|
||||
out("\n");
|
||||
}
|
||||
|
||||
measure_text :: (self: *FontAtlas, text: string, font_size: f32) -> Size {
|
||||
if self.char_data == null { return Size.zero(); }
|
||||
scale := font_size / self.font_size;
|
||||
xpos : f32 = 0.0;
|
||||
ypos : f32 = 0.0;
|
||||
q : AlignedQuad = ---;
|
||||
i : s64 = 0;
|
||||
while i < text.len {
|
||||
ch : s32 = xx text[i];
|
||||
if ch >= FIRST_CHAR and ch < FIRST_CHAR + NUM_CHARS {
|
||||
stbtt_GetBakedQuad(xx self.char_data, ATLAS_W, ATLAS_H, ch - FIRST_CHAR, @xpos, @ypos, xx @q, 1);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
Size.{ width = xpos * scale, height = self.line_height * scale };
|
||||
}
|
||||
}
|
||||
|
||||
// Global font atlas pointer for views (Label, Button) to access
|
||||
g_font : *FontAtlas = xx 0;
|
||||
|
||||
set_global_font :: (font: *FontAtlas) {
|
||||
g_font = font;
|
||||
}
|
||||
|
||||
// Convenience measurement function for views
|
||||
measure_text :: (text: string, font_size: f32) -> Size {
|
||||
if xx g_font == 0 {
|
||||
// Fallback approximate measurement
|
||||
scale := font_size / 16.0;
|
||||
return Size.{ width = xx text.len * 8.0 * scale, height = font_size };
|
||||
}
|
||||
g_font.measure_text(text, font_size);
|
||||
}
|
||||
Reference in New Issue
Block a user