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 []u8 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 // panose panose_array []u8 = []u8{len: 12, init: 0} // cache glyph_cache map[int]Glyph // font widths array scale for PDF export width_scale f32 = 1.0 } 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.read_panose_table() tf.length = tf.glyph_count() dprintln('Number of symbols: ${tf.length}') dprintln('*****************************') dprintln('Unit per em: ${tf.units_per_em}') dprintln('advance_width_max: ${tf.advance_width_max}') } /****************************************************************************** * * 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 += u32(glyph_index) * 4 tf.pos = offset advance_width = tf.get_u16() left_side_bearing = tf.get_i16() // dprintln("${glyph_index} aw:${advance_width} lsb:${left_side_bearing}") } else { // read the last entry of the hMetrics array tf.pos = offset + u32(tf.num_of_long_hor_metrics - 1) * 4 advance_width = tf.get_u16() tf.pos = offset + u32(tf.num_of_long_hor_metrics) * 4 + 2 * u32(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 } pub 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) get_ttf_widths() ([]int, int, int) { mut space_cw, _ := tf.get_horizontal_metrics(u16(` `)) // div_space_cw := int((f32(space_cw) * 0.3)) // count := int(tf.glyph_count()) mut min_code := 0xFFFF + 1 mut max_code := 0 for i in 0 .. 300 { glyph_index := tf.map_code(i) if glyph_index == 0 { continue } // dprintln("$i = glyph_index: $glyph_index ${i:c}") if i > max_code { max_code = i } if i < min_code { min_code = i } } // dprintln("min_code: $min_code max_code: $max_code") mut widths := []int{len: max_code - min_code + 1, init: 0} for i in min_code .. max_code { pos := i - min_code glyph_index := tf.map_code(i) if glyph_index == 0 || i == 32 { widths[pos] = space_cw continue } x_min, x_max, _, _ := tf.read_glyph_dim(glyph_index) aw, lsb := tf.get_horizontal_metrics(u16(glyph_index)) w := x_max - x_min rsb := aw - (lsb + w) // pp1 := x_min - lsb // pp2 := pp1 + aw w1 := w + lsb + rsb widths[pos] = int(w1 / tf.width_scale) // if i >= int(`A`) && i <= int(`Z`) { // dprintln("${i:c}|$glyph_index [$pos] => width:${x_max-x_min} aw:${aw}|w1:${w1} lsb:${lsb} rsb:${rsb} pp1:${pp1} pp2:${pp2}") //} } // dprintln("Widths: ${widths.len}") return widths, min_code, max_code } 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 := []u8{} 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() u8 { x := tf.buf[tf.pos] tf.pos++ return u8(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]) << 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()) mut res := u32(tf.get_u16()) if (res & 0x8000) > 0 { res -= (u32(1) << 16) } return i16(res) } 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 { mut res := u64(tf.get_u32()) if (res & 0x8000_0000) > 0 { res -= (u64(1) << 32) } return int(res) } 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(&u8(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_u8(u8(c & 0xff)) } else { tmp_txt.write_u8(u8((c >> 8) & 0xff)) tmp_txt.write_u8(u8(c & 0xff)) } // dprintln("c: ${c:c}|${ u8(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] = Offset_Table{ 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 = u32(table_offset) + u32(string_offset) + u32(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 * ******************************************************************************/ pub 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 := u32(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 } } } pub fn (mut tf TTF_File) reset_kern() { for i in 0 .. tf.kern.len { tf.kern[i].reset() } } pub 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 } /****************************************************************************** * * Panose table * ******************************************************************************/ fn (mut tf TTF_File) read_panose_table() { dprintln('*** READ PANOSE TABLE ***') if 'OS/2' !in tf.tables { return } table_offset := tf.tables['OS/2'].offset tf.pos = table_offset // dprintln('READING! PANOSE offset:${tf.tables['OS/2']}') version := tf.get_u16() dprintln('Panose version: ${version:04x}') tf.pos += 2 * 14 // move to Panose class + 10 byte array mut count := 0 // get family family_class := tf.get_i16() tf.panose_array[count] = u8(family_class >> 8) count++ tf.panose_array[count] = u8(family_class & 0xFF) count++ dprintln('family_class: ${family_class:04x}') // get panose data for _ in 0 .. 10 { tf.panose_array[count] = tf.get_u8() count++ } // family_class1 := (i16(tf.panose_array[0]) << 8) + i16(tf.panose_array[1]) // dprintln("family_class: ${family_class1:04x}") // dprintln("Panose array: ${tf.panose_array}") } /****************************************************************************** * * 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 = [ u8(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() ) }