Harbor

branch main
showing the latest snapshot on main
names.odin 5.9 KB · Plain text
names.odin 0644 Raw
package font

// ============================================================================
// Font Names API
// ============================================================================

// Compare UTF-8 string prefix against big-endian UTF-16 string
// Returns number of UTF-8 bytes matched, or -1 on mismatch
@(private)
compare_utf8_to_utf16_bigendian_prefix :: proc(s1: [^]u8, len1: i32, s2: [^]u8, len2: i32) -> i32 {
	i: i32 = 0
	l2 := len2
	s2_off: i32 = 0

	for l2 > 0 {
		ch := u16(s2[s2_off]) * 256 + u16(s2[s2_off + 1])
		if ch < 0x80 {
			if i >= len1 do return -1
			if s1[i] != u8(ch) do return -1
			i += 1
		} else if ch < 0x800 {
			if i + 1 >= len1 do return -1
			if s1[i] != 0xc0 + u8(ch >> 6) do return -1
			i += 1
			if s1[i] != 0x80 + u8(ch & 0x3f) do return -1
			i += 1
		} else if ch >= 0xd800 && ch < 0xdc00 {
			// Surrogate pair
			ch2 := u16(s2[s2_off + 2]) * 256 + u16(s2[s2_off + 3])
			if i + 3 >= len1 do return -1
			c := (u32(ch - 0xd800) << 10) + u32(ch2 - 0xdc00) + 0x10000
			if s1[i] != 0xf0 + u8(c >> 18) do return -1
			i += 1
			if s1[i] != 0x80 + u8((c >> 12) & 0x3f) do return -1
			i += 1
			if s1[i] != 0x80 + u8((c >> 6) & 0x3f) do return -1
			i += 1
			if s1[i] != 0x80 + u8(c & 0x3f) do return -1
			i += 1
			s2_off += 2
			l2 -= 2
		} else if ch >= 0xdc00 && ch < 0xe000 {
			return -1 // Invalid lone low surrogate
		} else {
			if i + 2 >= len1 do return -1
			if s1[i] != 0xe0 + u8(ch >> 12) do return -1
			i += 1
			if s1[i] != 0x80 + u8((ch >> 6) & 0x3f) do return -1
			i += 1
			if s1[i] != 0x80 + u8(ch & 0x3f) do return -1
			i += 1
		}
		s2_off += 2
		l2 -= 2
	}
	return i
}

// Compare UTF-8 string to big-endian UTF-16 string
// Returns true if they are equal
compare_utf8_to_utf16_bigendian :: proc(s1: cstring, len1: i32, s2: cstring, len2: i32) -> bool {
	return compare_utf8_to_utf16_bigendian_prefix(
		transmute([^]u8)s1, len1,
		transmute([^]u8)s2, len2,
	) == len1
}

// Match font name against name table entries, handling family+subfamily pairs
@(private)
matchpair :: proc(fc: [^]u8, nm: u32, name: [^]u8, nlen: i32, target_id: i32, next_id: i32) -> bool {
	count := i32(ttUSHORT(fc[nm + 2:]))
	string_offset := nm + u32(ttUSHORT(fc[nm + 4:]))

	for i in 0 ..< count {
		loc := nm + 6 + u32(12 * i)
		id := i32(ttUSHORT(fc[loc + 6:]))
		if id == target_id {
			platform := i32(ttUSHORT(fc[loc + 0:]))
			encoding := i32(ttUSHORT(fc[loc + 2:]))
			language := i32(ttUSHORT(fc[loc + 4:]))

			// Is this a Unicode encoding?
			if platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10) {
				slen := i32(ttUSHORT(fc[loc + 8:]))
				off := i32(ttUSHORT(fc[loc + 10:]))

				// Check if there's a prefix match
				matchlen := compare_utf8_to_utf16_bigendian_prefix(name, nlen, fc[string_offset + u32(off):], slen)
				if matchlen >= 0 {
					// Check for target_id+1 immediately following, with same encoding & language
					if i + 1 < count &&
					   i32(ttUSHORT(fc[loc + 12 + 6:])) == next_id &&
					   i32(ttUSHORT(fc[loc + 12:])) == platform &&
					   i32(ttUSHORT(fc[loc + 12 + 2:])) == encoding &&
					   i32(ttUSHORT(fc[loc + 12 + 4:])) == language {
						slen = i32(ttUSHORT(fc[loc + 12 + 8:]))
						off = i32(ttUSHORT(fc[loc + 12 + 10:]))
						if slen == 0 {
							if matchlen == nlen {
								return true
							}
						} else if matchlen < nlen && name[matchlen] == ' ' {
							matchlen += 1
							if compare_utf8_to_utf16_bigendian_prefix(
								   name[matchlen:],
								   nlen - matchlen,
								   fc[string_offset + u32(off):],
								   slen,
							   ) ==
							   nlen - matchlen {
								return true
							}
						}
					} else {
						// If nothing immediately following
						if matchlen == nlen {
							return true
						}
					}
				}
			}
		}
	}
	return false
}

// Check if font matches name and style flags
@(private)
matches :: proc(fc: [^]u8, offset: u32, name: [^]u8, flags: i32) -> bool {
	// Get string length
	nlen: i32 = 0
	for name[nlen] != 0 {
		nlen += 1
	}

	if !is_font(fc[offset:]) do return false

	// Check italics/bold/underline flags in macStyle
	if flags != 0 {
		hd := find_table(fc, offset, "head")
		if (i32(ttUSHORT(fc[hd + 44:])) & 7) != (flags & 7) do return false
	}

	nm := find_table(fc, offset, "name")
	if nm == 0 do return false

	if flags != 0 {
		// If we checked the macStyle flags, then just check the family and ignore the subfamily
		if matchpair(fc, nm, name, nlen, 16, -1) do return true
		if matchpair(fc, nm, name, nlen, 1, -1) do return true
		if matchpair(fc, nm, name, nlen, 3, -1) do return true
	} else {
		if matchpair(fc, nm, name, nlen, 16, 17) do return true
		if matchpair(fc, nm, name, nlen, 1, 2) do return true
		if matchpair(fc, nm, name, nlen, 3, -1) do return true
	}

	return false
}

// Get font name string from name table
// Returns pointer to string data (may be big-endian UTF-16) and sets length
get_font_name_string :: proc(
	font: ^Font_Info,
	length: ^i32,
	platform_id, encoding_id, language_id, name_id: i32,
) -> cstring {
	fc := font.data
	offset := u32(font.fontstart)
	nm := find_table(fc, offset, "name")
	if nm == 0 do return nil

	count := i32(ttUSHORT(fc[nm + 2:]))
	string_offset := nm + u32(ttUSHORT(fc[nm + 4:]))

	for i in 0 ..< count {
		loc := nm + 6 + u32(12 * i)
		if platform_id == i32(ttUSHORT(fc[loc + 0:])) &&
		   encoding_id == i32(ttUSHORT(fc[loc + 2:])) &&
		   language_id == i32(ttUSHORT(fc[loc + 4:])) &&
		   name_id == i32(ttUSHORT(fc[loc + 6:])) {
			length^ = i32(ttUSHORT(fc[loc + 8:]))
			return cstring(&fc[string_offset + u32(ttUSHORT(fc[loc + 10:]))])
		}
	}
	return nil
}

// Find font by name in a font collection
// Returns font offset or -1 if not found
find_matching_font :: proc(fontdata: [^]u8, name: cstring, flags: i32) -> i32 {
	for i: i32 = 0;; i += 1 {
		off := get_font_offset_for_index(fontdata, i)
		if off < 0 do return off
		if matches(fontdata, u32(off), transmute([^]u8)name, flags) {
			return off
		}
	}
}