# HG changeset patch # User Michael Pavone # Date 1520400432 28800 # Node ID 4f6e8acd7b6a2db97d2c28610654eeb1cf529b1b # Parent 9bea1a199f1511f0407956718b04f78260d151ba Added support for TTC and dfont format true type fonts. More robust font selection on Windows diff -r 9bea1a199f15 -r 4f6e8acd7b6a Makefile --- a/Makefile Wed Feb 07 19:21:44 2018 -0800 +++ b/Makefile Tue Mar 06 21:27:12 2018 -0800 @@ -129,7 +129,7 @@ Z80OBJS=z80inst.o z80_to_x86.o AUDIOOBJS=ym2612.o psg.o wave.o CONFIGOBJS=config.o tern.o util.o paths.o -NUKLEAROBJS=$(FONT) nuklear_ui/blastem_nuklear.o +NUKLEAROBJS=$(FONT) nuklear_ui/blastem_nuklear.o nuklear_ui/sfnt.o MAINOBJS=blastem.o system.o genesis.o debug.o gdb_remote.o vdp.o render_sdl.o ppm.o io.o romdb.o hash.o menu.o xband.o \ realtec.o i2c.o nor.o sega_mapper.o multi_game.o serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o $(M68KOBJS) \ diff -r 9bea1a199f15 -r 4f6e8acd7b6a nuklear_ui/blastem_nuklear.c --- a/nuklear_ui/blastem_nuklear.c Wed Feb 07 19:21:44 2018 -0800 +++ b/nuklear_ui/blastem_nuklear.c Tue Mar 06 21:27:12 2018 -0800 @@ -902,11 +902,12 @@ nk_sdl_device_create(); struct nk_font_atlas *atlas; nk_sdl_font_stash_begin(&atlas); - char *font = default_font_path(); + uint32_t font_size; + uint8_t *font = default_font(&font_size); if (!font) { fatal_error("Failed to find default font path\n"); } - struct nk_font *def_font = nk_font_atlas_add_from_file(atlas, font, 30, NULL); + struct nk_font *def_font = nk_font_atlas_add_from_memory(atlas, font, font_size, 30, NULL); nk_sdl_font_stash_end(); nk_style_set_font(context, &def_font->handle); } @@ -944,11 +945,14 @@ struct nk_font_atlas *atlas; nk_sdl_font_stash_begin(&atlas); - char *font = default_font_path(); + //char *font = default_font_path(); + uint32_t font_size; + uint8_t *font = default_font(&font_size); if (!font) { fatal_error("Failed to find default font path\n"); } - struct nk_font *def_font = nk_font_atlas_add_from_file(atlas, font, 30, NULL); + //struct nk_font *def_font = nk_font_atlas_add_from_file(atlas, font, 30, NULL); + struct nk_font *def_font = nk_font_atlas_add_from_memory(atlas, font, font_size, 30, NULL); nk_sdl_font_stash_end(); nk_style_set_font(context, &def_font->handle); current_view = file_loaded ? view_play : view_menu; diff -r 9bea1a199f15 -r 4f6e8acd7b6a nuklear_ui/font.c --- a/nuklear_ui/font.c Wed Feb 07 19:21:44 2018 -0800 +++ b/nuklear_ui/font.c Tue Mar 06 21:27:12 2018 -0800 @@ -1,5 +1,8 @@ #include #include +#include +#include "../util.h" +#include "sfnt.h" char *default_font_path(void) { @@ -24,3 +27,31 @@ return buffer; } + +uint8_t *default_font(uint32_t *size_out) +{ + char *path = default_font_path(); + if (!path) { + goto error; + } + FILE *f = fopen(path, "rb"); + if (!f) { + goto error; + } + long size = file_size(f); + uint8_t *buffer = malloc(size); + if (size != fread(buffer, 1, size, f)) { + fclose(f); + goto error; + } + fclose(f); + sfnt_container *sfnt = load_sfnt(buffer, size); + if (!sfnt) { + free(buffer); + goto error; + } + return sfnt_flatten(sfnt->tables, size_out); +error: + //TODO: try to find a suitable font in /usr/share/fonts as a fallback + return NULL; +} \ No newline at end of file diff -r 9bea1a199f15 -r 4f6e8acd7b6a nuklear_ui/font.h --- a/nuklear_ui/font.h Wed Feb 07 19:21:44 2018 -0800 +++ b/nuklear_ui/font.h Tue Mar 06 21:27:12 2018 -0800 @@ -1,6 +1,6 @@ #ifndef FONT_H_ #define FONT_H_ -char *default_font_path(void); +uint8_t *default_font(uint32_t *size_out); #endif //FONT_H_ diff -r 9bea1a199f15 -r 4f6e8acd7b6a nuklear_ui/font_win.c --- a/nuklear_ui/font_win.c Wed Feb 07 19:21:44 2018 -0800 +++ b/nuklear_ui/font_win.c Tue Mar 06 21:27:12 2018 -0800 @@ -1,40 +1,107 @@ #include #include +#include #include "../paths.h" #include "../util.h" +#include "sfnt.h" -char *default_font_path(void) +uint8_t *default_font(uint32_t *size_out) { + static const char *thin[] = {"Thin", NULL}; + static const char *extra_light[] = {"ExtraLight", "UltraLight", NULL}; + static const char *light[] = {"Light", NULL}; + static const char *regular[] = {"Regular", "Normal", "Book", NULL}; + static const char *medium[] = {"Medium", NULL}; + static const char *semi_bold[] = {"SemiBold", "DemiBold", NULL}; + static const char *bold[] = {"Bold", NULL}; + static const char *extra_bold[] = {"ExtraBold", "UltraBold", NULL}; + static const char *heavy[] = {"Heavy", "Black", NULL}; + static const char **weight_to_subfamilies[] = { + NULL, + thin, + extra_light, + light, + regular, + medium, + semi_bold, + bold, + extra_bold, + heavy + }; + NONCLIENTMETRICSA metrics = { .cbSize = sizeof(metrics) }; - char *pref_name = NULL; + char *pref_name = NULL, *pref_prefix = NULL; + const char **pref_sub_families; if (SystemParametersInfoA(SPI_GETNONCLIENTMETRICS, sizeof(metrics), &metrics, 0)) { - pref_name = metrics.lfCaptionFont.lfFaceName; + pref_name = metrics.lfMenuFont.lfFaceName; + int32_t weight = metrics.lfMenuFont.lfWeight / 100; + if (weight < 1 || weight > 9) { + weight = 4; + } + printf("Preferred family: %s, weight: %d\n", pref_name, weight); + pref_sub_families = weight_to_subfamilies[weight]; } + if (pref_name) { + uint32_t prefix_len = 0; + while (pref_name[prefix_len] && pref_name[prefix_len] != ' ') + { + prefix_len++; + } + pref_prefix = malloc(prefix_len + 1); + memcpy(pref_prefix, pref_name, prefix_len); + pref_prefix[prefix_len] = 0; + } + sfnt_table *selected = NULL; char windows[MAX_PATH]; SHGetFolderPathA(NULL, CSIDL_WINDOWS, NULL, 0, windows); char *fonts = path_append(windows, "Fonts"); size_t num_entries; - char *preferred = NULL, *tahoma = NULL, *arial = NULL; + char *tahoma = NULL, *arial = NULL; dir_entry *entries = get_dir_list(fonts, &num_entries); + char *path = NULL; for (size_t i = 0; i < num_entries; i++) { if (entries[i].is_dir) { continue; } char *ext = path_extension(entries[i].name); - if (!ext || strcasecmp(ext, "ttf")) { + if (!ext || (strcasecmp(ext, "ttf") && strcasecmp(ext, "ttc") && strcasecmp(ext, "dfont"))) { //not a truetype font, ignore free(ext); continue; } free(ext); char *base = basename_no_extension(entries[i].name); - if (!strcasecmp(base, pref_name)) { - preferred = entries[i].name; + printf("basename: %s\n", base); + if (pref_prefix && !strncasecmp(base, pref_prefix, 6)) { + path = path_append(fonts, entries[i].name); + FILE *f = fopen(path, "rb"); + if (f) + { + long font_size = file_size(f); + uint8_t *blob = malloc(font_size); + if (font_size == fread(blob, 1, font_size, f)) + { + sfnt_container *sfnt = load_sfnt(blob, font_size); + if (sfnt) { + selected = sfnt_subfamily_by_names(sfnt, pref_sub_families); + if (!selected) { + sfnt_free(sfnt); + } + } else { + free(blob); + } + } + fclose(f); + } + free(path); free(base); - break; + if (selected) { + printf("Found preferred font in %s\n", entries[i].name); + break; + } } else if (!strcasecmp(base, "tahoma")) { tahoma = entries[i].name; } else if (!strcasecmp(base, "arial")) { @@ -42,15 +109,37 @@ } free(base); } - char *path = NULL; - if (preferred) { - path = path_append(fonts, preferred); - } else if(tahoma) { - path = path_append(fonts, tahoma); - } else if(arial) { - path = path_append(fonts, arial); + if (!selected) { + path = NULL; + if (tahoma) { + path = path_append(fonts, tahoma); + } else if (arial) { + path = path_append(fonts, arial); + } + if (path) { + FILE *f = fopen(path, "rb"); + if (f) + { + long font_size = file_size(f); + uint8_t *blob = malloc(font_size); + if (font_size == fread(blob, 1, font_size, f)) + { + sfnt_container *sfnt = load_sfnt(blob, font_size); + if (sfnt) { + selected = sfnt->tables; + } else { + free(blob); + } + } + fclose(f); + } + free(path); + } } free(fonts); free_dir_list(entries, num_entries); - return path; + if (selected) { + return sfnt_flatten(selected, size_out); + } + return NULL; } diff -r 9bea1a199f15 -r 4f6e8acd7b6a nuklear_ui/sfnt.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nuklear_ui/sfnt.c Tue Mar 06 21:27:12 2018 -0800 @@ -0,0 +1,273 @@ +#include +#include +#include +#include "sfnt.h" +#include "../util.h" + +static uint32_t big32(uint8_t *src) +{ + uint32_t ret = *(src++) << 24; + ret |= *(src++) << 16; + ret |= *(src++) << 8; + ret |= *src; + return ret; +} + +static uint32_t big16(uint8_t *src) +{ + uint32_t ret = *(src++) << 8; + ret |= *src; + return ret; +} + +#define MIN_RESOURCE_MAP_SIZE (16 + 12 + 2 + 8) + +sfnt_container *load_sfnt(uint8_t *buffer, uint32_t size) +{ + if (size < 0x100) { + return NULL; + } + uint32_t sfnt_res_count, sfnt_res_offset, res_offset; + uint8_t type; + if (!memcmp(buffer, "true", 4) || !memcmp(buffer, "OTTO", 4) || !memcmp(buffer, "typ1", 4) || !memcmp(buffer, "\0\x01\0\0", 4)) { + type = CONTAINER_TTF; + } else if (!memcmp(buffer, "ttcf", 4)) { + type = CONTAINER_TTC; + } else { + static uint8_t all_zeroes[16]; + uint32_t resource_map_off = big32(buffer + 4); + if (resource_map_off + MIN_RESOURCE_MAP_SIZE > size) { + return NULL; + } + //first 16 bytes of map should match header or be all zeroes + if (memcmp(buffer, buffer + resource_map_off, 16) && memcmp(all_zeroes, buffer + resource_map_off, 16)) { + return NULL; + } + uint32_t type_start_off = resource_map_off + big16(buffer + resource_map_off + 24); + if (type_start_off + sizeof(uint16_t) > size) { + return NULL; + } + uint32_t num_types = 1 + big16(buffer + type_start_off); + if (type_start_off + sizeof(uint16_t) + 8 * num_types > size) { + return NULL; + } + res_offset = big32(buffer); + if (res_offset > size) { + return NULL; + } + uint8_t *cur = buffer + type_start_off + 2; + sfnt_res_count = 0; + for (uint32_t i = 0; i < num_types; i++, cur += 8) + { + if (!memcmp("sfnt", cur, 4)) { + sfnt_res_count = 1 + big16(cur + 4); + sfnt_res_offset = type_start_off + big16(cur + 6); + if (sfnt_res_offset + sfnt_res_count * 12 > size) { + return NULL; + } + type = CONTAINER_DFONT; + break; + } + } + if (!sfnt_res_count) { + //No "sfnt" resources in this dfont + return NULL; + } + } + sfnt_container *sfnt = calloc(1, sizeof(sfnt_container)); + sfnt->blob = buffer; + sfnt->size = size; + sfnt->container_type = type; + switch (type) + { + case CONTAINER_TTF: + sfnt->num_fonts = 1; + sfnt->tables = calloc(1, sizeof(sfnt_table)); + sfnt->tables->container = sfnt; + sfnt->tables->data = buffer + 0xC; + sfnt->tables->num_entries = big16(buffer + 4); + sfnt->tables->offset = 0; + break; + case CONTAINER_TTC: { + sfnt->num_fonts = big32(buffer+8); + sfnt->tables = calloc(sfnt->num_fonts, sizeof(sfnt_table)); + uint8_t *offsets = buffer + 0xC; + for (int i = 0; i < sfnt->num_fonts; i++, offsets += sizeof(uint32_t)) + { + uint32_t offset = big32(offsets); + sfnt->tables[i].data = buffer + offset + 0xC; + sfnt->tables[i].container = sfnt; + sfnt->tables[i].num_entries = big16(buffer + offset + 4); + sfnt->tables[i].offset = 0; + } + break; + } + case CONTAINER_DFONT:{ + sfnt->num_fonts = sfnt_res_count; + sfnt->tables = calloc(sfnt->num_fonts, sizeof(sfnt_table)); + uint8_t *cur = buffer + sfnt_res_offset; + for (int i = 0; i < sfnt->num_fonts; i++, cur += 12) + { + uint32_t offset = res_offset + (big32(cur + 4) & 0xFFFFFF); + if (offset + 4 > size) { + sfnt->tables[i].num_entries = 0; + sfnt->tables[i].data = NULL; + continue; + } + uint32_t res_size = big32(buffer + offset); + if (offset + 4 + res_size > size || res_size < 0xC) { + sfnt->tables[i].num_entries = 0; + sfnt->tables[i].data = NULL; + continue; + } + sfnt->tables[i].container = sfnt; + sfnt->tables[i].data = buffer + offset + 4 + 0xC; + sfnt->tables[i].num_entries = big16(buffer + offset + 4 + 4); + sfnt->tables[i].offset = offset + 4; + } + break; + } + } + return sfnt; +} + +uint8_t *sfnt_find_table(sfnt_table *sfnt, char *table, uint32_t *size_out) +{ + uint8_t *entry = sfnt->data; + for (int i = 0; i < sfnt->num_entries; i++, entry += 16) + { + if (!strncmp(entry, table, 4)) { + if (size_out) { + *size_out = big32(entry + 12); + } + return sfnt->container->blob + sfnt->offset + big32(entry + 8); + } + } + return NULL; +} + +char *sfnt_name(sfnt_table *sfnt, uint16_t name_type) +{ + uint32_t name_size; + uint8_t *name_table = sfnt_find_table(sfnt, "name", &name_size); + if (!name_table) { + return NULL; + } + uint16_t num_names = big16(name_table + 2); + if ((6 + num_names *12) > name_size) { + //count is too big for the name table size, abort + return NULL; + } + uint8_t *entry = name_table + 6; + uint16_t name_length = 0, name_offset; + uint8_t *unicode_entry = NULL, *macroman_entry = NULL, *winunicode_entry = NULL; + for (uint16_t i = 0; i < num_names; i++, entry += 12) + { + if (big16(entry + 6) != name_type) { + continue; + } + uint16_t language_id = big16(entry + 4); + if (language_id >= 0x8000) { + //ingore language tag records + continue; + } + uint16_t platform_id = big16(entry); + if (platform_id == 0) { + //prefer Unicode first + unicode_entry = entry; + break; + } else if (platform_id == 3 && big16(entry + 2) < 2) { + if (!winunicode_entry || (language_id & 0xFF) == 0x09) { + winunicode_entry = entry; + } + } else if (platform_id == 1 && big16(entry + 2) == 0) { + if (!macroman_entry || (language_id == 0)) { + macroman_entry = entry; + } + } + } + entry = unicode_entry ? unicode_entry : winunicode_entry ? winunicode_entry : macroman_entry; + if (entry) { + name_length = big16(entry + 8); + name_offset = big16(entry + 10); + } + if (!name_length) { + return NULL; + } + uint32_t full_off = name_offset + big16(name_table + 4); + if ((full_off + name_length) > name_size) { + return NULL; + } + if (entry == macroman_entry) { + //TODO: convert these properly to UTF-8 + char *ret = malloc(name_size + 1); + memcpy(ret, name_table + full_off, name_length); + ret[name_size] = 0; + return ret; + } else { + return utf16be_to_utf8(name_table + full_off, name_length/2); + } +} + +uint8_t *sfnt_flatten(sfnt_table *sfnt, uint32_t *size_out) +{ + uint8_t *ret = NULL;; + sfnt_container *cont = sfnt->container; + switch(cont->container_type) + { + case CONTAINER_TTF: + ret = cont->blob; + if (size_out) { + *size_out = cont->size; + } + break; + case CONTAINER_TTC: + memmove(cont->blob, sfnt->data - 0xC, 0xC + sfnt->num_entries * 12); + ret = cont->blob; + if (size_out) { + *size_out = cont->size; + } + break; + case CONTAINER_DFONT:{ + uint8_t * start = sfnt->data - 0xC; + uint32_t size = big32(start - 4); + if (size + (start-cont->blob) > cont->size) { + size = cont->size - (start-cont->blob); + } + ret = malloc(size); + memcpy(ret, start, size); + free(cont->blob); + if (size_out) { + *size_out = size; + } + break; + } + } + free(cont->tables); + free(cont); + return ret; +} + +sfnt_table *sfnt_subfamily_by_names(sfnt_container *sfnt, const char **names) +{ + for (int i = 0; i < sfnt->num_fonts; i++) + { + for (const char **name = names; *name; name++) + { + char *font_subfam = sfnt_name(sfnt->tables + i, SFNT_SUBFAMILY); + if (font_subfam && !strcasecmp(*name, font_subfam)) { + free(font_subfam); + return sfnt->tables + i; + } + free(font_subfam); + } + } + return NULL; +} + +void sfnt_free(sfnt_container *sfnt) +{ + free(sfnt->tables); + free(sfnt->blob); + free(sfnt); +} diff -r 9bea1a199f15 -r 4f6e8acd7b6a nuklear_ui/sfnt.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nuklear_ui/sfnt.h Tue Mar 06 21:27:12 2018 -0800 @@ -0,0 +1,44 @@ +#ifndef SFNT_H_ +#define SFNT_H_ + +#include +enum { + CONTAINER_TTF, + CONTAINER_TTC, + CONTAINER_DFONT +}; + +enum { + SFNT_COPYRIGHT, + SFNT_FAMILY, + SFNT_SUBFAMILY, + SFNT_SUBFAMILY_UNIQUE, + SFNT_FULL_NAME, + SFNT_VERSION, + SFNT_POSTSCRIPT, + //TODO: add the rest of the name IDs +}; + +typedef struct sfnt_container sfnt_container; +typedef struct { + uint8_t *data; + sfnt_container *container; + uint32_t offset; + uint16_t num_entries; +} sfnt_table; + +struct sfnt_container { + uint8_t *blob; + sfnt_table *tables; + uint32_t size; + uint8_t num_fonts; + uint8_t container_type; +}; + +sfnt_container *load_sfnt(uint8_t *buffer, uint32_t size); +char *sfnt_name(sfnt_table *sfnt, uint16_t name_type); +uint8_t *sfnt_flatten(sfnt_table *sfnt, uint32_t *size_out); +sfnt_table *sfnt_subfamily_by_names(sfnt_container *sfnt, const char **names); +void sfnt_free(sfnt_container *sfnt); + +#endif // SFNT_H_ \ No newline at end of file diff -r 9bea1a199f15 -r 4f6e8acd7b6a util.c --- a/util.c Wed Feb 07 19:21:44 2018 -0800 +++ b/util.c Tue Mar 06 21:27:12 2018 -0800 @@ -204,6 +204,50 @@ *(output++) = 0; } +char *utf16be_to_utf8(uint8_t *buf, uint32_t max_size) +{ + uint8_t *cur = buf; + uint32_t converted_size = 0; + for (uint32_t i = 0; i < max_size; i++, cur+=2) + { + uint16_t code = *cur << 16 | cur[1]; + if (!code) { + break; + } + if (code < 0x80) { + converted_size++; + } else if (code < 0x800) { + converted_size += 2; + } else { + //TODO: Deal with surrogate pairs + converted_size += 3; + } + } + char *out = malloc(converted_size + 1); + char *cur_out = out; + cur = buf; + for (uint32_t i = 0; i < max_size; i++, cur+=2) + { + uint16_t code = *cur << 16 | cur[1]; + if (!code) { + break; + } + if (code < 0x80) { + *(cur_out++) = code; + } else if (code < 0x800) { + *(cur_out++) = 0xC0 | code >> 6; + *(cur_out++) = 0x80 | (code & 0x3F); + } else { + //TODO: Deal with surrogate pairs + *(cur_out++) = 0xF0 | code >> 12; + *(cur_out++) = 0x80 | (code >> 6 & 0x3F); + *(cur_out++) = 0x80 | (code & 0x3F); + } + } + *cur_out = 0; + return out; +} + char is_path_sep(char c) { #ifdef _WIN32 diff -r 9bea1a199f15 -r 4f6e8acd7b6a util.h --- a/util.h Wed Feb 07 19:21:44 2018 -0800 +++ b/util.h Tue Mar 06 21:27:12 2018 -0800 @@ -34,6 +34,8 @@ char * split_keyval(char * text); //Takes a binary byte buffer and produces a lowercase hex string void bin_to_hex(uint8_t *output, uint8_t *input, uint64_t size); +//Takes an (optionally) null-terminated UTF16-BE string and converts a maximum of max_size code-units to UTF-8 +char *utf16be_to_utf8(uint8_t *buf, uint32_t max_size); //Determines whether a character is a valid path separator for the current platform char is_path_sep(char c); //Determines whether a path is considered an absolute path on the current platform