Created
February 26, 2025 01:49
-
-
Save marcs-feh/d83458e2bed4ce51f9e6da78dc837da7 to your computer and use it in GitHub Desktop.
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 fnt | |
import "core:mem" | |
import "core:fmt" | |
import "core:math" | |
import "core:time" | |
import rl "vendor:raylib" | |
import ttf "vendor:stb/truetype" | |
FONT_DATA :: #load("jetbrains.ttf", []byte) | |
Color :: [4]u8 | |
Bitmap :: struct { | |
pixels: []Color, | |
width: int, | |
height: int, | |
offset: [2]int, | |
} | |
DistanceField :: struct { | |
values: []u8, | |
width, height: int, | |
offset: [2]int, | |
} | |
Font :: struct { | |
info: ttf.fontinfo, | |
atlas: GlyphAtlas, | |
height: f32, | |
// SDF Rendering parameters | |
padding: i32, | |
edge_value: f32, | |
dist_scale: f32, | |
sharpness: f32, | |
shift: f32, | |
} | |
GLYPHS_PER_ATLAS :: 256 | |
AtlasPos :: struct { | |
atlas_offset: [2]int, | |
glyph_offset: [2]int, | |
width, height: int, | |
} | |
GlyphAtlas :: struct { | |
pixels: []Color, | |
width, height: int, | |
base: rune, | |
glyphs: [GLYPHS_PER_ATLAS]AtlasPos, | |
} | |
bmp_pack_rows :: proc(rects: []Bitmap) -> (container_width: int, container_height: int){ | |
} | |
create_glyph_atlas :: proc(font: ^Font, base: rune) -> (atlas: GlyphAtlas, err: mem.Allocator_Error) { | |
base := mem.align_backward_int(int(base), GLYPHS_PER_ATLAS) | |
bitmaps := [GLYPHS_PER_ATLAS]Bitmap{} | |
for i in 0..<GLYPHS_PER_ATLAS { | |
bitmaps[i] = render_codepoint_bitmap(font, rune(int(base) + 1), context.temp_allocator) or_return | |
atlas.width += bitmaps[i].width | |
atlas.height = max(atlas.height, bitmaps[i].height) | |
} | |
atlas.pixels = make([]Color, atlas.width * atlas.height) | |
return | |
} | |
sdf_activation :: proc "contextless" (x: f32, a: f32, t: f32) -> f32 { | |
return 1.0 / (1 + math.exp(- a * (x - t))) | |
} | |
bitmap_create :: proc(width, height: int, allocator := context.allocator) -> (bm: Bitmap, error: mem.Allocator_Error) { | |
assert(width >= 0 && height >= 0, "Bad width/height") | |
bm.pixels = make([]Color, width * height, allocator) or_return | |
bm.width = width | |
bm.height = height | |
return | |
} | |
bitmap_destroy :: proc(bm: ^Bitmap, allocator := context.allocator){ | |
delete(bm.pixels, allocator) | |
} | |
DEFAULT_PADDING :: 0 | |
DEFAULT_SIG_ALPHA :: 9.25 | |
DEFAULT_EDGE_VALUE :: 0.63 | |
DEFAULT_DIST_SCALE :: 0.61 | |
font_load :: proc(data: []byte, height: f32) -> (font: Font, ok := true) { | |
err : mem.Allocator_Error | |
bool(ttf.InitFont(&font.info, raw_data(data), 0)) or_return | |
font.height = height | |
font.padding = DEFAULT_PADDING | |
font.sharpness = DEFAULT_SIG_ALPHA | |
font.dist_scale = DEFAULT_DIST_SCALE | |
font.edge_value = DEFAULT_EDGE_VALUE | |
ok = err == nil | |
return | |
} | |
font_flush_cache :: proc(font: ^Font){ | |
// for _, &bmap in font.cache { | |
// bitmap_destroy(&bmap) | |
// } | |
// clear(&font.cache) | |
} | |
render_codepoint_sdf :: proc(font: ^Font, codepoint: rune) -> DistanceField { | |
scale := ttf.ScaleForPixelHeight(&font.info, font.height) | |
padding := font.padding | |
on_edge := u8(clamp(0, font.edge_value * 255, 0xff)) | |
dist_scale := (font.edge_value * font.dist_scale) * 255 | |
width, height, x_off, y_off : i32 | |
sdf_pixels := ttf.GetCodepointSDF(&font.info, scale, i32(codepoint), padding, on_edge, dist_scale, | |
&width, &height, &x_off, &y_off) | |
ensure(sdf_pixels != nil, "STB failed to allocate SDF") | |
sdf := DistanceField { | |
values = sdf_pixels[:width * height], | |
width = int(width), | |
height = int(height), | |
offset = {int(x_off), int(y_off)}, | |
} | |
return sdf | |
} | |
render_codepoint_bitmap :: proc(font: ^Font, codepoint: rune, allocator := context.allocator) -> (bmap: Bitmap, err: mem.Allocator_Error) { | |
sdf := render_codepoint_sdf(font, codepoint) | |
defer ttf.FreeSDF(raw_data(sdf.values), nil) | |
bmap = bitmap_create(sdf.width, sdf.height, allocator) or_return | |
bmap.offset = sdf.offset | |
for y in 0..<bmap.height { | |
for x in 0..<bmap.width { | |
v := f32(sdf.values[y * sdf.width + x]) / 255.0 | |
px := sdf_activation(v, font.sharpness, 0.5) * 255 | |
if px > 20 { | |
bmap.pixels[y * bmap.width + x] = u8(px) | |
} | |
} | |
} | |
return | |
} | |
to_rl_texture :: proc(bmap: Bitmap) -> rl.Texture { | |
img := rl.Image{ | |
width = i32(bmap.width), | |
height = i32(bmap.height), | |
data = raw_data(bmap.pixels), | |
mipmaps = 1, | |
format = .UNCOMPRESSED_R8G8B8A8, | |
} | |
tex := rl.LoadTextureFromImage(img) | |
return tex | |
} | |
render_text :: proc(font: ^Font, text: string, origin: [2]int){ | |
x := origin.x | |
line_offset := 0 | |
scale := ttf.ScaleForPixelHeight(&font.info, font.height) | |
for r, i in text { | |
bmap, _ := render_codepoint_bitmap(font, r) | |
tex := get_codepoint_texture(font, r) | |
if r == '\n' { | |
line_offset += int(font.height) + 4 | |
x = origin.x | |
continue | |
} | |
advance, lsb : i32 | |
ttf.GetCodepointHMetrics(&font.info, r, &advance, &lsb) | |
next_i := min(len(text) - 1, i + 1) | |
kern_adv := f32(ttf.GetGlyphKernAdvance(&font.info, i32(r), i32(text[next_i]))) * scale | |
defer x += int(math.round(kern_adv)) | |
x += int(f32(advance) * scale) + int(f32(lsb) * scale) | |
y := origin.y + bmap.offset.y + line_offset | |
TEXTURE_SCALE :: 1 | |
rl.DrawTextureEx(tex, {f32(x) ,f32(y)}, 0, TEXTURE_SCALE, {0xff, 0xff, 0xff, 0xff}) | |
} | |
} | |
texture_cache := make(map[rune]rl.Texture) | |
get_codepoint_texture :: proc(font: ^Font, r: rune) -> rl.Texture { | |
if tex, ok := texture_cache[r]; ok { | |
return tex | |
} | |
else { | |
bmap, _ := render_codepoint_bitmap(font, r) | |
tex = to_rl_texture(bmap) | |
texture_cache[r] = tex | |
return tex | |
} | |
} | |
flush_cache :: proc(font: ^Font){ | |
for _, tex in texture_cache { | |
rl.UnloadTexture(tex) | |
} | |
clear(&texture_cache) | |
// for _, &bmap in font.cache { | |
// bitmap_destroy(&bmap) | |
// } | |
// clear(&font.cache) | |
} | |
main :: proc(){ | |
rl.InitWindow(800, 600, "fntest") | |
rl.SetTargetFPS(60) | |
defer rl.CloseWindow() | |
font, font_ok := font_load(FONT_DATA, 16) | |
ensure(font_ok, "Font is fucked") | |
bmap, _ := render_codepoint_bitmap(&font, 'G') | |
for !rl.WindowShouldClose(){ | |
defer free_all(context.temp_allocator) | |
rl.BeginDrawing() | |
rl.ClearBackground(rl.BLACK) | |
mouse_pos := rl.GetMousePosition() | |
msg := fmt.tprintf("Sig Alpha: %v\nEdge Val: %.3f\nDist Scale: %.3f\nHeight: %.2fpx", font.sharpness, font.edge_value, font.dist_scale, font.height) | |
rl.DrawText(transmute(cstring)raw_data(msg), 20, 320, 18, rl.WHITE) | |
if rl.IsKeyDown(.ONE){ font.sharpness -= 0.25; flush_cache(&font) } | |
if rl.IsKeyDown(.TWO){ font.sharpness += 0.25; flush_cache(&font) } | |
if rl.IsKeyDown(.THREE){ font.edge_value -= 0.01; flush_cache(&font) } | |
if rl.IsKeyDown(.FOUR) { font.edge_value += 0.01; flush_cache(&font) } | |
if rl.IsKeyDown(.FIVE){ font.dist_scale -= 0.01; flush_cache(&font) } | |
if rl.IsKeyDown(.SIX) { font.dist_scale += 0.01; flush_cache(&font) } | |
if rl.IsKeyDown(.SEVEN){ font.height -= 0.5; flush_cache(&font) } | |
if rl.IsKeyDown(.EIGHT){ font.height += 0.5; flush_cache(&font) } | |
// render_text(&font, "The quick brown fox jumped over the lazy dog", {20, 40}) | |
// render_text(&font, "(0 + 1) / 2 * 3 - (4 ^ 5) / ((6 % 7) + 8 - 9)", {20, 80}) | |
// render_text(&font, "Hoje à noite, sem luz, decidi xeretar a quinta gaveta da vovó: achei linguiça, pão e fubá", {20, 120}) | |
// render_text(&font, | |
// `#include <stdio.h> | |
// | |
// int main(){ | |
// printf("Hello, world!\n"); | |
// return 0; | |
// } | |
// `, {20, 160}) | |
rl.EndDrawing() | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment