1086 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			V
		
	
	
			
		
		
	
	
			1086 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			V
		
	
	
| module ttf
 | |
| 
 | |
| /**********************************************************************
 | |
| *
 | |
| * TrueTypeFont reader V implementation
 | |
| *
 | |
| * Copyright (c) 2021 Dario Deledda. All rights reserved.
 | |
| * Use of this source code is governed by an MIT license
 | |
| * that can be found in the LICENSE file.
 | |
| *
 | |
| * Note:
 | |
| * - inspired by: http://stevehanov.ca/blog/?id=143 
 | |
| *
 | |
| * TODO: 
 | |
| * - check for unicode > 0xFFFF if supported
 | |
| * - evaluate use a buffer for the points in the glyph
 | |
| **********************************************************************/
 | |
| import strings
 | |
| 
 | |
| /******************************************************************************
 | |
| *
 | |
| * CMAP structs
 | |
| *
 | |
| ******************************************************************************/
 | |
| struct Segment {
 | |
| mut:
 | |
| 	id_range_offset u32
 | |
| 	start_code      u16
 | |
| 	end_code        u16
 | |
| 	id_delta        u16
 | |
| }
 | |
| 
 | |
| struct TrueTypeCmap {
 | |
| mut:
 | |
| 	format   int
 | |
| 	cache    []int = []int{len: 65536, init: -1} // for now we allocate 2^16 charcode
 | |
| 	segments []Segment
 | |
| 	arr      []int
 | |
| }
 | |
| 
 | |
| /******************************************************************************
 | |
| *
 | |
| * TTF_File structs
 | |
| *
 | |
| ******************************************************************************/
 | |
| pub struct TTF_File {
 | |
| pub mut:
 | |
| 	buf                     []byte
 | |
| 	pos                     u32
 | |
| 	length                  u16
 | |
| 	scalar_type             u32
 | |
| 	search_range            u16
 | |
| 	entry_selector          u16
 | |
| 	range_shift             u16
 | |
| 	tables                  map[string]Offset_Table
 | |
| 	version                 f32
 | |
| 	font_revision           f32
 | |
| 	checksum_adjustment     u32
 | |
| 	magic_number            u32
 | |
| 	flags                   u16
 | |
| 	units_per_em            u16
 | |
| 	created                 u64
 | |
| 	modified                u64
 | |
| 	x_min                   f32
 | |
| 	y_min                   f32
 | |
| 	x_max                   f32
 | |
| 	y_max                   f32
 | |
| 	mac_style               u16
 | |
| 	lowest_rec_ppem         u16
 | |
| 	font_direction_hint     i16
 | |
| 	index_to_loc_format     i16
 | |
| 	glyph_data_format       i16
 | |
| 	font_family             string
 | |
| 	font_sub_family         string
 | |
| 	full_name               string
 | |
| 	postscript_name         string
 | |
| 	cmaps                   []TrueTypeCmap
 | |
| 	ascent                  i16
 | |
| 	descent                 i16
 | |
| 	line_gap                i16
 | |
| 	advance_width_max       u16
 | |
| 	min_left_side_bearing   i16
 | |
| 	min_right_side_bearing  i16
 | |
| 	x_max_extent            i16
 | |
| 	caret_slope_rise        i16
 | |
| 	caret_slope_run         i16
 | |
| 	caret_offset            i16
 | |
| 	metric_data_format      i16
 | |
| 	num_of_long_hor_metrics u16
 | |
| 	kern                    []Kern0Table
 | |
| 	// cache
 | |
| 	glyph_cache map[int]Glyph
 | |
| }
 | |
| 
 | |
| pub fn (mut tf TTF_File) init() {
 | |
| 	tf.read_offset_tables()
 | |
| 	tf.read_head_table()
 | |
| 	// dprintln(tf.get_info_string())
 | |
| 	tf.read_name_table()
 | |
| 	tf.read_cmap_table()
 | |
| 	tf.read_hhea_table()
 | |
| 	tf.read_kern_table()
 | |
| 	tf.length = tf.glyph_count()
 | |
| 	dprintln('Number of symbols: $tf.length')
 | |
| 	dprintln('*****************************')
 | |
| }
 | |
| 
 | |
| /******************************************************************************
 | |
| *
 | |
| * TTF_File Glyph Structs
 | |
| *
 | |
| ******************************************************************************/
 | |
| pub struct Point {
 | |
| pub mut:
 | |
| 	x        int
 | |
| 	y        int
 | |
| 	on_curve bool
 | |
| }
 | |
| 
 | |
| struct Gylph_Component {
 | |
| mut:
 | |
| 	points []Point
 | |
| }
 | |
| 
 | |
| // type of glyph
 | |
| const (
 | |
| 	g_type_simple  = u16(1) // simple type
 | |
| 	g_type_complex = u16(2) // compound type
 | |
| )
 | |
| 
 | |
| pub struct Glyph {
 | |
| pub mut:
 | |
| 	g_type             u16 = ttf.g_type_simple
 | |
| 	contour_ends       []u16
 | |
| 	number_of_contours i16
 | |
| 	points             []Point
 | |
| 	x_min              i16
 | |
| 	x_max              i16
 | |
| 	y_min              i16
 | |
| 	y_max              i16
 | |
| 	valid_glyph        bool
 | |
| 	components         []Component
 | |
| }
 | |
| 
 | |
| /******************************************************************************
 | |
| *
 | |
| * TTF_File metrics and glyph
 | |
| *
 | |
| ******************************************************************************/
 | |
| pub fn (mut tf TTF_File) get_horizontal_metrics(glyph_index u16) (int, int) {
 | |
| 	assert 'hmtx' in tf.tables
 | |
| 	old_pos := tf.pos
 | |
| 	mut offset := tf.tables['hmtx'].offset
 | |
| 
 | |
| 	mut advance_width := 0
 | |
| 	mut left_side_bearing := 0
 | |
| 	if glyph_index < tf.num_of_long_hor_metrics {
 | |
| 		offset += glyph_index * 4
 | |
| 		tf.pos = offset
 | |
| 		advance_width = tf.get_u16()
 | |
| 		left_side_bearing = tf.get_i16()
 | |
| 		// dprintln("Found h_metric aw: $advance_width lsb: $left_side_bearing")
 | |
| 	} else {
 | |
| 		// read the last entry of the hMetrics array
 | |
| 		tf.pos = offset + (tf.num_of_long_hor_metrics - 1) * 4
 | |
| 		advance_width = tf.get_u16()
 | |
| 		tf.pos = offset + tf.num_of_long_hor_metrics * 4 +
 | |
| 			2 * (glyph_index - tf.num_of_long_hor_metrics)
 | |
| 		left_side_bearing = tf.get_fword()
 | |
| 	}
 | |
| 	tf.pos = old_pos
 | |
| 	return advance_width, left_side_bearing
 | |
| }
 | |
| 
 | |
| fn (mut tf TTF_File) get_glyph_offset(index u32) u32 {
 | |
| 	// check if needed tables exists
 | |
| 	assert 'loca' in tf.tables
 | |
| 	assert 'glyf' in tf.tables
 | |
| 	mut old_pos := tf.pos
 | |
| 
 | |
| 	table := tf.tables['loca']
 | |
| 	mut offset := u32(0)
 | |
| 	mut next := u32(0)
 | |
| 	if tf.index_to_loc_format == 1 {
 | |
| 		tf.pos = table.offset + (index << 2)
 | |
| 		offset = tf.get_u32()
 | |
| 		next = tf.get_u32()
 | |
| 	} else {
 | |
| 		tf.pos = table.offset + (index << 1)
 | |
| 		offset = tf.get_u16() << 1
 | |
| 		next = tf.get_u16() << 1
 | |
| 	}
 | |
| 
 | |
| 	if offset == next {
 | |
| 		// indicates glyph has no outline( eg space)
 | |
| 		return 0
 | |
| 	}
 | |
| 	// dprintln("Offset for glyph index $index is $offset")
 | |
| 	tf.pos = old_pos
 | |
| 	return offset + tf.tables['glyf'].offset
 | |
| }
 | |
| 
 | |
| fn (mut tf TTF_File) glyph_count() u16 {
 | |
| 	assert 'maxp' in tf.tables
 | |
| 	old_pos := tf.pos
 | |
| 	tf.pos = tf.tables['maxp'].offset + 4
 | |
| 	count := tf.get_u16()
 | |
| 	tf.pos = old_pos
 | |
| 	return count
 | |
| }
 | |
| 
 | |
| pub fn (mut tf TTF_File) read_glyph_dim(index u16) (int, int, int, int) {
 | |
| 	offset := tf.get_glyph_offset(index)
 | |
| 	// dprintln("offset: $offset")
 | |
| 	if offset == 0 || offset >= tf.tables['glyf'].offset + tf.tables['glyf'].length {
 | |
| 		dprintln('No glyph found!')
 | |
| 		return 0, 0, 0, 0
 | |
| 	}
 | |
| 
 | |
| 	assert offset >= tf.tables['glyf'].offset
 | |
| 	assert offset < tf.tables['glyf'].offset + tf.tables['glyf'].length
 | |
| 
 | |
| 	tf.pos = offset
 | |
| 	// dprintln("file seek read_glyph: $tf.pos")
 | |
| 
 | |
| 	// number_of_contours
 | |
| 	_ := tf.get_i16()
 | |
| 	x_min := tf.get_fword()
 | |
| 	y_min := tf.get_fword()
 | |
| 	x_max := tf.get_fword()
 | |
| 	y_max := tf.get_fword()
 | |
| 
 | |
| 	return x_min, x_max, y_min, y_max
 | |
| }
 | |
| 
 | |
| pub fn (mut tf TTF_File) read_glyph(index u16) Glyph {
 | |
| 	index_int := int(index) // index.str()
 | |
| 	if index_int in tf.glyph_cache {
 | |
| 		// dprintln("Found glyp: ${index}")
 | |
| 		return tf.glyph_cache[index_int]
 | |
| 	}
 | |
| 	// dprintln("Create glyp: ${index}")
 | |
| 
 | |
| 	offset := tf.get_glyph_offset(index)
 | |
| 	// dprintln("offset: $offset")
 | |
| 	if offset == 0 || offset >= tf.tables['glyf'].offset + tf.tables['glyf'].length {
 | |
| 		dprintln('No glyph found!')
 | |
| 		return Glyph{}
 | |
| 	}
 | |
| 
 | |
| 	assert offset >= tf.tables['glyf'].offset
 | |
| 	assert offset < tf.tables['glyf'].offset + tf.tables['glyf'].length
 | |
| 
 | |
| 	tf.pos = offset
 | |
| 	// dprintln("file seek read_glyph: $tf.pos")
 | |
| 
 | |
| 	/*
 | |
| 	---- BUG TO SOLVE -----
 | |
| 	--- Order of the data if printed in the main is shuffled!! Very Strange
 | |
| 	mut tmp_glyph := Glyph{
 | |
| 		number_of_contours : tf.get_i16()
 | |
| 		x_min : tf.get_fword()
 | |
| 		y_min : tf.get_fword()
 | |
| 		x_max : tf.get_fword()
 | |
| 		y_max : tf.get_fword()
 | |
| 	}
 | |
| 	*/
 | |
| 
 | |
| 	mut tmp_glyph := Glyph{}
 | |
| 	tmp_glyph.number_of_contours = tf.get_i16()
 | |
| 	tmp_glyph.x_min = tf.get_fword()
 | |
| 	tmp_glyph.y_min = tf.get_fword()
 | |
| 	tmp_glyph.x_max = tf.get_fword()
 | |
| 	tmp_glyph.y_max = tf.get_fword()
 | |
| 
 | |
| 	// dprintln("file seek after read_glyph: $tf.pos")
 | |
| 
 | |
| 	assert tmp_glyph.number_of_contours >= -1
 | |
| 
 | |
| 	if tmp_glyph.number_of_contours == -1 {
 | |
| 		// dprintln("read_compound_glyph")
 | |
| 		tf.read_compound_glyph(mut tmp_glyph)
 | |
| 	} else {
 | |
| 		// dprintln("read_simple_glyph")
 | |
| 		tf.read_simple_glyph(mut tmp_glyph)
 | |
| 	}
 | |
| 
 | |
| 	tf.glyph_cache[index_int] = tmp_glyph
 | |
| 	return tmp_glyph
 | |
| }
 | |
| 
 | |
| const (
 | |
| 	tfk_on_curve  = 1
 | |
| 	tfk_x_is_byte = 2
 | |
| 	tfk_y_is_byte = 4
 | |
| 	tfk_repeat    = 8
 | |
| 	tfk_x_delta   = 16
 | |
| 	tfk_y_delta   = 32
 | |
| )
 | |
| 
 | |
| fn (mut tf TTF_File) read_simple_glyph(mut in_glyph Glyph) {
 | |
| 	if in_glyph.number_of_contours == 0 {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	for _ in 0 .. in_glyph.number_of_contours {
 | |
| 		in_glyph.contour_ends << tf.get_u16()
 | |
| 	}
 | |
| 
 | |
| 	// skip over intructions
 | |
| 	tf.pos = tf.get_u16() + tf.pos
 | |
| 
 | |
| 	mut num_points := 0
 | |
| 	for ce in in_glyph.contour_ends {
 | |
| 		if ce > num_points {
 | |
| 			num_points = ce
 | |
| 		}
 | |
| 	}
 | |
| 	num_points++
 | |
| 
 | |
| 	mut i := 0
 | |
| 	mut flags := []byte{}
 | |
| 	for i < num_points {
 | |
| 		flag := tf.get_u8()
 | |
| 		flags << flag
 | |
| 		in_glyph.points << Point{
 | |
| 			x: 0
 | |
| 			y: 0
 | |
| 			on_curve: (flag & ttf.tfk_on_curve) > 0
 | |
| 		}
 | |
| 		if (flag & ttf.tfk_repeat) > 0 {
 | |
| 			mut repeat_count := tf.get_u8()
 | |
| 			assert repeat_count > 0
 | |
| 			i += repeat_count
 | |
| 			for repeat_count > 0 {
 | |
| 				flags << flag
 | |
| 				in_glyph.points << Point{
 | |
| 					x: 0
 | |
| 					y: 0
 | |
| 					on_curve: (flag & ttf.tfk_on_curve) > 0
 | |
| 				}
 | |
| 				repeat_count--
 | |
| 			}
 | |
| 		}
 | |
| 		i++
 | |
| 	}
 | |
| 
 | |
| 	// read coords x
 | |
| 	mut value := 0
 | |
| 	for i_x in 0 .. num_points {
 | |
| 		flag_x := flags[i_x]
 | |
| 		if (flag_x & ttf.tfk_x_is_byte) > 0 {
 | |
| 			if (flag_x & ttf.tfk_x_delta) > 0 {
 | |
| 				value += tf.get_u8()
 | |
| 			} else {
 | |
| 				value -= tf.get_u8()
 | |
| 			}
 | |
| 		} else if (~flag_x & ttf.tfk_x_delta) > 0 {
 | |
| 			value += tf.get_i16()
 | |
| 		} else {
 | |
| 			// value is unchanged
 | |
| 		}
 | |
| 		// dprintln("$i_x x: $value")
 | |
| 		in_glyph.points[i_x].x = value
 | |
| 	}
 | |
| 
 | |
| 	// read coords y
 | |
| 	value = 0
 | |
| 	for i_y in 0 .. num_points {
 | |
| 		flag_y := flags[i_y]
 | |
| 		if (flag_y & ttf.tfk_y_is_byte) > 0 {
 | |
| 			if (flag_y & ttf.tfk_y_delta) > 0 {
 | |
| 				value += tf.get_u8()
 | |
| 			} else {
 | |
| 				value -= tf.get_u8()
 | |
| 			}
 | |
| 		} else if (~flag_y & ttf.tfk_y_delta) > 0 {
 | |
| 			value += tf.get_i16()
 | |
| 		} else {
 | |
| 			// value is unchanged
 | |
| 		}
 | |
| 		// dprintln("$i_y y: $value")
 | |
| 		in_glyph.points[i_y].y = value
 | |
| 	}
 | |
| 
 | |
| 	// ok we have a valid glyph
 | |
| 	in_glyph.valid_glyph = true
 | |
| }
 | |
| 
 | |
| const (
 | |
| 	tfkc_arg_1_and_2_are_words    = 1
 | |
| 	tfkc_args_are_xy_values       = 2
 | |
| 	tfkc_round_xy_to_grid         = 4
 | |
| 	tfkc_we_have_a_scale          = 8
 | |
| 	// reserved                   = 16
 | |
| 	tfkc_more_components          = 32
 | |
| 	tfkc_we_have_an_x_and_y_scale = 64
 | |
| 	tfkc_we_have_a_two_by_two     = 128
 | |
| 	tfkc_we_have_instructions     = 256
 | |
| 	tfkc_use_my_metrics           = 512
 | |
| 	tfkc_overlap_component        = 1024
 | |
| )
 | |
| 
 | |
| struct Component {
 | |
| mut:
 | |
| 	glyph_index      u16
 | |
| 	dest_point_index i16
 | |
| 	src_point_index  i16
 | |
| 	matrix           []f32 = [f32(1.0), 0, 0, 1.0, 0, 0]
 | |
| }
 | |
| 
 | |
| fn (mut tf TTF_File) read_compound_glyph(mut in_glyph Glyph) {
 | |
| 	in_glyph.g_type = ttf.g_type_complex
 | |
| 	mut component := Component{}
 | |
| 	mut flags := ttf.tfkc_more_components
 | |
| 	for (flags & ttf.tfkc_more_components) > 0 {
 | |
| 		mut arg1 := i16(0)
 | |
| 		mut arg2 := i16(0)
 | |
| 
 | |
| 		flags = tf.get_u16()
 | |
| 
 | |
| 		component.glyph_index = tf.get_u16()
 | |
| 
 | |
| 		if (flags & ttf.tfkc_arg_1_and_2_are_words) > 0 {
 | |
| 			arg1 = tf.get_i16()
 | |
| 			arg2 = tf.get_i16()
 | |
| 		} else {
 | |
| 			arg1 = tf.get_u8()
 | |
| 			arg2 = tf.get_u8()
 | |
| 		}
 | |
| 
 | |
| 		if (flags & ttf.tfkc_args_are_xy_values) > 0 {
 | |
| 			component.matrix[4] = arg1
 | |
| 			component.matrix[5] = arg2
 | |
| 		} else {
 | |
| 			component.dest_point_index = arg1
 | |
| 			component.src_point_index = arg2
 | |
| 		}
 | |
| 
 | |
| 		if (flags & ttf.tfkc_we_have_a_scale) > 0 {
 | |
| 			component.matrix[0] = tf.get_2dot14()
 | |
| 			component.matrix[3] = component.matrix[0]
 | |
| 		} else if (flags & ttf.tfkc_we_have_an_x_and_y_scale) > 0 {
 | |
| 			component.matrix[0] = tf.get_2dot14()
 | |
| 			component.matrix[3] = tf.get_2dot14()
 | |
| 		} else if (flags & ttf.tfkc_we_have_a_two_by_two) > 0 {
 | |
| 			component.matrix[0] = tf.get_2dot14()
 | |
| 			component.matrix[1] = tf.get_2dot14()
 | |
| 			component.matrix[2] = tf.get_2dot14()
 | |
| 			component.matrix[3] = tf.get_2dot14()
 | |
| 		}
 | |
| 		// dprintln("Read component glyph index ${component.glyph_index}")
 | |
| 		// dprintln("Transform: ${component.matrix}")
 | |
| 
 | |
| 		old_pos := tf.pos
 | |
| 
 | |
| 		simple_glyph := tf.read_glyph(component.glyph_index)
 | |
| 		if simple_glyph.valid_glyph {
 | |
| 			point_offset := in_glyph.points.len
 | |
| 			for i in 0 .. simple_glyph.contour_ends.len {
 | |
| 				in_glyph.contour_ends << u16(simple_glyph.contour_ends[i] + point_offset)
 | |
| 			}
 | |
| 
 | |
| 			for p in simple_glyph.points {
 | |
| 				mut x := f32(p.x)
 | |
| 				mut y := f32(p.y)
 | |
| 				x = component.matrix[0] * x + component.matrix[1] * y + component.matrix[4]
 | |
| 				y = component.matrix[2] * x + component.matrix[3] * y + component.matrix[5]
 | |
| 				in_glyph.points << Point{
 | |
| 					x: int(x)
 | |
| 					y: int(y)
 | |
| 					on_curve: p.on_curve
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		tf.pos = old_pos
 | |
| 	}
 | |
| 
 | |
| 	in_glyph.number_of_contours = i16(in_glyph.contour_ends.len)
 | |
| 
 | |
| 	if (flags & ttf.tfkc_we_have_instructions) > 0 {
 | |
| 		tf.pos = tf.get_u16() + tf.pos
 | |
| 	}
 | |
| 	// ok we have a valid glyph
 | |
| 	in_glyph.valid_glyph = true
 | |
| }
 | |
| 
 | |
| /******************************************************************************
 | |
| *
 | |
| * TTF_File get functions
 | |
| *
 | |
| ******************************************************************************/
 | |
| fn (mut tf TTF_File) get_u8() byte {
 | |
| 	x := tf.buf[tf.pos]
 | |
| 	tf.pos++
 | |
| 	return byte(x)
 | |
| }
 | |
| 
 | |
| fn (mut tf TTF_File) get_i8() i8 {
 | |
| 	return i8(tf.get_u8())
 | |
| }
 | |
| 
 | |
| fn (mut tf TTF_File) get_u16() u16 {
 | |
| 	x := u16(tf.buf[tf.pos] << u16(8)) | u16(tf.buf[tf.pos + 1])
 | |
| 	tf.pos += 2
 | |
| 	return x
 | |
| }
 | |
| 
 | |
| fn (mut tf TTF_File) get_ufword() u16 {
 | |
| 	return tf.get_u16()
 | |
| }
 | |
| 
 | |
| fn (mut tf TTF_File) get_i16() i16 {
 | |
| 	return i16(tf.get_u16())
 | |
| }
 | |
| 
 | |
| fn (mut tf TTF_File) get_fword() i16 {
 | |
| 	return tf.get_i16()
 | |
| }
 | |
| 
 | |
| fn (mut tf TTF_File) get_u32() u32 {
 | |
| 	x := (u32(tf.buf[tf.pos]) << u32(24)) | (u32(tf.buf[tf.pos + 1]) << u32(16)) | (u32(tf.buf[
 | |
| 		tf.pos + 2]) << u32(8)) | u32(tf.buf[tf.pos + 3])
 | |
| 	tf.pos += 4
 | |
| 	return x
 | |
| }
 | |
| 
 | |
| fn (mut tf TTF_File) get_i32() int {
 | |
| 	return int(tf.get_u32())
 | |
| }
 | |
| 
 | |
| fn (mut tf TTF_File) get_2dot14() f32 {
 | |
| 	return f32(tf.get_i16()) / f32(i16(1 << 14))
 | |
| }
 | |
| 
 | |
| fn (mut tf TTF_File) get_fixed() f32 {
 | |
| 	return f32(tf.get_i32() / f32(1 << 16))
 | |
| }
 | |
| 
 | |
| fn (mut tf TTF_File) get_string(length int) string {
 | |
| 	tmp_pos := u64(tf.pos)
 | |
| 	tf.pos += u32(length)
 | |
| 	return unsafe { tos(byteptr(u64(tf.buf.data) + tmp_pos), length) }
 | |
| }
 | |
| 
 | |
| fn (mut tf TTF_File) get_unicode_string(length int) string {
 | |
| 	mut tmp_txt := strings.new_builder(length)
 | |
| 	mut real_len := 0
 | |
| 
 | |
| 	for _ in 0 .. (length >> 1) {
 | |
| 		c := tf.get_u16()
 | |
| 		c_len := ((0xe5000000 >> ((c >> 3) & 0x1e)) & 3) + 1
 | |
| 		real_len += c_len
 | |
| 		if c_len == 1 {
 | |
| 			tmp_txt.write_b(byte(c & 0xff))
 | |
| 		} else {
 | |
| 			tmp_txt.write_b(byte((c >> 8) & 0xff))
 | |
| 			tmp_txt.write_b(byte(c & 0xff))
 | |
| 		}
 | |
| 		// dprintln("c: ${c:c}|${ byte(c &0xff) :c} c_len: ${c_len} str_len: ${real_len} in_len: ${length}")
 | |
| 	}
 | |
| 	tf.pos += u32(real_len)
 | |
| 	res_txt := tmp_txt.str()
 | |
| 	// dprintln("get_unicode_string: ${res_txt}")
 | |
| 	return res_txt
 | |
| }
 | |
| 
 | |
| fn (mut tf TTF_File) get_date() u64 {
 | |
| 	// 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 {
 | |
| 	old_index := tf.pos
 | |
| 	mut sum := u64(0)
 | |
| 	mut nlongs := int((length + 3) >> 2)
 | |
| 	tf.pos = offset
 | |
| 	// dprintln("offs: $offset nlongs: $nlongs")
 | |
| 	for nlongs > 0 {
 | |
| 		sum = sum + u64(tf.get_u32())
 | |
| 		nlongs--
 | |
| 	}
 | |
| 	tf.pos = old_index
 | |
| 	return u32(sum & u64(0xffff_ffff))
 | |
| }
 | |
| 
 | |
| /******************************************************************************
 | |
| *
 | |
| * Offset_Table
 | |
| *
 | |
| ******************************************************************************/
 | |
| struct Offset_Table {
 | |
| mut:
 | |
| 	checksum u32
 | |
| 	offset   u32
 | |
| 	length   u32
 | |
| }
 | |
| 
 | |
| fn (mut tf TTF_File) read_offset_tables() {
 | |
| 	dprintln('*** READ TABLES OFFSET ***')
 | |
| 	tf.pos = 0
 | |
| 	tf.scalar_type = tf.get_u32()
 | |
| 	num_tables := tf.get_u16()
 | |
| 	tf.search_range = tf.get_u16()
 | |
| 	tf.entry_selector = tf.get_u16()
 | |
| 	tf.range_shift = tf.get_u16()
 | |
| 
 | |
| 	dprintln('scalar_type   : [0x$tf.scalar_type.hex()]')
 | |
| 	dprintln('num tables    : [$num_tables]')
 | |
| 	dprintln('search_range  : [0x$tf.search_range.hex()]')
 | |
| 	dprintln('entry_selector: [0x$tf.entry_selector.hex()]')
 | |
| 	dprintln('range_shift   : [0x$tf.range_shift.hex()]')
 | |
| 
 | |
| 	mut i := 0
 | |
| 	for i < num_tables {
 | |
| 		tag := tf.get_string(4)
 | |
| 		tf.tables[tag] = {
 | |
| 			checksum: tf.get_u32()
 | |
| 			offset: tf.get_u32()
 | |
| 			length: tf.get_u32()
 | |
| 		}
 | |
| 		dprintln('Table: [$tag]')
 | |
| 		// dprintln("${tf.tables[tag]}")
 | |
| 
 | |
| 		if tag != 'head' {
 | |
| 			assert tf.calc_checksum(tf.tables[tag].offset, tf.tables[tag].length) == tf.tables[tag].checksum
 | |
| 		}
 | |
| 		i++
 | |
| 	}
 | |
| 	dprintln('*** END READ TABLES OFFSET ***')
 | |
| }
 | |
| 
 | |
| /******************************************************************************
 | |
| *
 | |
| * Head_Table
 | |
| *
 | |
| ******************************************************************************/
 | |
| fn (mut tf TTF_File) read_head_table() {
 | |
| 	dprintln('*** READ HEAD TABLE ***')
 | |
| 	tf.pos = tf.tables['head'].offset
 | |
| 	dprintln('Offset: $tf.pos')
 | |
| 
 | |
| 	tf.version = tf.get_fixed()
 | |
| 	tf.font_revision = tf.get_fixed()
 | |
| 	tf.checksum_adjustment = tf.get_u32()
 | |
| 	tf.magic_number = tf.get_u32()
 | |
| 	assert tf.magic_number == 0x5f0f3cf5
 | |
| 	tf.flags = tf.get_u16()
 | |
| 	tf.units_per_em = tf.get_u16()
 | |
| 	tf.created = tf.get_date()
 | |
| 	tf.modified = tf.get_date()
 | |
| 	tf.x_min = tf.get_i16()
 | |
| 	tf.y_min = tf.get_i16()
 | |
| 	tf.x_max = tf.get_i16()
 | |
| 	tf.y_max = tf.get_i16()
 | |
| 	tf.mac_style = tf.get_u16()
 | |
| 	tf.lowest_rec_ppem = tf.get_u16()
 | |
| 	tf.font_direction_hint = tf.get_i16()
 | |
| 	tf.index_to_loc_format = tf.get_i16()
 | |
| 	tf.glyph_data_format = tf.get_i16()
 | |
| }
 | |
| 
 | |
| /******************************************************************************
 | |
| *
 | |
| * Name_Table
 | |
| *
 | |
| ******************************************************************************/
 | |
| fn (mut tf TTF_File) read_name_table() {
 | |
| 	dprintln('*** READ NAME TABLE ***')
 | |
| 	assert 'name' in tf.tables
 | |
| 	table_offset := tf.tables['name'].offset
 | |
| 	tf.pos = tf.tables['name'].offset
 | |
| 
 | |
| 	format := tf.get_u16() // must be 0
 | |
| 	assert format == 0
 | |
| 	count := tf.get_u16()
 | |
| 	string_offset := tf.get_u16()
 | |
| 
 | |
| 	for _ in 0 .. count {
 | |
| 		platform_id := tf.get_u16()
 | |
| 		// platform_specific_id :=
 | |
| 		tf.get_u16()
 | |
| 		// language_id          :=
 | |
| 		tf.get_u16()
 | |
| 		name_id := tf.get_u16()
 | |
| 		length := tf.get_u16()
 | |
| 		offset := tf.get_u16()
 | |
| 
 | |
| 		old_pos := tf.pos
 | |
| 		tf.pos = table_offset + string_offset + offset
 | |
| 
 | |
| 		mut name := ''
 | |
| 		if platform_id == 0 || platform_id == 3 {
 | |
| 			name = tf.get_unicode_string(length)
 | |
| 		} else {
 | |
| 			name = tf.get_string(length)
 | |
| 		}
 | |
| 		// dprintln("Name [${platform_id} / ${platform_specific_id}] id:[$name_id] language:[$language_id] [$name]")
 | |
| 		tf.pos = old_pos
 | |
| 
 | |
| 		match name_id {
 | |
| 			1 { tf.font_family = name }
 | |
| 			2 { tf.font_sub_family = name }
 | |
| 			4 { tf.full_name = name }
 | |
| 			6 { tf.postscript_name = name }
 | |
| 			else {}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /******************************************************************************
 | |
| *
 | |
| * Cmap_Table
 | |
| *
 | |
| ******************************************************************************/
 | |
| fn (mut tf TTF_File) read_cmap_table() {
 | |
| 	dprintln('*** READ CMAP TABLE ***')
 | |
| 	assert 'cmap' in tf.tables
 | |
| 	table_offset := tf.tables['cmap'].offset
 | |
| 	tf.pos = table_offset
 | |
| 
 | |
| 	version := tf.get_u16() // must be 0
 | |
| 	assert version == 0
 | |
| 	number_sub_tables := tf.get_u16()
 | |
| 
 | |
| 	// tables must be sorted by platform id and then platform specific
 | |
| 	// encoding.
 | |
| 	for _ in 0 .. number_sub_tables {
 | |
| 		// platforms are:
 | |
| 		// 0 - Unicode -- use specific id 6 for full coverage. 0/4 common.
 | |
| 		// 1 - Macintosh (Discouraged)
 | |
| 		// 2 - reserved
 | |
| 		// 3 - Microsoft
 | |
| 		platform_id := tf.get_u16()
 | |
| 		platform_specific_id := tf.get_u16()
 | |
| 		offset := tf.get_u32()
 | |
| 		dprintln('CMap platform_id=$platform_id specific_id=$platform_specific_id offset=$offset')
 | |
| 		if platform_id == 3 && platform_specific_id <= 1 {
 | |
| 			tf.read_cmap(table_offset + offset)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| fn (mut tf TTF_File) read_cmap(offset u32) {
 | |
| 	old_pos := tf.pos
 | |
| 	tf.pos = offset
 | |
| 	format := tf.get_u16()
 | |
| 	length := tf.get_u16()
 | |
| 	language := tf.get_u16()
 | |
| 
 | |
| 	dprintln('  Cmap format: $format length: $length language: $language')
 | |
| 	if format == 0 {
 | |
| 		dprintln('  Cmap 0 Init...')
 | |
| 		mut cmap := TrueTypeCmap{}
 | |
| 		cmap.init_0(mut tf)
 | |
| 		tf.cmaps << cmap
 | |
| 	} else if format == 4 {
 | |
| 		dprintln('  Cmap 4 Init...')
 | |
| 		mut cmap := TrueTypeCmap{}
 | |
| 		cmap.init_4(mut tf)
 | |
| 		tf.cmaps << cmap
 | |
| 	}
 | |
| 
 | |
| 	tf.pos = old_pos
 | |
| }
 | |
| 
 | |
| /******************************************************************************
 | |
| *
 | |
| * CMAPS 0/4
 | |
| *
 | |
| ******************************************************************************/
 | |
| fn (mut tf TTF_File) map_code(char_code int) u16 {
 | |
| 	mut index := 0
 | |
| 	for i in 0 .. tf.cmaps.len {
 | |
| 		mut cmap := tf.cmaps[i]
 | |
| 		if cmap.format == 0 {
 | |
| 			// dprintln("format 0")
 | |
| 			index = cmap.map_0(char_code)
 | |
| 		} else if cmap.format == 4 {
 | |
| 			// dprintln("format 4")
 | |
| 			index = cmap.map_4(char_code, mut tf)
 | |
| 		}
 | |
| 	}
 | |
| 	return u16(index)
 | |
| }
 | |
| 
 | |
| fn (mut tm TrueTypeCmap) init_0(mut tf TTF_File) {
 | |
| 	tm.format = 0
 | |
| 	for i in 0 .. 256 {
 | |
| 		glyph_index := tf.get_u8()
 | |
| 		dprintln('   Glyph[$i] = %glyph_index')
 | |
| 		tm.arr << glyph_index
 | |
| 	}
 | |
| }
 | |
| 
 | |
| fn (mut tm TrueTypeCmap) map_0(char_code int) int {
 | |
| 	if char_code >= 0 && char_code <= 255 {
 | |
| 		// dprintln("charCode $char_code maps to ${tm.arr[char_code]}")
 | |
| 		return tm.arr[char_code]
 | |
| 	}
 | |
| 	return 0
 | |
| }
 | |
| 
 | |
| fn (mut tm TrueTypeCmap) init_4(mut tf TTF_File) {
 | |
| 	tm.format = 4
 | |
| 
 | |
| 	// 2x segcount
 | |
| 	seg_count := tf.get_u16() >> 1
 | |
| 	// search_range   :=
 | |
| 	tf.get_u16()
 | |
| 	// entry_selector :=
 | |
| 	tf.get_u16()
 | |
| 	// range_shift    :=
 | |
| 	tf.get_u16()
 | |
| 
 | |
| 	// Ending character code for each segment, last is 0xffff
 | |
| 	for _ in 0 .. seg_count {
 | |
| 		tm.segments << Segment{0, 0, tf.get_u16(), 0}
 | |
| 	}
 | |
| 
 | |
| 	// reservePAD
 | |
| 	tf.get_u16()
 | |
| 
 | |
| 	// starting character code for each segment
 | |
| 	for i in 0 .. seg_count {
 | |
| 		tm.segments[i].start_code = tf.get_u16()
 | |
| 	}
 | |
| 
 | |
| 	// Delta for all character codes in segment
 | |
| 	for i in 0 .. seg_count {
 | |
| 		tm.segments[i].id_delta = tf.get_u16()
 | |
| 	}
 | |
| 
 | |
| 	// offset in bytes to glyph indexArray, or 0
 | |
| 	for i in 0 .. seg_count {
 | |
| 		ro := u32(tf.get_u16())
 | |
| 		if ro != 0 {
 | |
| 			tm.segments[i].id_range_offset = tf.pos - 2 + ro
 | |
| 		} else {
 | |
| 			tm.segments[i].id_range_offset = 0
 | |
| 		}
 | |
| 	}
 | |
| 	/*
 | |
| 	// DEBUG LOG
 | |
| 	for i in 0..seg_count {
 | |
| 	seg := tm.segments[i]
 | |
| 	dprintln("    segments[$i] = $seg.start_code $seg.end_code $seg.id_delta $seg.id_range_offset")
 | |
| 	}
 | |
| 	*/
 | |
| }
 | |
| 
 | |
| fn (mut tm TrueTypeCmap) map_4(char_code int, mut tf TTF_File) int {
 | |
| 	// dprintln("HERE map_4 for char [$char_code]")
 | |
| 	old_pos := tf.pos
 | |
| 	if tm.cache[char_code] == -1 {
 | |
| 		// dprintln("Not found, search for it!")
 | |
| 		mut found := false
 | |
| 		for segment in tm.segments {
 | |
| 			if segment.start_code <= char_code && segment.end_code >= char_code {
 | |
| 				mut index := (segment.id_delta + char_code) & 0xffff
 | |
| 				if segment.id_range_offset > 0 {
 | |
| 					glyph_index_address := segment.id_range_offset +
 | |
| 						2 * u32(char_code - segment.start_code)
 | |
| 					tf.pos = glyph_index_address
 | |
| 					index = tf.get_u16()
 | |
| 				}
 | |
| 
 | |
| 				tm.cache[char_code] = index
 | |
| 				found = true
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		if !found {
 | |
| 			tm.cache[char_code] = 0
 | |
| 		}
 | |
| 	}
 | |
| 	tf.pos = old_pos
 | |
| 	return tm.cache[char_code]
 | |
| }
 | |
| 
 | |
| /******************************************************************************
 | |
| *
 | |
| * Hhea table
 | |
| *
 | |
| ******************************************************************************/
 | |
| fn (mut tf TTF_File) read_hhea_table() {
 | |
| 	dprintln('*** READ HHEA TABLE ***')
 | |
| 	assert 'hhea' in tf.tables
 | |
| 	table_offset := tf.tables['hhea'].offset
 | |
| 	tf.pos = table_offset
 | |
| 
 | |
| 	// version :=
 | |
| 	tf.get_fixed() // 0x00010000
 | |
| 
 | |
| 	tf.ascent = tf.get_fword()
 | |
| 	tf.descent = tf.get_fword()
 | |
| 	tf.line_gap = tf.get_fword()
 | |
| 	tf.advance_width_max = tf.get_ufword()
 | |
| 	tf.min_left_side_bearing = tf.get_fword()
 | |
| 	tf.min_right_side_bearing = tf.get_fword()
 | |
| 	tf.x_max_extent = tf.get_fword()
 | |
| 	tf.caret_slope_rise = tf.get_i16()
 | |
| 	tf.caret_slope_run = tf.get_i16()
 | |
| 	tf.caret_offset = tf.get_fword()
 | |
| 	tf.get_i16() // reserved
 | |
| 	tf.get_i16() // reserved
 | |
| 	tf.get_i16() // reserved
 | |
| 	tf.get_i16() // reserved
 | |
| 	tf.metric_data_format = tf.get_i16()
 | |
| 	tf.num_of_long_hor_metrics = tf.get_u16()
 | |
| }
 | |
| 
 | |
| /******************************************************************************
 | |
| *
 | |
| * Kern table
 | |
| *
 | |
| ******************************************************************************/
 | |
| struct Kern0Table {
 | |
| mut:
 | |
| 	swap      bool
 | |
| 	offset    u32
 | |
| 	n_pairs   int
 | |
| 	kmap      map[u32]i16
 | |
| 	old_index int = -1
 | |
| }
 | |
| 
 | |
| fn (mut kt Kern0Table) reset() {
 | |
| 	kt.old_index = -1
 | |
| }
 | |
| 
 | |
| fn (mut kt Kern0Table) get(glyph_index int) (int, int) {
 | |
| 	mut x := 0
 | |
| 
 | |
| 	if kt.old_index >= 0 {
 | |
| 		ch := ((u32(kt.old_index & 0xFFFF) << 16) | u32(glyph_index & 0xFFFF))
 | |
| 		// dprintln("kern_get: $ch")
 | |
| 		if ch in kt.kmap {
 | |
| 			x = int(kt.kmap[ch])
 | |
| 		}
 | |
| 	}
 | |
| 	kt.old_index = glyph_index
 | |
| 	if kt.swap {
 | |
| 		return 0, x
 | |
| 	}
 | |
| 	return x, 0
 | |
| }
 | |
| 
 | |
| fn (mut tf TTF_File) create_kern_table0(vertical bool, cross bool) Kern0Table {
 | |
| 	offset := tf.pos
 | |
| 	n_pairs := tf.get_u16()
 | |
| 	search_range := tf.get_u16()
 | |
| 	entry_selector := tf.get_u16()
 | |
| 	range_shift := tf.get_u16()
 | |
| 	dprintln('n_pairs: $n_pairs search_range: $search_range entry_selector: $entry_selector range_shift: $range_shift')
 | |
| 
 | |
| 	mut kt0 := Kern0Table{
 | |
| 		swap: (vertical && !cross) || (!vertical && cross)
 | |
| 		offset: offset
 | |
| 		n_pairs: n_pairs
 | |
| 	}
 | |
| 
 | |
| 	for _ in 0 .. n_pairs {
 | |
| 		left := tf.get_u16()
 | |
| 		right := tf.get_u16()
 | |
| 		value := tf.get_fword()
 | |
| 		tmp_index := (u32(left) << 16) | u32(right)
 | |
| 		kt0.kmap[tmp_index] = value
 | |
| 		// dprintln("index: ${tmp_index.hex()} val: ${value.hex()}")
 | |
| 	}
 | |
| 	kt0.old_index = -1
 | |
| 	return kt0
 | |
| }
 | |
| 
 | |
| fn (mut tf TTF_File) read_kern_table() {
 | |
| 	dprintln('*** READ KERN TABLE ***')
 | |
| 	if !('kern' in tf.tables) {
 | |
| 		return
 | |
| 	}
 | |
| 	table_offset := tf.tables['kern'].offset
 | |
| 	tf.pos = table_offset
 | |
| 
 | |
| 	version := tf.get_u16() // must be 0
 | |
| 	assert version == 0 // must be 0
 | |
| 	n_tables := tf.get_u16()
 | |
| 
 | |
| 	dprintln('Kern Table version: $version Kern nTables: $n_tables')
 | |
| 
 | |
| 	for _ in 0 .. n_tables {
 | |
| 		st_version := tf.get_u16() // sub table version
 | |
| 		length := tf.get_u16()
 | |
| 		coverage := tf.get_u16()
 | |
| 		format := coverage >> 8
 | |
| 		cross := coverage & 4
 | |
| 		vertical := (coverage & 0x1) == 0
 | |
| 		dprintln('Kerning subtable version [$st_version] format [$format] length [$length] coverage: [$coverage.hex()]')
 | |
| 		if format == 0 {
 | |
| 			dprintln('kern format: 0')
 | |
| 			kern := tf.create_kern_table0(vertical, cross != 0)
 | |
| 			tf.kern << kern
 | |
| 		} else {
 | |
| 			dprintln('Unknown format -- skip')
 | |
| 			tf.pos = tf.pos + length
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| fn (mut tf TTF_File) reset_kern() {
 | |
| 	for i in 0 .. tf.kern.len {
 | |
| 		tf.kern[i].reset()
 | |
| 	}
 | |
| }
 | |
| 
 | |
| fn (mut tf TTF_File) next_kern(glyph_index int) (int, int) {
 | |
| 	mut x := 0
 | |
| 	mut y := 0
 | |
| 	for i in 0 .. tf.kern.len {
 | |
| 		tmp_x, tmp_y := tf.kern[i].get(glyph_index)
 | |
| 		x = x + tmp_x
 | |
| 		y = y + tmp_y
 | |
| 	}
 | |
| 	return x, y
 | |
| }
 | |
| 
 | |
| /******************************************************************************
 | |
| *
 | |
| * TTF_File Utility
 | |
| *
 | |
| ******************************************************************************/
 | |
| pub fn (tf TTF_File) get_info_string() string {
 | |
| 	txt := '----- Font Info -----
 | |
| 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
 | |
| }
 | |
| 
 | |
| /******************************************************************************
 | |
| *
 | |
| * TTF_File test
 | |
| *
 | |
| ******************************************************************************/
 | |
| fn tst() {
 | |
| 	mut tf := TTF_File{}
 | |
| 
 | |
| 	tf.buf = [
 | |
| 		byte(0xFF), /* 8  bit */
 | |
| 		0xF1,
 | |
| 		0xF2, /* 16 bit */
 | |
| 		0x81,
 | |
| 		0x23,
 | |
| 		0x45,
 | |
| 		0x67, /* 32 bit */
 | |
| 		0x12,
 | |
| 		0x34,
 | |
| 		0x12,
 | |
| 		0x34, /* get_2dot14 16 bit */
 | |
| 		0x12,
 | |
| 		0x34,
 | |
| 		0x12,
 | |
| 		0x34 /* get_fixed 32 bit int */,
 | |
| 	]
 | |
| 	assert tf.get_u8().hex() == 'ff'
 | |
| 	assert tf.get_u16().hex() == 'f1f2'
 | |
| 	assert tf.get_u32().hex() == '81234567'
 | |
| 
 | |
| 	dprintln('buf len: $tf.buf.len')
 | |
| 	// dprintln( tf.get_u8().hex() )
 | |
| 	// dprintln( tf.get_u16().hex() )
 | |
| 	// dprintln( tf.get_u32().hex() )
 | |
| 	// dprintln( tf.get_2dot14() )
 | |
| 	// dprintln( tf.get_fixed() )
 | |
| }
 |