Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions doc/ExternalResources.md
Original file line number Diff line number Diff line change
Expand Up @@ -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());

```

1 change: 1 addition & 0 deletions doc/buildAndProgram.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1142,5 +1144,13 @@ endif()

if(BUILD_RESOURCES)
add_subdirectory(resources)
target_compile_options(compiled_fonts PUBLIC
${COMMON_FLAGS}
$<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
$<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}>
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
)
set_property(TARGET compiled_fonts PROPERTY INTERPROCEDURAL_OPTIMIZATION FALSE)
endif()

80 changes: 80 additions & 0 deletions src/displayapp/fonts/FastFont.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#include "displayapp/fonts/FastFont.h"

#include "components/fs/FS.h"

#include <cstdint>
#include <memory>
#include <type_traits>

using namespace Pinetime::Components;

namespace {
template <typename T>
void FixPointer(T** ptr, uintptr_t base) {
// extremely naughty generic const removal
using ConstFreeBase = std::remove_const_t<T>;
using ConstFreePtr = std::add_pointer_t<std::add_pointer_t<ConstFreeBase>>;
auto ptrStripped = const_cast<ConstFreePtr>(ptr);
// reinterpret as a pointer to a pointer, which we can safely add to
auto* ptrRaw = reinterpret_cast<uintptr_t*>(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<uint8_t[]>(size);
if (fontData.get() == nullptr) {
filesystem.FileClose(&file);
return nullptr;
}
ret = filesystem.FileRead(&file, fontData.get(), size);
filesystem.FileClose(&file);
if (ret != static_cast<int>(size)) {
return nullptr;
}
auto* font = reinterpret_cast<lv_font_t*>(fontData.get());
auto base = reinterpret_cast<uintptr_t>(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<lv_font_fmt_txt_dsc_t*>(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<lv_font_fmt_txt_cmap_t*>(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<lv_font_fmt_txt_kern_pair_t*>(const_cast<void*>(font_dsc->kern_dsc));
FixPointer(&kern_dsc->glyph_ids, base);
FixPointer(&kern_dsc->values, base);
} else {
auto* kern_dsc = static_cast<lv_font_fmt_txt_kern_classes_t*>(const_cast<void*>(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<lv_font_t*>(fontData.release()));
}
22 changes: 22 additions & 0 deletions src/displayapp/fonts/FastFont.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

#include "displayapp/LittleVgl.h"
#include <memory>

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<uint8_t*>(fontData);
std::default_delete<uint8_t[]> {}(fontDataReal);
}
};

using Font = std::unique_ptr<lv_font_t, CastingDeleter>;
Font LoadFont(Pinetime::Controllers::FS& filesystem, const char* fontPath);
}
}
}
53 changes: 12 additions & 41 deletions src/displayapp/screens/WatchFaceCasioStyleG7710.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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());
}

Expand Down Expand Up @@ -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;
}
7 changes: 4 additions & 3 deletions src/displayapp/screens/WatchFaceCasioStyleG7710.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
};
}

Expand Down
42 changes: 11 additions & 31 deletions src/displayapp/screens/WatchFaceInfineat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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}},
Expand Down Expand Up @@ -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);
Expand All @@ -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");

Expand All @@ -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");

Expand Down Expand Up @@ -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());
}

Expand Down Expand Up @@ -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;
}
Loading
Loading