1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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
}