Harbor

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

// ============================================================================
// COLR/CPAL — Color Fonts
// ============================================================================

// RGBA color
RGBA_Color :: struct {
	r, g, b, a: u8,
}

// A color layer in a COLR v0 glyph
Color_Layer :: struct {
	glyph_index: i32,
	palette_entry: u16,
}

// Get the number of color palettes in the font (from CPAL table)
get_color_palette_count :: proc(info: ^Font_Info) -> i32 {
	cpal := find_table(info.data, u32(info.fontstart), "CPAL")
	if cpal == 0 do return 0

	data := info.data[cpal:]
	version := ttUSHORT(data)
	if version > 1 do return 0

	return i32(ttUSHORT(data[4:]))
}

// Get a color from a palette. Returns false if indices are out of range.
get_color_from_palette :: proc(info: ^Font_Info, palette_index: i32, entry_index: i32, color: ^RGBA_Color) -> bool {
	cpal := find_table(info.data, u32(info.fontstart), "CPAL")
	if cpal == 0 do return false

	data := info.data[cpal:]
	version := ttUSHORT(data)
	if version > 1 do return false

	num_entries := i32(ttUSHORT(data[2:]))
	num_palettes := i32(ttUSHORT(data[4:]))
	// num_colors := i32(ttUSHORT(data[6:]))
	color_records_offset := u32(ttULONG(data[8:]))

	if palette_index < 0 || palette_index >= num_palettes do return false
	if entry_index < 0 || entry_index >= num_entries do return false

	// Color indices array starts at offset 12
	first_color_index := i32(ttUSHORT(data[12 + u32(palette_index) * 2:]))
	color_idx := first_color_index + entry_index

	// Color records are BGRA (4 bytes each)
	rec := data[color_records_offset + u32(color_idx) * 4:]
	color.b = rec[0]
	color.g = rec[1]
	color.r = rec[2]
	color.a = rec[3]
	return true
}

// Get color layers for a glyph (COLR v0).
// Returns the number of layers, or 0 if the glyph has no color layers.
// Layers are written to the provided slice.
get_glyph_color_layers :: proc(info: ^Font_Info, glyph_index: i32, layers: []Color_Layer) -> i32 {
	colr := find_table(info.data, u32(info.fontstart), "COLR")
	if colr == 0 do return 0

	data := info.data[colr:]
	version := ttUSHORT(data)

	// We support COLR v0 (layered glyphs)
	if version > 1 do return 0

	num_base_glyphs := i32(ttUSHORT(data[2:]))
	base_glyph_offset := u32(ttULONG(data[4:]))
	layer_records_offset := u32(ttULONG(data[8:]))
	// num_layer_records := i32(ttUSHORT(data[12:]))

	// Binary search for the glyph in base glyph records
	base_glyphs := data[base_glyph_offset:]
	l: i32 = 0
	r := num_base_glyphs - 1

	for l <= r {
		m := (l + r) >> 1
		rec := base_glyphs[m * 6:]
		gid := i32(ttUSHORT(rec))
		if glyph_index < gid {
			r = m - 1
		} else if glyph_index > gid {
			l = m + 1
		} else {
			first_layer := i32(ttUSHORT(rec[2:]))
			num_layers := i32(ttUSHORT(rec[4:]))

			n := min(num_layers, i32(len(layers)))
			layer_data := data[layer_records_offset:]
			for i in 0..<n {
				layer_rec := layer_data[(first_layer + i) * 4:]
				layers[i] = {
					glyph_index   = i32(ttUSHORT(layer_rec)),
					palette_entry = ttUSHORT(layer_rec[2:]),
				}
			}
			return num_layers
		}
	}

	return 0
}

// Check if a glyph has color layers
is_color_glyph :: proc(info: ^Font_Info, glyph_index: i32) -> bool {
	// Check COLR
	colr := find_table(info.data, u32(info.fontstart), "COLR")
	if colr != 0 {
		data := info.data[colr:]
		num_base := i32(ttUSHORT(data[2:]))
		base_off := u32(ttULONG(data[4:]))
		base_glyphs := data[base_off:]

		l: i32 = 0
		r := num_base - 1
		for l <= r {
			m := (l + r) >> 1
			gid := i32(ttUSHORT(base_glyphs[m * 6:]))
			if glyph_index < gid {
				r = m - 1
			} else if glyph_index > gid {
				l = m + 1
			} else {
				return true
			}
		}
	}

	// Also check SVG
	if info.svg != 0 {
		svg_data: [^]u8
		if get_glyph_svg(info, glyph_index, &svg_data) > 0 {
			return true
		}
	}

	return false
}