/* * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "clar.h" #include "fixtures/load_test_resources.h" // FW headers #include "applib/graphics/text_resources.h" #include "resource/resource.h" #include "resource/resource_ids.auto.h" #include "resource/system_resource.h" #include "util/size.h" // Fakes #include "fake_app_manager.h" // Stubs #include "stubs_analytics.h" #include "stubs_bootbits.h" #include "stubs_logging.h" #include "stubs_mutex.h" #include "stubs_pbl_malloc.h" #include "stubs_pebble_tasks.h" #include "stubs_passert.h" #include "stubs_print.h" #include "stubs_prompt.h" #include "stubs_queue.h" #include "stubs_serial.h" #include "stubs_sleep.h" #include "stubs_syscalls.h" #include "stubs_prompt.h" #include "stubs_task_watchdog.h" #include "stubs_memory_layout.h" #define WILDCARD_CODEPOINT 0x25AF static FontCache s_font_cache; static FontInfo s_font_info; #define FONT_COMPRESSION_FIXTURE_PATH "font_compression" // Helpers //////////////////////////////////// static uint8_t glyph_get_size_bytes(const GlyphData *glyph) { return ((glyph->header.width_px * glyph->header.height_px) + (8 - 1)) / 8; } void test_text_resources__initialize(void) { fake_spi_flash_init(0, 0x1000000); pfs_init(false); pfs_format(true /* write erase headers */); load_resource_fixture_in_flash(RESOURCES_FIXTURE_PATH, SYSTEM_RESOURCES_FIXTURE_NAME, false /* is_next */); load_resource_fixture_on_pfs(RESOURCES_FIXTURE_PATH, CHINESE_FIXTURE_NAME, "lang"); //cl_assert(resource_has_valid_system_resources()); memset(&s_font_info, 0, sizeof(s_font_info)); memset(&s_font_cache, 0, sizeof(s_font_cache)); FontCache *font_cache = &s_font_cache; memset(font_cache->cache_keys, 0, sizeof(font_cache->cache_keys)); memset(font_cache->cache_data, 0, sizeof(font_cache->cache_data)); keyed_circular_cache_init(&font_cache->line_cache, font_cache->cache_keys, font_cache->cache_data, sizeof(LineCacheData), LINE_CACHE_SIZE); resource_init(); } void test_text_resources__cleanup(void) { } void test_text_resources__init_font(void) { uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18; cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info)); cl_assert_equal_i(FONT_VERSION(s_font_info.base.md.version), 3); cl_assert_equal_i(s_font_info.base.md.wildcard_codepoint, WILDCARD_CODEPOINT); cl_assert_equal_i(s_font_info.base.md.codepoint_bytes, 2); } void test_text_resources__horiz_advance(void) { uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18; cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info)); int8_t horiz_advance = text_resources_get_glyph_horiz_advance(&s_font_cache, 'a', &s_font_info); cl_assert(horiz_advance != 0); cl_assert_equal_i(horiz_advance, text_resources_get_glyph_horiz_advance(&s_font_cache, 'a', &s_font_info)); cl_assert_equal_i(horiz_advance, text_resources_get_glyph_horiz_advance(&s_font_cache, 'a', &s_font_info)); } void test_text_resources__horiz_advance_multiple(void) { uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18; cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info)); int8_t horiz_advance = text_resources_get_glyph_horiz_advance(&s_font_cache, 'a', &s_font_info); cl_assert(horiz_advance != 0); cl_assert_equal_i(horiz_advance, text_resources_get_glyph_horiz_advance(&s_font_cache, 'a', &s_font_info)); horiz_advance = text_resources_get_glyph_horiz_advance(&s_font_cache, 'b', &s_font_info); cl_assert(horiz_advance != 0); cl_assert_equal_i(horiz_advance, text_resources_get_glyph_horiz_advance(&s_font_cache, 'b', &s_font_info)); horiz_advance = text_resources_get_glyph_horiz_advance(&s_font_cache, 'c', &s_font_info); cl_assert(horiz_advance != 0); cl_assert_equal_i(horiz_advance, text_resources_get_glyph_horiz_advance(&s_font_cache, 'c', &s_font_info)); } void test_text_resources__get_glyph_multiple(void) { const uint8_t a_glyph_data_bytes[] = {0x2e, 0x42, 0x2e, 0x63, 0xb6}; const uint8_t b_glyph_data_bytes[] = {0x21, 0x84, 0x36, 0x63, 0x8c, 0x71, 0x36}; const uint8_t c_glyph_data_bytes[] = {0x2e, 0x86, 0x10, 0x42, 0x74}; uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18; cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info)); uint8_t glyph_size_bytes; const GlyphData *glyph; glyph = text_resources_get_glyph(&s_font_cache, 'a', &s_font_info); glyph_size_bytes = glyph_get_size_bytes(glyph); cl_assert_equal_m(a_glyph_data_bytes, glyph->data, glyph_size_bytes); glyph = text_resources_get_glyph(&s_font_cache, 'b', &s_font_info); glyph_size_bytes = glyph_get_size_bytes(glyph); cl_assert_equal_m(b_glyph_data_bytes, glyph->data, glyph_size_bytes); glyph = text_resources_get_glyph(&s_font_cache, 'c', &s_font_info); glyph_size_bytes = glyph_get_size_bytes(glyph); cl_assert_equal_m(c_glyph_data_bytes, glyph->data, glyph_size_bytes); } void test_text_resources__init_backup_font(void) { // load the built in fallback font uint32_t font_fallback = RESOURCE_ID_FONT_FALLBACK_INTERNAL; cl_assert(text_resources_init_font(0, font_fallback, 0, &s_font_info)); cl_assert_equal_i(FONT_VERSION(s_font_info.base.md.version), 3); cl_assert_equal_i(s_font_info.base.md.wildcard_codepoint, WILDCARD_CODEPOINT); } void test_text_resources__test_backup_wildcard(void) { const uint8_t wildcard_bytes[] = {0x3f, 0xc6, 0x18, 0x63, 0x8c, 0x31, 0xc6, 0x0f}; uint32_t font_fallback = RESOURCE_ID_FONT_FALLBACK_INTERNAL; cl_assert(text_resources_init_font(0, font_fallback, 0, &s_font_info)); int8_t horiz_advance = text_resources_get_glyph_horiz_advance(&s_font_cache, WILDCARD_CODEPOINT, &s_font_info); cl_assert(horiz_advance != 0); cl_assert_equal_i(horiz_advance, text_resources_get_glyph_horiz_advance(&s_font_cache, WILDCARD_CODEPOINT, &s_font_info)); const GlyphData *glyph = text_resources_get_glyph(&s_font_cache, WILDCARD_CODEPOINT, &s_font_info); cl_assert_equal_i(glyph->header.width_px, 5); cl_assert_equal_i(glyph->header.height_px, 12); uint8_t glyph_size_bytes = glyph_get_size_bytes(glyph); cl_assert_equal_m(wildcard_bytes, glyph->data, glyph_size_bytes); } void test_text_resources__test_gothic_wildcard(void) { uint8_t wildcard_bytes[] = {0xff, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x83, 0xc1, 0x60, 0x30, 0x18, 0x0c, 0xfe, 0x01}; uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18; cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info)); int8_t horiz_advance = text_resources_get_glyph_horiz_advance(&s_font_cache, WILDCARD_CODEPOINT, &s_font_info); cl_assert(horiz_advance != 0); cl_assert_equal_i(horiz_advance, text_resources_get_glyph_horiz_advance(&s_font_cache, WILDCARD_CODEPOINT, &s_font_info)); const GlyphData *glyph = text_resources_get_glyph(&s_font_cache, WILDCARD_CODEPOINT, &s_font_info); cl_assert_equal_i(glyph->header.width_px, 7); cl_assert_equal_i(glyph->header.height_px, 15); uint8_t glyph_size_bytes = glyph_get_size_bytes(glyph); cl_assert_equal_m(wildcard_bytes, glyph->data, glyph_size_bytes); } void test_text_resources__extended_font(void) { const uint8_t chinese_wildcard_bytes[] = {0xff, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x83, 0xc1, 0x60, 0x30, 0x18, 0x0c, 0xfe, 0x01}; const uint8_t a_glyph_data_bytes[] = {0x2e, 0x42, 0x2e, 0x63, 0xb6}; const uint8_t chinese_glyph_data_bytes[] = {0x00, 0x0C, 0xE2, 0x01, 0x0F, 0x80, 0x30, 0x40, 0x08, 0x10, 0x04, 0x08, 0x82, 0xFC, 0xFF, 0x80, 0x00, 0x44, 0x00, 0x26, 0x01, 0x11, 0x41, 0x08, 0x11, 0x84, 0x04, 0x82, 0xC0, 0x01, 0x40, 0x00}; uint32_t gothic_18_bold_handle = RESOURCE_ID_GOTHIC_18; uint32_t gothic_18_bold_extended_handle = RESOURCE_ID_GOTHIC_18_EXTENDED; cl_assert(text_resources_init_font(0, gothic_18_bold_handle, gothic_18_bold_extended_handle, &s_font_info)); cl_assert(s_font_info.loaded); cl_assert(s_font_info.extended); uint8_t glyph_size_bytes; const GlyphData *glyph; glyph = text_resources_get_glyph(&s_font_cache, 'a', &s_font_info); cl_assert(glyph != NULL); glyph_size_bytes = glyph_get_size_bytes(glyph); cl_assert_equal_m(a_glyph_data_bytes, glyph->data, glyph_size_bytes); glyph = text_resources_get_glyph(&s_font_cache, 0x4E50 /* 乐 */, &s_font_info); // the chinese pbpack contains the letter 你, it should succeed cl_assert(glyph != NULL); glyph_size_bytes = glyph_get_size_bytes(glyph); cl_assert_equal_m(chinese_glyph_data_bytes, glyph->data, glyph_size_bytes); glyph = text_resources_get_glyph(&s_font_cache, 0x8888 /* 袈 */, &s_font_info); // the chinese pbpack does not contain the letter 袈, it should return the wildcard cl_assert(glyph != NULL); glyph_size_bytes = glyph_get_size_bytes(glyph); cl_assert_equal_m(chinese_wildcard_bytes, glyph->data, glyph_size_bytes); } void test_text_resources__test_emoji_font(void) { const uint8_t phone_bytes[] = {0xfe, 0x81, 0x81, 0x3c, 0x66, 0x42, 0xc3, 0xe7, 0xff, 0x00, 0x00, 0x00}; uint32_t gothic_18_emoji_handle = RESOURCE_ID_GOTHIC_18_EMOJI; cl_assert(text_resources_init_font(0, gothic_18_emoji_handle, 0, &s_font_info)); uint8_t glyph_size_bytes; const GlyphData *glyph; const Codepoint PHONE_CODEPOINT = 0x260E; glyph = text_resources_get_glyph(&s_font_cache, PHONE_CODEPOINT, &s_font_info); cl_assert(glyph != NULL); glyph_size_bytes = glyph_get_size_bytes(glyph); cl_assert_equal_m(phone_bytes, glyph->data, glyph_size_bytes); } void DISABLED_test_text_resources__test_emoji_fallback(void) { const uint8_t phone_bytes[] = {0xfe, 0x81, 0x81, 0x3c, 0x66, 0x42, 0xc3, 0xe7, 0xff, 0x00, 0x00, 0x00}; uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18; cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info)); uint8_t glyph_size_bytes; const GlyphData *glyph; const Codepoint PHONE_CODEPOINT = 0x260E; glyph = text_resources_get_glyph(&s_font_cache, PHONE_CODEPOINT, &s_font_info); cl_assert(glyph != NULL); glyph_size_bytes = glyph_get_size_bytes(glyph); cl_assert_equal_m(phone_bytes, glyph->data, glyph_size_bytes); } void test_text_resources__test_glyph_decompression(void) { // There is no way to get the list of glyphs present in a font with the existing API. This list // of ranges lists the 371 glyphs currently in fontname.ttf typedef struct Codepoint_Range { uint16_t start; uint16_t end; } CodePoint_Range; const CodePoint_Range codepoint_range[] = { { 0x0020, 0x007E }, { 0x00A0, 0x00AC }, { 0x00AE, 0x00D6 }, { 0x00D9, 0x017F }, { 0x0192, 0x0192 }, { 0x01FC, 0x01FF }, { 0x0218, 0x021B }, { 0x02C6, 0x02DD }, { 0x03C0, 0x03C0 }, { 0x2013, 0x2014 }, { 0x2018, 0x201A }, { 0x201C, 0x201E }, { 0x2020, 0x2022 }, { 0x2026, 0x2026 }, { 0x2030, 0x2030 }, { 0x2039, 0x203A }, { 0x2044, 0x2044 }, { 0x20AC, 0x20AC }, { 0x2122, 0x2122 }, { 0x2126, 0x2126 }, { 0x2202, 0x2202 }, { 0x2206, 0x2206 }, { 0x220F, 0x220F }, { 0x2211, 0x2212 }, { 0x221A, 0x221A }, { 0x221E, 0x221E }, { 0x222B, 0x222B }, { 0x2248, 0x2248 }, { 0x2260, 0x2260 }, { 0x2264, 0x2265 }, { 0x25AF, 0x25AF }, { 0x25CA, 0x25CA }, { 0xF6C3, 0xF6C3 }, { 0xFB01, 0xFB02 } }; // Create a second FontInfo for the compressed font. // The uncompressed font will use the global. FontInfo font_info_compressed; memset(&font_info_compressed, 0, sizeof(font_info_compressed)); // Load GOTHIC_18 uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18; cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info)); cl_assert_equal_i(FONT_VERSION(s_font_info.base.md.version), 3); cl_assert(!HAS_FEATURE(s_font_info.base.md.version, VERSION_FIELD_FEATURE_RLE4)); // Load GOTHIC_18_COMPRESSED. This is the same font, added by hand to the system resource pack. // To do this, simply copy the GOTHIC_18 stanza in resource/normal/base/resource_map.json, change // the name to include _COMPRESSED, and add the field: "compress": "RLE4". Rebuild, and run // ./tools/update_system_pbpack.sh uint32_t gothic_18_compressed_handle = RESOURCE_ID_GOTHIC_18_COMPRESSED; // Read source to fix cl_assert(text_resources_init_font(0, gothic_18_compressed_handle, 0, &font_info_compressed)); cl_assert_equal_i(FONT_VERSION(font_info_compressed.base.md.version), 3); cl_assert(HAS_FEATURE(font_info_compressed.base.md.version, VERSION_FIELD_FEATURE_RLE4)); // For each glyph in the font, get both the compressed and uncompressed bit field & header, and // assert that they are identical (ignoring any possible garbage after the bitmap). // Load the uncompressed glyph into this local glyph_buffer, and use the font cache for the // compressed glyph. uint8_t glyph_buffer[sizeof(GlyphHeaderData) + CACHE_GLYPH_SIZE]; for (unsigned index = 0; index < ARRAY_LENGTH(codepoint_range); ++index) { for (unsigned codepoint = codepoint_range[index].start; codepoint <= codepoint_range[index].end; ++codepoint) { const GlyphData *glyph = text_resources_get_glyph(&s_font_cache, codepoint, &s_font_info); cl_assert(glyph); unsigned glyph_size = sizeof(GlyphHeaderData) + glyph_get_size_bytes(glyph); memcpy(glyph_buffer, glyph->data, glyph_size); glyph = text_resources_get_glyph(&s_font_cache, codepoint, &font_info_compressed); cl_assert(glyph); cl_assert_equal_m(glyph->data, glyph_buffer, glyph_size); } } }