From 25d854b26184f670b375e504b8881e417c2e2a7b Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Sat, 25 Jan 2025 01:49:29 +0000 Subject: [PATCH 1/2] Fast fonts --- doc/ExternalResources.md | 15 ++- doc/buildAndProgram.md | 1 + src/CMakeLists.txt | 10 ++ src/displayapp/fonts/FastFont.cpp | 79 ++++++++++++++++ src/displayapp/fonts/FastFont.h | 9 ++ .../screens/WatchFaceCasioStyleG7710.cpp | 31 ++----- src/displayapp/screens/WatchFaceInfineat.cpp | 21 ++--- src/resources/CMakeLists.txt | 26 +++++- src/resources/fonts.json | 19 ++-- src/resources/generate-font-bin.py | 91 +++++++++++++++++++ .../{generate-fonts.py => generate-font-c.py} | 14 +-- 11 files changed, 251 insertions(+), 65 deletions(-) create mode 100644 src/displayapp/fonts/FastFont.cpp create mode 100644 src/displayapp/fonts/FastFont.h create mode 100644 src/resources/generate-font-bin.py rename src/resources/{generate-fonts.py => generate-font-c.py} (85%) diff --git a/doc/ExternalResources.md b/doc/ExternalResources.md index 8a67f9cf6c..6315fb599a 100644 --- a/doc/ExternalResources.md +++ b/doc/ExternalResources.md @@ -53,18 +53,17 @@ lv_obj_t* logo = lv_img_create(lv_scr_act(), nullptr); lv_img_set_src(logo, "F:/images/logo.bin"); ``` -Load a font from the external resources: you first need to check that the file actually exists. LVGL will crash when trying to open a font that doesn't exist. +Load a font from the external resources. + +You don't need to check if it exists in the constructor as LVGL can handle the font not existing gracefully (nothing will render). +However, do check that it exists in the `IsAvailable()` method so users can see when resources are missing (the watchface will not be selectable in the list if `IsAvailable()` returns `false`). +Remember to free any loaded fonts (with `free()`) in the screen destructor. ``` lv_font_t* font_teko = nullptr; -if (filesystem.FileOpen(&f, "/fonts/font.bin", LFS_O_RDONLY) >= 0) { - filesystem.FileClose(&f); - font_teko = lv_font_load("F:/fonts/font.bin"); -} +font_teko = Pinetime::Components::FastFont::LoadFont(filesystem, "/fastfonts/teko.bin"); -if(font != nullptr) { - lv_obj_set_style_local_text_font(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font); -} +lv_obj_set_style_local_text_font(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); ``` diff --git a/doc/buildAndProgram.md b/doc/buildAndProgram.md index 5d3af46ef0..9a1eca7e75 100644 --- a/doc/buildAndProgram.md +++ b/doc/buildAndProgram.md @@ -7,6 +7,7 @@ To build this project, you'll need: - A cross-compiler : [ARM-GCC (10.3-2021.10)](https://developer.arm.com/downloads/-/gnu-rm) - The NRF52 SDK 15.3.0 : [nRF-SDK v15.3.0](https://nsscprodmedia.blob.core.windows.net/prod/software-and-other-downloads/sdks/nrf5/binaries/nrf5sdk153059ac345.zip) - The Python 3 modules `cbor`, `intelhex`, `click` and `cryptography` modules for the `mcuboot` tool (see [requirements.txt](../tools/mcuboot/requirements.txt)) +- The Python 3 module `pyelftools` to build external resources - To keep the system clean, you can install python modules into a python virtual environment (`venv`) ```sh python -m venv .venv diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e4a354df64..fd6473a280 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -479,6 +479,7 @@ list(APPEND SOURCE_FILES FreeRTOS/port_cmsis.c displayapp/LittleVgl.cpp + displayapp/fonts/FastFont.cpp displayapp/InfiniTimeTheme.cpp systemtask/SystemTask.cpp @@ -666,6 +667,7 @@ set(INCLUDE_FILES FreeRTOS/portmacro.h FreeRTOS/portmacro_cmsis.h displayapp/LittleVgl.h + displayapp/fonts/FastFont.h displayapp/InfiniTimeTheme.h systemtask/SystemTask.h systemtask/SystemMonitor.h @@ -1142,5 +1144,13 @@ endif() if(BUILD_RESOURCES) add_subdirectory(resources) + target_compile_options(compiled_fonts PUBLIC + ${COMMON_FLAGS} + $<$: ${DEBUG_FLAGS}> + $<$: ${RELEASE_FLAGS}> + $<$: ${CXX_FLAGS}> + $<$: ${ASM_FLAGS}> + ) + set_property(TARGET compiled_fonts PROPERTY INTERPROCEDURAL_OPTIMIZATION FALSE) endif() diff --git a/src/displayapp/fonts/FastFont.cpp b/src/displayapp/fonts/FastFont.cpp new file mode 100644 index 0000000000..48ceec3643 --- /dev/null +++ b/src/displayapp/fonts/FastFont.cpp @@ -0,0 +1,79 @@ +#include "displayapp/fonts/FastFont.h" + +#include "components/fs/FS.h" + +#include + +using namespace Pinetime::Components; + +namespace { + template + void FixPointer(T** ptr, uintptr_t base) { + // extremely naughty generic const removal + using ConstFreeBase = std::remove_const_t; + using ConstFreePtr = std::add_pointer_t>; + auto ptrStripped = const_cast(ptr); + // reinterpret as a pointer to a pointer, which we can safely add to + auto* ptrRaw = reinterpret_cast(ptrStripped); + *ptrRaw += base; + } +} + +lv_font_t* FastFont::LoadFont(Pinetime::Controllers::FS& filesystem, const char* fontPath) { + int ret; + lfs_file_t file; + ret = filesystem.FileOpen(&file, fontPath, LFS_O_RDONLY); + if (ret < 0) { + return nullptr; + } + // Can use stat to get the size, but since the file is open we can grab it from there + lfs_size_t size = file.ctz.size; + void* fontData = malloc(size); + if (fontData == nullptr) { + filesystem.FileClose(&file); + return nullptr; + } + ret = filesystem.FileRead(&file, static_cast(fontData), size); + filesystem.FileClose(&file); + if (ret != static_cast(size)) { + free(fontData); + return nullptr; + } + auto* font = static_cast(fontData); + auto base = reinterpret_cast(fontData); + + // Fix LVGL fetch pointers + font->get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt; + font->get_glyph_bitmap = lv_font_get_bitmap_fmt_txt; + + // Fix internal pointers + FixPointer(&font->dsc, base); + auto* font_dsc = static_cast(font->dsc); + FixPointer(&font_dsc->glyph_bitmap, base); + FixPointer(&font_dsc->glyph_dsc, base); + FixPointer(&font_dsc->cmaps, base); + // cmaps are in RAM, so we can cast away const safely + auto* cmaps = const_cast(font_dsc->cmaps); + for (uint16_t i = 0; i < font_dsc->cmap_num; i++) { + if (cmaps[i].glyph_id_ofs_list != nullptr) { + FixPointer(&cmaps[i].glyph_id_ofs_list, base); + } + if (cmaps[i].unicode_list != nullptr) { + FixPointer(&cmaps[i].unicode_list, base); + } + } + if (font_dsc->kern_dsc != nullptr) { + if (font_dsc->kern_classes == 0) { + auto* kern_dsc = static_cast(const_cast(font_dsc->kern_dsc)); + FixPointer(&kern_dsc->glyph_ids, base); + FixPointer(&kern_dsc->values, base); + } else { + auto* kern_dsc = static_cast(const_cast(font_dsc->kern_dsc)); + FixPointer(&kern_dsc->class_pair_values, base); + FixPointer(&kern_dsc->left_class_mapping, base); + FixPointer(&kern_dsc->right_class_mapping, base); + } + } + + return font; +} diff --git a/src/displayapp/fonts/FastFont.h b/src/displayapp/fonts/FastFont.h new file mode 100644 index 0000000000..64a47b40d7 --- /dev/null +++ b/src/displayapp/fonts/FastFont.h @@ -0,0 +1,9 @@ +#include "displayapp/LittleVgl.h" + +namespace Pinetime { + namespace Components { + namespace FastFont { + lv_font_t* LoadFont(Pinetime::Controllers::FS& filesystem, const char* fontPath); + } + } +} diff --git a/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp b/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp index c695f852fe..ffb96a8ea4 100644 --- a/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp +++ b/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp @@ -12,6 +12,7 @@ #include "components/heartrate/HeartRateController.h" #include "components/motion/MotionController.h" #include "components/settings/Settings.h" +#include "displayapp/fonts/FastFont.h" using namespace Pinetime::Applications::Screens; WatchFaceCasioStyleG7710::WatchFaceCasioStyleG7710(Controllers::DateTime& dateTimeController, @@ -32,21 +33,9 @@ WatchFaceCasioStyleG7710::WatchFaceCasioStyleG7710(Controllers::DateTime& dateTi heartRateController {heartRateController}, motionController {motionController} { - lfs_file f = {}; - if (filesystem.FileOpen(&f, "/fonts/lv_font_dots_40.bin", LFS_O_RDONLY) >= 0) { - filesystem.FileClose(&f); - font_dot40 = lv_font_load("F:/fonts/lv_font_dots_40.bin"); - } - - if (filesystem.FileOpen(&f, "/fonts/7segments_40.bin", LFS_O_RDONLY) >= 0) { - filesystem.FileClose(&f); - font_segment40 = lv_font_load("F:/fonts/7segments_40.bin"); - } - - if (filesystem.FileOpen(&f, "/fonts/7segments_115.bin", LFS_O_RDONLY) >= 0) { - filesystem.FileClose(&f); - font_segment115 = lv_font_load("F:/fonts/7segments_115.bin"); - } + font_dot40 = Components::FastFont::LoadFont(filesystem, "/fastfonts/lv_font_dots_40.bin"); + font_segment40 = Components::FastFont::LoadFont(filesystem, "/fastfonts/seven_segments_40.bin"); + font_segment115 = Components::FastFont::LoadFont(filesystem, "/fastfonts/seven_segments_115.bin"); label_battery_value = lv_label_create(lv_scr_act(), nullptr); lv_obj_align(label_battery_value, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 0); @@ -179,15 +168,15 @@ WatchFaceCasioStyleG7710::~WatchFaceCasioStyleG7710() { lv_style_reset(&style_border); if (font_dot40 != nullptr) { - lv_font_free(font_dot40); + free(font_dot40); } if (font_segment40 != nullptr) { - lv_font_free(font_segment40); + free(font_segment40); } if (font_segment115 != nullptr) { - lv_font_free(font_segment115); + free(font_segment115); } lv_obj_clean(lv_scr_act()); @@ -315,17 +304,17 @@ void WatchFaceCasioStyleG7710::Refresh() { bool WatchFaceCasioStyleG7710::IsAvailable(Pinetime::Controllers::FS& filesystem) { lfs_file file = {}; - if (filesystem.FileOpen(&file, "/fonts/lv_font_dots_40.bin", LFS_O_RDONLY) < 0) { + if (filesystem.FileOpen(&file, "/fastfonts/lv_font_dots_40.bin", LFS_O_RDONLY) < 0) { return false; } filesystem.FileClose(&file); - if (filesystem.FileOpen(&file, "/fonts/7segments_40.bin", LFS_O_RDONLY) < 0) { + if (filesystem.FileOpen(&file, "/fastfonts/seven_segments_40.bin", LFS_O_RDONLY) < 0) { return false; } filesystem.FileClose(&file); - if (filesystem.FileOpen(&file, "/fonts/7segments_115.bin", LFS_O_RDONLY) < 0) { + if (filesystem.FileOpen(&file, "/fastfonts/seven_segments_115.bin", LFS_O_RDONLY) < 0) { return false; } diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index c793971103..c95565259f 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -9,6 +9,7 @@ #include "components/ble/BleController.h" #include "components/ble/NotificationManager.h" #include "components/motion/MotionController.h" +#include "displayapp/fonts/FastFont.h" using namespace Pinetime::Applications::Screens; @@ -133,16 +134,8 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, notificationManager {notificationManager}, settingsController {settingsController}, motionController {motionController} { - lfs_file f = {}; - if (filesystem.FileOpen(&f, "/fonts/teko.bin", LFS_O_RDONLY) >= 0) { - filesystem.FileClose(&f); - font_teko = lv_font_load("F:/fonts/teko.bin"); - } - - if (filesystem.FileOpen(&f, "/fonts/bebas.bin", LFS_O_RDONLY) >= 0) { - filesystem.FileClose(&f); - font_bebas = lv_font_load("F:/fonts/bebas.bin"); - } + font_teko = Components::FastFont::LoadFont(filesystem, "/fastfonts/teko.bin"); + font_bebas = Components::FastFont::LoadFont(filesystem, "/fastfonts/bebas.bin"); // Side Cover static constexpr lv_point_t linePoints[nLines][2] = {{{30, 25}, {68, -8}}, @@ -304,10 +297,10 @@ WatchFaceInfineat::~WatchFaceInfineat() { lv_task_del(taskRefresh); if (font_bebas != nullptr) { - lv_font_free(font_bebas); + free(font_bebas); } if (font_teko != nullptr) { - lv_font_free(font_teko); + free(font_teko); } lv_obj_clean(lv_scr_act()); @@ -493,12 +486,12 @@ void WatchFaceInfineat::ToggleBatteryIndicatorColor(bool showSideCover) { bool WatchFaceInfineat::IsAvailable(Pinetime::Controllers::FS& filesystem) { lfs_file file = {}; - if (filesystem.FileOpen(&file, "/fonts/teko.bin", LFS_O_RDONLY) < 0) { + if (filesystem.FileOpen(&file, "/fastfonts/teko.bin", LFS_O_RDONLY) < 0) { return false; } filesystem.FileClose(&file); - if (filesystem.FileOpen(&file, "/fonts/bebas.bin", LFS_O_RDONLY) < 0) { + if (filesystem.FileOpen(&file, "/fastfonts/bebas.bin", LFS_O_RDONLY) < 0) { return false; } diff --git a/src/resources/CMakeLists.txt b/src/resources/CMakeLists.txt index 9181d4a626..3629bf03a3 100644 --- a/src/resources/CMakeLists.txt +++ b/src/resources/CMakeLists.txt @@ -1,3 +1,5 @@ +set(FONTS teko bebas lv_font_dots_40 +seven_segments_40 seven_segments_115) find_program(LV_FONT_CONV "lv_font_conv" NO_CACHE REQUIRED HINTS "${CMAKE_SOURCE_DIR}/node_modules/.bin") @@ -16,11 +18,32 @@ else() set(Python3_EXECUTABLE "python") endif() +add_library(compiled_fonts OBJECT) +# add include directory to lvgl headers needed to compile the font files on its own +target_include_directories(compiled_fonts PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../../libs") +foreach(FONT ${FONTS}) + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${FONT}.c + COMMAND "${Python3_EXECUTABLE}" ${CMAKE_CURRENT_SOURCE_DIR}/generate-font-c.py + --lv-font-conv "${LV_FONT_CONV}" + --font ${FONT} ${CMAKE_CURRENT_SOURCE_DIR}/fonts.json + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/fonts.json + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + add_custom_target(compiled_fonts_${FONT} + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${FONT}.c + ) + target_sources(compiled_fonts PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/${FONT}.c") + add_dependencies(compiled_fonts compiled_fonts_${FONT}) +endforeach() + + # generate fonts add_custom_target(GenerateResources - COMMAND "${Python3_EXECUTABLE}" ${CMAKE_CURRENT_SOURCE_DIR}/generate-fonts.py --lv-font-conv "${LV_FONT_CONV}" ${CMAKE_CURRENT_SOURCE_DIR}/fonts.json COMMAND "${Python3_EXECUTABLE}" ${CMAKE_CURRENT_SOURCE_DIR}/generate-img.py --lv-img-conv "${LV_IMG_CONV}" ${CMAKE_CURRENT_SOURCE_DIR}/images.json + COMMAND "${Python3_EXECUTABLE}" ${CMAKE_CURRENT_SOURCE_DIR}/generate-font-bin.py $ COMMAND "${Python3_EXECUTABLE}" ${CMAKE_CURRENT_SOURCE_DIR}/generate-package.py --config ${CMAKE_CURRENT_SOURCE_DIR}/fonts.json --config ${CMAKE_CURRENT_SOURCE_DIR}/images.json --obsolete obsolete_files.json --output infinitime-resources-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip + COMMAND_EXPAND_LISTS DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/fonts.json DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/images.json WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} @@ -28,3 +51,4 @@ add_custom_target(GenerateResources COMMENT "Generate fonts and images for resource package" ) +add_dependencies(GenerateResources compiled_fonts) diff --git a/src/resources/fonts.json b/src/resources/fonts.json index c4a63349a1..a7b7f5a109 100644 --- a/src/resources/fonts.json +++ b/src/resources/fonts.json @@ -8,8 +8,7 @@ ], "bpp": 1, "size": 28, - "format": "bin", - "target_path": "/fonts/" + "target_path": "/fastfonts/" }, "bebas" : { "sources": [ @@ -20,8 +19,7 @@ ], "bpp": 1, "size": 120, - "format": "bin", - "target_path": "/fonts/" + "target_path": "/fastfonts/" }, "lv_font_dots_40": { "sources": [ @@ -32,10 +30,9 @@ ], "bpp": 1, "size": 40, - "format": "bin", - "target_path": "/fonts/" + "target_path": "/fastfonts/" }, - "7segments_40" : { + "seven_segments_40" : { "sources": [ { "file": "fonts/7segment.woff", @@ -44,10 +41,9 @@ ], "bpp": 1, "size": 40, - "format": "bin", - "target_path": "/fonts/" + "target_path": "/fastfonts/" }, - "7segments_115" : { + "seven_segments_115" : { "sources": [ { "file": "fonts/7segment.woff", @@ -56,7 +52,6 @@ ], "bpp": 1, "size": 115, - "format": "bin", - "target_path": "/fonts/" + "target_path": "/fastfonts/" } } diff --git a/src/resources/generate-font-bin.py b/src/resources/generate-font-bin.py new file mode 100644 index 0000000000..e0f0c7d6fe --- /dev/null +++ b/src/resources/generate-font-bin.py @@ -0,0 +1,91 @@ +import os +import sys +from typing import Dict, Iterable, List, NamedTuple + +from elftools.elf.elffile import ELFFile + +POINTER_SIZE = 4 + + +class Relocation(NamedTuple): + """Reference to another section""" + + # the section referenced + section_id: int + # the offset within the current section of the pointer pointing to the referenced section + offset: int + + +class Section(NamedTuple): + """Corresponds to a variable in the source""" + + # the section data + data: bytes + # relocations within the section + relocations: List[Relocation] + + +def load_data_section(section_id: int, elf_file: ELFFile) -> Section: + """Load a section (i.e a variable) and find the sections it references (relocations)""" + section = elf_file.get_section(section_id) + relocations: List[Relocation] = [] + relocation_section = elf_file.get_section_by_name(".rel" + section.name) + if relocation_section is not None: + symbol_table = elf_file.get_section_by_name(".symtab") + for reloc in relocation_section.iter_relocations(): + entry = symbol_table.get_symbol(reloc.entry.r_info_sym).entry + # if not a section, ignore + if entry.st_shndx == "SHN_UNDEF": + continue + assert ( + entry.st_shndx != section_id + ), "Relocation loop, is -fdata-sections set?" + relocations.append(Relocation(entry.st_shndx, reloc.entry.r_offset)) + return Section(section.data(), relocations) + + +def serialise_element( + section_id: int, file_data: bytearray, elf_file: ELFFile +) -> Dict[int, int]: + """Serialise a single section of the ELF (i.e one variable, as -fdata-sections used)""" + symbols = {} + data_section = load_data_section(section_id, elf_file) + # align section to POINTER_SIZE + if len(file_data) % POINTER_SIZE: + file_data.extend(bytes(POINTER_SIZE - len(file_data) % POINTER_SIZE)) + # current length is where this section will start + symbols[section_id] = len(file_data) + file_data.extend(data_section.data) + # serialise all children and store their locations + for field in data_section.relocations: + symbols.update(serialise_element(field.section_id, file_data, elf_file)) + # populate relative pointers to the child elements + for field in data_section.relocations: + file_offset = symbols[section_id] + file_data[ + file_offset + field.offset : file_offset + field.offset + POINTER_SIZE + ] = symbols[field.section_id].to_bytes(POINTER_SIZE, "little") + return symbols + + +def main(filenames: Iterable[str]) -> None: + """Entrypoint""" + for filename in filenames: + with open(filename, "rb") as elf_handle: + elf_file = ELFFile(elf_handle) + # get name of root font object from filename + obj_name = os.path.split(filename)[1].removesuffix(".c.o") + main_id = ( + elf_file.get_section_by_name(".symtab") + .get_symbol_by_name(obj_name)[0] + .entry.st_shndx + ) + + file_data = bytearray() + serialise_element(main_id, file_data, elf_file) + with open(f"{obj_name}.bin", "wb") as packed_font_handle: + packed_font_handle.write(file_data) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/src/resources/generate-fonts.py b/src/resources/generate-font-c.py similarity index 85% rename from src/resources/generate-fonts.py rename to src/resources/generate-font-c.py index 20408166d3..37c2d994cb 100755 --- a/src/resources/generate-fonts.py +++ b/src/resources/generate-font-c.py @@ -18,11 +18,8 @@ def __init__(self, d): self.symbols = d.get('symbols') -def gen_lvconv_line(lv_font_conv: str, dest: str, size: int, bpp: int, format: str, sources: typing.List[Source], compress:bool=False): - if format != "lvgl" and format != "bin": - format = "bin" if dest.lower().endswith(".bin") else "lvgl" - - args = [lv_font_conv, '--size', str(size), '--output', dest, '--bpp', str(bpp), '--format', format] +def gen_lvconv_line(lv_font_conv: str, dest: str, size: int, bpp: int, sources: typing.List[Source], compress:bool=False): + args = [lv_font_conv, '--size', str(size), '--output', dest, '--bpp', str(bpp), '--format', 'lvgl'] if not compress: args.append('--no-compress') for source in sources: @@ -64,15 +61,14 @@ def main(): for name in fonts_to_run: font = data[name] sources = font.pop('sources') + del font["target_path"] patches = font.pop('patches') if 'patches' in font else [] font['sources'] = [Source(thing) for thing in sources] - extension = 'c' if font['format'] != 'bin' else 'bin' - font.pop('target_path') - line = gen_lvconv_line(args.lv_font_conv, f'{name}.{extension}', **font) + line = gen_lvconv_line(args.lv_font_conv, f'{name}.c', **font) subprocess.check_call(line) if patches: for patch in patches: - subprocess.check_call(['/usr/bin/env', 'patch', name+'.'+extension, patch]) + subprocess.check_call(['/usr/bin/env', 'patch', '--silent', name+'.c', patch]) From d7bfb019ef955d7f32ef07c44f9d334ee0897e79 Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Tue, 16 Dec 2025 01:48:21 +0000 Subject: [PATCH 2/2] Smart pointer fonts --- doc/ExternalResources.md | 7 ++-- src/displayapp/fonts/FastFont.cpp | 17 ++++----- src/displayapp/fonts/FastFont.h | 15 +++++++- .../screens/WatchFaceCasioStyleG7710.cpp | 36 +++++-------------- .../screens/WatchFaceCasioStyleG7710.h | 7 ++-- src/displayapp/screens/WatchFaceInfineat.cpp | 31 +++++----------- src/displayapp/screens/WatchFaceInfineat.h | 6 ++-- 7 files changed, 51 insertions(+), 68 deletions(-) diff --git a/doc/ExternalResources.md b/doc/ExternalResources.md index 6315fb599a..c4c8e52851 100644 --- a/doc/ExternalResources.md +++ b/doc/ExternalResources.md @@ -57,13 +57,12 @@ Load a font from the external resources. You don't need to check if it exists in the constructor as LVGL can handle the font not existing gracefully (nothing will render). However, do check that it exists in the `IsAvailable()` method so users can see when resources are missing (the watchface will not be selectable in the list if `IsAvailable()` returns `false`). -Remember to free any loaded fonts (with `free()`) in the screen destructor. +The fonts will free themselves when the owning object is destructed. ``` -lv_font_t* font_teko = nullptr; -font_teko = Pinetime::Components::FastFont::LoadFont(filesystem, "/fastfonts/teko.bin"); +Components::FastFont::Font fontTeko = Components::FastFont::LoadFont(filesystem, "/fastfonts/teko.bin"); -lv_obj_set_style_local_text_font(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); +lv_obj_set_style_local_text_font(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, fontTeko.get()); ``` diff --git a/src/displayapp/fonts/FastFont.cpp b/src/displayapp/fonts/FastFont.cpp index 48ceec3643..6a7d2f81d9 100644 --- a/src/displayapp/fonts/FastFont.cpp +++ b/src/displayapp/fonts/FastFont.cpp @@ -2,6 +2,8 @@ #include "components/fs/FS.h" +#include +#include #include using namespace Pinetime::Components; @@ -19,7 +21,7 @@ namespace { } } -lv_font_t* FastFont::LoadFont(Pinetime::Controllers::FS& filesystem, const char* fontPath) { +FastFont::Font FastFont::LoadFont(Pinetime::Controllers::FS& filesystem, const char* fontPath) { int ret; lfs_file_t file; ret = filesystem.FileOpen(&file, fontPath, LFS_O_RDONLY); @@ -28,19 +30,18 @@ lv_font_t* FastFont::LoadFont(Pinetime::Controllers::FS& filesystem, const char* } // Can use stat to get the size, but since the file is open we can grab it from there lfs_size_t size = file.ctz.size; - void* fontData = malloc(size); - if (fontData == nullptr) { + auto fontData = std::make_unique_for_overwrite(size); + if (fontData.get() == nullptr) { filesystem.FileClose(&file); return nullptr; } - ret = filesystem.FileRead(&file, static_cast(fontData), size); + ret = filesystem.FileRead(&file, fontData.get(), size); filesystem.FileClose(&file); if (ret != static_cast(size)) { - free(fontData); return nullptr; } - auto* font = static_cast(fontData); - auto base = reinterpret_cast(fontData); + auto* font = reinterpret_cast(fontData.get()); + auto base = reinterpret_cast(fontData.get()); // Fix LVGL fetch pointers font->get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt; @@ -75,5 +76,5 @@ lv_font_t* FastFont::LoadFont(Pinetime::Controllers::FS& filesystem, const char* } } - return font; + return FastFont::Font(reinterpret_cast(fontData.release())); } diff --git a/src/displayapp/fonts/FastFont.h b/src/displayapp/fonts/FastFont.h index 64a47b40d7..69e4b75bd8 100644 --- a/src/displayapp/fonts/FastFont.h +++ b/src/displayapp/fonts/FastFont.h @@ -1,9 +1,22 @@ +#pragma once + #include "displayapp/LittleVgl.h" +#include namespace Pinetime { namespace Components { namespace FastFont { - lv_font_t* LoadFont(Pinetime::Controllers::FS& filesystem, const char* fontPath); + // Since the font pointer is actually a u8 array containing all loaded font data + // we need to use the deleter for the true datatype rather than the single struct + struct CastingDeleter { + void operator()(lv_font_t* fontData) const { + auto* fontDataReal = reinterpret_cast(fontData); + std::default_delete {}(fontDataReal); + } + }; + + using Font = std::unique_ptr; + Font LoadFont(Pinetime::Controllers::FS& filesystem, const char* fontPath); } } } diff --git a/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp b/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp index ffb96a8ea4..0ecb4b92fd 100644 --- a/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp +++ b/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp @@ -12,7 +12,6 @@ #include "components/heartrate/HeartRateController.h" #include "components/motion/MotionController.h" #include "components/settings/Settings.h" -#include "displayapp/fonts/FastFont.h" using namespace Pinetime::Applications::Screens; WatchFaceCasioStyleG7710::WatchFaceCasioStyleG7710(Controllers::DateTime& dateTimeController, @@ -64,19 +63,19 @@ WatchFaceCasioStyleG7710::WatchFaceCasioStyleG7710(Controllers::DateTime& dateTi label_day_of_week = lv_label_create(lv_scr_act(), nullptr); lv_obj_align(label_day_of_week, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 10, 64); lv_obj_set_style_local_text_color(label_day_of_week, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); - lv_obj_set_style_local_text_font(label_day_of_week, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_dot40); + lv_obj_set_style_local_text_font(label_day_of_week, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_dot40.get()); lv_label_set_text_static(label_day_of_week, "SUN"); label_week_number = lv_label_create(lv_scr_act(), nullptr); lv_obj_align(label_week_number, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 5, 22); lv_obj_set_style_local_text_color(label_week_number, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); - lv_obj_set_style_local_text_font(label_week_number, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_dot40); + lv_obj_set_style_local_text_font(label_week_number, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_dot40.get()); lv_label_set_text_static(label_week_number, "WK26"); label_day_of_year = lv_label_create(lv_scr_act(), nullptr); lv_obj_align(label_day_of_year, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 100, 30); lv_obj_set_style_local_text_color(label_day_of_year, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); - lv_obj_set_style_local_text_font(label_day_of_year, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_segment40); + lv_obj_set_style_local_text_font(label_day_of_year, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_segment40.get()); lv_label_set_text_static(label_day_of_year, "181-184"); lv_style_init(&style_line); @@ -107,7 +106,7 @@ WatchFaceCasioStyleG7710::WatchFaceCasioStyleG7710(Controllers::DateTime& dateTi label_date = lv_label_create(lv_scr_act(), nullptr); lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 100, 70); lv_obj_set_style_local_text_color(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); - lv_obj_set_style_local_text_font(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_segment40); + lv_obj_set_style_local_text_font(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_segment40.get()); lv_label_set_text_static(label_date, "6-30"); line_date = lv_line_create(lv_scr_act(), nullptr); @@ -117,7 +116,7 @@ WatchFaceCasioStyleG7710::WatchFaceCasioStyleG7710(Controllers::DateTime& dateTi label_time = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); - lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_segment115); + lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_segment115.get()); lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 40); line_time = lv_line_create(lv_scr_act(), nullptr); @@ -167,18 +166,6 @@ WatchFaceCasioStyleG7710::~WatchFaceCasioStyleG7710() { lv_style_reset(&style_line); lv_style_reset(&style_border); - if (font_dot40 != nullptr) { - free(font_dot40); - } - - if (font_segment40 != nullptr) { - free(font_segment40); - } - - if (font_segment115 != nullptr) { - free(font_segment115); - } - lv_obj_clean(lv_scr_act()); } @@ -302,22 +289,17 @@ void WatchFaceCasioStyleG7710::Refresh() { } bool WatchFaceCasioStyleG7710::IsAvailable(Pinetime::Controllers::FS& filesystem) { - lfs_file file = {}; + lfs_info stat {}; - if (filesystem.FileOpen(&file, "/fastfonts/lv_font_dots_40.bin", LFS_O_RDONLY) < 0) { + if (filesystem.Stat("/fastfonts/lv_font_dots_40.bin", &stat) != LFS_ERR_OK) { return false; } - - filesystem.FileClose(&file); - if (filesystem.FileOpen(&file, "/fastfonts/seven_segments_40.bin", LFS_O_RDONLY) < 0) { + if (filesystem.Stat("/fastfonts/seven_segments_40.bin", &stat) != LFS_ERR_OK) { return false; } - - filesystem.FileClose(&file); - if (filesystem.FileOpen(&file, "/fastfonts/seven_segments_115.bin", LFS_O_RDONLY) < 0) { + if (filesystem.Stat("/fastfonts/seven_segments_115.bin", &stat) != LFS_ERR_OK) { return false; } - filesystem.FileClose(&file); return true; } diff --git a/src/displayapp/screens/WatchFaceCasioStyleG7710.h b/src/displayapp/screens/WatchFaceCasioStyleG7710.h index 0f46a69251..fa625e72af 100644 --- a/src/displayapp/screens/WatchFaceCasioStyleG7710.h +++ b/src/displayapp/screens/WatchFaceCasioStyleG7710.h @@ -11,6 +11,7 @@ #include "components/ble/BleController.h" #include "utility/DirtyValue.h" #include "displayapp/apps/Apps.h" +#include "displayapp/fonts/FastFont.h" namespace Pinetime { namespace Controllers { @@ -96,9 +97,9 @@ namespace Pinetime { Controllers::MotionController& motionController; lv_task_t* taskRefresh; - lv_font_t* font_dot40 = nullptr; - lv_font_t* font_segment40 = nullptr; - lv_font_t* font_segment115 = nullptr; + Components::FastFont::Font font_dot40; + Components::FastFont::Font font_segment40; + Components::FastFont::Font font_segment115; }; } diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index c95565259f..557b7ab978 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -9,7 +9,6 @@ #include "components/ble/BleController.h" #include "components/ble/NotificationManager.h" #include "components/motion/MotionController.h" -#include "displayapp/fonts/FastFont.h" using namespace Pinetime::Applications::Screens; @@ -192,16 +191,16 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, labelHour = lv_label_create(lv_scr_act(), nullptr); lv_label_set_text_static(labelHour, "01"); - lv_obj_set_style_local_text_font(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas); + lv_obj_set_style_local_text_font(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas.get()); lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 0); labelMinutes = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_font(labelMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas); + lv_obj_set_style_local_text_font(labelMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas.get()); lv_label_set_text_static(labelMinutes, "00"); lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); labelTimeAmPm = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_font(labelTimeAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_set_style_local_text_font(labelTimeAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko.get()); lv_label_set_text_static(labelTimeAmPm, ""); lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 15); @@ -214,7 +213,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, static constexpr lv_color_t grayColor = LV_COLOR_MAKE(0x99, 0x99, 0x99); labelDate = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); - lv_obj_set_style_local_text_font(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_set_style_local_text_font(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko.get()); lv_obj_align(labelDate, dateContainer, LV_ALIGN_IN_TOP_MID, 0, 0); lv_label_set_text_static(labelDate, "Mon 01"); @@ -225,7 +224,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, stepValue = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); - lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko.get()); lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 10, 0); lv_label_set_text_static(stepValue, "0"); @@ -296,13 +295,6 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, WatchFaceInfineat::~WatchFaceInfineat() { lv_task_del(taskRefresh); - if (font_bebas != nullptr) { - free(font_bebas); - } - if (font_teko != nullptr) { - free(font_teko); - } - lv_obj_clean(lv_scr_act()); } @@ -484,22 +476,17 @@ void WatchFaceInfineat::ToggleBatteryIndicatorColor(bool showSideCover) { } bool WatchFaceInfineat::IsAvailable(Pinetime::Controllers::FS& filesystem) { - lfs_file file = {}; + lfs_info stat {}; - if (filesystem.FileOpen(&file, "/fastfonts/teko.bin", LFS_O_RDONLY) < 0) { + if (filesystem.Stat("/fastfonts/teko.bin", &stat) != LFS_ERR_OK) { return false; } - - filesystem.FileClose(&file); - if (filesystem.FileOpen(&file, "/fastfonts/bebas.bin", LFS_O_RDONLY) < 0) { + if (filesystem.Stat("/fastfonts/bebas.bin", &stat) != LFS_ERR_OK) { return false; } - - filesystem.FileClose(&file); - if (filesystem.FileOpen(&file, "/images/pine_small.bin", LFS_O_RDONLY) < 0) { + if (filesystem.Stat("/images/pine_small.bin", &stat) != LFS_ERR_OK) { return false; } - filesystem.FileClose(&file); return true; } diff --git a/src/displayapp/screens/WatchFaceInfineat.h b/src/displayapp/screens/WatchFaceInfineat.h index f646ebac58..792c2abe1d 100644 --- a/src/displayapp/screens/WatchFaceInfineat.h +++ b/src/displayapp/screens/WatchFaceInfineat.h @@ -3,12 +3,12 @@ #include #include #include -#include #include #include "displayapp/screens/Screen.h" #include "components/datetime/DateTimeController.h" #include "utility/DirtyValue.h" #include "displayapp/apps/Apps.h" +#include "displayapp/fonts/FastFont.h" namespace Pinetime { namespace Controllers { @@ -96,8 +96,8 @@ namespace Pinetime { void ToggleBatteryIndicatorColor(bool showSideCover); lv_task_t* taskRefresh; - lv_font_t* font_teko = nullptr; - lv_font_t* font_bebas = nullptr; + Components::FastFont::Font font_teko; + Components::FastFont::Font font_bebas; }; }