diff --git a/vlib/x/ttf/README.md b/vlib/x/ttf/README.md new file mode 100644 index 0000000000..6594b5c916 --- /dev/null +++ b/vlib/x/ttf/README.md @@ -0,0 +1,310 @@ +# TTF font utility +## introduction +This module is designed to perform two main task +- Load the font file +- Render text using a TTF font + +The render system can be single or multiple, for example it is possible to have a bitmap +render and a HW accelerated render. + +## TTF loader +This part of the module do a simple task, load a TTF file and preprocess all the loaded data +in order to simplify the rendering phase. + +Let's start with a simple snippet of code that load a font from the disk: +```v ignore +mut ttf_font := ttf.TTF_File{} +ttf_font.buf = os.read_bytes("arial.ttf") or { panic(err) } +ttf_font.init() +``` +*Note: the font must be passed to the `TTF_file` as RAM buffer.* +At this point the font "arial" is loaded and parsed and if it is a valid TTF font it is +ready for the rendering. +We can get some quick info on the font as string using the `get_info_string` function: + +```v ignore +println(ttf_font.get_info_string()) +``` +that give an outpul like this: +``` +----- Font Info ----- +font_family : Arial +font_sub_family : Normal +full_name : Arial +postscript_name : ArialMT +version : 1 +font_revision : 5.06 +magic_number : 5f0f3cf5 +flags : 81b +created unixTS : 649950890 +modified unixTS : 1282151447 +units_per_em : 2048 +box : [x_min:-1361, y_min:-665, x_Max:4096, y_Max:2060] +mac_style : 0 +----------------------- +``` + +Once loaded a font the `TTF_File` struct is filled with the font data and texts can be rendered. +At high level no more action are required to use the loaded font. +Multiple fonts can be loaded without problems at the same time. + +## TTF Bitmap render +In this modue it is possible to have different renders running at the same time. +At the present time all the rendering are made on the CPU, sokol is used only to draw the +rendered text to the screen. +Let's start with a simple snippet of code: +```v ignore +import os +import x.ttf +[console] +fn main(){ + mut ttf_font := ttf.TTF_File{} + ttf_font.buf = os.read_bytes("arial.ttf") or { panic(err) } + ttf_font.init() + // print font info + println(ttf_font.get_info_string()) +} +``` +This simple code load a TTF font and display its basic informations. + +### draw_text +The draw text function draw simple strings without indentation or other imagination tasks. +At this point we can render a simple text: +```v ignore +import os +import x.ttf + +[console] +fn main(){ + mut ttf_font := ttf.TTF_File{} + ttf_font.buf = os.read_bytes("arial.ttf") or { panic(err) } + ttf_font.init() + // print font info + println(ttf_font.get_info_string()) + + bmp_width := 200 + bmp_heigth := 64 + bmp_layers := 4 // number of planes for an RGBA buffer + // memory size of the buffer + bmp_size := bmp_width * bmp_heigth * bmp_layers + + font_size := 32 // font size in points + device_dpi := 72 // default screen DPI + // Formula for scale calculation + // scaler := (font_size * device dpi) / (72dpi * em_unit) + scale := f32(font_size * device_dpi) / f32(72 * ttf_font.units_per_em) + // height of the font to use in the buffer to separate the lines + y_base := int((ttf_font.y_max - ttf_font.y_min) * scale) + + // declare the bitmap struct + mut bmp:= ttf.BitMap{ + tf : &ttf_font + buf : malloc(bmp_size) + buf_size : bmp_size + width : bmp_width + height : bmp_heigth + bp : bmp_layers + color : 0x000000_FF // RGBA black + scale : scale + } + bmp.init_filler() + bmp.clear() + bmp.set_pos(10,y_base) + bmp.draw_text("Test Text!") + bmp.save_as_ppm("test.ppm") +} +``` +This is the low level render that draw ther text on a bitmap and save the bitmap on a disk as +`.ppm` file. +*Note: The render in this case is a raw rendering without any postfiltering or other processing.* + +Using the low level rendering you need to manage all the amenities like allocate and release +memory and other tasks like calc the character dimensions. + +You can specify the style for the text rendering in the `BitMap` struct:: +```v ignore +enum Style { + outline + outline_aliased + filled // default syle + raw +} +``` +Use this level only if you want achieve particular result on text rendering. + +### draw_text_block +Draw text block draw a justified and indented block of multiline text in the bitmap. +```v ignore +import os +import x.ttf + +[console] +fn main(){ + mut ttf_font := ttf.TTF_File{} + ttf_font.buf = os.read_bytes("arial.ttf") or { panic(err) } + ttf_font.init() + // print font info + println(ttf_font.get_info_string()) + + bmp_width := 200 + bmp_heigth := 200 + bmp_layers := 4 // number of planes for an RGBA buffer + // memory size of the buffer + bmp_size := bmp_width * bmp_heigth * bmp_layers + + font_size := 32 // font size in points + device_dpi := 72 // default screen DPI + // Formula for scale calculation + // scaler := (font_size * device dpi) / (72dpi * em_unit) + scale := f32(font_size * device_dpi) / f32(72 * ttf_font.units_per_em) + // height of the font to use in the buffer to separate the lines + y_base := int((ttf_font.y_max - ttf_font.y_min) * scale) + + text := "Today it is a good day! +Tomorrow I'm not so sure :( +But Vwill prevail for sure, V is the way!! +òàèì@ò!£$%& +" + // declare the bitmap struct + mut bmp:= ttf.BitMap{ + tf : &ttf_font + buf : malloc(bmp_size) + buf_size : bmp_size + width : bmp_width + height : bmp_heigth + bp : bmp_layers + color : 0x000000_FF // RGBA black + scale : scale + } + bmp.init_filler() + bmp.clear() + bmp.justify = true + bmp.align = .left + bmp.draw_text_block(text, {x: 0, y:0, w: bmp_width-20, h: bmp_heigth}) + bmp.save_as_ppm("test.ppm") +} +``` +This is the low level render that draw text block on the bitmap. +A text block is defined from a `Text_block` struct: +```v ignore +struct Text_block { + x int // x postion of the left high corner + y int // y postion of the left high corner + w int // width of the text block + h int // heigth of the text block + cut_lines bool = true // force to cut the line if the length is over the text block width +} +``` +and use the following bitmap fields: +```v ignore + style Style = .filled // default syle + align Text_align = .left // default text align + justify bool // justify text flag, default deactivated + justify_fill_ratio f32 = 0.5 // justify fill ratio, if the ratio of the filled row is >= of this then justify the text +``` + +It is possible to modify these parameters to obtain the desired effect on the text rendering. + +## TTF Sokol render +The sokol render use the bitmap render to create the text and the `gg` functions to render +the text to the screen. +It is mor esimpel to use in a `gg app` that the raw bitmap render. +Each single text rendered need its own reder to be declared, after you can modify it. +Here a simple example of the usage: +```v ignore +import gg +import gx +import sokol.sapp +import sokol.sgl + +import x.ttf +import os + +const ( + win_width = 600 + win_height = 700 + bg_color = gx.white + font_paths = [ + "arial.ttf" + ] +) + +struct App_data { +pub mut: + gg &gg.Context + sg_img C.sg_image + init_flag bool + frame_c int + + tf []ttf.TTF_File + ttf_render []ttf.TTF_render_Sokol +} + +fn my_init(mut app App_data) { + app.init_flag = true +} + +fn draw_frame(mut app &App_data) { + cframe_txt := "Current Frame: $app.frame_c" + + app.gg.begin() + + sgl.defaults() + sgl.matrix_mode_projection() + sgl.ortho(0.0, f32(sapp.width()), f32(sapp.height()), 0.0, -1.0, 1.0) + + // draw text only if the app is already initialized + if app.init_flag == true { + // update the text + mut txt1 := &app.ttf_render[0] + txt1.destroy_texture() + txt1.create_text(cframe_txt ,43) + txt1.create_texture() + txt1.draw_text_bmp(app.gg, 30, 60) + } + app.frame_c++ + app.gg.end() +} + +[console] +fn main(){ + mut app := &App_data{ + gg: 0 + } + + app.gg = gg.new_context({ + width: win_width + height: win_height + use_ortho: true // This is needed for 2D drawing + create_window: true + window_title: 'Test TTF module' + user_data: app + bg_color: bg_color + frame_fn: draw_frame + init_fn: my_init + }) + + // load TTF fonts + for font_path in font_paths { + mut tf := ttf.TTF_File{} + tf.buf = os.read_bytes(font_path) or { panic(err) } + println("TrueTypeFont file [$font_path] len: ${tf.buf.len}") + tf.init() + println(tf.get_info_string()) + app.tf << tf + } + + // TTF render 0 Frame counter + app.ttf_render << &ttf.TTF_render_Sokol { + bmp: &ttf.BitMap{ + tf: &(app.tf[0]) + buf: malloc(32000000) + buf_size: (32000000) + color : 0xFF0000FF + //style: .raw + } + } + + app.gg.run() +} +``` diff --git a/vlib/x/ttf/common.v b/vlib/x/ttf/common.v index aa070e0c30..2f1dabb10d 100644 --- a/vlib/x/ttf/common.v +++ b/vlib/x/ttf/common.v @@ -49,9 +49,50 @@ fn dprintln(txt string){ * Utility * ******************************************************************************/ +// transform the bitmap from one layer to color layers +fn (mut bmp BitMap) format_texture(){ + r := byte(bmp.color >> 24) + g := byte((bmp.color >> 16) & 0xFF) + b := byte((bmp.color >> 8 ) & 0xFF) + a := byte(bmp.color & 0xFF) + + b_r := byte(bmp.bg_color >> 24) + b_g := byte((bmp.bg_color >> 16) & 0xFF) + b_b := byte((bmp.bg_color >> 8 ) & 0xFF) + b_a := byte(bmp.bg_color & 0xFF) + + // trasform buffer in a texture + x := byteptr(bmp.buf) + unsafe{ + mut i := 0 + for i 0 { + x[i+0] = r + x[i+1] = g + x[i+2] = b + // alpha + x[i+3] = byte((a * data) >> 8) + } else { + x[i+0] = b_r + x[i+1] = b_g + x[i+2] = b_b + x[i+3] = b_a + } + i += 4 + } + } +} + // write out a .ppm file pub fn (mut bmp BitMap) save_as_ppm(file_name string) { + tmp_buf := bmp.buf + mut buf := malloc(bmp.buf_size) + unsafe { C.memcpy(buf, tmp_buf, bmp.buf_size) } + bmp.buf = buf + + bmp.format_texture() npixels := bmp.width * bmp.height mut f_out := os.create(file_name) or { panic(err) } f_out.writeln('P3') @@ -60,14 +101,17 @@ fn (mut bmp BitMap) save_as_ppm(file_name string) { for i in 0..npixels { pos := i * bmp.bp unsafe { - c_r := 0xFF - bmp.buf[pos] - c_g := 0xFF - bmp.buf[pos +1 ] - c_b := 0xFF - bmp.buf[pos + 2] + c_r := bmp.buf[pos] + c_g := bmp.buf[pos +1 ] + c_b := bmp.buf[pos + 2] f_out.write_str('${c_r} ${c_g} ${c_b} ') } } f_out.close() + + free(buf) + bmp.buf = tmp_buf } pub diff --git a/vlib/x/ttf/render_sokol_cpu.v b/vlib/x/ttf/render_sokol_cpu.v index 96d3be06ac..d646fa97a6 100644 --- a/vlib/x/ttf/render_sokol_cpu.v +++ b/vlib/x/ttf/render_sokol_cpu.v @@ -32,37 +32,7 @@ pub mut: * ******************************************************************************/ fn (mut tf_skl TTF_render_Sokol) format_texture(){ - r := byte(tf_skl.bmp.color >> 24) - g := byte((tf_skl.bmp.color >> 16) & 0xFF) - b := byte((tf_skl.bmp.color >> 8 ) & 0xFF) - a := byte(tf_skl.bmp.color & 0xFF) - - b_r := byte(tf_skl.bmp.bg_color >> 24) - b_g := byte((tf_skl.bmp.bg_color >> 16) & 0xFF) - b_b := byte((tf_skl.bmp.bg_color >> 8 ) & 0xFF) - b_a := byte(tf_skl.bmp.bg_color & 0xFF) - - // trasform buffer in a texture - x := byteptr(tf_skl.bmp.buf) - unsafe{ - mut i := 0 - for i 0 { - x[i+0] = r - x[i+1] = g - x[i+2] = b - // alpha - x[i+3] = byte((a * data) >> 8) - } else { - x[i+0] = b_r - x[i+1] = b_g - x[i+2] = b_b - x[i+3] = b_a - } - i += 4 - } - } + tf_skl.bmp.format_texture() } pub diff --git a/vlib/x/ttf/text_block.v b/vlib/x/ttf/text_block.v index 7af46d80e4..b547720326 100644 --- a/vlib/x/ttf/text_block.v +++ b/vlib/x/ttf/text_block.v @@ -13,14 +13,13 @@ module ttf **********************************************************************/ pub struct Text_block { - x int - y int + x int // x postion of the left high corner + y int // y postion of the left high corner w int // width of the text block h int // heigth of the text block - cut_lines bool = true + cut_lines bool = true // force to cut the line if the length is over the text block width } - fn (mut dev BitMap) get_justify_space_cw(txt string, w int, block_w int, space_cw int) f32 { num_spaces := txt.count(" ") if num_spaces < 1 { diff --git a/vlib/x/ttf/ttf.v b/vlib/x/ttf/ttf.v index c5b98d193e..388ff0d5af 100644 --- a/vlib/x/ttf/ttf.v +++ b/vlib/x/ttf/ttf.v @@ -558,9 +558,9 @@ fn (mut tf TTF_File) get_fixed() f32 { } fn (mut tf TTF_File) get_string(length int) string { - tmp_pos := tf.pos + tmp_pos := u64(tf.pos) tf.pos += u32(length) - return unsafe{ tos(byteptr(u64(tf.buf.data)+u64(tmp_pos)), length) } + return unsafe{ tos(byteptr(u64(tf.buf.data)+tmp_pos), length) } } fn (mut tf TTF_File) get_unicode_string(length int) string { @@ -586,12 +586,10 @@ fn (mut tf TTF_File) get_unicode_string(length int) string { } fn (mut tf TTF_File) get_date() u64 { - // var macTime = this.getUint32() * 0x100000000 + this.getUint32(); - // utcTime = macTime * 1000 + Date.UTC(1904, 1, 1); - // return new Date(utcTime); - - mac_time := u64( (tf.get_u32()) << 32) + u64(tf.get_u32()) - return mac_time + // get mac time and covert it to unix timestamp + mac_time := (u64(tf.get_u32()) << 32) + u64(tf.get_u32()) + utc_time := mac_time - u64(2082844800) + return utc_time } fn (mut tf TTF_File) calc_checksum(offset u32, length u32) u32 { @@ -1053,23 +1051,25 @@ fn (mut tf TTF_File) next_kern(glyph_index int) (int, int){ * TTF_File Utility * ******************************************************************************/ -/* +pub fn (tf TTF_File) get_info_string() string{ txt := "----- Font Info ----- -version: $tf.version -font_revision: $tf.font_revision -magic_number : ${tf.magic_number.hex()} -flags : ${tf.flags.hex()} -created : ${tf.created} -modified : ${tf.modified} -box : [xm:${tf.x_min}, ym:${tf.y_min}, xM:${tf.x_max}, yM:${tf.y_max}] -mac_style : ${tf.mac_style} +font_family : $tf.font_family +font_sub_family : $tf.font_sub_family +full_name : $tf.full_name +postscript_name : $tf.postscript_name +version : $tf.version +font_revision : $tf.font_revision +magic_number : ${tf.magic_number.hex()} +flags : ${tf.flags.hex()} +created unixTS : ${tf.created} +modified unixTS : ${tf.modified} +box : [x_min:${tf.x_min}, y_min:${tf.y_min}, x_Max:${tf.x_max}, y_Max:${tf.y_max}] +mac_style : ${tf.mac_style} ----------------------- " return txt } -*/ - /****************************************************************************** *