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