diff --git a/CHANGELOG.md b/CHANGELOG.md index 58bea995f..0bdda3592 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file. ### Breaking Changed ### Changed +- ESP32 LVGL library from v8.3.10 to v8.3.11 (no functional change) ### Fixed diff --git a/lib/libesp32_lvgl/lv_binding_berry/generate/be_lv_c_mapping.h b/lib/libesp32_lvgl/lv_binding_berry/generate/be_lv_c_mapping.h index ea0556f2c..18ca143e1 100644 --- a/lib/libesp32_lvgl/lv_binding_berry/generate/be_lv_c_mapping.h +++ b/lib/libesp32_lvgl/lv_binding_berry/generate/be_lv_c_mapping.h @@ -968,12 +968,14 @@ const be_ntv_func_def_t lv_switch_func[] = { const be_ntv_func_def_t lv_table_func[] = { { "add_cell_ctrl", { (const void*) &lv_table_add_cell_ctrl, "", "(lv.lv_obj)ii(lv.lv_table_cell_ctrl)" } }, { "clear_cell_ctrl", { (const void*) &lv_table_clear_cell_ctrl, "", "(lv.lv_obj)ii(lv.lv_table_cell_ctrl)" } }, + { "get_cell_user_data", { (const void*) &lv_table_get_cell_user_data, ".", "(lv.lv_obj)ii" } }, { "get_cell_value", { (const void*) &lv_table_get_cell_value, "s", "(lv.lv_obj)ii" } }, { "get_col_cnt", { (const void*) &lv_table_get_col_cnt, "i", "(lv.lv_obj)" } }, { "get_col_width", { (const void*) &lv_table_get_col_width, "i", "(lv.lv_obj)i" } }, { "get_row_cnt", { (const void*) &lv_table_get_row_cnt, "i", "(lv.lv_obj)" } }, { "get_selected_cell", { (const void*) &lv_table_get_selected_cell, "", "(lv.lv_obj)(lv.uint16)(lv.uint16)" } }, { "has_cell_ctrl", { (const void*) &lv_table_has_cell_ctrl, "b", "(lv.lv_obj)ii(lv.lv_table_cell_ctrl)" } }, + { "set_cell_user_data", { (const void*) &lv_table_set_cell_user_data, "", "(lv.lv_obj)ii." } }, { "set_cell_value", { (const void*) &lv_table_set_cell_value, "", "(lv.lv_obj)iis" } }, { "set_cell_value_fmt", { (const void*) &lv_table_set_cell_value_fmt, "", "(lv.lv_obj)iis[......]" } }, { "set_col_cnt", { (const void*) &lv_table_set_col_cnt, "", "(lv.lv_obj)i" } }, diff --git a/lib/libesp32_lvgl/lv_binding_berry/generate/be_lvgl_widgets_lib.c b/lib/libesp32_lvgl/lv_binding_berry/generate/be_lvgl_widgets_lib.c index 3df0ce70b..614e7f985 100644 --- a/lib/libesp32_lvgl/lv_binding_berry/generate/be_lvgl_widgets_lib.c +++ b/lib/libesp32_lvgl/lv_binding_berry/generate/be_lvgl_widgets_lib.c @@ -907,12 +907,14 @@ extern int lvbe_table_set_col_cnt(bvm *vm); extern int lvbe_table_set_col_width(bvm *vm); extern int lvbe_table_add_cell_ctrl(bvm *vm); extern int lvbe_table_clear_cell_ctrl(bvm *vm); +extern int lvbe_table_set_cell_user_data(bvm *vm); extern int lvbe_table_get_cell_value(bvm *vm); extern int lvbe_table_get_row_cnt(bvm *vm); extern int lvbe_table_get_col_cnt(bvm *vm); extern int lvbe_table_get_col_width(bvm *vm); extern int lvbe_table_has_cell_ctrl(bvm *vm); extern int lvbe_table_get_selected_cell(bvm *vm); +extern int lvbe_table_get_cell_user_data(bvm *vm); /* `lv_textarea` external functions definitions */ extern int lvbe_textarea_create(bvm *vm); diff --git a/lib/libesp32_lvgl/lv_binding_berry/mapping/lv_funcs.h b/lib/libesp32_lvgl/lv_binding_berry/mapping/lv_funcs.h index 39fbddc61..1a031f381 100644 --- a/lib/libesp32_lvgl/lv_binding_berry/mapping/lv_funcs.h +++ b/lib/libesp32_lvgl/lv_binding_berry/mapping/lv_funcs.h @@ -1130,12 +1130,14 @@ void lv_table_set_col_cnt(lv_obj_t * obj, uint16_t col_cnt) void lv_table_set_col_width(lv_obj_t * obj, uint16_t col_id, lv_coord_t w) void lv_table_add_cell_ctrl(lv_obj_t * obj, uint16_t row, uint16_t col, lv_table_cell_ctrl_t ctrl) void lv_table_clear_cell_ctrl(lv_obj_t * obj, uint16_t row, uint16_t col, lv_table_cell_ctrl_t ctrl) +void lv_table_set_cell_user_data(lv_obj_t * obj, uint16_t row, uint16_t col, void * user_data) const char * lv_table_get_cell_value(lv_obj_t * obj, uint16_t row, uint16_t col) uint16_t lv_table_get_row_cnt(lv_obj_t * obj) uint16_t lv_table_get_col_cnt(lv_obj_t * obj) lv_coord_t lv_table_get_col_width(lv_obj_t * obj, uint16_t col) bool lv_table_has_cell_ctrl(lv_obj_t * obj, uint16_t row, uint16_t col, lv_table_cell_ctrl_t ctrl) void lv_table_get_selected_cell(lv_obj_t * obj, uint16_t * row, uint16_t * col) +void * lv_table_get_cell_user_data(lv_obj_t * obj, uint16_t row, uint16_t col) // ../../lvgl/src/widgets/lv_textarea.h lv_obj_t * lv_textarea_create(lv_obj_t * parent) diff --git a/lib/libesp32_lvgl/lv_binding_berry/src/be_lvgl_ctypes_definitions.c b/lib/libesp32_lvgl/lv_binding_berry/src/be_lvgl_ctypes_definitions.c index 35b3e2ce5..7df88b6cd 100644 --- a/lib/libesp32_lvgl/lv_binding_berry/src/be_lvgl_ctypes_definitions.c +++ b/lib/libesp32_lvgl/lv_binding_berry/src/be_lvgl_ctypes_definitions.c @@ -432,11 +432,10 @@ const be_ctypes_structure_t be_lv_draw_mask_saved = { const be_ctypes_structure_t be_lv_meter_scale = { 36, /* size in bytes */ - 15, /* number of elements */ + 14, /* number of elements */ be_ctypes_instance_mappings, - (const be_ctypes_structure_item_t[15]) { + (const be_ctypes_structure_item_t[14]) { { "angle_range", 30, 0, 0, ctypes_u16, 0 }, - { "label_color", 18, 0, 0, ctypes_i16, 0 }, { "label_gap", 16, 0, 0, ctypes_i16, 0 }, { "max", 24, 0, 0, ctypes_i32, 0 }, { "min", 20, 0, 0, ctypes_i32, 0 }, diff --git a/lib/libesp32_lvgl/lv_binding_berry/src/embedded/lvgl_ctypes.py b/lib/libesp32_lvgl/lv_binding_berry/src/embedded/lvgl_ctypes.py index 1eddbcf10..a6649f7fe 100644 --- a/lib/libesp32_lvgl/lv_binding_berry/src/embedded/lvgl_ctypes.py +++ b/lib/libesp32_lvgl/lv_binding_berry/src/embedded/lvgl_ctypes.py @@ -353,7 +353,6 @@ lv_meter_scale = [ # valid LVGL8.3 [uint16_t, "tick_major_width"], [int16_t, "label_gap"], - [int16_t, "label_color"], [int32_t, "min"], [int32_t, "max"], diff --git a/lib/libesp32_lvgl/lvgl/library.json b/lib/libesp32_lvgl/lvgl/library.json index 5a1f9d7be..068fc5c47 100644 --- a/lib/libesp32_lvgl/lvgl/library.json +++ b/lib/libesp32_lvgl/lvgl/library.json @@ -1,6 +1,6 @@ { "name": "lvgl", - "version": "8.3.10", + "version": "8.3.11", "keywords": "graphics, gui, embedded, tft, lvgl", "description": "Graphics library to create embedded GUI with easy-to-use graphical elements, beautiful visual effects and low memory footprint. It offers anti-aliasing, opacity, and animations using only one frame buffer.", "repository": { diff --git a/lib/libesp32_lvgl/lvgl/library.properties b/lib/libesp32_lvgl/lvgl/library.properties index 87e1cbc89..bc7411552 100644 --- a/lib/libesp32_lvgl/lvgl/library.properties +++ b/lib/libesp32_lvgl/lvgl/library.properties @@ -1,5 +1,5 @@ name=lvgl -version=8.3.10 +version=8.3.11 author=kisvegabor maintainer=kisvegabor,embeddedt,pete-pjb sentence=Full-featured Graphics Library for Embedded Systems diff --git a/lib/libesp32_lvgl/lvgl/lv_conf_template.h b/lib/libesp32_lvgl/lvgl/lv_conf_template.h index 66535138d..bed77fb8f 100644 --- a/lib/libesp32_lvgl/lvgl/lv_conf_template.h +++ b/lib/libesp32_lvgl/lvgl/lv_conf_template.h @@ -1,6 +1,6 @@ /** * @file lv_conf.h - * Configuration file for v8.3.9 + * Configuration file for v8.3.11 */ /* @@ -639,6 +639,13 @@ #define LV_FS_FATFS_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/ #endif +/*API for LittleFS (library needs to be added separately). Uses lfs_file_open, lfs_file_read, etc*/ +#define LV_USE_FS_LITTLEFS 0 +#if LV_USE_FS_LITTLEFS + #define LV_FS_LITTLEFS_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/ + #define LV_FS_LITTLEFS_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/ +#endif + /*PNG decoder library*/ #define LV_USE_PNG 0 @@ -672,6 +679,13 @@ #endif #endif +/*Tiny TTF library*/ +#define LV_USE_TINY_TTF 0 +#if LV_USE_TINY_TTF + /*Load TTF data from files*/ + #define LV_TINY_TTF_FILE_SUPPORT 0 +#endif + /*Rlottie library*/ #define LV_USE_RLOTTIE 0 diff --git a/lib/libesp32_lvgl/lvgl/lvgl.h b/lib/libesp32_lvgl/lvgl/lvgl.h index c37b2ee8d..47700d4e6 100644 --- a/lib/libesp32_lvgl/lvgl/lvgl.h +++ b/lib/libesp32_lvgl/lvgl/lvgl.h @@ -15,7 +15,7 @@ extern "C" { ***************************/ #define LVGL_VERSION_MAJOR 8 #define LVGL_VERSION_MINOR 3 -#define LVGL_VERSION_PATCH 10 +#define LVGL_VERSION_PATCH 11 #define LVGL_VERSION_INFO "" /********************* diff --git a/lib/libesp32_lvgl/lvgl/src/core/lv_disp.c b/lib/libesp32_lvgl/lvgl/src/core/lv_disp.c index dd0f7898e..cacade51d 100644 --- a/lib/libesp32_lvgl/lvgl/src/core/lv_disp.c +++ b/lib/libesp32_lvgl/lvgl/src/core/lv_disp.c @@ -224,9 +224,13 @@ void lv_scr_load_anim(lv_obj_t * new_scr, lv_scr_load_anim_t anim_type, uint32_t lv_disp_t * d = lv_obj_get_disp(new_scr); lv_obj_t * act_scr = lv_scr_act(); + if(act_scr == new_scr || d->scr_to_load == new_scr) { + return; + } + /*If an other screen load animation is in progress *make target screen loaded immediately. */ - if(d->scr_to_load && act_scr != d->scr_to_load) { + if(d->scr_to_load) { scr_load_internal(d->scr_to_load); lv_anim_del(d->scr_to_load, NULL); lv_obj_set_pos(d->scr_to_load, 0, 0); @@ -258,7 +262,6 @@ void lv_scr_load_anim(lv_obj_t * new_scr, lv_scr_load_anim_t anim_type, uint32_t lv_obj_remove_local_style_prop(new_scr, LV_STYLE_OPA, 0); lv_obj_remove_local_style_prop(lv_scr_act(), LV_STYLE_OPA, 0); - /*Shortcut for immediate load*/ if(time == 0 && delay == 0) { scr_load_internal(new_scr); @@ -478,6 +481,7 @@ static void scr_load_internal(lv_obj_t * scr) if(d->act_scr) lv_event_send(scr, LV_EVENT_SCREEN_LOAD_START, NULL); d->act_scr = scr; + d->scr_to_load = NULL; if(d->act_scr) lv_event_send(scr, LV_EVENT_SCREEN_LOADED, NULL); if(d->act_scr) lv_event_send(old_scr, LV_EVENT_SCREEN_UNLOADED, NULL); diff --git a/lib/libesp32_lvgl/lvgl/src/core/lv_obj.c b/lib/libesp32_lvgl/lvgl/src/core/lv_obj.c index 8d814d1ab..f0f4d5500 100644 --- a/lib/libesp32_lvgl/lvgl/src/core/lv_obj.c +++ b/lib/libesp32_lvgl/lvgl/src/core/lv_obj.c @@ -149,9 +149,9 @@ void lv_init(void) lv_img_cache_set_size(LV_IMG_CACHE_DEF_SIZE); #endif /*Test if the IDE has UTF-8 encoding*/ - char * txt = "Á"; + const char * txt = "Á"; - uint8_t * txt_u8 = (uint8_t *)txt; + const uint8_t * txt_u8 = (uint8_t *)txt; if(txt_u8[0] != 0xc3 || txt_u8[1] != 0x81 || txt_u8[2] != 0x00) { LV_LOG_WARN("The strings have no UTF-8 encoding. Non-ASCII characters won't be displayed."); } @@ -862,6 +862,10 @@ static void lv_obj_event(const lv_obj_class_t * class_p, lv_event_t * e) lv_obj_mark_layout_as_dirty(obj); } } + else if(code == LV_EVENT_CHILD_DELETED) { + obj->readjust_scroll_after_layout = 1; + lv_obj_mark_layout_as_dirty(obj); + } else if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) { lv_coord_t d = lv_obj_calculate_ext_draw_size(obj, LV_PART_MAIN); lv_event_set_ext_draw_size(e, d); diff --git a/lib/libesp32_lvgl/lvgl/src/core/lv_obj.h b/lib/libesp32_lvgl/lvgl/src/core/lv_obj.h index a6a9ac38c..830d122e4 100644 --- a/lib/libesp32_lvgl/lvgl/src/core/lv_obj.h +++ b/lib/libesp32_lvgl/lvgl/src/core/lv_obj.h @@ -183,6 +183,7 @@ typedef struct _lv_obj_t { lv_obj_flag_t flags; lv_state_t state; uint16_t layout_inv : 1; + uint16_t readjust_scroll_after_layout : 1; uint16_t scr_layout_inv : 1; uint16_t skip_trans : 1; uint16_t style_cnt : 6; diff --git a/lib/libesp32_lvgl/lvgl/src/core/lv_obj_draw.c b/lib/libesp32_lvgl/lvgl/src/core/lv_obj_draw.c index e0b95c523..d906126c3 100644 --- a/lib/libesp32_lvgl/lvgl/src/core/lv_obj_draw.c +++ b/lib/libesp32_lvgl/lvgl/src/core/lv_obj_draw.c @@ -179,12 +179,11 @@ void lv_obj_init_draw_rect_dsc(lv_obj_t * obj, uint32_t part, lv_draw_rect_dsc_t } } #endif - if(opa < LV_OPA_MAX) { - draw_dsc->bg_opa = (opa * draw_dsc->shadow_opa) >> 8; - draw_dsc->bg_img_opa = (opa * draw_dsc->shadow_opa) >> 8; - draw_dsc->border_opa = (opa * draw_dsc->shadow_opa) >> 8; - draw_dsc->outline_opa = (opa * draw_dsc->shadow_opa) >> 8; + draw_dsc->bg_opa = (opa * draw_dsc->bg_opa) >> 8; + draw_dsc->bg_img_opa = (opa * draw_dsc->bg_img_opa) >> 8; + draw_dsc->border_opa = (opa * draw_dsc->border_opa) >> 8; + draw_dsc->outline_opa = (opa * draw_dsc->outline_opa) >> 8; draw_dsc->shadow_opa = (opa * draw_dsc->shadow_opa) >> 8; } } diff --git a/lib/libesp32_lvgl/lvgl/src/core/lv_obj_pos.c b/lib/libesp32_lvgl/lvgl/src/core/lv_obj_pos.c index 5fc7db9ee..c50ea3a3c 100644 --- a/lib/libesp32_lvgl/lvgl/src/core/lv_obj_pos.c +++ b/lib/libesp32_lvgl/lvgl/src/core/lv_obj_pos.c @@ -200,7 +200,7 @@ bool lv_obj_refr_size(lv_obj_t * obj) /*Invalidate the new area*/ lv_obj_invalidate(obj); - lv_obj_readjust_scroll(obj, LV_ANIM_OFF); + obj->readjust_scroll_after_layout = 1; /*If the object was out of the parent invalidate the new scrollbar area too. *If it wasn't out of the parent but out now, also invalidate the scrollbars*/ @@ -1137,20 +1137,24 @@ static void layout_update_core(lv_obj_t * obj) layout_update_core(child); } - if(obj->layout_inv == 0) return; + if(obj->layout_inv) { + obj->layout_inv = 0; + lv_obj_refr_size(obj); + lv_obj_refr_pos(obj); - obj->layout_inv = 0; - - lv_obj_refr_size(obj); - lv_obj_refr_pos(obj); - - if(child_cnt > 0) { - uint32_t layout_id = lv_obj_get_style_layout(obj, LV_PART_MAIN); - if(layout_id > 0 && layout_id <= layout_cnt) { - void * user_data = LV_GC_ROOT(_lv_layout_list)[layout_id - 1].user_data; - LV_GC_ROOT(_lv_layout_list)[layout_id - 1].cb(obj, user_data); + if(child_cnt > 0) { + uint32_t layout_id = lv_obj_get_style_layout(obj, LV_PART_MAIN); + if(layout_id > 0 && layout_id <= layout_cnt) { + void * user_data = LV_GC_ROOT(_lv_layout_list)[layout_id - 1].user_data; + LV_GC_ROOT(_lv_layout_list)[layout_id - 1].cb(obj, user_data); + } } } + + if(obj->readjust_scroll_after_layout) { + obj->readjust_scroll_after_layout = 0; + lv_obj_readjust_scroll(obj, LV_ANIM_OFF); + } } static void transform_point(const lv_obj_t * obj, lv_point_t * p, bool inv) diff --git a/lib/libesp32_lvgl/lvgl/src/core/lv_obj_scroll.h b/lib/libesp32_lvgl/lvgl/src/core/lv_obj_scroll.h index e1da245b7..459389e3f 100644 --- a/lib/libesp32_lvgl/lvgl/src/core/lv_obj_scroll.h +++ b/lib/libesp32_lvgl/lvgl/src/core/lv_obj_scroll.h @@ -290,7 +290,7 @@ void lv_obj_get_scrollbar_area(struct _lv_obj_t * obj, lv_area_t * hor, lv_area_ void lv_obj_scrollbar_invalidate(struct _lv_obj_t * obj); /** - * Checked if the content is scrolled "in" and adjusts it to a normal position. + * Checks if the content is scrolled "in" and adjusts it to a normal position. * @param obj pointer to an object * @param anim_en LV_ANIM_ON/OFF */ diff --git a/lib/libesp32_lvgl/lvgl/src/core/lv_obj_style_gen.c b/lib/libesp32_lvgl/lvgl/src/core/lv_obj_style_gen.c index a9635f473..81358ab9e 100644 --- a/lib/libesp32_lvgl/lvgl/src/core/lv_obj_style_gen.c +++ b/lib/libesp32_lvgl/lvgl/src/core/lv_obj_style_gen.c @@ -608,8 +608,7 @@ void lv_obj_set_style_opa_layered(struct _lv_obj_t * obj, lv_opa_t value, lv_sty lv_obj_set_local_style_prop(obj, LV_STYLE_OPA_LAYERED, v, selector); } -void lv_obj_set_style_color_filter_dsc(struct _lv_obj_t * obj, const lv_color_filter_dsc_t * value, - lv_style_selector_t selector) +void lv_obj_set_style_color_filter_dsc(struct _lv_obj_t * obj, const lv_color_filter_dsc_t * value, lv_style_selector_t selector) { lv_style_value_t v = { .ptr = value @@ -649,8 +648,7 @@ void lv_obj_set_style_anim_speed(struct _lv_obj_t * obj, uint32_t value, lv_styl lv_obj_set_local_style_prop(obj, LV_STYLE_ANIM_SPEED, v, selector); } -void lv_obj_set_style_transition(struct _lv_obj_t * obj, const lv_style_transition_dsc_t * value, - lv_style_selector_t selector) +void lv_obj_set_style_transition(struct _lv_obj_t * obj, const lv_style_transition_dsc_t * value, lv_style_selector_t selector) { lv_style_value_t v = { .ptr = value diff --git a/lib/libesp32_lvgl/lvgl/src/core/lv_obj_style_gen.h b/lib/libesp32_lvgl/lvgl/src/core/lv_obj_style_gen.h index 51d43aefc..4a135be09 100644 --- a/lib/libesp32_lvgl/lvgl/src/core/lv_obj_style_gen.h +++ b/lib/libesp32_lvgl/lvgl/src/core/lv_obj_style_gen.h @@ -162,8 +162,7 @@ static inline lv_color_t lv_obj_get_style_bg_grad_color(const struct _lv_obj_t * static inline lv_color_t lv_obj_get_style_bg_grad_color_filtered(const struct _lv_obj_t * obj, uint32_t part) { - lv_style_value_t v = _lv_obj_style_apply_color_filter(obj, part, lv_obj_get_style_prop(obj, part, - LV_STYLE_BG_GRAD_COLOR)); + lv_style_value_t v = _lv_obj_style_apply_color_filter(obj, part, lv_obj_get_style_prop(obj, part, LV_STYLE_BG_GRAD_COLOR)); return v.color; } @@ -217,8 +216,7 @@ static inline lv_color_t lv_obj_get_style_bg_img_recolor(const struct _lv_obj_t static inline lv_color_t lv_obj_get_style_bg_img_recolor_filtered(const struct _lv_obj_t * obj, uint32_t part) { - lv_style_value_t v = _lv_obj_style_apply_color_filter(obj, part, lv_obj_get_style_prop(obj, part, - LV_STYLE_BG_IMG_RECOLOR)); + lv_style_value_t v = _lv_obj_style_apply_color_filter(obj, part, lv_obj_get_style_prop(obj, part, LV_STYLE_BG_IMG_RECOLOR)); return v.color; } @@ -242,8 +240,7 @@ static inline lv_color_t lv_obj_get_style_border_color(const struct _lv_obj_t * static inline lv_color_t lv_obj_get_style_border_color_filtered(const struct _lv_obj_t * obj, uint32_t part) { - lv_style_value_t v = _lv_obj_style_apply_color_filter(obj, part, lv_obj_get_style_prop(obj, part, - LV_STYLE_BORDER_COLOR)); + lv_style_value_t v = _lv_obj_style_apply_color_filter(obj, part, lv_obj_get_style_prop(obj, part, LV_STYLE_BORDER_COLOR)); return v.color; } @@ -285,8 +282,7 @@ static inline lv_color_t lv_obj_get_style_outline_color(const struct _lv_obj_t * static inline lv_color_t lv_obj_get_style_outline_color_filtered(const struct _lv_obj_t * obj, uint32_t part) { - lv_style_value_t v = _lv_obj_style_apply_color_filter(obj, part, lv_obj_get_style_prop(obj, part, - LV_STYLE_OUTLINE_COLOR)); + lv_style_value_t v = _lv_obj_style_apply_color_filter(obj, part, lv_obj_get_style_prop(obj, part, LV_STYLE_OUTLINE_COLOR)); return v.color; } @@ -334,8 +330,7 @@ static inline lv_color_t lv_obj_get_style_shadow_color(const struct _lv_obj_t * static inline lv_color_t lv_obj_get_style_shadow_color_filtered(const struct _lv_obj_t * obj, uint32_t part) { - lv_style_value_t v = _lv_obj_style_apply_color_filter(obj, part, lv_obj_get_style_prop(obj, part, - LV_STYLE_SHADOW_COLOR)); + lv_style_value_t v = _lv_obj_style_apply_color_filter(obj, part, lv_obj_get_style_prop(obj, part, LV_STYLE_SHADOW_COLOR)); return v.color; } @@ -359,8 +354,7 @@ static inline lv_color_t lv_obj_get_style_img_recolor(const struct _lv_obj_t * o static inline lv_color_t lv_obj_get_style_img_recolor_filtered(const struct _lv_obj_t * obj, uint32_t part) { - lv_style_value_t v = _lv_obj_style_apply_color_filter(obj, part, lv_obj_get_style_prop(obj, part, - LV_STYLE_IMG_RECOLOR)); + lv_style_value_t v = _lv_obj_style_apply_color_filter(obj, part, lv_obj_get_style_prop(obj, part, LV_STYLE_IMG_RECOLOR)); return v.color; } @@ -520,8 +514,7 @@ static inline lv_opa_t lv_obj_get_style_opa_layered(const struct _lv_obj_t * obj return (lv_opa_t)v.num; } -static inline const lv_color_filter_dsc_t * lv_obj_get_style_color_filter_dsc(const struct _lv_obj_t * obj, - uint32_t part) +static inline const lv_color_filter_dsc_t * lv_obj_get_style_color_filter_dsc(const struct _lv_obj_t * obj, uint32_t part) { lv_style_value_t v = lv_obj_get_style_prop(obj, part, LV_STYLE_COLOR_FILTER_DSC); return (const lv_color_filter_dsc_t *)v.ptr; @@ -651,14 +644,12 @@ void lv_obj_set_style_radius(struct _lv_obj_t * obj, lv_coord_t value, lv_style_ void lv_obj_set_style_clip_corner(struct _lv_obj_t * obj, bool value, lv_style_selector_t selector); void lv_obj_set_style_opa(struct _lv_obj_t * obj, lv_opa_t value, lv_style_selector_t selector); void lv_obj_set_style_opa_layered(struct _lv_obj_t * obj, lv_opa_t value, lv_style_selector_t selector); -void lv_obj_set_style_color_filter_dsc(struct _lv_obj_t * obj, const lv_color_filter_dsc_t * value, - lv_style_selector_t selector); +void lv_obj_set_style_color_filter_dsc(struct _lv_obj_t * obj, const lv_color_filter_dsc_t * value, lv_style_selector_t selector); void lv_obj_set_style_color_filter_opa(struct _lv_obj_t * obj, lv_opa_t value, lv_style_selector_t selector); void lv_obj_set_style_anim(struct _lv_obj_t * obj, const lv_anim_t * value, lv_style_selector_t selector); void lv_obj_set_style_anim_time(struct _lv_obj_t * obj, uint32_t value, lv_style_selector_t selector); void lv_obj_set_style_anim_speed(struct _lv_obj_t * obj, uint32_t value, lv_style_selector_t selector); -void lv_obj_set_style_transition(struct _lv_obj_t * obj, const lv_style_transition_dsc_t * value, - lv_style_selector_t selector); +void lv_obj_set_style_transition(struct _lv_obj_t * obj, const lv_style_transition_dsc_t * value, lv_style_selector_t selector); void lv_obj_set_style_blend_mode(struct _lv_obj_t * obj, lv_blend_mode_t value, lv_style_selector_t selector); void lv_obj_set_style_layout(struct _lv_obj_t * obj, uint16_t value, lv_style_selector_t selector); void lv_obj_set_style_base_dir(struct _lv_obj_t * obj, lv_base_dir_t value, lv_style_selector_t selector); diff --git a/lib/libesp32_lvgl/lvgl/src/core/lv_obj_tree.c b/lib/libesp32_lvgl/lvgl/src/core/lv_obj_tree.c index dbd350a46..5ae629e75 100644 --- a/lib/libesp32_lvgl/lvgl/src/core/lv_obj_tree.c +++ b/lib/libesp32_lvgl/lvgl/src/core/lv_obj_tree.c @@ -49,9 +49,6 @@ void lv_obj_del(lv_obj_t * obj) lv_obj_invalidate(obj); lv_obj_t * par = lv_obj_get_parent(obj); - if(par) { - lv_obj_scrollbar_invalidate(par); - } lv_disp_t * disp = NULL; bool act_scr_del = false; @@ -65,8 +62,6 @@ void lv_obj_del(lv_obj_t * obj) /*Call the ancestor's event handler to the parent to notify it about the child delete*/ if(par) { - lv_obj_update_layout(par); - lv_obj_readjust_scroll(par, LV_ANIM_OFF); lv_obj_scrollbar_invalidate(par); lv_event_send(par, LV_EVENT_CHILD_CHANGED, NULL); lv_event_send(par, LV_EVENT_CHILD_DELETED, NULL); @@ -173,7 +168,6 @@ void lv_obj_set_parent(lv_obj_t * obj, lv_obj_t * parent) obj->parent = parent; /*Notify the original parent because one of its children is lost*/ - lv_obj_readjust_scroll(old_parent, LV_ANIM_OFF); lv_obj_scrollbar_invalidate(old_parent); lv_event_send(old_parent, LV_EVENT_CHILD_CHANGED, obj); lv_event_send(old_parent, LV_EVENT_CHILD_DELETED, NULL); diff --git a/lib/libesp32_lvgl/lvgl/src/draw/stm32_dma2d/lv_gpu_stm32_dma2d.c b/lib/libesp32_lvgl/lvgl/src/draw/stm32_dma2d/lv_gpu_stm32_dma2d.c index 715cdd2c0..84da4b69c 100644 --- a/lib/libesp32_lvgl/lvgl/src/draw/stm32_dma2d/lv_gpu_stm32_dma2d.c +++ b/lib/libesp32_lvgl/lvgl/src/draw/stm32_dma2d/lv_gpu_stm32_dma2d.c @@ -34,6 +34,19 @@ #error "Cannot use DMA2D with LV_COLOR_DEPTH other than 16 or 32" #endif +#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U) + #define LV_STM32_DMA2D_USE_M7_CACHE +#endif + +#if defined (LV_STM32_DMA2D_USE_M7_CACHE) + // Cortex-M7 DCache present + #define __lv_gpu_stm32_dma2d_clean_cache(address, offset, width, height, pixel_size) _lv_gpu_stm32_dma2d_clean_cache(address, offset, width, height, pixel_size) + #define __lv_gpu_stm32_dma2d_invalidate_cache(address, offset, width, height, pixel_size) _lv_gpu_stm32_dma2d_invalidate_cache(address, offset, width, height, pixel_size) +#else + #define __lv_gpu_stm32_dma2d_clean_cache(address, offset, width, height, pixel_size) + #define __lv_gpu_stm32_dma2d_invalidate_cache(address, offset, width, height, pixel_size) +#endif + /********************** * STATIC PROTOTYPES **********************/ @@ -61,13 +74,19 @@ LV_STM32_DMA2D_STATIC void _lv_draw_stm32_dma2d_copy_buffer(const lv_color_t * d const lv_area_t * draw_area, const lv_color_t * src_buf, lv_coord_t src_stride, const lv_point_t * src_offset); LV_STM32_DMA2D_STATIC void _lv_gpu_stm32_dma2d_await_dma_transfer_finish(lv_disp_drv_t * disp_drv); LV_STM32_DMA2D_STATIC void _lv_gpu_stm32_dma2d_start_dma_transfer(void); + +#if defined (LV_STM32_DMA2D_USE_M7_CACHE) LV_STM32_DMA2D_STATIC void _lv_gpu_stm32_dma2d_invalidate_cache(uint32_t address, lv_coord_t offset, lv_coord_t width, lv_coord_t height, uint8_t pixel_size); LV_STM32_DMA2D_STATIC void _lv_gpu_stm32_dma2d_clean_cache(uint32_t address, lv_coord_t offset, lv_coord_t width, lv_coord_t height, uint8_t pixel_size); -LV_STM32_DMA2D_STATIC bool _lv_gpu_stm32_dwt_init(void); -LV_STM32_DMA2D_STATIC void _lv_gpu_stm32_dwt_reset(void); -LV_STM32_DMA2D_STATIC uint32_t _lv_gpu_stm32_dwt_get_us(void); +#endif + +#if defined(LV_STM32_DMA2D_TEST) + LV_STM32_DMA2D_STATIC bool _lv_gpu_stm32_dwt_init(void); + LV_STM32_DMA2D_STATIC void _lv_gpu_stm32_dwt_reset(void); + LV_STM32_DMA2D_STATIC uint32_t _lv_gpu_stm32_dwt_get_us(void); +#endif static bool isDma2dInProgess = false; // indicates whether DMA2D transfer *initiated here* is in progress @@ -77,21 +96,21 @@ static bool isDma2dInProgess = false; // indicates whether DMA2D transfer *initi void lv_draw_stm32_dma2d_init(void) { // Enable DMA2D clock -#if defined(STM32F4) || defined(STM32F7) +#if defined(STM32F4) || defined(STM32F7) || defined(STM32U5) RCC->AHB1ENR |= RCC_AHB1ENR_DMA2DEN; // enable DMA2D + // wait for hardware access to complete + __asm volatile("DSB\n"); + volatile uint32_t temp = RCC->AHB1ENR; + LV_UNUSED(temp); #elif defined(STM32H7) RCC->AHB3ENR |= RCC_AHB3ENR_DMA2DEN; + // wait for hardware access to complete + __asm volatile("DSB\n"); + volatile uint32_t temp = RCC->AHB3ENR; + LV_UNUSED(temp); #else # warning "LVGL can't enable the clock of DMA2D" #endif - - // Wait for hardware access to complete - __asm volatile("DSB\n"); - - // Delay after setting peripheral clock - volatile uint32_t temp = RCC->AHB1ENR; - LV_UNUSED(temp); - // AHB master timer configuration DMA2D->AMTCR = 0; // AHB bus guaranteed dead time disabled #if defined(LV_STM32_DMA2D_TEST) @@ -206,7 +225,7 @@ static void lv_draw_stm32_dma2d_blend(lv_draw_ctx_t * draw_ctx, const lv_draw_sw // lv_coord_t draw_width = lv_area_get_width(&draw_area); // lv_coord_t draw_height = lv_area_get_height(&draw_area); // uint32_t dest_address = (uint32_t)(draw_ctx->buf + (dest_stride * draw_area.y1) + draw_area.x1); - // _lv_gpu_stm32_dma2d_clean_cache(dest_address, dest_stride - draw_width, draw_width, draw_height, sizeof(lv_color_t)); + // __lv_gpu_stm32_dma2d_clean_cache(dest_address, dest_stride - draw_width, draw_width, draw_height, sizeof(lv_color_t)); #endif } } @@ -412,7 +431,7 @@ LV_STM32_DMA2D_STATIC void _lv_draw_stm32_dma2d_blend_fill(const lv_color_t * de DMA2D->BGMAR = (uint32_t)(dest_buf + (dest_stride * draw_area->y1) + draw_area->x1); DMA2D->BGOR = dest_stride - draw_width; DMA2D->BGCOLR = 0; // used in A4 and A8 modes only - _lv_gpu_stm32_dma2d_clean_cache(DMA2D->BGMAR, DMA2D->BGOR, draw_width, draw_height, sizeof(lv_color_t)); + __lv_gpu_stm32_dma2d_clean_cache(DMA2D->BGMAR, DMA2D->BGOR, draw_width, draw_height, sizeof(lv_color_t)); DMA2D->OPFCCR = LvglColorFormat; #if defined(DMA2D_OPFCCR_RBS_Pos) @@ -500,7 +519,7 @@ LV_STM32_DMA2D_STATIC void _lv_draw_stm32_dma2d_blend_map(const lv_color_t * des DMA2D->FGMAR = ((uint32_t)src_buf) + srcBpp * ((src_stride * src_offset->y) + src_offset->x); DMA2D->FGOR = src_stride - draw_width; DMA2D->FGCOLR = 0; // used in A4 and A8 modes only - _lv_gpu_stm32_dma2d_clean_cache(DMA2D->FGMAR, DMA2D->FGOR, draw_width, draw_height, srcBpp); + __lv_gpu_stm32_dma2d_clean_cache(DMA2D->FGMAR, DMA2D->FGOR, draw_width, draw_height, srcBpp); DMA2D->OPFCCR = LvglColorFormat; #if defined(DMA2D_OPFCCR_RBS_Pos) @@ -519,7 +538,7 @@ LV_STM32_DMA2D_STATIC void _lv_draw_stm32_dma2d_blend_map(const lv_color_t * des DMA2D->BGMAR = DMA2D->OMAR; DMA2D->BGOR = DMA2D->OOR; DMA2D->BGCOLR = 0; // used in A4 and A8 modes only - _lv_gpu_stm32_dma2d_clean_cache(DMA2D->BGMAR, DMA2D->BGOR, draw_width, draw_height, sizeof(lv_color_t)); + __lv_gpu_stm32_dma2d_clean_cache(DMA2D->BGMAR, DMA2D->BGOR, draw_width, draw_height, sizeof(lv_color_t)); } // PL - pixel per lines (14 bit), NL - number of lines (16 bit) @@ -556,7 +575,7 @@ LV_STM32_DMA2D_STATIC void _lv_draw_stm32_dma2d_blend_paint(const lv_color_t * d DMA2D->FGMAR = (uint32_t)(mask_buf + (mask_stride * mask_offset->y) + mask_offset->x); DMA2D->FGOR = mask_stride - draw_width; DMA2D->FGCOLR = lv_color_to32(color) & 0x00ffffff; // swap FGCOLR R/B bits if FGPFCCR.RBS (RBS_BIT) bit is set - _lv_gpu_stm32_dma2d_clean_cache(DMA2D->FGMAR, DMA2D->FGOR, draw_width, draw_height, sizeof(lv_opa_t)); + __lv_gpu_stm32_dma2d_clean_cache(DMA2D->FGMAR, DMA2D->FGOR, draw_width, draw_height, sizeof(lv_opa_t)); DMA2D->BGPFCCR = LvglColorFormat; #if defined(DMA2D_BGPFCCR_RBS_Pos) @@ -565,7 +584,7 @@ LV_STM32_DMA2D_STATIC void _lv_draw_stm32_dma2d_blend_paint(const lv_color_t * d DMA2D->BGMAR = (uint32_t)(dest_buf + (dest_stride * draw_area->y1) + draw_area->x1); DMA2D->BGOR = dest_stride - draw_width; DMA2D->BGCOLR = 0; // used in A4 and A8 modes only - _lv_gpu_stm32_dma2d_clean_cache(DMA2D->BGMAR, DMA2D->BGOR, draw_width, draw_height, sizeof(lv_color_t)); + __lv_gpu_stm32_dma2d_clean_cache(DMA2D->BGMAR, DMA2D->BGOR, draw_width, draw_height, sizeof(lv_color_t)); DMA2D->OPFCCR = LvglColorFormat; #if defined(DMA2D_OPFCCR_RBS_Pos) @@ -602,7 +621,7 @@ LV_STM32_DMA2D_STATIC void _lv_draw_stm32_dma2d_copy_buffer(const lv_color_t * d DMA2D->FGMAR = (uint32_t)(src_buf + (src_stride * src_offset->y) + src_offset->x); DMA2D->FGOR = src_stride - draw_width; DMA2D->FGCOLR = 0; // used in A4 and A8 modes only - _lv_gpu_stm32_dma2d_clean_cache(DMA2D->FGMAR, DMA2D->FGOR, draw_width, draw_height, sizeof(lv_color_t)); + __lv_gpu_stm32_dma2d_clean_cache(DMA2D->FGMAR, DMA2D->FGOR, draw_width, draw_height, sizeof(lv_color_t)); // Note BG* registers do not need to be set up since BG is not used @@ -627,8 +646,8 @@ LV_STM32_DMA2D_STATIC void _lv_gpu_stm32_dma2d_start_dma_transfer(void) DMA2D->IFCR = 0x3FU; // trigger ISR flags reset // Note: cleaning output buffer cache is needed only when buffer may be misaligned or adjacent area may have been drawn in sw-fashion, e.g. using lv_draw_sw_blend_basic() #if LV_COLOR_DEPTH == 16 - _lv_gpu_stm32_dma2d_clean_cache(DMA2D->OMAR, DMA2D->OOR, (DMA2D->NLR & DMA2D_NLR_PL_Msk) >> DMA2D_NLR_PL_Pos, - (DMA2D->NLR & DMA2D_NLR_NL_Msk) >> DMA2D_NLR_NL_Pos, sizeof(lv_color_t)); + __lv_gpu_stm32_dma2d_clean_cache(DMA2D->OMAR, DMA2D->OOR, (DMA2D->NLR & DMA2D_NLR_PL_Msk) >> DMA2D_NLR_PL_Pos, + (DMA2D->NLR & DMA2D_NLR_NL_Msk) >> DMA2D_NLR_NL_Pos, sizeof(lv_color_t)); #endif DMA2D->CR |= DMA2D_CR_START; // Note: for some reason mask buffer gets damaged during transfer if waiting is postponed @@ -660,15 +679,16 @@ LV_STM32_DMA2D_STATIC void _lv_gpu_stm32_dma2d_await_dma_transfer_finish(lv_disp if(isDma2dInProgess) { // invalidate output buffer cached memory ONLY after DMA2D transfer - //_lv_gpu_stm32_dma2d_invalidate_cache(DMA2D->OMAR, DMA2D->OOR, (DMA2D->NLR & DMA2D_NLR_PL_Msk) >> DMA2D_NLR_PL_Pos, (DMA2D->NLR & DMA2D_NLR_NL_Msk) >> DMA2D_NLR_NL_Pos, sizeof(lv_color_t)); + //__lv_gpu_stm32_dma2d_invalidate_cache(DMA2D->OMAR, DMA2D->OOR, (DMA2D->NLR & DMA2D_NLR_PL_Msk) >> DMA2D_NLR_PL_Pos, (DMA2D->NLR & DMA2D_NLR_NL_Msk) >> DMA2D_NLR_NL_Pos, sizeof(lv_color_t)); isDma2dInProgess = false; } } +#if defined (LV_STM32_DMA2D_USE_M7_CACHE) +// Cortex-M7 DCache present LV_STM32_DMA2D_STATIC void _lv_gpu_stm32_dma2d_invalidate_cache(uint32_t address, lv_coord_t offset, lv_coord_t width, lv_coord_t height, uint8_t pixel_size) { -#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U) if(((SCB->CCR) & SCB_CCR_DC_Msk) == 0) return; // L1 data cache is disabled uint16_t stride = pixel_size * (width + offset); // in bytes uint16_t ll = pixel_size * width; // line length in bytes @@ -694,13 +714,11 @@ LV_STM32_DMA2D_STATIC void _lv_gpu_stm32_dma2d_invalidate_cache(uint32_t address __DSB(); __ISB(); -#endif } LV_STM32_DMA2D_STATIC void _lv_gpu_stm32_dma2d_clean_cache(uint32_t address, lv_coord_t offset, lv_coord_t width, lv_coord_t height, uint8_t pixel_size) { -#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U) if(((SCB->CCR) & SCB_CCR_DC_Msk) == 0) return; // L1 data cache is disabled uint16_t stride = pixel_size * (width + offset); // in bytes uint16_t ll = pixel_size * width; // line length in bytes @@ -725,9 +743,10 @@ LV_STM32_DMA2D_STATIC void _lv_gpu_stm32_dma2d_clean_cache(uint32_t address, lv_ __DSB(); __ISB(); -#endif } +#endif // LV_STM32_DMA2D_USE_M7_CACHE +#if defined(LV_STM32_DMA2D_TEST) // initialize µs timer LV_STM32_DMA2D_STATIC bool _lv_gpu_stm32_dwt_init(void) { @@ -773,5 +792,5 @@ LV_STM32_DMA2D_STATIC void _lv_gpu_stm32_dwt_reset(void) { DWT->CYCCNT = 0; } - -#endif +#endif // LV_STM32_DMA2D_TEST +#endif // LV_USE_GPU_STM32_DMA2D \ No newline at end of file diff --git a/lib/libesp32_lvgl/lvgl/src/draw/sw/lv_draw_sw_letter.c b/lib/libesp32_lvgl/lvgl/src/draw/sw/lv_draw_sw_letter.c index d0da8bcfc..ee27bc61b 100644 --- a/lib/libesp32_lvgl/lvgl/src/draw/sw/lv_draw_sw_letter.c +++ b/lib/libesp32_lvgl/lvgl/src/draw/sw/lv_draw_sw_letter.c @@ -103,7 +103,7 @@ void lv_draw_sw_letter(lv_draw_ctx_t * draw_ctx, const lv_draw_label_dsc_t * dsc if(letter >= 0x20 && letter != 0xf8ff && /*LV_SYMBOL_DUMMY*/ letter != 0x200c) { /*ZERO WIDTH NON-JOINER*/ - LV_LOG_WARN("lv_draw_letter: glyph dsc. not found for U+%" PRIX32, letter); + LV_LOG_WARN("lv_draw_letter: glyph dsc. not found for U+%" LV_PRIX32, letter); #if LV_USE_FONT_PLACEHOLDER /* draw placeholder */ diff --git a/lib/libesp32_lvgl/lvgl/src/draw/sw/lv_draw_sw_transform.c b/lib/libesp32_lvgl/lvgl/src/draw/sw/lv_draw_sw_transform.c index 204503a4f..9ead7b95c 100644 --- a/lib/libesp32_lvgl/lvgl/src/draw/sw/lv_draw_sw_transform.c +++ b/lib/libesp32_lvgl/lvgl/src/draw/sw/lv_draw_sw_transform.c @@ -273,7 +273,7 @@ static void argb_and_rgb_aa(const uint8_t * src, lv_coord_t src_w, lv_coord_t sr int32_t ys_ups_start = ys_ups; bool has_alpha; int32_t px_size; - lv_color_t ck = {0}; + lv_color_t ck = _LV_COLOR_ZERO_INITIALIZER; switch(cf) { case LV_IMG_CF_TRUE_COLOR: has_alpha = false; diff --git a/lib/libesp32_lvgl/lvgl/src/extra/libs/fsdrv/lv_fs_littlefs.c b/lib/libesp32_lvgl/lvgl/src/extra/libs/fsdrv/lv_fs_littlefs.c new file mode 100644 index 000000000..44261c31d --- /dev/null +++ b/lib/libesp32_lvgl/lvgl/src/extra/libs/fsdrv/lv_fs_littlefs.c @@ -0,0 +1,332 @@ +/** + * @file lv_fs_littlefs.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_FS_LITTLEFS +#include "lfs.h" + +/********************* + * DEFINES + *********************/ + +#if LV_FS_LITTLEFS_LETTER == '\0' + #error "LV_FS_LITTLEFS_LETTER must be an upper case ASCII letter" +#endif + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void fs_init(void); + +static void * fs_open(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode); +static lv_fs_res_t fs_close(lv_fs_drv_t * drv, void * file_p); + +static lv_fs_res_t fs_read(lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br); +static lv_fs_res_t fs_write(lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw); + +static lv_fs_res_t fs_seek(lv_fs_drv_t * drv, void * file_p, uint32_t pos, lv_fs_whence_t whence); +static lv_fs_res_t fs_tell(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p); + +static void * fs_dir_open(lv_fs_drv_t * drv, const char * path); +static lv_fs_res_t fs_dir_read(lv_fs_drv_t * drv, void * dir_p, char * fn); +static lv_fs_res_t fs_dir_close(lv_fs_drv_t * drv, void * dir_p); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void lv_fs_littlefs_init(void) +{ + /*---------------------------------------------------- + * Initialize your storage device and File System + * -------------------------------------------------*/ + fs_init(); + + /*--------------------------------------------------- + * Register the file system interface in LVGL + *--------------------------------------------------*/ + + /*Add a simple drive to open images*/ + static lv_fs_drv_t fs_drv; /*A driver descriptor*/ + lv_fs_drv_init(&fs_drv); + + /*Set up fields...*/ + fs_drv.letter = LV_FS_LITTLEFS_LETTER; + fs_drv.cache_size = LV_FS_LITTLEFS_CACHE_SIZE; + + fs_drv.open_cb = fs_open; + fs_drv.close_cb = fs_close; + fs_drv.read_cb = fs_read; + fs_drv.write_cb = fs_write; + fs_drv.seek_cb = fs_seek; + fs_drv.tell_cb = fs_tell; + + fs_drv.dir_open_cb = fs_dir_open; + fs_drv.dir_close_cb = fs_dir_close; + fs_drv.dir_read_cb = fs_dir_read; + + /*#if LV_USE_USER_DATA*/ + fs_drv.user_data = NULL; + /*#endif*/ + + lv_fs_drv_register(&fs_drv); +} + +/** + * Convenience function to attach registered driver to lfs_t structure by driver-label + * @param label the label assigned to the driver when it was registered + * @param lfs_p the pointer to the lfs_t structure initialized by external code/library + * @return pointer to a driver descriptor or NULL on error + */ +lv_fs_drv_t * lv_fs_littlefs_set_driver(char label, void * lfs_p) +{ + lv_fs_drv_t * drv_p = lv_fs_get_drv(label); + if(drv_p != NULL) drv_p->user_data = (lfs_t *) lfs_p; + return drv_p; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +/*Initialize your Storage device and File system.*/ +static void fs_init(void) +{ + /* Initialize the internal flash or SD-card and LittleFS itself. + * Better to do it in your code to keep this library untouched for easy updating */ +} + +/** + * Open a file + * @param drv pointer to a driver where this function belongs + * @param path path to the file beginning with the driver letter (e.g. S:/folder/file.txt) + * @param mode read: FS_MODE_RD, write: FS_MODE_WR, both: FS_MODE_RD | FS_MODE_WR + * @return pointer to a file descriptor or NULL on error + */ +static void * fs_open(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode) +{ + lfs_t * lfs_p = drv->user_data; + uint32_t flags = 0; + + flags = mode == LV_FS_MODE_RD ? LFS_O_RDONLY + : mode == LV_FS_MODE_WR ? LFS_O_WRONLY + : mode == (LV_FS_MODE_WR | LV_FS_MODE_RD) ? LFS_O_RDWR : 0; + + lfs_file_t * file_p = lv_mem_alloc(sizeof(lfs_file_t)); + if(file_p == NULL) return NULL; + + int result = lfs_file_open(lfs_p, file_p, path, flags); + + if(result != LFS_ERR_OK) { + lv_mem_free(file_p); + return NULL; + } + + return file_p; +} + +/** + * Close an opened file + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a file_t variable. (opened with fs_open) + * @return LV_FS_RES_OK: no error or any error from @lv_fs_res_t enum + */ +static lv_fs_res_t fs_close(lv_fs_drv_t * drv, void * file_p) +{ + lfs_t * lfs_p = drv->user_data; + + int result = lfs_file_close(lfs_p, file_p); + lv_mem_free(file_p); + /*lv_mem_free( lfs_p );*/ /*allocated and freed by outside-code*/ + + if(result != LFS_ERR_OK) return LV_FS_RES_UNKNOWN; + return LV_FS_RES_OK; +} + +/** + * Read data from an opened file + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a file_t variable. + * @param buf pointer to a memory block where to store the read data + * @param btr number of Bytes To Read + * @param br the real number of read bytes (Byte Read) + * @return LV_FS_RES_OK: no error or any error from @lv_fs_res_t enum + */ +static lv_fs_res_t fs_read(lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br) +{ + lfs_t * lfs_p = drv->user_data; + + lfs_ssize_t result = lfs_file_read(lfs_p, file_p, buf, btr); + if(result < 0) return LV_FS_RES_UNKNOWN; + + *br = (uint32_t) result; + return LV_FS_RES_OK; +} + +/** + * Write into a file + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a file_t variable + * @param buf pointer to a buffer with the bytes to write + * @param btw Bytes To Write + * @param bw the number of real written bytes (Bytes Written). NULL if unused. + * @return LV_FS_RES_OK: no error or any error from @lv_fs_res_t enum + */ +static lv_fs_res_t fs_write(lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw) +{ +#ifndef LFS_READONLY + lfs_t * lfs_p = drv->user_data; + + lfs_ssize_t result = lfs_file_write(lfs_p, file_p, buf, btw); + if(result < 0 || lfs_file_sync(lfs_p, file_p) < 0) return LV_FS_RES_UNKNOWN; + + *bw = (uint32_t) result; + return LV_FS_RES_OK; +#else + return LV_FS_RES_NOT_IMP; +#endif +} + +/** + * Set the read write pointer. Also expand the file size if necessary. + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a file_t variable. (opened with fs_open ) + * @param pos the new position of read write pointer + * @param whence tells from where to interpret the `pos`. See @lv_fs_whence_t + * @return LV_FS_RES_OK: no error or any error from @lv_fs_res_t enum + */ +static lv_fs_res_t fs_seek(lv_fs_drv_t * drv, void * file_p, uint32_t pos, lv_fs_whence_t whence) +{ + lfs_t * lfs_p = drv->user_data; + + int lfs_whence = whence == LV_FS_SEEK_SET ? LFS_SEEK_SET + : whence == LV_FS_SEEK_CUR ? LFS_SEEK_CUR + : whence == LV_FS_SEEK_END ? LFS_SEEK_END : 0; + + lfs_soff_t result = lfs_file_seek(lfs_p, file_p, pos, lfs_whence); + if(result < 0) return LV_FS_RES_UNKNOWN; + + /*pos = result;*/ /*not supported by lv_fs*/ + return LV_FS_RES_OK; +} + +/** + * Give the position of the read write pointer + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a file_t variable. + * @param pos_p pointer to where to store the result + * @return LV_FS_RES_OK: no error or any error from @lv_fs_res_t enum + */ +static lv_fs_res_t fs_tell(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p) +{ + lfs_t * lfs_p = drv->user_data; + + lfs_soff_t result = lfs_file_tell(lfs_p, file_p); + if(result < 0) return LV_FS_RES_UNKNOWN; + + *pos_p = (uint32_t) result; + return LV_FS_RES_OK; +} + +/** + * Initialize a 'lv_fs_dir_t' variable for directory reading + * @param drv pointer to a driver where this function belongs + * @param path path to a directory + * @return pointer to the directory read descriptor or NULL on error + */ +static void * fs_dir_open(lv_fs_drv_t * drv, const char * path) +{ + lfs_t * lfs_p = drv->user_data; + + lfs_dir_t * dir_p = lv_mem_alloc(sizeof(lfs_dir_t)); + if(dir_p == NULL) return NULL; + + int result = lfs_dir_open(lfs_p, dir_p, path); + if(result != LFS_ERR_OK) { + lv_mem_free(dir_p); + return NULL; + } + + return dir_p; +} + +/** + * Read the next filename form a directory. + * The name of the directories will begin with '/' + * @param drv pointer to a driver where this function belongs + * @param rddir_p pointer to an initialized 'lv_fs_dir_t' variable + * @param fn pointer to a buffer to store the filename + * @return LV_FS_RES_OK: no error or any error from @lv_fs_res_t enum + */ +static lv_fs_res_t fs_dir_read(lv_fs_drv_t * drv, void * rddir_p, char * fn) +{ + lfs_t * lfs_p = drv->user_data; + struct lfs_info info; + int result; + + info.name[0] = '\0'; + + do { + result = lfs_dir_read(lfs_p, rddir_p, &info); + if(result > 0) { + if(info.type == LFS_TYPE_DIR) { + fn[0] = '/'; + strcpy(&fn[1], info.name); + } + else strcpy(fn, info.name); + } + else if(result == 0) fn[0] = '\0'; /*dir-scan ended*/ + else return LV_FS_RES_UNKNOWN; + + } while(!strcmp(fn, "/.") || !strcmp(fn, "/..")); + + return LV_FS_RES_OK; +} + +/** + * Close the directory reading + * @param drv pointer to a driver where this function belongs + * @param rddir_p pointer to an initialized 'lv_fs_dir_t' variable + * @return LV_FS_RES_OK: no error or any error from @lv_fs_res_t enum + */ +static lv_fs_res_t fs_dir_close(lv_fs_drv_t * drv, void * rddir_p) +{ + lfs_t * lfs_p = drv->user_data; + + int result = lfs_dir_close(lfs_p, rddir_p); + lv_mem_free(rddir_p); + + if(result != LFS_ERR_OK) return LV_FS_RES_UNKNOWN; + return LV_FS_RES_OK; +} + +#else /*LV_USE_FS_LITTLEFS == 0*/ + +#if defined(LV_FS_LITTLEFS_LETTER) && LV_FS_LITTLEFS_LETTER != '\0' + #warning "LV_USE_FS_LITTLEFS is not enabled but LV_FS_LITTLEFS_LETTER is set" +#endif + +#endif /*LV_USE_FS_POSIX*/ diff --git a/lib/libesp32_lvgl/lvgl/src/extra/libs/fsdrv/lv_fsdrv.h b/lib/libesp32_lvgl/lvgl/src/extra/libs/fsdrv/lv_fsdrv.h index 285d598fd..b864ad6f3 100644 --- a/lib/libesp32_lvgl/lvgl/src/extra/libs/fsdrv/lv_fsdrv.h +++ b/lib/libesp32_lvgl/lvgl/src/extra/libs/fsdrv/lv_fsdrv.h @@ -31,6 +31,11 @@ extern "C" { void lv_fs_fatfs_init(void); #endif +#if LV_USE_FS_LITTLEFS != '\0' +void lv_fs_littlefs_init(void); +lv_fs_drv_t * lv_fs_littlefs_set_driver(char label, void * lfs_p); +#endif + #if LV_USE_FS_STDIO != '\0' void lv_fs_stdio_init(void); #endif diff --git a/lib/libesp32_lvgl/lvgl/src/extra/libs/lv_libs.h b/lib/libesp32_lvgl/lvgl/src/extra/libs/lv_libs.h index 6782b1d08..1fefe6c11 100644 --- a/lib/libesp32_lvgl/lvgl/src/extra/libs/lv_libs.h +++ b/lib/libesp32_lvgl/lvgl/src/extra/libs/lv_libs.h @@ -22,6 +22,7 @@ extern "C" { #include "freetype/lv_freetype.h" #include "rlottie/lv_rlottie.h" #include "ffmpeg/lv_ffmpeg.h" +#include "tiny_ttf/lv_tiny_ttf.h" /********************* * DEFINES diff --git a/lib/libesp32_lvgl/lvgl/src/extra/libs/qrcode/qrcodegen.c b/lib/libesp32_lvgl/lvgl/src/extra/libs/qrcode/qrcodegen.c index 37ee74233..bd9f08b36 100644 --- a/lib/libesp32_lvgl/lvgl/src/extra/libs/qrcode/qrcodegen.c +++ b/lib/libesp32_lvgl/lvgl/src/extra/libs/qrcode/qrcodegen.c @@ -21,11 +21,11 @@ * Software. */ -#include #include #include #include #include "qrcodegen.h" +#include "../../../misc/lv_assert.h" #ifndef QRCODEGEN_TEST #define testable static // Keep functions private @@ -127,12 +127,12 @@ static const int PENALTY_N4 = 10; // Public function - see documentation comment in header file. bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode[], enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl) { - + size_t textLen = strlen(text); if (textLen == 0) return qrcodegen_encodeSegmentsAdvanced(NULL, 0, ecl, minVersion, maxVersion, mask, boostEcl, tempBuffer, qrcode); size_t bufLen = qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion); - + struct qrcodegen_Segment seg; if (qrcodegen_isNumeric(text)) { if (qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_NUMERIC, textLen) > bufLen) @@ -155,7 +155,7 @@ bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode seg.data = tempBuffer; } return qrcodegen_encodeSegmentsAdvanced(&seg, 1, ecl, minVersion, maxVersion, mask, boostEcl, tempBuffer, qrcode); - + fail: qrcode[0] = 0; // Set size to invalid value for safety return false; @@ -165,7 +165,7 @@ fail: // Public function - see documentation comment in header file. bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcode[], enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl) { - + struct qrcodegen_Segment seg; seg.mode = qrcodegen_Mode_BYTE; seg.bitLength = calcSegmentBitLength(seg.mode, dataLen); @@ -182,7 +182,7 @@ bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcod // Appends the given number of low-order bits of the given value to the given byte-based // bit buffer, increasing the bit length. Requires 0 <= numBits <= 16 and val < 2^numBits. testable void appendBitsToBuffer(unsigned int val, int numBits, uint8_t buffer[], int *bitLen) { - assert(0 <= numBits && numBits <= 16 && (unsigned long)val >> numBits == 0); + LV_ASSERT(0 <= numBits && numBits <= 16 && (unsigned long)val >> numBits == 0); for (int i = numBits - 1; i >= 0; i--, (*bitLen)++) buffer[*bitLen >> 3] |= ((val >> i) & 1) << (7 - (*bitLen & 7)); } @@ -202,10 +202,10 @@ bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[], size_t len, // Public function - see documentation comment in header file. bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], size_t len, enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, int mask, bool boostEcl, uint8_t tempBuffer[], uint8_t qrcode[]) { - assert(segs != NULL || len == 0); - assert(qrcodegen_VERSION_MIN <= minVersion && minVersion <= maxVersion && maxVersion <= qrcodegen_VERSION_MAX); - assert(0 <= (int)ecl && (int)ecl <= 3 && -1 <= (int)mask && (int)mask <= 7); - + LV_ASSERT(segs != NULL || len == 0); + LV_ASSERT(qrcodegen_VERSION_MIN <= minVersion && minVersion <= maxVersion && maxVersion <= qrcodegen_VERSION_MAX); + LV_ASSERT(0 <= (int)ecl && (int)ecl <= 3 && -1 <= (int)mask && (int)mask <= 7); + // Find the minimal version number to use int version, dataUsedBits; for (version = minVersion; ; version++) { @@ -218,14 +218,14 @@ bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], siz return false; } } - assert(dataUsedBits != -1); - + LV_ASSERT(dataUsedBits != -1); + // Increase the error correction level while the data still fits in the current version number for (int i = (int)qrcodegen_Ecc_MEDIUM; i <= (int)qrcodegen_Ecc_HIGH; i++) { // From low to high if (boostEcl && dataUsedBits <= getNumDataCodewords(version, (enum qrcodegen_Ecc)i) * 8) ecl = (enum qrcodegen_Ecc)i; } - + // Concatenate all segments to create the data bit string memset(qrcode, 0, qrcodegen_BUFFER_LEN_FOR_VERSION(version) * sizeof(qrcode[0])); int bitLen = 0; @@ -236,29 +236,29 @@ bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], siz for (int j = 0; j < seg->bitLength; j++) appendBitsToBuffer((seg->data[j >> 3] >> (7 - (j & 7))) & 1, 1, qrcode, &bitLen); } - assert(bitLen == dataUsedBits); - + LV_ASSERT(bitLen == dataUsedBits); + // Add terminator and pad up to a byte if applicable int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; - assert(bitLen <= dataCapacityBits); + LV_ASSERT(bitLen <= dataCapacityBits); int terminatorBits = dataCapacityBits - bitLen; if (terminatorBits > 4) terminatorBits = 4; appendBitsToBuffer(0, terminatorBits, qrcode, &bitLen); appendBitsToBuffer(0, (8 - bitLen % 8) % 8, qrcode, &bitLen); - assert(bitLen % 8 == 0); - + LV_ASSERT(bitLen % 8 == 0); + // Pad with alternating bytes until data capacity is reached for (uint8_t padByte = 0xEC; bitLen < dataCapacityBits; padByte ^= 0xEC ^ 0x11) appendBitsToBuffer(padByte, 8, qrcode, &bitLen); - + // Draw function and data codeword modules addEccAndInterleave(qrcode, version, ecl, tempBuffer); initializeFunctionModules(version, qrcode); drawCodewords(tempBuffer, getNumRawDataModules(version) / 8, qrcode); drawWhiteFunctionModules(qrcode, version); initializeFunctionModules(version, tempBuffer); - + // Handle masking if (mask == qrcodegen_Mask_AUTO) { // Automatically choose best mask long minPenalty = LONG_MAX; @@ -274,7 +274,7 @@ bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], siz applyMask(tempBuffer, qrcode, msk); // Undoes the mask due to XOR } } - assert(0 <= (int)mask && (int)mask <= 7); + LV_ASSERT(0 <= (int)mask && (int)mask <= 7); applyMask(tempBuffer, qrcode, mask); drawFormatBits(ecl, mask, qrcode); return true; @@ -290,14 +290,14 @@ bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], siz // be clobbered by this function. The final answer is stored in result[0 : rawCodewords]. testable void addEccAndInterleave(uint8_t data[], int version, enum qrcodegen_Ecc ecl, uint8_t result[]) { // Calculate parameter numbers - assert(0 <= (int)ecl && (int)ecl < 4 && qrcodegen_VERSION_MIN <= version && version <= qrcodegen_VERSION_MAX); + LV_ASSERT(0 <= (int)ecl && (int)ecl < 4 && qrcodegen_VERSION_MIN <= version && version <= qrcodegen_VERSION_MAX); int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[(int)ecl][version]; int blockEccLen = ECC_CODEWORDS_PER_BLOCK [(int)ecl][version]; int rawCodewords = getNumRawDataModules(version) / 8; int dataLen = getNumDataCodewords(version, ecl); int numShortBlocks = numBlocks - rawCodewords % numBlocks; int shortBlockDataLen = rawCodewords / numBlocks - blockEccLen; - + // Split data into blocks, calculate ECC, and interleave // (not concatenate) the bytes into a single sequence uint8_t generator[qrcodegen_REED_SOLOMON_DEGREE_MAX]; @@ -323,7 +323,7 @@ testable void addEccAndInterleave(uint8_t data[], int version, enum qrcodegen_Ec // for the given version number and error correction level. The result is in the range [9, 2956]. testable int getNumDataCodewords(int version, enum qrcodegen_Ecc ecl) { int v = version, e = (int)ecl; - assert(0 <= e && e < 4); + LV_ASSERT(0 <= e && e < 4); return getNumRawDataModules(v) / 8 - ECC_CODEWORDS_PER_BLOCK [e][v] * NUM_ERROR_CORRECTION_BLOCKS[e][v]; @@ -334,7 +334,7 @@ testable int getNumDataCodewords(int version, enum qrcodegen_Ecc ecl) { // all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. // The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. testable int getNumRawDataModules(int ver) { - assert(qrcodegen_VERSION_MIN <= ver && ver <= qrcodegen_VERSION_MAX); + LV_ASSERT(qrcodegen_VERSION_MIN <= ver && ver <= qrcodegen_VERSION_MAX); int result = (16 * ver + 128) * ver + 64; if (ver >= 2) { int numAlign = ver / 7 + 2; @@ -352,10 +352,10 @@ testable int getNumRawDataModules(int ver) { // Calculates the Reed-Solomon generator polynomial of the given degree, storing in result[0 : degree]. testable void calcReedSolomonGenerator(int degree, uint8_t result[]) { // Start with the monomial x^0 - assert(1 <= degree && degree <= qrcodegen_REED_SOLOMON_DEGREE_MAX); + LV_ASSERT(1 <= degree && degree <= qrcodegen_REED_SOLOMON_DEGREE_MAX); memset(result, 0, degree * sizeof(result[0])); result[degree - 1] = 1; - + // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), // drop the highest term, and store the rest of the coefficients in order of descending powers. // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). @@ -376,9 +376,9 @@ testable void calcReedSolomonGenerator(int degree, uint8_t result[]) { // polynomials are in big endian and the generator has an implicit leading 1 term, storing the result in result[0 : degree]. testable void calcReedSolomonRemainder(const uint8_t data[], int dataLen, const uint8_t generator[], int degree, uint8_t result[]) { - + // Perform polynomial division - assert(1 <= degree && degree <= qrcodegen_REED_SOLOMON_DEGREE_MAX); + LV_ASSERT(1 <= degree && degree <= qrcodegen_REED_SOLOMON_DEGREE_MAX); memset(result, 0, degree * sizeof(result[0])); for (int i = 0; i < dataLen; i++) { uint8_t factor = data[i] ^ result[0]; @@ -415,16 +415,16 @@ testable void initializeFunctionModules(int version, uint8_t qrcode[]) { int qrsize = version * 4 + 17; memset(qrcode, 0, ((qrsize * qrsize + 7) / 8 + 1) * sizeof(qrcode[0])); qrcode[0] = (uint8_t)qrsize; - + // Fill horizontal and vertical timing patterns fillRectangle(6, 0, 1, qrsize, qrcode); fillRectangle(0, 6, qrsize, 1, qrcode); - + // Fill 3 finder patterns (all corners except bottom right) and format bits fillRectangle(0, 0, 9, 9, qrcode); fillRectangle(qrsize - 8, 0, 8, 9, qrcode); fillRectangle(0, qrsize - 8, 9, 8, qrcode); - + // Fill numerous alignment patterns uint8_t alignPatPos[7]; int numAlign = getAlignmentPatternPositions(version, alignPatPos); @@ -435,7 +435,7 @@ testable void initializeFunctionModules(int version, uint8_t qrcode[]) { fillRectangle(alignPatPos[i] - 2, alignPatPos[j] - 2, 5, 5, qrcode); } } - + // Fill version blocks if (version >= 7) { fillRectangle(qrsize - 11, 0, 3, 6, qrcode); @@ -454,7 +454,7 @@ static void drawWhiteFunctionModules(uint8_t qrcode[], int version) { setModule(qrcode, 6, i, false); setModule(qrcode, i, 6, false); } - + // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) for (int dy = -4; dy <= 4; dy++) { for (int dx = -4; dx <= 4; dx++) { @@ -468,7 +468,7 @@ static void drawWhiteFunctionModules(uint8_t qrcode[], int version) { } } } - + // Draw numerous alignment patterns uint8_t alignPatPos[7]; int numAlign = getAlignmentPatternPositions(version, alignPatPos); @@ -482,7 +482,7 @@ static void drawWhiteFunctionModules(uint8_t qrcode[], int version) { } } } - + // Draw version blocks if (version >= 7) { // Calculate error correction code and pack bits @@ -490,8 +490,8 @@ static void drawWhiteFunctionModules(uint8_t qrcode[], int version) { for (int i = 0; i < 12; i++) rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); long bits = (long)version << 12 | rem; // uint18 - assert(bits >> 18 == 0); - + LV_ASSERT(bits >> 18 == 0); + // Draw two copies for (int i = 0; i < 6; i++) { for (int j = 0; j < 3; j++) { @@ -510,15 +510,15 @@ static void drawWhiteFunctionModules(uint8_t qrcode[], int version) { // the format bits, unlike drawWhiteFunctionModules() which might skip black modules. static void drawFormatBits(enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, uint8_t qrcode[]) { // Calculate error correction code and pack bits - assert(0 <= (int)mask && (int)mask <= 7); + LV_ASSERT(0 <= (int)mask && (int)mask <= 7); static const int table[] = {1, 0, 3, 2}; int data = table[(int)ecl] << 3 | (int)mask; // errCorrLvl is uint2, mask is uint3 int rem = data; for (int i = 0; i < 10; i++) rem = (rem << 1) ^ ((rem >> 9) * 0x537); int bits = (data << 10 | rem) ^ 0x5412; // uint15 - assert(bits >> 15 == 0); - + LV_ASSERT(bits >> 15 == 0); + // Draw first copy for (int i = 0; i <= 5; i++) setModule(qrcode, 8, i, getBit(bits, i)); @@ -527,7 +527,7 @@ static void drawFormatBits(enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, uin setModule(qrcode, 7, 8, getBit(bits, 8)); for (int i = 9; i < 15; i++) setModule(qrcode, 14 - i, 8, getBit(bits, i)); - + // Draw second copy int qrsize = qrcodegen_getSize(qrcode); for (int i = 0; i < 8; i++) @@ -591,7 +591,7 @@ static void drawCodewords(const uint8_t data[], int dataLen, uint8_t qrcode[]) { } } } - assert(i == dataLen * 8); + LV_ASSERT(i == dataLen * 8); } @@ -601,7 +601,7 @@ static void drawCodewords(const uint8_t data[], int dataLen, uint8_t qrcode[]) { // the same mask value a second time will undo the mask. A final well-formed // QR Code needs exactly one (not zero, two, etc.) mask applied. static void applyMask(const uint8_t functionModules[], uint8_t qrcode[], enum qrcodegen_Mask mask) { - assert(0 <= (int)mask && (int)mask <= 7); // Disallows qrcodegen_Mask_AUTO + LV_ASSERT(0 <= (int)mask && (int)mask <= 7); // Disallows qrcodegen_Mask_AUTO int qrsize = qrcodegen_getSize(qrcode); for (int y = 0; y < qrsize; y++) { for (int x = 0; x < qrsize; x++) { @@ -617,7 +617,7 @@ static void applyMask(const uint8_t functionModules[], uint8_t qrcode[], enum qr case 5: invert = x * y % 2 + x * y % 3 == 0; break; case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; - default: assert(false); return; + default: LV_ASSERT(false); return; } bool val = getModule(qrcode, x, y); setModule(qrcode, x, y, val ^ invert); @@ -631,7 +631,7 @@ static void applyMask(const uint8_t functionModules[], uint8_t qrcode[], enum qr static long getPenaltyScore(const uint8_t qrcode[]) { int qrsize = qrcodegen_getSize(qrcode); long result = 0; - + // Adjacent modules in row having same color, and finder-like patterns for (int y = 0; y < qrsize; y++) { unsigned char runHistory[7] = {0}; @@ -684,7 +684,7 @@ static long getPenaltyScore(const uint8_t qrcode[]) { if (hasFinderLikePattern(runHistory)) result += PENALTY_N3; } - + // 2*2 blocks of modules having same color for (int y = 0; y < qrsize - 1; y++) { for (int x = 0; x < qrsize - 1; x++) { @@ -695,7 +695,7 @@ static long getPenaltyScore(const uint8_t qrcode[]) { result += PENALTY_N2; } } - + // Balance of black and white modules int black = 0; for (int y = 0; y < qrsize; y++) { @@ -737,9 +737,9 @@ static bool hasFinderLikePattern(const unsigned char runHistory[7]) { // Public function - see documentation comment in header file. int qrcodegen_getSize(const uint8_t qrcode[]) { - assert(qrcode != NULL); + LV_ASSERT(qrcode != NULL); int result = qrcode[0]; - assert((qrcodegen_VERSION_MIN * 4 + 17) <= result + LV_ASSERT((qrcodegen_VERSION_MIN * 4 + 17) <= result && result <= (qrcodegen_VERSION_MAX * 4 + 17)); return result; } @@ -747,7 +747,7 @@ int qrcodegen_getSize(const uint8_t qrcode[]) { // Public function - see documentation comment in header file. bool qrcodegen_getModule(const uint8_t qrcode[], int x, int y) { - assert(qrcode != NULL); + LV_ASSERT(qrcode != NULL); int qrsize = qrcode[0]; return (0 <= x && x < qrsize && 0 <= y && y < qrsize) && getModule(qrcode, x, y); } @@ -756,7 +756,7 @@ bool qrcodegen_getModule(const uint8_t qrcode[], int x, int y) { // Gets the module at the given coordinates, which must be in bounds. testable bool getModule(const uint8_t qrcode[], int x, int y) { int qrsize = qrcode[0]; - assert(21 <= qrsize && qrsize <= 177 && 0 <= x && x < qrsize && 0 <= y && y < qrsize); + LV_ASSERT(21 <= qrsize && qrsize <= 177 && 0 <= x && x < qrsize && 0 <= y && y < qrsize); int index = y * qrsize + x; return getBit(qrcode[(index >> 3) + 1], index & 7); } @@ -765,7 +765,7 @@ testable bool getModule(const uint8_t qrcode[], int x, int y) { // Sets the module at the given coordinates, which must be in bounds. testable void setModule(uint8_t qrcode[], int x, int y, bool isBlack) { int qrsize = qrcode[0]; - assert(21 <= qrsize && qrsize <= 177 && 0 <= x && x < qrsize && 0 <= y && y < qrsize); + LV_ASSERT(21 <= qrsize && qrsize <= 177 && 0 <= x && x < qrsize && 0 <= y && y < qrsize); int index = y * qrsize + x; int bitIndex = index & 7; int byteIndex = (index >> 3) + 1; @@ -795,7 +795,7 @@ static bool getBit(int x, int i) { // Public function - see documentation comment in header file. bool qrcodegen_isAlphanumeric(const char *text) { - assert(text != NULL); + LV_ASSERT(text != NULL); for (; *text != '\0'; text++) { if (strchr(ALPHANUMERIC_CHARSET, *text) == NULL) return false; @@ -806,7 +806,7 @@ bool qrcodegen_isAlphanumeric(const char *text) { // Public function - see documentation comment in header file. bool qrcodegen_isNumeric(const char *text) { - assert(text != NULL); + LV_ASSERT(text != NULL); for (; *text != '\0'; text++) { if (*text < '0' || *text > '9') return false; @@ -820,7 +820,7 @@ size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars int temp = calcSegmentBitLength(mode, numChars); if (temp == -1) return SIZE_MAX; - assert(0 <= temp && temp <= INT16_MAX); + LV_ASSERT(0 <= temp && temp <= INT16_MAX); return ((size_t)temp + 7) / 8; } @@ -849,10 +849,10 @@ testable int calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars) { else if (mode == qrcodegen_Mode_ECI && numChars == 0) result = 3 * 8; else { // Invalid argument - assert(false); + LV_ASSERT(false); return -1; } - assert(result >= 0); + LV_ASSERT(result >= 0); if ((unsigned int)result > (unsigned int)INT16_MAX) return -1; return (int)result; @@ -861,11 +861,11 @@ testable int calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars) { // Public function - see documentation comment in header file. struct qrcodegen_Segment qrcodegen_makeBytes(const uint8_t data[], size_t len, uint8_t buf[]) { - assert(data != NULL || len == 0); + LV_ASSERT(data != NULL || len == 0); struct qrcodegen_Segment result; result.mode = qrcodegen_Mode_BYTE; result.bitLength = calcSegmentBitLength(result.mode, len); - assert(result.bitLength != -1); + LV_ASSERT(result.bitLength != -1); result.numChars = (int)len; if (len > 0) memcpy(buf, data, len * sizeof(buf[0])); @@ -876,22 +876,22 @@ struct qrcodegen_Segment qrcodegen_makeBytes(const uint8_t data[], size_t len, u // Public function - see documentation comment in header file. struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]) { - assert(digits != NULL); + LV_ASSERT(digits != NULL); struct qrcodegen_Segment result; size_t len = strlen(digits); result.mode = qrcodegen_Mode_NUMERIC; int bitLen = calcSegmentBitLength(result.mode, len); - assert(bitLen != -1); + LV_ASSERT(bitLen != -1); result.numChars = (int)len; if (bitLen > 0) memset(buf, 0, ((size_t)bitLen + 7) / 8 * sizeof(buf[0])); result.bitLength = 0; - + unsigned int accumData = 0; int accumCount = 0; for (; *digits != '\0'; digits++) { char c = *digits; - assert('0' <= c && c <= '9'); + LV_ASSERT('0' <= c && c <= '9'); accumData = accumData * 10 + (unsigned int)(c - '0'); accumCount++; if (accumCount == 3) { @@ -902,7 +902,7 @@ struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[] } if (accumCount > 0) // 1 or 2 digits remaining appendBitsToBuffer(accumData, accumCount * 3 + 1, buf, &result.bitLength); - assert(result.bitLength == bitLen); + LV_ASSERT(result.bitLength == bitLen); result.data = buf; return result; } @@ -910,22 +910,22 @@ struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[] // Public function - see documentation comment in header file. struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t buf[]) { - assert(text != NULL); + LV_ASSERT(text != NULL); struct qrcodegen_Segment result; size_t len = strlen(text); result.mode = qrcodegen_Mode_ALPHANUMERIC; int bitLen = calcSegmentBitLength(result.mode, len); - assert(bitLen != -1); + LV_ASSERT(bitLen != -1); result.numChars = (int)len; if (bitLen > 0) memset(buf, 0, ((size_t)bitLen + 7) / 8 * sizeof(buf[0])); result.bitLength = 0; - + unsigned int accumData = 0; int accumCount = 0; for (; *text != '\0'; text++) { const char *temp = strchr(ALPHANUMERIC_CHARSET, *text); - assert(temp != NULL); + LV_ASSERT(temp != NULL); accumData = accumData * 45 + (unsigned int)(temp - ALPHANUMERIC_CHARSET); accumCount++; if (accumCount == 2) { @@ -936,7 +936,7 @@ struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t bu } if (accumCount > 0) // 1 character remaining appendBitsToBuffer(accumData, 6, buf, &result.bitLength); - assert(result.bitLength == bitLen); + LV_ASSERT(result.bitLength == bitLen); result.data = buf; return result; } @@ -949,7 +949,7 @@ struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]) { result.numChars = 0; result.bitLength = 0; if (assignVal < 0) { - assert(false); + LV_ASSERT(false); } else if (assignVal < (1 << 7)) { memset(buf, 0, 1 * sizeof(buf[0])); appendBitsToBuffer(assignVal, 8, buf, &result.bitLength); @@ -963,7 +963,7 @@ struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]) { appendBitsToBuffer(assignVal >> 10, 11, buf, &result.bitLength); appendBitsToBuffer(assignVal & 0x3FF, 10, buf, &result.bitLength); } else { - assert(false); + LV_ASSERT(false); } result.data = buf; return result; @@ -974,22 +974,22 @@ struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]) { // Returns a non-negative number if successful. Otherwise returns -1 if a segment has too // many characters to fit its length field, or the total bits exceeds INT16_MAX. testable int getTotalBits(const struct qrcodegen_Segment segs[], size_t len, int version) { - assert(segs != NULL || len == 0); + LV_ASSERT(segs != NULL || len == 0); long result = 0; for (size_t i = 0; i < len; i++) { int numChars = segs[i].numChars; int bitLength = segs[i].bitLength; - assert(0 <= numChars && numChars <= INT16_MAX); - assert(0 <= bitLength && bitLength <= INT16_MAX); + LV_ASSERT(0 <= numChars && numChars <= INT16_MAX); + LV_ASSERT(0 <= bitLength && bitLength <= INT16_MAX); int ccbits = numCharCountBits(segs[i].mode, version); - assert(0 <= ccbits && ccbits <= 16); + LV_ASSERT(0 <= ccbits && ccbits <= 16); if (numChars >= (1L << ccbits)) return -1; // The segment's length doesn't fit the field's bit width result += 4L + ccbits + bitLength; if (result > INT16_MAX) return -1; // The sum might overflow an int type } - assert(0 <= result && result <= INT16_MAX); + LV_ASSERT(0 <= result && result <= INT16_MAX); return (int)result; } @@ -997,7 +997,7 @@ testable int getTotalBits(const struct qrcodegen_Segment segs[], size_t len, int // Returns the bit width of the character count field for a segment in the given mode // in a QR Code at the given version number. The result is in the range [0, 16]. static int numCharCountBits(enum qrcodegen_Mode mode, int version) { - assert(qrcodegen_VERSION_MIN <= version && version <= qrcodegen_VERSION_MAX); + LV_ASSERT(qrcodegen_VERSION_MIN <= version && version <= qrcodegen_VERSION_MAX); int i = (version + 7) / 17; switch (mode) { case qrcodegen_Mode_NUMERIC : { static const int temp[] = {10, 12, 14}; return temp[i]; } @@ -1005,7 +1005,7 @@ static int numCharCountBits(enum qrcodegen_Mode mode, int version) { case qrcodegen_Mode_BYTE : { static const int temp[] = { 8, 16, 16}; return temp[i]; } case qrcodegen_Mode_KANJI : { static const int temp[] = { 8, 10, 12}; return temp[i]; } case qrcodegen_Mode_ECI : return 0; - default: assert(false); return -1; // Dummy value + default: LV_ASSERT(false); return -1; // Dummy value } } diff --git a/lib/libesp32_lvgl/lvgl/src/extra/libs/tiny_ttf/lv_tiny_ttf.c b/lib/libesp32_lvgl/lvgl/src/extra/libs/tiny_ttf/lv_tiny_ttf.c new file mode 100644 index 000000000..c275bfed2 --- /dev/null +++ b/lib/libesp32_lvgl/lvgl/src/extra/libs/tiny_ttf/lv_tiny_ttf.c @@ -0,0 +1,284 @@ +#include "lv_tiny_ttf.h" + +#if LV_USE_TINY_TTF +#include +#include "../../../misc/lv_lru.h" + +#define STB_RECT_PACK_IMPLEMENTATION +#define STBRP_STATIC +#define STBTT_STATIC +#define STB_TRUETYPE_IMPLEMENTATION +#define STBTT_HEAP_FACTOR_SIZE_32 50 +#define STBTT_HEAP_FACTOR_SIZE_128 20 +#define STBTT_HEAP_FACTOR_SIZE_DEFAULT 10 +#define STBTT_malloc(x, u) ((void)(u), lv_mem_alloc(x)) +#define STBTT_free(x, u) ((void)(u), lv_mem_free(x)) +#define TTF_MALLOC(x) (lv_mem_alloc(x)) +#define TTF_FREE(x) (lv_mem_free(x)) + +#if LV_TINY_TTF_FILE_SUPPORT +/* a hydra stream that can be in memory or from a file*/ +typedef struct ttf_cb_stream { + lv_fs_file_t * file; + const void * data; + size_t size; + size_t position; +} ttf_cb_stream_t; + +static void ttf_cb_stream_read(ttf_cb_stream_t * stream, void * data, size_t to_read) +{ + if(stream->file != NULL) { + uint32_t br; + lv_fs_read(stream->file, data, to_read, &br); + } + else { + if(to_read + stream->position >= stream->size) { + to_read = stream->size - stream->position; + } + lv_memcpy(data, ((const unsigned char *)stream->data + stream->position), to_read); + stream->position += to_read; + } +} +static void ttf_cb_stream_seek(ttf_cb_stream_t * stream, size_t position) +{ + if(stream->file != NULL) { + lv_fs_seek(stream->file, position, LV_FS_SEEK_SET); + } + else { + if(position > stream->size) { + stream->position = stream->size; + } + else { + stream->position = position; + } + } +} + +/* for stream support */ +#define STBTT_STREAM_TYPE ttf_cb_stream_t * +#define STBTT_STREAM_SEEK(s, x) ttf_cb_stream_seek(s, x); +#define STBTT_STREAM_READ(s, x, y) ttf_cb_stream_read(s, x, y); +#endif /*LV_TINY_TTF_FILE_SUPPORT*/ + +#include "stb_rect_pack.h" +#include "stb_truetype_htcw.h" + +typedef struct ttf_font_desc { + lv_fs_file_t file; +#if LV_TINY_TTF_FILE_SUPPORT + ttf_cb_stream_t stream; +#else + const uint8_t * stream; +#endif + stbtt_fontinfo info; + float scale; + int ascent; + int descent; + lv_lru_t * bitmap_cache; +} ttf_font_desc_t; + +typedef struct ttf_bitmap_cache_key { + uint32_t unicode_letter; + lv_coord_t line_height; +} ttf_bitmap_cache_key_t; + +static bool ttf_get_glyph_dsc_cb(const lv_font_t * font, lv_font_glyph_dsc_t * dsc_out, uint32_t unicode_letter, + uint32_t unicode_letter_next) +{ + if(unicode_letter < 0x20 || + unicode_letter == 0xf8ff || /*LV_SYMBOL_DUMMY*/ + unicode_letter == 0x200c) { /*ZERO WIDTH NON-JOINER*/ + dsc_out->box_w = 0; + dsc_out->adv_w = 0; + dsc_out->box_h = 0; /*height of the bitmap in [px]*/ + dsc_out->ofs_x = 0; /*X offset of the bitmap in [pf]*/ + dsc_out->ofs_y = 0; /*Y offset of the bitmap in [pf]*/ + dsc_out->bpp = 0; + dsc_out->is_placeholder = false; + return true; + } + ttf_font_desc_t * dsc = (ttf_font_desc_t *)font->dsc; + int g1 = stbtt_FindGlyphIndex(&dsc->info, (int)unicode_letter); + if(g1 == 0) { + /* Glyph not found */ + return false; + } + int x1, y1, x2, y2; + + stbtt_GetGlyphBitmapBox(&dsc->info, g1, dsc->scale, dsc->scale, &x1, &y1, &x2, &y2); + int g2 = 0; + if(unicode_letter_next != 0) { + g2 = stbtt_FindGlyphIndex(&dsc->info, (int)unicode_letter_next); + } + int advw, lsb; + stbtt_GetGlyphHMetrics(&dsc->info, g1, &advw, &lsb); + int k = stbtt_GetGlyphKernAdvance(&dsc->info, g1, g2); + dsc_out->adv_w = (uint16_t)floor((((float)advw + (float)k) * dsc->scale) + + 0.5f); /*Horizontal space required by the glyph in [px]*/ + + dsc_out->adv_w = (uint16_t)floor((((float)advw + (float)k) * dsc->scale) + + 0.5f); /*Horizontal space required by the glyph in [px]*/ + dsc_out->box_w = (x2 - x1 + 1); /*width of the bitmap in [px]*/ + dsc_out->box_h = (y2 - y1 + 1); /*height of the bitmap in [px]*/ + dsc_out->ofs_x = x1; /*X offset of the bitmap in [pf]*/ + dsc_out->ofs_y = -y2; /*Y offset of the bitmap measured from the as line*/ + dsc_out->bpp = 8; /*Bits per pixel: 1/2/4/8*/ + dsc_out->is_placeholder = false; + return true; /*true: glyph found; false: glyph was not found*/ +} + +static const uint8_t * ttf_get_glyph_bitmap_cb(const lv_font_t * font, uint32_t unicode_letter) +{ + ttf_font_desc_t * dsc = (ttf_font_desc_t *)font->dsc; + const stbtt_fontinfo * info = (const stbtt_fontinfo *)&dsc->info; + int g1 = stbtt_FindGlyphIndex(info, (int)unicode_letter); + if(g1 == 0) { + /* Glyph not found */ + return NULL; + } + int x1, y1, x2, y2; + stbtt_GetGlyphBitmapBox(info, g1, dsc->scale, dsc->scale, &x1, &y1, &x2, &y2); + int w, h; + w = x2 - x1 + 1; + h = y2 - y1 + 1; + uint32_t stride = w; + /*Try to load from cache*/ + ttf_bitmap_cache_key_t cache_key; + lv_memset(&cache_key, 0, sizeof(cache_key)); /*Zero padding*/ + cache_key.unicode_letter = unicode_letter; + cache_key.line_height = font->line_height; + uint8_t * buffer = NULL; + lv_lru_get(dsc->bitmap_cache, &cache_key, sizeof(cache_key), (void **)&buffer); + if(buffer) { + return buffer; + } + LV_LOG_TRACE("cache miss for letter: %u", unicode_letter); + /*Prepare space in cache*/ + size_t szb = h * stride; + buffer = lv_mem_alloc(szb); + if(!buffer) { + LV_LOG_ERROR("failed to allocate cache value"); + return NULL; + } + lv_memset(buffer, 0, szb); + if(LV_LRU_OK != lv_lru_set(dsc->bitmap_cache, &cache_key, sizeof(cache_key), buffer, szb)) { + LV_LOG_ERROR("failed to add cache value"); + lv_mem_free(buffer); + return NULL; + } + /*Render into cache*/ + stbtt_MakeGlyphBitmap(info, buffer, w, h, stride, dsc->scale, dsc->scale, g1); + return buffer; +} + +static lv_font_t * lv_tiny_ttf_create(const char * path, const void * data, size_t data_size, lv_coord_t font_size, + size_t cache_size) +{ + if((path == NULL && data == NULL) || 0 >= font_size) { + LV_LOG_ERROR("tiny_ttf: invalid argument\n"); + return NULL; + } + ttf_font_desc_t * dsc = (ttf_font_desc_t *)TTF_MALLOC(sizeof(ttf_font_desc_t)); + if(dsc == NULL) { + LV_LOG_ERROR("tiny_ttf: out of memory\n"); + return NULL; + } +#if LV_TINY_TTF_FILE_SUPPORT + if(path != NULL) { + if(LV_FS_RES_OK != lv_fs_open(&dsc->file, path, LV_FS_MODE_RD)) { + LV_LOG_ERROR("tiny_ttf: unable to open %s\n", path); + goto err_after_dsc; + } + dsc->stream.file = &dsc->file; + } + else { + dsc->stream.file = NULL; + dsc->stream.data = (const uint8_t *)data; + dsc->stream.size = data_size; + dsc->stream.position = 0; + } + if(0 == stbtt_InitFont(&dsc->info, &dsc->stream, stbtt_GetFontOffsetForIndex(&dsc->stream, 0))) { + LV_LOG_ERROR("tiny_ttf: init failed\n"); + goto err_after_dsc; + } + +#else + dsc->stream = (const uint8_t *)data; + LV_UNUSED(data_size); + if(0 == stbtt_InitFont(&dsc->info, dsc->stream, stbtt_GetFontOffsetForIndex(dsc->stream, 0))) { + LV_LOG_ERROR("tiny_ttf: init failed\n"); + goto err_after_dsc; + } +#endif + + dsc->bitmap_cache = lv_lru_create(cache_size, font_size * font_size, lv_mem_free, lv_mem_free); + if(dsc->bitmap_cache == NULL) { + LV_LOG_ERROR("failed to create lru cache"); + goto err_after_dsc; + } + + lv_font_t * out_font = (lv_font_t *)TTF_MALLOC(sizeof(lv_font_t)); + if(out_font == NULL) { + LV_LOG_ERROR("tiny_ttf: out of memory\n"); + goto err_after_bitmap_cache; + } + lv_memset(out_font, 0, sizeof(lv_font_t)); + out_font->get_glyph_dsc = ttf_get_glyph_dsc_cb; + out_font->get_glyph_bitmap = ttf_get_glyph_bitmap_cb; + out_font->dsc = dsc; + lv_tiny_ttf_set_size(out_font, font_size); + return out_font; +err_after_bitmap_cache: + lv_lru_del(dsc->bitmap_cache); +err_after_dsc: + TTF_FREE(dsc); + return NULL; +} +#if LV_TINY_TTF_FILE_SUPPORT +lv_font_t * lv_tiny_ttf_create_file_ex(const char * path, lv_coord_t font_size, size_t cache_size) +{ + return lv_tiny_ttf_create(path, NULL, 0, font_size, cache_size); +} +lv_font_t * lv_tiny_ttf_create_file(const char * path, lv_coord_t font_size) +{ + return lv_tiny_ttf_create_file_ex(path, font_size, 4096); +} +#endif /*LV_TINY_TTF_FILE_SUPPORT*/ +lv_font_t * lv_tiny_ttf_create_data_ex(const void * data, size_t data_size, lv_coord_t font_size, size_t cache_size) +{ + return lv_tiny_ttf_create(NULL, data, data_size, font_size, cache_size); +} +lv_font_t * lv_tiny_ttf_create_data(const void * data, size_t data_size, lv_coord_t font_size) +{ + return lv_tiny_ttf_create_data_ex(data, data_size, font_size, 4096); +} +void lv_tiny_ttf_set_size(lv_font_t * font, lv_coord_t font_size) +{ + if(font_size <= 0) { + LV_LOG_ERROR("invalid font size: %"PRIx32, font_size); + return; + } + ttf_font_desc_t * dsc = (ttf_font_desc_t *)font->dsc; + dsc->scale = stbtt_ScaleForMappingEmToPixels(&dsc->info, font_size); + int line_gap = 0; + stbtt_GetFontVMetrics(&dsc->info, &dsc->ascent, &dsc->descent, &line_gap); + font->line_height = (lv_coord_t)(dsc->scale * (dsc->ascent - dsc->descent + line_gap)); + font->base_line = (lv_coord_t)(dsc->scale * (line_gap - dsc->descent)); +} +void lv_tiny_ttf_destroy(lv_font_t * font) +{ + if(font != NULL) { + if(font->dsc != NULL) { + ttf_font_desc_t * ttf = (ttf_font_desc_t *)font->dsc; +#if LV_TINY_TTF_FILE_SUPPORT + if(ttf->stream.file != NULL) { + lv_fs_close(&ttf->file); + } +#endif + lv_lru_del(ttf->bitmap_cache); + TTF_FREE(ttf); + } + TTF_FREE(font); + } +} +#endif /*LV_USE_TINY_TTF*/ diff --git a/lib/libesp32_lvgl/lvgl/src/extra/libs/tiny_ttf/lv_tiny_ttf.h b/lib/libesp32_lvgl/lvgl/src/extra/libs/tiny_ttf/lv_tiny_ttf.h new file mode 100644 index 000000000..fe0936b3b --- /dev/null +++ b/lib/libesp32_lvgl/lvgl/src/extra/libs/tiny_ttf/lv_tiny_ttf.h @@ -0,0 +1,62 @@ +/** + * @file lv_tiny_ttf.h + * + */ + +#ifndef LV_TINY_TTF_H +#define LV_TINY_TTF_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_TINY_TTF + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +#if LV_TINY_TTF_FILE_SUPPORT +/* create a font from the specified file or path with the specified line height.*/ +lv_font_t * lv_tiny_ttf_create_file(const char * path, lv_coord_t font_size); + +/* create a font from the specified file or path with the specified line height with the specified cache size.*/ +lv_font_t * lv_tiny_ttf_create_file_ex(const char * path, lv_coord_t font_size, size_t cache_size); +#endif /*LV_TINY_TTF_FILE_SUPPORT*/ + +/* create a font from the specified data pointer with the specified line height.*/ +lv_font_t * lv_tiny_ttf_create_data(const void * data, size_t data_size, lv_coord_t font_size); + +/* create a font from the specified data pointer with the specified line height and the specified cache size.*/ +lv_font_t * lv_tiny_ttf_create_data_ex(const void * data, size_t data_size, lv_coord_t font_size, size_t cache_size); + +/* set the size of the font to a new font_size*/ +void lv_tiny_ttf_set_size(lv_font_t * font, lv_coord_t font_size); + +/* destroy a font previously created with lv_tiny_ttf_create_xxxx()*/ +void lv_tiny_ttf_destroy(lv_font_t * font); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_TINY_TTF*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_TINY_TTF_H*/ diff --git a/lib/libesp32_lvgl/lvgl/src/extra/libs/tiny_ttf/stb_rect_pack.h b/lib/libesp32_lvgl/lvgl/src/extra/libs/tiny_ttf/stb_rect_pack.h new file mode 100644 index 000000000..413e5047d --- /dev/null +++ b/lib/libesp32_lvgl/lvgl/src/extra/libs/tiny_ttf/stb_rect_pack.h @@ -0,0 +1,637 @@ +// stb_rect_pack.h - v1.01 - public domain - rectangle packing +// Sean Barrett 2014 +// +// Useful for e.g. packing rectangular textures into an atlas. +// Does not do rotation. +// +// Before #including, +// +// #define STB_RECT_PACK_IMPLEMENTATION +// +// in the file that you want to have the implementation. +// +// Not necessarily the awesomest packing method, but better than +// the totally naive one in stb_truetype (which is primarily what +// this is meant to replace). +// +// Has only had a few tests run, may have issues. +// +// More docs to come. +// +// No memory allocations; uses qsort() and assert() from stdlib. +// Can override those by defining STBRP_SORT and STBRP_ASSERT. +// +// This library currently uses the Skyline Bottom-Left algorithm. +// +// Please note: better rectangle packers are welcome! Please +// implement them to the same API, but with a different init +// function. +// +// Credits +// +// Library +// Sean Barrett +// Minor features +// Martins Mozeiko +// github:IntellectualKitty +// +// Bugfixes / warning fixes +// Jeremy Jaussaud +// Fabian Giesen +// +// Version history: +// +// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section +// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles +// 0.99 (2019-02-07) warning fixes +// 0.11 (2017-03-03) return packing success/fail result +// 0.10 (2016-10-25) remove cast-away-const to avoid warnings +// 0.09 (2016-08-27) fix compiler warnings +// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) +// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) +// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort +// 0.05: added STBRP_ASSERT to allow replacing assert +// 0.04: fixed minor bug in STBRP_LARGE_RECTS support +// 0.01: initial release +// +// LICENSE +// +// See end of file for license information. + +////////////////////////////////////////////////////////////////////////////// +// +// INCLUDE SECTION +// + +#ifndef STB_INCLUDE_STB_RECT_PACK_H +#define STB_INCLUDE_STB_RECT_PACK_H + +#define STB_RECT_PACK_VERSION 1 + +#ifdef STBRP_STATIC + #define STBRP_DEF static +#else + #define STBRP_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/// @cond +/** + * Tells Doxygen to ignore a duplicate declaration + */ +typedef struct stbrp_context stbrp_context; +typedef struct stbrp_node stbrp_node; +typedef struct stbrp_rect stbrp_rect; +/// @endcond + +typedef int stbrp_coord; + +#define STBRP__MAXVAL 0x7fffffff +// Mostly for internal use, but this is the maximum supported coordinate value. + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + + +STBRP_DEF int stbrp_pack_rects(stbrp_context * context, stbrp_rect * rects, int num_rects); +// Assign packed locations to rectangles. The rectangles are of type +// 'stbrp_rect' defined below, stored in the array 'rects', and there +// are 'num_rects' many of them. +// +// Rectangles which are successfully packed have the 'was_packed' flag +// set to a non-zero value and 'x' and 'y' store the minimum location +// on each axis (i.e. bottom-left in cartesian coordinates, top-left +// if you imagine y increasing downwards). Rectangles which do not fit +// have the 'was_packed' flag set to 0. +// +// You should not try to access the 'rects' array from another thread +// while this function is running, as the function temporarily reorders +// the array while it executes. +// +// To pack into another rectangle, you need to call stbrp_init_target +// again. To continue packing into the same rectangle, you can call +// this function again. Calling this multiple times with multiple rect +// arrays will probably produce worse packing results than calling it +// a single time with the full rectangle array, but the option is +// available. +// +// The function returns 1 if all of the rectangles were successfully +// packed and 0 otherwise. + +struct stbrp_rect { + // reserved for your use: + int id; + + // input: + stbrp_coord w, h; + + // output: + stbrp_coord x, y; + int was_packed; // non-zero if valid packing + +}; // 16 bytes, nominally + + +STBRP_DEF void stbrp_init_target(stbrp_context * context, int width, int height, stbrp_node * nodes, int num_nodes); +// Initialize a rectangle packer to: +// pack a rectangle that is 'width' by 'height' in dimensions +// using temporary storage provided by the array 'nodes', which is 'num_nodes' long +// +// You must call this function every time you start packing into a new target. +// +// There is no "shutdown" function. The 'nodes' memory must stay valid for +// the following stbrp_pack_rects() call (or calls), but can be freed after +// the call (or calls) finish. +// +// Note: to guarantee best results, either: +// 1. make sure 'num_nodes' >= 'width' +// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' +// +// If you don't do either of the above things, widths will be quantized to multiples +// of small integers to guarantee the algorithm doesn't run out of temporary storage. +// +// If you do #2, then the non-quantized algorithm will be used, but the algorithm +// may run out of temporary storage and be unable to pack some rectangles. + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context * context, int allow_out_of_mem); +// Optionally call this function after init but before doing any packing to +// change the handling of the out-of-temp-memory scenario, described above. +// If you call init again, this will be reset to the default (false). + + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context * context, int heuristic); +// Optionally select which packing heuristic the library should use. Different +// heuristics will produce better/worse results for different data sets. +// If you call init again, this will be reset to the default. + +enum { + STBRP_HEURISTIC_Skyline_default = 0, + STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, + STBRP_HEURISTIC_Skyline_BF_sortHeight +}; + + +////////////////////////////////////////////////////////////////////////////// +// +// the details of the following structures don't matter to you, but they must +// be visible so you can handle the memory allocations for them + +struct stbrp_node { + stbrp_coord x, y; + stbrp_node * next; +}; + +struct stbrp_context { + int width; + int height; + int align; + int init_mode; + int heuristic; + int num_nodes; + stbrp_node * active_head; + stbrp_node * free_head; + stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' +}; + +#ifdef __cplusplus +} +#endif + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION SECTION +// + +#ifdef STB_RECT_PACK_IMPLEMENTATION +#ifndef STBRP_SORT + #include + #define STBRP_SORT qsort +#endif + +#ifndef STBRP_ASSERT + #include + #define STBRP_ASSERT assert +#endif + +#ifdef _MSC_VER + #define STBRP__NOTUSED(v) (void)(v) + #define STBRP__CDECL __cdecl +#else + #define STBRP__NOTUSED(v) (void)sizeof(v) + #define STBRP__CDECL +#endif + +enum { + STBRP__INIT_skyline = 1 +}; + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context * context, int heuristic) +{ + switch(context->init_mode) { + case STBRP__INIT_skyline: + STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); + context->heuristic = heuristic; + break; + default: + STBRP_ASSERT(0); + } +} + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context * context, int allow_out_of_mem) +{ + if(allow_out_of_mem) + // if it's ok to run out of memory, then don't bother aligning them; + // this gives better packing, but may fail due to OOM (even though + // the rectangles easily fit). @TODO a smarter approach would be to only + // quantize once we've hit OOM, then we could get rid of this parameter. + context->align = 1; + else { + // if it's not ok to run out of memory, then quantize the widths + // so that num_nodes is always enough nodes. + // + // I.e. num_nodes * align >= width + // align >= width / num_nodes + // align = ceil(width/num_nodes) + + context->align = (context->width + context->num_nodes - 1) / context->num_nodes; + } +} + +STBRP_DEF void stbrp_init_target(stbrp_context * context, int width, int height, stbrp_node * nodes, int num_nodes) +{ + int i; + + for(i = 0; i < num_nodes - 1; ++i) + nodes[i].next = &nodes[i + 1]; + nodes[i].next = NULL; + context->init_mode = STBRP__INIT_skyline; + context->heuristic = STBRP_HEURISTIC_Skyline_default; + context->free_head = &nodes[0]; + context->active_head = &context->extra[0]; + context->width = width; + context->height = height; + context->num_nodes = num_nodes; + stbrp_setup_allow_out_of_mem(context, 0); + + // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) + context->extra[0].x = 0; + context->extra[0].y = 0; + context->extra[0].next = &context->extra[1]; + context->extra[1].x = (stbrp_coord) width; + context->extra[1].y = (1 << 30); + context->extra[1].next = NULL; +} + +// find minimum y position if it starts at x1 +static int stbrp__skyline_find_min_y(stbrp_context * c, stbrp_node * first, int x0, int width, int * pwaste) +{ + stbrp_node * node = first; + int x1 = x0 + width; + int min_y, visited_width, waste_area; + + STBRP__NOTUSED(c); + + STBRP_ASSERT(first->x <= x0); + +#if 0 + // skip in case we're past the node + while(node->next->x <= x0) + ++node; +#else + STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency +#endif + + STBRP_ASSERT(node->x <= x0); + + min_y = 0; + waste_area = 0; + visited_width = 0; + while(node->x < x1) { + if(node->y > min_y) { + // raise min_y higher. + // we've accounted for all waste up to min_y, + // but we'll now add more waste for everything we've visited + waste_area += visited_width * (node->y - min_y); + min_y = node->y; + // the first time through, visited_width might be reduced + if(node->x < x0) + visited_width += node->next->x - x0; + else + visited_width += node->next->x - node->x; + } + else { + // add waste area + int under_width = node->next->x - node->x; + if(under_width + visited_width > width) + under_width = width - visited_width; + waste_area += under_width * (min_y - node->y); + visited_width += under_width; + } + node = node->next; + } + + *pwaste = waste_area; + return min_y; +} + +typedef struct { + int x, y; + stbrp_node ** prev_link; +} stbrp__findresult; + +static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context * c, int width, int height) +{ + int best_waste = (1 << 30), best_x, best_y = (1 << 30); + stbrp__findresult fr; + stbrp_node ** prev, * node, * tail, ** best = NULL; + + // align to multiple of c->align + width = (width + c->align - 1); + width -= width % c->align; + STBRP_ASSERT(width % c->align == 0); + + // if it can't possibly fit, bail immediately + if(width > c->width || height > c->height) { + fr.prev_link = NULL; + fr.x = fr.y = 0; + return fr; + } + + node = c->active_head; + prev = &c->active_head; + while(node->x + width <= c->width) { + int y, waste; + y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); + if(c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL + // bottom left + if(y < best_y) { + best_y = y; + best = prev; + } + } + else { + // best-fit + if(y + height <= c->height) { + // can only use it if it first vertically + if(y < best_y || (y == best_y && waste < best_waste)) { + best_y = y; + best_waste = waste; + best = prev; + } + } + } + prev = &node->next; + node = node->next; + } + + best_x = (best == NULL) ? 0 : (*best)->x; + + // if doing best-fit (BF), we also have to try aligning right edge to each node position + // + // e.g, if fitting + // + // ____________________ + // |____________________| + // + // into + // + // | | + // | ____________| + // |____________| + // + // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned + // + // This makes BF take about 2x the time + + if(c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { + tail = c->active_head; + node = c->active_head; + prev = &c->active_head; + // find first node that's admissible + while(tail->x < width) + tail = tail->next; + while(tail) { + int xpos = tail->x - width; + int y, waste; + STBRP_ASSERT(xpos >= 0); + // find the left position that matches this + while(node->next->x <= xpos) { + prev = &node->next; + node = node->next; + } + STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); + y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); + if(y + height <= c->height) { + if(y <= best_y) { + if(y < best_y || waste < best_waste || (waste == best_waste && xpos < best_x)) { + best_x = xpos; + STBRP_ASSERT(y <= best_y); + best_y = y; + best_waste = waste; + best = prev; + } + } + } + tail = tail->next; + } + } + + fr.prev_link = best; + fr.x = best_x; + fr.y = best_y; + return fr; +} + +static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context * context, int width, int height) +{ + // find best position according to heuristic + stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); + stbrp_node * node, * cur; + + // bail if: + // 1. it failed + // 2. the best node doesn't fit (we don't always check this) + // 3. we're out of memory + if(res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { + res.prev_link = NULL; + return res; + } + + // on success, create new node + node = context->free_head; + node->x = (stbrp_coord) res.x; + node->y = (stbrp_coord)(res.y + height); + + context->free_head = node->next; + + // insert the new node into the right starting point, and + // let 'cur' point to the remaining nodes needing to be + // stiched back in + + cur = *res.prev_link; + if(cur->x < res.x) { + // preserve the existing one, so start testing with the next one + stbrp_node * next = cur->next; + cur->next = node; + cur = next; + } + else { + *res.prev_link = node; + } + + // from here, traverse cur and free the nodes, until we get to one + // that shouldn't be freed + while(cur->next && cur->next->x <= res.x + width) { + stbrp_node * next = cur->next; + // move the current node to the free list + cur->next = context->free_head; + context->free_head = cur; + cur = next; + } + + // stitch the list back in + node->next = cur; + + if(cur->x < res.x + width) + cur->x = (stbrp_coord)(res.x + width); + +#ifdef _DEBUG + cur = context->active_head; + while(cur->x < context->width) { + STBRP_ASSERT(cur->x < cur->next->x); + cur = cur->next; + } + STBRP_ASSERT(cur->next == NULL); + + { + int count = 0; + cur = context->active_head; + while(cur) { + cur = cur->next; + ++count; + } + cur = context->free_head; + while(cur) { + cur = cur->next; + ++count; + } + STBRP_ASSERT(count == context->num_nodes + 2); + } +#endif + + return res; +} + +static int STBRP__CDECL rect_height_compare(const void * a, const void * b) +{ + const stbrp_rect * p = (const stbrp_rect *) a; + const stbrp_rect * q = (const stbrp_rect *) b; + if(p->h > q->h) + return -1; + if(p->h < q->h) + return 1; + return (p->w > q->w) ? -1 : (p->w < q->w); +} + +static int STBRP__CDECL rect_original_order(const void * a, const void * b) +{ + const stbrp_rect * p = (const stbrp_rect *) a; + const stbrp_rect * q = (const stbrp_rect *) b; + return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); +} + +STBRP_DEF int stbrp_pack_rects(stbrp_context * context, stbrp_rect * rects, int num_rects) +{ + int i, all_rects_packed = 1; + + // we use the 'was_packed' field internally to allow sorting/unsorting + for(i = 0; i < num_rects; ++i) { + rects[i].was_packed = i; + } + + // sort according to heuristic + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); + + for(i = 0; i < num_rects; ++i) { + if(rects[i].w == 0 || rects[i].h == 0) { + rects[i].x = rects[i].y = 0; // empty rect needs no space + } + else { + stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); + if(fr.prev_link) { + rects[i].x = (stbrp_coord) fr.x; + rects[i].y = (stbrp_coord) fr.y; + } + else { + rects[i].x = rects[i].y = STBRP__MAXVAL; + } + } + } + + // unsort + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); + + // set was_packed flags and all_rects_packed status + for(i = 0; i < num_rects; ++i) { + rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); + if(!rects[i].was_packed) + all_rects_packed = 0; + } + + // return the all_rects_packed status + return all_rects_packed; +} +#endif + +#if defined(__GNUC__) || defined(__clang__) + #pragma GCC diagnostic pop +#endif + +/* +------------------------------------------------------------------------------ +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/lib/libesp32_lvgl/lvgl/src/extra/libs/tiny_ttf/stb_truetype_htcw.h b/lib/libesp32_lvgl/lvgl/src/extra/libs/tiny_ttf/stb_truetype_htcw.h new file mode 100644 index 000000000..d0e839d9d --- /dev/null +++ b/lib/libesp32_lvgl/lvgl/src/extra/libs/tiny_ttf/stb_truetype_htcw.h @@ -0,0 +1,5572 @@ +// stb_truetype.h - v1.26htcw (fork to enable streaming and low memory environments) +// 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 +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// 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 +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// 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 +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// 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() -- 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 +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// 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 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 +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// 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.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; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1 << 25]; + +int main(int argc, char ** argv) +{ + stbtt_fontinfo font; + unsigned char * bitmap; + int w, h, i, j, c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1 << 25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer, 0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0, stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0, 0); + + for(j = 0; j < h; ++j) { + for(i = 0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j * w + i] >> 5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24 << 20]; +unsigned char screen[20][79]; + +int main(int arg, char ** argv) +{ + stbtt_fontinfo font; + int i, j, ascent, baseline, ch = 0; + float scale, xpos = 2; // leave a little padding in case the character extends left + char * text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent, 0, 0); + baseline = (int)(ascent * scale); + + while(text[ch]) { + int advance, lsb, x0, y0, x1, y1; + float x_shift = xpos - (float)floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale, scale, x_shift, 0, &x0, &y0, &x1, &y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int)xpos + x0], x1 - x0, y1 - y0, 79, scale, scale, + x_shift, 0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if(text[ch + 1]) + xpos += scale * stbtt_GetCodepointKernAdvance(&font, text[ch], text[ch + 1]); + ++ch; + } + + for(j = 0; j < 20; ++j) { + for(i = 0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i] >> 5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// 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 +#ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; +#endif + +typedef char stbtt__check_size32[sizeof(stbtt_int32) == 4 ? 1 : -1]; +typedef char stbtt__check_size16[sizeof(stbtt_int16) == 2 ? 1 : -1]; + +// define STBTT_STDIO_STREAM to stream from a FILE object +// instead of from memory. Or define STBTT_STREAM_TYPE, +// STBTT_STREAM_READ and STBTT_STREAM_SEEK to implement +// another streaming source +#ifdef STBTT_STDIO_STREAM + #include + #define STBTT_STREAM_TYPE FILE* + #define STBTT_STREAM_READ(s,x,y) fread(x,1,y,s); + #define STBTT_STREAM_SEEK(s,x) fseek(s,x,SEEK_SET); +#endif + +// heap factor sizes for various counts of objects +// adjust for your platform. Below is suitable for +// modern PC class machines. +#ifndef STBTT_HEAP_FACTOR_SIZE_32 + #define STBTT_HEAP_FACTOR_SIZE_32 2000 +#endif + +#ifndef STBTT_HEAP_FACTOR_SIZE_128 + #define STBTT_HEAP_FACTOR_SIZE_128 800 +#endif + +#ifndef STBTT_HEAP_FACTOR_SIZE_DEFAULT + #define STBTT_HEAP_FACTOR_SIZE_DEFAULT 100 +#endif + +// e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h +#ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) +#endif + +#ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) (float)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 + #include + #define STBTT_fabs(x) (float)fabs(x) +#endif + +// #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h +#ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) +#endif + +#ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) +#endif + +#ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) +#endif + +#ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset +#endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC + #define STBTT_DEF static +#else + #define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct { +#ifdef STBTT_STREAM_TYPE + STBTT_STREAM_TYPE data; + stbtt_uint32 offset; +#else + unsigned char * data; +#endif + int cursor; + int size; +} stbtt__buf; + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct { + unsigned short x0, y0, x1, y1; // coordinates of bbox in bitmap + float xoff, yoff, xadvance; +} stbtt_bakedchar; + +typedef struct { + float x0, y0, s0, t0; // top-left + float x1, y1, s1, t1; // bottom-right +} stbtt_aligned_quad; + +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 + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct { + unsigned short x0, y0, x1, y1; // coordinates of bbox in bitmap + float xoff, yoff, xadvance; + float xoff2, yoff2; +} stbtt_packedchar; + +/// @cond +/** + * Tells Doxygen to ignore a duplicate declaration + */ +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +/// @endcond + +#ifndef STB_RECT_PACK_VERSION +/// @cond +/** + * Tells Doxygen to ignore a duplicate declaration + */ +typedef struct stbrp_rect stbrp_rect; +/// @endcond + +#endif +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 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 +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd(stbtt_pack_context * spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +typedef struct { + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int * array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar * chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context * spc, unsigned int h_oversample, + unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +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 received 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, 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, 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 +// 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 +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void * user_allocator_context; + void * pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char * pixels; + void * nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// +#ifdef STBTT_STREAM_TYPE +STBTT_DEF int stbtt_GetNumberOfFonts(STBTT_STREAM_TYPE data); +#else +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char * data); +#endif +// 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. +#ifdef STBTT_STREAM_TYPE +STBTT_DEF int stbtt_GetFontOffsetForIndex(STBTT_STREAM_TYPE, int index); +#else +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char * data, int index); +#endif + +// 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. + +// 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. +struct stbtt_fontinfo { + void * userdata; +#ifdef STBTT_STREAM_TYPE + STBTT_STREAM_TYPE data; +#else + unsigned char * data; // pointer to .ttf file +#endif + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + 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__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 +}; +#ifdef STBTT_STREAM_TYPE +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo * info, STBTT_STREAM_TYPE data, int offset); +#else +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo * info, const unsigned char * data, int offset); +#endif +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo * info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// 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. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo * info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo * info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo * info, int * ascent, int * descent, int * lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// 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 + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo * info, int codepoint, int * advanceWidth, + int * leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo * info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo * info, int codepoint, int * x0, int * y0, int * x1, int * y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo * info, int glyph_index, int * advanceWidth, + int * leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo * info, int glyph1, int glyph2); +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) + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) +enum { + STBTT_vmove = 1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic +}; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values +// (we share this with other code at RAD) +#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, cx1, cy1; + unsigned char type, padding; +} stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo * info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo * info, int unicode_codepoint, stbtt_vertex ** vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo * info, int glyph_index, stbtt_vertex ** vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// 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 +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo * info, stbtt_vertex * vertices); +// frees the data allocated above + +STBTT_DEF stbtt_uint32 stbtt_FindSVGDoc(const stbtt_fontinfo * info, int gl); +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo * info, int unicode_codepoint, stbtt_uint32 * svgOfs); +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo * info, int gl, stbtt_uint32 * svgOfs); +// fills svg with the character's SVG data. +// returns data size or 0 if SVG not found. + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char * bitmap, void * userdata); +// frees the bitmap allocated below + +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); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +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); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +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); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +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); +// 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 +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo * font, int codepoint, float scale_x, + float scale_y, float shift_x, float shift_y, int * ix0, int * iy0, int * ix1, int * iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +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); +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); + + +// @TODO: don't expose this structure +typedef struct { + int w, h, stride; + unsigned char * pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap * result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex * vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + 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... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + +#ifdef STBTT_STREAM_TYPE +STBTT_DEF int stbtt_FindMatchingFont(STBTT_STREAM_TYPE fontdata, const char * name, int flags); +#else +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char * fontdata, const char * name, int flags); +#endif +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +#ifdef STBTT_STREAM_TYPE +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char * s1, int len1, STBTT_STREAM_TYPE s2, stbtt_uint32 s2offs, + int len2); +#else +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char * s1, int len1, const char * s2, stbtt_uint32 s2offs, + int len2); +#endif +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF stbtt_uint32 stbtt_GetFontNameString(const stbtt_fontinfo * font, int * length, int platformID, + int encodingID, int languageID, int nameID); + +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE = 0, + STBTT_PLATFORM_ID_MAC = 1, + STBTT_PLATFORM_ID_ISO = 2, + STBTT_PLATFORM_ID_MICROSOFT = 3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 = 0, + STBTT_UNICODE_EID_UNICODE_1_1 = 1, + STBTT_UNICODE_EID_ISO_10646 = 2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP = 3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL = 4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL = 0, + STBTT_MS_EID_UNICODE_BMP = 1, + STBTT_MS_EID_SHIFTJIS = 2, + STBTT_MS_EID_UNICODE_FULL = 10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN = 0, STBTT_MAC_EID_ARABIC = 4, + STBTT_MAC_EID_JAPANESE = 1, STBTT_MAC_EID_HEBREW = 5, + STBTT_MAC_EID_CHINESE_TRAD = 2, STBTT_MAC_EID_GREEK = 6, + STBTT_MAC_EID_KOREAN = 3, STBTT_MAC_EID_RUSSIAN = 7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH = 0x0409, STBTT_MS_LANG_ITALIAN = 0x0410, + STBTT_MS_LANG_CHINESE = 0x0804, STBTT_MS_LANG_JAPANESE = 0x0411, + STBTT_MS_LANG_DUTCH = 0x0413, STBTT_MS_LANG_KOREAN = 0x0412, + STBTT_MS_LANG_FRENCH = 0x040c, STBTT_MS_LANG_RUSSIAN = 0x0419, + STBTT_MS_LANG_GERMAN = 0x0407, STBTT_MS_LANG_SPANISH = 0x0409, + STBTT_MS_LANG_HEBREW = 0x040d, STBTT_MS_LANG_SWEDISH = 0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH = 0, STBTT_MAC_LANG_JAPANESE = 11, + STBTT_MAC_LANG_ARABIC = 12, STBTT_MAC_LANG_KOREAN = 23, + STBTT_MAC_LANG_DUTCH = 4, STBTT_MAC_LANG_RUSSIAN = 32, + STBTT_MAC_LANG_FRENCH = 1, STBTT_MAC_LANG_SPANISH = 6, + STBTT_MAC_LANG_GERMAN = 2, STBTT_MAC_LANG_SWEDISH = 5, + STBTT_MAC_LANG_HEBREW = 10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED = 33, + STBTT_MAC_LANG_ITALIAN = 3, STBTT_MAC_LANG_CHINESE_TRAD = 19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE + #define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 + #error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE - 1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION + #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; +#ifdef STBTT_STREAM_TYPE + long pos = (long)(b->cursor + b->offset); + STBTT_STREAM_SEEK(b->data, pos); + stbtt_uint8 result; + STBTT_STREAM_READ(b->data, &result, 1); + ++b->cursor; + return result; +#else + return b->data[b->cursor++]; +#endif + +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf * b) +{ + if(b->cursor >= b->size) + return 0; +#ifdef STBTT_STREAM_TYPE + long pos = (long)(b->cursor + b->offset); + STBTT_STREAM_SEEK(b->data, pos); + stbtt_uint8 result; + STBTT_STREAM_READ(b->data, &result, 1); + return result; +#else + return b->data[b->cursor]; +#endif + +} + +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; +} +#ifdef STBTT_STREAM_TYPE + static stbtt__buf stbtt__new_buf(STBTT_STREAM_TYPE s, size_t size) +#else + static stbtt__buf stbtt__new_buf(const void * p, size_t size) +#endif +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); +#ifdef STBTT_STREAM_TYPE + r.data = s; + r.offset = 0; +#else + r.data = (stbtt_uint8 *)p; +#endif + 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; +#ifdef STBTT_STREAM_TYPE + r.data = b->data; + r.offset = b->offset + o; +#else + r.data = b->data + o; +#endif + 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 +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + + +#ifdef STBTT_STREAM_TYPE +static stbtt_uint8 ttBYTE(STBTT_STREAM_TYPE s, stbtt_uint32 offset) +{ + STBTT_STREAM_SEEK(s, offset); + stbtt_uint8 r; + STBTT_STREAM_READ(s, &r, 1); + return r; +} +#define ttCHAR(s, offset) ((stbtt_int8)ttBYTE(s,offset)) +static stbtt_uint16 ttUSHORT(STBTT_STREAM_TYPE s, stbtt_uint32 offset) +{ + STBTT_STREAM_SEEK(s, offset); + stbtt_uint8 r[2]; + STBTT_STREAM_READ(s, &r, 2); + return r[0] * 256 + r[1]; +} +static stbtt_int16 ttSHORT(STBTT_STREAM_TYPE s, stbtt_uint32 offset) +{ + STBTT_STREAM_SEEK(s, offset); + stbtt_uint8 r[2]; + STBTT_STREAM_READ(s, &r, 2); + return r[0] * 256 + r[1]; +} +static stbtt_uint32 ttULONG(STBTT_STREAM_TYPE s, stbtt_uint32 offset) +{ + STBTT_STREAM_SEEK(s, offset); + stbtt_uint8 r[4]; + STBTT_STREAM_READ(s, &r, 4); + return (r[0] << 24) + (r[1] << 16) + (r[2] << 8) + r[3]; +} +static stbtt_int32 ttLONG(STBTT_STREAM_TYPE s, stbtt_uint32 offset) +{ + STBTT_STREAM_SEEK(s, offset); + stbtt_uint8 r[4]; + STBTT_STREAM_READ(s, &r, 4); + return (r[0] << 24) + (r[1] << 16) + (r[2] << 8) + r[3]; +} +#else +#define ttBYTE(p, offset) (* (stbtt_uint8 *) (p+offset)) +#define ttCHAR(p, offset) (* (stbtt_int8 *) (p+offset)) +static stbtt_uint16 ttUSHORT(const stbtt_uint8 * p, stbtt_uint32 offset) +{ + return p[offset + 0] * 256 + p[offset + 1]; +} +static stbtt_int16 ttSHORT(const stbtt_uint8 * p, stbtt_uint32 offset) +{ + return p[offset + 0] * 256 + p[offset + 1]; +} +static stbtt_uint32 ttULONG(const stbtt_uint8 * p, stbtt_uint32 offset) +{ + return (p[offset + 0] << 24) + (p[offset + 1] << 16) + (p[offset + 2] << 8) + p[offset + 3]; +} +static stbtt_int32 ttLONG(const stbtt_uint8 * p, stbtt_uint32 offset) +{ + return (p[offset + 0] << 24) + (p[offset + 1] << 16) + (p[offset + 2] << 8) + p[offset + 3]; +} +#endif +#define ttFixed(p, offset) ttLONG(p, offset) + +#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]) +#ifdef STBTT_STREAM_TYPE + static int stbtt__isfont(STBTT_STREAM_TYPE stream, stbtt_uint32 offs) +#else + static int stbtt__isfont(stbtt_uint8 * font, stbtt_uint32 offs) +#endif +{ +#ifdef STBTT_STREAM_TYPE + stbtt_uint8 font[4]; + STBTT_STREAM_SEEK(stream, offs); + STBTT_STREAM_READ(stream, font, 4); +#else + font += offs; +#endif + // 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; +} + +// @OPTIMIZE: binary search +#ifdef STBTT_STREAM_TYPE + static stbtt_uint32 stbtt__find_table(STBTT_STREAM_TYPE data, stbtt_uint32 fontstart, const char * tag) +#else + static stbtt_uint32 stbtt__find_table(stbtt_uint8 * data, stbtt_uint32 fontstart, const char * tag) +#endif +{ + stbtt_int32 num_tables = ttUSHORT(data, fontstart + 4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for(i = 0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16 * i; +#ifdef STBTT_STREAM_TYPE + stbtt_uint8 buf[4]; + STBTT_STREAM_SEEK(data, loc + 0); + STBTT_STREAM_READ(data, buf, 4); + if(stbtt_tag(buf, tag)) + return ttULONG(data, loc + 8); +#else + if(stbtt_tag(data + loc + 0, tag)) + return ttULONG(data, loc + 8); +#endif + } + return 0; +} +#ifdef STBTT_STREAM_TYPE + static int stbtt_GetFontOffsetForIndex_internal(STBTT_STREAM_TYPE font_collection, int index) +#else + static int stbtt_GetFontOffsetForIndex_internal(unsigned char * font_collection, int index) +#endif +{ + // if it's just a font, there's only one valid index + if(stbtt__isfont(font_collection, 0)) + return index == 0 ? 0 : -1; + + // check if it's a TTC +#ifdef STBTT_STREAM_TYPE + stbtt_uint8 buf[4]; + STBTT_STREAM_SEEK(font_collection, 0); + STBTT_STREAM_READ(font_collection, buf, 4); + if(stbtt_tag(buf, "ttcf")) { +#else + if(stbtt_tag(font_collection, "ttcf")) { +#endif + // version 1? + if(ttULONG(font_collection, 4) == 0x00010000 || ttULONG(font_collection, 4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection, 8); + if(index >= n) + return -1; + return ttULONG(font_collection, 12 + index * 4); + } + } + return -1; +} +#ifdef STBTT_STREAM_TYPE + static int stbtt_GetNumberOfFonts_internal(STBTT_STREAM_TYPE font_collection) +#else + static int stbtt_GetNumberOfFonts_internal(unsigned char * font_collection) +#endif +{ + // if it's just a font, there's only one valid font + if(stbtt__isfont(font_collection, 0)) + return 1; + + // check if it's a TTC +#ifdef STBTT_STREAM_TYPE + stbtt_uint8 buf[4]; + STBTT_STREAM_SEEK(font_collection, 0); + STBTT_STREAM_READ(font_collection, buf, 4); + if(stbtt_tag(buf, "ttcf")) { +#else + if(stbtt_tag(font_collection, "ttcf")) { +#endif + // 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; +} +#ifdef STBTT_STREAM_TYPE + static int stbtt_InitFont_internal(stbtt_fontinfo * info, STBTT_STREAM_TYPE data, int fontstart) +#else + static int stbtt_InitFont_internal(stbtt_fontinfo * info, unsigned char * data, int fontstart) +#endif +{ + 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 + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + 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 + 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) + +#ifdef STBTT_STREAM_TYPE + info->cff = stbtt__new_buf(info->data, 512 * 1024 * 1024); + info->cff.offset = cff; +#else + info->cff = stbtt__new_buf(info->data + cff, 512 * 1024 * 1024); +#endif + 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) + info->numGlyphs = ttUSHORT(data, t + 4); + 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. + numTables = ttUSHORT(data, cmap + 2); + info->index_map = 0; + for(i = 0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data, encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch(ttUSHORT(data, encoding_record + 2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data, encoding_record + 4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data, encoding_record + 4); + break; + } + } + if(info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data, info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo * info, int unicode_codepoint) +{ +#ifdef STBTT_STREAM_TYPE + STBTT_STREAM_TYPE data = info->data; +#else + stbtt_uint8 * data = info->data; +#endif + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data, index_map + 0); + if(format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data, index_map + 2); + if(unicode_codepoint < bytes - 6) + return ttBYTE(data, index_map + 6 + unicode_codepoint); + return 0; + } + else if(format == 6) { + stbtt_uint32 first = ttUSHORT(data, index_map + 6); + stbtt_uint32 count = ttUSHORT(data, index_map + 8); + if((stbtt_uint32)unicode_codepoint >= first && (stbtt_uint32)unicode_codepoint < first + count) + return ttUSHORT(data, index_map + 10 + (unicode_codepoint - first) * 2); + return 0; + } + else if(format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } + else if(format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data, index_map + 6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data, index_map + 8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data, index_map + 10); + stbtt_uint16 rangeShift = ttUSHORT(data, index_map + 12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if(unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if(unicode_codepoint >= ttUSHORT(data, search + rangeShift * 2)) + search += rangeShift * 2; + + // now decrement to bias correctly to find smallest + search -= 2; + while(entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data, search + searchRange * 2); + if(unicode_codepoint > end) + search += searchRange * 2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start, last; + stbtt_uint16 item = (stbtt_uint16)((search - endCount) >> 1); + + start = ttUSHORT(data, index_map + 14 + segcount * 2 + 2 + 2 * item); + 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); + if(offset == 0) + return (stbtt_uint16)(unicode_codepoint + ttSHORT(data, index_map + 14 + segcount * 4 + 2 + 2 * item)); + + return ttUSHORT(data, offset + (unicode_codepoint - start) * 2 + index_map + 14 + segcount * 6 + 2 + 2 * item); + } + } + else if(format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data, index_map + 12); + stbtt_int32 low, high; + low = 0; + high = (stbtt_int32)ngroups; + // Binary search the right group. + while(low < high) { + stbtt_int32 mid = low + ((high - low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data, index_map + 16 + mid * 12); + stbtt_uint32 end_char = ttULONG(data, index_map + 16 + mid * 12 + 4); + if((stbtt_uint32)unicode_codepoint < start_char) + high = mid; + else if((stbtt_uint32)unicode_codepoint > end_char) + low = mid + 1; + else { + stbtt_uint32 start_glyph = ttULONG(data, index_map + 16 + mid * 12 + 8); + if(format == 12) + return start_glyph + unicode_codepoint - start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo * info, int unicode_codepoint, stbtt_vertex * *vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex * v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, + stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16)x; + v->y = (stbtt_int16)y; + v->cx = (stbtt_int16)cx; + v->cy = (stbtt_int16)cy; +} + +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 + + if(info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data, info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data, info->loca + glyph_index * 2 + 2) * 2; + } + else { + g1 = info->glyf + ttULONG(info->data, info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG(info->data, info->loca + glyph_index * 4 + 4); + } + + 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) +{ + 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); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo * info, int codepoint, int * x0, int * y0, int * x1, int * y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info, codepoint), x0, y0, x1, y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo * info, int glyph_index) +{ + stbtt_int16 numberOfContours; + 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; +} + +static int stbtt__close_shape(stbtt_vertex * vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if(start_off) { + if(was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx + scx) >> 1, (cy + scy) >> 1, cx, cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx, sy, scx, scy); + } + else { + if(was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx, sy, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, sx, sy, 0, 0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo * info, int glyph_index, stbtt_vertex * *pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint32 endPtsOfContours; +#ifdef STBTT_STREAM_TYPE + STBTT_STREAM_TYPE data = info->data; +#else + stbtt_uint8 * data = info->data; +#endif + stbtt_vertex * vertices = 0; + int num_vertices = 0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if(g < 0) return 0; + + numberOfContours = ttSHORT(data, g); + + if(numberOfContours > 0) { + stbtt_uint8 flags = 0, flagcount; + stbtt_int32 ins, i, j = 0, m, n, next_move, was_off = 0, off, start_off = 0; + stbtt_int32 x, y, cx, cy, sx, sy, scx, scy; + stbtt_uint32 points; + endPtsOfContours = (g + 10); + ins = ttUSHORT(data, g + 10 + numberOfContours * 2); + points = g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1 + ttUSHORT(data, endPtsOfContours + numberOfContours * 2 - 2); + + m = n + 2 * numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *)STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if(vertices == 0) + return 0; + + next_move = 0; + flagcount = 0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for(i = 0; i < n; ++i) { + if(flagcount == 0) { + flags = ttBYTE(data, points++); + if(flags & 8) + flagcount = ttBYTE(data, points++); + } + else + --flagcount; + vertices[off + i].type = flags; + } + + // now load x coordinates + x = 0; + for(i = 0; i < n; ++i) { + flags = vertices[off + i].type; + if(flags & 2) { + stbtt_int16 dx = ttBYTE(data, points++); + x += (flags & 16) ? dx : -dx; // ??? + } + else { + if(!(flags & 16)) { + x = x + (stbtt_int16)(ttBYTE(data, points) * 256 + ttBYTE(data, points + 1)); + points += 2; + } + } + vertices[off + i].x = (stbtt_int16)x; + } + + // now load y coordinates + y = 0; + for(i = 0; i < n; ++i) { + flags = vertices[off + i].type; + if(flags & 4) { + stbtt_int16 dy = ttBYTE(data, points++); + y += (flags & 32) ? dy : -dy; // ??? + } + else { + if(!(flags & 32)) { + y = y + (stbtt_int16)(ttBYTE(data, points) * 256 + ttBYTE(data, points + 1)); + points += 2; + } + } + vertices[off + i].y = (stbtt_int16)y; + } + + // now convert them to our format + num_vertices = 0; + sx = sy = cx = cy = scx = scy = 0; + for(i = 0; i < n; ++i) { + flags = vertices[off + i].type; + x = (stbtt_int16)vertices[off + i].x; + y = (stbtt_int16)vertices[off + i].y; + + if(next_move == i) { + 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 + 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 + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if(!(vertices[off + i + 1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32)vertices[off + i + 1].x) >> 1; + sy = (y + (stbtt_int32)vertices[off + i + 1].y) >> 1; + } + else { + // otherwise just use the next point as our start point + sx = (stbtt_int32)vertices[off + i + 1].x; + sy = (stbtt_int32)vertices[off + i + 1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } + else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove, sx, sy, 0, 0); + was_off = 0; + next_move = 1 + ttUSHORT(data, endPtsOfContours + j * 2); + ++j; + } + else { + if(!(flags & 1)) { // if it's a curve + if(was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx + x) >> 1, (cy + y) >> 1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } + else { + if(was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x, y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x, y, 0, 0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx, sy, scx, scy, cx, cy); + } + else if(numberOfContours < 0) { + // Compound shapes. + int more = 1; + stbtt_uint32 comp = g + 10; + num_vertices = 0; + vertices = 0; + while(more) { + stbtt_uint16 flags, gidx; + 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(data, comp); + comp += 2; + gidx = ttSHORT(data, comp); + comp += 2; + + if(flags & 2) { // XY values + if(flags & 1) { // shorts + mtx[4] = ttSHORT(data, comp); + comp += 2; + mtx[5] = ttSHORT(data, comp); + comp += 2; + } + else { + mtx[4] = ttCHAR(data, comp); + comp += 1; + mtx[5] = ttCHAR(data, comp); + comp += 1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if(flags & (1 << 3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(data, comp) / 16384.0f; + comp += 2; + mtx[1] = mtx[2] = 0; + } + else if(flags & (1 << 6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(data, comp) / 16384.0f; + comp += 2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(data, comp) / 16384.0f; + comp += 2; + } + else if(flags & (1 << 7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(data, comp) / 16384.0f; + comp += 2; + mtx[1] = ttSHORT(data, comp) / 16384.0f; + comp += 2; + mtx[2] = ttSHORT(data, comp) / 16384.0f; + comp += 2; + mtx[3] = ttSHORT(data, 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]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if(comp_num_verts > 0) { + // Transform vertices. + for(i = 0; i < comp_num_verts; ++i) { + stbtt_vertex * v = &comp_verts[i]; + stbtt_vertex_type x, y; + x = v->x; + y = v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0] * x + mtx[2] * y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1] * x + mtx[3] * y + mtx[5])); + x = v->cx; + y = v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0] * x + mtx[2] * y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1] * x + mtx[3] * y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex *)STBTT_malloc((num_vertices + comp_num_verts) * sizeof(stbtt_vertex), info->userdata); + if(!tmp) { + if(vertices) STBTT_free(vertices, info->userdata); + if(comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + 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; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1 << 5); + } + } + else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + 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); + if(glyph_index < numOfLongHorMetrics) { + if(advanceWidth) *advanceWidth = ttSHORT(info->data, info->hmtx + 4 * glyph_index); + if(leftSideBearing) *leftSideBearing = ttSHORT(info->data, info->hmtx + 4 * glyph_index + 2); + } + else { + if(advanceWidth) *advanceWidth = ttSHORT(info->data, info->hmtx + 4 * (numOfLongHorMetrics - 1)); + if(leftSideBearing) *leftSideBearing = ttSHORT(info->data, + info->hmtx + 4 * numOfLongHorMetrics + 2 * (glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo * info) +{ + // we only look at the first table. it must be 'horizontal' and format 0. + if(!info->kern) + return 0; + if(ttUSHORT(info->data, 2 + info->kern) < 1) // number of tables, need at least 1 + return 0; + if(ttUSHORT(info->data, 8 + info->kern) != 1) // horizontal flag must be set in format + return 0; + + return ttUSHORT(info->data, 10 + info->kern); +} + +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo * info, stbtt_kerningentry * table, int table_length) +{ + int k, length; + + // we only look at the first table. it must be 'horizontal' and format 0. + if(!info->kern) + return 0; + if(ttUSHORT(info->data, 2 + info->kern) < 1) // number of tables, need at least 1 + return 0; + if(ttUSHORT(info->data, 8 + info->kern) != 1) // horizontal flag must be set in format + return 0; + + length = ttUSHORT(info->data, 10 + info->kern); + if(table_length < length) + length = table_length; + + for(k = 0; k < length; k++) { + table[k].glyph1 = ttUSHORT(info->data, 18 + (k * 6) + info->kern); + table[k].glyph2 = ttUSHORT(info->data, 20 + (k * 6) + info->kern); + table[k].advance = ttSHORT(info->data, 22 + (k * 6) + info->kern); + } + + return length; +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo * info, int glyph1, int glyph2) +{ + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if(!info->kern) + return 0; + if(ttUSHORT(info->data, info->kern + 2) < 1) // number of tables, need at least 1 + return 0; + if(ttUSHORT(info->data, info->kern + 8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(info->data, info->kern + 10) - 1; + needle = glyph1 << 16 | glyph2; + while(l <= r) { + m = (l + r) >> 1; + straw = ttULONG(info->data, info->kern + 18 + (m * 6)); // note: unaligned read + if(needle < straw) + r = m - 1; + else if(needle > straw) + l = m + 1; + else + return ttSHORT(info->data, info->kern + 22 + (m * 6)); + } + return 0; +} +#ifdef STBTT_STREAM_TYPE + static stbtt_int32 stbtt__GetCoverageIndex(STBTT_STREAM_TYPE data, stbtt_uint32 coverageTable, int glyph) +#else + static stbtt_int32 stbtt__GetCoverageIndex(const stbtt_uint8 * data, stbtt_uint32 coverageTable, int glyph) +#endif +{ + stbtt_uint16 coverageFormat = ttUSHORT(data, coverageTable); + switch(coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(data, coverageTable + 2); + + // Binary search. + stbtt_int32 l = 0, r = glyphCount - 1, m; + int straw, needle = glyph; + while(l <= r) { + stbtt_uint32 glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(data, 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(data, coverageTable + 2); + stbtt_uint32 rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l = 0, r = rangeCount - 1, m; + int strawStart, strawEnd, needle = glyph; + while(l <= r) { + stbtt_uint32 rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(data, rangeRecord); + strawEnd = ttUSHORT(data, rangeRecord + 2); + if(needle < strawStart) + r = m - 1; + else if(needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(data, rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + break; + } + + default: + return -1; // unsupported + } + + return -1; +} +#ifdef STBTT_STREAM_TYPE + static stbtt_int32 stbtt__GetGlyphClass(STBTT_STREAM_TYPE data, stbtt_uint32 classDefTable, int glyph) +#else + static stbtt_int32 stbtt__GetGlyphClass(const stbtt_uint8 * data, stbtt_uint32 classDefTable, int glyph) +#endif +{ + stbtt_uint16 classDefFormat = ttUSHORT(data, classDefTable); + switch(classDefFormat) { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(data, classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(data, classDefTable + 4); + stbtt_uint32 classDef1ValueArray = classDefTable + 6; + + if(glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(data, classDef1ValueArray + 2 * (glyph - startGlyphID)); + break; + } + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(data, classDefTable + 2); + stbtt_uint32 classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l = 0, r = classRangeCount - 1, m; + int strawStart, strawEnd, needle = glyph; + while(l <= r) { + stbtt_uint32 classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(data, classRangeRecord); + strawEnd = ttUSHORT(data, classRangeRecord + 2); + if(needle < strawStart) + r = m - 1; + else if(needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(data, 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_uint32 lookupList; + stbtt_uint16 lookupCount; +#ifdef STBTT_STREAM_TYPE + STBTT_STREAM_TYPE data = info->data; +#else + const stbtt_uint8 * data = info->data; +#endif + stbtt_int32 i, sti; + + if(!info->gpos) return 0; + + if(ttUSHORT(data, 0 + info->gpos) != 1) return 0; // Major version 1 + if(ttUSHORT(data, 2 + info->gpos) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data, 8 + info->gpos); + lookupList = lookupListOffset; + lookupCount = ttUSHORT(data, lookupList); + + for(i = 0; i < lookupCount; ++i) { + stbtt_uint16 lookupOffset = ttUSHORT(data, lookupList + 2 + 2 * i); + stbtt_uint32 lookupTable = lookupList + lookupOffset; + + stbtt_uint16 lookupType = ttUSHORT(data, lookupTable); + stbtt_uint16 subTableCount = ttUSHORT(data, lookupTable + 4); + stbtt_uint32 subTableOffsets = lookupTable + 6; + if(lookupType != 2) // Pair Adjustment Positioning Subtable + continue; + + for(sti = 0; sti < subTableCount; sti++) { + stbtt_uint16 subtableOffset = ttUSHORT(data, subTableOffsets + 2 * sti); + stbtt_uint32 table = lookupTable + subtableOffset; + stbtt_uint16 posFormat = ttUSHORT(data, table); + stbtt_uint16 coverageOffset = ttUSHORT(data, table + 2); + stbtt_int32 coverageIndex = stbtt__GetCoverageIndex(data, table + coverageOffset, glyph1); + if(coverageIndex == -1) continue; + + switch(posFormat) { + case 1: { + stbtt_int32 l, r, m; + int straw, needle; + stbtt_uint16 valueFormat1 = ttUSHORT(data, table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(data, table + 6); + if(valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats? + stbtt_int32 valueRecordPairSizeInBytes = 2; + stbtt_uint16 pairSetCount = ttUSHORT(data, table + 8); + stbtt_uint16 pairPosOffset = ttUSHORT(data, table + 10 + 2 * coverageIndex); + stbtt_uint32 pairValueTable = table + pairPosOffset; + stbtt_uint16 pairValueCount = ttUSHORT(data, pairValueTable); + stbtt_uint32 pairValueArray = pairValueTable + 2; + + if(coverageIndex >= pairSetCount) return 0; + + needle = glyph2; + r = pairValueCount - 1; + l = 0; + + // Binary search. + while(l <= r) { + stbtt_uint16 secondGlyph; + stbtt_uint32 pairValue; + m = (l + r) >> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(data, pairValue); + straw = secondGlyph; + if(needle < straw) + r = m - 1; + else if(needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(data, pairValue + 2); + return xAdvance; + } + } + } + else + return 0; + break; + } + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(data, table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(data, table + 6); + if(valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats? + stbtt_uint16 classDef1Offset = ttUSHORT(data, table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(data, table + 10); + int glyph1class = stbtt__GetGlyphClass(data, table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(data, table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(data, table + 12); + stbtt_uint16 class2Count = ttUSHORT(data, table + 14); + stbtt_uint32 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(data, 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 && !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)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo * info, int codepoint, int * advanceWidth, + int * leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info, codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo * info, int * ascent, int * descent, int * lineGap) +{ + if(ascent) *ascent = ttSHORT(info->data, info->hhea + 4); + if(descent) *descent = ttSHORT(info->data, info->hhea + 6); + 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); + *y0 = ttSHORT(info->data, info->head + 38); + *x1 = ttSHORT(info->data, info->head + 40); + *y1 = ttSHORT(info->data, info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo * info, float height) +{ + int fheight = ttSHORT(info->data, info->hhea + 4) - ttSHORT(info->data, info->hhea + 6); + return (float)height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo * info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data, info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo * info, stbtt_vertex * v) +{ + STBTT_free(v, info->userdata); +} + +STBTT_DEF stbtt_uint32 stbtt_FindSVGDoc(const stbtt_fontinfo * info, int gl) +{ + int i; + stbtt_uint32 svg_doc_list = stbtt__get_svg((stbtt_fontinfo *)info); + + int numEntries = ttUSHORT(info->data, svg_doc_list); + stbtt_uint32 svg_docs = svg_doc_list + 2; + + for(i = 0; i < numEntries; i++) { + stbtt_uint32 svg_doc = svg_docs + (12 * i); + if((gl >= ttUSHORT(info->data, svg_doc)) && (gl <= ttUSHORT(info->data, svg_doc + 2))) + return svg_doc; + } + return 0; +} + +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo * info, int gl, stbtt_uint32 * svgOfs) +{ + stbtt_uint32 svg_doc; + + if(info->svg == 0) + return 0; + + svg_doc = stbtt_FindSVGDoc(info, gl); + if(svg_doc != 0) { + *svgOfs = info->svg + ttULONG(info->data, svg_doc + 4); + return ttULONG(info->data, svg_doc + 8); + } + else { + return 0; + } +} + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo * info, int unicode_codepoint, stbtt_uint32 * svgOfs) +{ + return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svgOfs); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +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 = 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; + if(iy0) *iy0 = 0; + if(ix1) *ix1 = 0; + if(iy1) *iy1 = 0; + } + else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if(ix0) *ix0 = STBTT_ifloor(x0 * scale_x + shift_x); + if(iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if(ix1) *ix1 = STBTT_iceil(x1 * scale_x + shift_x); + if(iy1) *iy1 = STBTT_iceil(-y0 * scale_y + shift_y); + } +} + +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_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y, 0.0f, 0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo * font, int codepoint, float scale_x, + float scale_y, float shift_x, float shift_y, int * ix0, int * iy0, int * ix1, int * iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font, codepoint), scale_x, scale_y, shift_x, shift_y, ix0, + iy0, ix1, iy1); +} + +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) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y, 0.0f, 0.0f, ix0, iy0, ix1, iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk { + struct stbtt__hheap_chunk * next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap { + struct stbtt__hheap_chunk * head; + void * first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void * stbtt__hheap_alloc(stbtt__hheap * hh, size_t size, void * userdata) +{ + if(hh->first_free) { + void * p = hh->first_free; + hh->first_free = *(void **)p; + return p; + } + else { + if(hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? STBTT_HEAP_FACTOR_SIZE_32 : size < 128 ? STBTT_HEAP_FACTOR_SIZE_128 : + STBTT_HEAP_FACTOR_SIZE_DEFAULT); + stbtt__hheap_chunk * c = (stbtt__hheap_chunk *)STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if(c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *)(hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap * hh, void * p) +{ + *(void **)p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap * hh, void * userdata) +{ + stbtt__hheap_chunk * c = hh->head; + while(c) { + stbtt__hheap_chunk * n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0, y0, x1, y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge { + struct stbtt__active_edge * next; +#if STBTT_RASTERIZER_VERSION==1 + int x, dx; + float ey; + int direction; +#elif STBTT_RASTERIZER_VERSION==2 + float fx, fdx, fdy; + float direction; + float sy; + float ey; +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge * stbtt__new_active(stbtt__hheap * hh, stbtt__edge * e, int off_x, float start_point, + void * userdata) +{ + 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); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - + e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge * stbtt__new_active(stbtt__hheap * hh, stbtt__edge * e, int off_x, float start_point, + void * userdata) +{ + 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; + z->fdy = dxdy != 0.0f ? (1.0f / dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char * scanline, int len, stbtt__active_edge * e, int max_weight) +{ + // non-zero winding fill + int x0 = 0, w = 0; + + while(e) { + if(w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; + w += e->direction; + } + else { + int x1 = e->x; + w += e->direction; + // if we went to zero, we need to draw + if(w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if(i < len && j >= 0) { + if(i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8)((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } + else { + if(i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8)(((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if(j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8)(((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for(++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8)max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap * result, stbtt__edge * e, int n, int vsubsample, int off_x, + int off_y, void * userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge * active = NULL; + int y, j = 0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], * scanline; + + if(result->w > 512) + scanline = (unsigned char *)STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float)vsubsample + 1; + + while(j < result->h) { + STBTT_memset(scanline, 0, result->w); + for(s = 0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge ** step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while(*step) { + stbtt__active_edge * z = *step; + if(z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } + else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed = 0; + step = &active; + while(*step && (*step)->next) { + if((*step)->x > (*step)->next->x) { + stbtt__active_edge * t = *step; + stbtt__active_edge * q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if(!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + 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 != 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; + } + + // now process all active edges in XOR fashion + if(active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if(scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float * scanline, int x, stbtt__active_edge * e, float x0, float y0, float x1, + float y1) +{ + if(y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if(y0 > e->ey) return; + if(y1 < e->sy) return; + if(y0 < e->sy) { + x0 += (x1 - x0) * (e->sy - y0) / (y1 - y0); + y0 = e->sy; + } + if(y1 > e->ey) { + x1 += (x1 - x0) * (e->ey - y1) / (y1 - y0); + y1 = e->ey; + } + + if(x0 == x) + STBTT_assert(x1 <= x + 1); + else if(x0 == x + 1) + STBTT_assert(x1 >= x); + else if(x0 <= x) + STBTT_assert(x1 <= x); + else if(x0 >= x + 1) + STBTT_assert(x1 >= x + 1); + else + STBTT_assert(x1 >= x && x1 <= x + 1); + + if(x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1 - y0); + else if(x0 >= x + 1 && x1 >= x + 1) { + /*Nothing to do*/; + } + else { + STBTT_assert(x0 >= x && x0 <= x + 1 && x1 >= x && x1 <= x + 1); + scanline[x] += e->direction * (y1 - y0) * (1 - ((x0 - x) + (x1 - x)) / 2); // coverage = 1 - average x position + } +} + +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; + + while(e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if(e->fdx == 0) { + float x0 = e->fx; + if(x0 < len) { + if(x0 >= 0) { + stbtt__handle_clipped_edge(scanline, (int)x0, e, x0, y_top, x0, y_bottom); + stbtt__handle_clipped_edge(scanline_fill - 1, (int)x0 + 1, e, x0, y_top, x0, y_bottom); + } + else { + stbtt__handle_clipped_edge(scanline_fill - 1, 0, e, x0, y_top, x0, y_bottom); + } + } + } + else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0, sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if(e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } + else { + x_top = x0; + sy0 = y_top; + } + if(e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } + else { + x_bottom = xb; + sy1 = y_bottom; + } + + if(x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if((int)x_top == (int)x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int)x_top; + height = (sy1 - sy0) * e->direction; + STBTT_assert(x >= 0 && x < len); + 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, y_final, step, sign, area; + // covers 2+ pixels + if(x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + 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 = 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 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; // area of trapezoid is 1*step/2 + area += step; + } + STBTT_assert(STBTT_fabs(area) <= 1.01f); // accumulated error from area += step unless we round step down + STBTT_assert(sy1 > y_final - 0.01f); + + // area covered in the last pixel is the rectangle from all the pixels to the left, + // plus the trapezoid filled by the line segment in this pixel all the way to the right edge + scanline[x2] += area + sign * stbtt__position_trapezoid_area(sy1 - y_final, (float)x2, x2 + 1.0f, x_bottom, x2 + 1.0f); + + // 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 { + // if edge goes outside of box we're drawing, we require + // 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: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // 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 clearly-defined pairs + float y0 = y_top; + float x1 = (float)(x); + float x2 = (float)(x + 1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->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); + stbtt__handle_clipped_edge(scanline, x, e, x1, y1, x2, y2); + stbtt__handle_clipped_edge(scanline, x, e, x2, y2, x3, y3); + } + else if(x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline, x, e, x0, y0, x2, y2); + stbtt__handle_clipped_edge(scanline, x, e, x2, y2, x1, y1); + stbtt__handle_clipped_edge(scanline, x, e, x1, y1, x3, y3); + } + else if(x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline, x, e, x0, y0, x1, y1); + stbtt__handle_clipped_edge(scanline, x, e, x1, y1, x3, y3); + } + else if(x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline, x, e, x0, y0, x1, y1); + stbtt__handle_clipped_edge(scanline, x, e, x1, y1, x3, y3); + } + else if(x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline, x, e, x0, y0, x2, y2); + stbtt__handle_clipped_edge(scanline, x, e, x2, y2, x3, y3); + } + else if(x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline, x, e, x0, y0, x2, y2); + stbtt__handle_clipped_edge(scanline, x, e, x2, y2, x3, y3); + } + else { // one segment + stbtt__handle_clipped_edge(scanline, x, e, x0, y0, x3, y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap * result, stbtt__edge * e, int n, int vsubsample, int off_x, + int off_y, void * userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge * active = NULL; + int y, j = 0, i; + float scanline_data[129], * scanline, * scanline2; + + STBTT__NOTUSED(vsubsample); + + if(result->w > 64) + scanline = (float *)STBTT_malloc((result->w * 2 + 1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float)(off_y + result->h) + 1; + + while(j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge ** step = &active; + + STBTT_memset(scanline, 0, result->w * sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w + 1) * sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while(*step) { + stbtt__active_edge * z = *step; + if(z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } + else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + 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 != 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; + } + + // now process all active edges + if(active) + stbtt__fill_active_edges_new(scanline, scanline2 + 1, result->w, active, scan_y_top); + + { + float sum = 0; + for(i = 0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float)STBTT_fabs(k) * 255 + 0.5f; + m = (int)k; + if(m > 255) m = 255; + result->pixels[j * result->stride + i] = (unsigned char)m; + } + } + // advance all the edges + step = &active; + while(*step) { + stbtt__active_edge * z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if(scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge * p, int n) +{ + int i, j; + for(i = 1; i < n; ++i) { + stbtt__edge t = p[i], * a = &t; + j = i; + while(j > 0) { + stbtt__edge * b = &p[j - 1]; + int c = STBTT__COMPARE(a, b); + if(!c) break; + p[j] = p[j - 1]; + --j; + } + if(i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge * p, int n) +{ + /* threshold for transitioning to insertion sort */ + while(n > 12) { + stbtt__edge t; + int c01, c12, c, m, i, j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0], &p[m]); + c12 = STBTT__COMPARE(&p[m], &p[n - 1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if(c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0], &p[n - 1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n - 1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i = 1; + j = n - 1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for(;; ++i) { + if(!STBTT__COMPARE(&p[i], &p[0])) break; + } + for(;; --j) { + if(!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if(i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if(j < (n - i)) { + stbtt__sort_edges_quicksort(p, j); + p = p + i; + n = n - i; + } + else { + stbtt__sort_edges_quicksort(p + i, n - i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge * p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct { + float x, y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap * result, stbtt__point * pts, int * wcount, int windings, float scale_x, + float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void * userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge * e; + int n, i, j, k, m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for(i = 0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *)STBTT_malloc(sizeof(*e) * (n + 1), userdata); // add an extra one as a sentinel + if(e == 0) return; + n = 0; + + m = 0; + for(i = 0; i < windings; ++i) { + stbtt__point * p = pts + m; + m += wcount[i]; + j = wcount[i] - 1; + for(k = 0; k < wcount[i]; j = k++) { + int a = k, b = j; + // skip the edge if horizontal + if(p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if(invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a = j, b = k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point * points, int n, float x, float y) +{ + if(!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// 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 + float mx = (x0 + 2 * x1 + x2) / 4; + float my = (y0 + 2 * y1 + y2) / 4; + // versus directly drawn line + float dx = (x0 + x2) / 2 - mx; + float dy = (y0 + y2) / 2 - my; + if(n > 16) // 65536 segments on one curve better be enough! + return 1; + if(dx * dx + dy * dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0, y0, (x0 + x1) / 2.0f, (y0 + y1) / 2.0f, mx, my, + objspace_flatness_squared, n + 1); + stbtt__tesselate_curve(points, num_points, mx, my, (x1 + x2) / 2.0f, (y1 + y2) / 2.0f, x2, y2, + objspace_flatness_squared, n + 1); + } + else { + stbtt__add_point(points, *num_points, x2, y2); + *num_points = *num_points + 1; + } + 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) +{ + stbtt__point * points = 0; + int num_points = 0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i, n = 0, start = 0, pass; + + // count how many "moves" there are to get the contour count + for(i = 0; i < num_verts; ++i) + if(vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if(n == 0) return 0; + + *contour_lengths = (int *)STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if(*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for(pass = 0; pass < 2; ++pass) { + float x = 0, y = 0; + if(pass == 1) { + points = (stbtt__point *)STBTT_malloc(num_points * sizeof(points[0]), userdata); + if(points == NULL) goto error; + } + num_points = 0; + n = -1; + for(i = 0; i < num_verts; ++i) { + switch(vertices[i].type) { + case STBTT_vmove: + // start the next contour + if(n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x, y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x, y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +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 = 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); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char * bitmap, void * userdata) +{ + STBTT_free(bitmap, userdata); +} + +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) +{ + int ix0, iy0, ix1, iy1; + stbtt__bitmap gbm; + stbtt_vertex * vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if(scale_x == 0) scale_x = scale_y; + if(scale_y == 0) { + if(scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0, &iy0, &ix1, &iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if(width) *width = gbm.w; + 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) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + 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) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +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) +{ + int ix0, iy0; + stbtt_vertex * vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0, &iy0, 0, 0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if(gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +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_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f, 0.0f, glyph); +} + +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) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, + stbtt_FindGlyphIndex(info, codepoint)); +} + +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) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f, 0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small +#ifdef STBTT_STREAM_TYPE +static int stbtt_BakeFontBitmap_internal(STBTT_STREAM_TYPE 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 + stbtt_bakedchar * chardata) +#else +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 + stbtt_bakedchar * chardata) +#endif +{ + 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 + x = y = 1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for(i = 0; i < num_chars; ++i) { + int advance, lsb, x0, y0, x1, y1, gw, gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale, scale, &x0, &y0, &x1, &y1); + gw = x1 - x0; + gh = y1 - y0; + if(x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if(y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x + gw < pw); + STBTT_assert(y + gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels + x + y * pw, gw, gh, pw, scale, scale, g); + chardata[i].x0 = (stbtt_int16)x; + chardata[i].y0 = (stbtt_int16)y; + chardata[i].x1 = (stbtt_int16)(x + gw); + chardata[i].y1 = (stbtt_int16)(y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float)x0; + chardata[i].yoff = (float)y0; + x = x + gw + 1; + if(y + gh + 1 > bottom_y) + bottom_y = y + gh + 1; + } + return bottom_y; +} + +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; + 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); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct { + int width, height; + int x, y, bottom_y; +} stbrp_context; + +typedef struct { + unsigned char x; +} stbrp_node; + +struct stbrp_rect { + stbrp_coord x, y; + int id, w, h, was_packed; +}; + +static void stbrp_init_target(stbrp_context * con, int pw, int ph, stbrp_node * nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context * con, stbrp_rect * rects, int num_rects) +{ + int i; + for(i = 0; i < num_rects; ++i) { + if(con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if(con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if(con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for(; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context * spc, unsigned char * pixels, int pw, int ph, int stride_in_bytes, + int padding, void * alloc_context) +{ + stbrp_context * context = (stbrp_context *)STBTT_malloc(sizeof(*context), alloc_context); + int num_nodes = pw - padding; + stbrp_node * nodes = (stbrp_node *)STBTT_malloc(sizeof(*nodes) * num_nodes, alloc_context); + + if(context == NULL || nodes == NULL) { + if(context != NULL) STBTT_free(context, alloc_context); + if(nodes != NULL) STBTT_free(nodes, alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + 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); + + if(pixels) + STBTT_memset(pixels, 0, pw * ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd(stbtt_pack_context * spc) +{ + STBTT_free(spc->nodes, spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context * spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if(h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if(v_oversample <= STBTT_MAX_OVERSAMPLE) + 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) +{ + 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; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch(kernel_width) { + case 2: + for(i = 0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char)(total / 2); + } + break; + case 3: + for(i = 0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char)(total / 3); + } + break; + case 4: + for(i = 0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char)(total / 4); + } + break; + case 5: + for(i = 0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char)(total / 5); + } + break; + default: + for(i = 0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char)(total / kernel_width); + } + break; + } + + for(; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char)(total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char * pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + 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; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch(kernel_width) { + case 2: + for(i = 0; i <= safe_h; ++i) { + total += pixels[i * stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i * stride_in_bytes]; + pixels[i * stride_in_bytes] = (unsigned char)(total / 2); + } + break; + case 3: + for(i = 0; i <= safe_h; ++i) { + total += pixels[i * stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i * stride_in_bytes]; + pixels[i * stride_in_bytes] = (unsigned char)(total / 3); + } + break; + case 4: + for(i = 0; i <= safe_h; ++i) { + total += pixels[i * stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i * stride_in_bytes]; + pixels[i * stride_in_bytes] = (unsigned char)(total / 4); + } + break; + case 5: + for(i = 0; i <= safe_h; ++i) { + total += pixels[i * stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i * stride_in_bytes]; + pixels[i * stride_in_bytes] = (unsigned char)(total / 5); + } + break; + default: + for(i = 0; i <= safe_h; ++i) { + total += pixels[i * stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i * stride_in_bytes]; + pixels[i * stride_in_bytes] = (unsigned char)(total / kernel_width); + } + break; + } + + for(; i < h; ++i) { + STBTT_assert(pixels[i * stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i * stride_in_bytes] = (unsigned char)(total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if(!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float) - (oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +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) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char)spc->h_oversample; + ranges[i].v_oversample = (unsigned char)spc->v_oversample; + for(j = 0; j < ranges[i].num_chars; ++j) { + 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); + 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; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo * info, unsigned char * output, int out_w, + int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, + float * sub_x, float * sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if(prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if(prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context * spc, 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; + int old_v_over = spc->v_oversample; + + k = 0; + for(i = 0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h, recip_v, sub_x, sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + 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 && 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]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord)spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0, &y0, &x1, &y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y * spc->stride_in_bytes, + r->w - spc->h_oversample + 1, + r->h - spc->v_oversample + 1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0, 0, + glyph); + + if(spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y * spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if(spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y * spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16)r->x; + bc->y0 = (stbtt_int16)r->y; + bc->x1 = (stbtt_int16)(r->x + r->w); + bc->y1 = (stbtt_int16)(r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float)x0 * recip_h + sub_x; + 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 + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +#ifdef STBTT_STREAM_TYPE +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context * spc, STBTT_STREAM_TYPE fontdata, int font_index, + stbtt_pack_range * ranges, int num_ranges); +#else +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context * spc, const unsigned char * fontdata, int font_index, + stbtt_pack_range * ranges, int num_ranges); +#endif + + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context * spc, stbrp_rect * rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *)spc->pack_info, rects, num_rects); +} +#ifdef STBTT_STREAM_TYPE +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context * spc, STBTT_STREAM_TYPE fontdata, int font_index, + stbtt_pack_range * ranges, int num_ranges) +#else +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context * spc, const unsigned char * fontdata, int font_index, + stbtt_pack_range * ranges, int num_ranges) +#endif +{ + stbtt_fontinfo info; + int i, j, n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect * rects; + + // flag all characters as NOT packed + for(i = 0; i < num_ranges; ++i) + for(j = 0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + 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; +} + +#ifdef STBTT_STREAM_TYPE +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context * spc, STBTT_STREAM_TYPE fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar * chardata_for_range); +#else +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); +#endif + +#ifdef STBTT_STREAM_TYPE +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context * spc, STBTT_STREAM_TYPE fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar * chardata_for_range) +#else +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) +#endif +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + + +#ifdef STBTT_STREAM_TYPE +STBTT_DEF void stbtt_GetScaledFontVMetrics(STBTT_STREAM_TYPE fontdata, int index, float size, float * ascent, + float * descent, float * lineGap); +#else +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char * fontdata, int index, float size, float * ascent, + float * descent, float * lineGap); +#endif + + +#ifdef STBTT_STREAM_TYPE +STBTT_DEF void stbtt_GetScaledFontVMetrics(STBTT_STREAM_TYPE fontdata, int index, float size, float * ascent, + float * descent, float * lineGap) +#else +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char * fontdata, int index, float size, float * ascent, + float * descent, float * lineGap) +#endif +{ + 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; + const stbtt_packedchar * b = chardata + char_index; + + if(align_to_integer) { + float x = (float)STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float)STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } + else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *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.0f) { + float discr = b * b - a * c; + if(discr > 0.0f) { + float rcpna = -1 / a; + float d = (float)STBTT_sqrt(discr); + s0 = (b + d) * rcpna; + s1 = (b - d) * rcpna; + if(s0 >= 0.0f && s0 <= 1.0f) + num_s = 1; + if(d > 0.0f && s1 >= 0.0f && s1 <= 1.0f) { + 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.0f && s0 <= 1.0f) + 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.141592f / 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.0f) { // 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.0f) { // if a is 0, it's linear + if(b != 0.0f) { + 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); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +#ifdef STBTT_STREAM_TYPE +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 * s1, stbtt_int32 len1, STBTT_STREAM_TYPE s2, + stbtt_uint32 s2offs, stbtt_int32 len2) +#else +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 * s1, stbtt_int32 len1, stbtt_uint8 * s2, + stbtt_uint32 s2offs, stbtt_int32 len2) +#endif +{ + stbtt_int32 i = 0; + + // convert utf16 to utf8 and compare the results while converting + while(len2) { + stbtt_uint16 ch = ttUSHORT(s2, s2offs); + if(ch < 0x80) { + if(i >= len1) return -1; + if(s1[i++] != ch) return -1; + } + else if(ch < 0x800) { + if(i + 1 >= len1) return -1; + if(s1[i++] != 0xc0 + (ch >> 6)) return -1; + if(s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } + else if(ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = ttUSHORT(s2, s2offs + 2); + if(i + 3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if(s1[i++] != 0xf0 + (c >> 18)) return -1; + if(s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if(s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if(s1[i++] != 0x80 + ((c) & 0x3f)) return -1; + s2offs += 2; // plus another 2 below + len2 -= 2; + } + else if(ch >= 0xdc00 && ch < 0xe000) { + return -1; + } + else { + if(i + 2 >= len1) return -1; + if(s1[i++] != 0xe0 + (ch >> 12)) return -1; + if(s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if(s1[i++] != 0x80 + ((ch) & 0x3f)) return -1; + } + s2offs += 2; + len2 -= 2; + } + return i; +} +#ifdef STBTT_STREAM_TYPE +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char * s1, int len1, STBTT_STREAM_TYPE s2, stbtt_uint32 s2offs, + int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8 *)s1, len1, s2, s2offs, len2); +} +#else +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char * s1, int len1, char * s2, stbtt_uint32 s2offs, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8 *)s1, len1, (stbtt_uint8 *)s2, s2offs, len2); +} +#endif +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF stbtt_uint32 stbtt_GetFontNameString(const stbtt_fontinfo * font, int * length, int platformID, + int encodingID, int languageID, int nameID) +{ + stbtt_int32 i, count, stringOffset; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(font->data, offset, "name"); + if(!nm) return 0; + + count = ttUSHORT(font->data, nm + 2); + stringOffset = nm + ttUSHORT(font->data, nm + 4); + for(i = 0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if(platformID == ttUSHORT(font->data, loc + 0) && encodingID == ttUSHORT(font->data, loc + 2) + && languageID == ttUSHORT(font->data, loc + 4) && nameID == ttUSHORT(font->data, loc + 6)) { + *length = ttUSHORT(font->data, loc + 8); + return stringOffset + ttUSHORT(font->data, loc + 10); + } + } + return 0; +} +#ifdef STBTT_STREAM_TYPE +static int stbtt__matchpair(STBTT_STREAM_TYPE fc, stbtt_uint32 nm, stbtt_uint8 * name, stbtt_int32 nlen, + stbtt_int32 target_id, stbtt_int32 next_id) +#else +static int stbtt__matchpair(stbtt_uint8 * fc, stbtt_uint32 nm, stbtt_uint8 * name, stbtt_int32 nlen, + stbtt_int32 target_id, stbtt_int32 next_id) +#endif +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc, nm + 2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc, nm + 4); + + for(i = 0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc, loc + 6); + if(id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc, loc + 0), encoding = ttUSHORT(fc, loc + 2), language = ttUSHORT(fc, loc + 4); + + // is this a Unicode encoding? + if(platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc, loc + 8); + stbtt_int32 off = ttUSHORT(fc, loc + 10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc, stringOffset + off, slen); + if(matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if(i + 1 < count && ttUSHORT(fc, loc + 12 + 6) == next_id && ttUSHORT(fc, loc + 12) == platform && + ttUSHORT(fc, loc + 12 + 2) == encoding && ttUSHORT(fc, loc + 12 + 4) == language) { + slen = ttUSHORT(fc, loc + 12 + 8); + off = ttUSHORT(fc, loc + 12 + 10); + if(slen == 0) { + if(matchlen == nlen) + return 1; + } + else if(matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; +#ifdef STBTT_STREAM_TYPE + if(stbtt_CompareUTF8toUTF16_bigendian_internal((char *)(name + matchlen), nlen - matchlen, fc, stringOffset + off, + slen)) +#else + if(stbtt_CompareUTF8toUTF16_bigendian_internal((char *)(name + matchlen), nlen - matchlen, (char *)fc, + stringOffset + off, slen)) +#endif + return 1; + } + } + else { + // if nothing immediately following + if(matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} +#ifdef STBTT_STREAM_TYPE + static int stbtt__matches(STBTT_STREAM_TYPE fc, stbtt_uint32 offset, stbtt_uint8 * name, stbtt_int32 flags) +#else + static int stbtt__matches(stbtt_uint8 * fc, stbtt_uint32 offset, stbtt_uint8 * name, stbtt_int32 flags) +#endif + +{ + stbtt_int32 nlen = (stbtt_int32)STBTT_strlen((char *)name); + stbtt_uint32 nm, hd; + if(!stbtt__isfont(fc, offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if(flags) { + hd = stbtt__find_table(fc, offset, "head"); + if((ttUSHORT(fc, hd + 44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if(!nm) return 0; + + if(flags) { + if(name == NULL) return 1; + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if(stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if(stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if(stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + else { + if(name == NULL) return 1; + if(stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if(stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if(stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} +#ifdef STBTT_STREAM_TYPE + static int stbtt_FindMatchingFont_internal(STBTT_STREAM_TYPE font_collection, char * name_utf8, stbtt_int32 flags) +#else + static int stbtt_FindMatchingFont_internal(unsigned char * font_collection, char * name_utf8, stbtt_int32 flags) +#endif +{ + stbtt_int32 i; + for(i = 0;; ++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if(off < 0) return off; +#ifdef STBTT_STREAM_TYPE + if(stbtt__matches(font_collection, off, (stbtt_uint8 *)name_utf8, flags)) +#else + if(stbtt__matches((stbtt_uint8 *)font_collection, off, (stbtt_uint8 *)name_utf8, flags)) +#endif + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wcast-qual" +#endif + + +#ifdef STBTT_STREAM_TYPE +STBTT_DEF int stbtt_BakeFontBitmap(STBTT_STREAM_TYPE data, int offset, + float pixel_height, unsigned char * pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar * chardata); +#else +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); +#endif + +#ifdef STBTT_STREAM_TYPE +STBTT_DEF int stbtt_BakeFontBitmap(STBTT_STREAM_TYPE data, int offset, + float pixel_height, unsigned char * pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar * chardata) +#else +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) +#endif +{ +#ifdef STBTT_STREAM_TYPE + return stbtt_BakeFontBitmap_internal(data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +#else + return stbtt_BakeFontBitmap_internal((unsigned char *)data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, + chardata); +#endif +} +#ifdef STBTT_STREAM_TYPE +STBTT_DEF int stbtt_GetFontOffsetForIndex(STBTT_STREAM_TYPE data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal(data, index); +} +#else +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char * data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *)data, index); +} +#endif +#ifdef STBTT_STREAM_TYPE +STBTT_DEF int stbtt_GetNumberOfFonts(STBTT_STREAM_TYPE data) +{ + return stbtt_GetNumberOfFonts_internal(data); +} +#else +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char * data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *)data); +} +#endif +#ifdef STBTT_STREAM_TYPE +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo * info, STBTT_STREAM_TYPE data, int offset) +{ + return stbtt_InitFont_internal(info, data, offset); +} +#else +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo * info, const unsigned char * data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *)data, offset); +} +#endif +#ifdef STBTT_STREAM_TYPE +STBTT_DEF int stbtt_FindMatchingFont(STBTT_STREAM_TYPE fontdata, const char * name, int flags) +{ + return stbtt_FindMatchingFont_internal(fontdata, (char *)name, flags); +} +#else +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char * fontdata, const char * name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *)fontdata, (char *)name, flags); +} +#endif +#ifdef STBTT_STREAM_TYPE +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char * s1, int len1, STBTT_STREAM_TYPE s2, stbtt_uint32 s2offs, + int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *)s1, len1, s2, s2offs, len2); +} +#else +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char * s1, int len1, const char * s2, stbtt_uint32 s2offs, + int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *)s1, len1, (char *)s2, s2offs, len2); +} +#endif + +#if defined(__GNUC__) || defined(__clang__) + #pragma GCC diagnostic pop + #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; +// 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 +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 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/lib/libesp32_lvgl/lvgl/src/extra/lv_extra.c b/lib/libesp32_lvgl/lvgl/src/extra/lv_extra.c index 0b5000240..380833725 100644 --- a/lib/libesp32_lvgl/lvgl/src/extra/lv_extra.c +++ b/lib/libesp32_lvgl/lvgl/src/extra/lv_extra.c @@ -50,6 +50,10 @@ void lv_extra_init(void) lv_fs_fatfs_init(); #endif +#if LV_USE_FS_LITTLEFS != '\0' + lv_fs_littlefs_init(); +#endif + #if LV_USE_FS_STDIO != '\0' lv_fs_stdio_init(); #endif diff --git a/lib/libesp32_lvgl/lvgl/src/extra/others/ime/lv_ime_pinyin.c b/lib/libesp32_lvgl/lvgl/src/extra/others/ime/lv_ime_pinyin.c index b1661e4f7..9834154fb 100644 --- a/lib/libesp32_lvgl/lvgl/src/extra/others/ime/lv_ime_pinyin.c +++ b/lib/libesp32_lvgl/lvgl/src/extra/others/ime/lv_ime_pinyin.c @@ -562,6 +562,8 @@ static void lv_ime_pinyin_constructor(const lv_obj_class_t * class_p, lv_obj_t * lv_memset_00(pinyin_ime->py_num, sizeof(pinyin_ime->py_num)); lv_memset_00(pinyin_ime->py_pos, sizeof(pinyin_ime->py_pos)); + lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN); + lv_obj_set_size(obj, LV_PCT(100), LV_PCT(55)); lv_obj_align(obj, LV_ALIGN_BOTTOM_MID, 0, 0); @@ -576,6 +578,7 @@ static void lv_ime_pinyin_constructor(const lv_obj_class_t * class_p, lv_obj_t * lv_obj_add_flag(pinyin_ime->cand_panel, LV_OBJ_FLAG_HIDDEN); lv_btnmatrix_set_one_checked(pinyin_ime->cand_panel, true); + lv_obj_clear_flag(pinyin_ime->cand_panel, LV_OBJ_FLAG_CLICK_FOCUSABLE); /* Set cand_panel style*/ // Default style diff --git a/lib/libesp32_lvgl/lvgl/src/extra/widgets/keyboard/lv_keyboard.c b/lib/libesp32_lvgl/lvgl/src/extra/widgets/keyboard/lv_keyboard.c index 8e052e33a..cbb5e1c4f 100644 --- a/lib/libesp32_lvgl/lvgl/src/extra/widgets/keyboard/lv_keyboard.c +++ b/lib/libesp32_lvgl/lvgl/src/extra/widgets/keyboard/lv_keyboard.c @@ -73,7 +73,7 @@ static const lv_btnmatrix_ctrl_t default_kb_ctrl_uc_map[] = { }; static const char * const default_kb_map_spec[] = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", LV_SYMBOL_BACKSPACE, "\n", - "abc", "+", "-", "/", "*", "=", "%", "!", "?", "#", "<", ">", "\n", + "abc", "+", "&", "/", "*", "=", "%", "!", "?", "#", "<", ">", "\n", "\\", "@", "$", "(", ")", "{", "}", "[", "]", ";", "\"", "'", "\n", LV_SYMBOL_KEYBOARD, LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT, LV_SYMBOL_OK, "" }; diff --git a/lib/libesp32_lvgl/lvgl/src/extra/widgets/meter/lv_meter.c b/lib/libesp32_lvgl/lvgl/src/extra/widgets/meter/lv_meter.c index 89e9e5433..f1ea9d507 100644 --- a/lib/libesp32_lvgl/lvgl/src/extra/widgets/meter/lv_meter.c +++ b/lib/libesp32_lvgl/lvgl/src/extra/widgets/meter/lv_meter.c @@ -360,6 +360,8 @@ static void draw_arcs(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, const lv_area_t int32_t end_angle = lv_map(indic->end_value, scale->min, scale->max, scale->rotation, scale->rotation + scale->angle_range); + arc_dsc.start_angle = start_angle; + arc_dsc.end_angle = end_angle; part_draw_dsc.radius = r_out + indic->type_data.arc.r_mod; part_draw_dsc.sub_part_ptr = indic; part_draw_dsc.p1 = &scale_center; @@ -511,7 +513,8 @@ static void draw_ticks_and_labels(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, cons lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); lv_point_t label_size; - lv_txt_get_size(&label_size, part_draw_dsc.text, label_dsc.font, label_dsc.letter_space, label_dsc.line_space, + lv_txt_get_size(&label_size, part_draw_dsc.text, label_dsc_tmp.font, label_dsc_tmp.letter_space, + label_dsc_tmp.line_space, LV_COORD_MAX, LV_TEXT_FLAG_NONE); lv_area_t label_cord; @@ -569,6 +572,7 @@ static void draw_needles(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, const lv_area lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); part_draw_dsc.class_p = MY_CLASS; part_draw_dsc.p1 = &scale_center; + part_draw_dsc.part = LV_PART_INDICATOR; lv_meter_indicator_t * indic; _LV_LL_READ_BACK(&meter->indicator_ll, indic) { @@ -585,12 +589,12 @@ static void draw_needles(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, const lv_area line_dsc.width = indic->type_data.needle_line.width; line_dsc.opa = indic->opa > LV_OPA_MAX ? opa_main : (opa_main * indic->opa) >> 8; - part_draw_dsc.id = LV_METER_DRAW_PART_NEEDLE_LINE; + part_draw_dsc.type = LV_METER_DRAW_PART_NEEDLE_LINE; part_draw_dsc.line_dsc = &line_dsc; part_draw_dsc.p2 = &p_end; - + part_draw_dsc.p1 = &scale_center; lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); - lv_draw_line(draw_ctx, &line_dsc, &scale_center, &p_end); + lv_draw_line(draw_ctx, &line_dsc, part_draw_dsc.p1, &p_end); lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); } else if(indic->type == LV_METER_INDICATOR_TYPE_NEEDLE_IMG) { @@ -612,7 +616,7 @@ static void draw_needles(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, const lv_area if(angle > 3600) angle -= 3600; img_dsc.angle = angle; - part_draw_dsc.id = LV_METER_DRAW_PART_NEEDLE_IMG; + part_draw_dsc.type = LV_METER_DRAW_PART_NEEDLE_IMG; part_draw_dsc.img_dsc = &img_dsc; lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); diff --git a/lib/libesp32_lvgl/lvgl/src/extra/widgets/meter/lv_meter.h b/lib/libesp32_lvgl/lvgl/src/extra/widgets/meter/lv_meter.h index 24c1dae0f..330bc2a95 100644 --- a/lib/libesp32_lvgl/lvgl/src/extra/widgets/meter/lv_meter.h +++ b/lib/libesp32_lvgl/lvgl/src/extra/widgets/meter/lv_meter.h @@ -42,7 +42,6 @@ typedef struct { uint16_t tick_major_width; int16_t label_gap; - int16_t label_color; int32_t min; int32_t max; diff --git a/lib/libesp32_lvgl/lvgl/src/lv_conf_internal.h b/lib/libesp32_lvgl/lvgl/src/lv_conf_internal.h index a625ba960..0c297081c 100644 --- a/lib/libesp32_lvgl/lvgl/src/lv_conf_internal.h +++ b/lib/libesp32_lvgl/lvgl/src/lv_conf_internal.h @@ -2099,6 +2099,31 @@ #endif #endif +/*API for LittleFS (library needs to be added separately). Uses lfs_file_open, lfs_file_read, etc*/ +#ifndef LV_USE_FS_LITTLEFS + #ifdef CONFIG_LV_USE_FS_LITTLEFS + #define LV_USE_FS_LITTLEFS CONFIG_LV_USE_FS_LITTLEFS + #else + #define LV_USE_FS_LITTLEFS 0 + #endif +#endif +#if LV_USE_FS_LITTLEFS + #ifndef LV_FS_LITTLEFS_LETTER + #ifdef CONFIG_LV_FS_LITTLEFS_LETTER + #define LV_FS_LITTLEFS_LETTER CONFIG_LV_FS_LITTLEFS_LETTER + #else + #define LV_FS_LITTLEFS_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/ + #endif + #endif + #ifndef LV_FS_LITTLEFS_CACHE_SIZE + #ifdef CONFIG_LV_FS_LITTLEFS_CACHE_SIZE + #define LV_FS_LITTLEFS_CACHE_SIZE CONFIG_LV_FS_LITTLEFS_CACHE_SIZE + #else + #define LV_FS_LITTLEFS_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/ + #endif + #endif +#endif + /*PNG decoder library*/ #ifndef LV_USE_PNG #ifdef CONFIG_LV_USE_PNG @@ -2192,6 +2217,25 @@ #endif #endif +/*Tiny TTF library*/ +#ifndef LV_USE_TINY_TTF + #ifdef CONFIG_LV_USE_TINY_TTF + #define LV_USE_TINY_TTF CONFIG_LV_USE_TINY_TTF + #else + #define LV_USE_TINY_TTF 0 + #endif +#endif +#if LV_USE_TINY_TTF + /*Load TTF data from files*/ + #ifndef LV_TINY_TTF_FILE_SUPPORT + #ifdef CONFIG_LV_TINY_TTF_FILE_SUPPORT + #define LV_TINY_TTF_FILE_SUPPORT CONFIG_LV_TINY_TTF_FILE_SUPPORT + #else + #define LV_TINY_TTF_FILE_SUPPORT 0 + #endif + #endif +#endif + /*Rlottie library*/ #ifndef LV_USE_RLOTTIE #ifdef CONFIG_LV_USE_RLOTTIE diff --git a/lib/libesp32_lvgl/lvgl/src/misc/lv_fs.h b/lib/libesp32_lvgl/lvgl/src/misc/lv_fs.h index 9f65e1b2a..c941402c5 100644 --- a/lib/libesp32_lvgl/lvgl/src/misc/lv_fs.h +++ b/lib/libesp32_lvgl/lvgl/src/misc/lv_fs.h @@ -57,7 +57,6 @@ enum { }; typedef uint8_t lv_fs_mode_t; - /** * Seek modes. */ diff --git a/lib/libesp32_lvgl/lvgl/src/misc/lv_printf.h b/lib/libesp32_lvgl/lvgl/src/misc/lv_printf.h index 4cbbd84c0..09563db6e 100644 --- a/lib/libesp32_lvgl/lvgl/src/misc/lv_printf.h +++ b/lib/libesp32_lvgl/lvgl/src/misc/lv_printf.h @@ -40,14 +40,20 @@ /* platform-specific printf format for int32_t, usually "d" or "ld" */ #define LV_PRId32 PRId32 #define LV_PRIu32 PRIu32 + #define LV_PRIx32 PRIx32 + #define LV_PRIX32 PRIX32 #else #define LV_PRId32 "d" #define LV_PRIu32 "u" + #define LV_PRIx32 "x" + #define LV_PRIX32 "X" #endif #else /* hope this is correct for ports without __has_include or without inttypes.h */ #define LV_PRId32 "d" #define LV_PRIu32 "u" + #define LV_PRIx32 "x" + #define LV_PRIX32 "X" #endif #ifdef __cplusplus diff --git a/lib/libesp32_lvgl/lvgl/src/widgets/lv_arc.c b/lib/libesp32_lvgl/lvgl/src/widgets/lv_arc.c index 848c20a72..63cf57740 100644 --- a/lib/libesp32_lvgl/lvgl/src/widgets/lv_arc.c +++ b/lib/libesp32_lvgl/lvgl/src/widgets/lv_arc.c @@ -21,6 +21,10 @@ #define MY_CLASS &lv_arc_class #define VALUE_UNSET INT16_MIN +#define CLICK_OUTSIDE_BG_ANGLES ((uint32_t) 0x00U) +#define CLICK_INSIDE_BG_ANGLES ((uint32_t) 0x01U) +#define CLICK_CLOSER_TO_MAX_END ((uint32_t) 0x00U) +#define CLICK_CLOSER_TO_MIN_END ((uint32_t) 0x01U) /********************** * TYPEDEFS @@ -40,6 +44,7 @@ static lv_coord_t get_angle(const lv_obj_t * obj); static void get_knob_area(lv_obj_t * arc, const lv_point_t * center, lv_coord_t r, lv_area_t * knob_area); static void value_update(lv_obj_t * arc); static lv_coord_t knob_get_extra_size(lv_obj_t * obj); +static bool lv_arc_angle_within_bg_bounds(lv_obj_t * obj, const uint32_t angle, const uint32_t tolerance_deg); /********************** * STATIC VARIABLES @@ -399,6 +404,7 @@ static void lv_arc_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) arc->chg_rate = 720; arc->last_tick = lv_tick_get(); arc->last_angle = arc->indic_angle_end; + arc->in_out = CLICK_OUTSIDE_BG_ANGLES; lv_obj_add_flag(obj, LV_OBJ_FLAG_CLICKABLE); lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLL_CHAIN | LV_OBJ_FLAG_SCROLLABLE); @@ -478,29 +484,54 @@ static void lv_arc_event(const lv_obj_class_t * class_p, lv_event_t * e) angle -= arc->rotation; angle -= arc->bg_angle_start; /*Make the angle relative to the start angle*/ + /* If we click near the bg_angle_start the angle will be close to 360° instead of an small angle */ if(angle < 0) angle += 360; - int16_t deg_range = bg_end - arc->bg_angle_start; + const uint32_t circumference = (uint32_t)((2U * r * 314U) / 100U); /* Equivalent to: 2r * 3.14, avoiding floats */ + const uint32_t tolerance_deg = (360U * LV_DPX(50U)) / circumference; + const uint32_t min_close_prev = (uint32_t) arc->min_close; + const bool is_angle_within_bg_bounds = lv_arc_angle_within_bg_bounds(obj, (uint32_t) angle, tolerance_deg); + if(!is_angle_within_bg_bounds) { + return; + } + + int16_t deg_range = bg_end - arc->bg_angle_start; int16_t last_angle_rel = arc->last_angle - arc->bg_angle_start; int16_t delta_angle = angle - last_angle_rel; - /*Do not allow big jumps. + /*Do not allow big jumps (jumps bigger than 280°). *It's mainly to avoid jumping to the opposite end if the "dead" range between min. and max. is crossed. *Check which end was closer on the last valid press (arc->min_close) and prefer that end*/ if(LV_ABS(delta_angle) > 280) { if(arc->min_close) angle = 0; else angle = deg_range; } - else { - if(angle < deg_range / 2)arc->min_close = 1; - else arc->min_close = 0; + /* Check if click was outside the background arc start and end angles */ + else if(CLICK_OUTSIDE_BG_ANGLES == arc->in_out) { + if(arc->min_close) angle = -deg_range; + else angle = deg_range; } + else { /* Keep the angle value */ } + + /* Prevent big jumps when the click goes from start to end angle in the invisible + * part of the background arc without being released */ + if(((min_close_prev == CLICK_CLOSER_TO_MIN_END) && (arc->min_close == CLICK_CLOSER_TO_MAX_END)) + && ((CLICK_OUTSIDE_BG_ANGLES == arc->in_out) && (LV_ABS(delta_angle) > 280))) { + angle = 0; + } + else if(((min_close_prev == CLICK_CLOSER_TO_MAX_END) && (arc->min_close == CLICK_CLOSER_TO_MIN_END)) + && (CLICK_OUTSIDE_BG_ANGLES == arc->in_out)) { + angle = deg_range; + } + else { /* Keep the angle value */ } /*Calculate the slew rate limited angle based on change rate (degrees/sec)*/ delta_angle = angle - last_angle_rel; + uint32_t delta_tick = lv_tick_elaps(arc->last_tick); - int16_t delta_angle_max = (arc->chg_rate * delta_tick) / 1000; + /* delta_angle_max can never be signed. delta_tick is always signed, same for ch_rate */ + const uint16_t delta_angle_max = (arc->chg_rate * delta_tick) / 1000; if(delta_angle > delta_angle_max) { delta_angle = delta_angle_max; @@ -508,6 +539,7 @@ static void lv_arc_event(const lv_obj_class_t * class_p, lv_event_t * e) else if(delta_angle < -delta_angle_max) { delta_angle = -delta_angle_max; } + else { /* Nothing to do */ } angle = last_angle_rel + delta_angle; /*Apply the limited angle change*/ @@ -564,7 +596,7 @@ static void lv_arc_event(const lv_obj_class_t * class_p, lv_event_t * e) } } else if(code == LV_EVENT_HIT_TEST) { - lv_hit_test_info_t * info = lv_event_get_param(e);; + lv_hit_test_info_t * info = lv_event_get_param(e); lv_point_t p; lv_coord_t r; @@ -868,4 +900,121 @@ static lv_coord_t knob_get_extra_size(lv_obj_t * obj) return LV_MAX(knob_shadow_size, knob_outline_size); } +/** + * Check if angle is within arc background bounds + * + * In order to avoid unexpected value update of the arc value when the user clicks + * outside of the arc background we need to check if the angle (of the clicked point) + * is within the bounds of the background. + * + * A tolerance (extra room) also should be taken into consideration. + * + * E.g. Arc with start angle of 0° and end angle of 90°, the background is only visible in + * that range, from 90° to 360° the background is invisible. Click in 150° should not update + * the arc value, click within the arc angle range should. + * + * IMPORTANT NOTE: angle is always relative to bg_angle_start, e.g. if bg_angle_start is 30 + * and we click a bit to the left, angle is 10, not the expected 40. + * + * @param obj Pointer to lv_arc + * @param angle Angle to be checked + * @param tolerance_deg Tolerance + * + * @return true if angle is within arc background bounds, false otherwise + */ +static bool lv_arc_angle_within_bg_bounds(lv_obj_t * obj, const uint32_t angle, const uint32_t tolerance_deg) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_arc_t * arc = (lv_arc_t *)obj; + + uint32_t smaller_angle = 0; + uint32_t bigger_angle = 0; + + /* Determine which background angle is smaller and bigger */ + if(arc->bg_angle_start < arc->bg_angle_end) { + bigger_angle = arc->bg_angle_end; + smaller_angle = arc->bg_angle_start; + } + else { + bigger_angle = (360 - arc->bg_angle_start) + arc->bg_angle_end; + smaller_angle = 0; + } + + /* Angle is between both background angles */ + if((smaller_angle <= angle) && (angle <= bigger_angle)) { + + if(((bigger_angle - smaller_angle) / 2U) >= angle) { + arc->min_close = 1; + } + else { + arc->min_close = 0; + } + + arc->in_out = CLICK_INSIDE_BG_ANGLES; + + return true; + } + /* Distance between background start and end angles is less than tolerance, + * consider the click inside the arc */ + else if(((smaller_angle - tolerance_deg) <= 0U) && + (360U - (bigger_angle + (smaller_angle - tolerance_deg)))) { + + arc->min_close = 1; + arc->in_out = CLICK_INSIDE_BG_ANGLES; + return true; + } + else { /* Case handled below */ } + + /* Legends: + * 0° = angle 0 + * 360° = angle 360 + * T: Tolerance + * A: Angle + * S: Arc background start angle + * E: Arc background end angle + * + * Start angle is bigger or equal to tolerance */ + if((smaller_angle >= tolerance_deg) + /* (360° - T) --- A --- 360° */ + && ((angle >= (360U - tolerance_deg)) && (angle <= 360U))) { + + arc->min_close = 1; + arc->in_out = CLICK_OUTSIDE_BG_ANGLES; + return true; + } + /* Tolerance is bigger than bg start angle */ + else if((smaller_angle < tolerance_deg) + /* (360° - (T - S)) --- A --- 360° */ + && (((360U - (tolerance_deg - smaller_angle)) <= angle)) && (angle <= 360U)) { + + arc->min_close = 1; + arc->in_out = CLICK_OUTSIDE_BG_ANGLES; + return true; + } + /* 360° is bigger than background end angle + tolerance */ + else if((360U >= (bigger_angle + tolerance_deg)) + /* E --- A --- (E + T) */ + && ((bigger_angle <= (angle + smaller_angle)) && + ((angle + smaller_angle) <= (bigger_angle + tolerance_deg)))) { + + arc->min_close = 0; + arc->in_out = CLICK_OUTSIDE_BG_ANGLES; + return true; + } + /* Background end angle + tolerance is bigger than 360° and bg_start_angle + tolerance is not near 0° + ((bg_end_angle + tolerance) - 360°) + * Here we can assume background is not near 0° because of the first two initial checks */ + else if((360U < (bigger_angle + tolerance_deg)) + && (angle <= 0U + ((bigger_angle + tolerance_deg) - 360U)) && (angle > bigger_angle)) { + + arc->min_close = 0; + arc->in_out = CLICK_OUTSIDE_BG_ANGLES; + return true; + } + else { + /* Nothing to do */ + } + + return false; +} + #endif diff --git a/lib/libesp32_lvgl/lvgl/src/widgets/lv_arc.h b/lib/libesp32_lvgl/lvgl/src/widgets/lv_arc.h index fd53fc15c..6a8a07246 100644 --- a/lib/libesp32_lvgl/lvgl/src/widgets/lv_arc.h +++ b/lib/libesp32_lvgl/lvgl/src/widgets/lv_arc.h @@ -44,10 +44,11 @@ typedef struct { int16_t value; /*Current value of the arc*/ int16_t min_value; /*Minimum value of the arc*/ int16_t max_value; /*Maximum value of the arc*/ - uint16_t dragging : 1; - uint16_t type : 2; - uint16_t min_close : 1; /*1: the last pressed angle was closer to minimum end*/ - uint16_t chg_rate; /*Drag angle rate of change of the arc (degrees/sec)*/ + uint32_t dragging : 1; + uint32_t type : 2; + uint32_t min_close : 1; /*1: the last pressed angle was closer to minimum end*/ + uint32_t in_out : 1; /* 1: The click was within the background arc angles. 0: Click outside */ + uint32_t chg_rate; /*Drag angle rate of change of the arc (degrees/sec)*/ uint32_t last_tick; /*Last dragging event timestamp of the arc*/ int16_t last_angle; /*Last dragging angle of the arc*/ } lv_arc_t; diff --git a/lib/libesp32_lvgl/lvgl/src/widgets/lv_btnmatrix.c b/lib/libesp32_lvgl/lvgl/src/widgets/lv_btnmatrix.c index b0dc8df3f..fd14e56e9 100644 --- a/lib/libesp32_lvgl/lvgl/src/widgets/lv_btnmatrix.c +++ b/lib/libesp32_lvgl/lvgl/src/widgets/lv_btnmatrix.c @@ -448,6 +448,9 @@ static void lv_btnmatrix_event(const lv_obj_class_t * class_p, lv_event_t * e) invalidate_button_area(obj, btnm->btn_id_sel); /*Invalidate the new area*/ } } + else { + btnm->btn_id_sel = LV_BTNMATRIX_BTN_NONE; + } } if(btnm->btn_id_sel != LV_BTNMATRIX_BTN_NONE) { diff --git a/lib/libesp32_lvgl/lvgl/src/widgets/lv_checkbox.c b/lib/libesp32_lvgl/lvgl/src/widgets/lv_checkbox.c index dd3b3d157..fa79b3793 100644 --- a/lib/libesp32_lvgl/lvgl/src/widgets/lv_checkbox.c +++ b/lib/libesp32_lvgl/lvgl/src/widgets/lv_checkbox.c @@ -74,14 +74,16 @@ void lv_checkbox_set_text(lv_obj_t * obj, const char * txt) size_t len = strlen(txt); #endif - if(!cb->static_txt) cb->txt = lv_mem_realloc(cb->txt, len + 1); - else cb->txt = lv_mem_alloc(len + 1); + char * _txt = (char *)cb->txt; + if(!cb->static_txt) _txt = lv_mem_realloc(_txt, len + 1); + else _txt = lv_mem_alloc(len + 1); #if LV_USE_ARABIC_PERSIAN_CHARS - _lv_txt_ap_proc(txt, cb->txt); + _lv_txt_ap_proc(txt, _txt); #else - strcpy(cb->txt, txt); + strcpy(_txt, txt); #endif + cb->txt = _txt; cb->static_txt = 0; lv_obj_refresh_self_size(obj); @@ -92,7 +94,7 @@ void lv_checkbox_set_text_static(lv_obj_t * obj, const char * txt) { lv_checkbox_t * cb = (lv_checkbox_t *)obj; - if(!cb->static_txt) lv_mem_free(cb->txt); + if(!cb->static_txt) lv_mem_free((void *)cb->txt); cb->txt = (char *)txt; cb->static_txt = 1; @@ -138,7 +140,7 @@ static void lv_checkbox_destructor(const lv_obj_class_t * class_p, lv_obj_t * ob lv_checkbox_t * cb = (lv_checkbox_t *)obj; if(!cb->static_txt) { - lv_mem_free(cb->txt); + lv_mem_free((void *)cb->txt); cb->txt = NULL; } LV_TRACE_OBJ_CREATE("finished"); diff --git a/lib/libesp32_lvgl/lvgl/src/widgets/lv_checkbox.h b/lib/libesp32_lvgl/lvgl/src/widgets/lv_checkbox.h index 772f500d5..11405bd21 100644 --- a/lib/libesp32_lvgl/lvgl/src/widgets/lv_checkbox.h +++ b/lib/libesp32_lvgl/lvgl/src/widgets/lv_checkbox.h @@ -28,7 +28,7 @@ extern "C" { typedef struct { lv_obj_t obj; - char * txt; + const char * txt; uint32_t static_txt : 1; } lv_checkbox_t; diff --git a/lib/libesp32_lvgl/lvgl/src/widgets/lv_dropdown.c b/lib/libesp32_lvgl/lvgl/src/widgets/lv_dropdown.c index 974435b2d..765bdde81 100644 --- a/lib/libesp32_lvgl/lvgl/src/widgets/lv_dropdown.c +++ b/lib/libesp32_lvgl/lvgl/src/widgets/lv_dropdown.c @@ -407,14 +407,19 @@ int32_t lv_dropdown_get_option_index(lv_obj_t * obj, const char * option) const char * opts = lv_dropdown_get_options(obj); uint32_t char_i = 0; uint32_t opt_i = 0; + uint32_t option_len = strlen(option); const char * start = opts; while(start[char_i] != '\0') { for(char_i = 0; (start[char_i] != '\n') && (start[char_i] != '\0'); char_i++); - if(memcmp(start, option, LV_MIN(strlen(option), char_i)) == 0) return opt_i; + if(option_len == char_i && memcmp(start, option, LV_MIN(option_len, char_i)) == 0) { + return opt_i; + } + start = &start[char_i]; if(start[0] == '\n') start++; + char_i = 0; opt_i++; } diff --git a/lib/libesp32_lvgl/lvgl/src/widgets/lv_table.c b/lib/libesp32_lvgl/lvgl/src/widgets/lv_table.c index f789e8d33..968cf8703 100644 --- a/lib/libesp32_lvgl/lvgl/src/widgets/lv_table.c +++ b/lib/libesp32_lvgl/lvgl/src/widgets/lv_table.c @@ -40,7 +40,7 @@ static void refr_size_form_row(lv_obj_t * obj, uint32_t start_row); static void refr_cell_size(lv_obj_t * obj, uint32_t row, uint32_t col); static lv_res_t get_pressed_cell(lv_obj_t * obj, uint16_t * row, uint16_t * col); static size_t get_cell_txt_len(const char * txt); -static void copy_cell_txt(char * dst, const char * txt); +static void copy_cell_txt(lv_table_cell_t * dst, const char * txt); static void get_cell_area(lv_obj_t * obj, uint16_t row, uint16_t col, lv_area_t * area); static void scroll_to_selected_cell(lv_obj_t * obj); @@ -98,7 +98,14 @@ void lv_table_set_cell_value(lv_obj_t * obj, uint16_t row, uint16_t col, const c lv_table_cell_ctrl_t ctrl = 0; /*Save the control byte*/ - if(table->cell_data[cell]) ctrl = table->cell_data[cell][0]; + if(table->cell_data[cell]) ctrl = table->cell_data[cell]->ctrl; + +#if LV_USE_USER_DATA + void * user_data = NULL; + + /*Save the user data*/ + if(table->cell_data[cell]) user_data = table->cell_data[cell]->user_data; +#endif size_t to_allocate = get_cell_txt_len(txt); @@ -108,7 +115,10 @@ void lv_table_set_cell_value(lv_obj_t * obj, uint16_t row, uint16_t col, const c copy_cell_txt(table->cell_data[cell], txt); - table->cell_data[cell][0] = ctrl; + table->cell_data[cell]->ctrl = ctrl; +#if LV_USE_USER_DATA + table->cell_data[cell]->user_data = user_data; +#endif refr_cell_size(obj, row, col); } @@ -131,7 +141,14 @@ void lv_table_set_cell_value_fmt(lv_obj_t * obj, uint16_t row, uint16_t col, con lv_table_cell_ctrl_t ctrl = 0; /*Save the control byte*/ - if(table->cell_data[cell]) ctrl = table->cell_data[cell][0]; + if(table->cell_data[cell]) ctrl = table->cell_data[cell]->ctrl; + +#if LV_USE_USER_DATA + void * user_data = NULL; + + /*Save the user_data*/ + if(table->cell_data[cell]) user_data = table->cell_data[cell]->user_data; +#endif va_list ap, ap2; va_start(ap, fmt); @@ -154,32 +171,35 @@ void lv_table_set_cell_value_fmt(lv_obj_t * obj, uint16_t row, uint16_t col, con /*Get the size of the Arabic text and process it*/ size_t len_ap = _lv_txt_ap_calc_bytes_cnt(raw_txt); - table->cell_data[cell] = lv_mem_realloc(table->cell_data[cell], len_ap + 1); + table->cell_data[cell] = lv_mem_realloc(table->cell_data[cell], sizeof(lv_table_cell_t) + len_ap + 1); LV_ASSERT_MALLOC(table->cell_data[cell]); if(table->cell_data[cell] == NULL) { va_end(ap2); return; } - _lv_txt_ap_proc(raw_txt, &table->cell_data[cell][1]); + _lv_txt_ap_proc(raw_txt, table->cell_data[cell]->txt); lv_mem_buf_release(raw_txt); #else - table->cell_data[cell] = lv_mem_realloc(table->cell_data[cell], len + 2); /*+1: trailing '\0; +1: format byte*/ + table->cell_data[cell] = lv_mem_realloc(table->cell_data[cell], + sizeof(lv_table_cell_t) + len + 1); /*+1: trailing '\0; */ LV_ASSERT_MALLOC(table->cell_data[cell]); if(table->cell_data[cell] == NULL) { va_end(ap2); return; } - table->cell_data[cell][len + 1] = 0; /*Ensure NULL termination*/ + table->cell_data[cell]->txt[len] = 0; /*Ensure NULL termination*/ - lv_vsnprintf(&table->cell_data[cell][1], len + 1, fmt, ap2); + lv_vsnprintf(table->cell_data[cell]->txt, len + 1, fmt, ap2); #endif va_end(ap2); - table->cell_data[cell][0] = ctrl; - + table->cell_data[cell]->ctrl = ctrl; +#if LV_USE_USER_DATA + table->cell_data[cell]->user_data = user_data; +#endif refr_cell_size(obj, row, col); } @@ -204,11 +224,17 @@ void lv_table_set_row_cnt(lv_obj_t * obj, uint16_t row_cnt) uint32_t new_cell_cnt = table->col_cnt * table->row_cnt; uint32_t i; for(i = new_cell_cnt; i < old_cell_cnt; i++) { +#if LV_USE_USER_DATA + if(table->cell_data[i]->user_data) { + lv_mem_free(table->cell_data[i]->user_data); + table->cell_data[i]->user_data = NULL; + } +#endif lv_mem_free(table->cell_data[i]); } } - table->cell_data = lv_mem_realloc(table->cell_data, table->row_cnt * table->col_cnt * sizeof(char *)); + table->cell_data = lv_mem_realloc(table->cell_data, table->row_cnt * table->col_cnt * sizeof(lv_table_cell_t *)); LV_ASSERT_MALLOC(table->cell_data); if(table->cell_data == NULL) return; @@ -233,7 +259,7 @@ void lv_table_set_col_cnt(lv_obj_t * obj, uint16_t col_cnt) uint16_t old_col_cnt = table->col_cnt; table->col_cnt = col_cnt; - char ** new_cell_data = lv_mem_alloc(table->row_cnt * table->col_cnt * sizeof(char *)); + lv_table_cell_t ** new_cell_data = lv_mem_alloc(table->row_cnt * table->col_cnt * sizeof(lv_table_cell_t *)); LV_ASSERT_MALLOC(new_cell_data); if(new_cell_data == NULL) return; uint32_t new_cell_cnt = table->col_cnt * table->row_cnt; @@ -256,6 +282,12 @@ void lv_table_set_col_cnt(lv_obj_t * obj, uint16_t col_cnt) int32_t i; for(i = 0; i < (int32_t)old_col_cnt - col_cnt; i++) { uint32_t idx = old_col_start + min_col_cnt + i; +#if LV_USE_USER_DATA + if(table->cell_data[idx]->user_data) { + lv_mem_free(table->cell_data[idx]->user_data); + table->cell_data[idx]->user_data = NULL; + } +#endif lv_mem_free(table->cell_data[idx]); table->cell_data[idx] = NULL; } @@ -304,15 +336,18 @@ void lv_table_add_cell_ctrl(lv_obj_t * obj, uint16_t row, uint16_t col, lv_table uint32_t cell = row * table->col_cnt + col; if(is_cell_empty(table->cell_data[cell])) { - table->cell_data[cell] = lv_mem_alloc(2); /*+1: trailing '\0; +1: format byte*/ + table->cell_data[cell] = lv_mem_alloc(sizeof(lv_table_cell_t) + 1); /*+1: trailing '\0 */ LV_ASSERT_MALLOC(table->cell_data[cell]); if(table->cell_data[cell] == NULL) return; - table->cell_data[cell][0] = 0; - table->cell_data[cell][1] = '\0'; + table->cell_data[cell]->ctrl = 0; +#if LV_USE_USER_DATA + table->cell_data[cell]->user_data = NULL; +#endif + table->cell_data[cell]->txt[0] = '\0'; } - table->cell_data[cell][0] |= ctrl; + table->cell_data[cell]->ctrl |= ctrl; } void lv_table_clear_cell_ctrl(lv_obj_t * obj, uint16_t row, uint16_t col, lv_table_cell_ctrl_t ctrl) @@ -328,17 +363,51 @@ void lv_table_clear_cell_ctrl(lv_obj_t * obj, uint16_t row, uint16_t col, lv_tab uint32_t cell = row * table->col_cnt + col; if(is_cell_empty(table->cell_data[cell])) { - table->cell_data[cell] = lv_mem_alloc(2); /*+1: trailing '\0; +1: format byte*/ + table->cell_data[cell] = lv_mem_alloc(sizeof(lv_table_cell_t) + 1); /*+1: trailing '\0 */ LV_ASSERT_MALLOC(table->cell_data[cell]); if(table->cell_data[cell] == NULL) return; - table->cell_data[cell][0] = 0; - table->cell_data[cell][1] = '\0'; + table->cell_data[cell]->ctrl = 0; +#if LV_USE_USER_DATA + table->cell_data[cell]->user_data = NULL; +#endif + table->cell_data[cell]->txt[0] = '\0'; } - table->cell_data[cell][0] &= (~ctrl); + table->cell_data[cell]->ctrl &= (~ctrl); } +#if LV_USE_USER_DATA +void lv_table_set_cell_user_data(lv_obj_t * obj, uint16_t row, uint16_t col, void * user_data) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_table_t * table = (lv_table_t *)obj; + + /*Auto expand*/ + if(col >= table->col_cnt) lv_table_set_col_cnt(obj, col + 1); + if(row >= table->row_cnt) lv_table_set_row_cnt(obj, row + 1); + + uint32_t cell = row * table->col_cnt + col; + + if(is_cell_empty(table->cell_data[cell])) { + table->cell_data[cell] = lv_mem_alloc(sizeof(lv_table_cell_t) + 1); /*+1: trailing '\0 */ + LV_ASSERT_MALLOC(table->cell_data[cell]); + if(table->cell_data[cell] == NULL) return; + + table->cell_data[cell]->ctrl = 0; + table->cell_data[cell]->user_data = NULL; + table->cell_data[cell]->txt[0] = '\0'; + } + + if(table->cell_data[cell]->user_data) { + lv_mem_free(table->cell_data[cell]->user_data); + } + + table->cell_data[cell]->user_data = user_data; +} +#endif + /*===================== * Getter functions *====================*/ @@ -356,7 +425,7 @@ const char * lv_table_get_cell_value(lv_obj_t * obj, uint16_t row, uint16_t col) if(is_cell_empty(table->cell_data[cell])) return ""; - return &table->cell_data[cell][1]; /*Skip the format byte*/ + return table->cell_data[cell]->txt; } uint16_t lv_table_get_row_cnt(lv_obj_t * obj) @@ -401,7 +470,7 @@ bool lv_table_has_cell_ctrl(lv_obj_t * obj, uint16_t row, uint16_t col, lv_table uint32_t cell = row * table->col_cnt + col; if(is_cell_empty(table->cell_data[cell])) return false; - else return (table->cell_data[cell][0] & ctrl) == ctrl; + else return (table->cell_data[cell]->ctrl & ctrl) == ctrl; } void lv_table_get_selected_cell(lv_obj_t * obj, uint16_t * row, uint16_t * col) @@ -411,6 +480,24 @@ void lv_table_get_selected_cell(lv_obj_t * obj, uint16_t * row, uint16_t * col) *col = table->col_act; } +#if LV_USE_USER_DATA +void * lv_table_get_cell_user_data(lv_obj_t * obj, uint16_t row, uint16_t col) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_table_t * table = (lv_table_t *)obj; + if(row >= table->row_cnt || col >= table->col_cnt) { + LV_LOG_WARN("invalid row or column"); + return NULL; + } + uint32_t cell = row * table->col_cnt + col; + + if(is_cell_empty(table->cell_data[cell])) return NULL; + + return table->cell_data[cell]->user_data; +} +#endif + /********************** * STATIC FUNCTIONS **********************/ @@ -428,7 +515,7 @@ static void lv_table_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) table->row_h = lv_mem_alloc(table->row_cnt * sizeof(table->row_h[0])); table->col_w[0] = LV_DPI_DEF; table->row_h[0] = LV_DPI_DEF; - table->cell_data = lv_mem_realloc(table->cell_data, table->row_cnt * table->col_cnt * sizeof(char *)); + table->cell_data = lv_mem_realloc(table->cell_data, table->row_cnt * table->col_cnt * sizeof(lv_table_cell_t *)); table->cell_data[0] = NULL; LV_TRACE_OBJ_CREATE("finished"); @@ -442,6 +529,12 @@ static void lv_table_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj) uint16_t i; for(i = 0; i < table->col_cnt * table->row_cnt; i++) { if(table->cell_data[i]) { +#if LV_USE_USER_DATA + if(table->cell_data[i]->user_data) { + lv_mem_free(table->cell_data[i]->user_data); + table->cell_data[i]->user_data = NULL; + } +#endif lv_mem_free(table->cell_data[i]); table->cell_data[i] = NULL; } @@ -639,7 +732,7 @@ static void draw_main(lv_event_t * e) for(col = 0; col < table->col_cnt; col++) { lv_table_cell_ctrl_t ctrl = 0; - if(table->cell_data[cell]) ctrl = table->cell_data[cell][0]; + if(table->cell_data[cell]) ctrl = table->cell_data[cell]->ctrl; if(rtl) { cell_area.x2 = cell_area.x1 - 1; @@ -652,11 +745,11 @@ static void draw_main(lv_event_t * e) uint16_t col_merge = 0; for(col_merge = 0; col_merge + col < table->col_cnt - 1; col_merge++) { - char * next_cell_data = table->cell_data[cell + col_merge]; + lv_table_cell_t * next_cell_data = table->cell_data[cell + col_merge]; if(is_cell_empty(next_cell_data)) break; - lv_table_cell_ctrl_t merge_ctrl = (lv_table_cell_ctrl_t) next_cell_data[0]; + lv_table_cell_ctrl_t merge_ctrl = (lv_table_cell_ctrl_t) next_cell_data->ctrl; if(merge_ctrl & LV_TABLE_CELL_CTRL_MERGE_RIGHT) { lv_coord_t offset = table->col_w[col + col_merge + 1]; @@ -739,7 +832,7 @@ static void draw_main(lv_event_t * e) bool crop = ctrl & LV_TABLE_CELL_CTRL_TEXT_CROP ? true : false; if(crop) txt_flags = LV_TEXT_FLAG_EXPAND; - lv_txt_get_size(&txt_size, table->cell_data[cell] + 1, label_dsc_def.font, + lv_txt_get_size(&txt_size, table->cell_data[cell]->txt, label_dsc_def.font, label_dsc_act.letter_space, label_dsc_act.line_space, lv_area_get_width(&txt_area), txt_flags); @@ -754,7 +847,7 @@ static void draw_main(lv_event_t * e) label_mask_ok = _lv_area_intersect(&label_clip_area, &clip_area, &cell_area); if(label_mask_ok) { draw_ctx->clip_area = &label_clip_area; - lv_draw_label(draw_ctx, &label_dsc_act, &txt_area, table->cell_data[cell] + 1, NULL); + lv_draw_label(draw_ctx, &label_dsc_act, &txt_area, table->cell_data[cell]->txt, NULL); draw_ctx->clip_area = &clip_area; } } @@ -845,7 +938,7 @@ static lv_coord_t get_row_height(lv_obj_t * obj, uint16_t row_id, const lv_font_ uint16_t cell; uint16_t col; for(cell = row_start, col = 0; cell < row_start + table->col_cnt; cell++, col++) { - char * cell_data = table->cell_data[cell]; + lv_table_cell_t * cell_data = table->cell_data[cell]; if(is_cell_empty(cell_data)) { continue; @@ -858,11 +951,11 @@ static lv_coord_t get_row_height(lv_obj_t * obj, uint16_t row_id, const lv_font_ * exit the traversal when the current cell control is not LV_TABLE_CELL_CTRL_MERGE_RIGHT */ uint16_t col_merge = 0; for(col_merge = 0; col_merge + col < table->col_cnt - 1; col_merge++) { - char * next_cell_data = table->cell_data[cell + col_merge]; + lv_table_cell_t * next_cell_data = table->cell_data[cell + col_merge]; if(is_cell_empty(next_cell_data)) break; - lv_table_cell_ctrl_t ctrl = (lv_table_cell_ctrl_t) next_cell_data[0]; + lv_table_cell_ctrl_t ctrl = (lv_table_cell_ctrl_t) next_cell_data->ctrl; if(ctrl & LV_TABLE_CELL_CTRL_MERGE_RIGHT) { txt_w += table->col_w[col + col_merge + 1]; } @@ -871,7 +964,7 @@ static lv_coord_t get_row_height(lv_obj_t * obj, uint16_t row_id, const lv_font_ } } - lv_table_cell_ctrl_t ctrl = (lv_table_cell_ctrl_t) cell_data[0]; + lv_table_cell_ctrl_t ctrl = (lv_table_cell_ctrl_t) cell_data->ctrl; /*When cropping the text we can assume the row height is equal to the line height*/ if(ctrl & LV_TABLE_CELL_CTRL_TEXT_CROP) { @@ -883,7 +976,7 @@ static lv_coord_t get_row_height(lv_obj_t * obj, uint16_t row_id, const lv_font_ lv_point_t txt_size; txt_w -= cell_left + cell_right; - lv_txt_get_size(&txt_size, table->cell_data[cell] + 1, font, + lv_txt_get_size(&txt_size, table->cell_data[cell]->txt, font, letter_space, line_space, txt_w, LV_TEXT_FLAG_NONE); h_max = LV_MAX(txt_size.y + cell_top + cell_bottom, h_max); @@ -953,23 +1046,21 @@ static size_t get_cell_txt_len(const char * txt) size_t retval = 0; #if LV_USE_ARABIC_PERSIAN_CHARS - retval = _lv_txt_ap_calc_bytes_cnt(txt) + 1; + retval = sizeof(lv_table_cell_t) + _lv_txt_ap_calc_bytes_cnt(txt) + 1; #else - /* cell_data layout: [ctrl][txt][trailing '\0' terminator] - * +2 because of the trailing '\0' and the ctrl */ - retval = strlen(txt) + 2; + retval = sizeof(lv_table_cell_t) + strlen(txt) + 1; #endif return retval; } /* Copy txt into dst skipping the format byte */ -static void copy_cell_txt(char * dst, const char * txt) +static void copy_cell_txt(lv_table_cell_t * dst, const char * txt) { #if LV_USE_ARABIC_PERSIAN_CHARS - _lv_txt_ap_proc(txt, &dst[1]); + _lv_txt_ap_proc(txt, dst->txt); #else - strcpy(&dst[1], txt); + strcpy(dst->txt, txt); #endif } diff --git a/lib/libesp32_lvgl/lvgl/src/widgets/lv_table.h b/lib/libesp32_lvgl/lvgl/src/widgets/lv_table.h index 910627087..9c60a7673 100644 --- a/lib/libesp32_lvgl/lvgl/src/widgets/lv_table.h +++ b/lib/libesp32_lvgl/lvgl/src/widgets/lv_table.h @@ -46,12 +46,21 @@ enum { typedef uint8_t lv_table_cell_ctrl_t; +/*Data of cell*/ +typedef struct { + lv_table_cell_ctrl_t ctrl; +#if LV_USE_USER_DATA + void * user_data; /**< Custom user data*/ +#endif + char txt[]; +} lv_table_cell_t; + /*Data of table*/ typedef struct { lv_obj_t obj; uint16_t col_cnt; uint16_t row_cnt; - char ** cell_data; + lv_table_cell_t ** cell_data; lv_coord_t * row_h; lv_coord_t * col_w; uint16_t col_act; @@ -144,6 +153,17 @@ void lv_table_add_cell_ctrl(lv_obj_t * obj, uint16_t row, uint16_t col, lv_table */ void lv_table_clear_cell_ctrl(lv_obj_t * obj, uint16_t row, uint16_t col, lv_table_cell_ctrl_t ctrl); +#if LV_USE_USER_DATA +/** + * Add custom user data to the cell. + * @param obj pointer to a Table object + * @param row id of the row [0 .. row_cnt -1] + * @param col id of the column [0 .. col_cnt -1] + * @param user_data pointer to the new user_data. It must be allocated by user as it will be freed automatically + */ +void lv_table_set_cell_user_data(lv_obj_t * obj, uint16_t row, uint16_t col, void * user_data); +#endif + /*===================== * Getter functions *====================*/ @@ -197,6 +217,16 @@ bool lv_table_has_cell_ctrl(lv_obj_t * obj, uint16_t row, uint16_t col, lv_table */ void lv_table_get_selected_cell(lv_obj_t * obj, uint16_t * row, uint16_t * col); +#if LV_USE_USER_DATA +/** + * Get custom user data to the cell. + * @param obj pointer to a Table object + * @param row id of the row [0 .. row_cnt -1] + * @param col id of the column [0 .. col_cnt -1] + */ +void * lv_table_get_cell_user_data(lv_obj_t * obj, uint16_t row, uint16_t col); +#endif + /********************** * MACROS **********************/