v/vlib/x/ttf/ttf.v

1114 lines
27 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 = 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 & tfk_on_curve) > 0
}
if (flag & 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 & 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 & tfk_x_is_byte) > 0 {
if (flag_x & tfk_x_delta) > 0 {
value += tf.get_u8()
} else {
value -= tf.get_u8()
}
} else if (~flag_x & 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 & tfk_y_is_byte) > 0 {
if (flag_y & tfk_y_delta) > 0 {
value += tf.get_u8()
} else {
value -= tf.get_u8()
}
} else if (~flag_y & 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 = g_type_complex
mut component := Component{}
mut flags := tfkc_more_components
for (flags & tfkc_more_components) > 0 {
mut arg1 := i16(0)
mut arg2 := i16(0)
flags = tf.get_u16()
component.glyph_index = tf.get_u16()
if (flags & 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 & 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 & tfkc_we_have_a_scale) > 0 {
component.matrix[0] = tf.get_2dot14()
component.matrix[3] = component.matrix[0]
} else if (flags & tfkc_we_have_an_x_and_y_scale) > 0 {
component.matrix[0] = tf.get_2dot14()
component.matrix[3] = tf.get_2dot14()
} else if (flags & 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 & 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))
}
/*---- restore when MSVC will work again! ----
fn (mut tf TTF_File) get_string(length int) string {
tmp_pos := tf.pos
tf.pos += u32(length)
unsafe{
return tos(&tf.buf.data[tmp_pos], length)
}
}
*/
fn (mut tf TTF_File) get_string(length int) string {
tmp_pos := tf.pos
mut tmp_txt := strings.new_builder(length)
for _ in 0..length {
tmp_txt.write_b(tf.get_u8())
}
res_txt := tmp_txt.str()
tf.pos = tmp_pos + u32(length)
return res_txt
}
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 {
// 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
}
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
*
******************************************************************************/
/*
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}
-----------------------
"
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() )
}