311 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Markdown
		
	
	
			
		
		
	
	
			311 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Markdown
		
	
	
| # 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()
 | |
| }
 | |
| ```
 |