// Copyright (c) 2019 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module ft import stbi import glm import gl #flag -I @VROOT/thirdparty/freetype #flag @VROOT/thirdparty/freetype/libfreetype.a #flag darwin -lpng -lbz2 -lz #flag linux -I/usr/include/freetype2 #flag linux -I. #include "ft2build.h" #include FT_FREETYPE_H struct GG { shader gl.Shader // use_ortho bool width int height int VAO u32 rect_vao u32 rect_vbo u32 line_vao u32 line_vbo u32 VBO u32 chars []gg.Character utf_runes []string utf_chars []gg.Character text_ctx *GG face Face scale int // retina = 2 , normal = 1 } struct Character { texture_id u32 size Vec2 bearing Vec2 advance u32 } // jfn ft_load_char(face FT_Face, code FT_ULong) Character { // fn ft_load_char(_face voidptr, _code voidptr) Character { fn ft_load_char(_face Face, code i64) Character { // #FT_Face face = *(FT_Face*)(_face); FT_ULong code = *(FT_ULong*)(code); # FT_Face face = *((FT_Face*)_face.cobj); # if (FT_Load_Char(face, code, FT_LOAD_RENDER)) { println('freetype: Failed to load Glyph') exit(1) } // Generate texture # GLuint texture; # glGenTextures(1, &texture); # glBindTexture(GL_TEXTURE_2D, texture); # glTexImage2D( # GL_TEXTURE_2D, # 0, # GL_RED, # face->glyph->bitmap.width, # face->glyph->bitmap.rows, # 0, # GL_RED, # GL_UNSIGNED_BYTE, # face->glyph->bitmap.buffer # ); // Set texture options # glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); # glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); # glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); # glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Now store character for later use ch := Character{} # ch.texture_id=texture ; # ch.size = gg__vec2(face->glyph->bitmap.width, face->glyph->bitmap.rows); # ch.bearing = gg__vec2(face->glyph->bitmap_left, face->glyph->bitmap_top), # ch.advance = face->glyph->advance.x; return ch } pub fn new_context_text(cfg Cfg, scale int) *GG { // Can only have text in ortho mode if !cfg.use_ortho { return &GG{text_ctx: 0} } mut width := cfg.width * scale mut height := cfg.height * scale font_size := cfg.font_size * scale // exit('fs=$font_size') // if false { // retina // width = width * 2// scale// 2 // height = height * 2// scale// 2 // font_size *= scale// 2 // } /* gl.viewport(0, 0, width, height) */ // gl.enable(GL_CULL_FACE) // TODO NEED CULL? gl.enable(GL_BLEND) return &GG{} // return &GG{} # glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); shader := gl.new_shader('text') shader.use() projection := glm.ortho(0, width, 0, height)// 0 at BOT // projection_new := ortho(0, width, 0, height)// 0 at BOT // projection := gl.ortho(0, width,height,0) // 0 at TOP shader.set_mat4('projection', projection) // FREETYPE # FT_Library ft; // All functions return a value different than 0 whenever an error occurred # if (FT_Init_FreeType(&ft)) println('ERROR::FREETYPE: Could not init FreeType Library') // Load font as face // face := FT_Face{} mut font_path := 'RobotoMono-Regular.ttf' if !os.file_exists(font_path) { exePath := os.getexepath() exeDir := os.basedir(exePath) println('Trying to load from $exeDir') font_path = '${exeDir}/RobotoMono-Regular.ttf' } if !os.file_exists(font_path) { println('failed to load RobotoMono-Regular.ttf') exit(1) } # FT_Face face; # if (FT_New_Face(ft, font_path.str, 0, &face)) // # if (FT_New_Face(ft, "/Library/Fonts/Courier New.ttf", 0, &face)) // # if (FT_New_Face(ft, "/System/Library/Fonts/Apple Color Emoji.ttc", 0, &face)) { println('freetyp: Failed to load font') exit(1) } // Set size to load glyphs as # FT_Set_Pixel_Sizes(face, 0, font_size) ; // Disable byte-alignment restriction # glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Gen texture // Load first 128 characters of ASCII set mut chars := []gg.Character{} f := Face { cobj: 0 } # f.cobj = &face; // # for (GLubyte c = 0; c < 128; c++) for c := 0; c < 128; c++ { // ch := Character{} // ch:=ft_load_char(face, c) // # ch =gg__ft_load_char(&face, &c); // //////////////////////////////// mut ch := ft_load_char(f, i64(c)) // s := utf32_to_str(uint(0x043f)) // s := 'п' // ch = ft_load_char(f, s.utf32_code()) // # ch = gg__ft_load_char(f, 0x043f); // RUS P // # unsigned long c = FT_Get_Char_Index(face, 0x043f ); // # printf("!!!!!!!!! %lu\n", c); // # c = FT_Get_Char_Index(face, 0xd0bf ); // # printf("!!!!!!!!! %lu\n", c); // # ch = gg__ft_load_char(f, 0xd0bf) ; // UTF 8 chars << ch } ch := Character{} // # ch = gg__ft_load_char(f, 0x0000043f); // # ch = gg__ft_load_char(f, 128169); // chars.push(ch) // Configure VAO VAO := gl.gen_vertex_array() println('new gg text context VAO=$VAO') VBO := gl.gen_buffer() gl.bind_vao(VAO) gl.bind_buffer(GL_ARRAY_BUFFER, VBO) // # glBufferData(GL_ARRAY_BUFFER, sizeof(GLf32) * 6 * 4, NULL, GL_DYNAMIC_DRAW); gl.enable_vertex_attrib_array(0) gl.vertex_attrib_pointer(0, 4, GL_FLOAT, false, 4, 0) // # glVertexAttribPointer(0, 4, GL_FLOAT,false, 4 * sizeof(GLf32), 0); // gl.bind_buffer(GL_ARRAY_BUFFER, uint(0)) // # glBindVertexArray(0); mut ctx := &GG { shader: shader, width: width, height: height, scale: scale VAO: VAO, VBO: VBO, chars: chars, face: f text_ctx: 0 } ctx.init_utf8_runes() return ctx } // A dirty hack to implement rendering of cyrillic letters. // All UTF-8 must be supported. fn (ctx mut GG) init_utf8_runes() { s := '≈йцукенгшщзхъфывапролджэячсмитьбюЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ' println(s) us := s.ustring() for i := 0; i < us.len; i++ { _rune := us.at(i) ch := ft_load_char(ctx.face, _rune.utf32_code()) // ctx.utf_rune_map.set(rune, ch) ctx.utf_runes << _rune ctx.utf_chars << ch } } // fn (ctx &GG) render_text(text string, x, y, scale f32, color gx.Color) { pub fn (ctx &GG) draw_text(_x, _y int, text string, cfg gx.TextCfg) { // dont draw non ascii for now /* for i := 0; i < text.len; i++ { c := text[i] if int(c) > 128 { // ctx.text_ctx._draw_text(_x, _y, '[NON ASCII]', cfg) // return } } */ // # glScissor(0,0,300,300); utext := text.ustring_tmp() // utext := text.ustring() ctx.text_ctx._draw_text(_x, _y, utext, cfg) // utext.free() // # glScissor(0,0,ctx->width*2,ctx->height*2); // gl.disable(GL_SCISSOR_TEST)// TODO // #free(text.str); } fn (ctx &GG) draw_text_fast(_x, _y int, text ustring, cfg gx.TextCfg) { ctx.text_ctx._draw_text(_x, _y, text, cfg) } // TODO HACK with second text context // fn (ctx &GG) _draw_text(_x, _y int, text string, cfg gx.TextCfg) { fn (ctx &GG) _draw_text(_x, _y int, utext ustring, cfg gx.TextCfg) { /* if utext.s.contains('on_seg') { println('\nat(0)') println(utext.runes) firstc := utext.at(0) println('drawtext "$utext.s" len=$utext.s.len ulen=$utext.len x=$_x firstc=$firstc') if firstc != ' ' { exit(1) } } */ // println('scale=$ctx.scale size=$cfg.size') if cfg.align == gx.ALIGN_RIGHT { width := utext.len * 7 _x -= width + 10 } x := f32(_x) * ctx.scale// f32(2) // println('y=$_y height=$ctx.height') // _y = _y * int(ctx.scale) //+ 26 _y = _y * int(ctx.scale) + ((cfg.size * ctx.scale) / 2) + 5 * ctx.scale y := f32(ctx.height - _y) color := cfg.color // Activate corresponding render state ctx.shader.use() ctx.shader.set_color('textColor', color) # glActiveTexture(GL_TEXTURE0); gl.bind_vao(ctx.VAO) // Iterate through all characters // utext := text.ustring() for i := 0; i < utext.len; i++ { _rune := utext.at(i) // println('$i => $_rune') mut ch := Character{} if _rune.len == 1 { idx := _rune[0] if idx < 0 || idx >= ctx.chars.len { println('BADE RUNE $_rune') continue } ch = ctx.chars[_rune[0]] } else if _rune.len > 1 { // TODO O(1) use map for j := 0; j < ctx.utf_runes.len; j++ { rune_j := ctx.utf_runes[j] // if string_eq(ctx.utf_runes[j], rune) { if rune_j==_rune { ch = ctx.utf_chars[j] break } } } if ch.size.x == 0 { // continue } // mut c := int(text[i]) // c = 128 // s := 'A' // c := int(s[0]) // ch := ctx.chars[c] xpos := x + f32(ch.bearing.x) * 1 ypos := y - f32(ch.size.y - ch.bearing.y) * 1 w := f32(ch.size.x) * 1 h := f32(ch.size.y) * 1 // Update VBO for each character # GLfloat vertices[6][4] = { # { xpos, ypos + h, 0.0, 0.0 }, # { xpos, ypos, 0.0, 1.0 }, # { xpos + w, ypos, 1.0, 1.0 }, # { xpos, ypos + h, 0.0, 0.0 }, # { xpos + w, ypos, 1.0, 1.0 }, # { xpos + w, ypos + h, 1.0, 0.0 } # }; // t := glfw.get_time() // Render glyph texture over quad // t1 := glfw.get_time() # glBindTexture(GL_TEXTURE_2D, ch.texture_id); // Update content of VBO memory gl.bind_buffer(GL_ARRAY_BUFFER, ctx.VBO) // t2 := glfw.get_time() // # glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); // Be sure to use glBufferSubData and not glBufferData # glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); // t3 := glfw.get_time() // gl.bind_buffer(GL_ARRAY_BUFFER, uint(0)) // t4 := glfw.get_time() // Render quad gl.draw_arrays(GL_TRIANGLES, 0, 6) // t5 := glfw.get_time() // # if (glfw__get_time() - t > 0.001) // { // # printf("do_text = %f '%s' \n", glfw__get_time() - t, text.str); // # printf("t1=%f, t2=%f, t3=%f, t4=%f, t5=%f\n\n\n", t1-t, t2-t1, t3-t2, t4-t3, t5-t4); // } // Now advance cursors for next glyph (note that advance is number of 1/64 pixels) // Bitshift by 6 to get value in pixels (2^6 = 64 (divide amount of 1/64th pixels by 64 to get amount of pixels)) # x += (ch.advance >> 6) * 1; } gl.bind_vao(u32(0)) # glBindTexture(GL_TEXTURE_2D, 0); // runes.free() // #free(runes.data); } pub fn (ctx &GG) draw_text_def(x, y int, text string) { cfg := gx.TextCfg { color: gx.Black, size: DEFAULT_FONT_SIZE, align: gx.ALIGN_LEFT, } ctx.draw_text(x, y, text, cfg) }