comparison nuklear_ui/sfnt.c @ 1527:4f6e8acd7b6a nuklear_ui

Added support for TTC and dfont format true type fonts. More robust font selection on Windows
author Michael Pavone <pavone@retrodev.com>
date Tue, 06 Mar 2018 21:27:12 -0800
parents
children 098c11aaf8f0
comparison
equal deleted inserted replaced
1526:9bea1a199f15 1527:4f6e8acd7b6a
1 #include <stddef.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include "sfnt.h"
5 #include "../util.h"
6
7 static uint32_t big32(uint8_t *src)
8 {
9 uint32_t ret = *(src++) << 24;
10 ret |= *(src++) << 16;
11 ret |= *(src++) << 8;
12 ret |= *src;
13 return ret;
14 }
15
16 static uint32_t big16(uint8_t *src)
17 {
18 uint32_t ret = *(src++) << 8;
19 ret |= *src;
20 return ret;
21 }
22
23 #define MIN_RESOURCE_MAP_SIZE (16 + 12 + 2 + 8)
24
25 sfnt_container *load_sfnt(uint8_t *buffer, uint32_t size)
26 {
27 if (size < 0x100) {
28 return NULL;
29 }
30 uint32_t sfnt_res_count, sfnt_res_offset, res_offset;
31 uint8_t type;
32 if (!memcmp(buffer, "true", 4) || !memcmp(buffer, "OTTO", 4) || !memcmp(buffer, "typ1", 4) || !memcmp(buffer, "\0\x01\0\0", 4)) {
33 type = CONTAINER_TTF;
34 } else if (!memcmp(buffer, "ttcf", 4)) {
35 type = CONTAINER_TTC;
36 } else {
37 static uint8_t all_zeroes[16];
38 uint32_t resource_map_off = big32(buffer + 4);
39 if (resource_map_off + MIN_RESOURCE_MAP_SIZE > size) {
40 return NULL;
41 }
42 //first 16 bytes of map should match header or be all zeroes
43 if (memcmp(buffer, buffer + resource_map_off, 16) && memcmp(all_zeroes, buffer + resource_map_off, 16)) {
44 return NULL;
45 }
46 uint32_t type_start_off = resource_map_off + big16(buffer + resource_map_off + 24);
47 if (type_start_off + sizeof(uint16_t) > size) {
48 return NULL;
49 }
50 uint32_t num_types = 1 + big16(buffer + type_start_off);
51 if (type_start_off + sizeof(uint16_t) + 8 * num_types > size) {
52 return NULL;
53 }
54 res_offset = big32(buffer);
55 if (res_offset > size) {
56 return NULL;
57 }
58 uint8_t *cur = buffer + type_start_off + 2;
59 sfnt_res_count = 0;
60 for (uint32_t i = 0; i < num_types; i++, cur += 8)
61 {
62 if (!memcmp("sfnt", cur, 4)) {
63 sfnt_res_count = 1 + big16(cur + 4);
64 sfnt_res_offset = type_start_off + big16(cur + 6);
65 if (sfnt_res_offset + sfnt_res_count * 12 > size) {
66 return NULL;
67 }
68 type = CONTAINER_DFONT;
69 break;
70 }
71 }
72 if (!sfnt_res_count) {
73 //No "sfnt" resources in this dfont
74 return NULL;
75 }
76 }
77 sfnt_container *sfnt = calloc(1, sizeof(sfnt_container));
78 sfnt->blob = buffer;
79 sfnt->size = size;
80 sfnt->container_type = type;
81 switch (type)
82 {
83 case CONTAINER_TTF:
84 sfnt->num_fonts = 1;
85 sfnt->tables = calloc(1, sizeof(sfnt_table));
86 sfnt->tables->container = sfnt;
87 sfnt->tables->data = buffer + 0xC;
88 sfnt->tables->num_entries = big16(buffer + 4);
89 sfnt->tables->offset = 0;
90 break;
91 case CONTAINER_TTC: {
92 sfnt->num_fonts = big32(buffer+8);
93 sfnt->tables = calloc(sfnt->num_fonts, sizeof(sfnt_table));
94 uint8_t *offsets = buffer + 0xC;
95 for (int i = 0; i < sfnt->num_fonts; i++, offsets += sizeof(uint32_t))
96 {
97 uint32_t offset = big32(offsets);
98 sfnt->tables[i].data = buffer + offset + 0xC;
99 sfnt->tables[i].container = sfnt;
100 sfnt->tables[i].num_entries = big16(buffer + offset + 4);
101 sfnt->tables[i].offset = 0;
102 }
103 break;
104 }
105 case CONTAINER_DFONT:{
106 sfnt->num_fonts = sfnt_res_count;
107 sfnt->tables = calloc(sfnt->num_fonts, sizeof(sfnt_table));
108 uint8_t *cur = buffer + sfnt_res_offset;
109 for (int i = 0; i < sfnt->num_fonts; i++, cur += 12)
110 {
111 uint32_t offset = res_offset + (big32(cur + 4) & 0xFFFFFF);
112 if (offset + 4 > size) {
113 sfnt->tables[i].num_entries = 0;
114 sfnt->tables[i].data = NULL;
115 continue;
116 }
117 uint32_t res_size = big32(buffer + offset);
118 if (offset + 4 + res_size > size || res_size < 0xC) {
119 sfnt->tables[i].num_entries = 0;
120 sfnt->tables[i].data = NULL;
121 continue;
122 }
123 sfnt->tables[i].container = sfnt;
124 sfnt->tables[i].data = buffer + offset + 4 + 0xC;
125 sfnt->tables[i].num_entries = big16(buffer + offset + 4 + 4);
126 sfnt->tables[i].offset = offset + 4;
127 }
128 break;
129 }
130 }
131 return sfnt;
132 }
133
134 uint8_t *sfnt_find_table(sfnt_table *sfnt, char *table, uint32_t *size_out)
135 {
136 uint8_t *entry = sfnt->data;
137 for (int i = 0; i < sfnt->num_entries; i++, entry += 16)
138 {
139 if (!strncmp(entry, table, 4)) {
140 if (size_out) {
141 *size_out = big32(entry + 12);
142 }
143 return sfnt->container->blob + sfnt->offset + big32(entry + 8);
144 }
145 }
146 return NULL;
147 }
148
149 char *sfnt_name(sfnt_table *sfnt, uint16_t name_type)
150 {
151 uint32_t name_size;
152 uint8_t *name_table = sfnt_find_table(sfnt, "name", &name_size);
153 if (!name_table) {
154 return NULL;
155 }
156 uint16_t num_names = big16(name_table + 2);
157 if ((6 + num_names *12) > name_size) {
158 //count is too big for the name table size, abort
159 return NULL;
160 }
161 uint8_t *entry = name_table + 6;
162 uint16_t name_length = 0, name_offset;
163 uint8_t *unicode_entry = NULL, *macroman_entry = NULL, *winunicode_entry = NULL;
164 for (uint16_t i = 0; i < num_names; i++, entry += 12)
165 {
166 if (big16(entry + 6) != name_type) {
167 continue;
168 }
169 uint16_t language_id = big16(entry + 4);
170 if (language_id >= 0x8000) {
171 //ingore language tag records
172 continue;
173 }
174 uint16_t platform_id = big16(entry);
175 if (platform_id == 0) {
176 //prefer Unicode first
177 unicode_entry = entry;
178 break;
179 } else if (platform_id == 3 && big16(entry + 2) < 2) {
180 if (!winunicode_entry || (language_id & 0xFF) == 0x09) {
181 winunicode_entry = entry;
182 }
183 } else if (platform_id == 1 && big16(entry + 2) == 0) {
184 if (!macroman_entry || (language_id == 0)) {
185 macroman_entry = entry;
186 }
187 }
188 }
189 entry = unicode_entry ? unicode_entry : winunicode_entry ? winunicode_entry : macroman_entry;
190 if (entry) {
191 name_length = big16(entry + 8);
192 name_offset = big16(entry + 10);
193 }
194 if (!name_length) {
195 return NULL;
196 }
197 uint32_t full_off = name_offset + big16(name_table + 4);
198 if ((full_off + name_length) > name_size) {
199 return NULL;
200 }
201 if (entry == macroman_entry) {
202 //TODO: convert these properly to UTF-8
203 char *ret = malloc(name_size + 1);
204 memcpy(ret, name_table + full_off, name_length);
205 ret[name_size] = 0;
206 return ret;
207 } else {
208 return utf16be_to_utf8(name_table + full_off, name_length/2);
209 }
210 }
211
212 uint8_t *sfnt_flatten(sfnt_table *sfnt, uint32_t *size_out)
213 {
214 uint8_t *ret = NULL;;
215 sfnt_container *cont = sfnt->container;
216 switch(cont->container_type)
217 {
218 case CONTAINER_TTF:
219 ret = cont->blob;
220 if (size_out) {
221 *size_out = cont->size;
222 }
223 break;
224 case CONTAINER_TTC:
225 memmove(cont->blob, sfnt->data - 0xC, 0xC + sfnt->num_entries * 12);
226 ret = cont->blob;
227 if (size_out) {
228 *size_out = cont->size;
229 }
230 break;
231 case CONTAINER_DFONT:{
232 uint8_t * start = sfnt->data - 0xC;
233 uint32_t size = big32(start - 4);
234 if (size + (start-cont->blob) > cont->size) {
235 size = cont->size - (start-cont->blob);
236 }
237 ret = malloc(size);
238 memcpy(ret, start, size);
239 free(cont->blob);
240 if (size_out) {
241 *size_out = size;
242 }
243 break;
244 }
245 }
246 free(cont->tables);
247 free(cont);
248 return ret;
249 }
250
251 sfnt_table *sfnt_subfamily_by_names(sfnt_container *sfnt, const char **names)
252 {
253 for (int i = 0; i < sfnt->num_fonts; i++)
254 {
255 for (const char **name = names; *name; name++)
256 {
257 char *font_subfam = sfnt_name(sfnt->tables + i, SFNT_SUBFAMILY);
258 if (font_subfam && !strcasecmp(*name, font_subfam)) {
259 free(font_subfam);
260 return sfnt->tables + i;
261 }
262 free(font_subfam);
263 }
264 }
265 return NULL;
266 }
267
268 void sfnt_free(sfnt_container *sfnt)
269 {
270 free(sfnt->tables);
271 free(sfnt->blob);
272 free(sfnt);
273 }