diff --git a/arch/sandbox/cpu/sdl.c b/arch/sandbox/cpu/sdl.c index 2c570ed8d16..590e406517b 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; } } diff --git a/arch/sandbox/dts/cedit.dtsi b/arch/sandbox/dts/cedit.dtsi new file mode 100644 index 00000000000..a9eb4c2d594 --- /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/sandbox.dtsi b/arch/sandbox/dts/sandbox.dtsi index 30a305c4d20..f0ee0b3481a 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 ff9f9222e6f..b5509eee8cf 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -96,6 +96,8 @@ theme { font-size = <30>; + menu-inset = <3>; + menuitem-gap-y = <1>; }; /* @@ -139,6 +141,15 @@ }; }; + cedit: cedit { + }; + + cedit-theme { + font-size = <30>; + menu-inset = <3>; + menuitem-gap-y = <1>; + }; + fuzzing-engine { compatible = "sandbox,fuzzing-engine"; }; @@ -1828,3 +1839,5 @@ #ifdef CONFIG_SANDBOX_VPL #include "sandbox_vpl.dtsi" #endif + +#include "cedit.dtsi" diff --git a/boot/Kconfig b/boot/Kconfig index a643a3d1286..c8b8f36d835 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 f94c31d922d..f828f870a37 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 @@ -50,7 +51,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/bootflow_menu.c b/boot/bootflow_menu.c index 7f06dac0af7..7c1abe5772c 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; @@ -205,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/bootmeth-uclass.c b/boot/bootmeth-uclass.c index 3b3e0614daf..701ee8ad1c8 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/boot/cedit.c b/boot/cedit.c new file mode 100644 index 00000000000..ee24658917b --- /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.c b/boot/expo.c index 05950a17603..db837f7b492 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 @@ -54,6 +56,22 @@ void expo_destroy(struct expo *exp) free(exp); } +uint resolve_id(struct expo *exp, uint id) +{ + log_debug("resolve id %d\n", id); + if (!id) + id = exp->next_id++; + else if (id >= exp->next_id) + exp->next_id = id + 1; + + return id; +} + +void expo_set_dynamic_start(struct expo *exp, uint dyn_start) +{ + exp->next_id = dyn_start; +} + int expo_str(struct expo *exp, const char *name, uint id, const char *str) { struct expo_string *estr; @@ -83,12 +101,45 @@ 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; } -void exp_set_text_mode(struct expo *exp, bool text_mode) +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; } @@ -107,13 +158,33 @@ 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; } +int expo_first_scene_id(struct expo *exp) +{ + struct scene *scn; + + if (list_empty(&exp->scene_head)) + return -ENOENT; + + scn = list_first_entry(&exp->scene_head, struct scene, sibling); + + return scn->id; +} + int expo_render(struct expo *exp) { struct udevice *dev = exp->display; @@ -156,6 +227,11 @@ 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; @@ -168,3 +244,25 @@ 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); + ofnode_read_u32(node, "menu-inset", &theme->menu_inset); + ofnode_read_u32(node, "menuitem-gap-y", &theme->menuitem_gap_y); + + list_for_each_entry(scn, &exp->scene_head, sibling) { + ret = scene_apply_theme(scn, theme); + if (ret) + return log_msg_ret("app", ret); + } + + return 0; +} diff --git a/boot/expo_build.c b/boot/expo_build.c new file mode 100644 index 00000000000..22f62eb54bc --- /dev/null +++ b/boot/expo_build.c @@ -0,0 +1,401 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Building an expo from an FDT description + * + * Copyright 2022 Google LLC + * Written by Simon Glass + */ + +#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); + if (_DEBUG) + list_strings(&info); + + ret = expo_new("name", NULL, &exp); + if (ret) + return log_msg_ret("exp", ret); + + if (!ofnode_read_u32(root, "dynamic-start", &dyn_start)) + expo_set_dynamic_start(exp, dyn_start); + + scenes = ofnode_find_subnode(root, "scenes"); + if (!ofnode_valid(scenes)) + return log_msg_ret("sno", -EINVAL); + + ofnode_for_each_subnode(node, scenes) { + ret = scene_build(&info, node, exp); + if (ret < 0) + return log_msg_ret("scn", ret); + } + *expp = exp; + + return 0; +} diff --git a/boot/scene.c b/boot/scene.c index 030f6aa2a0a..e52333371f9 100644 --- a/boot/scene.c +++ b/boot/scene.c @@ -6,26 +6,19 @@ * Written by Simon Glass */ +#define LOG_CATEGORY LOGC_EXPO + #include #include #include #include #include +#include #include #include #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; @@ -65,16 +58,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; } @@ -103,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) { @@ -213,22 +214,46 @@ 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; - if (obj->type == SCENEOBJT_MENU) - scene_menu_arrange(scn, (struct scene_obj_menu *)obj); + obj->dim.x = x; + obj->dim.y = y; return 0; } -int scene_obj_set_hide(struct scene *scn, uint id, bool hide) +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->hide = hide; + obj->dim.w = w; + obj->dim.h = h; + + return 0; +} + +int scene_obj_set_hide(struct scene *scn, uint id, bool hide) +{ + int ret; + + ret = scene_obj_flag_clrset(scn, id, SCENEOF_HIDE, + hide ? SCENEOF_HIDE : 0); + if (ret) + return log_msg_ret("flg", ret); + + return 0; +} + +int scene_obj_flag_clrset(struct scene *scn, uint id, uint clr, uint set) +{ + struct scene_obj *obj; + + obj = scene_obj_find(scn, id, SCENEOBJT_NONE); + if (!obj) + return log_msg_ret("find", -ENOENT); + obj->flags &= ~clr; + obj->flags |= set; return 0; } @@ -258,16 +283,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); + *widthp = bbox.x1; - /* use a sensible default */ - return 16; + return bbox.y1; } } @@ -282,18 +321,13 @@ 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; + const struct expo_theme *theme = &exp->theme; + struct udevice *dev = exp->display; + struct udevice *cons = text_mode ? NULL : exp->cons; int x, y, ret; - cons = NULL; - if (!text_mode) { - ret = device_find_first_child_by_uclass(dev, - UCLASS_VIDEO_CONSOLE, - &cons); - } - - x = obj->x; - y = obj->y; + x = obj->dim.x; + y = obj->dim.y; switch (obj->type) { case SCENEOBJT_NONE: @@ -325,14 +359,45 @@ 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 - theme->menu_inset, y, + x + obj->dim.w, + y + obj->dim.h, + vid_priv->colour_bg); + } + vidconsole_set_cursor_pos(cons, x, y); vidconsole_put_string(cons, str); + if (obj->flags & SCENEOF_POINT) + vidconsole_pop_colour(cons, &old); + } break; } case SCENEOBJT_MENU: { struct scene_obj_menu *menu = (struct scene_obj_menu *)obj; + + if (exp->popup && (obj->flags & SCENEOF_OPEN)) { + if (!cons) + return -ENOTSUPP; + + /* draw a background behind the menu items */ + scene_menu_render(menu); + } /* * With a vidconsole, the text and item pointer are rendered as * normal objects so we don't need to do anything here. The menu @@ -371,6 +436,30 @@ 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; @@ -378,21 +467,107 @@ 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); } } + /* render any highlighted object on top of the others */ + if (scn->highlight_id && !exp->text_mode) { + ret = scene_render_deps(scn, scn->highlight_id); + if (ret && ret != -ENOTSUPP) + return log_msg_ret("dep", ret); + } + return 0; } +/** + * send_key_obj() - Handle a keypress for moving between objects + * + * @scn: Scene to receive the key + * @key: Key to send (KEYCODE_UP) + * @event: Returns resulting event from this keypress + * Returns: 0 if OK, -ve on error + */ +static void send_key_obj(struct scene *scn, struct scene_obj *obj, int key, + struct expo_action *event) +{ + switch (key) { + case BKEY_UP: + while (obj != list_first_entry(&scn->obj_head, struct scene_obj, + sibling)) { + obj = list_entry(obj->sibling.prev, + struct scene_obj, sibling); + if (obj->type == SCENEOBJT_MENU) { + event->type = EXPOACT_POINT_OBJ; + event->select.id = obj->id; + log_debug("up to obj %d\n", event->select.id); + break; + } + } + break; + case BKEY_DOWN: + while (!list_is_last(&obj->sibling, &scn->obj_head)) { + obj = list_entry(obj->sibling.next, struct scene_obj, + sibling); + if (obj->type == SCENEOBJT_MENU) { + event->type = EXPOACT_POINT_OBJ; + event->select.id = obj->id; + log_debug("down to obj %d\n", event->select.id); + break; + } + } + break; + case BKEY_SELECT: + if (obj->type == SCENEOBJT_MENU) { + event->type = EXPOACT_OPEN; + event->select.id = obj->id; + log_debug("open obj %d\n", event->select.id); + } + break; + case BKEY_QUIT: + event->type = EXPOACT_QUIT; + log_debug("obj quit\n"); + break; + } +} + int scene_send_key(struct scene *scn, int key, struct expo_action *event) { + struct scene_obj_menu *menu; struct scene_obj *obj; int ret; + event->type = EXPOACT_NONE; + + /* + * In 'popup' mode, arrow keys move betwen objects, unless a menu is + * opened + */ + if (scn->expo->popup) { + obj = NULL; + if (scn->highlight_id) { + obj = scene_obj_find(scn, scn->highlight_id, + SCENEOBJT_NONE); + } + if (!obj) + return 0; + + if (!(obj->flags & SCENEOF_OPEN)) { + send_key_obj(scn, obj, key, event); + return 0; + } + + menu = (struct scene_obj_menu *)obj, + ret = scene_menu_send_key(scn, menu, key, event); + if (ret) + return log_msg_ret("key", ret); + return 0; + } + list_for_each_entry(obj, &scn->obj_head, sibling) { if (obj->type == SCENEOBJT_MENU) { struct scene_obj_menu *menu; @@ -401,14 +576,108 @@ 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; } } return 0; } + +int scene_calc_dims(struct scene *scn, bool do_menus) +{ + struct scene_obj *obj; + int ret; + + list_for_each_entry(obj, &scn->obj_head, sibling) { + switch (obj->type) { + case SCENEOBJT_NONE: + case SCENEOBJT_TEXT: + case SCENEOBJT_IMAGE: { + int width; + + if (!do_menus) { + ret = scene_obj_get_hw(scn, obj->id, &width); + if (ret < 0) + return log_msg_ret("get", ret); + obj->dim.w = width; + obj->dim.h = ret; + } + break; + } + case SCENEOBJT_MENU: { + struct scene_obj_menu *menu; + + if (do_menus) { + menu = (struct scene_obj_menu *)obj; + + ret = scene_menu_calc_dims(menu); + if (ret) + return log_msg_ret("men", ret); + } + break; + } + } + } + + return 0; +} + +int scene_apply_theme(struct scene *scn, struct expo_theme *theme) +{ + struct scene_obj *obj; + int ret; + + /* Avoid error-checking optional items */ + scene_txt_set_font(scn, scn->title_id, NULL, theme->font_size); + + list_for_each_entry(obj, &scn->obj_head, sibling) { + switch (obj->type) { + case SCENEOBJT_NONE: + case SCENEOBJT_IMAGE: + case SCENEOBJT_MENU: + break; + case SCENEOBJT_TEXT: + scene_txt_set_font(scn, obj->id, NULL, + theme->font_size); + break; + } + } + + ret = scene_arrange(scn); + if (ret) + return log_msg_ret("arr", ret); + + return 0; +} + +void scene_set_highlight_id(struct scene *scn, uint id) +{ + scn->highlight_id = id; +} + +void scene_highlight_first(struct scene *scn) +{ + struct scene_obj *obj; + + list_for_each_entry(obj, &scn->obj_head, sibling) { + switch (obj->type) { + case SCENEOBJT_MENU: + scene_set_highlight_id(scn, obj->id); + return; + default: + break; + } + } +} + +int scene_set_open(struct scene *scn, uint id, bool open) +{ + int ret; + + ret = scene_obj_flag_clrset(scn, id, SCENEOF_OPEN, + open ? SCENEOF_OPEN : 0); + if (ret) + return log_msg_ret("flg", ret); + + return 0; +} diff --git a/boot/scene_internal.h b/boot/scene_internal.h index e8fd765811e..fb1ea5533b9 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,9 +36,18 @@ 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); +/** + * 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 * @@ -53,6 +62,28 @@ 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_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 * @@ -62,9 +93,19 @@ 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); +/** + * 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 * @@ -72,7 +113,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 +130,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); @@ -120,4 +161,41 @@ 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_render_deps() - Render an object and its dependencies + * + * @scn: Scene to render + * @id: Object ID to render (or 0 for none) + * Returns: 0 if OK, -ve on error + */ +int scene_render_deps(struct scene *scn, uint id); + +/** + * scene_menu_render_deps() - Render a menu and its dependencies + * + * Renders the menu and all of its attached objects + * + * @scn: Scene to render + * @menu: Menu render + * Returns: 0 if OK, -ve on error + */ +int scene_menu_render_deps(struct scene *scn, struct scene_obj_menu *menu); + +/** + * scene_menu_calc_dims() - Calculate the dimensions of a menu + * + * Updates the width and height of the menu based on its contents + * + * @menu: Menu to update + * Returns 0 if OK, -ENOTSUPP if there is no graphical console + */ +int scene_menu_calc_dims(struct scene_obj_menu *menu); + #endif /* __SCENE_INTERNAL_H */ diff --git a/boot/scene_menu.c b/boot/scene_menu.c index 18998e862ab..8a355f838cc 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 @@ -33,6 +33,58 @@ 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 bool stack = scn->expo->popup; + const struct scene_menitem *item; + int ret; + + item = scene_menuitem_find(menu, id); + if (!item) + return log_msg_ret("itm", -ENOENT); + + /* adjust the pointer object to point to the selected item */ + if (menu->pointer_id && item && point) { + struct scene_obj *label; + + label = scene_obj_find(scn, item->label_id, SCENEOBJT_NONE); + + ret = scene_obj_set_pos(scn, menu->pointer_id, + menu->obj.dim.x + 200, label->dim.y); + if (ret < 0) + return log_msg_ret("ptr", ret); + } + + if (stack) { + point &= scn->highlight_id == menu->obj.id; + scene_obj_flag_clrset(scn, item->label_id, SCENEOF_POINT, + point ? SCENEOF_POINT : 0); + } + + return 0; +} + /** * menu_point_to_item() - Point to a particular menu item * @@ -40,18 +92,115 @@ 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, int inset, + struct vidconsole_bbox *bbox) +{ + struct scene_obj *obj; + + if (!id) + return 0; + obj = scene_obj_find(scn, id, SCENEOBJT_NONE); + if (!obj) + return log_msg_ret("obj", -ENOENT); + if (bbox->valid) { + bbox->x0 = min(bbox->x0, obj->dim.x - inset); + bbox->y0 = min(bbox->y0, obj->dim.y); + bbox->x1 = max(bbox->x1, obj->dim.x + obj->dim.w + inset); + bbox->y1 = max(bbox->y1, obj->dim.y + obj->dim.h); + } else { + bbox->x0 = obj->dim.x - inset; + bbox->y0 = obj->dim.y; + bbox->x1 = obj->dim.x + obj->dim.w + inset; + bbox->y1 = obj->dim.y + obj->dim.h; + bbox->valid = true; + } + + return 0; +} + +/** + * scene_menu_calc_bbox() - Calculate bounding boxes for the menu + * + * @menu: Menu to process + * @bbox: Returns bounding box of menu including prompts + * @label_bbox: Returns bounding box of labels + */ +static void scene_menu_calc_bbox(struct scene_obj_menu *menu, + struct vidconsole_bbox *bbox, + struct vidconsole_bbox *label_bbox) +{ + const struct expo_theme *theme = &menu->obj.scene->expo->theme; + const struct scene_menitem *item; + + bbox->valid = false; + scene_bbox_union(menu->obj.scene, menu->title_id, 0, bbox); + + label_bbox->valid = false; + + list_for_each_entry(item, &menu->item_head, sibling) { + scene_bbox_union(menu->obj.scene, item->label_id, + theme->menu_inset, bbox); + scene_bbox_union(menu->obj.scene, item->key_id, 0, bbox); + scene_bbox_union(menu->obj.scene, item->desc_id, 0, bbox); + scene_bbox_union(menu->obj.scene, item->preview_id, 0, bbox); + + /* Get the bounding box of all labels */ + scene_bbox_union(menu->obj.scene, item->label_id, + theme->menu_inset, label_bbox); + } + + /* + * subtract the final menuitem's gap to keep the insert the same top + * and bottom + */ + label_bbox->y1 -= theme->menuitem_gap_y; +} + +int scene_menu_calc_dims(struct scene_obj_menu *menu) +{ + struct vidconsole_bbox bbox, label_bbox; + const struct scene_menitem *item; + + scene_menu_calc_bbox(menu, &bbox, &label_bbox); + + /* Make all labels the same size */ + if (label_bbox.valid) { + list_for_each_entry(item, &menu->item_head, sibling) { + scene_obj_set_size(menu->obj.scene, item->label_id, + label_bbox.x1 - label_bbox.x0, + label_bbox.y1 - label_bbox.y0); + } + } + + if (bbox.valid) { + menu->obj.dim.w = bbox.x1 - bbox.x0; + menu->obj.dim.h = bbox.y1 - bbox.y0; + } + + return 0; } int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu) { + const bool open = menu->obj.flags & SCENEOF_OPEN; + struct expo *exp = scn->expo; + const bool stack = exp->popup; + const struct expo_theme *theme = &exp->theme; struct scene_menitem *item; - int y, cur_y; + uint sel_id; + int x, y; int ret; - y = menu->obj.y; + x = menu->obj.dim.x; + y = menu->obj.dim.y; if (menu->title_id) { - ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.x, y); + ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.dim.x, y); if (ret < 0) return log_msg_ret("tit", ret); @@ -59,7 +208,10 @@ 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; } /* @@ -68,11 +220,12 @@ 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) { + bool selected; int height; - ret = scene_obj_get_hw(scn, item->desc_id, NULL); + ret = scene_obj_get_hw(scn, item->label_id, NULL); if (ret < 0) return log_msg_ret("get", ret); height = ret; @@ -81,32 +234,33 @@ 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; + + selected = sel_id == item->id; /* * Put the label on the left, then leave a space for the * pointer, then the key and the description */ - if (item->label_id) { - ret = scene_obj_set_pos(scn, item->label_id, menu->obj.x, - y); + 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, + stack && !open && !selected); + + if (item->key_id) { + ret = scene_obj_set_pos(scn, item->key_id, x + 230, y); if (ret < 0) - return log_msg_ret("nam", ret); + return log_msg_ret("key", ret); } - ret = scene_obj_set_pos(scn, item->key_id, menu->obj.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, - y); - if (ret < 0) - return log_msg_ret("des", ret); - - if (menu->cur_item_id == item->id) - cur_y = y; + if (item->desc_id) { + ret = scene_obj_set_pos(scn, item->desc_id, x + 280, y); + if (ret < 0) + return log_msg_ret("des", ret); + } if (item->preview_id) { bool hide; @@ -125,19 +279,12 @@ 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 + theme->menuitem_gap_y; } - if (menu->pointer_id && cur_y != -1) { - /* - * put the pointer to the right of and level with the item it - * points to - */ - ret = scene_obj_set_pos(scn, menu->pointer_id, - menu->obj.x + 200, cur_y); - if (ret < 0) - return log_msg_ret("ptr", ret); - } + if (sel_id) + menu_point_to_item(menu, sel_id); return 0; } @@ -158,10 +305,6 @@ 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; } @@ -191,6 +334,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; @@ -215,7 +359,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); } @@ -224,7 +368,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); } @@ -235,8 +379,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); @@ -258,14 +407,13 @@ 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) return log_msg_ret("find", -ENOENT); /* Check that the text ID is valid */ - if (!scene_obj_find(scn, desc_id, SCENEOBJT_TEXT)) + if (!scene_obj_find(scn, label_id, SCENEOBJT_TEXT)) return log_msg_ret("txt", -EINVAL); item = calloc(1, sizeof(struct scene_obj_menu)); @@ -285,10 +433,6 @@ 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; @@ -388,3 +532,49 @@ 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); +} + +int scene_menu_render_deps(struct scene *scn, struct scene_obj_menu *menu) +{ + struct scene_menitem *item; + + scene_render_deps(scn, menu->title_id); + scene_render_deps(scn, menu->cur_item_id); + scene_render_deps(scn, menu->pointer_id); + + list_for_each_entry(item, &menu->item_head, sibling) { + scene_render_deps(scn, item->key_id); + scene_render_deps(scn, item->label_id); + scene_render_deps(scn, item->desc_id); + } + + return 0; +} diff --git a/cmd/Kconfig b/cmd/Kconfig index c1941849f98..f6b10e01f84 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 6c37521b4e2..9f8c0b058be 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/cat.c b/cmd/cat.c index 1273a26b145..b059080193d 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]; - // check file exists - if (fs_set_blk_dev(ifname, dev, FS_TYPE_ANY)) - return CMD_RET_FAILURE; + ret = fs_load_alloc(ifname, dev, file, 0, 0, (void **)&buffer, + &file_size); - if (!fs_exists(file)) { + // check file exists + switch (ret) { + case 0: + break; + case -ENOMEDIUM: + return CMD_RET_FAILURE; + 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; } diff --git a/cmd/cedit.c b/cmd/cedit.c new file mode 100644 index 00000000000..0cae304c4ad --- /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/common/log.c b/common/log.c index 7cfc49bc28a..6f02a25c593 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/configs/sandbox_defconfig b/configs/sandbox_defconfig index 1ec44d5b33b..4cef6c51539 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 32dd7f09030..2ac4af232da 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. @@ -97,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 ------ @@ -152,8 +158,287 @@ 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) + +menu-inset + Number of pixels to inset the menu on the sides and top (type: u32) + +menuitem-gap-y + Number of pixels between menu items + +Pop-up mode +----------- + +Expos support two modes. The simple mode is used for selecting from a single +menu, e.g. when choosing with OS to boot. In this mode the menu items are shown +in a list (label, > pointer, key and description) and can be chosen using arrow +keys and enter:: + + U-Boot Boot Menu + + UP and DOWN to choose, ENTER to select + + mmc1 > 0 Fedora-Workstation-armhfp-31-1.9 + mmc3 1 Armbian + +The popup mode allows multiple menus to be present in a scene. Each is shown +just as its title and label, as with the `CPU Speed` and `AC Power` menus here:: + + Test Configuration + + + CPU Speed <2 GHz> (highlighted) + + AC Power Always Off + + + UP and DOWN to choose, ENTER to select + + +.. _expo_format: + +Expo Format +----------- + +It can be tedious to create a complex expo using code. Expo supports a +data-driven approach, where the expo description is in a devicetree file. This +makes it easier and faster to create and edit the description. An expo builder +is provided to convert this format into an expo structure. + +Layout of the expo scenes is handled automatically, based on a set of simple +rules. The :doc:`../usage/cmd/cedit` can be used to load a configuration +and create an expo from it. + +Top-level node +~~~~~~~~~~~~~~ + +The top-level node has the following properties: + +dynamic-start + type: u32, optional + + Specifies the start of the dynamically allocated objects. This results in + a call to expo_set_dynamic_start(). + +The top-level node has the following subnodes: + +scenes + Specifies the scenes in the expo, each one being a subnode + +strings + Specifies the strings in the expo, each one being a subnode + +`scenes` node +~~~~~~~~~~~~~ + +Contains a list of scene subnodes. The name of each subnode is passed as the +name to `scene_new()`. + +`strings` node +~~~~~~~~~~~~~~ + +Contains a list of string subnodes. The name of each subnode is ignored. + +`strings` subnodes +~~~~~~~~~~~~~~~~~~ + +Each subnode defines a string which can be used by scenes and objects. Each +string has an ID number which is used to refer to it. + +The `strings` subnodes have the following properties: + +id + type: u32, required + + Specifies the ID number for the string. + +value: + type: string, required + + Specifies the string text. For now only a single value is supported. Future + work may add support for multiple languages by using a value for each + language. + +Scene nodes (`scenes` subnodes) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Each subnode of the `scenes` node contains a scene description. + +Most properties can use either a string or a string ID. For example, a `title` +property can be used to provide the title for a menu; alternatively a `title-id` +property can provide the string ID of the title. If both are present, the +ID takes preference, except that if a string with that ID does not exist, it +falls back to using the string from the property (`title` in this example). The +description below shows these are alternative properties with the same +description. + +The scene nodes have the following properties: + +id + type: u32, required + + Specifies the ID number for the string. + +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 ----------------- @@ -166,12 +451,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 multiple menus and - radio/option widgets +- Support for more widgets, e.g. text, numeric, radio/option - Mouse support - Integrate Nuklear, NxWidgets or some other library for a richer UI - Optimise rendering by only updating the display with changes since last render @@ -179,10 +462,10 @@ 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 +- 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 00000000000..8e1110c7c77 --- /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 388e59f1733..f2ffd2787aa 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/drivers/core/ofnode.c b/drivers/core/ofnode.c index ec574c44607..8df16e56af5 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) { @@ -67,7 +82,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(); } } @@ -77,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) { @@ -133,6 +154,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; diff --git a/drivers/gpio/gpio-uclass.c b/drivers/gpio/gpio-uclass.c index 712119c3415..31027f3d990 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; diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c index 6b5390136a7..0f9bb49e44f 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) @@ -154,33 +187,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; } @@ -256,7 +289,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; @@ -317,52 +350,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 (IS_ENABLED(CONFIG_VIDEO_BPP16)) { + for (i = 0; i < width; i++) { + int val = *bits; + int out; - if (vid_priv->colour_bg) - val = 255 - val; - out = val >> 3 | - (val >> 2) << 5 | - (val >> 3) << 11; - if (vid_priv->colour_fg) - *dst++ |= out; - else - *dst++ &= out; - bits++; + if (vid_priv->colour_bg) + val = 255 - val; + out = val >> 3 | + (val >> 2) << 5 | + (val >> 3) << 11; + if (vid_priv->colour_fg) + *dst++ |= out; + else + *dst++ &= out; + bits++; + } + end = dst; } - end = dst; break; } -#endif -#ifdef CONFIG_VIDEO_BPP32 case VIDEO_BPP32: { u32 *dst = (u32 *)line + xoff; int i; - for (i = 0; i < width; i++) { - int val = *bits; - int out; + if (IS_ENABLED(CONFIG_VIDEO_BPP32)) { + for (i = 0; i < width; i++) { + int val = *bits; + int out; - if (vid_priv->colour_bg) - val = 255 - val; - out = val | val << 8 | val << 16; - if (vid_priv->colour_fg) - *dst++ |= out; - else - *dst++ &= out; - bits++; + if (vid_priv->colour_bg) + val = 255 - val; + out = val | val << 8 | val << 16; + if (vid_priv->colour_fg) + *dst++ |= out; + else + *dst++ &= out; + bits++; + } + end = dst; } - end = dst; break; } -#endif default: free(data); return -ENOSYS; @@ -378,72 +411,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) { -#ifdef CONFIG_VIDEO_BPP8 - case VIDEO_BPP8: { - uint8_t *dst = line; - - for (i = 0; i < pixels; i++) - *dst++ = clr; - break; - } -#endif -#ifdef CONFIG_VIDEO_BPP16 - case VIDEO_BPP16: { - uint16_t *dst = line; - - for (i = 0; i < pixels; i++) - *dst++ = clr; - break; - } -#endif -#ifdef CONFIG_VIDEO_BPP32 - case VIDEO_BPP32: { - uint32_t *dst = line; - - for (i = 0; i < pixels; i++) - *dst++ = clr; - break; - } -#endif - default: - return -ENOSYS; - } - line += vid_priv->line_length; - } - ret = vidconsole_sync_copy(dev, start, line); - if (ret) - return ret; - - return 0; -} - /** * console_truetype_backspace() - Handle a backspace operation * @@ -482,9 +449,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; @@ -680,8 +647,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; @@ -719,11 +686,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); @@ -775,6 +801,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/stb_truetype.h b/drivers/video/stb_truetype.h index 438bfce468c..c6973bb353c 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 = 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 rectangle covered from sy0..y_crossing + area = sign * (y_crossing-sy0); + + // 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(STBTT_fabs(area) <= 1.01f); // accumulated error from area += step unless we round step down + STBTT_assert(sy1 > y_final-0.01f); - STBTT_assert(fabs(area) <= 1.01f); - - 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; } -// 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 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) { - int i,j,k, return_value = 1; + 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, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + 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/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c index a21fde0e1d4..05f93047809 100644 --- a/drivers/video/vidconsole-uclass.c +++ b/drivers/video/vidconsole-uclass.c @@ -596,6 +596,48 @@ 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) +{ + struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent); + + old->colour_fg = vid_priv->colour_fg; + old->colour_bg = vid_priv->colour_bg; + + vid_priv->colour_fg = video_index_to_colour(vid_priv, fg); + vid_priv->colour_bg = video_index_to_colour(vid_priv, bg); +} + +void vidconsole_pop_colour(struct udevice *dev, struct vidconsole_colour *old) +{ + struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent); + + vid_priv->colour_fg = old->colour_fg; + vid_priv->colour_bg = old->colour_bg; +} + /* Set up the number of rows and colours (rotated drivers override this) */ static int vidconsole_pre_probe(struct udevice *dev) { diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c index 1b66a8061a7..95e874b770b 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); @@ -208,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/fs/fs.c b/fs/fs.c index 8324b4a22f2..2b815b1db0f 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/dm/of.h b/include/dm/of.h index fce7cef0ff6..b1c934f610d 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/include/dm/ofnode.h b/include/dm/ofnode.h index 443db6252dd..0f38b3e736d 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/expo.h b/include/expo.h index d242f48e30c..0b1d944a169 100644 --- a/include/expo.h +++ b/include/expo.h @@ -7,22 +7,31 @@ #ifndef __SCENE_H #define __SCENE_H +#include #include struct udevice; +struct video_priv; /** * enum expoact_type - types of actions reported by the expo * * @EXPOACT_NONE: no action - * @EXPOACT_POINT: menu item was highlighted (@id indicates which) + * @EXPOACT_POINT_OBJ: object was highlighted (@id indicates which) + * @EXPOACT_POINT_ITEM: menu item was highlighted (@id indicates which) * @EXPOACT_SELECT: menu item was selected (@id indicates which) + * @EXPOACT_OPEN: menu was opened, so an item can be selected (@id indicates + * which menu object) + * @EXPOACT_CLOSE: menu was closed (@id indicates which menu object) * @EXPOACT_QUIT: request to exit the menu */ enum expoact_type { EXPOACT_NONE, - EXPOACT_POINT, + EXPOACT_POINT_OBJ, + EXPOACT_POINT_ITEM, EXPOACT_SELECT, + EXPOACT_OPEN, + EXPOACT_CLOSE, EXPOACT_QUIT, }; @@ -30,7 +39,7 @@ 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 { @@ -42,6 +51,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 * @@ -50,23 +72,29 @@ 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 * type set to EXPOACT_NONE if there is no action * @text_mode: true to use text mode for the menu (no vidconsole) + * @popup: true to use popup menus, instead of showing all items * @priv: Private data for the controller + * @theme: Information about fonts styles, etc. * @scene_head: List of scenes * @str_head: list of strings */ struct expo { char *name; struct udevice *display; + struct udevice *cons; uint scene_id; uint next_id; struct expo_action action; bool text_mode; + bool popup; void *priv; + struct expo_theme theme; struct list_head scene_head; struct list_head str_head; }; @@ -92,7 +120,8 @@ 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) + * @highlight_id: ID of highlighted object, if any * @sibling: Node to link this scene to its siblings * @obj_head: List of objects in the scene */ @@ -100,7 +129,8 @@ struct scene { struct expo *expo; char *name; uint id; - char *title; + uint title_id; + uint highlight_id; struct list_head sibling; struct list_head obj_head; }; @@ -120,6 +150,35 @@ 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; +}; + +/** + * enum scene_obj_flags_t - flags for objects + * + * @SCENEOF_HIDE: object should be hidden + * @SCENEOF_POINT: object should be highlighted + * @SCENEOF_OPEN: object should be opened (e.g. menu is opened so that an option + * can be selected) + */ +enum scene_obj_flags_t { + SCENEOF_HIDE = 1 << 0, + SCENEOF_POINT = 1 << 1, + SCENEOF_OPEN = 1 << 2, +}; + /** * struct scene_obj - information about an object in a scene * @@ -127,9 +186,8 @@ 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 - * @hide: true if the object should be hidden + * @dim: Dimensions for this object + * @flags: Flags for this object * @sibling: Node to link this object to its siblings */ struct scene_obj { @@ -137,9 +195,8 @@ struct scene_obj { char *name; uint id; enum scene_obj_t type; - int x; - int y; - bool hide; + struct scene_dim dim; + int flags; struct list_head sibling; }; @@ -255,6 +312,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 * @@ -284,6 +360,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 * @@ -293,6 +379,14 @@ int expo_set_display(struct expo *exp, struct udevice *dev); */ 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 * @@ -304,12 +398,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 @@ -334,14 +428,44 @@ 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 * * @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 @@ -425,6 +549,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 * @@ -519,4 +654,46 @@ 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); + +/** + * expo_build() - Build an expo from an FDT description + * + * Build a complete expo from a description in the provided devicetree. + * + * See doc/developer/expo.rst for a description of the format + * + * @root: Root node for expo description + * @expp: Returns the new expo + * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format + * error, -ENOENT if there is a references to a non-existent string + */ +int expo_build(ofnode root, struct expo **expp); + +/** + * cedit_arange() - Arrange objects in a configuration-editor scene + * + * @exp: Expo to update + * @vid_priv: Private info of the video device + * @scene_id: scene ID to arrange + * Returns: 0 if OK, -ve on error + */ +int cedit_arange(struct expo *exp, struct video_priv *vid_priv, uint scene_id); + +/** + * cedit_run() - Run a configuration editor + * + * This accepts input until the user quits with Escape + * + * @exp: Expo to use + * Returns: 0 if OK, -ve on error + */ +int cedit_run(struct expo *exp); + #endif /*__SCENE_H */ diff --git a/include/fs.h b/include/fs.h index 8370d88cb20..e341a0ed01b 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 */ diff --git a/include/log.h b/include/log.h index 3bab40b6171..6e84f080ef3 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 */ diff --git a/include/of_live.h b/include/of_live.h index f59d6af3350..05e86ac06b1 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/include/test/cedit-test.h b/include/test/cedit-test.h new file mode 100644 index 00000000000..349df75b16d --- /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/include/test/ut.h b/include/test/ut.h index dddf9ad241f..ea6ee95d734 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; \ }) diff --git a/include/video.h b/include/video.h index 03434a81234..e98d0f9c895 100644 --- a/include/video.h +++ b/include/video.h @@ -163,11 +163,11 @@ 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 - * @idx color index + * @priv private data of the video device (UCLASS_VIDEO) + * @idx color index (e.g. VID_YELLOW) * Return: color value */ -u32 video_index_to_colour(struct video_priv *priv, unsigned int idx); +u32 video_index_to_colour(struct video_priv *priv, enum colour_idx idx); /** * video_reserve() - Reserve frame-buffer memory for video devices @@ -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 * diff --git a/include/video_console.h b/include/video_console.h index 3db9a7e1fb9..2694e44f6ec 100644 --- a/include/video_console.h +++ b/include/video_console.h @@ -71,6 +71,38 @@ 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_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 * @@ -178,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 */ @@ -204,6 +250,38 @@ 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 + * + * @dev: Device to adjust + * @fg: Foreground colour to select + * @bg: Background colour to select + * @old: Place to store the current colour, so it can be restored + */ +void vidconsole_push_colour(struct udevice *dev, enum colour_idx fg, + enum colour_idx bg, struct vidconsole_colour *old); + +/** + * vidconsole_pop_colour() - Restore the original colour + * + * @dev: Device to adjust + * @old: Old colour to be restored + */ +void vidconsole_pop_colour(struct udevice *dev, struct vidconsole_colour *old); + /** * vidconsole_putc_xy() - write a single character to a position * diff --git a/lib/of_live.c b/lib/of_live.c index 1b5964d09a9..25f7af61061 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); @@ -327,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/boot/expo.c b/test/boot/expo.c index 7104dff05e8..3898f853a75 100644 --- a/test/boot/expo.c +++ b/test/boot/expo.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -13,6 +14,7 @@ #include #include #include "bootstd_common.h" +#include #include "../../boot/scene_internal.h" enum { @@ -28,6 +30,8 @@ enum { OBJ_MENU_TITLE, /* strings */ + STR_SCENE_TITLE, + STR_TEXT, STR_TEXT2, STR_MENU_TITLE, @@ -120,7 +124,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 +145,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); @@ -225,7 +228,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; @@ -235,6 +238,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; @@ -249,8 +253,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)); @@ -272,6 +276,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)); @@ -306,8 +315,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); @@ -347,29 +356,31 @@ 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 */ - ut_asserteq(menu->obj.x, tit->obj.x); - ut_asserteq(menu->obj.y, tit->obj.y); + ut_asserteq(menu->obj.dim.x, tit->obj.dim.x); + ut_asserteq(menu->obj.dim.y, tit->obj.dim.y); /* the first item should be next */ - ut_asserteq(menu->obj.x, name1->obj.x); - ut_asserteq(menu->obj.y + 32, name1->obj.y); + ut_asserteq(menu->obj.dim.x, name1->obj.dim.x); + ut_asserteq(menu->obj.dim.y + 32, name1->obj.dim.y); - ut_asserteq(menu->obj.x + 230, key1->obj.x); - ut_asserteq(menu->obj.y + 32, key1->obj.y); + ut_asserteq(menu->obj.dim.x + 230, key1->obj.dim.x); + ut_asserteq(menu->obj.dim.y + 32, key1->obj.dim.y); - ut_asserteq(menu->obj.x + 200, ptr->obj.x); - ut_asserteq(menu->obj.y + 32, ptr->obj.y); + ut_asserteq(menu->obj.dim.x + 200, ptr->obj.dim.x); + ut_asserteq(menu->obj.dim.y + 32, ptr->obj.dim.y); - ut_asserteq(menu->obj.x + 280, desc1->obj.x); - ut_asserteq(menu->obj.y + 32, desc1->obj.y); + ut_asserteq(menu->obj.dim.x + 280, desc1->obj.dim.x); + ut_asserteq(menu->obj.dim.y + 32, desc1->obj.dim.y); - ut_asserteq(-4, prev1->obj.x); - ut_asserteq(menu->obj.y + 32, prev1->obj.y); - ut_asserteq(false, prev1->obj.hide); + ut_asserteq(-4, prev1->obj.dim.x); + ut_asserteq(menu->obj.dim.y + 32, prev1->obj.dim.y); + ut_asserteq(true, prev1->obj.flags & SCENEOF_HIDE); expo_destroy(exp); @@ -470,6 +481,48 @@ 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)); @@ -479,16 +532,16 @@ 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)); /* make sure only the preview for the second item is shown */ obj = scene_obj_find(scn, ITEM1_PREVIEW, SCENEOBJT_NONE); - ut_asserteq(true, obj->hide); + ut_asserteq(true, obj->flags & SCENEOF_HIDE); obj = scene_obj_find(scn, ITEM2_PREVIEW, SCENEOBJT_NONE); - ut_asserteq(false, obj->hide); + ut_asserteq(false, obj->flags & SCENEOF_HIDE); /* select it */ ut_assertok(expo_send_key(exp, BKEY_SELECT)); @@ -504,7 +557,7 @@ 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"); @@ -519,7 +572,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)); @@ -537,3 +590,125 @@ 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); + +/* 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 00000000000..55d5c910dd5 --- /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/cmd/bdinfo.c b/test/cmd/bdinfo.c index 9068df79c4f..cddf1a46d49 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) { diff --git a/test/dm/ofnode.c b/test/dm/ofnode.c index 473a8cef578..6fbebc7da08 100644 --- a/test/dm/ofnode.c +++ b/test/dm/ofnode.c @@ -1240,3 +1240,48 @@ 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); + +/* check that it is possible to load an arbitrary livetree */ +static int dm_test_livetree_ensure(struct unit_test_state *uts) +{ + oftree tree; + ofnode node; + + /* read from other.dtb */ + ut_assertok(test_load_other_fdt(uts)); + tree = oftree_from_fdt(uts->other_fdt); + ut_assert(oftree_valid(tree)); + node = oftree_path(tree, "/node/subnode"); + ut_assert(ofnode_valid(node)); + ut_asserteq_str("sandbox-other2", + ofnode_read_string(node, "compatible")); + + return 0; +} +DM_TEST(dm_test_livetree_ensure, 0); diff --git a/test/dm/video.c b/test/dm/video.c index 30778157d94..0534ee93a3d 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(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(35030, 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(29018, compress_frame_buffer(uts, dev)); + ut_asserteq(29471, compress_frame_buffer(uts, dev)); return 0; } diff --git a/test/py/tests/test_ut.py b/test/py/tests/test_ut.py index 0b45863b438..aa1d477cd56 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/binman/control.py b/tools/binman/control.py index 68597c4e779..7e2dd3541b9 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 diff --git a/tools/expo.py b/tools/expo.py new file mode 100755 index 00000000000..c6eb87aec73 --- /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()