diff 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
line wrap: on
line diff
--- /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 <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+#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);
+}