changeset 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 9bea1a199f15
children 855210dca5b9
files Makefile nuklear_ui/blastem_nuklear.c nuklear_ui/font.c nuklear_ui/font.h nuklear_ui/font_win.c nuklear_ui/sfnt.c nuklear_ui/sfnt.h util.c util.h
diffstat 9 files changed, 509 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- 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) \
--- 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;
--- 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 <stdio.h>
 #include <stdlib.h>
+#include <stdint.h>
+#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
--- 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_
--- 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 <windows.h>
 #include <shlobj.h>
+#include <string.h>
 #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;
 }
--- /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);
+}
--- /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 <stdint.h>
+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
--- 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
--- 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