diff --git a/doc/ExternalResources.md b/doc/ExternalResources.md index 8a67f9cf6c..c4c8e52851 100644 --- a/doc/ExternalResources.md +++ b/doc/ExternalResources.md @@ -53,18 +53,16 @@ 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`). +The fonts will free themselves when the owning object is destructed. ``` -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"); -} +Components::FastFont::Font fontTeko = 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, fontTeko.get()); ``` 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..6a7d2f81d9 --- /dev/null +++ b/src/displayapp/fonts/FastFont.cpp @@ -0,0 +1,80 @@ +#include "displayapp/fonts/FastFont.h" + +#include "components/fs/FS.h" + +#include +#include +#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; + } +} + +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); + 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; + auto fontData = std::make_unique_for_overwrite(size); + if (fontData.get() == nullptr) { + filesystem.FileClose(&file); + return nullptr; + } + ret = filesystem.FileRead(&file, fontData.get(), size); + filesystem.FileClose(&file); + if (ret != static_cast(size)) { + return nullptr; + } + 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; + 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 FastFont::Font(reinterpret_cast(fontData.release())); +} diff --git a/src/displayapp/fonts/FastFont.h b/src/displayapp/fonts/FastFont.h new file mode 100644 index 0000000000..69e4b75bd8 --- /dev/null +++ b/src/displayapp/fonts/FastFont.h @@ -0,0 +1,22 @@ +#pragma once + +#include "displayapp/LittleVgl.h" +#include + +namespace Pinetime { + namespace Components { + namespace FastFont { + // 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 c695f852fe..0ecb4b92fd 100644 --- a/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp +++ b/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp @@ -32,21 +32,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); @@ -75,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); @@ -118,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); @@ -128,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); @@ -178,18 +166,6 @@ WatchFaceCasioStyleG7710::~WatchFaceCasioStyleG7710() { lv_style_reset(&style_line); lv_style_reset(&style_border); - if (font_dot40 != nullptr) { - lv_font_free(font_dot40); - } - - if (font_segment40 != nullptr) { - lv_font_free(font_segment40); - } - - if (font_segment115 != nullptr) { - lv_font_free(font_segment115); - } - lv_obj_clean(lv_scr_act()); } @@ -313,22 +289,17 @@ void WatchFaceCasioStyleG7710::Refresh() { } bool WatchFaceCasioStyleG7710::IsAvailable(Pinetime::Controllers::FS& filesystem) { - lfs_file file = {}; + lfs_info stat {}; - if (filesystem.FileOpen(&file, "/fonts/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, "/fonts/7segments_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, "/fonts/7segments_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 c793971103..557b7ab978 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -133,16 +133,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}}, @@ -199,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); @@ -221,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"); @@ -232,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"); @@ -303,13 +295,6 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, WatchFaceInfineat::~WatchFaceInfineat() { lv_task_del(taskRefresh); - if (font_bebas != nullptr) { - lv_font_free(font_bebas); - } - if (font_teko != nullptr) { - lv_font_free(font_teko); - } - lv_obj_clean(lv_scr_act()); } @@ -491,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, "/fonts/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, "/fonts/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; }; } 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])