// Copyright (c) 2019-2020 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 glm gl filepath ) #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 freebsd -I/usr/local/include/freetype2 #flag freebsd -Wl -L/usr/local/lib #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 fn C.FT_Init_FreeType() voidptr fn C.FT_New_Face() voidptr fn C.FT_Set_Pixel_Sizes() pub const ( default_font_size = 12 ) struct Character { code i64 texture_id u32 size gg.Vec2 horizontal_bearing_px gg.Vec2 horizontal_advance_px u32 vertical_bearing_px gg.Vec2 vertical_advance_px u32 } [typedef] struct C.FT_Library { _z int } pub struct FreeType { 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 face C.FT_Face scale int // retina = 2 , normal = 1 mut: utf_runes []string utf_chars []Character } struct C.Bitmap { width int rows int buffer int } struct C.Advance { x int y int } [typedef] struct C.FT_Glyph_Metrics { width int height int horiBearingX int horiBearingY int horiAdvance int vertBearingX int vertBearingY int vertAdvance int } struct C.Glyph { bitmap Bitmap bitmap_left int bitmap_top int advance Advance metrics FT_Glyph_Metrics } [typedef] struct C.FT_Face { glyph &Glyph family_name charptr style_name charptr } fn C.FT_Load_Char(voidptr, i64, int) int fn ft_load_char(face C.FT_Face, code i64) Character { //println('\nftload_char( code=$code)') //C.printf('face=%p\n', face) //C.printf('cobj=%p\n', _face.cobj) ret := C.FT_Load_Char(face, code, C.FT_LOAD_RENDER|C.FT_LOAD_FORCE_AUTOHINT) //println('ret=$ret') if ret != 0 { println('freetype: failed to load glyph (utf32 code=$code, ' + 'error code=$ret)') return Character{code: code} } // 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) // Create the character return Character { code: code texture_id: u32(texture) size: gg.vec2(fgwidth, fgrows) // Note: 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)) horizontal_bearing_px: gg.vec2(face.glyph.metrics.horiBearingX >> 6, face.glyph.metrics.horiBearingY >> 6) vertical_bearing_px: gg.vec2(face.glyph.metrics.vertBearingX >> 6, face.glyph.metrics.vertBearingY >> 6) // not used for now horizontal_advance_px: face.glyph.metrics.horiAdvance >> 6 vertical_advance_px: face.glyph.metrics.vertAdvance >> 6 } } pub fn new_context(cfg gg.Cfg) &FreeType { scale := cfg.scale // Can only have text in ortho mode if !cfg.use_ortho { return &FreeType{} } width := cfg.width * scale 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{0} // 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.exists(font_path) { exe_path := os.executable() exe_dir := filepath.basedir(exe_path) font_path = '$exe_dir/$font_path' } if !os.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 for c := 0; c < 128; c++ { ch := ft_load_char(face, 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); ctx := &FreeType { shader: shader width: width height: height scale: scale vao: vao vbo: vbo chars: chars face: face } //ctx.init_utf8_runes() return ctx } pub fn (ctx mut FreeType) draw_text(_x, _y int, text string, cfg gx.TextCfg) { //utext := text.ustring_tmp() utext := text.ustring() ctx.private_draw_text(_x, _y, utext, cfg) } fn (ctx mut FreeType) draw_text_fast(_x, _y int, text ustring, cfg gx.TextCfg) { ctx.private_draw_text(_x, _y, text, cfg) } fn (ctx mut FreeType) private_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) wx, wy := ctx.text_size(utext.s) yoffset := if ctx.scale > 1 { 5 /* highdpi */ } else { -1 /* lowdpi */ } // println('scale=$ctx.scale size=$cfg.size') if cfg.align == gx.ALIGN_RIGHT { //width := utext.len * 7 width := wx x -= width + 10 } x *= ctx.scale y *= ctx.scale y += yoffset y = f32(ctx.height) - y //invert y direction 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{} mut found := false if _rune.len == 1 { idx := _rune[0] if idx < 0 || idx >= ctx.chars.len { println('BADE RUNE $_rune') continue } found = true 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] found = true break } } } // A new Unicode character. Load it and cache it. if !found && _rune.len > 0 && _rune[0] > 32 { 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 //exit(1) // continue } xpos := x + f32(ch.horizontal_bearing_px.x) * 1 ypos := y - f32(ch.size.y + wy - ch.horizontal_bearing_px.y) * 1 //ypos := y - wy 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) x += f32(ch.horizontal_advance_px) // Stop drawing if the limit is reached if cfg.max_width > 0 { if x >= cfg.max_width { break } } } gl.bind_vao(u32(0)) C.glBindTexture(C.GL_TEXTURE_2D, 0) } pub fn (ctx mut FreeType) 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) } pub fn (ctx mut FreeType) text_width(s string) int { x, _ := ctx.text_size(s) return x } pub fn (ctx mut FreeType) text_height(s string) int { _, y := ctx.text_size(s) return y } pub fn (ctx mut FreeType) text_size(s string) (int, int) { //t := time.ticks() utext := s.ustring() mut x := u32(0) mut maxy := u32(0) mut _rune := '' mut ch := Character{} for i := 0; i < utext.len; i++ { _rune = utext.at(i) ch = Character{} mut found := false if _rune.len == 1 { idx := _rune[0] if idx < 0 || idx >= ctx.chars.len { println('BADE RUNE $_rune') continue } found = true 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] found = true break } } } if !found && _rune.len > 0 && _rune[0] > 32 { ch = ft_load_char(ctx.face, _rune.utf32_code()) ctx.utf_runes << _rune ctx.utf_chars << ch } x += ch.horizontal_advance_px if maxy < ch.vertical_advance_px { maxy = ch.vertical_advance_px } } //println('text width "$s" = ${time.ticks() - t} ms') //scaled_x := x //scaled_y := maxy scaled_x := int(f64(x)/ctx.scale) scaled_y := int(f64(maxy)/ctx.scale) //println('text_size of "${s}" | x,y: $x,$maxy | scaled_x: ${scaled_x:3d} | scaled_y: ${scaled_y:3d} ') return scaled_x, scaled_y } pub fn (f FT_Face) str() string { return 'FT_Face{ style_name: ${ptr_str(f.style_name)} family_name: ${ptr_str(f.family_name)} }' } pub fn (ac []Character) str() string { mut res := []string for c in ac { res << ' Character{ code: $c.code , texture_id: $c.texture_id }' } return '[\n' + res.join(',\n') + ']' }