package e2glyph import "core:fmt" /* Provides text rendering using glfw, stb_truetype for the engine TODO: Optimize for only calculate when there's changes */ import "core:math" import os "core:os" import "core:mem" import gl "vendor:OpenGL" import stbtt "vendor:stb/truetype" rect4 :: struct { l, t, r, b: f32, } GlyphAtlasItem :: struct #packed{ filled: bool, tex_coords: rect4, index: i32, //Baseline to top distance_b2t: i32, } GlyphRectInstance :: struct #packed{ pos: rect4, tex_coords: rect4, color: [4]f32, index: u32, subpixel_shift: f32, } GlyphState :: struct { font_info: stbtt.fontinfo, font_size_pt: f32, vao: u32, program_id: u32, curr, pos : [2]f32, atlas_width, atlas_height: i32, atlas_items: [127]GlyphAtlasItem, atlas_texture: u32, rect_buffer: [dynamic]GlyphRectInstance, rect_instances_vbo: u32, framebuf: u32, width, height : i32, } glyph_delete :: proc(glyph : ^GlyphState) { //Delete the textures gl.DeleteProgram(glyph.program_id) gl.DeleteBuffers(1, &glyph.rect_instances_vbo) gl.DeleteVertexArrays(1, &glyph.vao) gl.DeleteTextures(1, &glyph.atlas_texture) } glyph_init :: proc(glyph: ^GlyphState, font_path : string = "") { ok : bool glyph.program_id, ok = gl.load_shaders_source(#load("./shaders/font_vert.glsl"), #load("./shaders/font_frag.glsl"), ) gl.UseProgram(glyph.program_id) gl.CreateBuffers(1, &glyph.rect_instances_vbo) gl.CreateVertexArrays(1, &glyph.vao) gl.VertexArrayVertexBuffer(glyph.vao, 0, glyph.rect_instances_vbo, 0, size_of(GlyphRectInstance)) gl.VertexArrayBindingDivisor(glyph.vao, 0, 1) gl.EnableVertexArrayAttrib(glyph.vao, 0) // read it from a data source gl.VertexArrayAttribBinding(glyph.vao, 0, 0) // read from data source 1 gl.VertexArrayAttribFormat(glyph.vao, 0, 4, gl.FLOAT, false, 0) gl.EnableVertexArrayAttrib(glyph.vao, 1) // read it from a data source gl.VertexArrayAttribBinding(glyph.vao, 1, 0) // read from data source 1 gl.VertexArrayAttribFormat(glyph.vao, 1, 4, gl.FLOAT, false, u32(offset_of(GlyphRectInstance, tex_coords))) gl.EnableVertexArrayAttrib(glyph.vao, 2) // read it from a data source gl.VertexArrayAttribBinding(glyph.vao, 2, 0) // read from data source 1 gl.VertexArrayAttribFormat(glyph.vao, 2, 4, gl.FLOAT, false, u32(offset_of(GlyphRectInstance, color))) gl.EnableVertexArrayAttrib(glyph.vao, 3) // read it from a data source gl.VertexArrayAttribBinding(glyph.vao, 3, 0) // read from data source 1 gl.VertexArrayAttribFormat(glyph.vao, 3, 1, gl.FLOAT, false, u32(offset_of(GlyphRectInstance, subpixel_shift))) gl.CreateTextures(gl.TEXTURE_RECTANGLE, 1, &glyph.atlas_texture) gl.TextureStorage2D(glyph.atlas_texture, 1, gl.RGB8, glyph.atlas_width, glyph.atlas_height) if !os.exists(font_path) { fmt.eprintln("Font not provided or not exist using default font") /* TODO: use microui's font when no font's provided */ font_data := #load("./mononoki-Regular.ttf", []byte) stbtt.InitFont(&glyph.font_info, &font_data[0], 0) }else { font_data, _ := os.read_entire_file_from_path(font_path, context.allocator) stbtt.InitFont(&glyph.font_info, &font_data[0], 0) } gl.UseProgram(0) } glyph_draw_font :: proc(glyph_state: ^GlyphState, text: string, pos : [2]f32, color : [4]u8, flush : bool = false) { //using app //text := get_string(&gap_buf) coverage_adjustment: f32 = 0.0 text_color: [4]f32 = {f32( color.r)/ 255.0 ,f32(color.g)/255.0, f32(color.b)/255.0, f32(color.a)/255.0} glyph_state.rect_buffer = make([dynamic]GlyphRectInstance, 0, 1000) { //put every glyph in text into rect_buffer //Get the font metrics, the stbtt_ScaleForMappingEmToPixels() and stbtt_GetFontVMetrics() documentation for details //From "Font size in pixels or points" in stb_truetype font_size_px := glyph_state.font_size_pt * 1.3333 font_scale := stbtt.ScaleForMappingEmToPixels(&glyph_state.font_info, font_size_px) font_ascent, font_descent, font_line_gap: i32 = 0, 0, 0 stbtt.GetFontVMetrics(&glyph_state.font_info, &font_ascent, &font_descent, &font_line_gap) line_height := f32(font_ascent - font_descent + font_line_gap) * font_scale baseline := f32(font_ascent) * font_scale //glyph_state.curr.x, glyph_state.curr.y = glyph_state.pos.x, f32(glyph_state.pos.y) + math.round(baseline) prev_codepoint: rune = 0 delete(glyph_state.rect_buffer) glyph_state.rect_buffer = make([dynamic]GlyphRectInstance, 0, 1000) glyph_state.curr = pos glyph_state.curr.y += math.round(baseline) for c, i in text { codepoint := u32(c) if prev_codepoint != 0 { glyph_state.curr.x += f32(stbtt.GetCodepointKernAdvance(&glyph_state.font_info, prev_codepoint, c)) * font_scale } prev_codepoint = c if c == '\n' { glyph_state.curr.x = glyph_state.pos.x glyph_state.curr.y += math.round(line_height) } else if c == '\t' { glyph_state.curr.x += 2 * glyph_state.font_size_pt } else { horizontal_filter_padding, subpixel_positioning_left_padding: i32 = 1, 1 if codepoint >= 127{ return } assert(codepoint < 127) glyph_atlas := glyph_state.atlas_items[codepoint] if !glyph_atlas.filled || flush { glyph_index := stbtt.FindGlyphIndex(&glyph_state.font_info, c) x0, y0, x1, y1: i32 = 0, 0, 0, 0 stbtt.GetGlyphBitmapBox(&glyph_state.font_info, glyph_index, font_scale, font_scale, &x0, &y0, &x1, &y1) glyph_width_px : i32 = x1 - x0 glyph_height_px: i32 = y1 - y0 distance_from_baseline_to_top_px: i32 = -y0 if glyph_width_px > 0 && glyph_height_px > 0 { padded_glyph_width_px : i32 = subpixel_positioning_left_padding + horizontal_filter_padding + glyph_width_px + horizontal_filter_padding padded_glyph_height_px: i32 = glyph_height_px atlas_item_width, atlas_item_height: i32 = 32, 32 atlas_item_x: i32 = i32( codepoint % u32(glyph_state.atlas_width / atlas_item_width), ) * atlas_item_width atlas_item_y: i32 = i32( codepoint / u32(glyph_state.atlas_height / atlas_item_height), ) * atlas_item_height assert(padded_glyph_width_px <= atlas_item_width && padded_glyph_height_px <= atlas_item_height) horizontal_resolution: i32 = 3 bitmap_stride : i32 = atlas_item_width * horizontal_resolution bitmap_size : uint = uint(bitmap_stride * atlas_item_height) glyph_bitmap, _ := mem.alloc_bytes(int(bitmap_size)) glyph_offset_x := (subpixel_positioning_left_padding + horizontal_filter_padding) * horizontal_resolution stbtt.MakeGlyphBitmap( &glyph_state.font_info, &glyph_bitmap[glyph_offset_x], atlas_item_width * horizontal_resolution, atlas_item_height, bitmap_stride, font_scale * f32(horizontal_resolution), font_scale, glyph_index ) atlas_item_bitmap, _ := mem.alloc_bytes(int(bitmap_size)) filter_weights: [5]u8 = {0x08, 0x4D, 0x56, 0x4D, 0x08} for y in 0 ..< padded_glyph_height_px { x_end: i32 = padded_glyph_width_px * horizontal_resolution - 1 for x in 4 ..< x_end { filter_weight_index: i32 = 0 sum: i32 kernel_x_end: i32 = (x == x_end - 1) ? x + 1 : x + 2 for kernel_x in x - 2 ..= kernel_x_end { assert(kernel_x >= 0 && kernel_x < x_end + 1) assert(y >= 0 && y < padded_glyph_height_px) offset: i32 = kernel_x + y * bitmap_stride assert(offset >= 0 && uint(offset) < bitmap_size) sum += i32(i32(glyph_bitmap[offset]) * i32(filter_weights[filter_weight_index])) filter_weight_index += 1 } sum = sum / 255 atlas_item_bitmap[x + y * bitmap_stride] = (sum > 255) ? 255 : u8(sum) } } mem.free_bytes(glyph_bitmap) gl.TextureSubImage2D(glyph_state.atlas_texture, 0, atlas_item_x, atlas_item_y, atlas_item_width, atlas_item_height, gl.RGB, gl.UNSIGNED_BYTE, rawptr(&atlas_item_bitmap[0])) mem.free_bytes(atlas_item_bitmap) glyph_atlas.tex_coords.l = f32(atlas_item_x) glyph_atlas.tex_coords.t = f32(atlas_item_y) glyph_atlas.tex_coords.r = f32(atlas_item_x + padded_glyph_width_px) glyph_atlas.tex_coords.b = f32(atlas_item_y + padded_glyph_height_px) } else { glyph_atlas.tex_coords.l = -1 glyph_atlas.tex_coords.t = -1 glyph_atlas.tex_coords.r = -1 glyph_atlas.tex_coords.b = -1 } glyph_atlas.index = glyph_index glyph_atlas.distance_b2t = distance_from_baseline_to_top_px glyph_atlas.filled = true glyph_state.atlas_items[codepoint] = glyph_atlas } glyph_advance_width, glyph_left_side_bearing: i32 = 0, 0 stbtt.GetGlyphHMetrics(&glyph_state.font_info, glyph_atlas.index, &glyph_advance_width, &glyph_left_side_bearing) if glyph_atlas.tex_coords.l != -1 { glyph_pos_x: f32 = glyph_state.curr.x + (f32(glyph_left_side_bearing) * font_scale) glyph_pos_x_px, glyph_pos_x_subpixel_shift: f32 = math.modf_f32(glyph_pos_x) glyph_pos_y_px: f32 = glyph_state.curr.y - f32(glyph_atlas.distance_b2t) glyph_width_with_horiz_filter_padding: i32 = i32(glyph_atlas.tex_coords.r - glyph_atlas.tex_coords.l) glyph_height: i32 = i32(glyph_atlas.tex_coords.b - glyph_atlas.tex_coords.t) r: GlyphRectInstance r.pos.l = f32(glyph_pos_x_px - f32(subpixel_positioning_left_padding + horizontal_filter_padding)) r.pos.t = f32(glyph_pos_y_px) r.pos.r = f32(glyph_pos_x_px - f32(subpixel_positioning_left_padding + horizontal_filter_padding) + f32(glyph_width_with_horiz_filter_padding)) r.pos.b = f32(glyph_pos_y_px + f32(glyph_height)) r.subpixel_shift = glyph_pos_x_subpixel_shift r.tex_coords = glyph_atlas.tex_coords r.color = text_color r.index = u32(i + 1) append(&glyph_state.rect_buffer, r) } glyph_state.curr.x += f32(glyph_advance_width) * font_scale } } } { if len(text) > 0 { gl.NamedBufferData(glyph_state.rect_instances_vbo, int(len(glyph_state.rect_buffer) * size_of(GlyphRectInstance)), &glyph_state.rect_buffer[0], gl.DYNAMIC_DRAW) } gl.Enable(gl.BLEND) gl.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC1_COLOR) gl.UseProgram( glyph_state.program_id) gl.BindVertexArray( glyph_state.vao) gl.ProgramUniform2f( glyph_state.program_id, 0, f32(glyph_state.width) / 2.0, f32(glyph_state.height) / 2.0) gl.ProgramUniform1ui( glyph_state.program_id, 1, u32(coverage_adjustment)) gl.ProgramUniform1ui( glyph_state.program_id, 2, 0) gl.BindTextureUnit(0, glyph_state.atlas_texture) gl.DrawArraysInstanced(gl.TRIANGLES, 0, 6, i32(len(glyph_state.rect_buffer))) gl.Disable(gl.BLEND) //gl.UseProgram(0) //gl.BindVertexArray(0) gl.InvalidateBufferData(glyph_state.rect_instances_vbo) } }