x.ttf: add README, some improvements (#8157)
							parent
							
								
									5ae55731b9
								
							
						
					
					
						commit
						ee9f9c9d81
					
				|  | @ -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() | ||||
| } | ||||
| ``` | ||||
|  | @ -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<bmp.buf_size { | ||||
| 			data := x[i] | ||||
| 			if data > 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 | ||||
|  |  | |||
|  | @ -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<tf_skl.bmp.buf_size { | ||||
| 			data := x[i] | ||||
| 			if data > 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 | ||||
|  |  | |||
|  | @ -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 { | ||||
|  |  | |||
|  | @ -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 | ||||
| } | ||||
| */ | ||||
| 
 | ||||
| 
 | ||||
| /****************************************************************************** | ||||
| * | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue