Harbor

branch main
showing the latest snapshot on main
parser.odin 19.3 KB · Plain text
font/parser.odin 0644 Raw
package font

import "core:mem"
import "core:math"

// Check if data represents a font
@(private)
is_font :: proc(font: [^]u8) -> bool {
	// Check for known font signatures
	if tag4(font, '1', 0, 0, 0) do return true  // TrueType 1
	if tag(font, "typ1") do return true         // TrueType with type 1 font
	if tag(font, "OTTO") do return true         // OpenType with CFF
	if tag4(font, 0, 1, 0, 0) do return true    // OpenType 1.0
	if tag(font, "true") do return true         // Apple TrueType
	return false
}

// Find a table in the font directory
@(private)
find_table :: proc(data: [^]u8, fontstart: u32, tag_str: string) -> u32 {
	num_tables := i32(ttUSHORT(data[fontstart+4:]))
	tabledir := fontstart + 12

	for i in 0..<num_tables {
		loc := tabledir + u32(16 * i)
		if tag(data[loc:], tag_str) {
			return ttULONG(data[loc+8:])
		}
	}
	return 0
}

// Phase 1 Functions - Font Loading

@(private)
get_font_offset_for_index :: proc(font_collection: [^]u8, index: i32) -> i32 {
	// If it's just a font, there's only one valid index
	if is_font(font_collection) {
		return 0 if index == 0 else -1
	}

	// Check if it's a TTC (TrueType Collection)
	if tag(font_collection, "ttcf") {
		// Version 1.0 or 2.0?
		version := ttULONG(font_collection[4:])
		if version == 0x00010000 || version == 0x00020000 {
			n := ttLONG(font_collection[8:])
			if index >= n {
				return -1
			}
			return i32(ttULONG(font_collection[12 + u32(index)*4:]))
		}
	}
	return -1
}

@(private)
get_number_of_fonts :: proc(font_collection: [^]u8) -> i32 {
	// If it's just a font, there's only one valid font
	if is_font(font_collection) {
		return 1
	}

	// Check if it's a TTC
	if tag(font_collection, "ttcf") {
		// Version 1.0 or 2.0?
		version := ttULONG(font_collection[4:])
		if version == 0x00010000 || version == 0x00020000 {
			return ttLONG(font_collection[8:])
		}
	}
	return 0
}

@(private)
new_buf :: proc(data: [^]u8, size: i32) -> Buf {
	return Buf{data = data, cursor = 0, size = size}
}

// Buf helper functions for CFF parsing
@(private)
buf_get8 :: #force_inline proc(b: ^Buf) -> u8 {
	if b.cursor < b.size {
		r := b.data[b.cursor]
		b.cursor += 1
		return r
	}
	return 0
}

@(private)
buf_peek8 :: proc(b: ^Buf) -> u8 {
	if b.cursor < b.size {
		return b.data[b.cursor]
	}
	return 0
}

@(private)
buf_seek :: proc(b: ^Buf, o: i32) {
	b.cursor = b.size if (o > b.size || o < 0) else o
}

@(private)
buf_skip :: proc(b: ^Buf, o: i32) {
	buf_seek(b, b.cursor + o)
}

@(private)
buf_get :: proc(b: ^Buf, n: i32) -> u32 {
	v: u32 = 0
	for _ in 0..<n {
		v = (v << 8) | u32(buf_get8(b))
	}
	return v
}

@(private)
buf_get16 :: #force_inline proc(b: ^Buf) -> u32 {
	return buf_get(b, 2)
}

@(private)
buf_get32 :: #force_inline proc(b: ^Buf) -> u32 {
	return buf_get(b, 4)
}

@(private)
buf_range :: proc(b: ^Buf, o: i32, s: i32) -> Buf {
	if o < 0 || s < 0 || o > b.size || s > b.size - o {
		return new_buf(nil, 0)
	}
	return Buf{data = &b.data[o], cursor = 0, size = s}
}

// CFF INDEX parsing functions
@(private)
cff_get_index :: proc(b: ^Buf) -> Buf {
	start := b.cursor
	count := i32(buf_get16(b))
	if count != 0 {
		offsize := i32(buf_get8(b))
		buf_skip(b, offsize * count)
		buf_skip(b, i32(buf_get(b, offsize)) - 1)
	}
	return buf_range(b, start, b.cursor - start)
}

@(private)
cff_index_count :: proc(b: ^Buf) -> i32 {
	b := b^
	buf_seek(&b, 0)
	return i32(buf_get16(&b))
}

@(private)
cff_index_get :: proc(b: Buf, i: i32) -> Buf {
	b := b
	buf_seek(&b, 0)
	count := i32(buf_get16(&b))
	offsize := i32(buf_get8(&b))
	buf_skip(&b, i * offsize)
	start := i32(buf_get(&b, offsize))
	end := i32(buf_get(&b, offsize))
	return buf_range(&b, 2 + (count + 1) * offsize + start, end - start)
}

// CFF DICT parsing functions
@(private)
cff_int :: proc(b: ^Buf) -> i32 {
	b0 := buf_get8(b)
	if b0 >= 32 && b0 <= 246 {
		return i32(b0) - 139
	} else if b0 >= 247 && b0 <= 250 {
		return (i32(b0) - 247) * 256 + i32(buf_get8(b)) + 108
	} else if b0 >= 251 && b0 <= 254 {
		return -(i32(b0) - 251) * 256 - i32(buf_get8(b)) - 108
	} else if b0 == 28 {
		return i32(buf_get16(b))
	} else if b0 == 29 {
		return i32(buf_get32(b))
	}
	return 0
}

@(private)
cff_skip_operand :: proc(b: ^Buf) {
	b0 := buf_peek8(b)
	if b0 == 30 {
		buf_skip(b, 1)
		for b.cursor < b.size {
			v := buf_get8(b)
			if (v & 0xF) == 0xF || (v >> 4) == 0xF {
				break
			}
		}
	} else {
		cff_int(b)
	}
}

@(private)
dict_get :: proc(b: ^Buf, key: i32) -> Buf {
	buf_seek(b, 0)
	for b.cursor < b.size {
		start := b.cursor
		for buf_peek8(b) >= 28 {
			cff_skip_operand(b)
		}
		end := b.cursor
		op := i32(buf_get8(b))
		if op == 12 {
			op = i32(buf_get8(b)) | 0x100
		}
		if op == key {
			return buf_range(b, start, end - start)
		}
	}
	return buf_range(b, 0, 0)
}

@(private)
dict_get_ints :: proc(b: ^Buf, key: i32, outcount: i32, out: [^]u32) {
	operands := dict_get(b, key)
	for i in 0..<outcount {
		if operands.cursor >= operands.size {
			break
		}
		out[i] = u32(cff_int(&operands))
	}
}

// Subroutine retrieval functions
@(private)
get_subrs :: proc(cff: Buf, fontdict: Buf) -> Buf {
	subrsoff: u32 = 0
	private_loc: [2]u32 = {0, 0}
	fontdict := fontdict
	cff := cff

	dict_get_ints(&fontdict, 18, 2, &private_loc[0])
	if private_loc[1] == 0 || private_loc[0] == 0 {
		return new_buf(nil, 0)
	}

	pdict := buf_range(&cff, i32(private_loc[1]), i32(private_loc[0]))
	dict_get_ints(&pdict, 19, 1, &subrsoff)
	if subrsoff == 0 {
		return new_buf(nil, 0)
	}

	buf_seek(&cff, i32(private_loc[1]) + i32(subrsoff))
	return cff_get_index(&cff)
}

@(private)
get_subr :: proc(idx: Buf, n: i32) -> Buf {
	idx := idx
	count := cff_index_count(&idx)
	bias: i32 = 107
	if count >= 33900 {
		bias = 32768
	} else if count >= 1240 {
		bias = 1131
	}
	n := n + bias
	if n < 0 || n >= count {
		return new_buf(nil, 0)
	}
	return cff_index_get(idx, n)
}

@(private)
cid_get_glyph_subrs :: proc(info: ^Font_Info, glyph_index: i32) -> Buf {
	fdselect := info.fdselect
	buf_seek(&fdselect, 0)
	fmt := buf_get8(&fdselect)
	fdselector: i32 = -1

	if fmt == 0 {
		buf_skip(&fdselect, glyph_index)
		fdselector = i32(buf_get8(&fdselect))
	} else if fmt == 3 {
		nranges := i32(buf_get16(&fdselect))
		start := i32(buf_get16(&fdselect))
		for _ in 0..<nranges {
			v := i32(buf_get8(&fdselect))
			end := i32(buf_get16(&fdselect))
			if glyph_index >= start && glyph_index < end {
				fdselector = v
				break
			}
			start = end
		}
	}

	if fdselector == -1 {
		return new_buf(nil, 0)
	}
	return get_subrs(info.cff, cff_index_get(info.fontdicts, fdselector))
}

// Initialize a font from raw data with known size.
// The size enables bounds checking on malformed font data.
init_font_sized :: proc(info: ^Font_Info, data: [^]u8, data_size: i32, fontstart: i32) -> bool {
	info.data_size = data_size
	return init_font(info, data, fontstart)
}

init_font :: proc(info: ^Font_Info, data: [^]u8, fontstart: i32) -> bool {
	info.data = data
	info.fontstart = fontstart
	info.cff = new_buf(nil, 0)

	// Find required tables
	cmap := find_table(data, u32(fontstart), "cmap")
	info.loca = i32(find_table(data, u32(fontstart), "loca"))
	info.head = i32(find_table(data, u32(fontstart), "head"))
	info.glyf = i32(find_table(data, u32(fontstart), "glyf"))
	info.hhea = i32(find_table(data, u32(fontstart), "hhea"))
	info.hmtx = i32(find_table(data, u32(fontstart), "hmtx"))
	info.kern = i32(find_table(data, u32(fontstart), "kern"))
	info.gpos = i32(find_table(data, u32(fontstart), "GPOS"))
	info.gsub = i32(find_table(data, u32(fontstart), "GSUB"))

	// Verify required tables exist
	if cmap == 0 || info.head == 0 || info.hhea == 0 || info.hmtx == 0 {
		return false
	}

	// For TrueType, loca is required
	if info.glyf != 0 && info.loca == 0 {
		return false
	}

	// Cache font type for optimal dispatch performance
	info.is_cff = (info.glyf == 0)

	// Handle CFF/OpenType fonts
	if info.is_cff {
		cff_offset := find_table(data, u32(fontstart), "CFF ")
		if cff_offset == 0 {
			return false
		}

		info.fontdicts = new_buf(nil, 0)
		info.fdselect = new_buf(nil, 0)

		// Create CFF buffer - we need a reasonable size for the CFF data
		// The actual CFF data extent will be controlled by cursor/size tracking
		info.cff = new_buf(&data[cff_offset], 512 * 1024 * 1024)
		b := info.cff

		// Read CFF header
		buf_skip(&b, 2)  // Skip major, minor version
		buf_seek(&b, i32(buf_get8(&b)))  // Seek past header using hdrsize

		// Parse INDEX structures
		cff_get_index(&b)  // Skip Name INDEX
		topdictidx := cff_get_index(&b)
		topdict := cff_index_get(topdictidx, 0)
		cff_get_index(&b)  // Skip String INDEX
		info.gsubrs = cff_get_index(&b)

		// Get values from Top DICT
		charstrings: u32 = 0
		cstype: u32 = 2
		fdarrayoff: u32 = 0
		fdselectoff: u32 = 0

		dict_get_ints(&topdict, 17, 1, &charstrings)            // CharStrings offset
		dict_get_ints(&topdict, 0x100 | 6, 1, &cstype)          // CharstringType
		dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff)     // FDArray offset
		dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff)    // FDSelect offset

		info.subrs = get_subrs(info.cff, topdict)

		// Only support Type 2 CharStrings
		if cstype != 2 {
			return false
		}
		if charstrings == 0 {
			return false
		}

		// Handle CID fonts
		if fdarrayoff != 0 {
			if fdselectoff == 0 {
				return false
			}
			buf_seek(&b, i32(fdarrayoff))
			info.fontdicts = cff_get_index(&b)
			info.fdselect = buf_range(&b, i32(fdselectoff), b.size - i32(fdselectoff))
		}

		buf_seek(&b, i32(charstrings))
		info.charstrings = cff_get_index(&b)
	}

	// Get number of glyphs from maxp table
	t := find_table(data, u32(fontstart), "maxp")
	if t != 0 {
		info.num_glyphs = i32(ttUSHORT(data[t+4:]))
	} else {
		info.num_glyphs = 0xffff
	}

	info.svg = -1

	// Find a cmap encoding table we understand
	num_tables := i32(ttUSHORT(data[cmap+2:]))
	info.index_map = 0

	for i in 0..<num_tables {
		encoding_record := cmap + u32(4 + 8 * i)
		platform_id := ttUSHORT(data[encoding_record:])
		encoding_id := ttUSHORT(data[encoding_record+2:])

		switch platform_id {
		case PLATFORM_ID_MICROSOFT:
			switch encoding_id {
			case MS_EID_UNICODE_BMP, MS_EID_UNICODE_FULL:
				info.index_map = i32(cmap + ttULONG(data[encoding_record+4:]))
			}
		case PLATFORM_ID_UNICODE:
			// Mac/iOS has these - all encodingIDs are unicode
			info.index_map = i32(cmap + ttULONG(data[encoding_record+4:]))
		}
	}

	if info.index_map == 0 {
		return false
	}

	info.index_to_loc_format = i32(ttUSHORT(data[u32(info.head)+50:]))

	// Validate units_per_em (must be 16-16384 per OpenType spec)
	units_per_em := i32(ttUSHORT(data[u32(info.head)+18:]))
	if units_per_em < 16 || units_per_em > 16384 {
		return false
	}

	// Initialize caches
	info.kern_cache = make(map[u64]i32)
	info.kern_cache_ready = true
	info.shape_cache = make(map[i32]Cached_Shape)
	info.shape_cache_ready = true

	// Build cmap cache lazily on first lookup (see find_glyph_index)

	return true
}

// Phase 1 Functions - Character to Glyph Mapping

find_glyph_index :: proc(info: ^Font_Info, unicode_codepoint: i32) -> i32 {
	// Build cache on first call
	if !info.cmap_cache_ready {
		build_cmap_cache(info)
	}

	if gi, ok := info.cmap_cache[unicode_codepoint]; ok {
		return gi
	}
	return 0
}

// Build cmap HashMap by iterating all codepoint→glyph mappings
@(private)
build_cmap_cache :: proc(info: ^Font_Info) {
	data := info.data
	index_map := u32(info.index_map)
	format := ttUSHORT(data[index_map:])

	info.cmap_cache = make(map[i32]i32, int(info.num_glyphs))
	info.cmap_cache_ready = true

	switch format {
	case 0:
		bytes := i32(ttUSHORT(data[index_map+2:]))
		for cp: i32 = 0; cp < bytes - 6; cp += 1 {
			gi := i32(ttBYTE(data[index_map+6+u32(cp):]))
			if gi != 0 do info.cmap_cache[cp] = gi
		}

	case 4:
		segcount := i32(ttUSHORT(data[index_map+6:]) >> 1)
		end_count := index_map + 14
		for seg: i32 = 0; seg < segcount; seg += 1 {
			end := i32(ttUSHORT(data[end_count + u32(seg)*2:]))
			start := i32(ttUSHORT(data[index_map+14+u32(segcount)*2+2+u32(seg)*2:]))
			delta := i32(ttSHORT(data[index_map+14+u32(segcount)*4+2+u32(seg)*2:]))
			offset := u32(ttUSHORT(data[index_map+14+u32(segcount)*6+2+u32(seg)*2:]))
			if end == 0xFFFF && start == 0xFFFF do break
			for cp := start; cp <= end; cp += 1 {
				gi: i32
				if offset == 0 {
					gi = i32(u16(cp + delta))
				} else {
					addr := index_map+14+u32(segcount)*6+2+u32(seg)*2 + offset + u32(cp-start)*2
					if info.data_size > 0 && i32(addr) + 2 > info.data_size do continue
					gi = i32(ttUSHORT(data[addr:]))
					if gi != 0 do gi = i32(u16(i32(gi) + delta))
				}
				if gi != 0 do info.cmap_cache[cp] = gi
			}
		}

	case 6:
		first := i32(ttUSHORT(data[index_map+6:]))
		count := i32(ttUSHORT(data[index_map+8:]))
		for i: i32 = 0; i < count; i += 1 {
			gi := i32(ttUSHORT(data[index_map+10+u32(i)*2:]))
			if gi != 0 do info.cmap_cache[first + i] = gi
		}

	case 12, 13:
		ngroups := i32(ttULONG(data[index_map+12:]))
		for g: i32 = 0; g < ngroups; g += 1 {
			start_char := i32(ttULONG(data[index_map+16+u32(g)*12:]))
			end_char := i32(ttULONG(data[index_map+16+u32(g)*12+4:]))
			start_glyph := i32(ttULONG(data[index_map+16+u32(g)*12+8:]))
			for cp := start_char; cp <= end_char; cp += 1 {
				gi: i32
				if format == 12 {
					gi = start_glyph + cp - start_char
				} else {
					gi = start_glyph
				}
				if gi != 0 do info.cmap_cache[cp] = gi
			}
		}
	}
}

// Phase 1 Functions - Scaling and Metrics

scale_for_pixel_height :: proc(info: ^Font_Info, pixels: f32) -> f32 {
	fheight := f32(ttSHORT(info.data[u32(info.hhea)+4:]) - ttSHORT(info.data[u32(info.hhea)+6:]))
	return pixels / fheight
}

scale_for_mapping_em_to_pixels :: proc(info: ^Font_Info, pixels: f32) -> f32 {
	units_per_em := i32(ttUSHORT(info.data[u32(info.head)+18:]))
	return pixels / f32(units_per_em)
}

get_font_vmetrics :: proc(info: ^Font_Info, ascent: ^i32, descent: ^i32, line_gap: ^i32) {
	if ascent != nil {
		ascent^ = i32(ttSHORT(info.data[u32(info.hhea)+4:]))
	}
	if descent != nil {
		descent^ = i32(ttSHORT(info.data[u32(info.hhea)+6:]))
	}
	if line_gap != nil {
		line_gap^ = i32(ttSHORT(info.data[u32(info.hhea)+8:]))
	}
}

get_codepoint_hmetrics :: proc(info: ^Font_Info, codepoint: i32, advance_width: ^i32, left_side_bearing: ^i32) {
	glyph := find_glyph_index(info, codepoint)
	get_glyph_hmetrics(info, glyph, advance_width, left_side_bearing)
}

get_glyph_hmetrics :: proc(info: ^Font_Info, glyph_index: i32, advance_width: ^i32, left_side_bearing: ^i32) {
	num_of_long_hor_metrics := u32(ttUSHORT(info.data[u32(info.hhea)+34:]))

	if u32(glyph_index) < num_of_long_hor_metrics {
		if advance_width != nil {
			advance_width^ = i32(ttSHORT(info.data[u32(info.hmtx)+4*u32(glyph_index):]))
		}
		if left_side_bearing != nil {
			left_side_bearing^ = i32(ttSHORT(info.data[u32(info.hmtx)+4*u32(glyph_index)+2:]))
		}
	} else {
		if advance_width != nil {
			advance_width^ = i32(ttSHORT(info.data[u32(info.hmtx)+4*(num_of_long_hor_metrics-1):]))
		}
		if left_side_bearing != nil {
			left_side_bearing^ = i32(ttSHORT(info.data[u32(info.hmtx)+4*num_of_long_hor_metrics+2*u32(glyph_index-i32(num_of_long_hor_metrics)):]))
		}
	}
}

// Get horizontal metrics adjusted for variation coordinates.
get_glyph_hmetrics_var :: proc(info: ^Font_Info, glyph_index: i32, coords: ^Var_Coords, advance_width: ^i32, left_side_bearing: ^i32) {
	get_glyph_hmetrics(info, glyph_index, advance_width, left_side_bearing)

	hvar := find_table(info.data, u32(info.fontstart), "HVAR")
	if hvar == 0 || advance_width == nil do return

	data := info.data[hvar:]
	version := ttULONG(data)
	if version != 0x00010000 do return

	var_store_off := u32(ttULONG(data[4:]))
	adv_map_off := u32(ttULONG(data[8:]))

	outer_idx: u16 = 0
	inner_idx: u16 = u16(glyph_index)

	if adv_map_off != 0 {
		map_data := data[adv_map_off:]
		map_format := map_data[0]
		entry_format := map_data[1]
		map_count: i32

		if map_format == 0 {
			map_count = i32(ttUSHORT(map_data[2:]))
			map_data = map_data[4:]
		} else {
			map_count = i32(ttULONG(map_data[2:]))
			map_data = map_data[6:]
		}

		inner_bits := u16((entry_format & 0x0F) + 1)
		entry_size := i32(((entry_format >> 4) & 3) + 1)
		inner_mask := u16((1 << inner_bits) - 1)

		idx := min(i32(glyph_index), map_count - 1)
		if idx >= 0 {
			entry: u32
			e := map_data[u32(idx) * u32(entry_size):]
			switch entry_size {
			case 1: entry = u32(e[0])
			case 2: entry = u32(ttUSHORT(e))
			case 3: entry = (u32(e[0]) << 16) | u32(ttUSHORT(e[1:]))
			case 4: entry = ttULONG(e)
			}
			outer_idx = u16(entry >> inner_bits)
			inner_idx = u16(entry) & inner_mask
		}
	}

	delta := parse_item_var_delta(data[var_store_off:], outer_idx, inner_idx, coords)
	advance_width^ += i32(delta + 0.5)
}

@(private)
parse_item_var_delta :: proc(store: [^]u8, outer_idx: u16, inner_idx: u16, coords: ^Var_Coords) -> f32 {
	format := ttUSHORT(store)
	if format != 1 do return 0

	region_list_off := u32(ttULONG(store[2:]))
	data_count := i32(ttUSHORT(store[6:]))
	if i32(outer_idx) >= data_count do return 0

	data_off := u32(ttULONG(store[8 + u32(outer_idx) * 4:]))
	ivd := store[data_off:]

	item_count := i32(ttUSHORT(ivd))
	word_delta_count_raw := ttUSHORT(ivd[2:])
	region_index_count := i32(ttUSHORT(ivd[4:]))
	if i32(inner_idx) >= item_count do return 0

	word_delta_count := i32(word_delta_count_raw & 0x7FFF)
	long_words := (word_delta_count_raw & 0x8000) != 0

	region_indices := ivd[6:]
	regions := store[region_list_off:]
	axis_count := i32(ttUSHORT(regions))

	delta_data := ivd[6 + u32(region_index_count) * 2:]

	row_size: i32
	if long_words {
		row_size = word_delta_count * 4 + (region_index_count - word_delta_count) * 2
	} else {
		row_size = word_delta_count * 2 + (region_index_count - word_delta_count)
	}

	row := delta_data[u32(inner_idx) * u32(row_size):]

	result: f32 = 0
	for ri in 0..<region_index_count {
		delta_val: f32
		if long_words {
			if ri < word_delta_count {
				delta_val = f32(i32(ttULONG(row[u32(ri) * 4:])))
			} else {
				off := u32(word_delta_count) * 4 + u32(ri - word_delta_count) * 2
				delta_val = f32(i16(ttUSHORT(row[off:])))
			}
		} else {
			if ri < word_delta_count {
				delta_val = f32(i16(ttUSHORT(row[u32(ri) * 2:])))
			} else {
				off := u32(word_delta_count) * 2 + u32(ri - word_delta_count)
				delta_val = f32(i8(row[off]))
			}
		}

		region_idx := i32(ttUSHORT(region_indices[u32(ri) * 2:]))
		region_off := 4 + u32(region_idx) * u32(axis_count) * 6
		scalar: f32 = 1.0

		for ai in 0..<min(axis_count, MAX_VAR_AXES) {
			rr := regions[region_off + u32(ai) * 6:]
			start_coord := f32(i16(ttUSHORT(rr))) / 16384.0
			peak_coord := f32(i16(ttUSHORT(rr[2:]))) / 16384.0
			end_coord := f32(i16(ttUSHORT(rr[4:]))) / 16384.0

			v := coords[ai]
			if peak_coord == 0 do continue
			if v == peak_coord do continue
			if v < start_coord || v > end_coord { scalar = 0; break }
			if v < peak_coord {
				if peak_coord != start_coord do scalar *= (v - start_coord) / (peak_coord - start_coord)
			} else {
				if peak_coord != end_coord do scalar *= (end_coord - v) / (end_coord - peak_coord)
			}
		}

		result += delta_val * scalar
	}
	return result
}

@(private)
get_glyf_offset :: proc(info: ^Font_Info, glyph_index: i32) -> i32 {
	if glyph_index >= info.num_glyphs do return -1
	if info.index_to_loc_format >= 2 do return -1

	g1, g2: i32
	if info.index_to_loc_format == 0 {
		g1 = info.glyf + i32(ttUSHORT(info.data[u32(info.loca)+u32(glyph_index)*2:])) * 2
		g2 = info.glyf + i32(ttUSHORT(info.data[u32(info.loca)+u32(glyph_index)*2+2:])) * 2
	} else {
		g1 = info.glyf + i32(ttULONG(info.data[u32(info.loca)+u32(glyph_index)*4:]))
		g2 = info.glyf + i32(ttULONG(info.data[u32(info.loca)+u32(glyph_index)*4+4:]))
	}

	return -1 if g1 == g2 else g1
}