// 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 freetype import ( os gx gg stbi glm gl ) #flag windows -I @VROOT/thirdparty/freetype/include #flag windows -L @VROOT/thirdparty/freetype/win64 #flag darwin -I/usr/local/include/freetype2 #flag darwin -I/opt/local/include/freetype2 #flag -lfreetype //#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 const ( DEFAULT_FONT_SIZE = 12 ) struct Character { texture_id u32 size gg.Vec2 bearing gg.Vec2 advance u32 } struct Face { cobj voidptr } [typedef] struct C.FT_Library { } struct Context { 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 []Character utf_runes []string utf_chars []Character face Face scale int // retina = 2 , normal = 1 } struct C.Bitmap { width int rows int buffer int } struct C.Advance { x int } struct C.Glyph { bitmap Bitmap bitmap_left int bitmap_top int advance Advance } [typedef] struct C.FT_Face { glyph *Glyph } fn ft_load_char(_face Face, code i64) Character { //println('ftload_char( code=$code)') //C.printf('face=%p\n', _face) face := FT_Face(_face.cobj) ret := int(C.FT_Load_Char(face, code, C.FT_LOAD_RENDER)) if ret != 0 { println('freetype: failed to load glyph (utf32 code=$code, ' + 'error code=$ret)') return Character{} } // Generate texture mut texture := 0 C.glGenTextures(1, &texture) C.glBindTexture(C.GL_TEXTURE_2D, texture) fgwidth := face.glyph.bitmap.width fgrows := face.glyph.bitmap.rows C.glTexImage2D(C.GL_TEXTURE_2D, 0, C.GL_RED, fgwidth, fgrows, 0, C.GL_RED, C.GL_UNSIGNED_BYTE, face.glyph.bitmap.buffer) // Set texture options C.glTexParameteri(C.GL_TEXTURE_2D, C.GL_TEXTURE_WRAP_S, C.GL_CLAMP_TO_EDGE) C.glTexParameteri(C.GL_TEXTURE_2D, C.GL_TEXTURE_WRAP_T, C.GL_CLAMP_TO_EDGE) C.glTexParameteri(C.GL_TEXTURE_2D, C.GL_TEXTURE_MIN_FILTER, C.GL_LINEAR) C.glTexParameteri(C.GL_TEXTURE_2D, C.GL_TEXTURE_MAG_FILTER, C.GL_LINEAR) fgleft := face.glyph.bitmap_left fgtop := face.glyph.bitmap_top // Create the character return Character { texture_id: u32(texture) size: gg.vec2(int(u32(fgwidth)), int(u32(fgrows))) bearing: gg.vec2(int(u32(fgleft)), int(u32(fgtop))) advance: (u32(face.glyph.advance.x)) } } pub fn new_context(cfg gg.Cfg) *Context { scale := cfg.scale // Can only have text in ortho mode if !cfg.use_ortho { return &Context{} } 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(C.GL_BLEND) C.glBlendFunc(C.GL_SRC_ALPHA, C.GL_ONE_MINUS_SRC_ALPHA) shader := gl.new_shader('text') shader.use() projection := glm.ortho(0, width, 0, height)// 0 at BOT shader.set_mat4('projection', projection) // FREETYPE ft := FT_Library{} // All functions return a value different than 0 whenever // an error occurred mut ret := C.FT_Init_FreeType(&ft) if ret != 0 { panic('freetype: Could not init FreeType Library') } // Load font as face mut font_path := cfg.font_path if font_path == '' { font_path = 'RobotoMono-Regular.ttf' } if !os.file_exists(font_path) { exe_path := os.executable() exe_dir := os.basedir(exe_path) font_path = '$exe_dir/$font_path' } if !os.file_exists(font_path) { println('failed to load $font_path') return 0 } println('Trying to load font from $font_path') face := C.FT_Face{} ret = int(C.FT_New_Face(ft, font_path.str, 0, &face)) if ret != 0 { println('freetype: failed to load the font (error=$ret)') exit(1) } // Set size to load glyphs as C.FT_Set_Pixel_Sizes(face, 0, font_size) // Disable byte-alignment restriction C.glPixelStorei(C.GL_UNPACK_ALIGNMENT, 1) // Gen texture // Load first 128 characters of ASCII set mut chars := []Character{} f := Face { cobj: &face } for c := 0; c < 128; c++ { mut ch := ft_load_char(f, i64(c)) // s := utf32_to_str(uint(0x043f)) // s := 'п' // ch = ft_load_char(f, s.utf32_code()) // # 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{} // 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(C.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, C.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 := &Context { shader: shader width: width height: height scale: scale vao: vao vbo: vbo chars: chars face: f } ctx.init_utf8_runes() return ctx } // A dirty hack to implement rendering of cyrillic letters. // All UTF-8 must be supported. fn (ctx mut Context) init_utf8_runes() { s := '≈≠⩽⩾йцукенгшщзхъфывапролджэячсмитьбюЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ' print('init utf8 runes: ') //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 } } pub fn (ctx mut Context) draw_text(_x, _y int, text string, cfg gx.TextCfg) { //utext := text.ustring_tmp() utext := text.ustring() ctx._draw_text(_x, _y, utext, cfg) } fn (ctx mut Context) draw_text_fast(_x, _y int, text ustring, cfg gx.TextCfg) { ctx._draw_text(_x, _y, text, cfg) } fn (ctx mut Context) _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) } } */ mut x := f32(_x) mut y := f32(_y) // println('scale=$ctx.scale size=$cfg.size') if cfg.align == gx.ALIGN_RIGHT { width := utext.len * 7 x -= width + 10 } 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) C.glActiveTexture(C.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 rune_j==_rune { ch = ctx.utf_chars[j] break } } } if ch.size.x == 0 && _rune.len > 1{ c := _rune[0] println('cant draw rune "$_rune" code=$c, loading') continue ch = ft_load_char(ctx.face, _rune.utf32_code()) println('done loading') ctx.utf_runes << _rune ctx.utf_chars << ch // continue } 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 vertices := [ 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 ] // Render glyph texture over quad C.glBindTexture(C.GL_TEXTURE_2D, ch.texture_id) // Update content of VBO memory gl.bind_buffer(C.GL_ARRAY_BUFFER, ctx.vbo) // glBufferSubData(..) C.glBufferData(C.GL_ARRAY_BUFFER, 96, vertices.data, C.GL_DYNAMIC_DRAW) // Render quad gl.draw_arrays(C.GL_TRIANGLES, 0, 6) // 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 >> u32(6) } gl.bind_vao(u32(0)) C.glBindTexture(C.GL_TEXTURE_2D, 0) } pub fn (ctx mut Context) 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) }