Merge branch '2023-07-14-nuvoton-platform-updates'

- A number of updates for the nuvoton family of platforms
diff --git a/arch/sandbox/cpu/sdl.c b/arch/sandbox/cpu/sdl.c
index 2c570ed..590e406 100644
--- a/arch/sandbox/cpu/sdl.c
+++ b/arch/sandbox/cpu/sdl.c
@@ -6,6 +6,7 @@
 #include <errno.h>
 #include <unistd.h>
 #include <stdbool.h>
+#include <sysreset.h>
 #include <linux/input.h>
 #include <SDL2/SDL.h>
 #include <asm/state.h>
@@ -81,7 +82,7 @@
 		switch (event.type) {
 		case SDL_QUIT:
 			puts("LCD window closed - quitting\n");
-			reset_cpu();
+			sysreset_walk(SYSRESET_POWER_OFF);
 			break;
 		}
 	}
diff --git a/arch/sandbox/dts/cedit.dtsi b/arch/sandbox/dts/cedit.dtsi
new file mode 100644
index 0000000..a9eb4c2
--- /dev/null
+++ b/arch/sandbox/dts/cedit.dtsi
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Expo definition for the configuration editor
+ *
+ * This used for testing building an expo from a data file. This devicetree
+ * provides a description of the objects to be created.
+ */
+
+#include <test/cedit-test.h>
+
+&cedit {
+	dynamic-start = <ID_DYNAMIC_START>;
+
+	scenes {
+		main {
+			id = <ID_SCENE1>;
+
+			/* value refers to the matching id in /strings */
+			title-id = <ID_SCENE1_TITLE>;
+
+			/* simple string is used as it is */
+			prompt = "UP and DOWN to choose, ENTER to select";
+
+			/* defines a menu within the scene */
+			cpu-speed {
+				type = "menu";
+				id = <ID_CPU_SPEED>;
+
+				/*
+				 * has both string and ID. The string is ignored
+				 * if the ID is present and points to a string
+				 */
+				title = "CPU speed";
+				title-id = <ID_CPU_SPEED_TITLE>;
+
+				/* menu items as simple strings */
+				item-label = "2 GHz", "2.5 GHz", "3 GHz";
+
+				/* IDs for the menu items */
+				item-id = <ID_CPU_SPEED_1 ID_CPU_SPEED_2
+					ID_CPU_SPEED_3>;
+			};
+
+			power-loss {
+				type = "menu";
+				id = <ID_POWER_LOSS>;
+
+				title = "AC Power";
+				item-label = "Always Off", "Always On",
+					"Memory";
+
+				item-id = <ID_AC_OFF ID_AC_ON ID_AC_MEMORY>;
+			};
+		};
+	};
+
+	strings {
+		title {
+			id = <ID_SCENE1_TITLE>;
+			value = "Test Configuration";
+			value-es = "configuración de prueba";
+		};
+	};
+};
diff --git a/arch/sandbox/dts/sandbox.dtsi b/arch/sandbox/dts/sandbox.dtsi
index 30a305c..f0ee0b3 100644
--- a/arch/sandbox/dts/sandbox.dtsi
+++ b/arch/sandbox/dts/sandbox.dtsi
@@ -16,6 +16,12 @@
 		stdout-path = "/serial";
 	};
 
+	cedit-theme {
+		font-size = <30>;
+		menu-inset = <3>;
+		menuitem-gap-y = <1>;
+	};
+
 	alarm_wdt: alarm-wdt {
 		compatible = "sandbox,alarm-wdt";
 		timeout-sec = <5>;
diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
index ff9f922..b5509ee 100644
--- a/arch/sandbox/dts/test.dts
+++ b/arch/sandbox/dts/test.dts
@@ -96,6 +96,8 @@
 
 		theme {
 			font-size = <30>;
+			menu-inset = <3>;
+			menuitem-gap-y = <1>;
 		};
 
 		/*
@@ -139,6 +141,15 @@
 		};
 	};
 
+	cedit: cedit {
+	};
+
+	cedit-theme {
+		font-size = <30>;
+		menu-inset = <3>;
+		menuitem-gap-y = <1>;
+	};
+
 	fuzzing-engine {
 		compatible = "sandbox,fuzzing-engine";
 	};
@@ -1828,3 +1839,5 @@
 #ifdef CONFIG_SANDBOX_VPL
 #include "sandbox_vpl.dtsi"
 #endif
+
+#include "cedit.dtsi"
diff --git a/boot/Kconfig b/boot/Kconfig
index a643a3d..c8b8f36 100644
--- a/boot/Kconfig
+++ b/boot/Kconfig
@@ -1630,4 +1630,18 @@
 	  If no initramfs was provided by previous bootloader, no env variables
 	  will be created.
 
+menu "Configuration editor"
+
+config CEDIT
+	bool "Configuration editor"
+	depends on BOOTSTD
+	help
+	  Provides a way to deal with board configuration and present it to
+	  the user for adjustment.
+
+	  This is intended to provide both graphical and text-based user
+	  interfaces, but only graphical is support at present.
+
+endmenu		# Configuration editor
+
 endmenu		# Booting
diff --git a/boot/Makefile b/boot/Makefile
index f94c31d..f828f87 100644
--- a/boot/Makefile
+++ b/boot/Makefile
@@ -33,6 +33,7 @@
 obj-$(CONFIG_CMD_BOOTEFI_BOOTMGR) += bootmeth_efi_mgr.o
 obj-$(CONFIG_$(SPL_TPL_)EXPO) += bootflow_menu.o
 obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootflow_menu.o
+obj-$(CONFIG_$(SPL_TPL_)CEDIT) += cedit.o
 endif
 
 obj-$(CONFIG_$(SPL_TPL_)OF_LIBFDT) += image-fdt.o
@@ -50,7 +51,7 @@
 obj-$(CONFIG_SPL_LOAD_FIT) += common_fit.o
 endif
 
-obj-$(CONFIG_$(SPL_TPL_)EXPO) += expo.o scene.o scene_menu.o
+obj-$(CONFIG_$(SPL_TPL_)EXPO) += expo.o scene.o scene_menu.o expo_build.o
 
 obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE) += vbe.o
 obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_REQUEST) += vbe_request.o
diff --git a/boot/bootflow_menu.c b/boot/bootflow_menu.c
index 7f06dac..7c1abe5 100644
--- a/boot/bootflow_menu.c
+++ b/boot/bootflow_menu.c
@@ -124,6 +124,10 @@
 		priv->num_bootflows++;
 	}
 
+	ret = scene_arrange(scn);
+	if (ret)
+		return log_msg_ret("arr", ret);
+
 	*expp = exp;
 
 	return 0;
@@ -205,7 +209,7 @@
 		return log_msg_ret("scn", ret);
 
 	if (text_mode)
-		exp_set_text_mode(exp, text_mode);
+		expo_set_text_mode(exp, text_mode);
 
 	done = false;
 	do {
diff --git a/boot/bootmeth-uclass.c b/boot/bootmeth-uclass.c
index 3b3e061..701ee8a 100644
--- a/boot/bootmeth-uclass.c
+++ b/boot/bootmeth-uclass.c
@@ -301,32 +301,6 @@
 	return 0;
 }
 
-static int alloc_file(const char *fname, uint size, void **bufp)
-{
-	loff_t bytes_read;
-	ulong addr;
-	char *buf;
-	int ret;
-
-	buf = malloc(size + 1);
-	if (!buf)
-		return log_msg_ret("buf", -ENOMEM);
-	addr = map_to_sysmem(buf);
-
-	ret = fs_read(fname, addr, 0, size, &bytes_read);
-	if (ret) {
-		free(buf);
-		return log_msg_ret("read", ret);
-	}
-	if (size != bytes_read)
-		return log_msg_ret("bread", -EIO);
-	buf[size] = '\0';
-
-	*bufp = buf;
-
-	return 0;
-}
-
 int bootmeth_alloc_file(struct bootflow *bflow, uint size_limit, uint align)
 {
 	void *buf;
@@ -338,7 +312,7 @@
 	if (size > size_limit)
 		return log_msg_ret("chk", -E2BIG);
 
-	ret = alloc_file(bflow->fname, bflow->size, &buf);
+	ret = fs_read_alloc(bflow->fname, bflow->size, align, &buf);
 	if (ret)
 		return log_msg_ret("all", ret);
 
@@ -374,7 +348,7 @@
 	if (ret)
 		return log_msg_ret("fs", ret);
 
-	ret = alloc_file(path, size, &buf);
+	ret = fs_read_alloc(path, size, 0, &buf);
 	if (ret)
 		return log_msg_ret("all", ret);
 
diff --git a/boot/cedit.c b/boot/cedit.c
new file mode 100644
index 0000000..ee24658
--- /dev/null
+++ b/boot/cedit.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Implementation of configuration editor
+ *
+ * Copyright 2023 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <cli.h>
+#include <dm.h>
+#include <expo.h>
+#include <menu.h>
+#include <video.h>
+#include <linux/delay.h>
+#include "scene_internal.h"
+
+int cedit_arange(struct expo *exp, struct video_priv *vpriv, uint scene_id)
+{
+	struct scene_obj_txt *txt;
+	struct scene_obj *obj;
+	struct scene *scn;
+	int y;
+
+	scn = expo_lookup_scene_id(exp, scene_id);
+	if (!scn)
+		return log_msg_ret("scn", -ENOENT);
+
+	txt = scene_obj_find_by_name(scn, "prompt");
+	if (txt)
+		scene_obj_set_pos(scn, txt->obj.id, 0, vpriv->ysize - 50);
+
+	txt = scene_obj_find_by_name(scn, "title");
+	if (txt)
+		scene_obj_set_pos(scn, txt->obj.id, 200, 10);
+
+	y = 100;
+	list_for_each_entry(obj, &scn->obj_head, sibling) {
+		if (obj->type == SCENEOBJT_MENU) {
+			scene_obj_set_pos(scn, obj->id, 50, y);
+			scene_menu_arrange(scn, (struct scene_obj_menu *)obj);
+			y += 50;
+		}
+	}
+
+	return 0;
+}
+
+int cedit_run(struct expo *exp)
+{
+	struct cli_ch_state s_cch, *cch = &s_cch;
+	struct video_priv *vid_priv;
+	uint scene_id;
+	struct udevice *dev;
+	struct scene *scn;
+	bool done;
+	int ret;
+
+	cli_ch_init(cch);
+
+	/* For now we only support a video console */
+	ret = uclass_first_device_err(UCLASS_VIDEO, &dev);
+	if (ret)
+		return log_msg_ret("vid", ret);
+	ret = expo_set_display(exp, dev);
+	if (ret)
+		return log_msg_ret("dis", ret);
+
+	ret = expo_first_scene_id(exp);
+	if (ret < 0)
+		return log_msg_ret("scn", ret);
+	scene_id = ret;
+
+	ret = expo_set_scene_id(exp, scene_id);
+	if (ret)
+		return log_msg_ret("sid", ret);
+
+	exp->popup = true;
+
+	/* This is not supported for now */
+	if (0)
+		expo_set_text_mode(exp, true);
+
+	vid_priv = dev_get_uclass_priv(dev);
+
+	scn = expo_lookup_scene_id(exp, scene_id);
+	scene_highlight_first(scn);
+
+	cedit_arange(exp, vid_priv, scene_id);
+
+	ret = expo_calc_dims(exp);
+	if (ret)
+		return log_msg_ret("dim", ret);
+
+	done = false;
+	do {
+		struct expo_action act;
+		int ichar, key;
+
+		ret = expo_render(exp);
+		if (ret)
+			break;
+
+		ichar = cli_ch_process(cch, 0);
+		if (!ichar) {
+			while (!ichar && !tstc()) {
+				schedule();
+				mdelay(2);
+				ichar = cli_ch_process(cch, -ETIMEDOUT);
+			}
+			if (!ichar) {
+				ichar = getchar();
+				ichar = cli_ch_process(cch, ichar);
+			}
+		}
+
+		key = 0;
+		if (ichar) {
+			key = bootmenu_conv_key(ichar);
+			if (key == BKEY_NONE)
+				key = ichar;
+		}
+		if (!key)
+			continue;
+
+		ret = expo_send_key(exp, key);
+		if (ret)
+			break;
+
+		ret = expo_action_get(exp, &act);
+		if (!ret) {
+			switch (act.type) {
+			case EXPOACT_POINT_OBJ:
+				scene_set_highlight_id(scn, act.select.id);
+				cedit_arange(exp, vid_priv, scene_id);
+				break;
+			case EXPOACT_OPEN:
+				scene_set_open(scn, act.select.id, true);
+				cedit_arange(exp, vid_priv, scene_id);
+				break;
+			case EXPOACT_CLOSE:
+				scene_set_open(scn, act.select.id, false);
+				cedit_arange(exp, vid_priv, scene_id);
+				break;
+			case EXPOACT_SELECT:
+				scene_set_open(scn, scn->highlight_id, false);
+				cedit_arange(exp, vid_priv, scene_id);
+				break;
+			case EXPOACT_QUIT:
+				log_debug("quitting\n");
+				done = true;
+				break;
+			default:
+				break;
+			}
+		}
+	} while (!done);
+
+	if (ret)
+		return log_msg_ret("end", ret);
+
+	return 0;
+}
diff --git a/boot/expo.c b/boot/expo.c
index 05950a1..db837f7 100644
--- a/boot/expo.c
+++ b/boot/expo.c
@@ -6,6 +6,8 @@
  * Written by Simon Glass <sjg@chromium.org>
  */
 
+#define LOG_CATEGORY	LOGC_EXPO
+
 #include <common.h>
 #include <dm.h>
 #include <expo.h>
@@ -54,6 +56,22 @@
 	free(exp);
 }
 
+uint resolve_id(struct expo *exp, uint id)
+{
+	log_debug("resolve id %d\n", id);
+	if (!id)
+		id = exp->next_id++;
+	else if (id >= exp->next_id)
+		exp->next_id = id + 1;
+
+	return id;
+}
+
+void expo_set_dynamic_start(struct expo *exp, uint dyn_start)
+{
+	exp->next_id = dyn_start;
+}
+
 int expo_str(struct expo *exp, const char *name, uint id, const char *str)
 {
 	struct expo_string *estr;
@@ -83,12 +101,45 @@
 
 int expo_set_display(struct expo *exp, struct udevice *dev)
 {
+	struct udevice *cons;
+	int ret;
+
+	ret = device_find_first_child_by_uclass(dev, UCLASS_VIDEO_CONSOLE,
+						&cons);
+	if (ret)
+		return log_msg_ret("con", ret);
+
 	exp->display = dev;
+	exp->cons = cons;
+
+	return 0;
+}
+
+int expo_calc_dims(struct expo *exp)
+{
+	struct scene *scn;
+	int ret;
+
+	if (!exp->cons)
+		return log_msg_ret("dim", -ENOTSUPP);
+
+	list_for_each_entry(scn, &exp->scene_head, sibling) {
+		/*
+		 * Do the menus last so that all the menus' text objects
+		 * are dimensioned
+		 */
+		ret = scene_calc_dims(scn, false);
+		if (ret)
+			return log_msg_ret("scn", ret);
+		ret = scene_calc_dims(scn, true);
+		if (ret)
+			return log_msg_ret("scn", ret);
+	}
 
 	return 0;
 }
 
-void exp_set_text_mode(struct expo *exp, bool text_mode)
+void expo_set_text_mode(struct expo *exp, bool text_mode)
 {
 	exp->text_mode = text_mode;
 }
@@ -107,13 +158,33 @@
 
 int expo_set_scene_id(struct expo *exp, uint scene_id)
 {
-	if (!expo_lookup_scene_id(exp, scene_id))
+	struct scene *scn;
+	int ret;
+
+	scn = expo_lookup_scene_id(exp, scene_id);
+	if (!scn)
 		return log_msg_ret("id", -ENOENT);
+	ret = scene_arrange(scn);
+	if (ret)
+		return log_msg_ret("arr", ret);
+
 	exp->scene_id = scene_id;
 
 	return 0;
 }
 
+int expo_first_scene_id(struct expo *exp)
+{
+	struct scene *scn;
+
+	if (list_empty(&exp->scene_head))
+		return -ENOENT;
+
+	scn = list_first_entry(&exp->scene_head, struct scene, sibling);
+
+	return scn->id;
+}
+
 int expo_render(struct expo *exp)
 {
 	struct udevice *dev = exp->display;
@@ -156,6 +227,11 @@
 		ret = scene_send_key(scn, key, &exp->action);
 		if (ret)
 			return log_msg_ret("key", ret);
+
+		/* arrange it to get any changes */
+		ret = scene_arrange(scn);
+		if (ret)
+			return log_msg_ret("arr", ret);
 	}
 
 	return scn ? 0 : -ECHILD;
@@ -168,3 +244,25 @@
 
 	return act->type == EXPOACT_NONE ? -EAGAIN : 0;
 }
+
+int expo_apply_theme(struct expo *exp, ofnode node)
+{
+	struct scene *scn;
+	struct expo_theme *theme = &exp->theme;
+	int ret;
+
+	log_debug("Applying theme %s\n", ofnode_get_name(node));
+
+	memset(theme, '\0', sizeof(struct expo_theme));
+	ofnode_read_u32(node, "font-size", &theme->font_size);
+	ofnode_read_u32(node, "menu-inset", &theme->menu_inset);
+	ofnode_read_u32(node, "menuitem-gap-y", &theme->menuitem_gap_y);
+
+	list_for_each_entry(scn, &exp->scene_head, sibling) {
+		ret = scene_apply_theme(scn, theme);
+		if (ret)
+			return log_msg_ret("app", ret);
+	}
+
+	return 0;
+}
diff --git a/boot/expo_build.c b/boot/expo_build.c
new file mode 100644
index 0000000..22f62eb
--- /dev/null
+++ b/boot/expo_build.c
@@ -0,0 +1,401 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Building an expo from an FDT description
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY	LOGC_EXPO
+
+#include <common.h>
+#include <expo.h>
+#include <fdtdec.h>
+#include <log.h>
+#include <malloc.h>
+#include <dm/ofnode.h>
+#include <linux/libfdt.h>
+
+/**
+ * struct build_info - Information to use when building
+ *
+ * @str_for_id: String for each ID in use, NULL if empty. The string is NULL
+ *	if there is nothing for this ID. Since ID 0 is never used, the first
+ *	element of this array is always NULL
+ * @str_count: Number of entries in @str_for_id
+ */
+struct build_info {
+	const char **str_for_id;
+	int str_count;
+};
+
+/**
+ * add_txt_str - Add a string or lookup its ID, then add to expo
+ *
+ * @info: Build information
+ * @node: Node describing scene
+ * @scn: Scene to add to
+ * @find_name: Name to look for (e.g. "title"). This will find a property called
+ * "title" if it exists, else will look up the string for "title-id"
+ * Return: ID of added string, or -ve on error
+ */
+int add_txt_str(struct build_info *info, ofnode node, struct scene *scn,
+		const char *find_name, uint obj_id)
+{
+	const char *text;
+	uint str_id;
+	int ret;
+
+	text = ofnode_read_string(node, find_name);
+	if (!text) {
+		char name[40];
+		u32 id;
+
+		snprintf(name, sizeof(name), "%s-id", find_name);
+		ret = ofnode_read_u32(node, name, &id);
+		if (ret)
+			return log_msg_ret("id", -EINVAL);
+
+		if (id >= info->str_count)
+			return log_msg_ret("id", -E2BIG);
+		text = info->str_for_id[id];
+		if (!text)
+			return log_msg_ret("id", -EINVAL);
+	}
+
+	ret = expo_str(scn->expo, find_name, 0, text);
+	if (ret < 0)
+		return log_msg_ret("add", ret);
+	str_id = ret;
+
+	ret = scene_txt_str(scn, find_name, obj_id, str_id, text, NULL);
+	if (ret < 0)
+		return log_msg_ret("add", ret);
+
+	return ret;
+}
+
+/**
+ * add_txt_str_list - Add a list string or lookup its ID, then add to expo
+ *
+ * @info: Build information
+ * @node: Node describing scene
+ * @scn: Scene to add to
+ * @find_name: Name to look for (e.g. "title"). This will find a string-list
+ * property called "title" if it exists, else will look up the string in the
+ * "title-id" string list.
+ * Return: ID of added string, or -ve on error
+ */
+int add_txt_str_list(struct build_info *info, ofnode node, struct scene *scn,
+		     const char *find_name, int index, uint obj_id)
+{
+	const char *text;
+	uint str_id;
+	int ret;
+
+	ret = ofnode_read_string_index(node, find_name, index, &text);
+	if (ret) {
+		char name[40];
+		u32 id;
+
+		snprintf(name, sizeof(name), "%s-id", find_name);
+		ret = ofnode_read_u32_index(node, name, index, &id);
+		if (ret)
+			return log_msg_ret("id", -ENOENT);
+
+		if (id >= info->str_count)
+			return log_msg_ret("id", -E2BIG);
+		text = info->str_for_id[id];
+		if (!text)
+			return log_msg_ret("id", -EINVAL);
+	}
+
+	ret = expo_str(scn->expo, find_name, 0, text);
+	if (ret < 0)
+		return log_msg_ret("add", ret);
+	str_id = ret;
+
+	ret = scene_txt_str(scn, find_name, obj_id, str_id, text, NULL);
+	if (ret < 0)
+		return log_msg_ret("add", ret);
+
+	return ret;
+}
+
+/*
+ * build_element() - Handle creating a text object from a label
+ *
+ * Look up a property called @label or @label-id and create a string for it
+ */
+int build_element(void *ldtb, int node, const char *label)
+{
+	return 0;
+}
+
+/**
+ * read_strings() - Read in the list of strings
+ *
+ * Read the strings into an ID-indexed list, so they can be used for building
+ * an expo. The strings are in a /strings node and each has its own subnode
+ * containing the ID and the string itself:
+ *
+ * example {
+ *    id = <123>;
+ *    value = "This is a test";
+ * };
+ *
+ * Future work may add support for unicode and multiple languages
+ *
+ * @info: Build information
+ * @root: Root node to read from
+ * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
+ * error
+ */
+static int read_strings(struct build_info *info, ofnode root)
+{
+	ofnode strings, node;
+
+	strings = ofnode_find_subnode(root, "strings");
+	if (!ofnode_valid(strings))
+		return log_msg_ret("str", -EINVAL);
+
+	ofnode_for_each_subnode(node, strings) {
+		const char *val;
+		int ret;
+		u32 id;
+
+		ret = ofnode_read_u32(node, "id", &id);
+		if (ret)
+			return log_msg_ret("id", -EINVAL);
+		val = ofnode_read_string(node, "value");
+		if (!val)
+			return log_msg_ret("val", -EINVAL);
+
+		if (id >= info->str_count) {
+			int new_count = info->str_count + 20;
+			void *new_arr;
+
+			new_arr = realloc(info->str_for_id,
+					  new_count * sizeof(char *));
+			if (!new_arr)
+				return log_msg_ret("id", -ENOMEM);
+			memset(new_arr + info->str_count, '\0',
+			       (new_count - info->str_count) * sizeof(char *));
+			info->str_for_id = new_arr;
+			info->str_count = new_count;
+		}
+
+		info->str_for_id[id] = val;
+	}
+
+	return 0;
+}
+
+/**
+ * list_strings() - List the available strings with their IDs
+ *
+ * @info: Build information
+ */
+static void list_strings(struct build_info *info)
+{
+	int i;
+
+	for (i = 0; i < info->str_count; i++) {
+		if (info->str_for_id[i])
+			printf("%3d %s\n", i, info->str_for_id[i]);
+	}
+}
+
+/**
+ * menu_build() - Build a menu and add it to a scene
+ *
+ * See doc/developer/expo.rst for a description of the format
+ *
+ * @info: Build information
+ * @node: Node containing the menu description
+ * @scn: Scene to add the menu to
+ * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
+ * error, -ENOENT if there is a references to a non-existent string
+ */
+static int menu_build(struct build_info *info, ofnode node, struct scene *scn)
+{
+	struct scene_obj_menu *menu;
+	uint title_id, menu_id;
+	const u32 *item_ids;
+	int ret, size, i;
+	const char *name;
+	u32 id;
+
+	name = ofnode_get_name(node);
+	ret = ofnode_read_u32(node, "id", &id);
+	if (ret)
+		return log_msg_ret("id", -EINVAL);
+
+	ret = scene_menu(scn, name, id, &menu);
+	if (ret < 0)
+		return log_msg_ret("men", ret);
+	menu_id = ret;
+
+	/* Set the title */
+	ret = add_txt_str(info, node, scn, "title", 0);
+	if (ret < 0)
+		return log_msg_ret("tit", ret);
+	title_id = ret;
+	ret = scene_menu_set_title(scn, menu_id, title_id);
+
+	item_ids = ofnode_read_prop(node, "item-id", &size);
+	if (!item_ids)
+		return log_msg_ret("itm", -EINVAL);
+	if (!size || size % sizeof(u32))
+		return log_msg_ret("isz", -EINVAL);
+	size /= sizeof(u32);
+
+	for (i = 0; i < size; i++) {
+		struct scene_menitem *item;
+		uint label, key, desc;
+
+		ret = add_txt_str_list(info, node, scn, "item-label", i, 0);
+		if (ret < 0 && ret != -ENOENT)
+			return log_msg_ret("lab", ret);
+		label = max(0, ret);
+
+		ret = add_txt_str_list(info, node, scn, "key-label", i, 0);
+		if (ret < 0 && ret != -ENOENT)
+			return log_msg_ret("key", ret);
+		key = max(0, ret);
+
+		ret = add_txt_str_list(info, node, scn, "desc-label", i, 0);
+		if (ret < 0  && ret != -ENOENT)
+			return log_msg_ret("lab", ret);
+		desc = max(0, ret);
+
+		ret = scene_menuitem(scn, menu_id, simple_xtoa(i),
+				     fdt32_to_cpu(item_ids[i]), key, label,
+				     desc, 0, 0, &item);
+		if (ret < 0)
+			return log_msg_ret("mi", ret);
+	}
+
+	return 0;
+}
+
+/**
+ * menu_build() - Build an expo object and add it to a scene
+ *
+ * See doc/developer/expo.rst for a description of the format
+ *
+ * @info: Build information
+ * @node: Node containing the object description
+ * @scn: Scene to add the object to
+ * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
+ * error, -ENOENT if there is a references to a non-existent string
+ */
+static int obj_build(struct build_info *info, ofnode node, struct scene *scn)
+{
+	const char *type;
+	u32 id;
+	int ret;
+
+	log_debug("- object %s\n", ofnode_get_name(node));
+	ret = ofnode_read_u32(node, "id", &id);
+	if (ret)
+		return log_msg_ret("id", -EINVAL);
+
+	type = ofnode_read_string(node, "type");
+	if (!type)
+		return log_msg_ret("typ", -EINVAL);
+
+	if (!strcmp("menu", type))
+		ret = menu_build(info, node, scn);
+	 else
+		ret = -EINVAL;
+	if (ret)
+		return log_msg_ret("bld", ret);
+
+	return 0;
+}
+
+/**
+ * scene_build() - Build a scene and all its objects
+ *
+ * See doc/developer/expo.rst for a description of the format
+ *
+ * @info: Build information
+ * @node: Node containing the scene description
+ * @scn: Scene to add the object to
+ * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
+ * error, -ENOENT if there is a references to a non-existent string
+ */
+static int scene_build(struct build_info *info, ofnode scn_node,
+		       struct expo *exp)
+{
+	const char *name;
+	struct scene *scn;
+	uint id, title_id;
+	ofnode node;
+	int ret;
+
+	name = ofnode_get_name(scn_node);
+	log_debug("Building scene %s\n", name);
+	ret = ofnode_read_u32(scn_node, "id", &id);
+	if (ret)
+		return log_msg_ret("id", -EINVAL);
+
+	ret = scene_new(exp, name, id, &scn);
+	if (ret < 0)
+		return log_msg_ret("scn", ret);
+
+	ret = add_txt_str(info, scn_node, scn, "title", 0);
+	if (ret < 0)
+		return log_msg_ret("tit", ret);
+	title_id = ret;
+	scene_title_set(scn, title_id);
+
+	ret = add_txt_str(info, scn_node, scn, "prompt", 0);
+	if (ret < 0)
+		return log_msg_ret("pr", ret);
+
+	ofnode_for_each_subnode(node, scn_node) {
+		ret = obj_build(info, node, scn);
+		if (ret < 0)
+			return log_msg_ret("mit", ret);
+	}
+
+	return 0;
+}
+
+int expo_build(ofnode root, struct expo **expp)
+{
+	struct build_info info;
+	ofnode scenes, node;
+	struct expo *exp;
+	u32 dyn_start;
+	int ret;
+
+	memset(&info, '\0', sizeof(info));
+	ret = read_strings(&info, root);
+	if (ret)
+		return log_msg_ret("str", ret);
+	if (_DEBUG)
+		list_strings(&info);
+
+	ret = expo_new("name", NULL, &exp);
+	if (ret)
+		return log_msg_ret("exp", ret);
+
+	if (!ofnode_read_u32(root, "dynamic-start", &dyn_start))
+		expo_set_dynamic_start(exp, dyn_start);
+
+	scenes = ofnode_find_subnode(root, "scenes");
+	if (!ofnode_valid(scenes))
+		return log_msg_ret("sno", -EINVAL);
+
+	ofnode_for_each_subnode(node, scenes) {
+		ret = scene_build(&info, node, exp);
+		if (ret < 0)
+			return log_msg_ret("scn", ret);
+	}
+	*expp = exp;
+
+	return 0;
+}
diff --git a/boot/scene.c b/boot/scene.c
index 030f6aa..e523333 100644
--- a/boot/scene.c
+++ b/boot/scene.c
@@ -6,26 +6,19 @@
  * Written by Simon Glass <sjg@chromium.org>
  */
 
+#define LOG_CATEGORY	LOGC_EXPO
+
 #include <common.h>
 #include <dm.h>
 #include <expo.h>
 #include <malloc.h>
 #include <mapmem.h>
+#include <menu.h>
 #include <video.h>
 #include <video_console.h>
 #include <linux/input.h>
 #include "scene_internal.h"
 
-uint resolve_id(struct expo *exp, uint id)
-{
-	if (!id)
-		id = exp->next_id++;
-	else if (id >= exp->next_id)
-		exp->next_id = id + 1;
-
-	return id;
-}
-
 int scene_new(struct expo *exp, const char *name, uint id, struct scene **scnp)
 {
 	struct scene *scn;
@@ -65,16 +58,12 @@
 		scene_obj_destroy(obj);
 
 	free(scn->name);
-	free(scn->title);
 	free(scn);
 }
 
-int scene_title_set(struct scene *scn, const char *title)
+int scene_title_set(struct scene *scn, uint id)
 {
-	free(scn->title);
-	scn->title = strdup(title);
-	if (!scn->title)
-		return log_msg_ret("tit", -ENOMEM);
+	scn->title_id = id;
 
 	return 0;
 }
@@ -103,6 +92,18 @@
 	return NULL;
 }
 
+void *scene_obj_find_by_name(struct scene *scn, const char *name)
+{
+	struct scene_obj *obj;
+
+	list_for_each_entry(obj, &scn->obj_head, sibling) {
+		if (!strcmp(name, obj->name))
+			return obj;
+	}
+
+	return NULL;
+}
+
 int scene_obj_add(struct scene *scn, const char *name, uint id,
 		  enum scene_obj_t type, uint size, struct scene_obj **objp)
 {
@@ -213,22 +214,46 @@
 	obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
 	if (!obj)
 		return log_msg_ret("find", -ENOENT);
-	obj->x = x;
-	obj->y = y;
-	if (obj->type == SCENEOBJT_MENU)
-		scene_menu_arrange(scn, (struct scene_obj_menu *)obj);
+	obj->dim.x = x;
+	obj->dim.y = y;
 
 	return 0;
 }
 
+int scene_obj_set_size(struct scene *scn, uint id, int w, int h)
+{
+	struct scene_obj *obj;
+
+	obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
+	if (!obj)
+		return log_msg_ret("find", -ENOENT);
+	obj->dim.w = w;
+	obj->dim.h = h;
+
+	return 0;
+}
+
 int scene_obj_set_hide(struct scene *scn, uint id, bool hide)
 {
+	int ret;
+
+	ret = scene_obj_flag_clrset(scn, id, SCENEOF_HIDE,
+				    hide ? SCENEOF_HIDE : 0);
+	if (ret)
+		return log_msg_ret("flg", ret);
+
+	return 0;
+}
+
+int scene_obj_flag_clrset(struct scene *scn, uint id, uint clr, uint set)
+{
 	struct scene_obj *obj;
 
 	obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
 	if (!obj)
 		return log_msg_ret("find", -ENOENT);
-	obj->hide = hide;
+	obj->flags &= ~clr;
+	obj->flags |= set;
 
 	return 0;
 }
@@ -258,16 +283,30 @@
 	case SCENEOBJT_TEXT: {
 		struct scene_obj_txt *txt = (struct scene_obj_txt *)obj;
 		struct expo *exp = scn->expo;
+		struct vidconsole_bbox bbox;
+		const char *str;
+		int len, ret;
 
+		str = expo_get_str(exp, txt->str_id);
+		if (!str)
+			return log_msg_ret("str", -ENOENT);
+		len = strlen(str);
+
+		/* if there is no console, make it up */
+		if (!exp->cons) {
+			if (widthp)
+				*widthp = 8 * len;
+			return 16;
+		}
+
+		ret = vidconsole_measure(scn->expo->cons, txt->font_name,
+					 txt->font_size, str, &bbox);
+		if (ret)
+			return log_msg_ret("mea", ret);
 		if (widthp)
-			*widthp = 16; /* fake value for now */
-		if (txt->font_size)
-			return txt->font_size;
-		if (exp->display)
-			return video_default_font_height(exp->display);
+			*widthp = bbox.x1;
 
-		/* use a sensible default */
-		return 16;
+		return bbox.y1;
 	}
 	}
 
@@ -282,18 +321,13 @@
 {
 	struct scene *scn = obj->scene;
 	struct expo *exp = scn->expo;
-	struct udevice *cons, *dev = exp->display;
+	const struct expo_theme *theme = &exp->theme;
+	struct udevice *dev = exp->display;
+	struct udevice *cons = text_mode ? NULL : exp->cons;
 	int x, y, ret;
 
-	cons = NULL;
-	if (!text_mode) {
-		ret = device_find_first_child_by_uclass(dev,
-							UCLASS_VIDEO_CONSOLE,
-							&cons);
-	}
-
-	x = obj->x;
-	y = obj->y;
+	x = obj->dim.x;
+	y = obj->dim.y;
 
 	switch (obj->type) {
 	case SCENEOBJT_NONE:
@@ -325,14 +359,45 @@
 		}
 		if (ret && ret != -ENOSYS)
 			return log_msg_ret("font", ret);
-		vidconsole_set_cursor_pos(cons, x, y);
 		str = expo_get_str(exp, txt->str_id);
-		if (str)
+		if (str) {
+			struct video_priv *vid_priv;
+			struct vidconsole_colour old;
+			enum colour_idx fore, back;
+
+			if (CONFIG_IS_ENABLED(SYS_WHITE_ON_BLACK)) {
+				fore = VID_BLACK;
+				back = VID_WHITE;
+			} else {
+				fore = VID_LIGHT_GRAY;
+				back = VID_BLACK;
+			}
+
+			vid_priv = dev_get_uclass_priv(dev);
+			if (obj->flags & SCENEOF_POINT) {
+				vidconsole_push_colour(cons, fore, back, &old);
+				video_fill_part(dev, x - theme->menu_inset, y,
+						x + obj->dim.w,
+						y + obj->dim.h,
+						vid_priv->colour_bg);
+			}
+			vidconsole_set_cursor_pos(cons, x, y);
 			vidconsole_put_string(cons, str);
+			if (obj->flags & SCENEOF_POINT)
+				vidconsole_pop_colour(cons, &old);
+		}
 		break;
 	}
 	case SCENEOBJT_MENU: {
 		struct scene_obj_menu *menu = (struct scene_obj_menu *)obj;
+
+		if (exp->popup && (obj->flags & SCENEOF_OPEN)) {
+			if (!cons)
+				return -ENOTSUPP;
+
+			/* draw a background behind the menu items */
+			scene_menu_render(menu);
+		}
 		/*
 		 * With a vidconsole, the text and item pointer are rendered as
 		 * normal objects so we don't need to do anything here. The menu
@@ -371,6 +436,30 @@
 	return 0;
 }
 
+int scene_render_deps(struct scene *scn, uint id)
+{
+	struct scene_obj *obj;
+	int ret;
+
+	if (!id)
+		return 0;
+	obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
+	if (!obj)
+		return log_msg_ret("obj", -ENOENT);
+
+	if (!(obj->flags & SCENEOF_HIDE)) {
+		ret = scene_obj_render(obj, false);
+		if (ret && ret != -ENOTSUPP)
+			return log_msg_ret("ren", ret);
+
+		if (obj->type == SCENEOBJT_MENU)
+			scene_menu_render_deps(scn,
+					       (struct scene_obj_menu *)obj);
+	}
+
+	return 0;
+}
+
 int scene_render(struct scene *scn)
 {
 	struct expo *exp = scn->expo;
@@ -378,21 +467,107 @@
 	int ret;
 
 	list_for_each_entry(obj, &scn->obj_head, sibling) {
-		if (!obj->hide) {
+		if (!(obj->flags & SCENEOF_HIDE)) {
 			ret = scene_obj_render(obj, exp->text_mode);
 			if (ret && ret != -ENOTSUPP)
 				return log_msg_ret("ren", ret);
 		}
 	}
 
+	/* render any highlighted object on top of the others */
+	if (scn->highlight_id && !exp->text_mode) {
+		ret = scene_render_deps(scn, scn->highlight_id);
+		if (ret && ret != -ENOTSUPP)
+			return log_msg_ret("dep", ret);
+	}
+
 	return 0;
 }
 
+/**
+ * send_key_obj() - Handle a keypress for moving between objects
+ *
+ * @scn: Scene to receive the key
+ * @key: Key to send (KEYCODE_UP)
+ * @event: Returns resulting event from this keypress
+ * Returns: 0 if OK, -ve on error
+ */
+static void send_key_obj(struct scene *scn, struct scene_obj *obj, int key,
+			 struct expo_action *event)
+{
+	switch (key) {
+	case BKEY_UP:
+		while (obj != list_first_entry(&scn->obj_head, struct scene_obj,
+					       sibling)) {
+			obj = list_entry(obj->sibling.prev,
+					 struct scene_obj, sibling);
+			if (obj->type == SCENEOBJT_MENU) {
+				event->type = EXPOACT_POINT_OBJ;
+				event->select.id = obj->id;
+				log_debug("up to obj %d\n", event->select.id);
+				break;
+			}
+		}
+		break;
+	case BKEY_DOWN:
+		while (!list_is_last(&obj->sibling, &scn->obj_head)) {
+			obj = list_entry(obj->sibling.next, struct scene_obj,
+					 sibling);
+			if (obj->type == SCENEOBJT_MENU) {
+				event->type = EXPOACT_POINT_OBJ;
+				event->select.id = obj->id;
+				log_debug("down to obj %d\n", event->select.id);
+				break;
+			}
+		}
+		break;
+	case BKEY_SELECT:
+		if (obj->type == SCENEOBJT_MENU) {
+			event->type = EXPOACT_OPEN;
+			event->select.id = obj->id;
+			log_debug("open obj %d\n", event->select.id);
+		}
+		break;
+	case BKEY_QUIT:
+		event->type = EXPOACT_QUIT;
+		log_debug("obj quit\n");
+		break;
+	}
+}
+
 int scene_send_key(struct scene *scn, int key, struct expo_action *event)
 {
+	struct scene_obj_menu *menu;
 	struct scene_obj *obj;
 	int ret;
 
+	event->type = EXPOACT_NONE;
+
+	/*
+	 * In 'popup' mode, arrow keys move betwen objects, unless a menu is
+	 * opened
+	 */
+	if (scn->expo->popup) {
+		obj = NULL;
+		if (scn->highlight_id) {
+			obj = scene_obj_find(scn, scn->highlight_id,
+					     SCENEOBJT_NONE);
+		}
+		if (!obj)
+			return 0;
+
+		if (!(obj->flags & SCENEOF_OPEN)) {
+			send_key_obj(scn, obj, key, event);
+			return 0;
+		}
+
+		menu = (struct scene_obj_menu *)obj,
+		ret = scene_menu_send_key(scn, menu, key, event);
+		if (ret)
+			return log_msg_ret("key", ret);
+		return 0;
+	}
+
 	list_for_each_entry(obj, &scn->obj_head, sibling) {
 		if (obj->type == SCENEOBJT_MENU) {
 			struct scene_obj_menu *menu;
@@ -401,14 +576,108 @@
 			ret = scene_menu_send_key(scn, menu, key, event);
 			if (ret)
 				return log_msg_ret("key", ret);
+			break;
+		}
+	}
 
-			/* only allow one menu */
-			ret = scene_menu_arrange(scn, menu);
-			if (ret)
-				return log_msg_ret("arr", ret);
+	return 0;
+}
+
+int scene_calc_dims(struct scene *scn, bool do_menus)
+{
+	struct scene_obj *obj;
+	int ret;
+
+	list_for_each_entry(obj, &scn->obj_head, sibling) {
+		switch (obj->type) {
+		case SCENEOBJT_NONE:
+		case SCENEOBJT_TEXT:
+		case SCENEOBJT_IMAGE: {
+			int width;
+
+			if (!do_menus) {
+				ret = scene_obj_get_hw(scn, obj->id, &width);
+				if (ret < 0)
+					return log_msg_ret("get", ret);
+				obj->dim.w = width;
+				obj->dim.h = ret;
+			}
+			break;
+		}
+		case SCENEOBJT_MENU: {
+			struct scene_obj_menu *menu;
+
+			if (do_menus) {
+				menu = (struct scene_obj_menu *)obj;
+
+				ret = scene_menu_calc_dims(menu);
+				if (ret)
+					return log_msg_ret("men", ret);
+			}
 			break;
 		}
+		}
+	}
+
+	return 0;
+}
+
+int scene_apply_theme(struct scene *scn, struct expo_theme *theme)
+{
+	struct scene_obj *obj;
+	int ret;
+
+	/* Avoid error-checking optional items */
+	scene_txt_set_font(scn, scn->title_id, NULL, theme->font_size);
+
+	list_for_each_entry(obj, &scn->obj_head, sibling) {
+		switch (obj->type) {
+		case SCENEOBJT_NONE:
+		case SCENEOBJT_IMAGE:
+		case SCENEOBJT_MENU:
+			break;
+		case SCENEOBJT_TEXT:
+			scene_txt_set_font(scn, obj->id, NULL,
+					   theme->font_size);
+			break;
+		}
 	}
 
+	ret = scene_arrange(scn);
+	if (ret)
+		return log_msg_ret("arr", ret);
+
+	return 0;
+}
+
+void scene_set_highlight_id(struct scene *scn, uint id)
+{
+	scn->highlight_id = id;
+}
+
+void scene_highlight_first(struct scene *scn)
+{
+	struct scene_obj *obj;
+
+	list_for_each_entry(obj, &scn->obj_head, sibling) {
+		switch (obj->type) {
+		case SCENEOBJT_MENU:
+			scene_set_highlight_id(scn, obj->id);
+			return;
+		default:
+			break;
+		}
+	}
+}
+
+int scene_set_open(struct scene *scn, uint id, bool open)
+{
+	int ret;
+
+	ret = scene_obj_flag_clrset(scn, id, SCENEOF_OPEN,
+				    open ? SCENEOF_OPEN : 0);
+	if (ret)
+		return log_msg_ret("flg", ret);
+
 	return 0;
 }
diff --git a/boot/scene_internal.h b/boot/scene_internal.h
index e8fd765..fb1ea55 100644
--- a/boot/scene_internal.h
+++ b/boot/scene_internal.h
@@ -23,7 +23,7 @@
  *
  * @exp: Expo to use
  * @id: ID to use, or 0 to auto-allocate one
- * @return: Either @id, or the auto-allocated ID
+ * Returns: Either @id, or the auto-allocated ID
  */
 uint resolve_id(struct expo *exp, uint id);
 
@@ -36,10 +36,19 @@
  * @scn: Scene to search
  * @id: ID of object to find
  * @type: Type of the object, or SCENEOBJT_NONE to match any type
+ * Returns: Object found, or NULL if not found
  */
 void *scene_obj_find(struct scene *scn, uint id, enum scene_obj_t type);
 
 /**
+ * scene_obj_find_by_name() - Find an object in a scene by name
+ *
+ * @scn: Scene to search
+ * @name: Name to search for
+ */
+void *scene_obj_find_by_name(struct scene *scn, const char *name);
+
+/**
  * scene_obj_add() - Add a new object to a scene
  *
  * @scn: Scene to update
@@ -54,6 +63,28 @@
 		  enum scene_obj_t type, uint size, struct scene_obj **objp);
 
 /**
+ * scene_obj_flag_clrset() - Adjust object flags
+ *
+ * @scn: Scene to update
+ * @id: ID of object to update
+ * @clr: Bits to clear in the object's flags
+ * @set: Bits to set in the object's flags
+ * Returns 0 if OK, -ENOENT if the object was not found
+ */
+int scene_obj_flag_clrset(struct scene *scn, uint id, uint clr, uint set);
+
+/**
+ * scene_calc_dims() - Calculate the dimensions of the scene objects
+ *
+ * Updates the width and height of all objects based on their contents
+ *
+ * @scn: Scene to update
+ * @do_menus: true to calculate only menus, false to calculate everything else
+ * Returns 0 if OK, -ENOTSUPP if there is no graphical console
+ */
+int scene_calc_dims(struct scene *scn, bool do_menus);
+
+/**
  * scene_menu_arrange() - Set the position of things in the menu
  *
  * This updates any items associated with a menu to make sure they are
@@ -62,17 +93,27 @@
  *
  * @scn: Scene to update
  * @menu: Menu to process
+ * Returns: 0 if OK, -ve on error
  */
 int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu);
 
 /**
+ * scene_apply_theme() - Apply a theme to a scene
+ *
+ * @scn: Scene to update
+ * @theme: Theme to apply
+ * Returns: 0 if OK, -ve on error
+ */
+int scene_apply_theme(struct scene *scn, struct expo_theme *theme);
+
+/**
  * scene_menu_send_key() - Send a key to a menu for processing
  *
  * @scn: Scene to use
  * @menu: Menu to use
  * @key: Key code to send (KEY_...)
  * @event: Place to put any event which is generated by the key
- * @return 0 if OK, -ENOTTY if there is no current menu item, other -ve on other
+ * Returns: 0 if OK, -ENOTTY if there is no current menu item, other -ve on other
  *	error
  */
 int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
@@ -89,7 +130,7 @@
  * scene_menu_display() - Display a menu as text
  *
  * @menu: Menu to display
- * @return 0 if OK, -ENOENT if @id is invalid
+ * Returns: 0 if OK, -ENOENT if @id is invalid
  */
 int scene_menu_display(struct scene_obj_menu *menu);
 
@@ -120,4 +161,41 @@
  */
 int scene_send_key(struct scene *scn, int key, struct expo_action *event);
 
+/**
+ * scene_menu_render() - Render the background behind a menu
+ *
+ * @menu: Menu to render
+ */
+void scene_menu_render(struct scene_obj_menu *menu);
+
+/**
+ * scene_render_deps() - Render an object and its dependencies
+ *
+ * @scn: Scene to render
+ * @id: Object ID to render (or 0 for none)
+ * Returns: 0 if OK, -ve on error
+ */
+int scene_render_deps(struct scene *scn, uint id);
+
+/**
+ * scene_menu_render_deps() - Render a menu and its dependencies
+ *
+ * Renders the menu and all of its attached objects
+ *
+ * @scn: Scene to render
+ * @menu: Menu render
+ * Returns: 0 if OK, -ve on error
+ */
+int scene_menu_render_deps(struct scene *scn, struct scene_obj_menu *menu);
+
+/**
+ * scene_menu_calc_dims() - Calculate the dimensions of a menu
+ *
+ * Updates the width and height of the menu based on its contents
+ *
+ * @menu: Menu to update
+ * Returns 0 if OK, -ENOTSUPP if there is no graphical console
+ */
+int scene_menu_calc_dims(struct scene_obj_menu *menu);
+
 #endif /* __SCENE_INTERNAL_H */
diff --git a/boot/scene_menu.c b/boot/scene_menu.c
index 18998e8..8a355f8 100644
--- a/boot/scene_menu.c
+++ b/boot/scene_menu.c
@@ -6,7 +6,7 @@
  * Written by Simon Glass <sjg@chromium.org>
  */
 
-#define LOG_CATEGORY	LOGC_BOOT
+#define LOG_CATEGORY	LOGC_EXPO
 
 #include <common.h>
 #include <dm.h>
@@ -33,6 +33,58 @@
 		scene_menuitem_destroy(item);
 }
 
+static struct scene_menitem *scene_menuitem_find(struct scene_obj_menu *menu,
+						 int id)
+{
+	struct scene_menitem *item;
+
+	list_for_each_entry(item, &menu->item_head, sibling) {
+		if (item->id == id)
+			return item;
+	}
+
+	return NULL;
+}
+
+/**
+ * update_pointers() - Update the pointer object and handle highlights
+ *
+ * @menu: Menu to update
+ * @id: ID of menu item to select/deselect
+ * @point: true if @id is being selected, false if it is being deselected
+ */
+static int update_pointers(struct scene_obj_menu *menu, uint id, bool point)
+{
+	struct scene *scn = menu->obj.scene;
+	const bool stack = scn->expo->popup;
+	const struct scene_menitem *item;
+	int ret;
+
+	item = scene_menuitem_find(menu, id);
+	if (!item)
+		return log_msg_ret("itm", -ENOENT);
+
+	/* adjust the pointer object to point to the selected item */
+	if (menu->pointer_id && item && point) {
+		struct scene_obj *label;
+
+		label = scene_obj_find(scn, item->label_id, SCENEOBJT_NONE);
+
+		ret = scene_obj_set_pos(scn, menu->pointer_id,
+					menu->obj.dim.x + 200, label->dim.y);
+		if (ret < 0)
+			return log_msg_ret("ptr", ret);
+	}
+
+	if (stack) {
+		point &= scn->highlight_id == menu->obj.id;
+		scene_obj_flag_clrset(scn, item->label_id, SCENEOF_POINT,
+				      point ? SCENEOF_POINT : 0);
+	}
+
+	return 0;
+}
+
 /**
  * menu_point_to_item() - Point to a particular menu item
  *
@@ -40,18 +92,115 @@
  */
 static void menu_point_to_item(struct scene_obj_menu *menu, uint item_id)
 {
+	if (menu->cur_item_id)
+		update_pointers(menu, menu->cur_item_id, false);
 	menu->cur_item_id = item_id;
+	update_pointers(menu, item_id, true);
+}
+
+static int scene_bbox_union(struct scene *scn, uint id, int inset,
+			    struct vidconsole_bbox *bbox)
+{
+	struct scene_obj *obj;
+
+	if (!id)
+		return 0;
+	obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
+	if (!obj)
+		return log_msg_ret("obj", -ENOENT);
+	if (bbox->valid) {
+		bbox->x0 = min(bbox->x0, obj->dim.x - inset);
+		bbox->y0 = min(bbox->y0, obj->dim.y);
+		bbox->x1 = max(bbox->x1, obj->dim.x + obj->dim.w + inset);
+		bbox->y1 = max(bbox->y1, obj->dim.y + obj->dim.h);
+	} else {
+		bbox->x0 = obj->dim.x - inset;
+		bbox->y0 = obj->dim.y;
+		bbox->x1 = obj->dim.x + obj->dim.w + inset;
+		bbox->y1 = obj->dim.y + obj->dim.h;
+		bbox->valid = true;
+	}
+
+	return 0;
 }
 
+/**
+ * scene_menu_calc_bbox() - Calculate bounding boxes for the menu
+ *
+ * @menu: Menu to process
+ * @bbox: Returns bounding box of menu including prompts
+ * @label_bbox: Returns bounding box of labels
+ */
+static void scene_menu_calc_bbox(struct scene_obj_menu *menu,
+				 struct vidconsole_bbox *bbox,
+				 struct vidconsole_bbox *label_bbox)
+{
+	const struct expo_theme *theme = &menu->obj.scene->expo->theme;
+	const struct scene_menitem *item;
+
+	bbox->valid = false;
+	scene_bbox_union(menu->obj.scene, menu->title_id, 0, bbox);
+
+	label_bbox->valid = false;
+
+	list_for_each_entry(item, &menu->item_head, sibling) {
+		scene_bbox_union(menu->obj.scene, item->label_id,
+				 theme->menu_inset, bbox);
+		scene_bbox_union(menu->obj.scene, item->key_id, 0, bbox);
+		scene_bbox_union(menu->obj.scene, item->desc_id, 0, bbox);
+		scene_bbox_union(menu->obj.scene, item->preview_id, 0, bbox);
+
+		/* Get the bounding box of all labels */
+		scene_bbox_union(menu->obj.scene, item->label_id,
+				 theme->menu_inset, label_bbox);
+	}
+
+	/*
+	 * subtract the final menuitem's gap to keep the insert the same top
+	 * and bottom
+	 */
+	label_bbox->y1 -= theme->menuitem_gap_y;
+}
+
+int scene_menu_calc_dims(struct scene_obj_menu *menu)
+{
+	struct vidconsole_bbox bbox, label_bbox;
+	const struct scene_menitem *item;
+
+	scene_menu_calc_bbox(menu, &bbox, &label_bbox);
+
+	/* Make all labels the same size */
+	if (label_bbox.valid) {
+		list_for_each_entry(item, &menu->item_head, sibling) {
+			scene_obj_set_size(menu->obj.scene, item->label_id,
+					   label_bbox.x1 - label_bbox.x0,
+					   label_bbox.y1 - label_bbox.y0);
+		}
+	}
+
+	if (bbox.valid) {
+		menu->obj.dim.w = bbox.x1 - bbox.x0;
+		menu->obj.dim.h = bbox.y1 - bbox.y0;
+	}
+
+	return 0;
+}
+
 int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu)
 {
+	const bool open = menu->obj.flags & SCENEOF_OPEN;
+	struct expo *exp = scn->expo;
+	const bool stack = exp->popup;
+	const struct expo_theme *theme = &exp->theme;
 	struct scene_menitem *item;
-	int y, cur_y;
+	uint sel_id;
+	int x, y;
 	int ret;
 
-	y = menu->obj.y;
+	x = menu->obj.dim.x;
+	y = menu->obj.dim.y;
 	if (menu->title_id) {
-		ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.x, y);
+		ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.dim.x, y);
 		if (ret < 0)
 			return log_msg_ret("tit", ret);
 
@@ -59,7 +208,10 @@
 		if (ret < 0)
 			return log_msg_ret("hei", ret);
 
-		y += ret * 2;
+		if (stack)
+			x += 200;
+		else
+			y += ret * 2;
 	}
 
 	/*
@@ -68,11 +220,12 @@
 	 * small. This can be updated once text measuring is supported in
 	 * vidconsole
 	 */
-	cur_y = -1;
+	sel_id = menu->cur_item_id;
 	list_for_each_entry(item, &menu->item_head, sibling) {
+		bool selected;
 		int height;
 
-		ret = scene_obj_get_hw(scn, item->desc_id, NULL);
+		ret = scene_obj_get_hw(scn, item->label_id, NULL);
 		if (ret < 0)
 			return log_msg_ret("get", ret);
 		height = ret;
@@ -81,32 +234,33 @@
 			y += height;
 
 		/* select an item if not done already */
-		if (!menu->cur_item_id)
-			menu_point_to_item(menu, item->id);
+		if (!sel_id)
+			sel_id = item->id;
+
+		selected = sel_id == item->id;
 
 		/*
 		 * Put the label on the left, then leave a space for the
 		 * pointer, then the key and the description
 		 */
-		if (item->label_id) {
-			ret = scene_obj_set_pos(scn, item->label_id, menu->obj.x,
-						y);
-			if (ret < 0)
-				return log_msg_ret("nam", ret);
-		}
-
-		ret = scene_obj_set_pos(scn, item->key_id, menu->obj.x + 230,
-					y);
+		ret = scene_obj_set_pos(scn, item->label_id,
+					x + theme->menu_inset, y);
 		if (ret < 0)
-			return log_msg_ret("key", ret);
+			return log_msg_ret("nam", ret);
+		scene_obj_set_hide(scn, item->label_id,
+				   stack && !open && !selected);
 
-		ret = scene_obj_set_pos(scn, item->desc_id, menu->obj.x + 280,
-					y);
-		if (ret < 0)
-			return log_msg_ret("des", ret);
+		if (item->key_id) {
+			ret = scene_obj_set_pos(scn, item->key_id, x + 230, y);
+			if (ret < 0)
+				return log_msg_ret("key", ret);
+		}
 
-		if (menu->cur_item_id == item->id)
-			cur_y = y;
+		if (item->desc_id) {
+			ret = scene_obj_set_pos(scn, item->desc_id, x + 280, y);
+			if (ret < 0)
+				return log_msg_ret("des", ret);
+		}
 
 		if (item->preview_id) {
 			bool hide;
@@ -125,19 +279,12 @@
 				return log_msg_ret("hid", ret);
 		}
 
-		y += height;
+		if (!stack || open)
+			y += height + theme->menuitem_gap_y;
 	}
 
-	if (menu->pointer_id && cur_y != -1) {
-		/*
-		 * put the pointer to the right of and level with the item it
-		 * points to
-		 */
-		ret = scene_obj_set_pos(scn, menu->pointer_id,
-					menu->obj.x + 200, cur_y);
-		if (ret < 0)
-			return log_msg_ret("ptr", ret);
-	}
+	if (sel_id)
+		menu_point_to_item(menu, sel_id);
 
 	return 0;
 }
@@ -158,10 +305,6 @@
 		*menup = menu;
 	INIT_LIST_HEAD(&menu->item_head);
 
-	ret = scene_menu_arrange(scn, menu);
-	if (ret)
-		return log_msg_ret("pos", ret);
-
 	return menu->obj.id;
 }
 
@@ -191,6 +334,7 @@
 int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
 			struct expo_action *event)
 {
+	const bool open = menu->obj.flags & SCENEOF_OPEN;
 	struct scene_menitem *item, *cur, *key_item;
 
 	cur = NULL;
@@ -215,7 +359,7 @@
 					     struct scene_menitem, sibling)) {
 			item = list_entry(item->sibling.prev,
 					  struct scene_menitem, sibling);
-			event->type = EXPOACT_POINT;
+			event->type = EXPOACT_POINT_ITEM;
 			event->select.id = item->id;
 			log_debug("up to item %d\n", event->select.id);
 		}
@@ -224,7 +368,7 @@
 		if (!list_is_last(&item->sibling, &menu->item_head)) {
 			item = list_entry(item->sibling.next,
 					  struct scene_menitem, sibling);
-			event->type = EXPOACT_POINT;
+			event->type = EXPOACT_POINT_ITEM;
 			event->select.id = item->id;
 			log_debug("down to item %d\n", event->select.id);
 		}
@@ -235,8 +379,13 @@
 		log_debug("select item %d\n", event->select.id);
 		break;
 	case BKEY_QUIT:
-		event->type = EXPOACT_QUIT;
-		log_debug("quit\n");
+		if (scn->expo->popup && open) {
+			event->type = EXPOACT_CLOSE;
+			event->select.id = menu->obj.id;
+		} else {
+			event->type = EXPOACT_QUIT;
+			log_debug("menu quit\n");
+		}
 		break;
 	case '0'...'9':
 		key_item = scene_menu_find_key(scn, menu, key);
@@ -258,14 +407,13 @@
 {
 	struct scene_obj_menu *menu;
 	struct scene_menitem *item;
-	int ret;
 
 	menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU);
 	if (!menu)
 		return log_msg_ret("find", -ENOENT);
 
 	/* Check that the text ID is valid */
-	if (!scene_obj_find(scn, desc_id, SCENEOBJT_TEXT))
+	if (!scene_obj_find(scn, label_id, SCENEOBJT_TEXT))
 		return log_msg_ret("txt", -EINVAL);
 
 	item = calloc(1, sizeof(struct scene_obj_menu));
@@ -285,10 +433,6 @@
 	item->flags = flags;
 	list_add_tail(&item->sibling, &menu->item_head);
 
-	ret = scene_menu_arrange(scn, menu);
-	if (ret)
-		return log_msg_ret("pos", ret);
-
 	if (itemp)
 		*itemp = item;
 
@@ -388,3 +532,49 @@
 
 	return -ENOTSUPP;
 }
+
+void scene_menu_render(struct scene_obj_menu *menu)
+{
+	struct expo *exp = menu->obj.scene->expo;
+	const struct expo_theme *theme = &exp->theme;
+	struct vidconsole_bbox bbox, label_bbox;
+	struct udevice *dev = exp->display;
+	struct video_priv *vid_priv;
+	struct udevice *cons = exp->cons;
+	struct vidconsole_colour old;
+	enum colour_idx fore, back;
+
+	if (CONFIG_IS_ENABLED(SYS_WHITE_ON_BLACK)) {
+		fore = VID_BLACK;
+		back = VID_WHITE;
+	} else {
+		fore = VID_LIGHT_GRAY;
+		back = VID_BLACK;
+	}
+
+	scene_menu_calc_bbox(menu, &bbox, &label_bbox);
+	vidconsole_push_colour(cons, fore, back, &old);
+	vid_priv = dev_get_uclass_priv(dev);
+	video_fill_part(dev, label_bbox.x0 - theme->menu_inset,
+			label_bbox.y0 - theme->menu_inset,
+			label_bbox.x1, label_bbox.y1 + theme->menu_inset,
+			vid_priv->colour_fg);
+	vidconsole_pop_colour(cons, &old);
+}
+
+int scene_menu_render_deps(struct scene *scn, struct scene_obj_menu *menu)
+{
+	struct scene_menitem *item;
+
+	scene_render_deps(scn, menu->title_id);
+	scene_render_deps(scn, menu->cur_item_id);
+	scene_render_deps(scn, menu->pointer_id);
+
+	list_for_each_entry(item, &menu->item_head, sibling) {
+		scene_render_deps(scn, item->key_id);
+		scene_render_deps(scn, item->label_id);
+		scene_render_deps(scn, item->desc_id);
+	}
+
+	return 0;
+}
diff --git a/cmd/Kconfig b/cmd/Kconfig
index c194184..f6b10e0 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -428,6 +428,15 @@
 
 	  See doc/android/boot-image.rst for details.
 
+config CMD_CEDIT
+	bool "cedit - Configuration editor"
+	depends on CEDIT
+	default y
+	help
+	  Provides a command to allow editing of board configuration and
+	  providing a UI for the user to adjust settings. Subcommands allow
+	  loading and saving of configuration as well as showing an editor.
+
 config CMD_ELF
 	bool "bootelf, bootvx"
 	default y
diff --git a/cmd/Makefile b/cmd/Makefile
index 6c37521..9f8c0b0 100644
--- a/cmd/Makefile
+++ b/cmd/Makefile
@@ -43,6 +43,7 @@
 obj-$(CONFIG_CMD_CAT) += cat.o
 obj-$(CONFIG_CMD_CACHE) += cache.o
 obj-$(CONFIG_CMD_CBFS) += cbfs.o
+obj-$(CONFIG_CMD_CEDIT) += cedit.o
 obj-$(CONFIG_CMD_CLK) += clk.o
 obj-$(CONFIG_CMD_CLS) += cls.o
 obj-$(CONFIG_CMD_CONFIG) += config.o
diff --git a/cmd/cat.c b/cmd/cat.c
index 1273a26..b059080 100644
--- a/cmd/cat.c
+++ b/cmd/cat.c
@@ -17,8 +17,8 @@
 	char *dev;
 	char *file;
 	char *buffer;
-	phys_addr_t addr;
-	loff_t file_size;
+	ulong file_size;
+	int ret;
 
 	if (argc < 4)
 		return CMD_RET_USAGE;
@@ -27,40 +27,27 @@
 	dev = argv[2];
 	file = argv[3];
 
+	ret = fs_load_alloc(ifname, dev, file, 0, 0, (void **)&buffer,
+			    &file_size);
+
 	// check file exists
-	if (fs_set_blk_dev(ifname, dev, FS_TYPE_ANY))
+	switch (ret) {
+	case 0:
+		break;
+	case -ENOMEDIUM:
 		return CMD_RET_FAILURE;
-
-	if (!fs_exists(file)) {
+	case -ENOENT:
 		log_err("File does not exist: ifname=%s dev=%s file=%s\n", ifname, dev, file);
 		return CMD_RET_FAILURE;
-	}
-
-	// get file size
-	if (fs_set_blk_dev(ifname, dev, FS_TYPE_ANY))
-		return CMD_RET_FAILURE;
-
-	if (fs_size(file, &file_size)) {
-		log_err("Cannot read file size: ifname=%s dev=%s file=%s\n", ifname, dev, file);
-		return CMD_RET_FAILURE;
-	}
-
-	// allocate memory for file content
-	buffer = calloc(sizeof(char), file_size + 1);
-	if (!buffer) {
-		log_err("Out of memory\n");
+	case -E2BIG:
+		log_err("File is too large: ifname=%s dev=%s file=%s\n", ifname, dev, file);
 		return CMD_RET_FAILURE;
-	}
-
-	// map pointer to system memory
-	addr = map_to_sysmem(buffer);
-
-	// read file to memory
-	if (fs_set_blk_dev(ifname, dev, FS_TYPE_ANY))
+	case -ENOMEM:
+		log_err("Not enough memory: ifname=%s dev=%s file=%s\n", ifname, dev, file);
 		return CMD_RET_FAILURE;
-
-	if (fs_read(file, addr, 0, 0, &file_size)) {
-		log_err("Cannot read file: ifname=%s dev=%s file=%s\n", ifname, dev, file);
+	default:
+	case -EIO:
+		log_err("File-read failed: ifname=%s dev=%s file=%s\n", ifname, dev, file);
 		return CMD_RET_FAILURE;
 	}
 
diff --git a/cmd/cedit.c b/cmd/cedit.c
new file mode 100644
index 0000000..0cae304
--- /dev/null
+++ b/cmd/cedit.c
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * 'cedit' command
+ *
+ * Copyright 2023 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <command.h>
+#include <expo.h>
+#include <fs.h>
+#include <dm/ofnode.h>
+#include <linux/sizes.h>
+
+struct expo *cur_exp;
+
+static int do_cedit_load(struct cmd_tbl *cmdtp, int flag, int argc,
+			 char *const argv[])
+{
+	const char *fname;
+	struct expo *exp;
+	oftree tree;
+	ulong size;
+	void *buf;
+	int ret;
+
+	if (argc < 4)
+		return CMD_RET_USAGE;
+	fname = argv[3];
+
+	ret = fs_load_alloc(argv[1], argv[2], argv[3], SZ_1M, 0, &buf, &size);
+	if (ret) {
+		printf("File not found\n");
+		return CMD_RET_FAILURE;
+	}
+
+	tree = oftree_from_fdt(buf);
+	if (!oftree_valid(tree)) {
+		printf("Cannot create oftree\n");
+		return CMD_RET_FAILURE;
+	}
+
+	ret = expo_build(oftree_root(tree), &exp);
+	oftree_dispose(tree);
+	if (ret) {
+		printf("Failed to build expo: %dE\n", ret);
+		return CMD_RET_FAILURE;
+	}
+
+	cur_exp = exp;
+
+	return 0;
+}
+
+static int do_cedit_run(struct cmd_tbl *cmdtp, int flag, int argc,
+			char *const argv[])
+{
+	ofnode node;
+	int ret;
+
+	if (!cur_exp) {
+		printf("No expo loaded\n");
+		return CMD_RET_FAILURE;
+	}
+
+	node = ofnode_path("/cedit-theme");
+	if (ofnode_valid(node)) {
+		ret = expo_apply_theme(cur_exp, node);
+		if (ret)
+			return CMD_RET_FAILURE;
+	} else {
+		log_warning("No theme found\n");
+	}
+	ret = cedit_run(cur_exp);
+	if (ret) {
+		log_err("Failed (err=%dE)\n", ret);
+		return CMD_RET_FAILURE;
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_SYS_LONGHELP
+static char cedit_help_text[] =
+	"load <interface> <dev[:part]> <filename>   - load config editor\n"
+	"cedit run                                        - run config editor";
+#endif /* CONFIG_SYS_LONGHELP */
+
+U_BOOT_CMD_WITH_SUBCMDS(cedit, "Configuration editor", cedit_help_text,
+	U_BOOT_SUBCMD_MKENT(load, 5, 1, do_cedit_load),
+	U_BOOT_SUBCMD_MKENT(run, 1, 1, do_cedit_run),
+);
diff --git a/common/log.c b/common/log.c
index 7cfc49b..6f02a25 100644
--- a/common/log.c
+++ b/common/log.c
@@ -31,6 +31,7 @@
 	"boot",
 	"event",
 	"fs",
+	"expo",
 };
 
 _Static_assert(ARRAY_SIZE(log_cat_name) == LOGC_COUNT - LOGC_NONE,
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index 1ec44d5..4cef6c5 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -30,6 +30,7 @@
 CONFIG_AUTOBOOT_STOP_STR_CRYPT="$5$rounds=640000$HrpE65IkB8CM5nCL$BKT3QdF98Bo8fJpTr9tjZLZQyzqPASBY20xuK5Rent9"
 CONFIG_IMAGE_PRE_LOAD=y
 CONFIG_IMAGE_PRE_LOAD_SIG=y
+CONFIG_CEDIT=y
 CONFIG_CONSOLE_RECORD=y
 CONFIG_CONSOLE_RECORD_OUT_SIZE=0x6000
 CONFIG_PRE_CONSOLE_BUFFER=y
diff --git a/doc/develop/expo.rst b/doc/develop/expo.rst
index 32dd7f0..2ac4af2 100644
--- a/doc/develop/expo.rst
+++ b/doc/develop/expo.rst
@@ -85,6 +85,9 @@
 handled by allocating space in the enum for a maximum number of items, then
 adding the loop count to the enum values to obtain unique IDs.
 
+Where dynamic IDs are need, use expo_set_dynamic_start() to set the start value,
+so that they are allocated above the starting (enum) IDs.
+
 All text strings are stored in a structure attached to the expo, referenced by
 a text ID. This makes it easier at some point to implement multiple languages or
 to support Unicode strings.
@@ -97,10 +100,13 @@
 Creating an expo
 ----------------
 
-To create an expo, use `expo_new()` followed by `scene_new()` to create a scene.
-Then add objects to the scene, using functions like `scene_txt_str()` and
-`scene_menu()`. For every menu item, add text and image objects, then create
-the menu item with `scene_menuitem()`, referring to those objects.
+To create an expo programmatically, use `expo_new()` followed by `scene_new()`
+to create a scene. Then add objects to the scene, using functions like
+`scene_txt_str()` and `scene_menu()`. For every menu item, add text and image
+objects, then create the menu item with `scene_menuitem()`, referring to those
+objects.
+
+To create an expo using a description file, see :ref:`expo_format` below.
 
 Layout
 ------
@@ -152,8 +158,287 @@
 Themes
 ------
 
+Expo supports simple themes, for setting the font size, for example. Use the
+expo_apply_theme() function to load a theme, passing a node with the required
+properties:
+
+font-size
+    Font size to use for all text (type: u32)
+
+menu-inset
+    Number of pixels to inset the menu on the sides and top (type: u32)
+
+menuitem-gap-y
+    Number of pixels between menu items
+
+Pop-up mode
+-----------
+
+Expos support two modes. The simple mode is used for selecting from a single
+menu, e.g. when choosing with OS to boot. In this mode the menu items are shown
+in a list (label, > pointer, key and description) and can be chosen using arrow
+keys and enter::
+
+   U-Boot Boot Menu
+
+   UP and DOWN to choose, ENTER to select
+
+   mmc1           > 0  Fedora-Workstation-armhfp-31-1.9
+   mmc3             1  Armbian
+
+The popup mode allows multiple menus to be present in a scene. Each is shown
+just as its title and label, as with the `CPU Speed` and `AC Power` menus here::
+
+              Test Configuration
+
+
+   CPU Speed        <2 GHz>  (highlighted)
+
+   AC Power         Always Off
+
+
+     UP and DOWN to choose, ENTER to select
+
+
+.. _expo_format:
+
+Expo Format
+-----------
+
+It can be tedious to create a complex expo using code. Expo supports a
+data-driven approach, where the expo description is in a devicetree file. This
+makes it easier and faster to create and edit the description. An expo builder
+is provided to convert this format into an expo structure.
+
+Layout of the expo scenes is handled automatically, based on a set of simple
+rules. The :doc:`../usage/cmd/cedit` can be used to load a configuration
+and create an expo from it.
+
+Top-level node
+~~~~~~~~~~~~~~
+
+The top-level node has the following properties:
+
+dynamic-start
+    type: u32, optional
+
+    Specifies the start of the dynamically allocated objects. This results in
+    a call to expo_set_dynamic_start().
+
+The top-level node has the following subnodes:
+
+scenes
+    Specifies the scenes in the expo, each one being a subnode
+
+strings
+    Specifies the strings in the expo, each one being a subnode
+
+`scenes` node
+~~~~~~~~~~~~~
+
+Contains a list of scene subnodes. The name of each subnode is passed as the
+name to `scene_new()`.
+
+`strings` node
+~~~~~~~~~~~~~~
+
+Contains a list of string subnodes. The name of each subnode is ignored.
+
+`strings` subnodes
+~~~~~~~~~~~~~~~~~~
+
+Each subnode defines a string which can be used by scenes and objects. Each
+string has an ID number which is used to refer to it.
+
+The `strings` subnodes have the following properties:
+
+id
+    type: u32, required
+
+    Specifies the ID number for the string.
+
+value:
+    type: string, required
+
+    Specifies the string text. For now only a single value is supported. Future
+    work may add support for multiple languages by using a value for each
+    language.
+
+Scene nodes (`scenes` subnodes)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Each subnode of the `scenes` node contains a scene description.
+
+Most properties can use either a string or a string ID. For example, a `title`
+property can be used to provide the title for a menu; alternatively a `title-id`
+property can provide the string ID of the title. If both are present, the
+ID takes preference, except that if a string with that ID does not exist, it
+falls back to using the string from the property (`title` in this example). The
+description below shows these are alternative properties with the same
+description.
+
+The scene nodes have the following properties:
+
+id
+    type: u32, required
+
+    Specifies the ID number for the string.
+
-Expo does not itself support themes. The bootflow_menu implement supposed a
-basic theme, applying font sizes to the various text objects in the expo.
+title / title-id
+    type: string / u32, required
+
+    Specifies the title of the scene. This is shown at the top of the scene.
+
+prompt / prompt-id
+    type: string / u32, required
+
+    Specifies a prompt for the scene. This is shown at the bottom of the scene.
+
+The scene nodes have a subnode for each object in the scene.
+
+Object nodes
+~~~~~~~~~~~~
+
+The object-node name is used as the name of the object, e.g. when calling
+`scene_menu()` to create a menu.
+
+Object nodes have the following common properties:
+
+type
+    type: string, required
+
+    Specifies the type of the object. Valid types are:
+
+    "menu"
+        Menu containing items which can be selected by the user
+
+id
+    type: u32, required
+
+    Specifies the ID of the object. This is used when referring to the object.
+
+
+Menu nodes have the following additional properties:
+
+title / title-id
+    type: string / u32, required
+
+    Specifies the title of the menu. This is shown to the left of the area for
+    this menu.
+
+item-id
+    type: u32 list, required
+
+    Specifies the ID for each menu item. These are used for checking which item
+    has been selected.
+
+item-label / item-label-id
+    type: string list / u32 list, required
+
+    Specifies the label for each item in the menu. These are shown to the user.
+    In 'popup' mode these form the items in the menu.
+
+key-label / key-label-id
+    type: string list / u32 list, optional
+
+    Specifies the key for each item in the menu. These are currently only
+    intended for use in simple mode.
+
+desc-label / desc-label-id
+    type: string list / u32 list, optional
+
+    Specifies the description for each item in the menu. These are currently
+    only intended for use in simple mode.
+
+
+Expo layout
+~~~~~~~~~~~
+
+The `expo_arrange()` function can be called to arrange the expo objects in a
+suitable manner. For each scene it puts the title at the top, the prompt at the
+bottom and the objects in order from top to bottom.
+
+Expo format example
+~~~~~~~~~~~~~~~~~~~
+
+This example shows an expo with a single scene consisting of two menus. The
+scene title is specified using a string from the strings table, but all other
+strings are provided inline in the nodes where they are used.
+
+::
+
+    #define ID_PROMPT           1
+    #define ID_SCENE1           2
+    #define ID_SCENE1_TITLE     3
+
+    #define ID_CPU_SPEED        4
+    #define ID_CPU_SPEED_TITLE  5
+    #define ID_CPU_SPEED_1      6
+    #define ID_CPU_SPEED_2      7
+    #define ID_CPU_SPEED_3      8
+
+    #define ID_POWER_LOSS       9
+    #define ID_AC_OFF           10
+    #define ID_AC_ON            11
+    #define ID_AC_MEMORY        12
+
+    #define ID_DYNAMIC_START    13
+
+    &cedit {
+        dynamic-start = <ID_DYNAMIC_START>;
+
+        scenes {
+            main {
+                id = <ID_SCENE1>;
+
+                /* value refers to the matching id in /strings */
+                title-id = <ID_SCENE1_TITLE>;
+
+                /* simple string is used as it is */
+                prompt = "UP and DOWN to choose, ENTER to select";
+
+                /* defines a menu within the scene */
+                cpu-speed {
+                    type = "menu";
+                    id = <ID_CPU_SPEED>;
+
+                    /*
+                     * has both string and ID. The string is ignored
+                     * if the ID is present and points to a string
+                     */
+                    title = "CPU speed";
+                    title-id = <ID_CPU_SPEED_TITLE>;
+
+                    /* menu items as simple strings */
+                    item-label = "2 GHz", "2.5 GHz", "3 GHz";
+
+                    /* IDs for the menu items */
+                    item-id = <ID_CPU_SPEED_1 ID_CPU_SPEED_2
+                        ID_CPU_SPEED_3>;
+                };
+
+                power-loss {
+                    type = "menu";
+                    id = <ID_POWER_LOSS>;
+
+                    title = "AC Power";
+                    item-label = "Always Off", "Always On",
+                        "Memory";
+
+                    item-id = <ID_AC_OFF ID_AC_ON ID_AC_MEMORY>;
+                };
+            };
+        };
+
+        strings {
+            title {
+                id = <ID_SCENE1_TITLE>;
+                value = "Test Configuration";
+                value-es = "configuración de prueba";
+            };
+        };
+    };
+
 
 API documentation
 -----------------
@@ -166,12 +451,10 @@
 Some ideas for future work:
 
 - Default menu item and a timeout
-- Higher-level / automatic / more flexible layout of objects
 - Image formats other than BMP
 - Use of ANSI sequences to control a serial terminal
 - Colour selection
-- Better support for handling lots of settings, e.g. with multiple menus and
-  radio/option widgets
+- Support for more widgets, e.g. text, numeric, radio/option
 - Mouse support
 - Integrate Nuklear, NxWidgets or some other library for a richer UI
 - Optimise rendering by only updating the display with changes since last render
@@ -179,10 +462,10 @@
 - Add a Kconfig option to drop the names to save code / data space
 - Add a Kconfig option to disable vidconsole support to save code / data space
 - Support both graphical and text menus at the same time on different devices
-- Implement proper measurement of object bounding boxes, to permit more exact
-  layout. This would tidy up the layout when Truetype is not used
 - Support unicode
 - Support curses for proper serial-terminal menus
+- Add support for large menus which need to scroll
+- Add support for reading and writing configuration settings with cedit
 
 .. Simon Glass <sjg@chromium.org>
 .. 7-Oct-22
diff --git a/doc/usage/cmd/cedit.rst b/doc/usage/cmd/cedit.rst
new file mode 100644
index 0000000..8e1110c
--- /dev/null
+++ b/doc/usage/cmd/cedit.rst
@@ -0,0 +1,31 @@
+.. SPDX-License-Identifier: GPL-2.0+:
+
+cedit command
+=============
+
+Synopis
+-------
+
+::
+
+    cedit load <interface> <dev[:part]> <filename>
+    cedit run
+
+Description
+-----------
+
+The *cedit* command is used to load a configuration-editor description and allow
+the user to interact with it.
+
+It makes use of the expo subsystem.
+
+The description is in the form of a devicetree file, as documented at
+:ref:`expo_format`.
+
+Example
+-------
+
+::
+
+    => cedit load hostfs - fred.dtb
+    => cedit run
diff --git a/doc/usage/index.rst b/doc/usage/index.rst
index 388e59f..f2ffd27 100644
--- a/doc/usage/index.rst
+++ b/doc/usage/index.rst
@@ -39,6 +39,7 @@
    cmd/bootz
    cmd/cat
    cmd/cbsysinfo
+   cmd/cedit
    cmd/cls
    cmd/cmp
    cmd/coninfo
diff --git a/drivers/core/ofnode.c b/drivers/core/ofnode.c
index ec574c4..8df16e5 100644
--- a/drivers/core/ofnode.c
+++ b/drivers/core/ofnode.c
@@ -12,6 +12,7 @@
 #include <fdt_support.h>
 #include <log.h>
 #include <malloc.h>
+#include <of_live.h>
 #include <linux/libfdt.h>
 #include <dm/of_access.h>
 #include <dm/of_addr.h>
@@ -51,6 +52,20 @@
 	oftree tree;
 	int i;
 
+	if (of_live_active()) {
+		struct device_node *root;
+		int ret;
+
+		ret = unflatten_device_tree(fdt, &root);
+		if (ret) {
+			log_err("Failed to create live tree: err=%d\n", ret);
+			return oftree_null();
+		}
+		tree = oftree_from_np(root);
+
+		return tree;
+	}
+
 	if (gd->flags & GD_FLG_RELOC) {
 		i = oftree_find(fdt);
 		if (i == -1) {
@@ -67,7 +82,7 @@
 		}
 	} else {
 		if (fdt != gd->fdt_blob) {
-			log_debug("Cannot only access control FDT before relocation\n");
+			log_debug("Only the control FDT can be accessed before relocation\n");
 			return oftree_null();
 		}
 	}
@@ -77,6 +92,12 @@
 	return tree;
 }
 
+void oftree_dispose(oftree tree)
+{
+	if (of_live_active())
+		of_live_free(tree.np);
+}
+
 void *ofnode_lookup_fdt(ofnode node)
 {
 	if (gd->flags & GD_FLG_RELOC) {
@@ -133,6 +154,10 @@
 	if (CONFIG_IS_ENABLED(OFNODE_MULTI_TREE))
 		return oftree_ensure(fdt);
 
+#ifdef OF_CHECKS
+	if (of_live_active())
+		return oftree_null();
+#endif
 	tree.fdt = fdt;
 
 	return tree;
diff --git a/drivers/gpio/gpio-uclass.c b/drivers/gpio/gpio-uclass.c
index 712119c..31027f3 100644
--- a/drivers/gpio/gpio-uclass.c
+++ b/drivers/gpio/gpio-uclass.c
@@ -1474,7 +1474,7 @@
 	}
 #endif
 
-	if (CONFIG_IS_ENABLED(GPIO_HOG)) {
+	if (CONFIG_IS_ENABLED(GPIO_HOG) && dev_has_ofnode(dev)) {
 		struct udevice *child;
 		ofnode node;
 
diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c
index 6b53901..0f9bb49 100644
--- a/drivers/video/console_truetype.c
+++ b/drivers/video/console_truetype.c
@@ -62,10 +62,43 @@
 	return lo;
 }
 
+static double tt_fmod(double x, double y)
+{
+	double rem;
+
+	if (y == 0.0)
+		return 0.0;
+	rem = x - (x / y) * y;
+
+	return rem;
+}
+
+/* dummy implementation */
+static double tt_pow(double x, double y)
+{
+	return 0;
+}
+
+/* dummy implementation */
+static double tt_cos(double val)
+{
+	return 0;
+}
+
+/* dummy implementation */
+static double tt_acos(double val)
+{
+	return 0;
+}
+
 #define STBTT_ifloor		tt_floor
 #define STBTT_iceil		tt_ceil
 #define STBTT_fabs		tt_fabs
 #define STBTT_sqrt		tt_sqrt
+#define STBTT_pow		tt_pow
+#define STBTT_fmod		tt_fmod
+#define STBTT_cos		tt_cos
+#define STBTT_acos		tt_acos
 #define STBTT_malloc(size, u)	((void)(u), malloc(size))
 #define STBTT_free(size, u)	((void)(u), free(size))
 #define STBTT_assert(x)
@@ -154,33 +187,33 @@
 	end = line + met->font_size * vid_priv->line_length;
 
 	switch (vid_priv->bpix) {
-#ifdef CONFIG_VIDEO_BPP8
 	case VIDEO_BPP8: {
 		u8 *dst;
 
-		for (dst = line; dst < (u8 *)end; ++dst)
-			*dst = clr;
+		if (IS_ENABLED(CONFIG_VIDEO_BPP8)) {
+			for (dst = line; dst < (u8 *)end; ++dst)
+				*dst = clr;
+		}
 		break;
 	}
-#endif
-#ifdef CONFIG_VIDEO_BPP16
 	case VIDEO_BPP16: {
 		u16 *dst = line;
 
-		for (dst = line; dst < (u16 *)end; ++dst)
-			*dst = clr;
+		if (IS_ENABLED(CONFIG_VIDEO_BPP16)) {
+			for (dst = line; dst < (u16 *)end; ++dst)
+				*dst = clr;
+		}
 		break;
 	}
-#endif
-#ifdef CONFIG_VIDEO_BPP32
 	case VIDEO_BPP32: {
 		u32 *dst = line;
 
-		for (dst = line; dst < (u32 *)end; ++dst)
-			*dst = clr;
+		if (IS_ENABLED(CONFIG_VIDEO_BPP32)) {
+			for (dst = line; dst < (u32 *)end; ++dst)
+				*dst = clr;
+		}
 		break;
 	}
-#endif
 	default:
 		return -ENOSYS;
 	}
@@ -256,7 +289,7 @@
 	 */
 	x_shift = xpos - (double)tt_floor(xpos);
 	xpos += advance * met->scale;
-	width_frac = (int)VID_TO_POS(xpos);
+	width_frac = (int)VID_TO_POS(advance * met->scale);
 	if (x + width_frac >= vc_priv->xsize_frac)
 		return -EAGAIN;
 
@@ -317,52 +350,52 @@
 				end = dst;
 			}
 			break;
-#ifdef CONFIG_VIDEO_BPP16
 		case VIDEO_BPP16: {
 			uint16_t *dst = (uint16_t *)line + xoff;
 			int i;
 
-			for (i = 0; i < width; i++) {
-				int val = *bits;
-				int out;
+			if (IS_ENABLED(CONFIG_VIDEO_BPP16)) {
+				for (i = 0; i < width; i++) {
+					int val = *bits;
+					int out;
 
-				if (vid_priv->colour_bg)
-					val = 255 - val;
-				out = val >> 3 |
-					(val >> 2) << 5 |
-					(val >> 3) << 11;
-				if (vid_priv->colour_fg)
-					*dst++ |= out;
-				else
-					*dst++ &= out;
-				bits++;
+					if (vid_priv->colour_bg)
+						val = 255 - val;
+					out = val >> 3 |
+						(val >> 2) << 5 |
+						(val >> 3) << 11;
+					if (vid_priv->colour_fg)
+						*dst++ |= out;
+					else
+						*dst++ &= out;
+					bits++;
+				}
+				end = dst;
 			}
-			end = dst;
 			break;
 		}
-#endif
-#ifdef CONFIG_VIDEO_BPP32
 		case VIDEO_BPP32: {
 			u32 *dst = (u32 *)line + xoff;
 			int i;
 
-			for (i = 0; i < width; i++) {
-				int val = *bits;
-				int out;
+			if (IS_ENABLED(CONFIG_VIDEO_BPP32)) {
+				for (i = 0; i < width; i++) {
+					int val = *bits;
+					int out;
 
-				if (vid_priv->colour_bg)
-					val = 255 - val;
-				out = val | val << 8 | val << 16;
-				if (vid_priv->colour_fg)
-					*dst++ |= out;
-				else
-					*dst++ &= out;
-				bits++;
+					if (vid_priv->colour_bg)
+						val = 255 - val;
+					out = val | val << 8 | val << 16;
+					if (vid_priv->colour_fg)
+						*dst++ |= out;
+					else
+						*dst++ &= out;
+					bits++;
+				}
+				end = dst;
 			}
-			end = dst;
 			break;
 		}
-#endif
 		default:
 			free(data);
 			return -ENOSYS;
@@ -379,72 +412,6 @@
 }
 
 /**
- * console_truetype_erase() - Erase a character
- *
- * This is used for backspace. We erase a square of the display within the
- * given bounds.
- *
- * @dev:	Device to update
- * @xstart:	X start position in pixels from the left
- * @ystart:	Y start position in pixels from the top
- * @xend:	X end position in pixels from the left
- * @yend:	Y end position  in pixels from the top
- * @clr:	Value to write
- * Return: 0 if OK, -ENOSYS if the display depth is not supported
- */
-static int console_truetype_erase(struct udevice *dev, int xstart, int ystart,
-				  int xend, int yend, int clr)
-{
-	struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
-	void *start, *line;
-	int pixels = xend - xstart;
-	int row, i, ret;
-
-	start = vid_priv->fb + ystart * vid_priv->line_length;
-	start += xstart * VNBYTES(vid_priv->bpix);
-	line = start;
-	for (row = ystart; row < yend; row++) {
-		switch (vid_priv->bpix) {
-#ifdef CONFIG_VIDEO_BPP8
-		case VIDEO_BPP8: {
-			uint8_t *dst = line;
-
-			for (i = 0; i < pixels; i++)
-				*dst++ = clr;
-			break;
-		}
-#endif
-#ifdef CONFIG_VIDEO_BPP16
-		case VIDEO_BPP16: {
-			uint16_t *dst = line;
-
-			for (i = 0; i < pixels; i++)
-				*dst++ = clr;
-			break;
-		}
-#endif
-#ifdef CONFIG_VIDEO_BPP32
-		case VIDEO_BPP32: {
-			uint32_t *dst = line;
-
-			for (i = 0; i < pixels; i++)
-				*dst++ = clr;
-			break;
-		}
-#endif
-		default:
-			return -ENOSYS;
-		}
-		line += vid_priv->line_length;
-	}
-	ret = vidconsole_sync_copy(dev, start, line);
-	if (ret)
-		return ret;
-
-	return 0;
-}
-
-/**
  * console_truetype_backspace() - Handle a backspace operation
  *
  * This clears the previous character so that the console looks as if it had
@@ -482,9 +449,9 @@
 	else
 		xend = vid_priv->xsize;
 
-	console_truetype_erase(dev, VID_TO_PIXEL(pos->xpos_frac), pos->ypos,
-			       xend, pos->ypos + vc_priv->y_charsize,
-			       vid_priv->colour_bg);
+	video_fill_part(vid_dev, VID_TO_PIXEL(pos->xpos_frac), pos->ypos,
+			xend, pos->ypos + vc_priv->y_charsize,
+			vid_priv->colour_bg);
 
 	/* Move the cursor back to where it was when we pushed this record */
 	vc_priv->xcur_frac = pos->xpos_frac;
@@ -680,8 +647,8 @@
 	vc_priv->tab_width_frac = VID_TO_POS(met->font_size) * 8 / 2;
 }
 
-static int truetype_select_font(struct udevice *dev, const char *name,
-				uint size)
+static int get_metrics(struct udevice *dev, const char *name, uint size,
+		       struct console_tt_metrics **metp)
 {
 	struct console_tt_priv *priv = dev_get_priv(dev);
 	struct console_tt_metrics *met;
@@ -719,11 +686,70 @@
 		met = priv->metrics;
 	}
 
+	*metp = met;
+
+	return 0;
+}
+
+static int truetype_select_font(struct udevice *dev, const char *name,
+				uint size)
+{
+	struct console_tt_metrics *met;
+	int ret;
+
+	ret = get_metrics(dev, name, size, &met);
+	if (ret)
+		return log_msg_ret("sel", ret);
+
 	select_metrics(dev, met);
 
 	return 0;
 }
 
+int truetype_measure(struct udevice *dev, const char *name, uint size,
+		     const char *text, struct vidconsole_bbox *bbox)
+{
+	struct console_tt_metrics *met;
+	stbtt_fontinfo *font;
+	int lsb, advance;
+	const char *s;
+	int width;
+	int last;
+	int ret;
+
+	ret = get_metrics(dev, name, size, &met);
+	if (ret)
+		return log_msg_ret("sel", ret);
+
+	bbox->valid = false;
+	if (!*text)
+		return 0;
+
+	font = &met->font;
+	width = 0;
+	for (last = 0, s = text; *s; s++) {
+		int ch = *s;
+
+		/* Used kerning to fine-tune the position of this character */
+		if (last)
+			width += stbtt_GetCodepointKernAdvance(font, last, ch);
+
+		/* First get some basic metrics about this character */
+		stbtt_GetCodepointHMetrics(font, ch, &advance, &lsb);
+
+		width += advance;
+		last = ch;
+	}
+
+	bbox->valid = true;
+	bbox->x0 = 0;
+	bbox->y0 = 0;
+	bbox->x1 = tt_ceil((double)width * met->scale);
+	bbox->y1 = met->font_size;
+
+	return 0;
+}
+
 const char *console_truetype_get_font_size(struct udevice *dev, uint *sizep)
 {
 	struct console_tt_priv *priv = dev_get_priv(dev);
@@ -775,6 +801,7 @@
 	.get_font	= console_truetype_get_font,
 	.get_font_size	= console_truetype_get_font_size,
 	.select_font	= truetype_select_font,
+	.measure	= truetype_measure,
 };
 
 U_BOOT_DRIVER(vidconsole_truetype) = {
diff --git a/drivers/video/stb_truetype.h b/drivers/video/stb_truetype.h
index 438bfce..c6973bb 100644
--- a/drivers/video/stb_truetype.h
+++ b/drivers/video/stb_truetype.h
@@ -1,11 +1,21 @@
-// stb_truetype.h - v1.08 - public domain
-// authored from 2009-2015 by Sean Barrett / RAD Game Tools
+// stb_truetype.h - v1.26 - public domain
+// authored from 2009-2021 by Sean Barrett / RAD Game Tools
+//
+// =======================================================================
+//
+//    NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES
+//
+// This library does no range checking of the offsets found in the file,
+// meaning an attacker can use it to read arbitrary memory.
+//
+// =======================================================================
 //
 //   This library processes TrueType files:
 //        parse files
 //        extract glyph metrics
 //        extract glyph shapes
 //        render glyphs to one-channel bitmaps with antialiasing (box filter)
+//        render glyphs to one-channel SDF bitmaps (signed-distance field/function)
 //
 //   Todo:
 //        non-MS cmaps
@@ -20,58 +30,68 @@
 //
 //   Mikko Mononen: compound shape support, more cmap formats
 //   Tor Andersson: kerning, subpixel rendering
-//
-//   Bug/warning reports/fixes:
-//       "Zer" on mollyrocket (with fix)
-//       Cass Everitt
-//       stoiko (Haemimont Games)
-//       Brian Hook 
-//       Walter van Niftrik
-//       David Gow
-//       David Given
-//       Ivan-Assen Ivanov
-//       Anthony Pesch
-//       Johan Duparc
-//       Hou Qiming
-//       Fabian "ryg" Giesen
-//       Martins Mozeiko
-//       Cap Petschulat
-//       Omar Cornut
-//       github:aloucks
-//       Peter LaValle
-//       Sergey Popov
-//       Giumo X. Clanjor
-//       Higor Euripedes
+//   Dougall Johnson: OpenType / Type 2 font handling
+//   Daniel Ribeiro Maciel: basic GPOS-based kerning
 //
 //   Misc other:
 //       Ryan Gordon
+//       Simon Glass
+//       github:IntellectualKitty
+//       Imanol Celaya
+//       Daniel Ribeiro Maciel
+//
+//   Bug/warning reports/fixes:
+//       "Zer" on mollyrocket       Fabian "ryg" Giesen   github:NiLuJe
+//       Cass Everitt               Martins Mozeiko       github:aloucks
+//       stoiko (Haemimont Games)   Cap Petschulat        github:oyvindjam
+//       Brian Hook                 Omar Cornut           github:vassvik
+//       Walter van Niftrik         Ryan Griege
+//       David Gow                  Peter LaValle
+//       David Given                Sergey Popov
+//       Ivan-Assen Ivanov          Giumo X. Clanjor
+//       Anthony Pesch              Higor Euripedes
+//       Johan Duparc               Thomas Fields
+//       Hou Qiming                 Derek Vinyard
+//       Rob Loach                  Cort Stratton
+//       Kenney Phillis Jr.         Brian Costabile
+//       Ken Voskuil (kaesve)
 //
 // VERSION HISTORY
 //
+//   1.26 (2021-08-28) fix broken rasterizer
+//   1.25 (2021-07-11) many fixes
+//   1.24 (2020-02-05) fix warning
+//   1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS)
+//   1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined
+//   1.21 (2019-02-25) fix warning
+//   1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics()
+//   1.19 (2018-02-11) GPOS kerning, STBTT_fmod
+//   1.18 (2018-01-29) add missing function
+//   1.17 (2017-07-23) make more arguments const; doc fix
+//   1.16 (2017-07-12) SDF support
+//   1.15 (2017-03-03) make more arguments const
+//   1.14 (2017-01-16) num-fonts-in-TTC function
+//   1.13 (2017-01-02) support OpenType fonts, certain Apple fonts
+//   1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual
+//   1.11 (2016-04-02) fix unused-variable warning
+//   1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef
+//   1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly
 //   1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges
 //   1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints;
 //                     variant PackFontRanges to pack and render in separate phases;
 //                     fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?);
 //                     fixed an assert() bug in the new rasterizer
 //                     replace assert() with STBTT_assert() in new rasterizer
-//   1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine)
-//                     also more precise AA rasterizer, except if shapes overlap
-//                     remove need for STBTT_sort
-//   1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC
-//   1.04 (2015-04-15) typo in example
-//   1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes
 //
 //   Full history can be found at the end of this file.
 //
 // LICENSE
 //
-//   This software is in the public domain. Where that dedication is not
-//   recognized, you are granted a perpetual, irrevocable license to copy,
-//   distribute, and modify this file as you see fit.
+//   See end of file for license information.
 //
 // USAGE
 //
-//   Include this file in whatever places neeed to refer to it. In ONE C/C++
+//   Include this file in whatever places need to refer to it. In ONE C/C++
 //   file, write:
 //      #define STB_TRUETYPE_IMPLEMENTATION
 //   before the #include of this file. This expands out the actual
@@ -87,14 +107,15 @@
 //   Improved 3D API (more shippable):
 //           #include "stb_rect_pack.h"           -- optional, but you really want it
 //           stbtt_PackBegin()
-//           stbtt_PackSetOversample()            -- for improved quality on small fonts
+//           stbtt_PackSetOversampling()          -- for improved quality on small fonts
 //           stbtt_PackFontRanges()               -- pack and renders
 //           stbtt_PackEnd()
 //           stbtt_GetPackedQuad()
 //
 //   "Load" a font file from a memory buffer (you have to keep the buffer loaded)
 //           stbtt_InitFont()
-//           stbtt_GetFontOffsetForIndex()        -- use for TTC font collections
+//           stbtt_GetFontOffsetForIndex()        -- indexing for TTC font collections
+//           stbtt_GetNumberOfFonts()             -- number of fonts for TTC font collections
 //
 //   Render a unicode codepoint to a bitmap
 //           stbtt_GetCodepointBitmap()           -- allocates and returns a bitmap
@@ -104,6 +125,7 @@
 //   Character advance/positioning
 //           stbtt_GetCodepointHMetrics()
 //           stbtt_GetFontVMetrics()
+//           stbtt_GetFontVMetricsOS2()
 //           stbtt_GetCodepointKernAdvance()
 //
 //   Starting with version 1.06, the rasterizer was replaced with a new,
@@ -159,7 +181,7 @@
 //         measurement for describing font size, defined as 72 points per inch.
 //         stb_truetype provides a point API for compatibility. However, true
 //         "per inch" conventions don't make much sense on computer displays
-//         since they different monitors have different number of pixels per
+//         since different monitors have different number of pixels per
 //         inch. For example, Windows traditionally uses a convention that
 //         there are 96 pixels per inch, thus making 'inch' measurements have
 //         nothing to do with inches, and thus effectively defining a point to
@@ -169,6 +191,39 @@
 //         for non-commercial fonts, thus making fonts scaled in points
 //         according to the TrueType spec incoherently sized in practice.
 //
+// DETAILED USAGE:
+//
+//  Scale:
+//    Select how high you want the font to be, in points or pixels.
+//    Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute
+//    a scale factor SF that will be used by all other functions.
+//
+//  Baseline:
+//    You need to select a y-coordinate that is the baseline of where
+//    your text will appear. Call GetFontBoundingBox to get the baseline-relative
+//    bounding box for all characters. SF*-y0 will be the distance in pixels
+//    that the worst-case character could extend above the baseline, so if
+//    you want the top edge of characters to appear at the top of the
+//    screen where y=0, then you would set the baseline to SF*-y0.
+//
+//  Current point:
+//    Set the current point where the first character will appear. The
+//    first character could extend left of the current point; this is font
+//    dependent. You can either choose a current point that is the leftmost
+//    point and hope, or add some padding, or check the bounding box or
+//    left-side-bearing of the first character to be displayed and set
+//    the current point based on that.
+//
+//  Displaying a character:
+//    Compute the bounding box of the character. It will contain signed values
+//    relative to <current_point, baseline>. I.e. if it returns x0,y0,x1,y1,
+//    then the character should be displayed in the rectangle from
+//    <current_point+SF*x0, baseline+SF*y0> to <current_point+SF*x1,baseline+SF*y1).
+//
+//  Advancing for the next character:
+//    Call GlyphHMetrics, and compute 'current_point += SF * advance'.
+//
+//
 // ADVANCED USAGE
 //
 //   Quality:
@@ -202,19 +257,6 @@
 //   given file is in a general way. I provide an API for this, but I don't
 //   recommend it.
 //
-//
-// SOURCE STATISTICS (based on v0.6c, 2050 LOC)
-//
-//   Documentation & header file        520 LOC  \___ 660 LOC documentation
-//   Sample code                        140 LOC  /
-//   Truetype parsing                   620 LOC  ---- 620 LOC TrueType
-//   Software rasterization             240 LOC  \                           .
-//   Curve tesselation                  120 LOC   \__ 550 LOC Bitmap creation
-//   Bitmap management                  100 LOC   /
-//   Baked bitmap interface              70 LOC  /
-//   Font name matching & access        150 LOC  ---- 150 
-//   C runtime library abstraction       60 LOC  ----  60
-//
 //
 // PERFORMANCE MEASUREMENTS FOR 1.06:
 //
@@ -230,8 +272,8 @@
 ////  SAMPLE PROGRAMS
 ////
 //
-//  Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless
-//
+//  Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless.
+//  See "tests/truetype_demo_win32.c" for a complete version.
 #if 0
 #define STB_TRUETYPE_IMPLEMENTATION  // force following include to generate implementation
 #include "stb_truetype.h"
@@ -257,6 +299,8 @@
 void my_stbtt_print(float x, float y, char *text)
 {
    // assume orthographic projection with units = screen pixels, origin at top left
+   glEnable(GL_BLEND);
+   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, ftex);
    glBegin(GL_QUADS);
@@ -264,10 +308,10 @@
       if (*text >= 32 && *text < 128) {
          stbtt_aligned_quad q;
          stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9
-         glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0);
-         glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0);
-         glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1);
-         glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1);
+         glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y0);
+         glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y0);
+         glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y1);
+         glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y1);
       }
       ++text;
    }
@@ -305,7 +349,7 @@
    }
    return 0;
 }
-#endif 
+#endif
 //
 // Output:
 //
@@ -319,9 +363,9 @@
 //  :@@.  M@M
 //   @@@o@@@@
 //   :M@@V:@@.
-//  
+//
 //////////////////////////////////////////////////////////////////////////////
-// 
+//
 // Complete program: print "Hello World!" banner, with bugs
 //
 #if 0
@@ -375,7 +419,8 @@
 ////   INTEGRATION WITH YOUR CODEBASE
 ////
 ////   The following sections allow you to supply alternate definitions
-////   of C library functions used by stb_truetype.
+////   of C library functions used by stb_truetype, e.g. if you don't
+////   link with the C runtime library.
 
 #ifdef STB_TRUETYPE_IMPLEMENTATION
    // #define your own (u)stbtt_int8/16/32 before including to override this
@@ -391,7 +436,7 @@
    typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1];
    typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1];
 
-   // #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h
+   // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h
    #ifndef STBTT_ifloor
    #include <math.h>
    #define STBTT_ifloor(x)   ((int) floor(x))
@@ -401,8 +446,20 @@
    #ifndef STBTT_sqrt
    #include <math.h>
    #define STBTT_sqrt(x)      sqrt(x)
+   #define STBTT_pow(x,y)     pow(x,y)
    #endif
 
+   #ifndef STBTT_fmod
+   #include <math.h>
+   #define STBTT_fmod(x,y)    fmod(x,y)
+   #endif
+
+   #ifndef STBTT_cos
+   #include <math.h>
+   #define STBTT_cos(x)       cos(x)
+   #define STBTT_acos(x)      acos(x)
+   #endif
+
    #ifndef STBTT_fabs
    #include <math.h>
    #define STBTT_fabs(x)      fabs(x)
@@ -452,6 +509,14 @@
 extern "C" {
 #endif
 
+// private structure
+typedef struct
+{
+   unsigned char *data;
+   int cursor;
+   int size;
+} stbtt__buf;
+
 //////////////////////////////////////////////////////////////////////////////
 //
 // TEXTURE BAKING API
@@ -481,7 +546,7 @@
    float x1,y1,s1,t1; // bottom-right
 } stbtt_aligned_quad;
 
-STBTT_DEF void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph,  // same data as above
+STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph,  // same data as above
                                int char_index,             // character to display
                                float *xpos, float *ypos,   // pointers to current position in screen pixel space
                                stbtt_aligned_quad *q,      // output: quad to draw
@@ -496,6 +561,9 @@
 //
 // It's inefficient; you might want to c&p it and optimize it.
 
+STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap);
+// Query the font vertical metrics without having to create a font first.
+
 
 //////////////////////////////////////////////////////////////////////////////
 //
@@ -520,7 +588,7 @@
 STBTT_DEF int  stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context);
 // Initializes a packing context stored in the passed-in stbtt_pack_context.
 // Future calls using this context will pack characters into the bitmap passed
-// in here: a 1-channel bitmap that is weight x height. stride_in_bytes is
+// in here: a 1-channel bitmap that is width * height. stride_in_bytes is
 // the distance from one row to the next (or 0 to mean they are packed tightly
 // together). "padding" is the amount of padding to leave between each
 // character (normally you want '1' for bitmaps you'll use as textures with
@@ -533,7 +601,7 @@
 
 #define STBTT_POINT_SIZE(x)   (-(x))
 
-STBTT_DEF int  stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size,
+STBTT_DEF int  stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size,
                                 int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range);
 // Creates character bitmaps from the font_index'th font found in fontdata (use
 // font_index=0 if you don't know what that is). It creates num_chars_in_range
@@ -558,7 +626,7 @@
    unsigned char h_oversample, v_oversample; // don't set these, they're used internally
 } stbtt_pack_range;
 
-STBTT_DEF int  stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges);
+STBTT_DEF int  stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges);
 // Creates character bitmaps from multiple ranges of characters stored in
 // ranges. This will usually create a better-packed bitmap than multiple
 // calls to stbtt_PackFontRange. Note that you can call this multiple
@@ -580,19 +648,25 @@
 // To use with PackFontRangesGather etc., you must set it before calls
 // call to PackFontRangesGatherRects.
 
-STBTT_DEF void stbtt_GetPackedQuad(stbtt_packedchar *chardata, int pw, int ph,  // same data as above
+STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip);
+// If skip != 0, this tells stb_truetype to skip any codepoints for which
+// there is no corresponding glyph. If skip=0, which is the default, then
+// codepoints without a glyph recived the font's "missing character" glyph,
+// typically an empty box by convention.
+
+STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph,  // same data as above
                                int char_index,             // character to display
                                float *xpos, float *ypos,   // pointers to current position in screen pixel space
                                stbtt_aligned_quad *q,      // output: quad to draw
                                int align_to_integer);
 
-STBTT_DEF int  stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects);
+STBTT_DEF int  stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects);
 STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects);
-STBTT_DEF int  stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects);
+STBTT_DEF int  stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects);
 // Calling these functions in sequence is roughly equivalent to calling
 // stbtt_PackFontRanges(). If you more control over the packing of multiple
 // fonts, or if you want to pack custom data into a font texture, take a look
-// at the source to of stbtt_PackFontRanges() and create a custom version 
+// at the source to of stbtt_PackFontRanges() and create a custom version
 // using these functions, e.g. call GatherRects multiple times,
 // building up a single array of rects, then call PackRects once,
 // then call RenderIntoRects repeatedly. This may result in a
@@ -608,6 +682,7 @@
    int   height;
    int   stride_in_bytes;
    int   padding;
+   int   skip_missing;
    unsigned int   h_oversample, v_oversample;
    unsigned char *pixels;
    void  *nodes;
@@ -619,18 +694,23 @@
 //
 //
 
+STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data);
+// This function will determine the number of fonts in a font file.  TrueType
+// collection (.ttc) files may contain multiple fonts, while TrueType font
+// (.ttf) files only contain one font. The number of fonts can be used for
+// indexing with the previous function where the index is between zero and one
+// less than the total fonts. If an error occurs, -1 is returned.
+
 STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index);
 // Each .ttf/.ttc file may have more than one font. Each font has a sequential
 // index number starting from 0. Call this function to get the font offset for
 // a given index; it returns -1 if the index is out of range. A regular .ttf
 // file will only define one font and it always be at offset 0, so it will
-// return '0' for index 0, and -1 for all other indices. You can just skip
-// this step if you know it's that kind of font.
+// return '0' for index 0, and -1 for all other indices.
 
-
-// The following structure is defined publically so you can declare one on
+// The following structure is defined publicly so you can declare one on
 // the stack or as a global or etc, but you should treat it as opaque.
-typedef struct stbtt_fontinfo
+struct stbtt_fontinfo
 {
    void           * userdata;
    unsigned char  * data;              // pointer to .ttf file
@@ -638,10 +718,17 @@
 
    int numGlyphs;                     // number of glyphs, needed for range checking
 
-   int loca,head,glyf,hhea,hmtx,kern; // table locations as offset from start of .ttf
+   int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf
    int index_map;                     // a cmap mapping for our chosen character encoding
    int indexToLocFormat;              // format needed to map from glyph index to glyph
-} stbtt_fontinfo;
+
+   stbtt__buf cff;                    // cff font data
+   stbtt__buf charstrings;            // the charstring index
+   stbtt__buf gsubrs;                 // global charstring subroutines index
+   stbtt__buf subrs;                  // private charstring subroutines index
+   stbtt__buf fontdicts;              // array of font dicts
+   stbtt__buf fdselect;               // map from glyph to fontdict
+};
 
 STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset);
 // Given an offset into the file that defines a font, this function builds
@@ -660,6 +747,7 @@
 // and you want a speed-up, call this function with the character you're
 // going to process, then use glyph-based functions instead of the
 // codepoint-based functions.
+// Returns 0 if the character codepoint is not defined in the font.
 
 
 //////////////////////////////////////////////////////////////////////////////
@@ -688,6 +776,12 @@
 //   these are expressed in unscaled coordinates, so you must multiply by
 //   the scale factor for a given size
 
+STBTT_DEF int  stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap);
+// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2
+// table (specific to MS/Windows TTF files).
+//
+// Returns 1 on success (table present), 0 on failure.
+
 STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1);
 // the bounding box around all possible characters
 
@@ -707,6 +801,18 @@
 STBTT_DEF int  stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1);
 // as above, but takes one or more glyph indices for greater efficiency
 
+typedef struct stbtt_kerningentry
+{
+   int glyph1; // use stbtt_FindGlyphIndex
+   int glyph2;
+   int advance;
+} stbtt_kerningentry;
+
+STBTT_DEF int  stbtt_GetKerningTableLength(const stbtt_fontinfo *info);
+STBTT_DEF int  stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length);
+// Retrieves a complete list of all of the kerning pairs provided by the font
+// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write.
+// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1)
 
 //////////////////////////////////////////////////////////////////////////////
 //
@@ -718,7 +824,8 @@
    enum {
       STBTT_vmove=1,
       STBTT_vline,
-      STBTT_vcurve
+      STBTT_vcurve,
+      STBTT_vcubic
    };
 #endif
 
@@ -727,7 +834,7 @@
    #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file
    typedef struct
    {
-      stbtt_vertex_type x,y,cx,cy;
+      stbtt_vertex_type x,y,cx,cy,cx1,cy1;
       unsigned char type,padding;
    } stbtt_vertex;
 #endif
@@ -740,7 +847,7 @@
 // returns # of vertices and fills *vertices with the pointer to them
 //   these are expressed in "unscaled" coordinates
 //
-// The shape is a series of countours. Each one starts with
+// The shape is a series of contours. Each one starts with
 // a STBTT_moveto, then consists of a series of mixed
 // STBTT_lineto and STBTT_curveto segments. A lineto
 // draws a line from previous endpoint to its x,y; a curveto
@@ -750,6 +857,12 @@
 STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices);
 // frees the data allocated above
 
+STBTT_DEF unsigned char *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl);
+STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg);
+STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg);
+// fills svg with the character's SVG data.
+// returns data size or 0 if SVG not found.
+
 //////////////////////////////////////////////////////////////////////////////
 //
 // BITMAP RENDERING
@@ -781,6 +894,10 @@
 // same as stbtt_MakeCodepointBitmap, but you can specify a subpixel
 // shift for the character
 
+STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint);
+// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering
+// is performed (see stbtt_PackSetOversampling)
+
 STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1);
 // get the bbox of the bitmap centered around the glyph origin; so the
 // bitmap width is ix1-ix0, height is iy1-iy0, and location to place
@@ -798,6 +915,7 @@
 STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff);
 STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph);
 STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph);
+STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph);
 STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1);
 STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1);
 
@@ -822,6 +940,64 @@
 
 //////////////////////////////////////////////////////////////////////////////
 //
+// Signed Distance Function (or Field) rendering
+
+STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata);
+// frees the SDF bitmap allocated below
+
+STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff);
+STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff);
+// These functions compute a discretized SDF field for a single character, suitable for storing
+// in a single-channel texture, sampling with bilinear filtering, and testing against
+// larger than some threshold to produce scalable fonts.
+//        info              --  the font
+//        scale             --  controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap
+//        glyph/codepoint   --  the character to generate the SDF for
+//        padding           --  extra "pixels" around the character which are filled with the distance to the character (not 0),
+//                                 which allows effects like bit outlines
+//        onedge_value      --  value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character)
+//        pixel_dist_scale  --  what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale)
+//                                 if positive, > onedge_value is inside; if negative, < onedge_value is inside
+//        width,height      --  output height & width of the SDF bitmap (including padding)
+//        xoff,yoff         --  output origin of the character
+//        return value      --  a 2D array of bytes 0..255, width*height in size
+//
+// pixel_dist_scale & onedge_value are a scale & bias that allows you to make
+// optimal use of the limited 0..255 for your application, trading off precision
+// and special effects. SDF values outside the range 0..255 are clamped to 0..255.
+//
+// Example:
+//      scale = stbtt_ScaleForPixelHeight(22)
+//      padding = 5
+//      onedge_value = 180
+//      pixel_dist_scale = 180/5.0 = 36.0
+//
+//      This will create an SDF bitmap in which the character is about 22 pixels
+//      high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled
+//      shape, sample the SDF at each pixel and fill the pixel if the SDF value
+//      is greater than or equal to 180/255. (You'll actually want to antialias,
+//      which is beyond the scope of this example.) Additionally, you can compute
+//      offset outlines (e.g. to stroke the character border inside & outside,
+//      or only outside). For example, to fill outside the character up to 3 SDF
+//      pixels, you would compare against (180-36.0*3)/255 = 72/255. The above
+//      choice of variables maps a range from 5 pixels outside the shape to
+//      2 pixels inside the shape to 0..255; this is intended primarily for apply
+//      outside effects only (the interior range is needed to allow proper
+//      antialiasing of the font at *smaller* sizes)
+//
+// The function computes the SDF analytically at each SDF pixel, not by e.g.
+// building a higher-res bitmap and approximating it. In theory the quality
+// should be as high as possible for an SDF of this size & representation, but
+// unclear if this is true in practice (perhaps building a higher-res bitmap
+// and computing from that can allow drop-out prevention).
+//
+// The algorithm has not been optimized at all, so expect it to be slow
+// if computing lots of characters or very large sizes.
+
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
 // Finding the right font...
 //
 // You should really just solve this offline, keep your own tables
@@ -943,6 +1119,158 @@
 #define STBTT_RASTERIZER_VERSION 2
 #endif
 
+#ifdef _MSC_VER
+#define STBTT__NOTUSED(v)  (void)(v)
+#else
+#define STBTT__NOTUSED(v)  (void)sizeof(v)
+#endif
+
+//////////////////////////////////////////////////////////////////////////
+//
+// stbtt__buf helpers to parse data from file
+//
+
+static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b)
+{
+   if (b->cursor >= b->size)
+      return 0;
+   return b->data[b->cursor++];
+}
+
+static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b)
+{
+   if (b->cursor >= b->size)
+      return 0;
+   return b->data[b->cursor];
+}
+
+static void stbtt__buf_seek(stbtt__buf *b, int o)
+{
+   STBTT_assert(!(o > b->size || o < 0));
+   b->cursor = (o > b->size || o < 0) ? b->size : o;
+}
+
+static void stbtt__buf_skip(stbtt__buf *b, int o)
+{
+   stbtt__buf_seek(b, b->cursor + o);
+}
+
+static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n)
+{
+   stbtt_uint32 v = 0;
+   int i;
+   STBTT_assert(n >= 1 && n <= 4);
+   for (i = 0; i < n; i++)
+      v = (v << 8) | stbtt__buf_get8(b);
+   return v;
+}
+
+static stbtt__buf stbtt__new_buf(const void *p, size_t size)
+{
+   stbtt__buf r;
+   STBTT_assert(size < 0x40000000);
+   r.data = (stbtt_uint8*) p;
+   r.size = (int) size;
+   r.cursor = 0;
+   return r;
+}
+
+#define stbtt__buf_get16(b)  stbtt__buf_get((b), 2)
+#define stbtt__buf_get32(b)  stbtt__buf_get((b), 4)
+
+static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s)
+{
+   stbtt__buf r = stbtt__new_buf(NULL, 0);
+   if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r;
+   r.data = b->data + o;
+   r.size = s;
+   return r;
+}
+
+static stbtt__buf stbtt__cff_get_index(stbtt__buf *b)
+{
+   int count, start, offsize;
+   start = b->cursor;
+   count = stbtt__buf_get16(b);
+   if (count) {
+      offsize = stbtt__buf_get8(b);
+      STBTT_assert(offsize >= 1 && offsize <= 4);
+      stbtt__buf_skip(b, offsize * count);
+      stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1);
+   }
+   return stbtt__buf_range(b, start, b->cursor - start);
+}
+
+static stbtt_uint32 stbtt__cff_int(stbtt__buf *b)
+{
+   int b0 = stbtt__buf_get8(b);
+   if (b0 >= 32 && b0 <= 246)       return b0 - 139;
+   else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108;
+   else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108;
+   else if (b0 == 28)               return stbtt__buf_get16(b);
+   else if (b0 == 29)               return stbtt__buf_get32(b);
+   STBTT_assert(0);
+   return 0;
+}
+
+static void stbtt__cff_skip_operand(stbtt__buf *b) {
+   int v, b0 = stbtt__buf_peek8(b);
+   STBTT_assert(b0 >= 28);
+   if (b0 == 30) {
+      stbtt__buf_skip(b, 1);
+      while (b->cursor < b->size) {
+         v = stbtt__buf_get8(b);
+         if ((v & 0xF) == 0xF || (v >> 4) == 0xF)
+            break;
+      }
+   } else {
+      stbtt__cff_int(b);
+   }
+}
+
+static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key)
+{
+   stbtt__buf_seek(b, 0);
+   while (b->cursor < b->size) {
+      int start = b->cursor, end, op;
+      while (stbtt__buf_peek8(b) >= 28)
+         stbtt__cff_skip_operand(b);
+      end = b->cursor;
+      op = stbtt__buf_get8(b);
+      if (op == 12)  op = stbtt__buf_get8(b) | 0x100;
+      if (op == key) return stbtt__buf_range(b, start, end-start);
+   }
+   return stbtt__buf_range(b, 0, 0);
+}
+
+static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out)
+{
+   int i;
+   stbtt__buf operands = stbtt__dict_get(b, key);
+   for (i = 0; i < outcount && operands.cursor < operands.size; i++)
+      out[i] = stbtt__cff_int(&operands);
+}
+
+static int stbtt__cff_index_count(stbtt__buf *b)
+{
+   stbtt__buf_seek(b, 0);
+   return stbtt__buf_get16(b);
+}
+
+static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i)
+{
+   int count, offsize, start, end;
+   stbtt__buf_seek(&b, 0);
+   count = stbtt__buf_get16(&b);
+   offsize = stbtt__buf_get8(&b);
+   STBTT_assert(i >= 0 && i < count);
+   STBTT_assert(offsize >= 1 && offsize <= 4);
+   stbtt__buf_skip(&b, i*offsize);
+   start = stbtt__buf_get(&b, offsize);
+   end = stbtt__buf_get(&b, offsize);
+   return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start);
+}
+
 //////////////////////////////////////////////////////////////////////////
 //
 // accessors to parse data from file
@@ -955,32 +1283,22 @@
 #define ttCHAR(p)     (* (stbtt_int8 *) (p))
 #define ttFixed(p)    ttLONG(p)
 
-#if defined(STB_TRUETYPE_BIGENDIAN) && !defined(ALLOW_UNALIGNED_TRUETYPE)
-
-   #define ttUSHORT(p)   (* (stbtt_uint16 *) (p))
-   #define ttSHORT(p)    (* (stbtt_int16 *) (p))
-   #define ttULONG(p)    (* (stbtt_uint32 *) (p))
-   #define ttLONG(p)     (* (stbtt_int32 *) (p))
-
-#else
-
-   static stbtt_uint16 ttUSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; }
-   static stbtt_int16 ttSHORT(const stbtt_uint8 *p)   { return p[0]*256 + p[1]; }
-   static stbtt_uint32 ttULONG(const stbtt_uint8 *p)  { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; }
-   static stbtt_int32 ttLONG(const stbtt_uint8 *p)    { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; }
-
-#endif
+static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; }
+static stbtt_int16 ttSHORT(stbtt_uint8 *p)   { return p[0]*256 + p[1]; }
+static stbtt_uint32 ttULONG(stbtt_uint8 *p)  { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; }
+static stbtt_int32 ttLONG(stbtt_uint8 *p)    { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; }
 
 #define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3))
 #define stbtt_tag(p,str)           stbtt_tag4(p,str[0],str[1],str[2],str[3])
 
-static int stbtt__isfont(const stbtt_uint8 *font)
+static int stbtt__isfont(stbtt_uint8 *font)
 {
    // check the version number
    if (stbtt_tag4(font, '1',0,0,0))  return 1; // TrueType 1
    if (stbtt_tag(font, "typ1"))   return 1; // TrueType with type 1 font -- we don't support this!
    if (stbtt_tag(font, "OTTO"))   return 1; // OpenType with CFF
    if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0
+   if (stbtt_tag(font, "true"))   return 1; // Apple specification for TrueType fonts
    return 0;
 }
 
@@ -998,7 +1316,7 @@
    return 0;
 }
 
-STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *font_collection, int index)
+static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index)
 {
    // if it's just a font, there's only one valid index
    if (stbtt__isfont(font_collection))
@@ -1017,14 +1335,59 @@
    return -1;
 }
 
-STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data2, int fontstart)
+static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection)
 {
-   stbtt_uint8 *data = (stbtt_uint8 *) data2;
+   // if it's just a font, there's only one valid font
+   if (stbtt__isfont(font_collection))
+      return 1;
+
+   // check if it's a TTC
+   if (stbtt_tag(font_collection, "ttcf")) {
+      // version 1?
+      if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) {
+         return ttLONG(font_collection+8);
+      }
+   }
+   return 0;
+}
+
+static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict)
+{
+   stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 };
+   stbtt__buf pdict;
+   stbtt__dict_get_ints(&fontdict, 18, 2, private_loc);
+   if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0);
+   pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]);
+   stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff);
+   if (!subrsoff) return stbtt__new_buf(NULL, 0);
+   stbtt__buf_seek(&cff, private_loc[1]+subrsoff);
+   return stbtt__cff_get_index(&cff);
+}
+
+// since most people won't use this, find this table the first time it's needed
+static int stbtt__get_svg(stbtt_fontinfo *info)
+{
+   stbtt_uint32 t;
+   if (info->svg < 0) {
+      t = stbtt__find_table(info->data, info->fontstart, "SVG ");
+      if (t) {
+         stbtt_uint32 offset = ttULONG(info->data + t + 2);
+         info->svg = t + offset;
+      } else {
+         info->svg = 0;
+      }
+   }
+   return info->svg;
+}
+
+static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart)
+{
    stbtt_uint32 cmap, t;
    stbtt_int32 i,numTables;
 
    info->data = data;
    info->fontstart = fontstart;
+   info->cff = stbtt__new_buf(NULL, 0);
 
    cmap = stbtt__find_table(data, fontstart, "cmap");       // required
    info->loca = stbtt__find_table(data, fontstart, "loca"); // required
@@ -1033,8 +1396,62 @@
    info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required
    info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required
    info->kern = stbtt__find_table(data, fontstart, "kern"); // not required
-   if (!cmap || !info->loca || !info->head || !info->glyf || !info->hhea || !info->hmtx)
+   info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required
+
+   if (!cmap || !info->head || !info->hhea || !info->hmtx)
       return 0;
+   if (info->glyf) {
+      // required for truetype
+      if (!info->loca) return 0;
+   } else {
+      // initialization for CFF / Type2 fonts (OTF)
+      stbtt__buf b, topdict, topdictidx;
+      stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0;
+      stbtt_uint32 cff;
+
+      cff = stbtt__find_table(data, fontstart, "CFF ");
+      if (!cff) return 0;
+
+      info->fontdicts = stbtt__new_buf(NULL, 0);
+      info->fdselect = stbtt__new_buf(NULL, 0);
+
+      // @TODO this should use size from table (not 512MB)
+      info->cff = stbtt__new_buf(data+cff, 512*1024*1024);
+      b = info->cff;
+
+      // read the header
+      stbtt__buf_skip(&b, 2);
+      stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize
+
+      // @TODO the name INDEX could list multiple fonts,
+      // but we just use the first one.
+      stbtt__cff_get_index(&b);  // name INDEX
+      topdictidx = stbtt__cff_get_index(&b);
+      topdict = stbtt__cff_index_get(topdictidx, 0);
+      stbtt__cff_get_index(&b);  // string INDEX
+      info->gsubrs = stbtt__cff_get_index(&b);
+
+      stbtt__dict_get_ints(&topdict, 17, 1, &charstrings);
+      stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype);
+      stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff);
+      stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff);
+      info->subrs = stbtt__get_subrs(b, topdict);
+
+      // we only support Type 2 charstrings
+      if (cstype != 2) return 0;
+      if (charstrings == 0) return 0;
+
+      if (fdarrayoff) {
+         // looks like a CID font
+         if (!fdselectoff) return 0;
+         stbtt__buf_seek(&b, fdarrayoff);
+         info->fontdicts = stbtt__cff_get_index(&b);
+         info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff);
+      }
+
+      stbtt__buf_seek(&b, charstrings);
+      info->charstrings = stbtt__cff_get_index(&b);
+   }
 
    t = stbtt__find_table(data, fontstart, "maxp");
    if (t)
@@ -1042,6 +1459,8 @@
    else
       info->numGlyphs = 0xffff;
 
+   info->svg = -1;
+
    // find a cmap encoding table we understand *now* to avoid searching
    // later. (todo: could make this installable)
    // the same regardless of glyph.
@@ -1125,12 +1544,12 @@
       search += 2;
 
       {
-         stbtt_uint16 offset, start;
+         stbtt_uint16 offset, start, last;
          stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1);
 
-         STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item));
          start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item);
-         if (unicode_codepoint < start)
+         last = ttUSHORT(data + endCount + 2*item);
+         if (unicode_codepoint < start || unicode_codepoint > last)
             return 0;
 
          offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item);
@@ -1185,6 +1604,8 @@
 {
    int g1,g2;
 
+   STBTT_assert(!info->cff.size);
+
    if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range
    if (info->indexToLocFormat >= 2)    return -1; // unknown index->glyph map format
 
@@ -1199,15 +1620,21 @@
    return g1==g2 ? -1 : g1; // if length is 0, return -1
 }
 
+static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1);
+
 STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1)
 {
-   int g = stbtt__GetGlyfOffset(info, glyph_index);
-   if (g < 0) return 0;
+   if (info->cff.size) {
+      stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1);
+   } else {
+      int g = stbtt__GetGlyfOffset(info, glyph_index);
+      if (g < 0) return 0;
 
-   if (x0) *x0 = ttSHORT(info->data + g + 2);
-   if (y0) *y0 = ttSHORT(info->data + g + 4);
-   if (x1) *x1 = ttSHORT(info->data + g + 6);
-   if (y1) *y1 = ttSHORT(info->data + g + 8);
+      if (x0) *x0 = ttSHORT(info->data + g + 2);
+      if (y0) *y0 = ttSHORT(info->data + g + 4);
+      if (x1) *x1 = ttSHORT(info->data + g + 6);
+      if (y1) *y1 = ttSHORT(info->data + g + 8);
+   }
    return 1;
 }
 
@@ -1219,7 +1646,10 @@
 STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index)
 {
    stbtt_int16 numberOfContours;
-   int g = stbtt__GetGlyfOffset(info, glyph_index);
+   int g;
+   if (info->cff.size)
+      return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0;
+   g = stbtt__GetGlyfOffset(info, glyph_index);
    if (g < 0) return 1;
    numberOfContours = ttSHORT(info->data + g);
    return numberOfContours == 0;
@@ -1241,7 +1671,7 @@
    return num_vertices;
 }
 
-STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices)
+static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices)
 {
    stbtt_int16 numberOfContours;
    stbtt_uint8 *endPtsOfContours;
@@ -1337,7 +1767,7 @@
             if (i != 0)
                num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy);
 
-            // now start the new one               
+            // now start the new one
             start_off = !(flags & 1);
             if (start_off) {
                // if we start off with an off-curve point, then when we need to find a point on the curve
@@ -1379,7 +1809,7 @@
          }
       }
       num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy);
-   } else if (numberOfContours == -1) {
+   } else if (numberOfContours < 0) {
       // Compound shapes.
       int more = 1;
       stbtt_uint8 *comp = data + g + 10;
@@ -1390,7 +1820,7 @@
          int comp_num_verts = 0, i;
          stbtt_vertex *comp_verts = 0, *tmp = 0;
          float mtx[6] = {1,0,0,1,0,0}, m, n;
-         
+
          flags = ttSHORT(comp); comp+=2;
          gidx = ttSHORT(comp); comp+=2;
 
@@ -1420,7 +1850,7 @@
             mtx[2] = ttSHORT(comp)/16384.0f; comp+=2;
             mtx[3] = ttSHORT(comp)/16384.0f; comp+=2;
          }
-         
+
          // Find transformation scales.
          m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]);
          n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]);
@@ -1446,7 +1876,7 @@
                if (comp_verts) STBTT_free(comp_verts, info->userdata);
                return 0;
             }
-            if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex));
+            if (num_vertices > 0 && vertices) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex));
             STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex));
             if (vertices) STBTT_free(vertices, info->userdata);
             vertices = tmp;
@@ -1456,9 +1886,6 @@
          // More components ?
          more = flags & (1<<5);
       }
-   } else if (numberOfContours < 0) {
-      // @TODO other compound variations?
-      STBTT_assert(0);
    } else {
       // numberOfCounters == 0, do nothing
    }
@@ -1467,6 +1894,414 @@
    return num_vertices;
 }
 
+typedef struct
+{
+   int bounds;
+   int started;
+   float first_x, first_y;
+   float x, y;
+   stbtt_int32 min_x, max_x, min_y, max_y;
+
+   stbtt_vertex *pvertices;
+   int num_vertices;
+} stbtt__csctx;
+
+#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0}
+
+static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y)
+{
+   if (x > c->max_x || !c->started) c->max_x = x;
+   if (y > c->max_y || !c->started) c->max_y = y;
+   if (x < c->min_x || !c->started) c->min_x = x;
+   if (y < c->min_y || !c->started) c->min_y = y;
+   c->started = 1;
+}
+
+static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1)
+{
+   if (c->bounds) {
+      stbtt__track_vertex(c, x, y);
+      if (type == STBTT_vcubic) {
+         stbtt__track_vertex(c, cx, cy);
+         stbtt__track_vertex(c, cx1, cy1);
+      }
+   } else {
+      stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy);
+      c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1;
+      c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1;
+   }
+   c->num_vertices++;
+}
+
+static void stbtt__csctx_close_shape(stbtt__csctx *ctx)
+{
+   if (ctx->first_x != ctx->x || ctx->first_y != ctx->y)
+      stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0);
+}
+
+static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy)
+{
+   stbtt__csctx_close_shape(ctx);
+   ctx->first_x = ctx->x = ctx->x + dx;
+   ctx->first_y = ctx->y = ctx->y + dy;
+   stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0);
+}
+
+static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy)
+{
+   ctx->x += dx;
+   ctx->y += dy;
+   stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0);
+}
+
+static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3)
+{
+   float cx1 = ctx->x + dx1;
+   float cy1 = ctx->y + dy1;
+   float cx2 = cx1 + dx2;
+   float cy2 = cy1 + dy2;
+   ctx->x = cx2 + dx3;
+   ctx->y = cy2 + dy3;
+   stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2);
+}
+
+static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n)
+{
+   int count = stbtt__cff_index_count(&idx);
+   int bias = 107;
+   if (count >= 33900)
+      bias = 32768;
+   else if (count >= 1240)
+      bias = 1131;
+   n += bias;
+   if (n < 0 || n >= count)
+      return stbtt__new_buf(NULL, 0);
+   return stbtt__cff_index_get(idx, n);
+}
+
+static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index)
+{
+   stbtt__buf fdselect = info->fdselect;
+   int nranges, start, end, v, fmt, fdselector = -1, i;
+
+   stbtt__buf_seek(&fdselect, 0);
+   fmt = stbtt__buf_get8(&fdselect);
+   if (fmt == 0) {
+      // untested
+      stbtt__buf_skip(&fdselect, glyph_index);
+      fdselector = stbtt__buf_get8(&fdselect);
+   } else if (fmt == 3) {
+      nranges = stbtt__buf_get16(&fdselect);
+      start = stbtt__buf_get16(&fdselect);
+      for (i = 0; i < nranges; i++) {
+         v = stbtt__buf_get8(&fdselect);
+         end = stbtt__buf_get16(&fdselect);
+         if (glyph_index >= start && glyph_index < end) {
+            fdselector = v;
+            break;
+         }
+         start = end;
+      }
+   }
+   if (fdselector == -1) stbtt__new_buf(NULL, 0);
+   return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector));
+}
+
+static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c)
+{
+   int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0;
+   int has_subrs = 0, clear_stack;
+   float s[48];
+   stbtt__buf subr_stack[10], subrs = info->subrs, b;
+   float f;
+
+#define STBTT__CSERR(s) (0)
+
+   // this currently ignores the initial width value, which isn't needed if we have hmtx
+   b = stbtt__cff_index_get(info->charstrings, glyph_index);
+   while (b.cursor < b.size) {
+      i = 0;
+      clear_stack = 1;
+      b0 = stbtt__buf_get8(&b);
+      switch (b0) {
+      // @TODO implement hinting
+      case 0x13: // hintmask
+      case 0x14: // cntrmask
+         if (in_header)
+            maskbits += (sp / 2); // implicit "vstem"
+         in_header = 0;
+         stbtt__buf_skip(&b, (maskbits + 7) / 8);
+         break;
+
+      case 0x01: // hstem
+      case 0x03: // vstem
+      case 0x12: // hstemhm
+      case 0x17: // vstemhm
+         maskbits += (sp / 2);
+         break;
+
+      case 0x15: // rmoveto
+         in_header = 0;
+         if (sp < 2) return STBTT__CSERR("rmoveto stack");
+         stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]);
+         break;
+      case 0x04: // vmoveto
+         in_header = 0;
+         if (sp < 1) return STBTT__CSERR("vmoveto stack");
+         stbtt__csctx_rmove_to(c, 0, s[sp-1]);
+         break;
+      case 0x16: // hmoveto
+         in_header = 0;
+         if (sp < 1) return STBTT__CSERR("hmoveto stack");
+         stbtt__csctx_rmove_to(c, s[sp-1], 0);
+         break;
+
+      case 0x05: // rlineto
+         if (sp < 2) return STBTT__CSERR("rlineto stack");
+         for (; i + 1 < sp; i += 2)
+            stbtt__csctx_rline_to(c, s[i], s[i+1]);
+         break;
+
+      // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical
+      // starting from a different place.
+
+      case 0x07: // vlineto
+         if (sp < 1) return STBTT__CSERR("vlineto stack");
+         goto vlineto;
+      case 0x06: // hlineto
+         if (sp < 1) return STBTT__CSERR("hlineto stack");
+         for (;;) {
+            if (i >= sp) break;
+            stbtt__csctx_rline_to(c, s[i], 0);
+            i++;
+      vlineto:
+            if (i >= sp) break;
+            stbtt__csctx_rline_to(c, 0, s[i]);
+            i++;
+         }
+         break;
+
+      case 0x1F: // hvcurveto
+         if (sp < 4) return STBTT__CSERR("hvcurveto stack");
+         goto hvcurveto;
+      case 0x1E: // vhcurveto
+         if (sp < 4) return STBTT__CSERR("vhcurveto stack");
+         for (;;) {
+            if (i + 3 >= sp) break;
+            stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f);
+            i += 4;
+      hvcurveto:
+            if (i + 3 >= sp) break;
+            stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]);
+            i += 4;
+         }
+         break;
+
+      case 0x08: // rrcurveto
+         if (sp < 6) return STBTT__CSERR("rcurveline stack");
+         for (; i + 5 < sp; i += 6)
+            stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]);
+         break;
+
+      case 0x18: // rcurveline
+         if (sp < 8) return STBTT__CSERR("rcurveline stack");
+         for (; i + 5 < sp - 2; i += 6)
+            stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]);
+         if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack");
+         stbtt__csctx_rline_to(c, s[i], s[i+1]);
+         break;
+
+      case 0x19: // rlinecurve
+         if (sp < 8) return STBTT__CSERR("rlinecurve stack");
+         for (; i + 1 < sp - 6; i += 2)
+            stbtt__csctx_rline_to(c, s[i], s[i+1]);
+         if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack");
+         stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]);
+         break;
+
+      case 0x1A: // vvcurveto
+      case 0x1B: // hhcurveto
+         if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack");
+         f = 0.0;
+         if (sp & 1) { f = s[i]; i++; }
+         for (; i + 3 < sp; i += 4) {
+            if (b0 == 0x1B)
+               stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0);
+            else
+               stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]);
+            f = 0.0;
+         }
+         break;
+
+      case 0x0A: // callsubr
+         if (!has_subrs) {
+            if (info->fdselect.size)
+               subrs = stbtt__cid_get_glyph_subrs(info, glyph_index);
+            has_subrs = 1;
+         }
+         // FALLTHROUGH
+      case 0x1D: // callgsubr
+         if (sp < 1) return STBTT__CSERR("call(g|)subr stack");
+         v = (int) s[--sp];
+         if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit");
+         subr_stack[subr_stack_height++] = b;
+         b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v);
+         if (b.size == 0) return STBTT__CSERR("subr not found");
+         b.cursor = 0;
+         clear_stack = 0;
+         break;
+
+      case 0x0B: // return
+         if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr");
+         b = subr_stack[--subr_stack_height];
+         clear_stack = 0;
+         break;
+
+      case 0x0E: // endchar
+         stbtt__csctx_close_shape(c);
+         return 1;
+
+      case 0x0C: { // two-byte escape
+         float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6;
+         float dx, dy;
+         int b1 = stbtt__buf_get8(&b);
+         switch (b1) {
+         // @TODO These "flex" implementations ignore the flex-depth and resolution,
+         // and always draw beziers.
+         case 0x22: // hflex
+            if (sp < 7) return STBTT__CSERR("hflex stack");
+            dx1 = s[0];
+            dx2 = s[1];
+            dy2 = s[2];
+            dx3 = s[3];
+            dx4 = s[4];
+            dx5 = s[5];
+            dx6 = s[6];
+            stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0);
+            stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0);
+            break;
+
+         case 0x23: // flex
+            if (sp < 13) return STBTT__CSERR("flex stack");
+            dx1 = s[0];
+            dy1 = s[1];
+            dx2 = s[2];
+            dy2 = s[3];
+            dx3 = s[4];
+            dy3 = s[5];
+            dx4 = s[6];
+            dy4 = s[7];
+            dx5 = s[8];
+            dy5 = s[9];
+            dx6 = s[10];
+            dy6 = s[11];
+            //fd is s[12]
+            stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3);
+            stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6);
+            break;
+
+         case 0x24: // hflex1
+            if (sp < 9) return STBTT__CSERR("hflex1 stack");
+            dx1 = s[0];
+            dy1 = s[1];
+            dx2 = s[2];
+            dy2 = s[3];
+            dx3 = s[4];
+            dx4 = s[5];
+            dx5 = s[6];
+            dy5 = s[7];
+            dx6 = s[8];
+            stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0);
+            stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5));
+            break;
+
+         case 0x25: // flex1
+            if (sp < 11) return STBTT__CSERR("flex1 stack");
+            dx1 = s[0];
+            dy1 = s[1];
+            dx2 = s[2];
+            dy2 = s[3];
+            dx3 = s[4];
+            dy3 = s[5];
+            dx4 = s[6];
+            dy4 = s[7];
+            dx5 = s[8];
+            dy5 = s[9];
+            dx6 = dy6 = s[10];
+            dx = dx1+dx2+dx3+dx4+dx5;
+            dy = dy1+dy2+dy3+dy4+dy5;
+            if (STBTT_fabs(dx) > STBTT_fabs(dy))
+               dy6 = -dy;
+            else
+               dx6 = -dx;
+            stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3);
+            stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6);
+            break;
+
+         default:
+            return STBTT__CSERR("unimplemented");
+         }
+      } break;
+
+      default:
+         if (b0 != 255 && b0 != 28 && b0 < 32)
+            return STBTT__CSERR("reserved operator");
+
+         // push immediate
+         if (b0 == 255) {
+            f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000;
+         } else {
+            stbtt__buf_skip(&b, -1);
+            f = (float)(stbtt_int16)stbtt__cff_int(&b);
+         }
+         if (sp >= 48) return STBTT__CSERR("push stack overflow");
+         s[sp++] = f;
+         clear_stack = 0;
+         break;
+      }
+      if (clear_stack) sp = 0;
+   }
+   return STBTT__CSERR("no endchar");
+
+#undef STBTT__CSERR
+}
+
+static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices)
+{
+   // runs the charstring twice, once to count and once to output (to avoid realloc)
+   stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1);
+   stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0);
+   if (stbtt__run_charstring(info, glyph_index, &count_ctx)) {
+      *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata);
+      output_ctx.pvertices = *pvertices;
+      if (stbtt__run_charstring(info, glyph_index, &output_ctx)) {
+         STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices);
+         return output_ctx.num_vertices;
+      }
+   }
+   *pvertices = NULL;
+   return 0;
+}
+
+static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1)
+{
+   stbtt__csctx c = STBTT__CSCTX_INIT(1);
+   int r = stbtt__run_charstring(info, glyph_index, &c);
+   if (x0)  *x0 = r ? c.min_x : 0;
+   if (y0)  *y0 = r ? c.min_y : 0;
+   if (x1)  *x1 = r ? c.max_x : 0;
+   if (y1)  *y1 = r ? c.max_y : 0;
+   return r ? c.num_vertices : 0;
+}
+
+STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices)
+{
+   if (!info->cff.size)
+      return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices);
+   else
+      return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices);
+}
+
 STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing)
 {
    stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34);
@@ -1477,41 +2312,316 @@
       if (advanceWidth)     *advanceWidth    = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1));
       if (leftSideBearing)  *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics));
    }
+}
+
+STBTT_DEF int  stbtt_GetKerningTableLength(const stbtt_fontinfo *info)
+{
+   stbtt_uint8 *data = info->data + info->kern;
+
+   // we only look at the first table. it must be 'horizontal' and format 0.
+   if (!info->kern)
+      return 0;
+   if (ttUSHORT(data+2) < 1) // number of tables, need at least 1
+      return 0;
+   if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format
+      return 0;
+
+   return ttUSHORT(data+10);
+}
+
+STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length)
+{
+   stbtt_uint8 *data = info->data + info->kern;
+   int k, length;
+
+   // we only look at the first table. it must be 'horizontal' and format 0.
+   if (!info->kern)
+      return 0;
+   if (ttUSHORT(data+2) < 1) // number of tables, need at least 1
+      return 0;
+   if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format
+      return 0;
+
+   length = ttUSHORT(data+10);
+   if (table_length < length)
+      length = table_length;
+
+   for (k = 0; k < length; k++)
+   {
+      table[k].glyph1 = ttUSHORT(data+18+(k*6));
+      table[k].glyph2 = ttUSHORT(data+20+(k*6));
+      table[k].advance = ttSHORT(data+22+(k*6));
+   }
+
+   return length;
+}
+
+static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2)
+{
+   stbtt_uint8 *data = info->data + info->kern;
+   stbtt_uint32 needle, straw;
+   int l, r, m;
+
+   // we only look at the first table. it must be 'horizontal' and format 0.
+   if (!info->kern)
+      return 0;
+   if (ttUSHORT(data+2) < 1) // number of tables, need at least 1
+      return 0;
+   if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format
+      return 0;
+
+   l = 0;
+   r = ttUSHORT(data+10) - 1;
+   needle = glyph1 << 16 | glyph2;
+   while (l <= r) {
+      m = (l + r) >> 1;
+      straw = ttULONG(data+18+(m*6)); // note: unaligned read
+      if (needle < straw)
+         r = m - 1;
+      else if (needle > straw)
+         l = m + 1;
+      else
+         return ttSHORT(data+22+(m*6));
+   }
+   return 0;
+}
+
+static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph)
+{
+   stbtt_uint16 coverageFormat = ttUSHORT(coverageTable);
+   switch (coverageFormat) {
+      case 1: {
+         stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2);
+
+         // Binary search.
+         stbtt_int32 l=0, r=glyphCount-1, m;
+         int straw, needle=glyph;
+         while (l <= r) {
+            stbtt_uint8 *glyphArray = coverageTable + 4;
+            stbtt_uint16 glyphID;
+            m = (l + r) >> 1;
+            glyphID = ttUSHORT(glyphArray + 2 * m);
+            straw = glyphID;
+            if (needle < straw)
+               r = m - 1;
+            else if (needle > straw)
+               l = m + 1;
+            else {
+               return m;
+            }
+         }
+         break;
+      }
+
+      case 2: {
+         stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2);
+         stbtt_uint8 *rangeArray = coverageTable + 4;
+
+         // Binary search.
+         stbtt_int32 l=0, r=rangeCount-1, m;
+         int strawStart, strawEnd, needle=glyph;
+         while (l <= r) {
+            stbtt_uint8 *rangeRecord;
+            m = (l + r) >> 1;
+            rangeRecord = rangeArray + 6 * m;
+            strawStart = ttUSHORT(rangeRecord);
+            strawEnd = ttUSHORT(rangeRecord + 2);
+            if (needle < strawStart)
+               r = m - 1;
+            else if (needle > strawEnd)
+               l = m + 1;
+            else {
+               stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4);
+               return startCoverageIndex + glyph - strawStart;
+            }
+         }
+         break;
+      }
+
+      default: return -1; // unsupported
+   }
+
+   return -1;
+}
+
+static stbtt_int32  stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph)
+{
+   stbtt_uint16 classDefFormat = ttUSHORT(classDefTable);
+   switch (classDefFormat)
+   {
+      case 1: {
+         stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2);
+         stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4);
+         stbtt_uint8 *classDef1ValueArray = classDefTable + 6;
+
+         if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount)
+            return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID));
+         break;
+      }
+
+      case 2: {
+         stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2);
+         stbtt_uint8 *classRangeRecords = classDefTable + 4;
+
+         // Binary search.
+         stbtt_int32 l=0, r=classRangeCount-1, m;
+         int strawStart, strawEnd, needle=glyph;
+         while (l <= r) {
+            stbtt_uint8 *classRangeRecord;
+            m = (l + r) >> 1;
+            classRangeRecord = classRangeRecords + 6 * m;
+            strawStart = ttUSHORT(classRangeRecord);
+            strawEnd = ttUSHORT(classRangeRecord + 2);
+            if (needle < strawStart)
+               r = m - 1;
+            else if (needle > strawEnd)
+               l = m + 1;
+            else
+               return (stbtt_int32)ttUSHORT(classRangeRecord + 4);
+         }
+         break;
+      }
+
+      default:
+         return -1; // Unsupported definition type, return an error.
+   }
+
+   // "All glyphs not assigned to a class fall into class 0". (OpenType spec)
+   return 0;
+}
+
+// Define to STBTT_assert(x) if you want to break on unimplemented formats.
+#define STBTT_GPOS_TODO_assert(x)
+
+static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2)
+{
+   stbtt_uint16 lookupListOffset;
+   stbtt_uint8 *lookupList;
+   stbtt_uint16 lookupCount;
+   stbtt_uint8 *data;
+   stbtt_int32 i, sti;
+
+   if (!info->gpos) return 0;
+
+   data = info->data + info->gpos;
+
+   if (ttUSHORT(data+0) != 1) return 0; // Major version 1
+   if (ttUSHORT(data+2) != 0) return 0; // Minor version 0
+
+   lookupListOffset = ttUSHORT(data+8);
+   lookupList = data + lookupListOffset;
+   lookupCount = ttUSHORT(lookupList);
+
+   for (i=0; i<lookupCount; ++i) {
+      stbtt_uint16 lookupOffset = ttUSHORT(lookupList + 2 + 2 * i);
+      stbtt_uint8 *lookupTable = lookupList + lookupOffset;
+
+      stbtt_uint16 lookupType = ttUSHORT(lookupTable);
+      stbtt_uint16 subTableCount = ttUSHORT(lookupTable + 4);
+      stbtt_uint8 *subTableOffsets = lookupTable + 6;
+      if (lookupType != 2) // Pair Adjustment Positioning Subtable
+         continue;
+
+      for (sti=0; sti<subTableCount; sti++) {
+         stbtt_uint16 subtableOffset = ttUSHORT(subTableOffsets + 2 * sti);
+         stbtt_uint8 *table = lookupTable + subtableOffset;
+         stbtt_uint16 posFormat = ttUSHORT(table);
+         stbtt_uint16 coverageOffset = ttUSHORT(table + 2);
+         stbtt_int32 coverageIndex = stbtt__GetCoverageIndex(table + coverageOffset, glyph1);
+         if (coverageIndex == -1) continue;
+
+         switch (posFormat) {
+            case 1: {
+               stbtt_int32 l, r, m;
+               int straw, needle;
+               stbtt_uint16 valueFormat1 = ttUSHORT(table + 4);
+               stbtt_uint16 valueFormat2 = ttUSHORT(table + 6);
+               if (valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats?
+                  stbtt_int32 valueRecordPairSizeInBytes = 2;
+                  stbtt_uint16 pairSetCount = ttUSHORT(table + 8);
+                  stbtt_uint16 pairPosOffset = ttUSHORT(table + 10 + 2 * coverageIndex);
+                  stbtt_uint8 *pairValueTable = table + pairPosOffset;
+                  stbtt_uint16 pairValueCount = ttUSHORT(pairValueTable);
+                  stbtt_uint8 *pairValueArray = pairValueTable + 2;
+
+                  if (coverageIndex >= pairSetCount) return 0;
+
+                  needle=glyph2;
+                  r=pairValueCount-1;
+                  l=0;
+
+                  // Binary search.
+                  while (l <= r) {
+                     stbtt_uint16 secondGlyph;
+                     stbtt_uint8 *pairValue;
+                     m = (l + r) >> 1;
+                     pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m;
+                     secondGlyph = ttUSHORT(pairValue);
+                     straw = secondGlyph;
+                     if (needle < straw)
+                        r = m - 1;
+                     else if (needle > straw)
+                        l = m + 1;
+                     else {
+                        stbtt_int16 xAdvance = ttSHORT(pairValue + 2);
+                        return xAdvance;
+                     }
+                  }
+               } else
+                  return 0;
+               break;
+            }
+
+            case 2: {
+               stbtt_uint16 valueFormat1 = ttUSHORT(table + 4);
+               stbtt_uint16 valueFormat2 = ttUSHORT(table + 6);
+               if (valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats?
+                  stbtt_uint16 classDef1Offset = ttUSHORT(table + 8);
+                  stbtt_uint16 classDef2Offset = ttUSHORT(table + 10);
+                  int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1);
+                  int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2);
+
+                  stbtt_uint16 class1Count = ttUSHORT(table + 12);
+                  stbtt_uint16 class2Count = ttUSHORT(table + 14);
+                  stbtt_uint8 *class1Records, *class2Records;
+                  stbtt_int16 xAdvance;
+
+                  if (glyph1class < 0 || glyph1class >= class1Count) return 0; // malformed
+                  if (glyph2class < 0 || glyph2class >= class2Count) return 0; // malformed
+
+                  class1Records = table + 16;
+                  class2Records = class1Records + 2 * (glyph1class * class2Count);
+                  xAdvance = ttSHORT(class2Records + 2 * glyph2class);
+                  return xAdvance;
+               } else
+                  return 0;
+               break;
+            }
+
+            default:
+               return 0; // Unsupported position format
+         }
+      }
+   }
+
+   return 0;
 }
 
-STBTT_DEF int  stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2)
+STBTT_DEF int  stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2)
 {
-   stbtt_uint8 *data = info->data + info->kern;
-   stbtt_uint32 needle, straw;
-   int l, r, m;
+   int xAdvance = 0;
 
-   // we only look at the first table. it must be 'horizontal' and format 0.
-   if (!info->kern)
-      return 0;
-   if (ttUSHORT(data+2) < 1) // number of tables, need at least 1
-      return 0;
-   if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format
-      return 0;
+   if (info->gpos)
+      xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2);
+   else if (info->kern)
+      xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2);
 
-   l = 0;
-   r = ttUSHORT(data+10) - 1;
-   needle = glyph1 << 16 | glyph2;
-   while (l <= r) {
-      m = (l + r) >> 1;
-      straw = ttULONG(data+18+(m*6)); // note: unaligned read
-      if (needle < straw)
-         r = m - 1;
-      else if (needle > straw)
-         l = m + 1;
-      else
-         return ttSHORT(data+22+(m*6));
-   }
-   return 0;
+   return xAdvance;
 }
 
 STBTT_DEF int  stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2)
 {
-   if (!info->kern) // if no kerning table, don't waste time looking up both codepoint->glyphs
+   if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs
       return 0;
    return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2));
 }
@@ -1528,6 +2638,17 @@
    if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8);
 }
 
+STBTT_DEF int  stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap)
+{
+   int tab = stbtt__find_table(info->data, info->fontstart, "OS/2");
+   if (!tab)
+      return 0;
+   if (typoAscent ) *typoAscent  = ttSHORT(info->data+tab + 68);
+   if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70);
+   if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72);
+   return 1;
+}
+
 STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1)
 {
    *x0 = ttSHORT(info->data + info->head + 36);
@@ -1553,6 +2674,45 @@
    STBTT_free(v, info->userdata);
 }
 
+STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl)
+{
+   int i;
+   stbtt_uint8 *data = info->data;
+   stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info);
+
+   int numEntries = ttUSHORT(svg_doc_list);
+   stbtt_uint8 *svg_docs = svg_doc_list + 2;
+
+   for(i=0; i<numEntries; i++) {
+      stbtt_uint8 *svg_doc = svg_docs + (12 * i);
+      if ((gl >= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2)))
+         return svg_doc;
+   }
+   return 0;
+}
+
+STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg)
+{
+   stbtt_uint8 *data = info->data;
+   stbtt_uint8 *svg_doc;
+
+   if (info->svg == 0)
+      return 0;
+
+   svg_doc = stbtt_FindSVGDoc(info, gl);
+   if (svg_doc != NULL) {
+      *svg = (char *) data + info->svg + ttULONG(svg_doc + 4);
+      return ttULONG(svg_doc + 8);
+   } else {
+      return 0;
+   }
+}
+
+STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg)
+{
+   return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg);
+}
+
 //////////////////////////////////////////////////////////////////////////////
 //
 // antialiasing software rasterizer
@@ -1560,7 +2720,7 @@
 
 STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1)
 {
-   int x0,y0,x1,y1;
+   int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning
    if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) {
       // e.g. space character
       if (ix0) *ix0 = 0;
@@ -1624,7 +2784,7 @@
          hh->num_remaining_in_head_chunk = count;
       }
       --hh->num_remaining_in_head_chunk;
-      return (char *) (hh->head) + size * hh->num_remaining_in_head_chunk;
+      return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk;
    }
 }
 
@@ -1676,8 +2836,9 @@
 {
    stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata);
    float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0);
+   STBTT_assert(z != NULL);
    if (!z) return z;
-   
+
    // round dx down to avoid overshooting
    if (dxdy < 0)
       z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy);
@@ -1697,6 +2858,7 @@
 {
    stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata);
    float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0);
+   STBTT_assert(z != NULL);
    //STBTT_assert(e->y0 <= start_point);
    if (!z) return z;
    z->fdx = dxdy;
@@ -1754,7 +2916,7 @@
             }
          }
       }
-      
+
       e = e->next;
    }
 }
@@ -1768,13 +2930,10 @@
    int s; // vertical subsample index
    unsigned char scanline_data[512], *scanline;
 
-   if (result->w > 512) {
+   if (result->w > 512)
       scanline = (unsigned char *) STBTT_malloc(result->w, userdata);
-      if (!scanline)
-         return;
-   } else {
+   else
       scanline = scanline_data;
-   }
 
    y = off_y * vsubsample;
    e[n].y0 = (off_y + result->h) * (float) vsubsample + 1;
@@ -1824,23 +2983,23 @@
          while (e->y0 <= scan_y) {
             if (e->y1 > scan_y) {
                stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata);
-               if (!z)
-                  return;
-               // find insertion point
-               if (active == NULL)
-                  active = z;
-               else if (z->x < active->x) {
-                  // insert at front
-                  z->next = active;
-                  active = z;
-               } else {
-                  // find thing to insert AFTER
-                  stbtt__active_edge *p = active;
-                  while (p->next && p->next->x < z->x)
-                     p = p->next;
-                  // at this point, p->next->x is NOT < z->x
-                  z->next = p->next;
-                  p->next = z;
+               if (z != NULL) {
+                  // find insertion point
+                  if (active == NULL)
+                     active = z;
+                  else if (z->x < active->x) {
+                     // insert at front
+                     z->next = active;
+                     active = z;
+                  } else {
+                     // find thing to insert AFTER
+                     stbtt__active_edge *p = active;
+                     while (p->next && p->next->x < z->x)
+                        p = p->next;
+                     // at this point, p->next->x is NOT < z->x
+                     z->next = p->next;
+                     p->next = z;
+                  }
                }
             }
             ++e;
@@ -1903,6 +3062,23 @@
    }
 }
 
+static float stbtt__sized_trapezoid_area(float height, float top_width, float bottom_width)
+{
+   STBTT_assert(top_width >= 0);
+   STBTT_assert(bottom_width >= 0);
+   return (top_width + bottom_width) / 2.0f * height;
+}
+
+static float stbtt__position_trapezoid_area(float height, float tx0, float tx1, float bx0, float bx1)
+{
+   return stbtt__sized_trapezoid_area(height, tx1 - tx0, bx1 - bx0);
+}
+
+static float stbtt__sized_triangle_area(float height, float width)
+{
+   return height * width / 2;
+}
+
 static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top)
 {
    float y_bottom = y_top+1;
@@ -1957,13 +3133,13 @@
                float height;
                // simple case, only spans one pixel
                int x = (int) x_top;
-               height = sy1 - sy0;
+               height = (sy1 - sy0) * e->direction;
                STBTT_assert(x >= 0 && x < len);
-               scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2)  * height;
-               scanline_fill[x] += e->direction * height; // everything right of this pixel is filled
+               scanline[x]      += stbtt__position_trapezoid_area(height, x_top, x+1.0f, x_bottom, x+1.0f);
+               scanline_fill[x] += height; // everything right of this pixel is filled
             } else {
                int x,x1,x2;
-               float y_crossing, step, sign, area;
+               float y_crossing, y_final, step, sign, area;
                // covers 2+ pixels
                if (x_top > x_bottom) {
                   // flip scanline vertically; signed area is the same
@@ -1976,29 +3152,79 @@
                   dy = -dy;
                   t = x0, x0 = xb, xb = t;
                }
+               STBTT_assert(dy >= 0);
+               STBTT_assert(dx >= 0);
 
                x1 = (int) x_top;
                x2 = (int) x_bottom;
                // compute intersection with y axis at x1+1
-               y_crossing = (x1+1 - x0) * dy + y_top;
+               y_crossing = y_top + dy * (x1+1 - x0);
+
+               // compute intersection with y axis at x2
+               y_final = y_top + dy * (x2 - x0);
+
+               //           x1    x_top                            x2    x_bottom
+               //     y_top  +------|-----+------------+------------+--------|---+------------+
+               //            |            |            |            |            |            |
+               //            |            |            |            |            |            |
+               //       sy0  |      Txxxxx|............|............|............|............|
+               // y_crossing |            *xxxxx.......|............|............|............|
+               //            |            |     xxxxx..|............|............|............|
+               //            |            |     /-   xx*xxxx........|............|............|
+               //            |            | dy <       |    xxxxxx..|............|............|
+               //   y_final  |            |     \-     |          xx*xxx.........|............|
+               //       sy1  |            |            |            |   xxxxxB...|............|
+               //            |            |            |            |            |            |
+               //            |            |            |            |            |            |
+               //  y_bottom  +------------+------------+------------+------------+------------+
+               //
+               // goal is to measure the area covered by '.' in each pixel
+
+               // if x2 is right at the right edge of x1, y_crossing can blow up, github #1057
+               // @TODO: maybe test against sy1 rather than y_bottom?
+               if (y_crossing > y_bottom)
+                  y_crossing = y_bottom;
 
                sign = e->direction;
-               // area of the rectangle covered from y0..y_crossing
+
+               // area of the rectangle covered from sy0..y_crossing
                area = sign * (y_crossing-sy0);
-               // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing)
-               scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2);
+
+               // area of the triangle (x_top,sy0), (x1+1,sy0), (x1+1,y_crossing)
+               scanline[x1] += stbtt__sized_triangle_area(area, x1+1 - x_top);
+
+               // check if final y_crossing is blown up; no test case for this
+               if (y_final > y_bottom) {
+                  y_final = y_bottom;
+                  dy = (y_final - y_crossing ) / (x2 - (x1+1)); // if denom=0, y_final = y_crossing, so y_final <= y_bottom
+               }
 
-               step = sign * dy;
+               // in second pixel, area covered by line segment found in first pixel
+               // is always a rectangle 1 wide * the height of that line segment; this
+               // is exactly what the variable 'area' stores. it also gets a contribution
+               // from the line segment within it. the THIRD pixel will get the first
+               // pixel's rectangle contribution, the second pixel's rectangle contribution,
+               // and its own contribution. the 'own contribution' is the same in every pixel except
+               // the leftmost and rightmost, a trapezoid that slides down in each pixel.
+               // the second pixel's contribution to the third pixel will be the
+               // rectangle 1 wide times the height change in the second pixel, which is dy.
+
+               step = sign * dy * 1; // dy is dy/dx, change in y for every 1 change in x,
+               // which multiplied by 1-pixel-width is how much pixel area changes for each step in x
+               // so the area advances by 'step' every time
+
                for (x = x1+1; x < x2; ++x) {
-                  scanline[x] += area + step/2;
+                  scanline[x] += area + step/2; // area of trapezoid is 1*step/2
                   area += step;
                }
-               y_crossing += dy * (x2 - (x1+1));
+               STBTT_assert(STBTT_fabs(area) <= 1.01f); // accumulated error from area += step unless we round step down
+               STBTT_assert(sy1 > y_final-0.01f);
 
-               STBTT_assert(fabs(area) <= 1.01f);
+               // area covered in the last pixel is the rectangle from all the pixels to the left,
+               // plus the trapezoid filled by the line segment in this pixel all the way to the right edge
+               scanline[x2] += area + sign * stbtt__position_trapezoid_area(sy1-y_final, (float) x2, x2+1.0f, x_bottom, x2+1.0f);
 
-               scanline[x2] += area + sign * (1-(x_bottom-x2)/2) * (sy1-y_crossing);
-
+               // the rest of the line is filled based on the total height of the line segment in this pixel
                scanline_fill[x2] += sign * (sy1-sy0);
             }
          } else {
@@ -2006,6 +3232,9 @@
             // clipping logic. since this does not match the intended use
             // of this library, we use a different, very slow brute
             // force implementation
+            // note though that this does happen some of the time because
+            // x_top and x_bottom can be extrapolated at the top & bottom of
+            // the shape and actually lie outside the bounding box
             int x;
             for (x=0; x < len; ++x) {
                // cases:
@@ -2021,19 +3250,18 @@
                // from the other y segment, and it might ignored as an empty segment. to avoid
                // that, we need to explicitly produce segments based on x positions.
 
-               // rename variables to clear pairs
+               // rename variables to clearly-defined pairs
                float y0 = y_top;
                float x1 = (float) (x);
                float x2 = (float) (x+1);
                float x3 = xb;
                float y3 = y_bottom;
-               float y1,y2;
 
                // x = e->x + e->dx * (y-y_top)
                // (y-y_top) = (x - e->x) / e->dx
                // y = (x - e->x) / e->dx + y_top
-               y1 = (x - x0) / dx + y_top;
-               y2 = (x+1 - x0) / dx + y_top;
+               float y1 = (x - x0) / dx + y_top;
+               float y2 = (x+1 - x0) / dx + y_top;
 
                if (x0 < x1 && x3 > x2) {         // three segments descending down-right
                   stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1);
@@ -2073,13 +3301,12 @@
    int y,j=0, i;
    float scanline_data[129], *scanline, *scanline2;
 
+   STBTT__NOTUSED(vsubsample);
+
-   if (result->w > 64) {
+   if (result->w > 64)
       scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata);
-      if (!scanline)
-         return;
-   } else {
+   else
       scanline = scanline_data;
-   }
 
    scanline2 = scanline + result->w;
 
@@ -2113,12 +3340,18 @@
       while (e->y0 <= scan_y_bottom) {
          if (e->y0 != e->y1) {
             stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata);
-            if (!z)
-               return;
-            STBTT_assert(z->ey >= scan_y_top);
-            // insert at front
-            z->next = active;
-            active = z;
+            if (z != NULL) {
+               if (j == 0 && off_y != 0) {
+                  if (z->ey < scan_y_top) {
+                     // this can happen due to subpixel positioning and some kind of fp rounding error i think
+                     z->ey = scan_y_top;
+                  }
+               }
+               STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds
+               // insert at front
+               z->next = active;
+               active = z;
+            }
          }
          ++e;
       }
@@ -2183,7 +3416,7 @@
 
 static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n)
 {
-   /* threshhold for transitioning to insertion sort */
+   /* threshold for transitioning to insertion sort */
    while (n > 12) {
       stbtt__edge t;
       int c01,c12,c,m,i,j;
@@ -2318,7 +3551,7 @@
    points[n].y = y;
 }
 
-// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching
+// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching
 static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n)
 {
    // midpoint
@@ -2339,6 +3572,48 @@
    return 1;
 }
 
+static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n)
+{
+   // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough
+   float dx0 = x1-x0;
+   float dy0 = y1-y0;
+   float dx1 = x2-x1;
+   float dy1 = y2-y1;
+   float dx2 = x3-x2;
+   float dy2 = y3-y2;
+   float dx = x3-x0;
+   float dy = y3-y0;
+   float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2));
+   float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy);
+   float flatness_squared = longlen*longlen-shortlen*shortlen;
+
+   if (n > 16) // 65536 segments on one curve better be enough!
+      return;
+
+   if (flatness_squared > objspace_flatness_squared) {
+      float x01 = (x0+x1)/2;
+      float y01 = (y0+y1)/2;
+      float x12 = (x1+x2)/2;
+      float y12 = (y1+y2)/2;
+      float x23 = (x2+x3)/2;
+      float y23 = (y2+y3)/2;
+
+      float xa = (x01+x12)/2;
+      float ya = (y01+y12)/2;
+      float xb = (x12+x23)/2;
+      float yb = (y12+y23)/2;
+
+      float mx = (xa+xb)/2;
+      float my = (ya+yb)/2;
+
+      stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1);
+      stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1);
+   } else {
+      stbtt__add_point(points, *num_points,x3,y3);
+      *num_points = *num_points+1;
+   }
+}
+
 // returns number of contours
 static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata)
 {
@@ -2391,6 +3666,14 @@
             case STBTT_vcurve:
                stbtt__tesselate_curve(points, &num_points, x,y,
                                         vertices[i].cx, vertices[i].cy,
+                                        vertices[i].x,  vertices[i].y,
+                                        objspace_flatness_squared, 0);
+               x = vertices[i].x, y = vertices[i].y;
+               break;
+            case STBTT_vcubic:
+               stbtt__tesselate_cubic(points, &num_points, x,y,
+                                        vertices[i].cx, vertices[i].cy,
+                                        vertices[i].cx1, vertices[i].cy1,
                                         vertices[i].x,  vertices[i].y,
                                         objspace_flatness_squared, 0);
                x = vertices[i].x, y = vertices[i].y;
@@ -2411,8 +3694,9 @@
 
 STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata)
 {
-   float scale = scale_x > scale_y ? scale_y : scale_x;
-   int winding_count, *winding_lengths;
+   float scale            = scale_x > scale_y ? scale_y : scale_x;
+   int winding_count      = 0;
+   int *winding_lengths   = NULL;
    stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata);
    if (windings) {
       stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata);
@@ -2430,7 +3714,7 @@
 {
    int ix0,iy0,ix1,iy1;
    stbtt__bitmap gbm;
-   stbtt_vertex *vertices;   
+   stbtt_vertex *vertices;
    int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices);
 
    if (scale_x == 0) scale_x = scale_y;
@@ -2453,7 +3737,7 @@
    if (height) *height = gbm.h;
    if (xoff  ) *xoff   = ix0;
    if (yoff  ) *yoff   = iy0;
-   
+
    if (gbm.w && gbm.h) {
       gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata);
       if (gbm.pixels) {
@@ -2464,7 +3748,7 @@
    }
    STBTT_free(vertices, info->userdata);
    return gbm.pixels;
-}   
+}
 
 STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff)
 {
@@ -2476,7 +3760,7 @@
    int ix0,iy0;
    stbtt_vertex *vertices;
    int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices);
-   stbtt__bitmap gbm;   
+   stbtt__bitmap gbm;
 
    stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0);
    gbm.pixels = output;
@@ -2498,7 +3782,12 @@
 STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff)
 {
    return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff);
-}   
+}
+
+STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint)
+{
+   stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint));
+}
 
 STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint)
 {
@@ -2508,7 +3797,7 @@
 STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff)
 {
    return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff);
-}   
+}
 
 STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint)
 {
@@ -2521,7 +3810,7 @@
 //
 // This is SUPER-CRAPPY packing to keep source code small
 
-STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset,  // font location (use offset=0 for plain .ttf)
+static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset,  // font location (use offset=0 for plain .ttf)
                                 float pixel_height,                     // height of font in pixels
                                 unsigned char *pixels, int pw, int ph,  // bitmap to be filled in
                                 int first_char, int num_chars,          // characters to bake
@@ -2530,6 +3819,7 @@
    float scale;
    int x,y,bottom_y, i;
    stbtt_fontinfo f;
+   f.userdata = NULL;
    if (!stbtt_InitFont(&f, data, offset))
       return -1;
    STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels
@@ -2566,11 +3856,11 @@
    return bottom_y;
 }
 
-STBTT_DEF void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule)
+STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule)
 {
    float d3d_bias = opengl_fillrule ? 0 : -0.5f;
    float ipw = 1.0f / pw, iph = 1.0f / ph;
-   stbtt_bakedchar *b = chardata + char_index;
+   const stbtt_bakedchar *b = chardata + char_index;
    int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f);
    int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f);
 
@@ -2593,11 +3883,6 @@
 //
 
 #ifndef STB_RECT_PACK_VERSION
-#ifdef _MSC_VER
-#define STBTT__NOTUSED(v)  (void)(v)
-#else
-#define STBTT__NOTUSED(v)  (void)sizeof(v)
-#endif
 
 typedef int stbrp_coord;
 
@@ -2637,7 +3922,7 @@
    con->y = 0;
    con->bottom_y = 0;
    STBTT__NOTUSED(nodes);
-   STBTT__NOTUSED(num_nodes);   
+   STBTT__NOTUSED(num_nodes);
 }
 
 static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects)
@@ -2691,6 +3976,7 @@
    spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw;
    spc->h_oversample = 1;
    spc->v_oversample = 1;
+   spc->skip_missing = 0;
 
    stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes);
 
@@ -2716,6 +4002,11 @@
       spc->v_oversample = v_oversample;
 }
 
+STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip)
+{
+   spc->skip_missing = skip;
+}
+
 #define STBTT__OVER_MASK  (STBTT_MAX_OVERSAMPLE-1)
 
 static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width)
@@ -2723,6 +4014,7 @@
    unsigned char buffer[STBTT_MAX_OVERSAMPLE];
    int safe_w = w - kernel_width;
    int j;
+   STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze
    for (j=0; j < h; ++j) {
       int i;
       unsigned int total;
@@ -2784,6 +4076,7 @@
    unsigned char buffer[STBTT_MAX_OVERSAMPLE];
    int safe_h = h - kernel_width;
    int j;
+   STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze
    for (j=0; j < w; ++j) {
       int i;
       unsigned int total;
@@ -2853,9 +4146,10 @@
 }
 
 // rects array must be big enough to accommodate all characters in the given ranges
-STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects)
+STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects)
 {
    int i,j,k;
+   int missing_glyph_added = 0;
 
    k=0;
    for (i=0; i < num_ranges; ++i) {
@@ -2867,13 +4161,19 @@
          int x0,y0,x1,y1;
          int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j];
          int glyph = stbtt_FindGlyphIndex(info, codepoint);
-         stbtt_GetGlyphBitmapBoxSubpixel(info,glyph,
-                                         scale * spc->h_oversample,
-                                         scale * spc->v_oversample,
-                                         0,0,
-                                         &x0,&y0,&x1,&y1);
-         rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1);
-         rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1);
+         if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) {
+            rects[k].w = rects[k].h = 0;
+         } else {
+            stbtt_GetGlyphBitmapBoxSubpixel(info,glyph,
+                                            scale * spc->h_oversample,
+                                            scale * spc->v_oversample,
+                                            0,0,
+                                            &x0,&y0,&x1,&y1);
+            rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1);
+            rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1);
+            if (glyph == 0)
+               missing_glyph_added = 1;
+         }
          ++k;
       }
    }
@@ -2881,10 +4181,33 @@
    return k;
 }
 
+STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph)
+{
+   stbtt_MakeGlyphBitmapSubpixel(info,
+                                 output,
+                                 out_w - (prefilter_x - 1),
+                                 out_h - (prefilter_y - 1),
+                                 out_stride,
+                                 scale_x,
+                                 scale_y,
+                                 shift_x,
+                                 shift_y,
+                                 glyph);
+
+   if (prefilter_x > 1)
+      stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x);
+
+   if (prefilter_y > 1)
+      stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y);
+
+   *sub_x = stbtt__oversample_shift(prefilter_x);
+   *sub_y = stbtt__oversample_shift(prefilter_y);
+}
+
 // rects array must be big enough to accommodate all characters in the given ranges
-STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects)
+STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects)
 {
-   int i,j,k, return_value = 1;
+   int i,j,k, missing_glyph = -1, return_value = 1;
 
    // save current values
    int old_h_over = spc->h_oversample;
@@ -2903,7 +4226,7 @@
       sub_y = stbtt__oversample_shift(spc->v_oversample);
       for (j=0; j < ranges[i].num_chars; ++j) {
          stbrp_rect *r = &rects[k];
-         if (r->was_packed) {
+         if (r->was_packed && r->w != 0 && r->h != 0) {
             stbtt_packedchar *bc = &ranges[i].chardata_for_range[j];
             int advance, lsb, x0,y0,x1,y1;
             int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j];
@@ -2949,6 +4272,13 @@
             bc->yoff     =       (float)  y0 * recip_v + sub_y;
             bc->xoff2    =                (x0 + r->w) * recip_h + sub_x;
             bc->yoff2    =                (y0 + r->h) * recip_v + sub_y;
+
+            if (glyph == 0)
+               missing_glyph = j;
+         } else if (spc->skip_missing) {
+            return_value = 0;
+         } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) {
+            ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph];
          } else {
             return_value = 0; // if any fail, report failure
          }
@@ -2969,7 +4299,7 @@
    stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects);
 }
 
-STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges)
+STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges)
 {
    stbtt_fontinfo info;
    int i,j,n, return_value = 1;
@@ -2987,24 +4317,25 @@
    n = 0;
    for (i=0; i < num_ranges; ++i)
       n += ranges[i].num_chars;
-         
+
    rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context);
    if (rects == NULL)
       return 0;
 
+   info.userdata = spc->user_allocator_context;
    stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index));
 
    n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects);
 
    stbtt_PackFontRangesPackRects(spc, rects, n);
-  
+
    return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects);
 
    STBTT_free(rects, spc->user_allocator_context);
    return return_value;
 }
 
-STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size,
+STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size,
             int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range)
 {
    stbtt_pack_range range;
@@ -3016,10 +4347,23 @@
    return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1);
 }
 
-STBTT_DEF void stbtt_GetPackedQuad(stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer)
+STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap)
+{
+   int i_ascent, i_descent, i_lineGap;
+   float scale;
+   stbtt_fontinfo info;
+   stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index));
+   scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size);
+   stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap);
+   *ascent  = (float) i_ascent  * scale;
+   *descent = (float) i_descent * scale;
+   *lineGap = (float) i_lineGap * scale;
+}
+
+STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer)
 {
    float ipw = 1.0f / pw, iph = 1.0f / ph;
-   stbtt_packedchar *b = chardata + char_index;
+   const stbtt_packedchar *b = chardata + char_index;
 
    if (align_to_integer) {
       float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f);
@@ -3043,6 +4387,385 @@
    *xpos += b->xadvance;
 }
 
+//////////////////////////////////////////////////////////////////////////////
+//
+// sdf computation
+//
+
+#define STBTT_min(a,b)  ((a) < (b) ? (a) : (b))
+#define STBTT_max(a,b)  ((a) < (b) ? (b) : (a))
+
+static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2])
+{
+   float q0perp = q0[1]*ray[0] - q0[0]*ray[1];
+   float q1perp = q1[1]*ray[0] - q1[0]*ray[1];
+   float q2perp = q2[1]*ray[0] - q2[0]*ray[1];
+   float roperp = orig[1]*ray[0] - orig[0]*ray[1];
+
+   float a = q0perp - 2*q1perp + q2perp;
+   float b = q1perp - q0perp;
+   float c = q0perp - roperp;
+
+   float s0 = 0., s1 = 0.;
+   int num_s = 0;
+
+   if (a != 0.0) {
+      float discr = b*b - a*c;
+      if (discr > 0.0) {
+         float rcpna = -1 / a;
+         float d = (float) STBTT_sqrt(discr);
+         s0 = (b+d) * rcpna;
+         s1 = (b-d) * rcpna;
+         if (s0 >= 0.0 && s0 <= 1.0)
+            num_s = 1;
+         if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) {
+            if (num_s == 0) s0 = s1;
+            ++num_s;
+         }
+      }
+   } else {
+      // 2*b*s + c = 0
+      // s = -c / (2*b)
+      s0 = c / (-2 * b);
+      if (s0 >= 0.0 && s0 <= 1.0)
+         num_s = 1;
+   }
+
+   if (num_s == 0)
+      return 0;
+   else {
+      float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]);
+      float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2;
+
+      float q0d =   q0[0]*rayn_x +   q0[1]*rayn_y;
+      float q1d =   q1[0]*rayn_x +   q1[1]*rayn_y;
+      float q2d =   q2[0]*rayn_x +   q2[1]*rayn_y;
+      float rod = orig[0]*rayn_x + orig[1]*rayn_y;
+
+      float q10d = q1d - q0d;
+      float q20d = q2d - q0d;
+      float q0rd = q0d - rod;
+
+      hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d;
+      hits[0][1] = a*s0+b;
+
+      if (num_s > 1) {
+         hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d;
+         hits[1][1] = a*s1+b;
+         return 2;
+      } else {
+         return 1;
+      }
+   }
+}
+
+static int equal(float *a, float *b)
+{
+   return (a[0] == b[0] && a[1] == b[1]);
+}
+
+static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts)
+{
+   int i;
+   float orig[2], ray[2] = { 1, 0 };
+   float y_frac;
+   int winding = 0;
+
+   // make sure y never passes through a vertex of the shape
+   y_frac = (float) STBTT_fmod(y, 1.0f);
+   if (y_frac < 0.01f)
+      y += 0.01f;
+   else if (y_frac > 0.99f)
+      y -= 0.01f;
+
+   orig[0] = x;
+   orig[1] = y;
+
+   // test a ray from (-infinity,y) to (x,y)
+   for (i=0; i < nverts; ++i) {
+      if (verts[i].type == STBTT_vline) {
+         int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y;
+         int x1 = (int) verts[i  ].x, y1 = (int) verts[i  ].y;
+         if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) {
+            float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0;
+            if (x_inter < x)
+               winding += (y0 < y1) ? 1 : -1;
+         }
+      }
+      if (verts[i].type == STBTT_vcurve) {
+         int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ;
+         int x1 = (int) verts[i  ].cx, y1 = (int) verts[i  ].cy;
+         int x2 = (int) verts[i  ].x , y2 = (int) verts[i  ].y ;
+         int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2));
+         int by = STBTT_max(y0,STBTT_max(y1,y2));
+         if (y > ay && y < by && x > ax) {
+            float q0[2],q1[2],q2[2];
+            float hits[2][2];
+            q0[0] = (float)x0;
+            q0[1] = (float)y0;
+            q1[0] = (float)x1;
+            q1[1] = (float)y1;
+            q2[0] = (float)x2;
+            q2[1] = (float)y2;
+            if (equal(q0,q1) || equal(q1,q2)) {
+               x0 = (int)verts[i-1].x;
+               y0 = (int)verts[i-1].y;
+               x1 = (int)verts[i  ].x;
+               y1 = (int)verts[i  ].y;
+               if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) {
+                  float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0;
+                  if (x_inter < x)
+                     winding += (y0 < y1) ? 1 : -1;
+               }
+            } else {
+               int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits);
+               if (num_hits >= 1)
+                  if (hits[0][0] < 0)
+                     winding += (hits[0][1] < 0 ? -1 : 1);
+               if (num_hits >= 2)
+                  if (hits[1][0] < 0)
+                     winding += (hits[1][1] < 0 ? -1 : 1);
+            }
+         }
+      }
+   }
+   return winding;
+}
+
+static float stbtt__cuberoot( float x )
+{
+   if (x<0)
+      return -(float) STBTT_pow(-x,1.0f/3.0f);
+   else
+      return  (float) STBTT_pow( x,1.0f/3.0f);
+}
+
+// x^3 + a*x^2 + b*x + c = 0
+static int stbtt__solve_cubic(float a, float b, float c, float* r)
+{
+   float s = -a / 3;
+   float p = b - a*a / 3;
+   float q = a * (2*a*a - 9*b) / 27 + c;
+   float p3 = p*p*p;
+   float d = q*q + 4*p3 / 27;
+   if (d >= 0) {
+      float z = (float) STBTT_sqrt(d);
+      float u = (-q + z) / 2;
+      float v = (-q - z) / 2;
+      u = stbtt__cuberoot(u);
+      v = stbtt__cuberoot(v);
+      r[0] = s + u + v;
+      return 1;
+   } else {
+      float u = (float) STBTT_sqrt(-p/3);
+      float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative
+      float m = (float) STBTT_cos(v);
+      float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f;
+      r[0] = s + u * 2 * m;
+      r[1] = s - u * (m + n);
+      r[2] = s - u * (m - n);
+
+      //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f);  // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe?
+      //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f);
+      //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f);
+      return 3;
+   }
+}
+
+STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff)
+{
+   float scale_x = scale, scale_y = scale;
+   int ix0,iy0,ix1,iy1;
+   int w,h;
+   unsigned char *data;
+
+   if (scale == 0) return NULL;
+
+   stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1);
+
+   // if empty, return NULL
+   if (ix0 == ix1 || iy0 == iy1)
+      return NULL;
+
+   ix0 -= padding;
+   iy0 -= padding;
+   ix1 += padding;
+   iy1 += padding;
+
+   w = (ix1 - ix0);
+   h = (iy1 - iy0);
+
+   if (width ) *width  = w;
+   if (height) *height = h;
+   if (xoff  ) *xoff   = ix0;
+   if (yoff  ) *yoff   = iy0;
+
+   // invert for y-downwards bitmaps
+   scale_y = -scale_y;
+
+   {
+      int x,y,i,j;
+      float *precompute;
+      stbtt_vertex *verts;
+      int num_verts = stbtt_GetGlyphShape(info, glyph, &verts);
+      data = (unsigned char *) STBTT_malloc(w * h, info->userdata);
+      precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata);
+
+      for (i=0,j=num_verts-1; i < num_verts; j=i++) {
+         if (verts[i].type == STBTT_vline) {
+            float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y;
+            float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y;
+            float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0));
+            precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist;
+         } else if (verts[i].type == STBTT_vcurve) {
+            float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y;
+            float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y;
+            float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y;
+            float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2;
+            float len2 = bx*bx + by*by;
+            if (len2 != 0.0f)
+               precompute[i] = 1.0f / (bx*bx + by*by);
+            else
+               precompute[i] = 0.0f;
+         } else
+            precompute[i] = 0.0f;
+      }
+
+      for (y=iy0; y < iy1; ++y) {
+         for (x=ix0; x < ix1; ++x) {
+            float val;
+            float min_dist = 999999.0f;
+            float sx = (float) x + 0.5f;
+            float sy = (float) y + 0.5f;
+            float x_gspace = (sx / scale_x);
+            float y_gspace = (sy / scale_y);
+
+            int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path
+
+            for (i=0; i < num_verts; ++i) {
+               float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y;
+
+               if (verts[i].type == STBTT_vline && precompute[i] != 0.0f) {
+                  float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y;
+
+                  float dist,dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy);
+                  if (dist2 < min_dist*min_dist)
+                     min_dist = (float) STBTT_sqrt(dist2);
+
+                  // coarse culling against bbox
+                  //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist &&
+                  //    sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist)
+                  dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i];
+                  STBTT_assert(i != 0);
+                  if (dist < min_dist) {
+                     // check position along line
+                     // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0)
+                     // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy)
+                     float dx = x1-x0, dy = y1-y0;
+                     float px = x0-sx, py = y0-sy;
+                     // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy
+                     // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve
+                     float t = -(px*dx + py*dy) / (dx*dx + dy*dy);
+                     if (t >= 0.0f && t <= 1.0f)
+                        min_dist = dist;
+                  }
+               } else if (verts[i].type == STBTT_vcurve) {
+                  float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y;
+                  float x1 = verts[i  ].cx*scale_x, y1 = verts[i  ].cy*scale_y;
+                  float box_x0 = STBTT_min(STBTT_min(x0,x1),x2);
+                  float box_y0 = STBTT_min(STBTT_min(y0,y1),y2);
+                  float box_x1 = STBTT_max(STBTT_max(x0,x1),x2);
+                  float box_y1 = STBTT_max(STBTT_max(y0,y1),y2);
+                  // coarse culling against bbox to avoid computing cubic unnecessarily
+                  if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) {
+                     int num=0;
+                     float ax = x1-x0, ay = y1-y0;
+                     float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2;
+                     float mx = x0 - sx, my = y0 - sy;
+                     float res[3] = {0.f,0.f,0.f};
+                     float px,py,t,it,dist2;
+                     float a_inv = precompute[i];
+                     if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula
+                        float a = 3*(ax*bx + ay*by);
+                        float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by);
+                        float c = mx*ax+my*ay;
+                        if (a == 0.0) { // if a is 0, it's linear
+                           if (b != 0.0) {
+                              res[num++] = -c/b;
+                           }
+                        } else {
+                           float discriminant = b*b - 4*a*c;
+                           if (discriminant < 0)
+                              num = 0;
+                           else {
+                              float root = (float) STBTT_sqrt(discriminant);
+                              res[0] = (-b - root)/(2*a);
+                              res[1] = (-b + root)/(2*a);
+                              num = 2; // don't bother distinguishing 1-solution case, as code below will still work
+                           }
+                        }
+                     } else {
+                        float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point
+                        float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv;
+                        float d = (mx*ax+my*ay) * a_inv;
+                        num = stbtt__solve_cubic(b, c, d, res);
+                     }
+                     dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy);
+                     if (dist2 < min_dist*min_dist)
+                        min_dist = (float) STBTT_sqrt(dist2);
+
+                     if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) {
+                        t = res[0], it = 1.0f - t;
+                        px = it*it*x0 + 2*t*it*x1 + t*t*x2;
+                        py = it*it*y0 + 2*t*it*y1 + t*t*y2;
+                        dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy);
+                        if (dist2 < min_dist * min_dist)
+                           min_dist = (float) STBTT_sqrt(dist2);
+                     }
+                     if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) {
+                        t = res[1], it = 1.0f - t;
+                        px = it*it*x0 + 2*t*it*x1 + t*t*x2;
+                        py = it*it*y0 + 2*t*it*y1 + t*t*y2;
+                        dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy);
+                        if (dist2 < min_dist * min_dist)
+                           min_dist = (float) STBTT_sqrt(dist2);
+                     }
+                     if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) {
+                        t = res[2], it = 1.0f - t;
+                        px = it*it*x0 + 2*t*it*x1 + t*t*x2;
+                        py = it*it*y0 + 2*t*it*y1 + t*t*y2;
+                        dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy);
+                        if (dist2 < min_dist * min_dist)
+                           min_dist = (float) STBTT_sqrt(dist2);
+                     }
+                  }
+               }
+            }
+            if (winding == 0)
+               min_dist = -min_dist;  // if outside the shape, value is negative
+            val = onedge_value + pixel_dist_scale * min_dist;
+            if (val < 0)
+               val = 0;
+            else if (val > 255)
+               val = 255;
+            data[(y-iy0)*w+(x-ix0)] = (unsigned char) val;
+         }
+      }
+      STBTT_free(precompute, info->userdata);
+      STBTT_free(verts, info->userdata);
+   }
+   return data;
+}
+
+STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff)
+{
+   return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff);
+}
+
+STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata)
+{
+   STBTT_free(bitmap, userdata);
+}
 
 //////////////////////////////////////////////////////////////////////////////
 //
@@ -3050,7 +4773,7 @@
 //
 
 // check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string
-static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(const stbtt_uint8 *s1, stbtt_int32 len1, const stbtt_uint8 *s2, stbtt_int32 len2) 
+static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2)
 {
    stbtt_int32 i=0;
 
@@ -3089,9 +4812,9 @@
    return i;
 }
 
-STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) 
+static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2)
 {
-   return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((const stbtt_uint8*) s1, len1, (const stbtt_uint8*) s2, len2);
+   return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2);
 }
 
 // returns results in whatever encoding you request... but note that 2-byte encodings
@@ -3147,7 +4870,7 @@
                         return 1;
                   } else if (matchlen < nlen && name[matchlen] == ' ') {
                      ++matchlen;
-                     if (stbtt_CompareUTF8toUTF16_bigendian((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen))
+                     if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen))
                         return 1;
                   }
                } else {
@@ -3193,7 +4916,7 @@
    return 0;
 }
 
-STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *font_collection, const char *name_utf8, stbtt_int32 flags)
+static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags)
 {
    stbtt_int32 i;
    for (i=0;;++i) {
@@ -3204,11 +4927,71 @@
    }
 }
 
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-qual"
+#endif
+
+STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset,
+                                float pixel_height, unsigned char *pixels, int pw, int ph,
+                                int first_char, int num_chars, stbtt_bakedchar *chardata)
+{
+   return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata);
+}
+
+STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index)
+{
+   return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index);
+}
+
+STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data)
+{
+   return stbtt_GetNumberOfFonts_internal((unsigned char *) data);
+}
+
+STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset)
+{
+   return stbtt_InitFont_internal(info, (unsigned char *) data, offset);
+}
+
+STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags)
+{
+   return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags);
+}
+
+STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2)
+{
+   return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2);
+}
+
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
 #endif // STB_TRUETYPE_IMPLEMENTATION
 
 
 // FULL VERSION HISTORY
 //
+//   1.25 (2021-07-11) many fixes
+//   1.24 (2020-02-05) fix warning
+//   1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS)
+//   1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined
+//   1.21 (2019-02-25) fix warning
+//   1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics()
+//   1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod
+//   1.18 (2018-01-29) add missing function
+//   1.17 (2017-07-23) make more arguments const; doc fix
+//   1.16 (2017-07-12) SDF support
+//   1.15 (2017-03-03) make more arguments const
+//   1.14 (2017-01-16) num-fonts-in-TTC function
+//   1.13 (2017-01-02) support OpenType fonts, certain Apple fonts
+//   1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual
+//   1.11 (2016-04-02) fix unused-variable warning
+//   1.10 (2016-04-02) allow user-defined fabs() replacement
+//                     fix memory leak if fontsize=0.0
+//                     fix warning from duplicate typedef
+//   1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges
 //   1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges
 //   1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints;
 //                     allow PackFontRanges to pack and render in separate phases;
@@ -3250,3 +5033,45 @@
 //   0.2  (2009-03-11) Fix unsigned/signed char warnings
 //   0.1  (2009-03-09) First public release
 //
+
+/*
+------------------------------------------------------------------------------
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2017 Sean Barrett
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+This is free and unencumbered software released into the public domain.
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+software, either in source code form or as a compiled binary, for any purpose,
+commercial or non-commercial, and by any means.
+In jurisdictions that recognize copyright laws, the author or authors of this
+software dedicate any and all copyright interest in the software to the public
+domain. We make this dedication for the benefit of the public at large and to
+the detriment of our heirs and successors. We intend this dedication to be an
+overt act of relinquishment in perpetuity of all present and future rights to
+this software under copyright law.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------------------------------
+*/
diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c
index a21fde0..05f9304 100644
--- a/drivers/video/vidconsole-uclass.c
+++ b/drivers/video/vidconsole-uclass.c
@@ -596,6 +596,48 @@
 	return ops->select_font(dev, name, size);
 }
 
+int vidconsole_measure(struct udevice *dev, const char *name, uint size,
+		       const char *text, struct vidconsole_bbox *bbox)
+{
+	struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
+	struct vidconsole_ops *ops = vidconsole_get_ops(dev);
+	int ret;
+
+	if (ops->select_font) {
+		ret = ops->measure(dev, name, size, text, bbox);
+		if (ret != -ENOSYS)
+			return ret;
+	}
+
+	bbox->valid = true;
+	bbox->x0 = 0;
+	bbox->y0 = 0;
+	bbox->x1 = priv->x_charsize * strlen(text);
+	bbox->y1 = priv->y_charsize;
+
+	return 0;
+}
+
+void vidconsole_push_colour(struct udevice *dev, enum colour_idx fg,
+			    enum colour_idx bg, struct vidconsole_colour *old)
+{
+	struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
+
+	old->colour_fg = vid_priv->colour_fg;
+	old->colour_bg = vid_priv->colour_bg;
+
+	vid_priv->colour_fg = video_index_to_colour(vid_priv, fg);
+	vid_priv->colour_bg = video_index_to_colour(vid_priv, bg);
+}
+
+void vidconsole_pop_colour(struct udevice *dev, struct vidconsole_colour *old)
+{
+	struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
+
+	vid_priv->colour_fg = old->colour_fg;
+	vid_priv->colour_bg = old->colour_bg;
+}
+
 /* Set up the number of rows and colours (rotated drivers override this) */
 static int vidconsole_pre_probe(struct udevice *dev)
 {
diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c
index 1b66a80..95e874b 100644
--- a/drivers/video/video-uclass.c
+++ b/drivers/video/video-uclass.c
@@ -142,6 +142,58 @@
 	return 0;
 }
 
+int video_fill_part(struct udevice *dev, int xstart, int ystart, int xend,
+		    int yend, u32 colour)
+{
+	struct video_priv *priv = dev_get_uclass_priv(dev);
+	void *start, *line;
+	int pixels = xend - xstart;
+	int row, i, ret;
+
+	start = priv->fb + ystart * priv->line_length;
+	start += xstart * VNBYTES(priv->bpix);
+	line = start;
+	for (row = ystart; row < yend; row++) {
+		switch (priv->bpix) {
+		case VIDEO_BPP8: {
+			u8 *dst = line;
+
+			if (IS_ENABLED(CONFIG_VIDEO_BPP8)) {
+				for (i = 0; i < pixels; i++)
+					*dst++ = colour;
+			}
+			break;
+		}
+		case VIDEO_BPP16: {
+			u16 *dst = line;
+
+			if (IS_ENABLED(CONFIG_VIDEO_BPP16)) {
+				for (i = 0; i < pixels; i++)
+					*dst++ = colour;
+			}
+			break;
+		}
+		case VIDEO_BPP32: {
+			u32 *dst = line;
+
+			if (IS_ENABLED(CONFIG_VIDEO_BPP32)) {
+				for (i = 0; i < pixels; i++)
+					*dst++ = colour;
+			}
+			break;
+		}
+		default:
+			return -ENOSYS;
+		}
+		line += priv->line_length;
+	}
+	ret = video_sync_copy(dev, start, line);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
 int video_fill(struct udevice *dev, u32 colour)
 {
 	struct video_priv *priv = dev_get_uclass_priv(dev);
@@ -208,7 +260,7 @@
 	{ 0xff, 0xff, 0xff },  /* white */
 };
 
-u32 video_index_to_colour(struct video_priv *priv, unsigned int idx)
+u32 video_index_to_colour(struct video_priv *priv, enum colour_idx idx)
 {
 	switch (priv->bpix) {
 	case VIDEO_BPP16:
diff --git a/fs/fs.c b/fs/fs.c
index 8324b4a..2b815b1 100644
--- a/fs/fs.c
+++ b/fs/fs.c
@@ -13,6 +13,7 @@
 #include <env.h>
 #include <lmb.h>
 #include <log.h>
+#include <malloc.h>
 #include <mapmem.h>
 #include <part.h>
 #include <ext4fs.h>
@@ -26,6 +27,7 @@
 #include <asm/io.h>
 #include <div64.h>
 #include <linux/math64.h>
+#include <linux/sizes.h>
 #include <efi_loader.h>
 #include <squashfs.h>
 #include <erofs.h>
@@ -1008,3 +1010,59 @@
 	puts("\n");
 	return CMD_RET_SUCCESS;
 }
+
+int fs_read_alloc(const char *fname, ulong size, uint align, void **bufp)
+{
+	loff_t bytes_read;
+	ulong addr;
+	char *buf;
+	int ret;
+
+	buf = memalign(align, size + 1);
+	if (!buf)
+		return log_msg_ret("buf", -ENOMEM);
+	addr = map_to_sysmem(buf);
+
+	ret = fs_read(fname, addr, 0, size, &bytes_read);
+	if (ret) {
+		free(buf);
+		return log_msg_ret("read", ret);
+	}
+	if (size != bytes_read)
+		return log_msg_ret("bread", -EIO);
+	buf[size] = '\0';
+
+	*bufp = buf;
+
+	return 0;
+}
+
+int fs_load_alloc(const char *ifname, const char *dev_part_str,
+		  const char *fname, ulong max_size, ulong align, void **bufp,
+		  ulong *sizep)
+{
+	loff_t size;
+	void *buf;
+	int ret;
+
+	if (fs_set_blk_dev(ifname, dev_part_str, FS_TYPE_ANY))
+		return log_msg_ret("set", -ENOMEDIUM);
+
+	ret = fs_size(fname, &size);
+	if (ret)
+		return log_msg_ret("sz", -ENOENT);
+
+	if (size >= (max_size ?: SZ_1G))
+		return log_msg_ret("sz", -E2BIG);
+
+	if (fs_set_blk_dev(ifname, dev_part_str, FS_TYPE_ANY))
+		return log_msg_ret("set", -ENOMEDIUM);
+
+	ret = fs_read_alloc(fname, size, align, &buf);
+	if (ret)
+		return log_msg_ret("al", ret);
+	*sizep = size;
+	*bufp = buf;
+
+	return 0;
+}
diff --git a/include/dm/of.h b/include/dm/of.h
index fce7cef..b1c934f 100644
--- a/include/dm/of.h
+++ b/include/dm/of.h
@@ -63,6 +63,8 @@
 	struct device_node *sibling;
 };
 
+#define BAD_OF_ROOT	0xdead11e3
+
 #define OF_MAX_PHANDLE_ARGS 16
 
 /**
diff --git a/include/dm/ofnode.h b/include/dm/ofnode.h
index 443db62..0f38b3e 100644
--- a/include/dm/ofnode.h
+++ b/include/dm/ofnode.h
@@ -353,6 +353,16 @@
 }
 
 /**
+ * oftree_dispose() - Dispose of an oftree
+ *
+ * This can be used to dispose of a tree that has been created (other than
+ * the control FDT which must not be disposed)
+ *
+ * @tree: Tree to dispose
+ */
+void oftree_dispose(oftree tree);
+
+/**
  * ofnode_name_eq() - Check if the node name is equivalent to a given name
  *                    ignoring the unit address
  *
diff --git a/include/expo.h b/include/expo.h
index d242f48..0b1d944 100644
--- a/include/expo.h
+++ b/include/expo.h
@@ -7,22 +7,31 @@
 #ifndef __SCENE_H
 #define __SCENE_H
 
+#include <dm/ofnode_decl.h>
 #include <linux/list.h>
 
 struct udevice;
+struct video_priv;
 
 /**
  * enum expoact_type - types of actions reported by the expo
  *
  * @EXPOACT_NONE: no action
- * @EXPOACT_POINT: menu item was highlighted (@id indicates which)
+ * @EXPOACT_POINT_OBJ: object was highlighted (@id indicates which)
+ * @EXPOACT_POINT_ITEM: menu item was highlighted (@id indicates which)
  * @EXPOACT_SELECT: menu item was selected (@id indicates which)
+ * @EXPOACT_OPEN: menu was opened, so an item can be selected (@id indicates
+ * which menu object)
+ * @EXPOACT_CLOSE: menu was closed (@id indicates which menu object)
  * @EXPOACT_QUIT: request to exit the menu
  */
 enum expoact_type {
 	EXPOACT_NONE,
-	EXPOACT_POINT,
+	EXPOACT_POINT_OBJ,
+	EXPOACT_POINT_ITEM,
 	EXPOACT_SELECT,
+	EXPOACT_OPEN,
+	EXPOACT_CLOSE,
 	EXPOACT_QUIT,
 };
 
@@ -30,7 +39,7 @@
  * struct expo_action - an action report by the expo
  *
  * @type: Action type (EXPOACT_NONE if there is no action)
- * @select: Used for EXPOACT_POINT and EXPOACT_SELECT
+ * @select: Used for EXPOACT_POINT_ITEM and EXPOACT_SELECT
  * @id: ID number of the object affected.
  */
 struct expo_action {
@@ -43,6 +52,19 @@
 };
 
 /**
+ * struct expo_theme - theme for the expo
+ *
+ * @font_size: Default font size for all text
+ * @menu_inset: Inset width (on each side and top/bottom) for menu items
+ * @menuitem_gap_y: Gap between menu items in pixels
+ */
+struct expo_theme {
+	u32 font_size;
+	u32 menu_inset;
+	u32 menuitem_gap_y;
+};
+
+/**
  * struct expo - information about an expo
  *
  * A group of scenes which can be presented to the user, typically to obtain
@@ -50,23 +72,29 @@
  *
  * @name: Name of the expo (allocated)
  * @display: Display to use (`UCLASS_VIDEO`), or NULL to use text mode
+ * @cons: Console to use (`UCLASS_VIDEO_CONSOLE`), or NULL to use text mode
  * @scene_id: Current scene ID (0 if none)
  * @next_id: Next ID number to use, for automatic allocation
  * @action: Action selected by user. At present only one is supported, with the
  * type set to EXPOACT_NONE if there is no action
  * @text_mode: true to use text mode for the menu (no vidconsole)
+ * @popup: true to use popup menus, instead of showing all items
  * @priv: Private data for the controller
+ * @theme: Information about fonts styles, etc.
  * @scene_head: List of scenes
  * @str_head: list of strings
  */
 struct expo {
 	char *name;
 	struct udevice *display;
+	struct udevice *cons;
 	uint scene_id;
 	uint next_id;
 	struct expo_action action;
 	bool text_mode;
+	bool popup;
 	void *priv;
+	struct expo_theme theme;
 	struct list_head scene_head;
 	struct list_head str_head;
 };
@@ -92,7 +120,8 @@
  * @expo: Expo this scene is part of
  * @name: Name of the scene (allocated)
  * @id: ID number of the scene
- * @title: Title of the scene (allocated)
+ * @title_id: String ID of title of the scene (allocated)
+ * @highlight_id: ID of highlighted object, if any
  * @sibling: Node to link this scene to its siblings
  * @obj_head: List of objects in the scene
  */
@@ -100,7 +129,8 @@
 	struct expo *expo;
 	char *name;
 	uint id;
-	char *title;
+	uint title_id;
+	uint highlight_id;
 	struct list_head sibling;
 	struct list_head obj_head;
 };
@@ -121,15 +151,43 @@
 };
 
 /**
+ * struct scene_dim - Dimensions of an object
+ *
+ * @x: x position, in pixels from left side
+ * @y: y position, in pixels from top
+ * @w: width, in pixels
+ * @h: height, in pixels
+ */
+struct scene_dim {
+	int x;
+	int y;
+	int w;
+	int h;
+};
+
+/**
+ * enum scene_obj_flags_t - flags for objects
+ *
+ * @SCENEOF_HIDE: object should be hidden
+ * @SCENEOF_POINT: object should be highlighted
+ * @SCENEOF_OPEN: object should be opened (e.g. menu is opened so that an option
+ * can be selected)
+ */
+enum scene_obj_flags_t {
+	SCENEOF_HIDE	= 1 << 0,
+	SCENEOF_POINT	= 1 << 1,
+	SCENEOF_OPEN	= 1 << 2,
+};
+
+/**
  * struct scene_obj - information about an object in a scene
  *
  * @scene: Scene that this object relates to
  * @name: Name of the object (allocated)
  * @id: ID number of the object
  * @type: Type of this object
- * @x: x position, in pixels from left side
- * @y: y position, in pixels from top
- * @hide: true if the object should be hidden
+ * @dim: Dimensions for this object
+ * @flags: Flags for this object
  * @sibling: Node to link this object to its siblings
  */
 struct scene_obj {
@@ -137,9 +195,8 @@
 	char *name;
 	uint id;
 	enum scene_obj_t type;
-	int x;
-	int y;
-	bool hide;
+	struct scene_dim dim;
+	int flags;
 	struct list_head sibling;
 };
 
@@ -256,6 +313,25 @@
 void expo_destroy(struct expo *exp);
 
 /**
+ * expo_set_dynamic_start() - Set the start of the 'dynamic' IDs
+ *
+ * It is common for a set of 'static' IDs to be used to refer to objects in the
+ * expo. These typically use an enum so that they are defined in sequential
+ * order.
+ *
+ * Dynamic IDs (for objects not in the enum) are intended to be used for
+ * objects to which the code does not need to refer. These are ideally located
+ * above the static IDs.
+ *
+ * Use this function to set the start of the dynamic range, making sure that the
+ * value is higher than all the statically allocated IDs.
+ *
+ * @exp: Expo to update
+ * @dyn_start: Start ID that expo should use for dynamic allocation
+ */
+void expo_set_dynamic_start(struct expo *exp, uint dyn_start);
+
+/**
  * expo_str() - add a new string to an expo
  *
  * @exp: Expo to update
@@ -285,6 +361,16 @@
 int expo_set_display(struct expo *exp, struct udevice *dev);
 
 /**
+ * expo_calc_dims() - Calculate the dimensions of the objects
+ *
+ * Updates the width and height of all objects based on their contents
+ *
+ * @exp: Expo to update
+ * Returns 0 if OK, -ENOTSUPP if there is no graphical console
+ */
+int expo_calc_dims(struct expo *exp);
+
+/**
  * expo_set_scene_id() - Set the current scene ID
  *
  * @exp: Expo to update
@@ -294,6 +380,14 @@
 int expo_set_scene_id(struct expo *exp, uint scene_id);
 
 /**
+ * expo_first_scene_id() - Get the ID of the first scene
+ *
+ * @exp: Expo to check
+ * Returns: Scene ID of first scene, or -ENOENT if there are no scenes
+ */
+int expo_first_scene_id(struct expo *exp);
+
+/**
  * expo_render() - render the expo on the display / console
  *
  * @exp: Expo to render
@@ -304,12 +398,12 @@
 int expo_render(struct expo *exp);
 
 /**
- * exp_set_text_mode() - Controls whether the expo renders in text mode
+ * expo_set_text_mode() - Controls whether the expo renders in text mode
  *
  * @exp: Expo to update
  * @text_mode: true to use text mode, false to use the console
  */
-void exp_set_text_mode(struct expo *exp, bool text_mode);
+void expo_set_text_mode(struct expo *exp, bool text_mode);
 
 /**
  * scene_new() - create a new scene in a expo
@@ -335,13 +429,43 @@
 struct scene *expo_lookup_scene_id(struct expo *exp, uint scene_id);
 
 /**
+ * scene_highlight_first() - Highlight the first item in a scene
+ *
+ * This highlights the first item, so that the user can see that it is pointed
+ * to
+ *
+ * @scn: Scene to update
+ */
+void scene_highlight_first(struct scene *scn);
+
+/**
+ * scene_set_highlight_id() - Set the object which is highlighted
+ *
+ * Sets a new object to highlight in the scene
+ *
+ * @scn: Scene to update
+ * @id: ID of object to highlight
+ */
+void scene_set_highlight_id(struct scene *scn, uint id);
+
+/**
+ * scene_set_open() - Set whether an item is open or not
+ *
+ * @scn: Scene to update
+ * @id: ID of object to update
+ * @open: true to open the object, false to close it
+ * Returns: 0 if OK, -ENOENT if @id is invalid
+ */
+int scene_set_open(struct scene *scn, uint id, bool open);
+
+/**
  * scene_title_set() - set the scene title
  *
  * @scn: Scene to update
- * @title: Title to set, NULL if none (this is allocated by this call)
- * Returns: 0 if OK, -ENOMEM if out of memory
+ * @title_id: Title ID to set
+ * Returns: 0 if OK
  */
-int scene_title_set(struct scene *scn, const char *title);
+int scene_title_set(struct scene *scn, uint title_id);
 
 /**
  * scene_obj_count() - Count the number of objects in a scene
@@ -426,6 +550,17 @@
 int scene_obj_set_pos(struct scene *scn, uint id, int x, int y);
 
 /**
+ * scene_obj_set_size() - Set the size of an object
+ *
+ * @scn: Scene to update
+ * @id: ID of object to update
+ * @w: width in pixels
+ * @h: height in pixels
+ * Returns: 0 if OK, -ENOENT if @id is invalid
+ */
+int scene_obj_set_size(struct scene *scn, uint id, int w, int h);
+
+/**
  * scene_obj_set_hide() - Set whether an object is hidden
  *
  * The update happens when the expo is next rendered.
@@ -519,4 +654,46 @@
  */
 int expo_action_get(struct expo *exp, struct expo_action *act);
 
+/**
+ * expo_apply_theme() - Apply a theme to an expo
+ *
+ * @exp: Expo to update
+ * @node: Node containing the theme
+ */
+int expo_apply_theme(struct expo *exp, ofnode node);
+
+/**
+ * expo_build() - Build an expo from an FDT description
+ *
+ * Build a complete expo from a description in the provided devicetree.
+ *
+ * See doc/developer/expo.rst for a description of the format
+ *
+ * @root: Root node for expo description
+ * @expp: Returns the new expo
+ * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
+ * error, -ENOENT if there is a references to a non-existent string
+ */
+int expo_build(ofnode root, struct expo **expp);
+
+/**
+ * cedit_arange() - Arrange objects in a configuration-editor scene
+ *
+ * @exp: Expo to update
+ * @vid_priv: Private info of the video device
+ * @scene_id: scene ID to arrange
+ * Returns: 0 if OK, -ve on error
+ */
+int cedit_arange(struct expo *exp, struct video_priv *vid_priv, uint scene_id);
+
+/**
+ * cedit_run() - Run a configuration editor
+ *
+ * This accepts input until the user quits with Escape
+ *
+ * @exp: Expo to use
+ * Returns: 0 if OK, -ve on error
+ */
+int cedit_run(struct expo *exp);
+
 #endif /*__SCENE_H */
diff --git a/include/fs.h b/include/fs.h
index 8370d88..e341a0e 100644
--- a/include/fs.h
+++ b/include/fs.h
@@ -300,4 +300,42 @@
  */
 int do_fs_types(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]);
 
+/**
+ * fs_read_alloc() - Allocate space for a file and read it
+ *
+ * You must call fs_set_blk_dev() or a similar function before calling this,
+ * since that sets up the block device to use.
+ *
+ * The file is terminated with a nul character
+ *
+ * @fname: Filename to read
+ * @size: Size of file to read (must be correct!)
+ * @align: Alignment to use for memory allocation (0 for default)
+ * @bufp: On success, returns the allocated buffer with the nul-terminated file
+ *	in it
+ * Return: 0 if OK, -ENOMEM if out of memory, -EIO if read failed
+ */
+int fs_read_alloc(const char *fname, ulong size, uint align, void **bufp);
+
+/**
+ * fs_load_alloc() - Load a file into allocated space
+ *
+ * The file is terminated with a nul character
+ *
+ * @ifname: Interface name to read from (e.g. "mmc")
+ * @dev_part_str: Device and partition string (e.g. "1:2")
+ * @fname: Filename to read
+ * @max_size: Maximum allowed size for the file (use 0 for 1GB)
+ * @align: Alignment to use for memory allocation (0 for default)
+ * @bufp: On success, returns the allocated buffer with the nul-terminated file
+ *	in it
+ * @sizep: On success, returns the size of the file
+ * Return: 0 if OK, -ENOMEM if out of memory, -ENOENT if the file does not
+ * exist, -ENOMEDIUM if the device does not exist, -E2BIG if the file is too
+ * large (greater than @max_size), -EIO if read failed
+ */
+int fs_load_alloc(const char *ifname, const char *dev_part_str,
+		  const char *fname, ulong max_size, ulong align, void **bufp,
+		  ulong *sizep);
+
 #endif /* _FS_H */
diff --git a/include/log.h b/include/log.h
index 3bab40b..6e84f080 100644
--- a/include/log.h
+++ b/include/log.h
@@ -102,6 +102,8 @@
 	LOGC_EVENT,
 	/** @LOGC_FS: Related to filesystems */
 	LOGC_FS,
+	/** @LOGC_EXPO: Related to expo handling */
+	LOGC_EXPO,
 	/** @LOGC_COUNT: Number of log categories */
 	LOGC_COUNT,
 	/** @LOGC_END: Sentinel value for lists of log categories */
diff --git a/include/of_live.h b/include/of_live.h
index f59d6af..05e86ac 100644
--- a/include/of_live.h
+++ b/include/of_live.h
@@ -36,4 +36,14 @@
  */
 int unflatten_device_tree(const void *blob, struct device_node **mynodes);
 
+/**
+ * of_live_free() - Dispose of a livetree
+ *
+ * This frees memory used by the tree, after which @root becomes invalid and
+ * cannot be used
+ *
+ * @root: Tree to dispose
+ */
+void of_live_free(struct device_node *root);
+
 #endif
diff --git a/include/test/cedit-test.h b/include/test/cedit-test.h
new file mode 100644
index 0000000..349df75
--- /dev/null
+++ b/include/test/cedit-test.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Binding shared between cedit.dtsi and test/boot/expo.c
+ *
+ * Copyright 2023 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#ifndef __cedit_test_h
+#define __cedit_test_h
+
+#define ID_PROMPT		1
+#define ID_SCENE1		2
+#define ID_SCENE1_TITLE		3
+
+#define ID_CPU_SPEED		4
+#define ID_CPU_SPEED_TITLE	5
+#define ID_CPU_SPEED_1		6
+#define ID_CPU_SPEED_2		7
+#define ID_CPU_SPEED_3		8
+
+#define ID_POWER_LOSS		9
+#define ID_AC_OFF		10
+#define ID_AC_ON		11
+#define ID_AC_MEMORY		12
+
+#define ID_DYNAMIC_START	13
+
+#endif
diff --git a/include/test/ut.h b/include/test/ut.h
index dddf9ad..ea6ee95 100644
--- a/include/test/ut.h
+++ b/include/test/ut.h
@@ -130,7 +130,7 @@
 									\
 	if (!(cond)) {							\
 		ut_fail(uts, __FILE__, __LINE__, __func__, #cond);	\
-		__ret = CMD_RET_FAILURE;				\
+		return CMD_RET_FAILURE;					\
 	}								\
 	__ret;								\
 })
@@ -142,7 +142,7 @@
 	if (!(cond)) {							\
 		ut_failf(uts, __FILE__, __LINE__, __func__, #cond,	\
 			 fmt, ##args);					\
-		__ret = CMD_RET_FAILURE;				\
+		return CMD_RET_FAILURE;					\
 	}								\
 	__ret;								\
 })
@@ -157,7 +157,7 @@
 			 #expr1 " == " #expr2,				\
 			 "Expected %#x (%d), got %#x (%d)",		\
 			 _val1, _val1, _val2, _val2);			\
-		__ret = CMD_RET_FAILURE;				\
+		return CMD_RET_FAILURE;					\
 	}								\
 	__ret;								\
 })
@@ -175,7 +175,7 @@
 			 (unsigned long long)_val1,			\
 			 (unsigned long long)_val2,			\
 			 (unsigned long long)_val2);			\
-		__ret = CMD_RET_FAILURE;				\
+		return CMD_RET_FAILURE;					\
 	}								\
 	__ret;								\
 })
@@ -189,7 +189,7 @@
 		ut_failf(uts, __FILE__, __LINE__, __func__,		\
 			 #expr1 " = " #expr2,				\
 			 "Expected \"%s\", got \"%s\"", _val1, _val2);	\
-		__ret = CMD_RET_FAILURE;				\
+		return CMD_RET_FAILURE;					\
 	}								\
 	__ret;								\
 })
@@ -208,7 +208,7 @@
 			 #expr1 " = " #expr2,				\
 			 "Expected \"%.*s\", got \"%.*s\"",		\
 			 _len, _val1, _len, _val2);			\
-		__ret = CMD_RET_FAILURE;				\
+		return CMD_RET_FAILURE;					\
 	}								\
 	__ret;								\
 })
@@ -228,7 +228,7 @@
 			 #expr1 " = " #expr2,				\
 			 "Expected \"%s\", got \"%s\"",			\
 			 __buf1, __buf2);				\
-		__ret = CMD_RET_FAILURE;				\
+		return CMD_RET_FAILURE;					\
 	}								\
 	__ret;								\
 })
@@ -242,7 +242,7 @@
 		ut_failf(uts, __FILE__, __LINE__, __func__,		\
 			 #expr1 " = " #expr2,				\
 			 "Expected %p, got %p", _val1, _val2);		\
-		__ret = CMD_RET_FAILURE;				\
+		return CMD_RET_FAILURE;					\
 	}								\
 	__ret;								\
 })
@@ -257,7 +257,7 @@
 		ut_failf(uts, __FILE__, __LINE__, __func__,		\
 			 #expr1 " = " #expr2,				\
 			 "Expected %lx, got %lx", _val1, _val2);	\
-		__ret = CMD_RET_FAILURE;				\
+		return CMD_RET_FAILURE;					\
 	}								\
 	__ret;								\
 })
@@ -271,7 +271,7 @@
 		ut_failf(uts, __FILE__, __LINE__, __func__,		\
 			 #expr " != NULL",				\
 			 "Expected NULL, got %p", _val);		\
-		__ret = CMD_RET_FAILURE;				\
+		return CMD_RET_FAILURE;					\
 	}								\
 	__ret;								\
 })
@@ -285,7 +285,7 @@
 		ut_failf(uts, __FILE__, __LINE__, __func__,		\
 			 #expr " = NULL",				\
 			 "Expected non-null, got NULL");		\
-		__ret = CMD_RET_FAILURE;				\
+		return CMD_RET_FAILURE;					\
 	}								\
 	__ret;								\
 })
@@ -300,7 +300,7 @@
 			 #expr " = NULL",				\
 			 "Expected pointer, got error %ld",		\
 			 PTR_ERR(_val));				\
-		__ret = CMD_RET_FAILURE;				\
+		return CMD_RET_FAILURE;					\
 	}								\
 	__ret;								\
 })
@@ -316,7 +316,7 @@
 		ut_failf(uts, __FILE__, __LINE__, __func__,		\
 			 "console", "\nExpected '%s',\n     got '%s'",	\
 			 uts->expect_str, uts->actual_str);		\
-		__ret = CMD_RET_FAILURE;				\
+		return CMD_RET_FAILURE;					\
 	}								\
 	__ret;								\
 })
@@ -329,7 +329,7 @@
 		ut_failf(uts, __FILE__, __LINE__, __func__,		\
 			 "console", "\nExpected '%s',\n     got '%s'",	\
 			 uts->expect_str, uts->actual_str);		\
-		__ret = CMD_RET_FAILURE;				\
+		return CMD_RET_FAILURE;					\
 	}								\
 	__ret;								\
 })
@@ -341,7 +341,7 @@
 	if (ut_check_skipline(uts)) {					\
 		ut_failf(uts, __FILE__, __LINE__, __func__,		\
 			 "console", "\nExpected a line, got end");	\
-		__ret = CMD_RET_FAILURE;				\
+		return CMD_RET_FAILURE;					\
 	}								\
 	__ret;								\
 })
@@ -354,7 +354,7 @@
 		ut_failf(uts, __FILE__, __LINE__, __func__,		\
 			 "console", "\nExpected '%s',\n     got to '%s'", \
 			 uts->expect_str, uts->actual_str);		\
-		__ret = CMD_RET_FAILURE;				\
+		return CMD_RET_FAILURE;					\
 	}								\
 	__ret;								\
 })
@@ -367,7 +367,7 @@
 		ut_failf(uts, __FILE__, __LINE__, __func__,		\
 			 "console", "Expected no more output, got '%s'",\
 			 uts->actual_str);				\
-		__ret = CMD_RET_FAILURE;				\
+		return CMD_RET_FAILURE;					\
 	}								\
 	__ret;								\
 })
@@ -381,7 +381,7 @@
 			 "console",					\
 			"Expected dump of length %x bytes, got '%s'",	\
 			 total_bytes, uts->actual_str);			\
-		__ret = CMD_RET_FAILURE;				\
+		return CMD_RET_FAILURE;					\
 	}								\
 	__ret;								\
 })
diff --git a/include/video.h b/include/video.h
index 03434a8..e98d0f9 100644
--- a/include/video.h
+++ b/include/video.h
@@ -163,11 +163,11 @@
  * The caller has to guarantee that the color index is less than
  * VID_COLOR_COUNT.
  *
- * @priv	private data of the console device
- * @idx		color index
+ * @priv	private data of the video device (UCLASS_VIDEO)
+ * @idx		color index (e.g. VID_YELLOW)
  * Return:	color value
  */
-u32 video_index_to_colour(struct video_priv *priv, unsigned int idx);
+u32 video_index_to_colour(struct video_priv *priv, enum colour_idx idx);
 
 /**
  * video_reserve() - Reserve frame-buffer memory for video devices
@@ -205,6 +205,22 @@
 int video_fill(struct udevice *dev, u32 colour);
 
 /**
+ * video_fill_part() - Erase a region
+ *
+ * Erase a rectangle of the display within the given bounds.
+ *
+ * @dev:	Device to update
+ * @xstart:	X start position in pixels from the left
+ * @ystart:	Y start position in pixels from the top
+ * @xend:	X end position in pixels from the left
+ * @yend:	Y end position  in pixels from the top
+ * @colour:	Value to write
+ * Return: 0 if OK, -ENOSYS if the display depth is not supported
+ */
+int video_fill_part(struct udevice *dev, int xstart, int ystart, int xend,
+		    int yend, u32 colour);
+
+/**
  * video_sync() - Sync a device's frame buffer with its hardware
  *
  * @vid:	Device to sync
diff --git a/include/video_console.h b/include/video_console.h
index 3db9a7e..2694e44 100644
--- a/include/video_console.h
+++ b/include/video_console.h
@@ -72,6 +72,38 @@
 };
 
 /**
+ * struct vidconsole_colour - Holds colour information
+ *
+ * @colour_fg:	Foreground colour (pixel value)
+ * @colour_bg:	Background colour (pixel value)
+ */
+struct vidconsole_colour {
+	u32 colour_fg;
+	u32 colour_bg;
+};
+
+/**
+ * struct vidconsole_bbox - Bounding box of text
+ *
+ * This describes the bounding box of something, measured in pixels. The x0/y0
+ * pair is inclusive; the x1/y2 pair is exclusive, meaning that it is one pixel
+ * beyond the extent of the object
+ *
+ * @valid: Values are valid (bounding box is known)
+ * @x0: left x position, in pixels from left side
+ * @y0: top y position, in pixels from top
+ * @x1: right x position + 1
+ * @y1: botton y position + 1
+ */
+struct vidconsole_bbox {
+	bool valid;
+	int x0;
+	int y0;
+	int x1;
+	int y1;
+};
+
+/**
  * struct vidconsole_ops - Video console operations
  *
  * These operations work on either an absolute console position (measured
@@ -178,6 +210,20 @@
 	 * Returns: 0 on success, -ENOENT if no such font
 	 */
 	int (*select_font)(struct udevice *dev, const char *name, uint size);
+
+	/**
+	 * measure() - Measure the bounds of some text
+	 *
+	 * @dev:	Device to adjust
+	 * @name:	Font name to use (NULL to use default)
+	 * @size:	Font size to use (0 to use default)
+	 * @text:	Text to measure
+	 * @bbox:	Returns bounding box of text, assuming it is positioned
+	 *		at 0,0
+	 * Returns: 0 on success, -ENOENT if no such font
+	 */
+	int (*measure)(struct udevice *dev, const char *name, uint size,
+		       const char *text, struct vidconsole_bbox *bbox);
 };
 
 /* Get a pointer to the driver operations for a video console device */
@@ -204,6 +250,38 @@
  */
 int vidconsole_select_font(struct udevice *dev, const char *name, uint size);
 
+/*
+ * vidconsole_measure() - Measuring the bounding box of some text
+ *
+ * @dev: Console device to use
+ * @name: Font name, NULL for default
+ * @size: Font size, ignored if @name is NULL
+ * @text: Text to measure
+ * @bbox: Returns nounding box of text
+ * Returns: 0 if OK, -ve on error
+ */
+int vidconsole_measure(struct udevice *dev, const char *name, uint size,
+		       const char *text, struct vidconsole_bbox *bbox);
+
+/**
+ * vidconsole_push_colour() - Temporarily change the font colour
+ *
+ * @dev:	Device to adjust
+ * @fg:		Foreground colour to select
+ * @bg:		Background colour to select
+ * @old:	Place to store the current colour, so it can be restored
+ */
+void vidconsole_push_colour(struct udevice *dev, enum colour_idx fg,
+			    enum colour_idx bg, struct vidconsole_colour *old);
+
+/**
+ * vidconsole_pop_colour() - Restore the original colour
+ *
+ * @dev:	Device to adjust
+ * @old:	Old colour to be restored
+ */
+void vidconsole_pop_colour(struct udevice *dev, struct vidconsole_colour *old);
+
 /**
  * vidconsole_putc_xy() - write a single character to a position
  *
diff --git a/lib/of_live.c b/lib/of_live.c
index 1b5964d..25f7af6 100644
--- a/lib/of_live.c
+++ b/lib/of_live.c
@@ -287,9 +287,12 @@
 	debug("  size is %lx, allocating...\n", size);
 
 	/* Allocate memory for the expanded device tree */
-	mem = malloc(size + 4);
+	mem = memalign(__alignof__(struct device_node), size + 4);
 	memset(mem, '\0', size);
 
+	/* Set up value for dm_test_livetree_align() */
+	*(u32 *)mem = BAD_OF_ROOT;
+
 	*(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);
 
 	debug("  unflattening %p...\n", mem);
@@ -327,3 +330,9 @@
 
 	return ret;
 }
+
+void of_live_free(struct device_node *root)
+{
+	/* the tree is stored as a contiguous block of memory */
+	free(root);
+}
diff --git a/test/boot/expo.c b/test/boot/expo.c
index 7104dff..3898f85 100644
--- a/test/boot/expo.c
+++ b/test/boot/expo.c
@@ -5,6 +5,7 @@
  */
 
 #include <common.h>
+#include <command.h>
 #include <dm.h>
 #include <expo.h>
 #include <menu.h>
@@ -13,6 +14,7 @@
 #include <test/suites.h>
 #include <test/ut.h>
 #include "bootstd_common.h"
+#include <test/cedit-test.h>
 #include "../../boot/scene_internal.h"
 
 enum {
@@ -28,6 +30,8 @@
 	OBJ_MENU_TITLE,
 
 	/* strings */
+	STR_SCENE_TITLE,
+
 	STR_TEXT,
 	STR_TEXT2,
 	STR_MENU_TITLE,
@@ -120,7 +124,7 @@
 	struct expo *exp;
 	ulong start_mem;
 	char name[100];
-	int id;
+	int id, title_id;
 
 	start_mem = ut_check_free();
 
@@ -141,21 +145,20 @@
 	ut_asserteq_str(SCENE_NAME1, scn->name);
 
 	/* Set the title */
-	strcpy(name, SCENE_TITLE);
-	ut_assertok(scene_title_set(scn, name));
-	*name = '\0';
-	ut_assertnonnull(scn->title);
-	ut_asserteq_str(SCENE_TITLE, scn->title);
+	title_id = expo_str(exp, "title", STR_SCENE_TITLE, SCENE_TITLE);
+	ut_assert(title_id >= 0);
 
-	/* Use an allocated ID */
+	/* Use an allocated ID - this will be allocated after the title str */
 	scn = NULL;
 	id = scene_new(exp, SCENE_NAME2, 0, &scn);
 	ut_assertnonnull(scn);
-	ut_asserteq(SCENE2, id);
-	ut_asserteq(SCENE2 + 1, exp->next_id);
+	ut_assertok(scene_title_set(scn, title_id));
+	ut_asserteq(STR_SCENE_TITLE + 1, id);
+	ut_asserteq(STR_SCENE_TITLE + 2, exp->next_id);
 	ut_asserteq_ptr(exp, scn->expo);
 
 	ut_asserteq_str(SCENE_NAME2, scn->name);
+	ut_asserteq(title_id, scn->title_id);
 
 	expo_destroy(exp);
 
@@ -225,7 +228,7 @@
 }
 BOOTSTD_TEST(expo_object, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
 
-/* Check setting object attributes */
+/* Check setting object attributes and using themes */
 static int expo_object_attr(struct unit_test_state *uts)
 {
 	struct scene_obj_menu *menu;
@@ -235,6 +238,7 @@
 	struct expo *exp;
 	ulong start_mem;
 	char name[100];
+	ofnode node;
 	char *data;
 	int id;
 
@@ -249,8 +253,8 @@
 	ut_assert(id > 0);
 
 	ut_assertok(scene_obj_set_pos(scn, OBJ_LOGO, 123, 456));
-	ut_asserteq(123, img->obj.x);
-	ut_asserteq(456, img->obj.y);
+	ut_asserteq(123, img->obj.dim.x);
+	ut_asserteq(456, img->obj.dim.y);
 
 	ut_asserteq(-ENOENT, scene_obj_set_pos(scn, OBJ_TEXT2, 0, 0));
 
@@ -272,6 +276,11 @@
 	ut_asserteq(-ENOENT, scene_menu_set_title(scn, OBJ_TEXT2, OBJ_TEXT));
 	ut_asserteq(-EINVAL, scene_menu_set_title(scn, OBJ_MENU, OBJ_TEXT2));
 
+	node = ofnode_path("/bootstd/theme");
+	ut_assert(ofnode_valid(node));
+	ut_assertok(expo_apply_theme(exp, node));
+	ut_asserteq(30, txt->font_size);
+
 	expo_destroy(exp);
 
 	ut_assertok(ut_check_delta(start_mem));
@@ -306,8 +315,8 @@
 	ut_asserteq(0, menu->pointer_id);
 
 	ut_assertok(scene_obj_set_pos(scn, OBJ_MENU, 50, 400));
-	ut_asserteq(50, menu->obj.x);
-	ut_asserteq(400, menu->obj.y);
+	ut_asserteq(50, menu->obj.dim.x);
+	ut_asserteq(400, menu->obj.dim.y);
 
 	id = scene_txt_str(scn, "title", OBJ_MENU_TITLE, STR_MENU_TITLE,
 			   "Main Menu", &tit);
@@ -347,29 +356,31 @@
 	ut_asserteq(desc_id, item->desc_id);
 	ut_asserteq(preview_id, item->preview_id);
 
-	/* adding an item should cause the first item to become current */
+	ut_assertok(scene_arrange(scn));
+
+	/* arranging the scene should cause the first item to become current */
 	ut_asserteq(id, menu->cur_item_id);
 
 	/* the title should be at the top */
-	ut_asserteq(menu->obj.x, tit->obj.x);
-	ut_asserteq(menu->obj.y, tit->obj.y);
+	ut_asserteq(menu->obj.dim.x, tit->obj.dim.x);
+	ut_asserteq(menu->obj.dim.y, tit->obj.dim.y);
 
 	/* the first item should be next */
-	ut_asserteq(menu->obj.x, name1->obj.x);
-	ut_asserteq(menu->obj.y + 32, name1->obj.y);
+	ut_asserteq(menu->obj.dim.x, name1->obj.dim.x);
+	ut_asserteq(menu->obj.dim.y + 32, name1->obj.dim.y);
 
-	ut_asserteq(menu->obj.x + 230, key1->obj.x);
-	ut_asserteq(menu->obj.y + 32, key1->obj.y);
+	ut_asserteq(menu->obj.dim.x + 230, key1->obj.dim.x);
+	ut_asserteq(menu->obj.dim.y + 32, key1->obj.dim.y);
 
-	ut_asserteq(menu->obj.x + 200, ptr->obj.x);
-	ut_asserteq(menu->obj.y + 32, ptr->obj.y);
+	ut_asserteq(menu->obj.dim.x + 200, ptr->obj.dim.x);
+	ut_asserteq(menu->obj.dim.y + 32, ptr->obj.dim.y);
 
-	ut_asserteq(menu->obj.x + 280, desc1->obj.x);
-	ut_asserteq(menu->obj.y + 32, desc1->obj.y);
+	ut_asserteq(menu->obj.dim.x + 280, desc1->obj.dim.x);
+	ut_asserteq(menu->obj.dim.y + 32, desc1->obj.dim.y);
 
-	ut_asserteq(-4, prev1->obj.x);
-	ut_asserteq(menu->obj.y + 32, prev1->obj.y);
-	ut_asserteq(false, prev1->obj.hide);
+	ut_asserteq(-4, prev1->obj.dim.x);
+	ut_asserteq(menu->obj.dim.y + 32, prev1->obj.dim.y);
+	ut_asserteq(true, prev1->obj.flags & SCENEOF_HIDE);
 
 	expo_destroy(exp);
 
@@ -470,6 +481,48 @@
 	/* render without a scene */
 	ut_asserteq(-ECHILD, expo_render(exp));
 
+	ut_assertok(expo_calc_dims(exp));
+	ut_assertok(scene_arrange(scn));
+
+	/* check dimensions of text */
+	obj = scene_obj_find(scn, OBJ_TEXT, SCENEOBJT_NONE);
+	ut_assertnonnull(obj);
+	ut_asserteq(400, obj->dim.x);
+	ut_asserteq(100, obj->dim.y);
+	ut_asserteq(126, obj->dim.w);
+	ut_asserteq(40, obj->dim.h);
+
+	/* check dimensions of image */
+	obj = scene_obj_find(scn, OBJ_LOGO, SCENEOBJT_NONE);
+	ut_assertnonnull(obj);
+	ut_asserteq(50, obj->dim.x);
+	ut_asserteq(20, obj->dim.y);
+	ut_asserteq(160, obj->dim.w);
+	ut_asserteq(160, obj->dim.h);
+
+	/* check dimensions of menu labels - both should be the same width */
+	obj = scene_obj_find(scn, ITEM1_LABEL, SCENEOBJT_NONE);
+	ut_assertnonnull(obj);
+	ut_asserteq(50, obj->dim.x);
+	ut_asserteq(436, obj->dim.y);
+	ut_asserteq(29, obj->dim.w);
+	ut_asserteq(18, obj->dim.h);
+
+	obj = scene_obj_find(scn, ITEM2_LABEL, SCENEOBJT_NONE);
+	ut_assertnonnull(obj);
+	ut_asserteq(50, obj->dim.x);
+	ut_asserteq(454, obj->dim.y);
+	ut_asserteq(29, obj->dim.w);
+	ut_asserteq(18, obj->dim.h);
+
+	/* check dimensions of menu */
+	obj = scene_obj_find(scn, OBJ_MENU, SCENEOBJT_NONE);
+	ut_assertnonnull(obj);
+	ut_asserteq(50, obj->dim.x);
+	ut_asserteq(400, obj->dim.y);
+	ut_asserteq(160, obj->dim.w);
+	ut_asserteq(160, obj->dim.h);
+
 	/* render it */
 	expo_set_scene_id(exp, SCENE1);
 	ut_assertok(expo_render(exp));
@@ -479,16 +532,16 @@
 
 	ut_assertok(expo_action_get(exp, &act));
 
-	ut_asserteq(EXPOACT_POINT, act.type);
+	ut_asserteq(EXPOACT_POINT_ITEM, act.type);
 	ut_asserteq(ITEM2, act.select.id);
 	ut_assertok(expo_render(exp));
 
 	/* make sure only the preview for the second item is shown */
 	obj = scene_obj_find(scn, ITEM1_PREVIEW, SCENEOBJT_NONE);
-	ut_asserteq(true, obj->hide);
+	ut_asserteq(true, obj->flags & SCENEOF_HIDE);
 
 	obj = scene_obj_find(scn, ITEM2_PREVIEW, SCENEOBJT_NONE);
-	ut_asserteq(false, obj->hide);
+	ut_asserteq(false, obj->flags & SCENEOF_HIDE);
 
 	/* select it */
 	ut_assertok(expo_send_key(exp, BKEY_SELECT));
@@ -504,7 +557,7 @@
 	ut_assert_console_end();
 
 	/* now try in text mode */
-	exp_set_text_mode(exp, true);
+	expo_set_text_mode(exp, true);
 	ut_assertok(expo_render(exp));
 
 	ut_assert_nextline("U-Boot    :    Boot Menu");
@@ -519,7 +572,7 @@
 
 	ut_assertok(expo_action_get(exp, &act));
 
-	ut_asserteq(EXPOACT_POINT, act.type);
+	ut_asserteq(EXPOACT_POINT_ITEM, act.type);
 	ut_asserteq(ITEM1, act.select.id);
 
 	ut_assertok(expo_render(exp));
@@ -537,3 +590,125 @@
 	return 0;
 }
 BOOTSTD_TEST(expo_render_image, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/* Check building an expo from a devicetree description */
+static int expo_test_build(struct unit_test_state *uts)
+{
+	struct scene_obj_menu *menu;
+	struct scene_menitem *item;
+	struct scene_obj_txt *txt;
+	struct scene_obj *obj;
+	struct scene *scn;
+	struct expo *exp;
+	int count;
+	ofnode node;
+
+	node = ofnode_path("/cedit");
+	ut_assert(ofnode_valid(node));
+	ut_assertok(expo_build(node, &exp));
+
+	ut_asserteq_str("name", exp->name);
+	ut_asserteq(0, exp->scene_id);
+	ut_asserteq(ID_DYNAMIC_START + 20, exp->next_id);
+	ut_asserteq(false, exp->popup);
+
+	/* check the scene */
+	scn = expo_lookup_scene_id(exp, ID_SCENE1);
+	ut_assertnonnull(scn);
+	ut_asserteq_str("main", scn->name);
+	ut_asserteq(ID_SCENE1, scn->id);
+	ut_asserteq(ID_DYNAMIC_START + 1, scn->title_id);
+	ut_asserteq(0, scn->highlight_id);
+
+	/* check the title */
+	txt = scene_obj_find(scn, scn->title_id, SCENEOBJT_NONE);
+	ut_assertnonnull(txt);
+	obj = &txt->obj;
+	ut_asserteq_ptr(scn, obj->scene);
+	ut_asserteq_str("title", obj->name);
+	ut_asserteq(scn->title_id, obj->id);
+	ut_asserteq(SCENEOBJT_TEXT, obj->type);
+	ut_asserteq(0, obj->flags);
+	ut_asserteq_str("Test Configuration", expo_get_str(exp, txt->str_id));
+
+	/* check the menu */
+	menu = scene_obj_find(scn, ID_CPU_SPEED, SCENEOBJT_NONE);
+	obj = &menu->obj;
+	ut_asserteq_ptr(scn, obj->scene);
+	ut_asserteq_str("cpu-speed", obj->name);
+	ut_asserteq(ID_CPU_SPEED, obj->id);
+	ut_asserteq(SCENEOBJT_MENU, obj->type);
+	ut_asserteq(0, obj->flags);
+
+	txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_NONE);
+	ut_asserteq_str("CPU speed", expo_get_str(exp, txt->str_id));
+
+	ut_asserteq(0, menu->cur_item_id);
+	ut_asserteq(0, menu->pointer_id);
+
+	/* check the items */
+	item = list_first_entry(&menu->item_head, struct scene_menitem,
+				sibling);
+	ut_asserteq_str("00", item->name);
+	ut_asserteq(ID_CPU_SPEED_1, item->id);
+	ut_asserteq(0, item->key_id);
+	ut_asserteq(0, item->desc_id);
+	ut_asserteq(0, item->preview_id);
+	ut_asserteq(0, item->flags);
+
+	txt = scene_obj_find(scn, item->label_id, SCENEOBJT_NONE);
+	ut_asserteq_str("2 GHz", expo_get_str(exp, txt->str_id));
+
+	count = 0;
+	list_for_each_entry(item, &menu->item_head, sibling)
+		count++;
+	ut_asserteq(3, count);
+
+	expo_destroy(exp);
+
+	return 0;
+}
+BOOTSTD_TEST(expo_test_build, UT_TESTF_DM);
+
+/* Check the cedit command */
+static int expo_cedit(struct unit_test_state *uts)
+{
+	extern struct expo *cur_exp;
+	struct scene_obj_menu *menu;
+	struct scene_obj_txt *txt;
+	struct expo *exp;
+	struct scene *scn;
+
+	if (!IS_ENABLED(CONFIG_CMD_CEDIT))
+		return -EAGAIN;
+
+	ut_assertok(run_command("cedit load hostfs - cedit.dtb", 0));
+
+	console_record_reset_enable();
+
+	/*
+	 * ^N  Move down to second menu
+	 * ^M  Open menu
+	 * ^N  Move down to second item
+	 * ^M  Select item
+	 * \e  Quit
+	 */
+	console_in_puts("\x0e\x0d\x0e\x0d\e");
+	ut_assertok(run_command("cedit run", 0));
+
+	exp = cur_exp;
+	scn = expo_lookup_scene_id(exp, exp->scene_id);
+	ut_assertnonnull(scn);
+
+	menu = scene_obj_find(scn, scn->highlight_id, SCENEOBJT_NONE);
+	ut_assertnonnull(menu);
+
+	txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_NONE);
+	ut_assertnonnull(txt);
+	ut_asserteq_str("AC Power", expo_get_str(exp, txt->str_id));
+
+	ut_asserteq(ID_AC_ON, menu->cur_item_id);
+
+	return 0;
+}
+BOOTSTD_TEST(expo_cedit, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
diff --git a/test/boot/files/expo_layout.dts b/test/boot/files/expo_layout.dts
new file mode 100644
index 0000000..55d5c91
--- /dev/null
+++ b/test/boot/files/expo_layout.dts
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Sample expo screen layout
+ */
+
+/dts-v1/;
+
+/*
+enum {
+	ZERO,
+	ID_PROMPT,
+
+	ID_SCENE1,
+	ID_SCENE1_TITLE,
+
+	ID_CPU_SPEED,
+	ID_CPU_SPEED_TITLE,
+	ID_CPU_SPEED_1,
+	ID_CPU_SPEED_2,
+	ID_CPU_SPEED_3,
+
+	ID_POWER_LOSS,
+	ID_AC_OFF,
+	ID_AC_ON,
+	ID_AC_MEMORY,
+
+	ID_DYNAMIC_START,
+};
+*/
+
+/ {
+	dynamic-start = <ID_DYNAMIC_START>;
+
+	scenes {
+		main {
+			id = <ID_SCENE1>;
+
+			/* value refers to the matching id in /strings */
+			title-id = <ID_SCENE1_TITLE>;
+
+			/* simple string is used as it is */
+			prompt = "UP and DOWN to choose, ENTER to select";
+
+			/* defines a menu within the scene */
+			cpu-speed {
+				type = "menu";
+				id = <ID_CPU_SPEED>;
+
+				/*
+				 * has both string and ID. The string is ignored
+				 * if the ID is present and points to a string
+				 */
+				title = "CPU speed";
+				title-id = <ID_CPU_SPEED_TITLE>;
+
+				/* menu items as simple strings */
+				item-label = "2 GHz", "2.5 GHz", "3 GHz";
+
+				/* IDs for the menu items */
+				item-id = <ID_CPU_SPEED_1 ID_CPU_SPEED_2
+					ID_CPU_SPEED_3>;
+			};
+
+			power-loss {
+				type = "menu";
+				id = <ID_POWER_LOSS>;
+
+				title = "AC Power";
+				item-label = "Always Off", "Always On",
+					"Memory";
+
+				item-id = <ID_AC_OFF ID_AC_ON ID_AC_MEMORY>;
+			};
+		};
+	};
+
+	strings {
+		title {
+			id = <ID_SCENE1_TITLE>;
+			value = "Test Configuration";
+			value-es = "configuración de prueba";
+		};
+	};
+};
diff --git a/test/cmd/bdinfo.c b/test/cmd/bdinfo.c
index 9068df7..cddf1a4 100644
--- a/test/cmd/bdinfo.c
+++ b/test/cmd/bdinfo.c
@@ -27,19 +27,25 @@
 /* Declare a new bdinfo test */
 #define BDINFO_TEST(_name, _flags)	UNIT_TEST(_name, _flags, bdinfo_test)
 
-static void bdinfo_test_num_l(struct unit_test_state *uts,
-			      const char *name, ulong value)
+static int test_num_l(struct unit_test_state *uts, const char *name,
+		      ulong value)
 {
-	ut_assert_nextline("%-12s= 0x%0*lx", name, 2 * (int)sizeof(value), value);
+	ut_assert_nextline("%-12s= 0x%0*lx", name, 2 * (int)sizeof(value),
+			   value);
+
+	return 0;
 }
 
-static void bdinfo_test_num_ll(struct unit_test_state *uts,
-			       const char *name, unsigned long long value)
+static int test_num_ll(struct unit_test_state *uts, const char *name,
+		       unsigned long long value)
 {
-	ut_assert_nextline("%-12s= 0x%.*llx", name, 2 * (int)sizeof(ulong), value);
+	ut_assert_nextline("%-12s= 0x%.*llx", name, 2 * (int)sizeof(ulong),
+			   value);
+
+	return 0;
 }
 
-static void test_eth(struct unit_test_state *uts)
+static int test_eth(struct unit_test_state *uts)
 {
 	const int idx = eth_get_dev_index();
 	uchar enetaddr[6];
@@ -59,9 +65,11 @@
 	else
 		ut_assert_nextline("%-12s= %pM", name, enetaddr);
 	ut_assert_nextline("IP addr     = %s", env_get("ipaddr"));
+
+	return 0;
 }
 
-static void test_video_info(struct unit_test_state *uts)
+static int test_video_info(struct unit_test_state *uts)
 {
 	const struct udevice *dev;
 	struct uclass *uc;
@@ -73,22 +81,25 @@
 			struct video_priv *upriv = dev_get_uclass_priv(dev);
 			struct video_uc_plat *plat = dev_get_uclass_plat(dev);
 
-			bdinfo_test_num_ll(uts, "FB base", (ulong)upriv->fb);
+			ut_assertok(test_num_ll(uts, "FB base",
+						(ulong)upriv->fb));
 			if (upriv->copy_fb) {
-				bdinfo_test_num_ll(uts, "FB copy",
-						   (ulong)upriv->copy_fb);
-				bdinfo_test_num_l(uts, " copy size",
-						  plat->copy_size);
+				ut_assertok(test_num_ll(uts, "FB copy",
+							(ulong)upriv->copy_fb));
+				ut_assertok(test_num_l(uts, " copy size",
+						       plat->copy_size));
 			}
 			ut_assert_nextline("%-12s= %dx%dx%d", "FB size",
 					   upriv->xsize, upriv->ysize,
 					   1 << upriv->bpix);
 		}
 	}
+
+	return 0;
 }
 
-static void lmb_test_dump_region(struct unit_test_state *uts,
-				 struct lmb_region *rgn, char *name)
+static int lmb_test_dump_region(struct unit_test_state *uts,
+				struct lmb_region *rgn, char *name)
 {
 	unsigned long long base, size, end;
 	enum lmb_flags flags;
@@ -105,13 +116,17 @@
 		ut_assert_nextline(" %s[%d]\t[0x%llx-0x%llx], 0x%08llx bytes flags: %x",
 				   name, i, base, end, size, flags);
 	}
+
+	return 0;
 }
 
-static void lmb_test_dump_all(struct unit_test_state *uts, struct lmb *lmb)
+static int lmb_test_dump_all(struct unit_test_state *uts, struct lmb *lmb)
 {
 	ut_assert_nextline("lmb_dump_all:");
 	lmb_test_dump_region(uts, &lmb->memory, "memory");
 	lmb_test_dump_region(uts, &lmb->reserved, "reserved");
+
+	return 0;
 }
 
 static int bdinfo_test_move(struct unit_test_state *uts)
@@ -123,44 +138,48 @@
 	ut_assertok(console_record_reset_enable());
 	ut_assertok(run_commandf("bdinfo"));
 
-	bdinfo_test_num_l(uts, "boot_params", 0);
+	ut_assertok(test_num_l(uts, "boot_params", 0));
 
 	for (i = 0; i < CONFIG_NR_DRAM_BANKS; ++i) {
 		if (bd->bi_dram[i].size) {
-			bdinfo_test_num_l(uts, "DRAM bank",  i);
-			bdinfo_test_num_ll(uts, "-> start", bd->bi_dram[i].start);
-			bdinfo_test_num_ll(uts, "-> size", bd->bi_dram[i].size);
+			ut_assertok(test_num_l(uts, "DRAM bank", i));
+			ut_assertok(test_num_ll(uts, "-> start",
+						bd->bi_dram[i].start));
+			ut_assertok(test_num_ll(uts, "-> size",
+						bd->bi_dram[i].size));
 		}
 	}
 
 	/* CONFIG_SYS_HAS_SRAM testing not supported */
-	bdinfo_test_num_l(uts, "flashstart", 0);
-	bdinfo_test_num_l(uts, "flashsize", 0);
-	bdinfo_test_num_l(uts, "flashoffset", 0);
+	ut_assertok(test_num_l(uts, "flashstart", 0));
+	ut_assertok(test_num_l(uts, "flashsize", 0));
+	ut_assertok(test_num_l(uts, "flashoffset", 0));
 	ut_assert_nextline("baudrate    = %lu bps",
 			   env_get_ulong("baudrate", 10, 1234));
-	bdinfo_test_num_l(uts, "relocaddr", gd->relocaddr);
-	bdinfo_test_num_l(uts, "reloc off", gd->reloc_off);
+	ut_assertok(test_num_l(uts, "relocaddr", gd->relocaddr));
+	ut_assertok(test_num_l(uts, "reloc off", gd->reloc_off));
 	ut_assert_nextline("%-12s= %u-bit", "Build", (uint)sizeof(void *) * 8);
 
 	if (IS_ENABLED(CONFIG_CMD_NET))
-		test_eth(uts);
+		ut_assertok(test_eth(uts));
 
 	/*
 	 * Make sure environment variable "fdtcontroladdr" address
 	 * matches mapped control DT address.
 	 */
 	ut_assert(map_to_sysmem(gd->fdt_blob) == env_get_hex("fdtcontroladdr", 0x1234));
-	bdinfo_test_num_l(uts, "fdt_blob", (ulong)map_to_sysmem(gd->fdt_blob));
-	bdinfo_test_num_l(uts, "new_fdt", (ulong)map_to_sysmem(gd->new_fdt));
-	bdinfo_test_num_l(uts, "fdt_size", (ulong)gd->fdt_size);
+	ut_assertok(test_num_l(uts, "fdt_blob",
+			       (ulong)map_to_sysmem(gd->fdt_blob)));
+	ut_assertok(test_num_l(uts, "new_fdt",
+			       (ulong)map_to_sysmem(gd->new_fdt)));
+	ut_assertok(test_num_l(uts, "fdt_size", (ulong)gd->fdt_size));
 
 	if (IS_ENABLED(CONFIG_VIDEO))
 		test_video_info(uts);
 
 	/* The gd->multi_dtb_fit may not be available, hence, #if below. */
 #if CONFIG_IS_ENABLED(MULTI_DTB_FIT)
-	bdinfo_test_num_l(uts, "multi_dtb_fit", (ulong)gd->multi_dtb_fit);
+	ut_assertok(test_num_l(uts, "multi_dtb_fit", (ulong)gd->multi_dtb_fit));
 #endif
 
 	if (IS_ENABLED(CONFIG_LMB) && gd->fdt_blob) {
diff --git a/test/dm/ofnode.c b/test/dm/ofnode.c
index 473a8ce..6fbebc7 100644
--- a/test/dm/ofnode.c
+++ b/test/dm/ofnode.c
@@ -1240,3 +1240,48 @@
 	return 0;
 }
 DM_TEST(dm_test_ofnode_copy_props_ot, UT_TESTF_SCAN_FDT | UT_TESTF_OTHER_FDT);
+
+/* check that the livetree is aligned to a structure boundary */
+static int dm_test_livetree_align(struct unit_test_state *uts)
+{
+	const int align = __alignof__(struct unit_test_state);
+	struct device_node *node;
+	u32 *sentinel;
+	ulong start;
+
+	start = (ulong)gd_of_root();
+	ut_asserteq(start, ALIGN(start, align));
+
+	node = gd_of_root();
+	sentinel = (void *)node - sizeof(u32);
+
+	/*
+	 * The sentinel should be overwritten with the root node. If it isn't,
+	 * then the root node is not at the very start of the livetree memory
+	 * area, and free(root) will fail to free the memory used by the
+	 * livetree.
+	 */
+	ut_assert(*sentinel != BAD_OF_ROOT);
+
+	return 0;
+}
+DM_TEST(dm_test_livetree_align, UT_TESTF_LIVE_TREE);
+
+/* check that it is possible to load an arbitrary livetree */
+static int dm_test_livetree_ensure(struct unit_test_state *uts)
+{
+	oftree tree;
+	ofnode node;
+
+	/* read from other.dtb */
+	ut_assertok(test_load_other_fdt(uts));
+	tree = oftree_from_fdt(uts->other_fdt);
+	ut_assert(oftree_valid(tree));
+	node = oftree_path(tree, "/node/subnode");
+	ut_assert(ofnode_valid(node));
+	ut_asserteq_str("sandbox-other2",
+			ofnode_read_string(node, "compatible"));
+
+	return 0;
+}
+DM_TEST(dm_test_livetree_ensure, 0);
diff --git a/test/dm/video.c b/test/dm/video.c
index 3077815..0534ee9 100644
--- a/test/dm/video.c
+++ b/test/dm/video.c
@@ -556,7 +556,7 @@
 	ut_assertok(video_get_nologo(uts, &dev));
 	ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con));
 	vidconsole_put_string(con, test_string);
-	ut_asserteq(12237, compress_frame_buffer(uts, dev));
+	ut_asserteq(12174, compress_frame_buffer(uts, dev));
 
 	return 0;
 }
@@ -577,7 +577,7 @@
 	ut_assertok(video_get_nologo(uts, &dev));
 	ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con));
 	vidconsole_put_string(con, test_string);
-	ut_asserteq(35030, compress_frame_buffer(uts, dev));
+	ut_asserteq(34287, compress_frame_buffer(uts, dev));
 
 	return 0;
 }
@@ -598,7 +598,7 @@
 	ut_assertok(video_get_nologo(uts, &dev));
 	ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con));
 	vidconsole_put_string(con, test_string);
-	ut_asserteq(29018, compress_frame_buffer(uts, dev));
+	ut_asserteq(29471, compress_frame_buffer(uts, dev));
 
 	return 0;
 }
diff --git a/test/py/tests/test_ut.py b/test/py/tests/test_ut.py
index 0b45863..aa1d477 100644
--- a/test/py/tests/test_ut.py
+++ b/test/py/tests/test_ut.py
@@ -282,6 +282,15 @@
         copy_prepared_image(cons, mmc_dev, fname)
 
 
+def setup_cedit_file(cons):
+    infname = os.path.join(cons.config.source_dir,
+                           'test/boot/files/expo_layout.dts')
+    expo_tool = os.path.join(cons.config.source_dir, 'tools/expo.py')
+    outfname = 'cedit.dtb'
+    u_boot_utils.run_and_log(
+        cons, f'{expo_tool} -e {infname} -l {infname} -o {outfname}')
+
+
 @pytest.mark.buildconfigspec('ut_dm')
 def test_ut_dm_init(u_boot_console):
     """Initialize data for ut dm tests."""
@@ -319,6 +328,7 @@
 
     setup_bootflow_image(u_boot_console)
     setup_bootmenu_image(u_boot_console)
+    setup_cedit_file(u_boot_console)
 
     # Restart so that the new mmc1.img is picked up
     u_boot_console.restart_uboot()
diff --git a/tools/binman/control.py b/tools/binman/control.py
index 68597c4..7e2dd35 100644
--- a/tools/binman/control.py
+++ b/tools/binman/control.py
@@ -9,7 +9,7 @@
 import glob
 try:
     import importlib.resources
-except ImportError:
+except ImportError:  # pragma: no cover
     # for Python 3.6
     import importlib_resources
 import os
diff --git a/tools/expo.py b/tools/expo.py
new file mode 100755
index 0000000..c6eb87a
--- /dev/null
+++ b/tools/expo.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0+
+
+"""
+Expo utility - used for testing of expo features
+
+Copyright 2023 Google LLC
+Written by Simon Glass <sjg@chromium.org>
+"""
+
+import argparse
+import collections
+import io
+import re
+import subprocess
+import sys
+
+#from u_boot_pylib import cros_subprocess
+from u_boot_pylib import tools
+
+# Parse:
+#	SCENE1		= 7,
+# or	SCENE2,
+RE_ENUM = re.compile(r'(\S*)(\s*= (\d))?,')
+
+# Parse #define <name>  "string"
+RE_DEF = re.compile(r'#define (\S*)\s*"(.*)"')
+
+def calc_ids(fname):
+    """Figure out the value of the enums in a C file
+
+    Args:
+        fname (str): Filename to parse
+
+    Returns:
+        OrderedDict():
+            key (str): enum name
+            value (int or str):
+                Value of enum, if int
+                Value of #define, if string
+    """
+    vals = collections.OrderedDict()
+    with open(fname, 'r', encoding='utf-8') as inf:
+        in_enum = False
+        cur_id = 0
+        for line in inf.readlines():
+            line = line.strip()
+            if line == 'enum {':
+                in_enum = True
+                continue
+            if in_enum and line == '};':
+                in_enum = False
+
+            if in_enum:
+                if not line or line.startswith('/*'):
+                    continue
+                m_enum = RE_ENUM.match(line)
+                if m_enum.group(3):
+                    cur_id = int(m_enum.group(3))
+                vals[m_enum.group(1)] = cur_id
+                cur_id += 1
+            else:
+                m_def = RE_DEF.match(line)
+                if m_def:
+                    vals[m_def.group(1)] = tools.to_bytes(m_def.group(2))
+
+    return vals
+
+
+def run_expo(args):
+    """Run the expo program"""
+    ids = calc_ids(args.enum_fname)
+
+    indata = tools.read_file(args.layout)
+
+    outf = io.BytesIO()
+
+    for name, val in ids.items():
+        if isinstance(val, int):
+            outval = b'%d' % val
+        else:
+            outval = b'"%s"' % val
+        find_str = r'\b%s\b' % name
+        indata = re.sub(tools.to_bytes(find_str), outval, indata)
+
+    outf.write(indata)
+    data = outf.getvalue()
+
+    with open('/tmp/asc', 'wb') as outf:
+        outf.write(data)
+    proc = subprocess.run('dtc', input=data, capture_output=True, check=True)
+    edtb = proc.stdout
+    if proc.stderr:
+        print(proc.stderr)
+        return 1
+    tools.write_file(args.outfile, edtb)
+    return 0
+
+
+def parse_args(argv):
+    """Parse the command-line arguments
+
+    Args:
+        argv (list of str): List of string arguments
+
+    Returns:
+        tuple: (options, args) with the command-line options and arugments.
+            options provides access to the options (e.g. option.debug)
+            args is a list of string arguments
+    """
+    parser = argparse.ArgumentParser()
+    parser.add_argument('-e', '--enum-fname', type=str,
+        help='C file containing enum declaration for expo items')
+    parser.add_argument('-l', '--layout', type=str,
+        help='Devicetree file source .dts for expo layout')
+    parser.add_argument('-o', '--outfile', type=str,
+        help='Filename to write expo layout dtb')
+
+    return parser.parse_args(argv)
+
+def start_expo():
+    """Start the expo program"""
+    args = parse_args(sys.argv[1:])
+
+    ret_code = run_expo(args)
+    sys.exit(ret_code)
+
+
+if __name__ == "__main__":
+    start_expo()