From f58d97d9905272665b9d412d02c0c2ac27f13d14 Mon Sep 17 00:00:00 2001 From: Fabio Guerra Date: Mon, 3 Aug 2020 19:33:52 -0300 Subject: [PATCH] Adding support to dynamic font loading. --- src/lv_font/lv_font.mk | 1 + src/lv_font/lv_font_loader.c | 589 +++++++++++++++++++++++ src/lv_font/lv_font_loader.h | 44 ++ tests/Makefile | 1 + tests/lv_test_assert.c | 9 + tests/lv_test_assert.h | 1 + tests/lv_test_core/lv_test_core.c | 2 + tests/lv_test_core/lv_test_font_loader.c | 154 ++++++ tests/lv_test_core/lv_test_font_loader.h | 39 ++ tests/lv_test_main.c | 69 +++ 10 files changed, 909 insertions(+) create mode 100644 src/lv_font/lv_font_loader.c create mode 100644 src/lv_font/lv_font_loader.h create mode 100644 tests/lv_test_core/lv_test_font_loader.c create mode 100644 tests/lv_test_core/lv_test_font_loader.h diff --git a/src/lv_font/lv_font.mk b/src/lv_font/lv_font.mk index 9f9eabede..5b2f8bc05 100644 --- a/src/lv_font/lv_font.mk +++ b/src/lv_font/lv_font.mk @@ -1,5 +1,6 @@ CSRCS += lv_font.c CSRCS += lv_font_fmt_txt.c +CSRCS += lv_font_loader.c CSRCS += lv_font_montserrat_12.c CSRCS += lv_font_montserrat_14.c CSRCS += lv_font_montserrat_16.c diff --git a/src/lv_font/lv_font_loader.c b/src/lv_font/lv_font_loader.c new file mode 100644 index 000000000..750cdc25d --- /dev/null +++ b/src/lv_font/lv_font_loader.c @@ -0,0 +1,589 @@ +/** + * @file lv_font_loader.c + * + */ + +/********************* + * INCLUDES + *********************/ +#if LV_USE_FILESYSTEM + +#include +#include + +#include "../lvgl.h" +#include "../lv_misc/lv_fs.h" +#include "lv_font_loader.h" + + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + lv_fs_file_t * fp; + int8_t bit_pos; + uint8_t byte_value; +} bit_iterator_t; + +typedef struct font_header_bin { + uint32_t version; + uint16_t tables_count; + uint16_t font_size; + uint16_t ascent; + int16_t descent; + uint16_t typo_ascent; + int16_t typo_descent; + uint16_t typo_line_gap; + int16_t min_y; + int16_t max_y; + uint16_t default_advance_width; + uint16_t kerning_scale; + uint8_t index_to_loc_format; + uint8_t glyph_id_format; + uint8_t advance_width_format; + uint8_t bits_per_pixel; + uint8_t xy_bits; + uint8_t wh_bits; + uint8_t advance_width_bits; + uint8_t compression_id; + uint8_t subpixels_mode; + uint8_t padding; +} font_header_bin_t; + +typedef struct cmap_table_bin +{ + uint32_t cmaps_subtable_length; + uint32_t range_start; + uint16_t range_length; + uint16_t glyph_id_start; + uint16_t data_entries_count; + uint8_t format_type; + uint8_t padding; +} cmap_table_bin_t; + + +/********************** + * STATIC PROTOTYPES + **********************/ +static bit_iterator_t init_bit_iterator(lv_fs_file_t * fp); +static bool lvgl_load_font(lv_fs_file_t * fp, lv_font_t * font); + +static int read_bits_signed(bit_iterator_t *it, int n_bits, lv_fs_res_t * res); +static int read_bits(bit_iterator_t *it, int n_bits, lv_fs_res_t * res); + + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_font_t * lv_font_load(const char * font_name) +{ + bool success = false; + + lv_font_t * font = lv_mem_alloc(sizeof(lv_font_t)); + memset(font, 0, sizeof(lv_font_t)); + + lv_fs_file_t file; + lv_fs_res_t res = lv_fs_open(&file, font_name, LV_FS_MODE_RD); + + if(res == LV_FS_RES_OK) { + success = lvgl_load_font(&file, font); + } + + if (!success) + { + LV_LOG_WARN("Error opening font file: %s\n", font_name); + lv_font_free(font); + font = NULL; + } + + lv_fs_close(&file); + + return font; +} + + +void lv_font_free(lv_font_t * font) +{ + if(NULL != font) { + lv_font_fmt_txt_dsc_t * dsc = (lv_font_fmt_txt_dsc_t *) font->dsc; + + if(NULL != dsc) { + lv_font_fmt_txt_kern_classes_t * kern_dsc = + (lv_font_fmt_txt_kern_classes_t *) dsc->kern_dsc; + + if(NULL != kern_dsc) { + if(kern_dsc->class_pair_values) + lv_mem_free((void *) kern_dsc->class_pair_values); + + if(kern_dsc->left_class_mapping) + lv_mem_free((void *) kern_dsc->left_class_mapping); + + if(kern_dsc->right_class_mapping) + lv_mem_free((void *) kern_dsc->right_class_mapping); + + lv_mem_free((void *) kern_dsc); + } + + lv_font_fmt_txt_cmap_t * cmaps = + (lv_font_fmt_txt_cmap_t *) dsc->cmaps; + + if(NULL != cmaps) { + for(int i = 0; i < dsc->cmap_num; ++i) { + if(NULL != cmaps[i].glyph_id_ofs_list) + lv_mem_free((void *) cmaps[i].glyph_id_ofs_list); + if(NULL != cmaps[i].unicode_list) + lv_mem_free((void *) cmaps[i].unicode_list); + } + lv_mem_free(cmaps); + } + + if(NULL != dsc->glyph_bitmap) { + lv_mem_free((void *) dsc->glyph_bitmap); + } + if(NULL != dsc->glyph_dsc) { + lv_mem_free((void *) dsc->glyph_dsc); + } + lv_mem_free(dsc); + } + lv_mem_free(font); + } +} + + +/********************** + * STATIC FUNCTIONS + **********************/ + +static bit_iterator_t init_bit_iterator(lv_fs_file_t * fp) +{ + bit_iterator_t it; + it.fp = fp; + it.bit_pos = -1; + it.byte_value = 0; + return it; +} + +static int read_bits(bit_iterator_t *it, int n_bits, lv_fs_res_t * res) +{ + int value = 0; + while(n_bits--) { + it->byte_value = it->byte_value << 1; + it->bit_pos--; + + if(it->bit_pos < 0) { + it->bit_pos = 7; + *res = lv_fs_read(it->fp, &(it->byte_value), 1, NULL); + if (*res != LV_FS_RES_OK) { + return -1; + } + } + int8_t bit = (it->byte_value & 0x80) ? 1 : 0; + + value |= (bit << n_bits); + } + *res = LV_FS_RES_OK; + return value; +} + +static int read_bits_signed(bit_iterator_t *it, int n_bits, lv_fs_res_t * res) +{ + int value = read_bits(it, n_bits, res); + if (value & (1 << (n_bits-1))) { + for (int bit = n_bits; bit < 8; ++bit) { + value |= (1 << bit); + } + } + return value; +} + +static int read_label(lv_fs_file_t *fp, int start, const char *label) +{ + lv_fs_seek(fp, start); + + uint32_t length; + char buf[4]; + + if(lv_fs_read(fp, &length, 4, NULL) != LV_FS_RES_OK + || lv_fs_read(fp, buf, 4, NULL) != LV_FS_RES_OK + || memcmp(label, buf, 4) != 0) { + LV_LOG_WARN("Error reading '%s'.", label); + return -1; + } + + return length; +} + +static bool lvgl_load_font(lv_fs_file_t * fp, lv_font_t * font) +{ + lv_font_fmt_txt_dsc_t * font_dsc = (lv_font_fmt_txt_dsc_t *) + lv_mem_alloc(sizeof(lv_font_fmt_txt_dsc_t)); + + memset(font_dsc, 0, sizeof(lv_font_fmt_txt_dsc_t)); + + font->dsc = font_dsc; + + // header + int32_t header_length = read_label(fp, 0, "head"); + if (header_length < 0) { + return false; + } + + font_header_bin_t font_header; + if(lv_fs_read(fp, &font_header, sizeof(font_header_bin_t), NULL) != LV_FS_RES_OK) { + return false; + } + + font->base_line = -font_header.descent; + font->line_height = font_header.ascent - font_header.descent; + font->get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt; + font->get_glyph_bitmap = lv_font_get_bitmap_fmt_txt; + +#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0) + font->subpx = LV_FONT_SUBPX_NONE; +#endif + + font_dsc->bpp = font_header.bits_per_pixel; + font_dsc->kern_scale = font_header.kerning_scale; + font_dsc->bitmap_format = font_header.compression_id; + + // cmaps + uint32_t cmaps_start = header_length; + int32_t cmaps_length = read_label(fp, cmaps_start, "cmap"); + if (cmaps_length < 0) { + return false; + } + + uint32_t cmaps_subtables_count; + if(lv_fs_read(fp, &cmaps_subtables_count, sizeof(uint32_t), NULL) != LV_FS_RES_OK) { + return false; + } + + lv_font_fmt_txt_cmap_t * cmaps = + lv_mem_alloc(cmaps_subtables_count * sizeof(lv_font_fmt_txt_cmap_t)); + + memset(cmaps, 0, cmaps_subtables_count * sizeof(lv_font_fmt_txt_cmap_t)); + + font_dsc->cmaps = cmaps; + font_dsc->cmap_num = cmaps_subtables_count; + + for(unsigned int i = 0; i < font_dsc->cmap_num; ++i) { + lv_font_fmt_txt_cmap_t * cmap = &cmaps[i]; + + cmap_table_bin_t cmap_table; + if(lv_fs_read(fp, &cmap_table, sizeof(cmap_table_bin_t), NULL) != LV_FS_RES_OK) { + return false; + } + cmap->range_start = cmap_table.range_start; + cmap->range_length = cmap_table.range_length; + cmap->glyph_id_start = cmap_table.glyph_id_start; + + switch(cmap_table.format_type) { + case 0: + { + uint8_t ids_size = sizeof(uint8_t) * cmap->range_length; + uint8_t * glyph_id_ofs_list = lv_mem_alloc(ids_size); + if(lv_fs_read(fp, glyph_id_ofs_list, ids_size, NULL) != LV_FS_RES_OK) { + return false; + } + cmap->type = LV_FONT_FMT_TXT_CMAP_FORMAT0_FULL; + cmap->unicode_list = NULL; + cmap->glyph_id_ofs_list = glyph_id_ofs_list; + cmap->list_length = 0; + break; + } + case 1: + LV_LOG_WARN("cmap format: 1 not yet supported."); + return false; + case 2: + cmap->type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY; + cmap->unicode_list = NULL; + cmap->glyph_id_ofs_list = NULL; + cmap->list_length = 0; + break; + case 3: + { + uint8_t list_size = sizeof(uint16_t) * cmap_table.data_entries_count; + uint16_t * unicode_list = (uint16_t *) lv_mem_alloc(list_size); + + if(lv_fs_read(fp, unicode_list, list_size, NULL) != LV_FS_RES_OK) { + return false; + } + + cmap->type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY; + cmap->unicode_list = unicode_list; + cmap->glyph_id_ofs_list = NULL; + cmap->list_length = cmap_table.data_entries_count; + break; + } + default: + LV_LOG_WARN("Unknown cmaps format type %d.", cmap_table.format_type); + return false; + } + } + + // loca + uint32_t loca_start = cmaps_start + cmaps_length; + int32_t loca_length = read_label(fp, loca_start, "loca"); + if (loca_length < 0) { + return false; + } + + uint32_t loca_count; + if(lv_fs_read(fp, &loca_count, sizeof(uint32_t), NULL) != LV_FS_RES_OK) { + return false; + } + + bool failed = false; + uint32_t *glyph_offset = lv_mem_alloc(sizeof(uint32_t) * (loca_count + 1)); + + for(unsigned int i = 0; i < loca_count; ++i) { + if(font_header.index_to_loc_format == 0) { + uint16_t offset; + if(lv_fs_read(fp, &offset, sizeof(uint16_t), NULL) != LV_FS_RES_OK) { + failed = true; + break; + } + glyph_offset[i] = offset; + } + else if(font_header.index_to_loc_format == 1) { + uint32_t offset; + if(lv_fs_read(fp, &offset, sizeof(uint32_t), NULL) != LV_FS_RES_OK) { + failed = true; + break; + } + glyph_offset[i] = offset; + } + else { + LV_LOG_WARN("Unknown index_to_loc_format: %d.", font_header.index_to_loc_format); + failed = true; + break; + } + } + + if (failed) { + lv_mem_free(glyph_offset); + return false; + } + + // glyph + uint32_t glyph_start = loca_start + loca_length; + int32_t glyph_length = read_label(fp, glyph_start, "glyf"); + if (glyph_length < 0) { + lv_mem_free(glyph_offset); + return false; + } + + lv_font_fmt_txt_glyph_dsc_t * glyph_dsc = (lv_font_fmt_txt_glyph_dsc_t *) + lv_mem_alloc((loca_count + 1) * sizeof(lv_font_fmt_txt_glyph_dsc_t)); + + memset(glyph_dsc, 0, + (loca_count + 1) * sizeof(lv_font_fmt_txt_glyph_dsc_t)); + + font_dsc->glyph_dsc = glyph_dsc; + + int cur_bmp_size = 0; + + for(unsigned int i = 0; i < loca_count; ++i) { + lv_font_fmt_txt_glyph_dsc_t * gdsc = &glyph_dsc[i]; + + lv_fs_res_t res = lv_fs_seek(fp, glyph_start + glyph_offset[i]); + if(res != LV_FS_RES_OK) { + failed = true; + break; + } + + bit_iterator_t bit_it = init_bit_iterator(fp); + + if(font_header.advance_width_bits == 0) { + gdsc->adv_w = font_header.default_advance_width; + } + else { + gdsc->adv_w = read_bits(&bit_it, font_header.advance_width_bits, &res); + if(res != LV_FS_RES_OK) { + failed = true; + break; + } + + // TODO: understand how to interpret advance_width_format + if(font_header.advance_width_format == 0) { // uint + } + else if(font_header.advance_width_format == 1) { // unsigned with 4 bits fractional part + } + else { + LV_LOG_WARN("error unknown advance_width_format"); + failed = true; + break; + } + } + + gdsc->ofs_x = read_bits_signed(&bit_it, font_header.xy_bits, &res); + if(res != LV_FS_RES_OK) { + failed = true; + break; + } + + gdsc->ofs_y = read_bits_signed(&bit_it, font_header.xy_bits, &res); + if(res != LV_FS_RES_OK) { + failed = true; + break; + } + + gdsc->box_w = read_bits(&bit_it, font_header.wh_bits, &res); + if(res != LV_FS_RES_OK) { + failed = true; + break; + } + + gdsc->box_h = read_bits(&bit_it, font_header.wh_bits, &res); + if(res != LV_FS_RES_OK) { + failed = true; + break; + } + + int nbits = font_header.advance_width_bits + 2 * font_header.xy_bits + 2 * font_header.wh_bits; + int next_offset = (i < loca_count - 1) ? glyph_offset[i + 1] : (uint32_t) glyph_length; + int bmp_size = next_offset - glyph_offset[i] - nbits / 8; + + if(i == 0) { + gdsc->adv_w = 0; + gdsc->box_w = 0; + gdsc->box_h = 0; + gdsc->ofs_x = 0; + gdsc->ofs_y = 0; + } + + gdsc->bitmap_index = cur_bmp_size; + if(gdsc->box_w * gdsc->box_h != 0) { + cur_bmp_size += bmp_size; + } + } + + if (failed) { + lv_mem_free(glyph_offset); + return false; + } + + uint8_t * glyph_bmp = (uint8_t *) lv_mem_alloc(sizeof(uint8_t) * cur_bmp_size); + + font_dsc->glyph_bitmap = glyph_bmp; + + cur_bmp_size = 0; + + for(unsigned int i = 1; i < loca_count; ++i) { + lv_fs_res_t res = lv_fs_seek(fp, glyph_start + glyph_offset[i]); + if(res != LV_FS_RES_OK) { + failed = true; + break; + } + bit_iterator_t bit_it = init_bit_iterator(fp); + + int nbits = font_header.advance_width_bits + 2 * font_header.xy_bits + 2 * font_header.wh_bits; + + read_bits(&bit_it, nbits, &res); + if(res != LV_FS_RES_OK) { + failed = true; + break; + } + + if(glyph_dsc[i].box_w * glyph_dsc[i].box_h == 0) { + continue; + } + + int next_offset = (i < loca_count - 1) ? glyph_offset[i + 1] : (uint32_t) glyph_length; + int bmp_size = next_offset - glyph_offset[i] - nbits / 8; + + for(int k = 0; k < bmp_size; ++k) { + glyph_bmp[cur_bmp_size + k] = read_bits(&bit_it, 8, &res); + if(res != LV_FS_RES_OK) { + failed = true; + break; + } + } + if (failed) { + break; + } + cur_bmp_size += bmp_size; + } + + lv_mem_free(glyph_offset); + + if (failed) { + return false; + } + + if(lv_fs_seek(fp, glyph_start + glyph_length) != LV_FS_RES_OK) { + return false; + } + + uint32_t kern_start = glyph_start + glyph_length; + int32_t kern_length = read_label(fp, kern_start, "kern"); + if (kern_length < 0) { + return false; + } + + uint8_t kern_format_type; + int32_t padding; + if (lv_fs_read(fp, &kern_format_type, sizeof(uint8_t), NULL) != LV_FS_RES_OK || + lv_fs_read(fp, &padding, 3 * sizeof(uint8_t), NULL) != LV_FS_RES_OK) { + return false; + } + + lv_font_fmt_txt_kern_classes_t * kern_classes = + lv_mem_alloc(sizeof(lv_font_fmt_txt_kern_classes_t)); + + memset(kern_classes, 0, sizeof(lv_font_fmt_txt_kern_classes_t)); + + font_dsc->kern_dsc = kern_classes; // TODO: review + font_dsc->kern_classes = 1; // TODO: review this + + if(0 == kern_format_type) { // sorted pairs + LV_LOG_WARN("kern_format_type 0 not supported yet!"); + return false; + } + else if(3 == kern_format_type) { // array M*N of classes + + uint16_t kern_class_mapping_length; + uint8_t kern_table_rows; + uint8_t kern_table_cols; + + if(lv_fs_read(fp, &kern_class_mapping_length, sizeof(uint16_t), NULL) != LV_FS_RES_OK || + lv_fs_read(fp, &kern_table_rows, sizeof(uint8_t), NULL) != LV_FS_RES_OK || + lv_fs_read(fp, &kern_table_cols, sizeof(uint8_t), NULL) != LV_FS_RES_OK) { + return false; + } + + kern_classes->left_class_cnt = kern_table_rows; + kern_classes->right_class_cnt = kern_table_cols; + + uint8_t * kern_left = lv_mem_alloc(kern_class_mapping_length); + uint8_t * kern_right = lv_mem_alloc(kern_class_mapping_length); + + kern_classes->left_class_mapping = kern_left; + kern_classes->right_class_mapping = kern_right; + + int kern_values_length = sizeof(int8_t) * kern_table_rows * kern_table_cols; + int8_t * kern_values = (int8_t *) lv_mem_alloc(kern_values_length); + + kern_classes->class_pair_values = kern_values; + + if(lv_fs_read(fp, kern_left, kern_class_mapping_length, NULL) != LV_FS_RES_OK || + lv_fs_read(fp, kern_right, kern_class_mapping_length, NULL) != LV_FS_RES_OK || + lv_fs_read(fp, kern_values, kern_values_length, NULL) != LV_FS_RES_OK) { + return false; + } + } + else { + LV_LOG_WARN("kern_format_type %d not supported yet!", kern_format_type); + return false; + } + + return true; +} + +#endif /*LV_USE_FILESYSTEM*/ + diff --git a/src/lv_font/lv_font_loader.h b/src/lv_font/lv_font_loader.h new file mode 100644 index 000000000..773ba8f8a --- /dev/null +++ b/src/lv_font/lv_font_loader.h @@ -0,0 +1,44 @@ +/** + * @file lv_font_loader.h + * + */ + +#ifndef LV_FONT_LOADER_H +#define LV_FONT_LOADER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +#if LV_USE_FILESYSTEM + +lv_font_t * lv_font_load(const char * fontName); +void lv_font_free(lv_font_t * font); + +#endif + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*LV_FONT_LOADER_H*/ diff --git a/tests/Makefile b/tests/Makefile index 839ee92d0..c429a5b8f 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -30,6 +30,7 @@ CSRCS += lv_test_assert.c CSRCS += lv_test_core/lv_test_core.c CSRCS += lv_test_core/lv_test_obj.c CSRCS += lv_test_core/lv_test_style.c +CSRCS += lv_test_core/lv_test_font_loader.c OBJEXT ?= .o diff --git a/tests/lv_test_assert.c b/tests/lv_test_assert.c index a72ac16f3..a81d48ea4 100644 --- a/tests/lv_test_assert.c +++ b/tests/lv_test_assert.c @@ -97,6 +97,15 @@ void lv_test_error(const char * s, ...) exit(1); } +void lv_test_assert_true(int32_t expression, const char * s) +{ + if(!expression) { + lv_test_error(" FAIL: %s. (Expected: not zero)", s, expression); + } else { + lv_test_print(" PASS: %s. (Expected: not zero)", s, expression); + } +} + void lv_test_assert_int_eq(int32_t n_ref, int32_t n_act, const char * s) { if(n_ref != n_act) { diff --git a/tests/lv_test_assert.h b/tests/lv_test_assert.h index 866a6159e..c95db784f 100644 --- a/tests/lv_test_assert.h +++ b/tests/lv_test_assert.h @@ -32,6 +32,7 @@ extern "C" { void lv_test_print(const char * s, ...); void lv_test_exit(const char * s, ...); void lv_test_error(const char * s, ...); +void lv_test_assert_true(int32_t expression, const char * s); void lv_test_assert_int_eq(int32_t n1, int32_t n2, const char * s); void lv_test_assert_int_gt(int32_t n_ref, int32_t n_act, const char * s); void lv_test_assert_int_lt(int32_t n_ref, int32_t n_act, const char * s); diff --git a/tests/lv_test_core/lv_test_core.c b/tests/lv_test_core/lv_test_core.c index f74f81611..37f65464b 100644 --- a/tests/lv_test_core/lv_test_core.c +++ b/tests/lv_test_core/lv_test_core.c @@ -12,6 +12,7 @@ #include "lv_test_core.h" #include "lv_test_obj.h" #include "lv_test_style.h" +#include "lv_test_font_loader.h" /********************* * DEFINES @@ -46,6 +47,7 @@ void lv_test_core(void) lv_test_obj(); lv_test_style(); + lv_test_font_loader(); } diff --git a/tests/lv_test_core/lv_test_font_loader.c b/tests/lv_test_core/lv_test_font_loader.c new file mode 100644 index 000000000..d34c4f004 --- /dev/null +++ b/tests/lv_test_core/lv_test_font_loader.c @@ -0,0 +1,154 @@ +/** + * @file lv_test_font_loader.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "../lv_test_assert.h" +#if LV_BUILD_TEST +#include "../lvgl.h" +#include "../src/lv_font/lv_font_fmt_txt.h" +#include "../src/lv_font/lv_font.h" +#include "../src/lv_font/lv_font_loader.h" + +#include "lv_test_font_loader.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static int compare_fonts(lv_font_t * f1, lv_font_t * f2); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void lv_test_font_loader(void) +{ + lv_font_t * font = lv_font_load("f:lv_font_montserrat_12.bin"); + compare_fonts(&lv_font_montserrat_12, font); +} + +static int compare_fonts(lv_font_t * f1, lv_font_t * f2) +{ + lv_test_assert_true(f1 != NULL && f2 != NULL, "error loading font"); + + lv_test_assert_ptr_eq(f1->get_glyph_dsc, f2->get_glyph_dsc, "glyph_dsc"); + lv_test_assert_ptr_eq(f1->get_glyph_bitmap, f2->get_glyph_bitmap, "glyph_bitmap"); + lv_test_assert_int_eq(f1->line_height, f2->line_height, "line_height"); + lv_test_assert_int_eq(f1->base_line, f2->base_line, "base_line"); +#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0) + lv_test_assert_int_eq(f1->subpx, f2->subpx, "subpx"); +#endif + lv_font_fmt_txt_dsc_t * dsc1 = (lv_font_fmt_txt_dsc_t *) f1->dsc; + lv_font_fmt_txt_dsc_t * dsc2 = (lv_font_fmt_txt_dsc_t *) f2->dsc; + + lv_test_assert_int_eq(dsc1->kern_scale, dsc2->kern_scale, "kern_scale"); + lv_test_assert_int_eq(dsc1->cmap_num, dsc2->cmap_num, "cmap_num"); + lv_test_assert_int_eq(dsc1->bpp, dsc2->bpp, "bpp"); + lv_test_assert_int_eq(dsc1->kern_classes, dsc2->kern_classes, "kern_classes"); + lv_test_assert_int_eq(dsc1->bitmap_format, dsc2->bitmap_format, "bitmap_format"); + + // cmaps + int total_glyphs = 0; + for(int i = 0; i < dsc1->cmap_num; ++i) { + lv_font_fmt_txt_cmap_t * cmaps1 = (lv_font_fmt_txt_cmap_t *) &dsc1->cmaps[i]; + lv_font_fmt_txt_cmap_t * cmaps2 = (lv_font_fmt_txt_cmap_t *) &dsc2->cmaps[i]; + + lv_test_assert_int_eq(cmaps1->range_start, cmaps2->range_start, "range_start"); + lv_test_assert_int_eq(cmaps1->range_length, cmaps2->range_length, "range_length"); + lv_test_assert_int_eq(cmaps1->glyph_id_start, cmaps2->glyph_id_start, "glyph_id_start"); + lv_test_assert_int_eq(cmaps1->type, cmaps2->type, "type"); + lv_test_assert_int_eq(cmaps1->list_length, cmaps2->list_length, "list_length"); + + if(cmaps1->unicode_list != NULL && cmaps2->unicode_list != NULL) { + lv_test_assert_true(cmaps1->unicode_list && cmaps2->unicode_list, "unicode_list"); + + for(int k = 0; k < cmaps1->list_length; ++k) { + lv_test_assert_int_eq(cmaps1->unicode_list[k], cmaps2->unicode_list[k], "unicode_list"); + } + total_glyphs += cmaps1->list_length; + } + else { + total_glyphs += cmaps1->range_length; + lv_test_assert_ptr_eq(cmaps1->unicode_list, cmaps2->unicode_list, "unicode_list"); + } + + if(cmaps1->glyph_id_ofs_list != NULL && cmaps2->glyph_id_ofs_list != NULL) { + uint8_t * ids1 = (uint8_t *) cmaps1->glyph_id_ofs_list; + uint8_t * ids2 = (uint8_t *) cmaps2->glyph_id_ofs_list; + + for(int j = 0; j < cmaps1->range_length; j++) { + lv_test_assert_int_eq(ids1[j], ids2[j], "glyph_id_ofs_list"); + } + } + else { + lv_test_assert_ptr_eq(cmaps1->glyph_id_ofs_list, cmaps2->glyph_id_ofs_list, "glyph_id_ofs_list"); + } + } + + // kern_dsc + lv_font_fmt_txt_kern_classes_t * kern1 = (lv_font_fmt_txt_kern_classes_t *) dsc1->kern_dsc; + lv_font_fmt_txt_kern_classes_t * kern2 = (lv_font_fmt_txt_kern_classes_t *) dsc2->kern_dsc; + + lv_test_assert_int_eq(kern1->right_class_cnt, kern2->right_class_cnt, "right_class_cnt"); + lv_test_assert_int_eq(kern1->left_class_cnt, kern2->left_class_cnt, "left_class_cnt"); + + for(int i = 0; i < kern1->left_class_cnt; ++i) { + lv_test_assert_int_eq(kern1->left_class_mapping[i], + kern2->left_class_mapping[i], "left_class_mapping"); + } + for(int i = 0; i < kern1->right_class_cnt; ++i) { + lv_test_assert_int_eq(kern1->right_class_mapping[i], + kern2->right_class_mapping[i], "right_class_mapping"); + } + + for(int i = 0; i < kern1->right_class_cnt * kern1->left_class_cnt; ++i) { + lv_test_assert_int_eq(kern1->class_pair_values[i], + kern2->class_pair_values[i], "class_pair_values"); + } + + // TODO: glyph_bitmap + + lv_font_fmt_txt_glyph_dsc_t * glyph_dsc1 = (lv_font_fmt_txt_glyph_dsc_t *) dsc1->glyph_dsc; + lv_font_fmt_txt_glyph_dsc_t * glyph_dsc2 = (lv_font_fmt_txt_glyph_dsc_t *) dsc2->glyph_dsc; + + for(int i = 0; i < total_glyphs; ++i) { + //lv_test_assert_int_eq(glyph_dsc1[i].bitmap_index, glyph_dsc2[i].bitmap_index, "bitmap_index"); + lv_test_assert_int_eq(glyph_dsc1[i].adv_w, glyph_dsc2[i].adv_w, "adv_w"); + lv_test_assert_int_eq(glyph_dsc1[i].box_w, glyph_dsc2[i].box_w, "box_w"); + lv_test_assert_int_eq(glyph_dsc1[i].box_h, glyph_dsc2[i].box_h, "box_h"); + lv_test_assert_int_eq(glyph_dsc1[i].ofs_x, glyph_dsc2[i].ofs_x, "ofs_x"); + lv_test_assert_int_eq(glyph_dsc1[i].ofs_y, glyph_dsc2[i].ofs_y, "ofs_y"); + } + + LV_LOG_INFO("No differences found!"); + return 0; +} + + +/********************** + * STATIC FUNCTIONS + **********************/ + +#endif // LV_BUILD_TEST + diff --git a/tests/lv_test_core/lv_test_font_loader.h b/tests/lv_test_core/lv_test_font_loader.h new file mode 100644 index 000000000..fe29926bf --- /dev/null +++ b/tests/lv_test_core/lv_test_font_loader.h @@ -0,0 +1,39 @@ +/** + * @file lv_font_loader.h + * + */ + +#ifndef LV_TEST_FONT_LOADER_H +#define LV_TEST_FONT_LOADER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void lv_test_font_loader(void); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*LV_TEST_FONT_LOADER_H*/ + diff --git a/tests/lv_test_main.c b/tests/lv_test_main.c index 8c69a98df..061b6b1ef 100644 --- a/tests/lv_test_main.c +++ b/tests/lv_test_main.c @@ -23,6 +23,61 @@ int main(void) } +static lv_fs_res_t open_cb(struct _lv_fs_drv_t * drv, void * file_p, const char * path, lv_fs_mode_t mode) +{ + (void) drv; + (void) mode; + + FILE * fp = fopen(path, "rb"); // only reading is supported + + *((FILE **)file_p) = fp; + return NULL == fp ? LV_FS_RES_UNKNOWN : LV_FS_RES_OK; +} + +static lv_fs_res_t close_cb(struct _lv_fs_drv_t * drv, void * file_p) +{ + (void) drv; + + FILE * fp = *((FILE **) file_p); + fclose(fp); + return LV_FS_RES_OK; +} + +static lv_fs_res_t read_cb(struct _lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br) +{ + (void) drv; + + FILE * fp = *((FILE **) file_p); + *br = fread(buf, 1, btr, fp); + return (*br <= 0) ? LV_FS_RES_UNKNOWN : LV_FS_RES_OK; +} + +static lv_fs_res_t seek_cb(struct _lv_fs_drv_t * drv, void * file_p, uint32_t pos) +{ + (void) drv; + + FILE * fp = *((FILE **) file_p); + fseek (fp, pos, SEEK_SET); + + return LV_FS_RES_OK; +} + +static lv_fs_res_t tell_cb(struct _lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p) +{ + (void) drv; + + FILE * fp = *((FILE **) file_p); + *pos_p = ftell(fp); + + return LV_FS_RES_OK; +} + +static bool ready_cb(struct _lv_fs_drv_t * drv) +{ + (void) drv; + return true; +} + static void hal_init(void) { static lv_disp_buf_t disp_buf; @@ -35,6 +90,20 @@ static void hal_init(void) disp_drv.buffer = &disp_buf; disp_drv.flush_cb = dummy_flush_cb; lv_disp_drv_register(&disp_drv); + + lv_fs_drv_t drv; + lv_fs_drv_init(&drv); /*Basic initialization*/ + + drv.letter = 'f'; /*An uppercase letter to identify the drive */ + drv.file_size = sizeof(FILE *); /*Size required to store a file object*/ + drv.ready_cb = ready_cb; /*Callback to tell if the drive is ready to use */ + drv.open_cb = open_cb; /*Callback to open a file */ + drv.close_cb = close_cb; /*Callback to close a file */ + drv.read_cb = read_cb; /*Callback to read a file */ + drv.seek_cb = seek_cb; /*Callback to seek in a file (Move cursor) */ + drv.tell_cb = tell_cb; /*Callback to tell the cursor position */ + + lv_fs_drv_register(&drv); /*Finally register the drive*/ }