From 2c522af748162fb69936b732825bc771e09e7256 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 11 Jul 2023 20:46:30 -0600 Subject: bdinfo: Correct use of assertions This test was written for the incorrect use of assertions. Update it to build with the previous approach, where tests fail at the first assertion. All assertion functions return 0 on success and non-zero on failure. They can be nested into functions simply by declaring a function that returns an int and using ut_assertok() to call it. Signed-off-by: Simon Glass Reviewed-by: Marek Vasut --- test/cmd/bdinfo.c | 79 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/test/cmd/bdinfo.c b/test/cmd/bdinfo.c index 9068df79c4..cddf1a46d4 100644 --- a/test/cmd/bdinfo.c +++ b/test/cmd/bdinfo.c @@ -27,19 +27,25 @@ DECLARE_GLOBAL_DATA_PTR; /* 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 @@ static void test_eth(struct unit_test_state *uts) 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 @@ static void test_video_info(struct unit_test_state *uts) 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 @@ static void lmb_test_dump_region(struct unit_test_state *uts, 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 @@ static int bdinfo_test_move(struct unit_test_state *uts) 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) { -- cgit v1.2.3 From e22596113a9b1fea2ac1da9cace9dff337fa1146 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:25 -0600 Subject: binman: Correct coverage gap in control Add a pragma to deal with the code-coverage gap which drops binman down to 90% coverage. Fixes: de65b122a25 (tools: Fall back to importlib_resources on Python 3.6) Signed-off-by: Simon Glass --- tools/binman/control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/binman/control.py b/tools/binman/control.py index 68597c4e77..7e2dd3541b 100644 --- a/tools/binman/control.py +++ b/tools/binman/control.py @@ -9,7 +9,7 @@ from collections import OrderedDict import glob try: import importlib.resources -except ImportError: +except ImportError: # pragma: no cover # for Python 3.6 import importlib_resources import os -- cgit v1.2.3 From d2043b5682558e81aa699ec5c5322a08d577aa86 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:26 -0600 Subject: expo: Correct some header-file comments The use of Returns instead of @return still confuses me. Fix some problems that have crept in. Signed-off-by: Simon Glass --- boot/scene_internal.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/boot/scene_internal.h b/boot/scene_internal.h index e8fd765811..9f173dd749 100644 --- a/boot/scene_internal.h +++ b/boot/scene_internal.h @@ -23,7 +23,7 @@ struct scene *expo_lookup_scene_id(struct expo *exp, uint scene_id); * * @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,6 +36,7 @@ uint resolve_id(struct expo *exp, uint id); * @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); @@ -62,6 +63,7 @@ int scene_obj_add(struct scene *scn, const char *name, uint id, * * @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); @@ -72,7 +74,7 @@ int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu); * @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 +91,7 @@ void scene_menu_destroy(struct scene_obj_menu *menu); * 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); -- cgit v1.2.3 From def898c458e65a71fb64cda370b4281cd026e48c Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:27 -0600 Subject: expo: Convert to using a string ID for the scene title This is easier to deal with if it uses the existing string handling, since we will be able to use translations, etc. in the future. Update it to use an ID instead of a string. Signed-off-by: Simon Glass --- boot/scene.c | 8 ++------ include/expo.h | 10 +++++----- test/boot/expo.c | 19 ++++++++++--------- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/boot/scene.c b/boot/scene.c index 030f6aa2a0..d2f77c008c 100644 --- a/boot/scene.c +++ b/boot/scene.c @@ -65,16 +65,12 @@ void scene_destroy(struct scene *scn) 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; } diff --git a/include/expo.h b/include/expo.h index d242f48e30..8827f4b0b4 100644 --- a/include/expo.h +++ b/include/expo.h @@ -92,7 +92,7 @@ struct expo_string { * @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) * @sibling: Node to link this scene to its siblings * @obj_head: List of objects in the scene */ @@ -100,7 +100,7 @@ struct scene { struct expo *expo; char *name; uint id; - char *title; + uint title_id; struct list_head sibling; struct list_head obj_head; }; @@ -338,10 +338,10 @@ struct scene *expo_lookup_scene_id(struct expo *exp, uint scene_id); * 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 diff --git a/test/boot/expo.c b/test/boot/expo.c index 7104dff05e..3c0bc78bb7 100644 --- a/test/boot/expo.c +++ b/test/boot/expo.c @@ -28,6 +28,8 @@ enum { OBJ_MENU_TITLE, /* strings */ + STR_SCENE_TITLE, + STR_TEXT, STR_TEXT2, STR_MENU_TITLE, @@ -120,7 +122,7 @@ static int expo_scene(struct unit_test_state *uts) struct expo *exp; ulong start_mem; char name[100]; - int id; + int id, title_id; start_mem = ut_check_free(); @@ -141,21 +143,20 @@ static int expo_scene(struct unit_test_state *uts) 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); -- cgit v1.2.3 From 06d3414ac77332c808e255a7036edad6f631b3d2 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:28 -0600 Subject: sandbox: Fix quiting when the LCD window is closed With recent changes to how sandbox handles reset, closing the window currently just restarts sandbox. Use the correct sysreset type to tell it to shut down. Signed-off-by: Simon Glass --- arch/sandbox/cpu/sdl.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/sandbox/cpu/sdl.c b/arch/sandbox/cpu/sdl.c index 2c570ed8d1..590e406517 100644 --- a/arch/sandbox/cpu/sdl.c +++ b/arch/sandbox/cpu/sdl.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -81,7 +82,7 @@ static void sandbox_sdl_poll_events(void) switch (event.type) { case SDL_QUIT: puts("LCD window closed - quitting\n"); - reset_cpu(); + sysreset_walk(SYSRESET_POWER_OFF); break; } } -- cgit v1.2.3 From 18030d9fa225569cc0ff3e87baca52c6e2558e91 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:29 -0600 Subject: test: Restore test behaviour on failure A recent change makes test continue to run after failure. This results in a lot of useless output and may lead to a segfault. Fix this by adding back the 'return' statement. Fixes: fa847bb409d ("test: Wrap assert macros in ({ ... }) and fix") Signed-off-by: Simon Glass --- include/test/ut.h | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/include/test/ut.h b/include/test/ut.h index dddf9ad241..ea6ee95d73 100644 --- a/include/test/ut.h +++ b/include/test/ut.h @@ -130,7 +130,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes); \ if (!(cond)) { \ ut_fail(uts, __FILE__, __LINE__, __func__, #cond); \ - __ret = CMD_RET_FAILURE; \ + return CMD_RET_FAILURE; \ } \ __ret; \ }) @@ -142,7 +142,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes); if (!(cond)) { \ ut_failf(uts, __FILE__, __LINE__, __func__, #cond, \ fmt, ##args); \ - __ret = CMD_RET_FAILURE; \ + return CMD_RET_FAILURE; \ } \ __ret; \ }) @@ -157,7 +157,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes); #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 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes); (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 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes); 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 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes); #expr1 " = " #expr2, \ "Expected \"%.*s\", got \"%.*s\"", \ _len, _val1, _len, _val2); \ - __ret = CMD_RET_FAILURE; \ + return CMD_RET_FAILURE; \ } \ __ret; \ }) @@ -228,7 +228,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes); #expr1 " = " #expr2, \ "Expected \"%s\", got \"%s\"", \ __buf1, __buf2); \ - __ret = CMD_RET_FAILURE; \ + return CMD_RET_FAILURE; \ } \ __ret; \ }) @@ -242,7 +242,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes); 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 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes); 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 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes); 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 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes); 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 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes); #expr " = NULL", \ "Expected pointer, got error %ld", \ PTR_ERR(_val)); \ - __ret = CMD_RET_FAILURE; \ + return CMD_RET_FAILURE; \ } \ __ret; \ }) @@ -316,7 +316,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes); 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 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes); 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 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes); 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 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes); 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 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes); 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 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes); "console", \ "Expected dump of length %x bytes, got '%s'", \ total_bytes, uts->actual_str); \ - __ret = CMD_RET_FAILURE; \ + return CMD_RET_FAILURE; \ } \ __ret; \ }) -- cgit v1.2.3 From 11c341b118a236423a294a7d62f71bf33e20aea3 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:30 -0600 Subject: gpio: Avoid using an invalid ofnode Devices do not necessarily have nodes attached to them, since they can be created from platdata. In SPL a devicetree may in fact not exist at all. Check the node before using it. This avoids failure when OF_CHECKS is enabled. Signed-off-by: Simon Glass Fixes: 5fc7cf8c8e2 ("gpio: add gpio-hog support") Reviewed-by: Heiko Schocher --- drivers/gpio/gpio-uclass.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-uclass.c b/drivers/gpio/gpio-uclass.c index 712119c341..31027f3d99 100644 --- a/drivers/gpio/gpio-uclass.c +++ b/drivers/gpio/gpio-uclass.c @@ -1474,7 +1474,7 @@ static int gpio_post_bind(struct udevice *dev) } #endif - if (CONFIG_IS_ENABLED(GPIO_HOG)) { + if (CONFIG_IS_ENABLED(GPIO_HOG) && dev_has_ofnode(dev)) { struct udevice *child; ofnode node; -- cgit v1.2.3 From a6c4f188948eaf957da8595dd76d947b8bad005a Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:31 -0600 Subject: dm: core: Avoid registering an inaccessible tree At present there are various restrictions on the use of livetree: - It is only available once the tree is unflattened, i.e. after relocation - It is designed to be used with the control FDT - It can (in principle) be used with other FDTs, but only if they are unflattened first; this is not supported Add a few checks to make sure that any tree that is created is actually valid. Otherwise it can be confusing when nodes and properties cannot actually be accessed. Signed-off-by: Simon Glass --- drivers/core/ofnode.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/core/ofnode.c b/drivers/core/ofnode.c index ec574c4460..1d4ab5bb6f 100644 --- a/drivers/core/ofnode.c +++ b/drivers/core/ofnode.c @@ -60,6 +60,11 @@ static oftree oftree_ensure(void *fdt) return oftree_null(); } + if (of_live_active()) { + log_err("Cannot register a flattree when OF_LIVE is active\n"); + return oftree_null(); + } + /* register the new tree */ i = oftree_count++; oftree_list[i] = fdt; @@ -133,6 +138,10 @@ oftree oftree_from_fdt(void *fdt) 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; -- cgit v1.2.3 From 7432f68c53526980d0a2b2ffd54fe61141bb1178 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:32 -0600 Subject: video: Drop #ifdefs from console_truetype Use if() instead to reduce the number of build paths. Signed-off-by: Simon Glass --- drivers/video/console_truetype.c | 112 +++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c index 6b5390136a..0ea8a9f621 100644 --- a/drivers/video/console_truetype.c +++ b/drivers/video/console_truetype.c @@ -154,33 +154,33 @@ static int console_truetype_set_row(struct udevice *dev, uint row, int clr) 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; } @@ -317,52 +317,52 @@ static int console_truetype_putc_xy(struct udevice *dev, uint x, uint y, 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 (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 (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++; + } + 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 (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 (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++; + } + end = dst; } - end = dst; break; } -#endif default: free(data); return -ENOSYS; @@ -405,33 +405,33 @@ static int console_truetype_erase(struct udevice *dev, int xstart, int ystart, 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; + if (IS_ENABLED(CONFIG_VIDEO_BPP8)) { + 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; + if (IS_ENABLED(CONFIG_VIDEO_BPP16)) { + 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; + if (IS_ENABLED(CONFIG_VIDEO_BPP32)) { + for (i = 0; i < pixels; i++) + *dst++ = clr; + } break; } -#endif default: return -ENOSYS; } -- cgit v1.2.3 From 0ab4f91a107832692781a367a1ef2173af75f108 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:33 -0600 Subject: video: Provide a way to clear part of the console This is useful when the background colour must be written before text is updated, to avoid strange display artifacts. Add a function for this, using the existing code from the truetype console. Signed-off-by: Simon Glass --- drivers/video/console_truetype.c | 72 ++-------------------------------------- drivers/video/video-uclass.c | 52 +++++++++++++++++++++++++++++ include/video.h | 16 +++++++++ 3 files changed, 71 insertions(+), 69 deletions(-) diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c index 0ea8a9f621..63d7557c71 100644 --- a/drivers/video/console_truetype.c +++ b/drivers/video/console_truetype.c @@ -378,72 +378,6 @@ static int console_truetype_putc_xy(struct udevice *dev, uint x, uint y, return width_frac; } -/** - * 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) { - case VIDEO_BPP8: { - uint8_t *dst = line; - - if (IS_ENABLED(CONFIG_VIDEO_BPP8)) { - for (i = 0; i < pixels; i++) - *dst++ = clr; - } - break; - } - case VIDEO_BPP16: { - uint16_t *dst = line; - - if (IS_ENABLED(CONFIG_VIDEO_BPP16)) { - for (i = 0; i < pixels; i++) - *dst++ = clr; - } - break; - } - case VIDEO_BPP32: { - uint32_t *dst = line; - - if (IS_ENABLED(CONFIG_VIDEO_BPP32)) { - for (i = 0; i < pixels; i++) - *dst++ = clr; - } - break; - } - 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 * @@ -482,9 +416,9 @@ static int console_truetype_backspace(struct udevice *dev) 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; diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c index 1b66a8061a..d304e92c24 100644 --- a/drivers/video/video-uclass.c +++ b/drivers/video/video-uclass.c @@ -142,6 +142,58 @@ int video_reserve(ulong *addrp) 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); diff --git a/include/video.h b/include/video.h index 03434a8123..6dc42d464b 100644 --- a/include/video.h +++ b/include/video.h @@ -204,6 +204,22 @@ int video_clear(struct udevice *dev); */ 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 * -- cgit v1.2.3 From 42b18494bdaf677c4726ef47a839b16a1a3daba2 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:34 -0600 Subject: expo: Store the console in the expo Rather than finding this each time, keep a pointer to it. This simplifies the code a little. Signed-off-by: Simon Glass --- boot/expo.c | 9 +++++++++ boot/scene.c | 10 ++-------- include/expo.h | 2 ++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/boot/expo.c b/boot/expo.c index 05950a1760..cd1b1a3de5 100644 --- a/boot/expo.c +++ b/boot/expo.c @@ -83,7 +83,16 @@ const char *expo_get_str(struct expo *exp, uint id) 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; } diff --git a/boot/scene.c b/boot/scene.c index d2f77c008c..7e9ba047f2 100644 --- a/boot/scene.c +++ b/boot/scene.c @@ -278,16 +278,10 @@ static int scene_obj_render(struct scene_obj *obj, bool text_mode) { struct scene *scn = obj->scene; struct expo *exp = scn->expo; - struct udevice *cons, *dev = exp->display; + 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; diff --git a/include/expo.h b/include/expo.h index 8827f4b0b4..06f5629e03 100644 --- a/include/expo.h +++ b/include/expo.h @@ -50,6 +50,7 @@ struct expo_action { * * @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 @@ -62,6 +63,7 @@ struct expo_action { struct expo { char *name; struct udevice *display; + struct udevice *cons; uint scene_id; uint next_id; struct expo_action action; -- cgit v1.2.3 From 14a86a510792cb8a69ded6ea3b6c34a150bae3ab Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:35 -0600 Subject: expo: Avoid automatically arranging the scene This should ideally be done once after all scene changes have been made. Require an explicit call when everything is ready. Always arrange after a key it sent, just for convenience. Signed-off-by: Simon Glass --- boot/bootflow_menu.c | 4 ++++ boot/expo.c | 15 ++++++++++++++- boot/scene.c | 7 ------- boot/scene_menu.c | 9 --------- test/boot/expo.c | 4 +++- 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/boot/bootflow_menu.c b/boot/bootflow_menu.c index 7f06dac0af..de0f445356 100644 --- a/boot/bootflow_menu.c +++ b/boot/bootflow_menu.c @@ -124,6 +124,10 @@ int bootflow_menu_new(struct expo **expp) priv->num_bootflows++; } + ret = scene_arrange(scn); + if (ret) + return log_msg_ret("arr", ret); + *expp = exp; return 0; diff --git a/boot/expo.c b/boot/expo.c index cd1b1a3de5..bfdda9570c 100644 --- a/boot/expo.c +++ b/boot/expo.c @@ -116,8 +116,16 @@ struct scene *expo_lookup_scene_id(struct expo *exp, uint scene_id) 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; @@ -165,6 +173,11 @@ int expo_send_key(struct expo *exp, int key) 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; diff --git a/boot/scene.c b/boot/scene.c index 7e9ba047f2..1383be2032 100644 --- a/boot/scene.c +++ b/boot/scene.c @@ -211,8 +211,6 @@ int scene_obj_set_pos(struct scene *scn, uint id, int x, int y) 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); return 0; } @@ -391,11 +389,6 @@ int scene_send_key(struct scene *scn, int key, struct expo_action *event) ret = scene_menu_send_key(scn, menu, key, event); if (ret) return log_msg_ret("key", ret); - - /* only allow one menu */ - ret = scene_menu_arrange(scn, menu); - if (ret) - return log_msg_ret("arr", ret); break; } } diff --git a/boot/scene_menu.c b/boot/scene_menu.c index 18998e862a..9ee911f2fa 100644 --- a/boot/scene_menu.c +++ b/boot/scene_menu.c @@ -158,10 +158,6 @@ int scene_menu(struct scene *scn, const char *name, uint id, *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; } @@ -258,7 +254,6 @@ int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id, { struct scene_obj_menu *menu; struct scene_menitem *item; - int ret; menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU); if (!menu) @@ -285,10 +280,6 @@ int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id, 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; diff --git a/test/boot/expo.c b/test/boot/expo.c index 3c0bc78bb7..56a22ba9b0 100644 --- a/test/boot/expo.c +++ b/test/boot/expo.c @@ -348,7 +348,9 @@ static int expo_object_menu(struct unit_test_state *uts) 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 */ -- cgit v1.2.3 From a8f80409b06a39605aadaaf64bdbf71b31d463ca Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:36 -0600 Subject: console: Correct truetype spacing error The putc_xy() method is supposed to return the amount of space used. The existing implementation erroneously adds the previous sub-pixel position to the returned value. This spaces out the characters very slightly more than it should. It is seldom noticeable but it does make accurate measurement of the text impossible. Fix this minor but long-standing bug. Fixes: a29b012037c ("video: Add a console driver that uses TrueType fonts") Signed-off-by: Simon Glass --- drivers/video/console_truetype.c | 2 +- test/dm/video.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c index 63d7557c71..6f3fc82f9b 100644 --- a/drivers/video/console_truetype.c +++ b/drivers/video/console_truetype.c @@ -256,7 +256,7 @@ static int console_truetype_putc_xy(struct udevice *dev, uint x, uint y, */ 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; diff --git a/test/dm/video.c b/test/dm/video.c index 30778157d9..6d9c55c140 100644 --- a/test/dm/video.c +++ b/test/dm/video.c @@ -556,7 +556,7 @@ static int dm_test_video_truetype(struct unit_test_state *uts) 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(12187, compress_frame_buffer(uts, dev)); return 0; } @@ -577,7 +577,7 @@ static int dm_test_video_truetype_scroll(struct unit_test_state *uts) 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(34481, compress_frame_buffer(uts, dev)); return 0; } @@ -598,7 +598,7 @@ static int dm_test_video_truetype_bs(struct unit_test_state *uts) 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(29579, compress_frame_buffer(uts, dev)); return 0; } -- cgit v1.2.3 From 5904d953a1183e191e9ec4ab15c31c6522f625ad Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:37 -0600 Subject: expo: Rename exp_set_text_mode() Rename this function to match its peers, using the full "expo' prefix. Signed-off-by: Simon Glass --- boot/bootflow_menu.c | 2 +- boot/expo.c | 2 +- include/expo.h | 4 ++-- test/boot/expo.c | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/boot/bootflow_menu.c b/boot/bootflow_menu.c index de0f445356..7c1abe5772 100644 --- a/boot/bootflow_menu.c +++ b/boot/bootflow_menu.c @@ -209,7 +209,7 @@ int bootflow_menu_run(struct bootstd_priv *std, bool text_mode, 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/expo.c b/boot/expo.c index bfdda9570c..e7c81a3983 100644 --- a/boot/expo.c +++ b/boot/expo.c @@ -97,7 +97,7 @@ int expo_set_display(struct expo *exp, struct udevice *dev) 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; } diff --git a/include/expo.h b/include/expo.h index 06f5629e03..f77ee70851 100644 --- a/include/expo.h +++ b/include/expo.h @@ -306,12 +306,12 @@ int expo_set_scene_id(struct expo *exp, uint scene_id); 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 diff --git a/test/boot/expo.c b/test/boot/expo.c index 56a22ba9b0..70750d307f 100644 --- a/test/boot/expo.c +++ b/test/boot/expo.c @@ -507,7 +507,7 @@ static int expo_render_image(struct unit_test_state *uts) 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"); -- cgit v1.2.3 From de7b5a8a1ac0b711db683711b3ccd800e00c4c46 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:38 -0600 Subject: fs: Create functions to load and allocate a file This functionality current sits in bootstd, but it is more generally useful. Add a function to load a file into memory, allocating it as needed. Adjust bootstd to use this version. Note: Tests are added in the subsequent patch which converts the 'cat' command to use this function. Signed-off-by: Simon Glass --- boot/bootmeth-uclass.c | 30 ++------------------------ fs/fs.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ include/fs.h | 38 +++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 28 deletions(-) diff --git a/boot/bootmeth-uclass.c b/boot/bootmeth-uclass.c index 3b3e0614da..701ee8ad1c 100644 --- a/boot/bootmeth-uclass.c +++ b/boot/bootmeth-uclass.c @@ -301,32 +301,6 @@ int bootmeth_try_file(struct bootflow *bflow, struct blk_desc *desc, 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 @@ int bootmeth_alloc_file(struct bootflow *bflow, uint size_limit, uint align) 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 @@ int bootmeth_alloc_other(struct bootflow *bflow, const char *fname, 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/fs/fs.c b/fs/fs.c index 8324b4a22f..2b815b1db0 100644 --- a/fs/fs.c +++ b/fs/fs.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -1008,3 +1010,59 @@ int do_fs_types(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) 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/fs.h b/include/fs.h index 8370d88cb2..e341a0ed01 100644 --- a/include/fs.h +++ b/include/fs.h @@ -300,4 +300,42 @@ int do_fs_type(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); */ 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 */ -- cgit v1.2.3 From 822f7a4543bff59819b6096f1aa1a26f5451bb35 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:39 -0600 Subject: cat: Update command to use fs_load_alloc() Use this new function since it implements the required functionality and reduces duplicated code. Signed-off-by: Simon Glass --- cmd/cat.c | 47 +++++++++++++++++------------------------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/cmd/cat.c b/cmd/cat.c index 1273a26b14..b059080193 100644 --- a/cmd/cat.c +++ b/cmd/cat.c @@ -17,8 +17,8 @@ static int do_cat(struct cmd_tbl *cmdtp, int flag, int argc, 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 @@ static int do_cat(struct cmd_tbl *cmdtp, int flag, int argc, 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)) + case -E2BIG: + log_err("File is too large: ifname=%s dev=%s file=%s\n", ifname, dev, file); 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); + case -ENOMEM: + log_err("Not enough memory: 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"); - 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)) - 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; } -- cgit v1.2.3 From 9cf39bbe96753d1099e3626d0c5be1d6e11c7f42 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:40 -0600 Subject: fdt: Align the start of the livetree Ensure that the block of memory used by live tree is aligned according to the default for structures. This ensures that the root node appears at the start of the block, so it can be used with free(), rather than being 4 bytes later in some cases. This corrects a rather obscure bug in unflatten_device_tree(). Fixes: 8b50d526ea5 ("dm: Add a function to create a 'live' device tree") Signed-off-by: Simon Glass --- include/dm/of.h | 2 ++ lib/of_live.c | 5 ++++- test/dm/ofnode.c | 26 ++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/include/dm/of.h b/include/dm/of.h index fce7cef0ff..b1c934f610 100644 --- a/include/dm/of.h +++ b/include/dm/of.h @@ -63,6 +63,8 @@ struct device_node { struct device_node *sibling; }; +#define BAD_OF_ROOT 0xdead11e3 + #define OF_MAX_PHANDLE_ARGS 16 /** diff --git a/lib/of_live.c b/lib/of_live.c index 1b5964d09a..05588d5ed2 100644 --- a/lib/of_live.c +++ b/lib/of_live.c @@ -287,9 +287,12 @@ int unflatten_device_tree(const void *blob, struct device_node **mynodes) 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); diff --git a/test/dm/ofnode.c b/test/dm/ofnode.c index 473a8cef57..64baaf68db 100644 --- a/test/dm/ofnode.c +++ b/test/dm/ofnode.c @@ -1240,3 +1240,29 @@ static int dm_test_ofnode_copy_props_ot(struct unit_test_state *uts) 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); -- cgit v1.2.3 From c8a4e293863348c27d0119cbbe425b91d500cec1 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:41 -0600 Subject: fdt: Clarify the fdt pre-relocation warning Reword this so it is easier to understand. Signed-off-by: Simon Glass --- drivers/core/ofnode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/core/ofnode.c b/drivers/core/ofnode.c index 1d4ab5bb6f..dee890b552 100644 --- a/drivers/core/ofnode.c +++ b/drivers/core/ofnode.c @@ -72,7 +72,7 @@ static oftree oftree_ensure(void *fdt) } } 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(); } } -- cgit v1.2.3 From a8f2ac2ae6f15b675a55bcf3a20181f3bb223562 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:42 -0600 Subject: fdt: Allow more general use of livetree At present livetree can only be used for the control FDT. It is useful to be able to use the ofnode API for other FDTs, e.g. those used by the upcoming configuration editor. We already have most of the support present, and tests can be marked with the UT_TESTF_OTHER_FDT flag to use another FDT as a special case. But with this change, the functionality becomes more generally available. Plumb in the require support. Signed-off-by: Simon Glass --- drivers/core/ofnode.c | 26 +++++++++++++++++++++----- include/dm/ofnode.h | 10 ++++++++++ include/of_live.h | 10 ++++++++++ lib/of_live.c | 6 ++++++ test/dm/ofnode.c | 19 +++++++++++++++++++ 5 files changed, 66 insertions(+), 5 deletions(-) diff --git a/drivers/core/ofnode.c b/drivers/core/ofnode.c index dee890b552..8df16e56af 100644 --- a/drivers/core/ofnode.c +++ b/drivers/core/ofnode.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -51,6 +52,20 @@ static oftree oftree_ensure(void *fdt) 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) { @@ -60,11 +75,6 @@ static oftree oftree_ensure(void *fdt) return oftree_null(); } - if (of_live_active()) { - log_err("Cannot register a flattree when OF_LIVE is active\n"); - return oftree_null(); - } - /* register the new tree */ i = oftree_count++; oftree_list[i] = fdt; @@ -82,6 +92,12 @@ static oftree oftree_ensure(void *fdt) 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) { diff --git a/include/dm/ofnode.h b/include/dm/ofnode.h index 443db6252d..0f38b3e736 100644 --- a/include/dm/ofnode.h +++ b/include/dm/ofnode.h @@ -352,6 +352,16 @@ static inline oftree oftree_from_np(struct device_node *root) return tree; } +/** + * 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/of_live.h b/include/of_live.h index f59d6af335..05e86ac06b 100644 --- a/include/of_live.h +++ b/include/of_live.h @@ -36,4 +36,14 @@ int of_live_build(const void *fdt_blob, struct device_node **rootp); */ 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/lib/of_live.c b/lib/of_live.c index 05588d5ed2..25f7af6106 100644 --- a/lib/of_live.c +++ b/lib/of_live.c @@ -330,3 +330,9 @@ int of_live_build(const void *fdt_blob, struct device_node **rootp) 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/dm/ofnode.c b/test/dm/ofnode.c index 64baaf68db..6fbebc7da0 100644 --- a/test/dm/ofnode.c +++ b/test/dm/ofnode.c @@ -1266,3 +1266,22 @@ static int dm_test_livetree_align(struct unit_test_state *uts) 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); -- cgit v1.2.3 From c98cb512521e5e035774c49ab9a855cbb6b13813 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:43 -0600 Subject: bootstd: Add a separate log category for expo This feature is different enough from bootstd that it probably deserves its own log category. It cannot use a uclass since it is not a device. Add a new category. Signed-off-by: Simon Glass --- boot/expo.c | 2 ++ boot/scene.c | 2 ++ boot/scene_menu.c | 2 +- common/log.c | 1 + include/log.h | 2 ++ 5 files changed, 8 insertions(+), 1 deletion(-) diff --git a/boot/expo.c b/boot/expo.c index e7c81a3983..8b966b6c79 100644 --- a/boot/expo.c +++ b/boot/expo.c @@ -6,6 +6,8 @@ * Written by Simon Glass */ +#define LOG_CATEGORY LOGC_EXPO + #include #include #include diff --git a/boot/scene.c b/boot/scene.c index 1383be2032..43c978e6ee 100644 --- a/boot/scene.c +++ b/boot/scene.c @@ -6,6 +6,8 @@ * Written by Simon Glass */ +#define LOG_CATEGORY LOGC_EXPO + #include #include #include diff --git a/boot/scene_menu.c b/boot/scene_menu.c index 9ee911f2fa..8b04d44031 100644 --- a/boot/scene_menu.c +++ b/boot/scene_menu.c @@ -6,7 +6,7 @@ * Written by Simon Glass */ -#define LOG_CATEGORY LOGC_BOOT +#define LOG_CATEGORY LOGC_EXPO #include #include diff --git a/common/log.c b/common/log.c index 7cfc49bc28..6f02a25c59 100644 --- a/common/log.c +++ b/common/log.c @@ -31,6 +31,7 @@ static const char *const log_cat_name[] = { "boot", "event", "fs", + "expo", }; _Static_assert(ARRAY_SIZE(log_cat_name) == LOGC_COUNT - LOGC_NONE, diff --git a/include/log.h b/include/log.h index 3bab40b617..6e84f080ef 100644 --- a/include/log.h +++ b/include/log.h @@ -102,6 +102,8 @@ enum log_category_t { 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 */ -- cgit v1.2.3 From 7ea207db2afc1ef70d8dc88fd2914b3ffcfbb1c7 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:44 -0600 Subject: video: Correct docs for video_index_to_colour() This uses the private data of the video uclass, not the console uclass (its child). Update the comment to avoid confusion. Signed-off-by: Simon Glass Fixes: a032e4b55ea ("video: Move console colours to the video uclass") --- include/video.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/video.h b/include/video.h index 6dc42d464b..55e9f32f59 100644 --- a/include/video.h +++ b/include/video.h @@ -163,7 +163,7 @@ enum colour_idx { * The caller has to guarantee that the color index is less than * VID_COLOR_COUNT. * - * @priv private data of the console device + * @priv private data of the video device (UCLASS_VIDEO) * @idx color index * Return: color value */ -- cgit v1.2.3 From 648a4991d0713af656a2fa50ec8e708c2beb044e Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:45 -0600 Subject: video: Allow temporary colour changes It is sometimes necessary to highlight some text in a different colour. Add an easy way to do this and then restore the original console colours. Signed-off-by: Simon Glass --- drivers/video/vidconsole-uclass.c | 20 ++++++++++++++++++++ include/video_console.h | 30 ++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c index a21fde0e1d..3f89537c47 100644 --- a/drivers/video/vidconsole-uclass.c +++ b/drivers/video/vidconsole-uclass.c @@ -596,6 +596,26 @@ int vidconsole_select_font(struct udevice *dev, const char *name, uint size) return ops->select_font(dev, name, size); } +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/include/video_console.h b/include/video_console.h index 3db9a7e1fb..81d4c4d874 100644 --- a/include/video_console.h +++ b/include/video_console.h @@ -71,6 +71,17 @@ struct vidfont_info { const char *name; }; +/** + * 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_ops - Video console operations * @@ -204,6 +215,25 @@ int vidconsole_get_font(struct udevice *dev, int seq, */ int vidconsole_select_font(struct udevice *dev, const char *name, uint size); +/** + * 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 * -- cgit v1.2.3 From b828ed7d79295cfebcb0f958f26a33664fae045c Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:46 -0600 Subject: console: Allow measuring the bounding box of text For laying out text accurately it is necessary to know the width and height of the text. Add a measure() method to the console API, so this can be supported. Add an implementation for truetype and a base implementation for the normal console. Signed-off-by: Simon Glass --- drivers/video/console_truetype.c | 64 +++++++++++++++++++++++++++++++++++++-- drivers/video/vidconsole-uclass.c | 22 ++++++++++++++ include/video_console.h | 48 +++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 2 deletions(-) diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c index 6f3fc82f9b..288123a2e0 100644 --- a/drivers/video/console_truetype.c +++ b/drivers/video/console_truetype.c @@ -614,8 +614,8 @@ static void select_metrics(struct udevice *dev, struct console_tt_metrics *met) 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; @@ -653,11 +653,70 @@ static int truetype_select_font(struct udevice *dev, const char *name, 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); @@ -709,6 +768,7 @@ struct vidconsole_ops console_truetype_ops = { .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/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c index 3f89537c47..05f9304780 100644 --- a/drivers/video/vidconsole-uclass.c +++ b/drivers/video/vidconsole-uclass.c @@ -596,6 +596,28 @@ int vidconsole_select_font(struct udevice *dev, const char *name, uint size) 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) { diff --git a/include/video_console.h b/include/video_console.h index 81d4c4d874..2694e44f6e 100644 --- a/include/video_console.h +++ b/include/video_console.h @@ -82,6 +82,27 @@ struct vidconsole_colour { 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 * @@ -189,6 +210,20 @@ struct vidconsole_ops { * 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 */ @@ -215,6 +250,19 @@ int vidconsole_get_font(struct udevice *dev, int seq, */ 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 * -- cgit v1.2.3 From 9af341502c1cdf1d40a41e8fe6c3922f1e409f33 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:47 -0600 Subject: expo: Allow setting the start of the dynamic-ID range Provide a way to set this value so that it is easy to separate the statically allocated IDs (generated by the caller) from those generated dynamically by expo itself. Signed-off-by: Simon Glass --- boot/expo.c | 15 +++++++++++++++ boot/scene.c | 10 ---------- doc/develop/expo.rst | 3 +++ include/expo.h | 19 +++++++++++++++++++ 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/boot/expo.c b/boot/expo.c index 8b966b6c79..be11cfd4e9 100644 --- a/boot/expo.c +++ b/boot/expo.c @@ -56,6 +56,21 @@ void expo_destroy(struct expo *exp) free(exp); } +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; +} + +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; diff --git a/boot/scene.c b/boot/scene.c index 43c978e6ee..2ac9bfcdbd 100644 --- a/boot/scene.c +++ b/boot/scene.c @@ -18,16 +18,6 @@ #include #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; diff --git a/doc/develop/expo.rst b/doc/develop/expo.rst index 32dd7f0903..9565974a28 100644 --- a/doc/develop/expo.rst +++ b/doc/develop/expo.rst @@ -85,6 +85,9 @@ or even the IDs of objects. Programmatic creation of many items in a loop can be 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. diff --git a/include/expo.h b/include/expo.h index f77ee70851..b8f5327f26 100644 --- a/include/expo.h +++ b/include/expo.h @@ -257,6 +257,25 @@ int expo_new(const char *name, void *priv, struct expo **expp); */ 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 * -- cgit v1.2.3 From 2d6ee92c6af06eeb7f7410180d5faa8f5e840bbb Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:48 -0600 Subject: video: Use enum with video_index_to_colour() Use the provided enum with this function, so it is clearer what should be passed to it. Signed-off-by: Simon Glass --- drivers/video/video-uclass.c | 2 +- include/video.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c index d304e92c24..95e874b770 100644 --- a/drivers/video/video-uclass.c +++ b/drivers/video/video-uclass.c @@ -260,7 +260,7 @@ static const struct vid_rgb colours[VID_COLOUR_COUNT] = { { 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/include/video.h b/include/video.h index 55e9f32f59..e98d0f9c89 100644 --- a/include/video.h +++ b/include/video.h @@ -164,10 +164,10 @@ enum colour_idx { * VID_COLOR_COUNT. * * @priv private data of the video device (UCLASS_VIDEO) - * @idx color index + * @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 -- cgit v1.2.3 From ae45d6cf5a564851a9b9d58e05425e4cf1dfe8aa Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:49 -0600 Subject: expo: Add width and height to objects At present objects only have a position so it is not possible to determine the amount of space they take up on the display. Add width and height properties, using a struct to keep all the dimensions together. For now this is not used. Future work will set up these new properties. Signed-off-by: Simon Glass --- boot/scene.c | 8 ++++---- boot/scene_menu.c | 12 ++++++------ include/expo.h | 21 +++++++++++++++++---- test/boot/expo.c | 32 ++++++++++++++++---------------- 4 files changed, 43 insertions(+), 30 deletions(-) diff --git a/boot/scene.c b/boot/scene.c index 2ac9bfcdbd..8033d77fb2 100644 --- a/boot/scene.c +++ b/boot/scene.c @@ -201,8 +201,8 @@ int scene_obj_set_pos(struct scene *scn, uint id, int x, int y) obj = scene_obj_find(scn, id, SCENEOBJT_NONE); if (!obj) return log_msg_ret("find", -ENOENT); - obj->x = x; - obj->y = y; + obj->dim.x = x; + obj->dim.y = y; return 0; } @@ -272,8 +272,8 @@ static int scene_obj_render(struct scene_obj *obj, bool text_mode) struct udevice *cons = text_mode ? NULL : exp->cons; int x, y, ret; - x = obj->x; - y = obj->y; + x = obj->dim.x; + y = obj->dim.y; switch (obj->type) { case SCENEOBJT_NONE: diff --git a/boot/scene_menu.c b/boot/scene_menu.c index 8b04d44031..eed7565f6a 100644 --- a/boot/scene_menu.c +++ b/boot/scene_menu.c @@ -49,9 +49,9 @@ int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu) int y, cur_y; int ret; - y = menu->obj.y; + 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); @@ -89,18 +89,18 @@ int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu) * pointer, then the key and the description */ if (item->label_id) { - ret = scene_obj_set_pos(scn, item->label_id, menu->obj.x, + ret = scene_obj_set_pos(scn, item->label_id, menu->obj.dim.x, y); if (ret < 0) return log_msg_ret("nam", ret); } - ret = scene_obj_set_pos(scn, item->key_id, menu->obj.x + 230, + ret = scene_obj_set_pos(scn, item->key_id, menu->obj.dim.x + 230, y); if (ret < 0) return log_msg_ret("key", ret); - ret = scene_obj_set_pos(scn, item->desc_id, menu->obj.x + 280, + ret = scene_obj_set_pos(scn, item->desc_id, menu->obj.dim.x + 280, y); if (ret < 0) return log_msg_ret("des", ret); @@ -134,7 +134,7 @@ int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu) * points to */ ret = scene_obj_set_pos(scn, menu->pointer_id, - menu->obj.x + 200, cur_y); + menu->obj.dim.x + 200, cur_y); if (ret < 0) return log_msg_ret("ptr", ret); } diff --git a/include/expo.h b/include/expo.h index b8f5327f26..5135954ba1 100644 --- a/include/expo.h +++ b/include/expo.h @@ -122,6 +122,21 @@ enum scene_obj_t { SCENEOBJT_MENU, }; +/** + * 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; +}; + /** * struct scene_obj - information about an object in a scene * @@ -129,8 +144,7 @@ enum scene_obj_t { * @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 + * @dim: Dimensions for this object * @hide: true if the object should be hidden * @sibling: Node to link this object to its siblings */ @@ -139,8 +153,7 @@ struct scene_obj { char *name; uint id; enum scene_obj_t type; - int x; - int y; + struct scene_dim dim; bool hide; struct list_head sibling; }; diff --git a/test/boot/expo.c b/test/boot/expo.c index 70750d307f..10cb7b246f 100644 --- a/test/boot/expo.c +++ b/test/boot/expo.c @@ -250,8 +250,8 @@ static int expo_object_attr(struct unit_test_state *uts) 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)); @@ -307,8 +307,8 @@ static int expo_object_menu(struct unit_test_state *uts) 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); @@ -354,24 +354,24 @@ static int expo_object_menu(struct unit_test_state *uts) 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(-4, prev1->obj.dim.x); + ut_asserteq(menu->obj.dim.y + 32, prev1->obj.dim.y); ut_asserteq(false, prev1->obj.hide); expo_destroy(exp); -- cgit v1.2.3 From ce72c9ec260d18cc127c275daf3ec1d18c230e2a Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:50 -0600 Subject: expo: Use flags for objects We currently have just a 'hide' property for each object. In preparation for adding more properties, convert the struct to use a flags value, instead of individual booleans. This is more extensible. Signed-off-by: Simon Glass --- boot/scene.c | 17 +++++++++++++++-- boot/scene_internal.h | 11 +++++++++++ include/expo.h | 13 +++++++++++-- test/boot/expo.c | 6 +++--- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/boot/scene.c b/boot/scene.c index 8033d77fb2..dd1472d4f9 100644 --- a/boot/scene.c +++ b/boot/scene.c @@ -208,13 +208,26 @@ int scene_obj_set_pos(struct scene *scn, uint id, int x, int y) } 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; } @@ -358,7 +371,7 @@ int scene_render(struct scene *scn) 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); diff --git a/boot/scene_internal.h b/boot/scene_internal.h index 9f173dd749..24a2ba6a6a 100644 --- a/boot/scene_internal.h +++ b/boot/scene_internal.h @@ -54,6 +54,17 @@ void *scene_obj_find(struct scene *scn, uint id, enum scene_obj_t type); int scene_obj_add(struct scene *scn, const char *name, uint id, 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_menu_arrange() - Set the position of things in the menu * diff --git a/include/expo.h b/include/expo.h index 5135954ba1..b6777cebcb 100644 --- a/include/expo.h +++ b/include/expo.h @@ -137,6 +137,15 @@ struct scene_dim { int h; }; +/** + * enum scene_obj_flags_t - flags for objects + * + * @SCENEOF_HIDE: object should be hidden + */ +enum scene_obj_flags_t { + SCENEOF_HIDE = 1 << 0, +}; + /** * struct scene_obj - information about an object in a scene * @@ -145,7 +154,7 @@ struct scene_dim { * @id: ID number of the object * @type: Type of this object * @dim: Dimensions for this object - * @hide: true if the object should be hidden + * @flags: Flags for this object * @sibling: Node to link this object to its siblings */ struct scene_obj { @@ -154,7 +163,7 @@ struct scene_obj { uint id; enum scene_obj_t type; struct scene_dim dim; - bool hide; + int flags; struct list_head sibling; }; diff --git a/test/boot/expo.c b/test/boot/expo.c index 10cb7b246f..5088776f7b 100644 --- a/test/boot/expo.c +++ b/test/boot/expo.c @@ -372,7 +372,7 @@ static int expo_object_menu(struct unit_test_state *uts) ut_asserteq(-4, prev1->obj.dim.x); ut_asserteq(menu->obj.dim.y + 32, prev1->obj.dim.y); - ut_asserteq(false, prev1->obj.hide); + ut_asserteq(false, prev1->obj.flags & SCENEOF_HIDE); expo_destroy(exp); @@ -488,10 +488,10 @@ static int expo_render_image(struct unit_test_state *uts) /* 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)); -- cgit v1.2.3 From 50f02037594563dbc8da67f65959499bc1f5a46a Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:51 -0600 Subject: expo: Calculate text bounding-box correctly Rather than estimating, measure the text accurately, using the new vidconsole feature. This allows accurate placement of objects. Signed-off-by: Simon Glass --- boot/scene.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/boot/scene.c b/boot/scene.c index dd1472d4f9..981a18b3ba 100644 --- a/boot/scene.c +++ b/boot/scene.c @@ -257,16 +257,30 @@ int scene_obj_get_hw(struct scene *scn, uint id, int *widthp) 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); - - /* use a sensible default */ - return 16; + *widthp = bbox.x1; + + return bbox.y1; } } -- cgit v1.2.3 From 699b0acb522fd808b67b745b541bacf18c275d15 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:52 -0600 Subject: expo: Set up the width and height of objects Provide a way to set the full dimensions of objects, i.e. including the width and height. For menus, calculate the bounding box of all objects in the menu. Set all labels to be the same size, so that highlighting works correct, once implemented. Signed-off-by: Simon Glass --- boot/expo.c | 24 ++++++++++++++++ boot/scene.c | 52 +++++++++++++++++++++++++++++++++ boot/scene_internal.h | 21 ++++++++++++++ boot/scene_menu.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++ doc/develop/expo.rst | 2 -- include/expo.h | 21 ++++++++++++++ test/boot/expo.c | 42 +++++++++++++++++++++++++++ 7 files changed, 239 insertions(+), 2 deletions(-) diff --git a/boot/expo.c b/boot/expo.c index be11cfd4e9..67cae3c7e2 100644 --- a/boot/expo.c +++ b/boot/expo.c @@ -114,6 +114,30 @@ int expo_set_display(struct expo *exp, struct udevice *dev) 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 expo_set_text_mode(struct expo *exp, bool text_mode) { exp->text_mode = text_mode; diff --git a/boot/scene.c b/boot/scene.c index 981a18b3ba..6d5e3c1f03 100644 --- a/boot/scene.c +++ b/boot/scene.c @@ -207,6 +207,19 @@ int scene_obj_set_pos(struct scene *scn, uint id, int x, int 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; @@ -414,3 +427,42 @@ int scene_send_key(struct scene *scn, int key, struct expo_action *event) 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; +} diff --git a/boot/scene_internal.h b/boot/scene_internal.h index 24a2ba6a6a..00085a2f55 100644 --- a/boot/scene_internal.h +++ b/boot/scene_internal.h @@ -65,6 +65,17 @@ int scene_obj_add(struct scene *scn, const char *name, uint id, */ 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 * @@ -133,4 +144,14 @@ int scene_render(struct scene *scn); */ int scene_send_key(struct scene *scn, int key, struct expo_action *event); +/** + * 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 eed7565f6a..fa79cecdbd 100644 --- a/boot/scene_menu.c +++ b/boot/scene_menu.c @@ -43,6 +43,85 @@ static void menu_point_to_item(struct scene_obj_menu *menu, uint item_id) menu->cur_item_id = item_id; } +static int scene_bbox_union(struct scene *scn, uint id, + 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); + bbox->y0 = min(bbox->y0, obj->dim.y); + bbox->x1 = max(bbox->x1, obj->dim.x + obj->dim.w); + bbox->y1 = max(bbox->y1, obj->dim.y + obj->dim.h); + } else { + bbox->x0 = obj->dim.x; + bbox->y0 = obj->dim.y; + bbox->x1 = obj->dim.x + obj->dim.w; + 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 scene_menitem *item; + + bbox->valid = false; + scene_bbox_union(menu->obj.scene, menu->title_id, bbox); + + label_bbox->valid = false; + + list_for_each_entry(item, &menu->item_head, sibling) { + scene_bbox_union(menu->obj.scene, item->label_id, bbox); + scene_bbox_union(menu->obj.scene, item->key_id, bbox); + scene_bbox_union(menu->obj.scene, item->desc_id, bbox); + scene_bbox_union(menu->obj.scene, item->preview_id, bbox); + + /* Get the bounding box of all labels */ + scene_bbox_union(menu->obj.scene, item->label_id, label_bbox); + } +} + +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) { struct scene_menitem *item; diff --git a/doc/develop/expo.rst b/doc/develop/expo.rst index 9565974a28..54861b93ac 100644 --- a/doc/develop/expo.rst +++ b/doc/develop/expo.rst @@ -182,8 +182,6 @@ Some ideas for future work: - 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 diff --git a/include/expo.h b/include/expo.h index b6777cebcb..6c45c403cf 100644 --- a/include/expo.h +++ b/include/expo.h @@ -327,6 +327,16 @@ const char *expo_get_str(struct expo *exp, uint id); */ 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 * @@ -468,6 +478,17 @@ int scene_txt_set_font(struct scene *scn, uint id, const char *font_name, */ 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 * diff --git a/test/boot/expo.c b/test/boot/expo.c index 5088776f7b..493d050baf 100644 --- a/test/boot/expo.c +++ b/test/boot/expo.c @@ -473,6 +473,48 @@ static int expo_render_image(struct unit_test_state *uts) /* 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)); -- cgit v1.2.3 From 2e59389704cd1e46101f7ffda2dac3f44f2fa332 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:53 -0600 Subject: expo: Support simple themes It is a pain to manually set the fonts of all objects to be consistent. Some spacing settings are also better set globally than by manually positioning each object. Add a 'theme' to the expo, to hold this information. For now it includes only the font size. Signed-off-by: Simon Glass --- boot/expo.c | 20 ++++++++++++++++++++ boot/scene.c | 28 ++++++++++++++++++++++++++++ boot/scene_internal.h | 9 +++++++++ doc/develop/expo.rst | 9 +++++++-- include/expo.h | 24 ++++++++++++++++++++++++ test/boot/expo.c | 8 +++++++- 6 files changed, 95 insertions(+), 3 deletions(-) diff --git a/boot/expo.c b/boot/expo.c index 67cae3c7e2..d5e935966b 100644 --- a/boot/expo.c +++ b/boot/expo.c @@ -231,3 +231,23 @@ int expo_action_get(struct expo *exp, struct expo_action *act) 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); + + 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/scene.c b/boot/scene.c index 6d5e3c1f03..4dbe12a2b7 100644 --- a/boot/scene.c +++ b/boot/scene.c @@ -466,3 +466,31 @@ int scene_calc_dims(struct scene *scn, bool do_menus) 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; +} diff --git a/boot/scene_internal.h b/boot/scene_internal.h index 00085a2f55..3387a90761 100644 --- a/boot/scene_internal.h +++ b/boot/scene_internal.h @@ -89,6 +89,15 @@ int scene_calc_dims(struct scene *scn, bool do_menus); */ 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 * diff --git a/doc/develop/expo.rst b/doc/develop/expo.rst index 54861b93ac..2f4882899b 100644 --- a/doc/develop/expo.rst +++ b/doc/develop/expo.rst @@ -155,8 +155,13 @@ such as scanning devices for more bootflows. Themes ------ -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. +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) + API documentation ----------------- diff --git a/include/expo.h b/include/expo.h index 6c45c403cf..ea8f38913d 100644 --- a/include/expo.h +++ b/include/expo.h @@ -7,6 +7,7 @@ #ifndef __SCENE_H #define __SCENE_H +#include #include struct udevice; @@ -42,6 +43,19 @@ struct expo_action { }; }; +/** + * 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 * @@ -57,6 +71,7 @@ struct expo_action { * type set to EXPOACT_NONE if there is no action * @text_mode: true to use text mode for the menu (no vidconsole) * @priv: Private data for the controller + * @theme: Information about fonts styles, etc. * @scene_head: List of scenes * @str_head: list of strings */ @@ -69,6 +84,7 @@ struct expo { struct expo_action action; bool text_mode; void *priv; + struct expo_theme theme; struct list_head scene_head; struct list_head str_head; }; @@ -583,4 +599,12 @@ int expo_send_key(struct expo *exp, int key); */ 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); + #endif /*__SCENE_H */ diff --git a/test/boot/expo.c b/test/boot/expo.c index 493d050baf..c0ef03b591 100644 --- a/test/boot/expo.c +++ b/test/boot/expo.c @@ -226,7 +226,7 @@ static int expo_object(struct unit_test_state *uts) } 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; @@ -236,6 +236,7 @@ static int expo_object_attr(struct unit_test_state *uts) struct expo *exp; ulong start_mem; char name[100]; + ofnode node; char *data; int id; @@ -273,6 +274,11 @@ static int expo_object_attr(struct unit_test_state *uts) 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)); -- cgit v1.2.3 From 8872bc7f9dabf923f245e9f737be8c4da0ca2a50 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:54 -0600 Subject: expo: Move menu-item selection into a function The current implementation supports a 'pointer' object which points to the currently highlighted menu item. We want to support highlighting the label of the menu item instead, e.g. with inverse video. In this case we will need to 'unhighlight' the old item and highlight the new one. As a first step, move the pointer logic into a function. This fixes a bug where the item is hidden when it should not be. Signed-off-by: Simon Glass --- boot/scene_menu.c | 67 ++++++++++++++++++++++++++++++++++++++++++++----------- test/boot/expo.c | 2 +- 2 files changed, 55 insertions(+), 14 deletions(-) diff --git a/boot/scene_menu.c b/boot/scene_menu.c index fa79cecdbd..154be7f012 100644 --- a/boot/scene_menu.c +++ b/boot/scene_menu.c @@ -33,6 +33,51 @@ void scene_menu_destroy(struct scene_obj_menu *menu) 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 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); + } + + return 0; +} + /** * menu_point_to_item() - Point to a particular menu item * @@ -40,7 +85,10 @@ void scene_menu_destroy(struct scene_obj_menu *menu) */ 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, @@ -125,6 +173,7 @@ int scene_menu_calc_dims(struct scene_obj_menu *menu) int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu) { struct scene_menitem *item; + uint sel_id; int y, cur_y; int ret; @@ -147,7 +196,7 @@ int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu) * 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) { int height; @@ -160,8 +209,8 @@ int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu) 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; /* * Put the label on the left, then leave a space for the @@ -207,16 +256,8 @@ int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu) y += height; } - 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.dim.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; } diff --git a/test/boot/expo.c b/test/boot/expo.c index c0ef03b591..453316ece7 100644 --- a/test/boot/expo.c +++ b/test/boot/expo.c @@ -378,7 +378,7 @@ static int expo_object_menu(struct unit_test_state *uts) ut_asserteq(-4, prev1->obj.dim.x); ut_asserteq(menu->obj.dim.y + 32, prev1->obj.dim.y); - ut_asserteq(false, prev1->obj.flags & SCENEOF_HIDE); + ut_asserteq(true, prev1->obj.flags & SCENEOF_HIDE); expo_destroy(exp); -- cgit v1.2.3 From d3db0216dc163594309a9930b31e3161261cd873 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:55 -0600 Subject: expo: Support drawing of popup menus At present only a single menu is supported. All items are shown and a pointer object points to the current item. Add support for multiple menus, one of which is highlighted, indicated by the highlight_id property in the scene. The highlighted menu item has a SCENEOF_POINT flag, indicating that it is currently pointed to. The popup menu is normally closed, in which case it shows only the current menu item. When it is opened, it shows all items, allowing the user to select one. Rather than requiring the menu item to have a description, require it to have a label. Use the label (only) for the popup menu. With this, most of the drawing and layout logic is complete. Signed-off-by: Simon Glass --- boot/scene_menu.c | 58 +++++++++++++++++++++++++++++++++++-------------------- include/expo.h | 9 +++++++++ 2 files changed, 46 insertions(+), 21 deletions(-) diff --git a/boot/scene_menu.c b/boot/scene_menu.c index 154be7f012..f2832b4ddf 100644 --- a/boot/scene_menu.c +++ b/boot/scene_menu.c @@ -56,6 +56,7 @@ static struct scene_menitem *scene_menuitem_find(struct scene_obj_menu *menu, 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; @@ -75,6 +76,12 @@ static int update_pointers(struct scene_obj_menu *menu, uint id, bool point) 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; } @@ -172,11 +179,15 @@ int scene_menu_calc_dims(struct scene_obj_menu *menu) 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; struct scene_menitem *item; uint sel_id; - int y, cur_y; + int x, y; int ret; + 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.dim.x, y); @@ -187,7 +198,10 @@ int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu) if (ret < 0) return log_msg_ret("hei", ret); - y += ret * 2; + if (stack) + x += 200; + else + y += ret * 2; } /* @@ -198,9 +212,10 @@ int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu) */ 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; @@ -212,29 +227,29 @@ int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu) 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.dim.x, - y); - if (ret < 0) - return log_msg_ret("nam", ret); - } - - ret = scene_obj_set_pos(scn, item->key_id, menu->obj.dim.x + 230, - y); + ret = scene_obj_set_pos(scn, item->label_id, x, 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.dim.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; @@ -253,7 +268,8 @@ int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu) return log_msg_ret("hid", ret); } - y += height; + if (!stack || open) + y += height; } if (sel_id) @@ -380,7 +396,7 @@ int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id, 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)); diff --git a/include/expo.h b/include/expo.h index ea8f38913d..0c55d60f71 100644 --- a/include/expo.h +++ b/include/expo.h @@ -70,6 +70,7 @@ struct expo_theme { * @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 @@ -83,6 +84,7 @@ struct expo { uint next_id; struct expo_action action; bool text_mode; + bool popup; void *priv; struct expo_theme theme; struct list_head scene_head; @@ -111,6 +113,7 @@ struct expo_string { * @name: Name of the scene (allocated) * @id: ID number of the scene * @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 */ @@ -119,6 +122,7 @@ struct scene { char *name; uint id; uint title_id; + uint highlight_id; struct list_head sibling; struct list_head obj_head; }; @@ -157,9 +161,14 @@ struct scene_dim { * 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, }; /** -- cgit v1.2.3 From 3f33b9c722a41e1577df110470521a014713ce2e Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:56 -0600 Subject: expo: Rename EXPOACT_POINT to EXPOACT_POINT_ITEM At present we only support a single menu, so all that can be pointed to is the current menu item. Rename this action so that we can also add an action for pointing to an object. This will allow cycling through the objects in a scene. Signed-off-by: Simon Glass --- boot/scene_menu.c | 4 ++-- include/expo.h | 6 +++--- test/boot/expo.c | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/boot/scene_menu.c b/boot/scene_menu.c index f2832b4ddf..892099557a 100644 --- a/boot/scene_menu.c +++ b/boot/scene_menu.c @@ -347,7 +347,7 @@ int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key, 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); } @@ -356,7 +356,7 @@ int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key, 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); } diff --git a/include/expo.h b/include/expo.h index 0c55d60f71..0f43888978 100644 --- a/include/expo.h +++ b/include/expo.h @@ -16,13 +16,13 @@ struct udevice; * 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_ITEM: menu item was highlighted (@id indicates which) * @EXPOACT_SELECT: menu item was selected (@id indicates which) * @EXPOACT_QUIT: request to exit the menu */ enum expoact_type { EXPOACT_NONE, - EXPOACT_POINT, + EXPOACT_POINT_ITEM, EXPOACT_SELECT, EXPOACT_QUIT, }; @@ -31,7 +31,7 @@ enum expoact_type { * 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 { diff --git a/test/boot/expo.c b/test/boot/expo.c index 453316ece7..c34eaeedd6 100644 --- a/test/boot/expo.c +++ b/test/boot/expo.c @@ -530,7 +530,7 @@ static int expo_render_image(struct unit_test_state *uts) 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)); @@ -570,7 +570,7 @@ static int expo_render_image(struct unit_test_state *uts) 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)); -- cgit v1.2.3 From 756c9559e60a0ef8434128205adced937240925d Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:57 -0600 Subject: expo: Draw popup menus in both opened and closed states When a popup menu is closed it shows only the selected item. When it is open it shows a background and all items, with a highlight that can be moved between the items. Add the drawing logic for this. Signed-off-by: Simon Glass --- boot/scene.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++++-- boot/scene_internal.h | 7 ++++++ boot/scene_menu.c | 29 ++++++++++++++++++++++ include/expo.h | 30 +++++++++++++++++++++++ 4 files changed, 130 insertions(+), 2 deletions(-) diff --git a/boot/scene.c b/boot/scene.c index 4dbe12a2b7..fb199ef295 100644 --- a/boot/scene.c +++ b/boot/scene.c @@ -345,14 +345,44 @@ static int scene_obj_render(struct scene_obj *obj, bool text_mode) } 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, 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 @@ -494,3 +524,35 @@ int scene_apply_theme(struct scene *scn, struct expo_theme *theme) 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 3387a90761..2544c961da 100644 --- a/boot/scene_internal.h +++ b/boot/scene_internal.h @@ -153,6 +153,13 @@ int scene_render(struct scene *scn); */ 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_menu_calc_dims() - Calculate the dimensions of a menu * diff --git a/boot/scene_menu.c b/boot/scene_menu.c index 892099557a..20ded91fb3 100644 --- a/boot/scene_menu.c +++ b/boot/scene_menu.c @@ -515,3 +515,32 @@ int scene_menu_display(struct scene_obj_menu *menu) 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); +} diff --git a/include/expo.h b/include/expo.h index 0f43888978..0699cdb4c1 100644 --- a/include/expo.h +++ b/include/expo.h @@ -412,6 +412,36 @@ int scene_new(struct expo *exp, const char *name, uint id, struct scene **scnp); */ 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 * -- cgit v1.2.3 From 4c87e073a4573159f97eb4ed80ec4088f33c7008 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:58 -0600 Subject: expo: Draw the current opened menu on top When a menu is opened, it must be displayed over all other objects in the scene, so that all its items are visible. Handle this by drawing the menu object a second time, after all other objects have been drawn. Draw all of the objects which are dependent on the menu object. Signed-off-by: Simon Glass --- boot/expo.c | 12 ++++++++++++ boot/scene.c | 31 +++++++++++++++++++++++++++++++ boot/scene_internal.h | 20 ++++++++++++++++++++ boot/scene_menu.c | 17 +++++++++++++++++ 4 files changed, 80 insertions(+) diff --git a/boot/expo.c b/boot/expo.c index d5e935966b..e99555163c 100644 --- a/boot/expo.c +++ b/boot/expo.c @@ -172,6 +172,18 @@ int expo_set_scene_id(struct expo *exp, uint 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; diff --git a/boot/scene.c b/boot/scene.c index fb199ef295..bc213bc08b 100644 --- a/boot/scene.c +++ b/boot/scene.c @@ -421,6 +421,30 @@ int scene_arrange(struct scene *scn) 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; @@ -435,6 +459,13 @@ int scene_render(struct scene *scn) } } + /* 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; } diff --git a/boot/scene_internal.h b/boot/scene_internal.h index 2544c961da..dc98ecd021 100644 --- a/boot/scene_internal.h +++ b/boot/scene_internal.h @@ -160,6 +160,26 @@ int scene_send_key(struct scene *scn, int key, struct expo_action *event); */ 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 * diff --git a/boot/scene_menu.c b/boot/scene_menu.c index 20ded91fb3..6aab27611d 100644 --- a/boot/scene_menu.c +++ b/boot/scene_menu.c @@ -544,3 +544,20 @@ void scene_menu_render(struct scene_obj_menu *menu) 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; +} -- cgit v1.2.3 From 4e64beeba7de7720b42714cbc542c9f7dfbba841 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:22:59 -0600 Subject: expo: Implement the keypress logic for popup menus In 'popup' mode, the expo allows moving around the objects in a scene. When 'enter' is pressed on a menu, it opens and the user can move around the items in the menu. Implement this using keypress handles and actions. Signed-off-by: Simon Glass --- boot/scene.c | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++ boot/scene_menu.c | 10 +++++-- doc/develop/expo.rst | 3 +- include/expo.h | 7 +++++ 4 files changed, 96 insertions(+), 4 deletions(-) diff --git a/boot/scene.c b/boot/scene.c index bc213bc08b..ea94b90584 100644 --- a/boot/scene.c +++ b/boot/scene.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -469,11 +470,90 @@ int scene_render(struct scene *scn) 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; diff --git a/boot/scene_menu.c b/boot/scene_menu.c index 6aab27611d..dfe5692d6c 100644 --- a/boot/scene_menu.c +++ b/boot/scene_menu.c @@ -323,6 +323,7 @@ static struct scene_menitem *scene_menu_find_key(struct scene *scn, 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; @@ -367,8 +368,13 @@ int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key, 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); diff --git a/doc/develop/expo.rst b/doc/develop/expo.rst index 2f4882899b..80e435c5e6 100644 --- a/doc/develop/expo.rst +++ b/doc/develop/expo.rst @@ -178,8 +178,7 @@ Some ideas for future work: - 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 +- Better support for handling lots of settings, e.g. with radio/option widgets - 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 diff --git a/include/expo.h b/include/expo.h index 0699cdb4c1..f7febe1c9a 100644 --- a/include/expo.h +++ b/include/expo.h @@ -16,14 +16,21 @@ struct udevice; * enum expoact_type - types of actions reported by the expo * * @EXPOACT_NONE: no action + * @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_OBJ, EXPOACT_POINT_ITEM, EXPOACT_SELECT, + EXPOACT_OPEN, + EXPOACT_CLOSE, EXPOACT_QUIT, }; -- cgit v1.2.3 From 7230fdb3837ad745adff4cf129dd04e893fe0a36 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:23:00 -0600 Subject: expo: Add spacing around menus and items It looks better if menus have a bit of an inset, rather than be drawn hard up against the background. Also, menu items look better if they have a bit of spacing between them. Add theme options for these and implement the required changes. Signed-off-by: Simon Glass --- arch/sandbox/dts/test.dts | 2 ++ boot/expo.c | 2 ++ boot/scene.c | 6 ++++-- boot/scene_menu.c | 37 ++++++++++++++++++++++++------------- doc/develop/expo.rst | 6 ++++++ 5 files changed, 38 insertions(+), 15 deletions(-) diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index ff9f9222e6..38d5739421 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>; }; /* diff --git a/boot/expo.c b/boot/expo.c index e99555163c..8c6fbc0e30 100644 --- a/boot/expo.c +++ b/boot/expo.c @@ -254,6 +254,8 @@ int expo_apply_theme(struct expo *exp, ofnode 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); diff --git a/boot/scene.c b/boot/scene.c index ea94b90584..6fbc1fc578 100644 --- a/boot/scene.c +++ b/boot/scene.c @@ -309,6 +309,7 @@ static int scene_obj_render(struct scene_obj *obj, bool text_mode) { struct scene *scn = obj->scene; struct expo *exp = scn->expo; + const struct expo_theme *theme = &exp->theme; struct udevice *dev = exp->display; struct udevice *cons = text_mode ? NULL : exp->cons; int x, y, ret; @@ -363,8 +364,9 @@ static int scene_obj_render(struct scene_obj *obj, bool text_mode) vid_priv = dev_get_uclass_priv(dev); if (obj->flags & SCENEOF_POINT) { vidconsole_push_colour(cons, fore, back, &old); - video_fill_part(dev, x, y, - x + obj->dim.w, y + obj->dim.h, + 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); diff --git a/boot/scene_menu.c b/boot/scene_menu.c index dfe5692d6c..8a355f838c 100644 --- a/boot/scene_menu.c +++ b/boot/scene_menu.c @@ -98,7 +98,7 @@ static void menu_point_to_item(struct scene_obj_menu *menu, uint item_id) update_pointers(menu, item_id, true); } -static int scene_bbox_union(struct scene *scn, uint id, +static int scene_bbox_union(struct scene *scn, uint id, int inset, struct vidconsole_bbox *bbox) { struct scene_obj *obj; @@ -109,14 +109,14 @@ static int scene_bbox_union(struct scene *scn, uint id, if (!obj) return log_msg_ret("obj", -ENOENT); if (bbox->valid) { - bbox->x0 = min(bbox->x0, obj->dim.x); + 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); + 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; + bbox->x0 = obj->dim.x - inset; bbox->y0 = obj->dim.y; - bbox->x1 = obj->dim.x + obj->dim.w; + bbox->x1 = obj->dim.x + obj->dim.w + inset; bbox->y1 = obj->dim.y + obj->dim.h; bbox->valid = true; } @@ -135,22 +135,31 @@ 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, bbox); + 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, bbox); - scene_bbox_union(menu->obj.scene, item->key_id, bbox); - scene_bbox_union(menu->obj.scene, item->desc_id, bbox); - scene_bbox_union(menu->obj.scene, item->preview_id, bbox); + 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, label_bbox); + 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) @@ -182,6 +191,7 @@ 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; uint sel_id; int x, y; @@ -233,7 +243,8 @@ int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu) * Put the label on the left, then leave a space for the * pointer, then the key and the description */ - ret = scene_obj_set_pos(scn, item->label_id, x, y); + ret = scene_obj_set_pos(scn, item->label_id, + x + theme->menu_inset, y); if (ret < 0) return log_msg_ret("nam", ret); scene_obj_set_hide(scn, item->label_id, @@ -269,7 +280,7 @@ int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu) } if (!stack || open) - y += height; + y += height + theme->menuitem_gap_y; } if (sel_id) diff --git a/doc/develop/expo.rst b/doc/develop/expo.rst index 80e435c5e6..bd593dc2b3 100644 --- a/doc/develop/expo.rst +++ b/doc/develop/expo.rst @@ -162,6 +162,12 @@ 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 + API documentation ----------------- -- cgit v1.2.3 From 82cafee133ee5c087449761988c096fc26a17cf6 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:23:01 -0600 Subject: expo: Support building an expo from a description file The only way to create an expo at present is by calling the functions to create each object. It is useful to have more data-driven approach, where the objects can be specified in a suitable file format and created from that. This makes testing easier as well. Add support for describing an expo in a devicetree node. This allows more complex tests to be set up, as well as providing an easier format for users. It also provides a better basis for the upcoming configuration editor. Signed-off-by: Simon Glass --- arch/sandbox/dts/cedit.dtsi | 64 +++++++ arch/sandbox/dts/test.dts | 5 + boot/Makefile | 2 +- boot/expo.c | 1 + boot/expo_build.c | 400 ++++++++++++++++++++++++++++++++++++++++++++ doc/develop/expo.rst | 282 ++++++++++++++++++++++++++++++- include/expo.h | 14 ++ include/test/cedit-test.h | 29 ++++ test/boot/expo.c | 80 +++++++++ 9 files changed, 870 insertions(+), 7 deletions(-) create mode 100644 arch/sandbox/dts/cedit.dtsi create mode 100644 boot/expo_build.c create mode 100644 include/test/cedit-test.h diff --git a/arch/sandbox/dts/cedit.dtsi b/arch/sandbox/dts/cedit.dtsi new file mode 100644 index 0000000000..a9eb4c2d59 --- /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 + +&cedit { + dynamic-start = ; + + scenes { + main { + id = ; + + /* value refers to the matching id in /strings */ + title-id = ; + + /* 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 = ; + + /* + * has both string and ID. The string is ignored + * if the ID is present and points to a string + */ + title = "CPU speed"; + title-id = ; + + /* menu items as simple strings */ + item-label = "2 GHz", "2.5 GHz", "3 GHz"; + + /* IDs for the menu items */ + item-id = ; + }; + + power-loss { + type = "menu"; + id = ; + + title = "AC Power"; + item-label = "Always Off", "Always On", + "Memory"; + + item-id = ; + }; + }; + }; + + strings { + title { + id = ; + value = "Test Configuration"; + value-es = "configuración de prueba"; + }; + }; +}; diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index 38d5739421..442222e4b9 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -141,6 +141,9 @@ }; }; + cedit: cedit { + }; + fuzzing-engine { compatible = "sandbox,fuzzing-engine"; }; @@ -1830,3 +1833,5 @@ #ifdef CONFIG_SANDBOX_VPL #include "sandbox_vpl.dtsi" #endif + +#include "cedit.dtsi" diff --git a/boot/Makefile b/boot/Makefile index f94c31d922..28c4e55ca6 100644 --- a/boot/Makefile +++ b/boot/Makefile @@ -50,7 +50,7 @@ ifdef CONFIG_SPL_BUILD 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/expo.c b/boot/expo.c index 8c6fbc0e30..db837f7b49 100644 --- a/boot/expo.c +++ b/boot/expo.c @@ -58,6 +58,7 @@ void expo_destroy(struct expo *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) diff --git a/boot/expo_build.c b/boot/expo_build.c new file mode 100644 index 0000000000..7e61ab06a8 --- /dev/null +++ b/boot/expo_build.c @@ -0,0 +1,400 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Building an expo from an FDT description + * + * Copyright 2022 Google LLC + * Written by Simon Glass + */ + +#define LOG_CATEGORY LOGC_EXPO + +#include +#include +#include +#include +#include +#include +#include + +/** + * 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); + 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/doc/develop/expo.rst b/doc/develop/expo.rst index bd593dc2b3..f5caadbfd9 100644 --- a/doc/develop/expo.rst +++ b/doc/develop/expo.rst @@ -100,10 +100,13 @@ objects first, then create the menu item, passing in the relevant IDs. 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 ------ @@ -168,6 +171,273 @@ menu-inset menuitem-gap-y Number of pixels between menu items +.. _expo_format: + +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 +----------- + +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. + +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. + +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 = ; + + scenes { + main { + id = ; + + /* value refers to the matching id in /strings */ + title-id = ; + + /* 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 = ; + + /* + * has both string and ID. The string is ignored + * if the ID is present and points to a string + */ + title = "CPU speed"; + title-id = ; + + /* menu items as simple strings */ + item-label = "2 GHz", "2.5 GHz", "3 GHz"; + + /* IDs for the menu items */ + item-id = ; + }; + + power-loss { + type = "menu"; + id = ; + + title = "AC Power"; + item-label = "Always Off", "Always On", + "Memory"; + + item-id = ; + }; + }; + }; + + strings { + title { + id = ; + value = "Test Configuration"; + value-es = "configuración de prueba"; + }; + }; + }; + API documentation ----------------- @@ -180,11 +450,10 @@ Future ideas 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 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 @@ -194,6 +463,7 @@ Some ideas for future work: - Support both graphical and text menus at the same time on different devices - Support unicode - Support curses for proper serial-terminal menus +- Add support for large menus which need to scroll .. Simon Glass .. 7-Oct-22 diff --git a/include/expo.h b/include/expo.h index f7febe1c9a..9fec4d0cd8 100644 --- a/include/expo.h +++ b/include/expo.h @@ -653,4 +653,18 @@ int expo_action_get(struct expo *exp, struct expo_action *act); */ 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); + #endif /*__SCENE_H */ diff --git a/include/test/cedit-test.h b/include/test/cedit-test.h new file mode 100644 index 0000000000..349df75b16 --- /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 + */ + +#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/test/boot/expo.c b/test/boot/expo.c index c34eaeedd6..e7148024fe 100644 --- a/test/boot/expo.c +++ b/test/boot/expo.c @@ -13,6 +13,7 @@ #include #include #include "bootstd_common.h" +#include #include "../../boot/scene_internal.h" enum { @@ -588,3 +589,82 @@ static int expo_render_image(struct unit_test_state *uts) 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); -- cgit v1.2.3 From a0874dc4ac7153f49f72c3fb818422d940ecbfea Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:23:02 -0600 Subject: expo: Add a configuration editor Add a new 'cedit' command which allows editing configuration using an expo. The configuration items appear as menus on the display. This is extremely basic, only supporting menus and not providing any way to load or save the configuration. Signed-off-by: Simon Glass --- boot/Kconfig | 14 ++++ boot/Makefile | 1 + boot/cedit.c | 163 ++++++++++++++++++++++++++++++++++++++++++++++ boot/expo_build.c | 3 +- boot/scene.c | 12 ++++ boot/scene_internal.h | 8 +++ cmd/Kconfig | 9 +++ cmd/Makefile | 1 + cmd/cedit.c | 93 ++++++++++++++++++++++++++ configs/sandbox_defconfig | 1 + doc/develop/expo.rst | 8 ++- doc/usage/cmd/cedit.rst | 31 +++++++++ doc/usage/index.rst | 1 + include/expo.h | 29 +++++++++ 14 files changed, 370 insertions(+), 4 deletions(-) create mode 100644 boot/cedit.c create mode 100644 cmd/cedit.c create mode 100644 doc/usage/cmd/cedit.rst diff --git a/boot/Kconfig b/boot/Kconfig index a643a3d128..c8b8f36d83 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -1630,4 +1630,18 @@ config SAVE_PREV_BL_INITRAMFS_START_ADDR 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 28c4e55ca6..f828f870a3 100644 --- a/boot/Makefile +++ b/boot/Makefile @@ -33,6 +33,7 @@ ifdef CONFIG_$(SPL_TPL_)BOOTSTD_FULL 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 diff --git a/boot/cedit.c b/boot/cedit.c new file mode 100644 index 0000000000..ee24658917 --- /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 + */ + +#include +#include +#include +#include +#include +#include +#include +#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_build.c b/boot/expo_build.c index 7e61ab06a8..22f62eb54b 100644 --- a/boot/expo_build.c +++ b/boot/expo_build.c @@ -376,7 +376,8 @@ int expo_build(ofnode root, struct expo **expp) ret = read_strings(&info, root); if (ret) return log_msg_ret("str", ret); - list_strings(&info); + if (_DEBUG) + list_strings(&info); ret = expo_new("name", NULL, &exp); if (ret) diff --git a/boot/scene.c b/boot/scene.c index 6fbc1fc578..e52333371f 100644 --- a/boot/scene.c +++ b/boot/scene.c @@ -92,6 +92,18 @@ void *scene_obj_find(struct scene *scn, uint id, enum scene_obj_t type) 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) { diff --git a/boot/scene_internal.h b/boot/scene_internal.h index dc98ecd021..fb1ea5533b 100644 --- a/boot/scene_internal.h +++ b/boot/scene_internal.h @@ -40,6 +40,14 @@ uint resolve_id(struct expo *exp, uint id); */ 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 * diff --git a/cmd/Kconfig b/cmd/Kconfig index c1941849f9..f6b10e01f8 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -428,6 +428,15 @@ config CMD_ABOOTIMG 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 6c37521b4e..9f8c0b058b 100644 --- a/cmd/Makefile +++ b/cmd/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_CMD_BUTTON) += button.o 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/cedit.c b/cmd/cedit.c new file mode 100644 index 0000000000..0cae304c4a --- /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 + */ + +#include +#include +#include +#include +#include +#include + +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 - 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/configs/sandbox_defconfig b/configs/sandbox_defconfig index 1ec44d5b33..4cef6c5153 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -30,6 +30,7 @@ CONFIG_AUTOBOOT_STOP_STR_ENABLE=y 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 f5caadbfd9..2ac4af232d 100644 --- a/doc/develop/expo.rst +++ b/doc/develop/expo.rst @@ -171,8 +171,6 @@ menu-inset menuitem-gap-y Number of pixels between menu items -.. _expo_format: - Pop-up mode ----------- @@ -202,6 +200,8 @@ just as its title and label, as with the `CPU Speed` and `AC Power` menus here:: UP and DOWN to choose, ENTER to select +.. _expo_format: + Expo Format ----------- @@ -211,7 +211,8 @@ 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. +rules. The :doc:`../usage/cmd/cedit` can be used to load a configuration +and create an expo from it. Top-level node ~~~~~~~~~~~~~~ @@ -464,6 +465,7 @@ Some ideas for future work: - 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 .. 7-Oct-22 diff --git a/doc/usage/cmd/cedit.rst b/doc/usage/cmd/cedit.rst new file mode 100644 index 0000000000..8e1110c7c7 --- /dev/null +++ b/doc/usage/cmd/cedit.rst @@ -0,0 +1,31 @@ +.. SPDX-License-Identifier: GPL-2.0+: + +cedit command +============= + +Synopis +------- + +:: + + cedit load + 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 388e59f173..f2ffd2787a 100644 --- a/doc/usage/index.rst +++ b/doc/usage/index.rst @@ -39,6 +39,7 @@ Shell commands cmd/bootz cmd/cat cmd/cbsysinfo + cmd/cedit cmd/cls cmd/cmp cmd/coninfo diff --git a/include/expo.h b/include/expo.h index 9fec4d0cd8..0b1d944a16 100644 --- a/include/expo.h +++ b/include/expo.h @@ -11,6 +11,7 @@ #include struct udevice; +struct video_priv; /** * enum expoact_type - types of actions reported by the expo @@ -378,6 +379,14 @@ int expo_calc_dims(struct expo *exp); */ 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 * @@ -667,4 +676,24 @@ int expo_apply_theme(struct expo *exp, ofnode node); */ 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 */ -- cgit v1.2.3 From 87c1a4130c30e9b1af5d76026f3cc93192eb5cb3 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:23:03 -0600 Subject: expo: Add tests for the configuration editor Add some simple tests and a helpful script to make the configuration editor easier to set up. Signed-off-by: Simon Glass --- arch/sandbox/dts/sandbox.dtsi | 6 ++ arch/sandbox/dts/test.dts | 6 ++ test/boot/expo.c | 44 ++++++++++++++ test/boot/files/expo_layout.dts | 84 ++++++++++++++++++++++++++ test/py/tests/test_ut.py | 10 ++++ tools/expo.py | 130 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 280 insertions(+) create mode 100644 test/boot/files/expo_layout.dts create mode 100755 tools/expo.py diff --git a/arch/sandbox/dts/sandbox.dtsi b/arch/sandbox/dts/sandbox.dtsi index 30a305c4d2..f0ee0b3481 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 442222e4b9..b5509eee8c 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -144,6 +144,12 @@ cedit: cedit { }; + cedit-theme { + font-size = <30>; + menu-inset = <3>; + menuitem-gap-y = <1>; + }; + fuzzing-engine { compatible = "sandbox,fuzzing-engine"; }; diff --git a/test/boot/expo.c b/test/boot/expo.c index e7148024fe..3898f853a7 100644 --- a/test/boot/expo.c +++ b/test/boot/expo.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -668,3 +669,46 @@ static int expo_test_build(struct unit_test_state *uts) 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 0000000000..55d5c910dd --- /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 = ; + + scenes { + main { + id = ; + + /* value refers to the matching id in /strings */ + title-id = ; + + /* 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 = ; + + /* + * has both string and ID. The string is ignored + * if the ID is present and points to a string + */ + title = "CPU speed"; + title-id = ; + + /* menu items as simple strings */ + item-label = "2 GHz", "2.5 GHz", "3 GHz"; + + /* IDs for the menu items */ + item-id = ; + }; + + power-loss { + type = "menu"; + id = ; + + title = "AC Power"; + item-label = "Always Off", "Always On", + "Memory"; + + item-id = ; + }; + }; + }; + + strings { + title { + id = ; + value = "Test Configuration"; + value-es = "configuración de prueba"; + }; + }; +}; diff --git a/test/py/tests/test_ut.py b/test/py/tests/test_ut.py index 0b45863b43..aa1d477cd5 100644 --- a/test/py/tests/test_ut.py +++ b/test/py/tests/test_ut.py @@ -282,6 +282,15 @@ label Fedora-Workstation-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl) 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 @@ def test_ut_dm_init_bootstd(u_boot_console): 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/expo.py b/tools/expo.py new file mode 100755 index 0000000000..c6eb87aec7 --- /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 +""" + +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 "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() -- cgit v1.2.3 From 04f3dcd503a537fab50329686874559dae8a1a22 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 1 Jun 2023 10:23:04 -0600 Subject: video: Update stb_truetype This was brought in in 2016 and a number of changes have been made since then. There does not seem to be much change in functionality, but it is a good idea to update from time to time. Bring in the latest version: 5736b15 ("re-add perlin noise again") Add a few necessary functions, with dummies in some cases. Update the tests as there are subtle changes in rendering, perhaps not for the better. Signed-off-by: Simon Glass --- drivers/video/console_truetype.c | 33 + drivers/video/stb_truetype.h | 2257 ++++++++++++++++++++++++++++++++++---- test/dm/video.c | 6 +- 3 files changed, 2077 insertions(+), 219 deletions(-) diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c index 288123a2e0..0f9bb49e44 100644 --- a/drivers/video/console_truetype.c +++ b/drivers/video/console_truetype.c @@ -62,10 +62,43 @@ static double tt_sqrt(double value) 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) diff --git a/drivers/video/stb_truetype.h b/drivers/video/stb_truetype.h index 438bfce468..c6973bb353 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 . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to = 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 @@ int main(int argc, char **argv) } return 0; } -#endif +#endif // // Output: // @@ -319,9 +363,9 @@ int main(int argc, char **argv) // :@@. M@M // @@@o@@@@ // :M@@V:@@. -// +// ////////////////////////////////////////////////////////////////////////////// -// +// // Complete program: print "Hello World!" banner, with bugs // #if 0 @@ -375,7 +419,8 @@ int main(int arg, char **argv) //// 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 @@ int main(int arg, char **argv) 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 #define STBTT_ifloor(x) ((int) floor(x)) @@ -401,6 +446,18 @@ int main(int arg, char **argv) #ifndef STBTT_sqrt #include #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) #endif #ifndef STBTT_fabs @@ -452,6 +509,14 @@ int main(int arg, char **argv) extern "C" { #endif +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + ////////////////////////////////////////////////////////////////////////////// // // TEXTURE BAKING API @@ -481,7 +546,7 @@ typedef struct 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 @@ STBTT_DEF void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, // // // 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 @@ typedef struct stbrp_rect stbrp_rect; 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 @@ STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); #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 @@ typedef struct 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 @@ STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h // 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 @@ struct stbtt_pack_context { 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 @@ struct stbtt_pack_context { // // +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 @@ typedef struct stbtt_fontinfo 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 @@ STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codep // 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 @@ STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, in // 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_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, 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 @@ STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, in enum { STBTT_vmove=1, STBTT_vline, - STBTT_vcurve + STBTT_vcurve, + STBTT_vcubic }; #endif @@ -727,7 +834,7 @@ STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, in #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 @@ STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, s // 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 int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, s 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 @@ STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, uns // 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_GetGlyphBitmap(const stbtt_fontinfo *info, float 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); @@ -820,6 +938,64 @@ STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap int invert, // if non-zero, vertically flip shape void *userdata); // context for to STBTT_MALLOC +////////////////////////////////////////////////////////////////////////////// +// +// 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... @@ -943,6 +1119,158 @@ typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERS #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 @@ typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERS #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 @@ static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, 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 @@ STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *font_collection, 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) +{ + // 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_uint8 *data = (stbtt_uint8 *) data2; 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 @@ STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data2, i 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 @@ STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data2, i 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 @@ STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codep 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 @@ static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) { 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 @@ static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) 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_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, i 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 @@ static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_ 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 @@ STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, s 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 @@ STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, s } } 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 @@ STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, s 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 @@ STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, s 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 @@ STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, s 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 @@ STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, s // 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 @@ STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, s 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); @@ -1479,7 +2314,49 @@ STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_inde } } -STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +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; @@ -1509,9 +2386,242 @@ STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, 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= 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 g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + 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 @@ STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, in 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_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) 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= 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_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) 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 @@ static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) 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 @@ static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, i { 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 @@ static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, i { 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 @@ static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__ac } } } - + e = e->next; } } @@ -1768,13 +2930,10 @@ static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, 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 @@ static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, 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 void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edg } } +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 @@ static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, 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 @@ static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, 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); - step = sign * dy; + // 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 + } + + // 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(fabs(area) <= 1.01f); + STBTT_assert(STBTT_fabs(area) <= 1.01f); // accumulated error from area += step unless we round step down + STBTT_assert(sy1 > y_final-0.01f); - scanline[x2] += area + sign * (1-(x_bottom-x2)/2) * (sy1-y_crossing); + // 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); + // 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 @@ static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, // 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 @@ static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, // 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 @@ static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int y,j=0, i; float scanline_data[129], *scanline, *scanline2; - if (result->w > 64) { + STBTT__NOTUSED(vsubsample); + + 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 @@ static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, 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_ins_sort(stbtt__edge *p, int n) 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 @@ static void stbtt__add_point(stbtt__point *points, int n, float x, float y) 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 @@ static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x 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) { @@ -2395,6 +3670,14 @@ static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, 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; + break; } } (*contour_lengths)[n] = num_points - start; @@ -2411,8 +3694,9 @@ error: 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 @@ STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info { 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 @@ STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info 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_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info } 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 @@ STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigne 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 void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char * 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 void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, uns 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 @@ STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned ch // // 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 @@ STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // fo 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 @@ STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // fo 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 @@ STBTT_DEF void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, int // #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 @@ static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *no 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 @@ STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, in 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 @@ STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h 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 @@ static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_i 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 @@ static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_i 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 @@ static float stbtt__oversample_shift(int oversample) } // 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 @@ STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, stbtt_fon 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 @@ STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, stbtt_fon 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 @@ STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, stbtt 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 @@ STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, stbtt 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 @@ STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect 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 @@ STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontd 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 @@ STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontda 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 @@ STBTT_DEF void stbtt_GetPackedQuad(stbtt_packedchar *chardata, int pw, int ph, i *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 @@ STBTT_DEF void stbtt_GetPackedQuad(stbtt_packedchar *chardata, int pw, int ph, i // // 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 @@ static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(const stbtt_uint8 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 @@ static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, 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 @@ static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *nam 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 @@ STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *font_collection, const } } +#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 @@ STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *font_collection, const // 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/test/dm/video.c b/test/dm/video.c index 6d9c55c140..0534ee93a3 100644 --- a/test/dm/video.c +++ b/test/dm/video.c @@ -556,7 +556,7 @@ static int dm_test_video_truetype(struct unit_test_state *uts) 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(12187, compress_frame_buffer(uts, dev)); + ut_asserteq(12174, compress_frame_buffer(uts, dev)); return 0; } @@ -577,7 +577,7 @@ static int dm_test_video_truetype_scroll(struct unit_test_state *uts) 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(34481, compress_frame_buffer(uts, dev)); + ut_asserteq(34287, compress_frame_buffer(uts, dev)); return 0; } @@ -598,7 +598,7 @@ static int dm_test_video_truetype_bs(struct unit_test_state *uts) 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(29579, compress_frame_buffer(uts, dev)); + ut_asserteq(29471, compress_frame_buffer(uts, dev)); return 0; } -- cgit v1.2.3