Skip to content

Commit 25d854b

Browse files
committed
Fast fonts
1 parent 66b5977 commit 25d854b

11 files changed

Lines changed: 251 additions & 65 deletions

doc/ExternalResources.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,17 @@ lv_obj_t* logo = lv_img_create(lv_scr_act(), nullptr);
5353
lv_img_set_src(logo, "F:/images/logo.bin");
5454
```
5555

56-
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.
56+
Load a font from the external resources.
57+
58+
You don't need to check if it exists in the constructor as LVGL can handle the font not existing gracefully (nothing will render).
59+
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`).
60+
Remember to free any loaded fonts (with `free()`) in the screen destructor.
5761

5862
```
5963
lv_font_t* font_teko = nullptr;
60-
if (filesystem.FileOpen(&f, "/fonts/font.bin", LFS_O_RDONLY) >= 0) {
61-
filesystem.FileClose(&f);
62-
font_teko = lv_font_load("F:/fonts/font.bin");
63-
}
64+
font_teko = Pinetime::Components::FastFont::LoadFont(filesystem, "/fastfonts/teko.bin");
6465
65-
if(font != nullptr) {
66-
lv_obj_set_style_local_text_font(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font);
67-
}
66+
lv_obj_set_style_local_text_font(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko);
6867
6968
```
7069

doc/buildAndProgram.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ To build this project, you'll need:
77
- A cross-compiler : [ARM-GCC (10.3-2021.10)](https://developer.arm.com/downloads/-/gnu-rm)
88
- 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)
99
- The Python 3 modules `cbor`, `intelhex`, `click` and `cryptography` modules for the `mcuboot` tool (see [requirements.txt](../tools/mcuboot/requirements.txt))
10+
- The Python 3 module `pyelftools` to build external resources
1011
- To keep the system clean, you can install python modules into a python virtual environment (`venv`)
1112
```sh
1213
python -m venv .venv

src/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ list(APPEND SOURCE_FILES
479479
FreeRTOS/port_cmsis.c
480480

481481
displayapp/LittleVgl.cpp
482+
displayapp/fonts/FastFont.cpp
482483
displayapp/InfiniTimeTheme.cpp
483484

484485
systemtask/SystemTask.cpp
@@ -666,6 +667,7 @@ set(INCLUDE_FILES
666667
FreeRTOS/portmacro.h
667668
FreeRTOS/portmacro_cmsis.h
668669
displayapp/LittleVgl.h
670+
displayapp/fonts/FastFont.h
669671
displayapp/InfiniTimeTheme.h
670672
systemtask/SystemTask.h
671673
systemtask/SystemMonitor.h
@@ -1142,5 +1144,13 @@ endif()
11421144

11431145
if(BUILD_RESOURCES)
11441146
add_subdirectory(resources)
1147+
target_compile_options(compiled_fonts PUBLIC
1148+
${COMMON_FLAGS}
1149+
$<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
1150+
$<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
1151+
$<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}>
1152+
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
1153+
)
1154+
set_property(TARGET compiled_fonts PROPERTY INTERPROCEDURAL_OPTIMIZATION FALSE)
11451155
endif()
11461156

src/displayapp/fonts/FastFont.cpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#include "displayapp/fonts/FastFont.h"
2+
3+
#include "components/fs/FS.h"
4+
5+
#include <type_traits>
6+
7+
using namespace Pinetime::Components;
8+
9+
namespace {
10+
template <typename T>
11+
void FixPointer(T** ptr, uintptr_t base) {
12+
// extremely naughty generic const removal
13+
using ConstFreeBase = std::remove_const_t<T>;
14+
using ConstFreePtr = std::add_pointer_t<std::add_pointer_t<ConstFreeBase>>;
15+
auto ptrStripped = const_cast<ConstFreePtr>(ptr);
16+
// reinterpret as a pointer to a pointer, which we can safely add to
17+
auto* ptrRaw = reinterpret_cast<uintptr_t*>(ptrStripped);
18+
*ptrRaw += base;
19+
}
20+
}
21+
22+
lv_font_t* FastFont::LoadFont(Pinetime::Controllers::FS& filesystem, const char* fontPath) {
23+
int ret;
24+
lfs_file_t file;
25+
ret = filesystem.FileOpen(&file, fontPath, LFS_O_RDONLY);
26+
if (ret < 0) {
27+
return nullptr;
28+
}
29+
// Can use stat to get the size, but since the file is open we can grab it from there
30+
lfs_size_t size = file.ctz.size;
31+
void* fontData = malloc(size);
32+
if (fontData == nullptr) {
33+
filesystem.FileClose(&file);
34+
return nullptr;
35+
}
36+
ret = filesystem.FileRead(&file, static_cast<uint8_t*>(fontData), size);
37+
filesystem.FileClose(&file);
38+
if (ret != static_cast<int>(size)) {
39+
free(fontData);
40+
return nullptr;
41+
}
42+
auto* font = static_cast<lv_font_t*>(fontData);
43+
auto base = reinterpret_cast<uintptr_t>(fontData);
44+
45+
// Fix LVGL fetch pointers
46+
font->get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt;
47+
font->get_glyph_bitmap = lv_font_get_bitmap_fmt_txt;
48+
49+
// Fix internal pointers
50+
FixPointer(&font->dsc, base);
51+
auto* font_dsc = static_cast<lv_font_fmt_txt_dsc_t*>(font->dsc);
52+
FixPointer(&font_dsc->glyph_bitmap, base);
53+
FixPointer(&font_dsc->glyph_dsc, base);
54+
FixPointer(&font_dsc->cmaps, base);
55+
// cmaps are in RAM, so we can cast away const safely
56+
auto* cmaps = const_cast<lv_font_fmt_txt_cmap_t*>(font_dsc->cmaps);
57+
for (uint16_t i = 0; i < font_dsc->cmap_num; i++) {
58+
if (cmaps[i].glyph_id_ofs_list != nullptr) {
59+
FixPointer(&cmaps[i].glyph_id_ofs_list, base);
60+
}
61+
if (cmaps[i].unicode_list != nullptr) {
62+
FixPointer(&cmaps[i].unicode_list, base);
63+
}
64+
}
65+
if (font_dsc->kern_dsc != nullptr) {
66+
if (font_dsc->kern_classes == 0) {
67+
auto* kern_dsc = static_cast<lv_font_fmt_txt_kern_pair_t*>(const_cast<void*>(font_dsc->kern_dsc));
68+
FixPointer(&kern_dsc->glyph_ids, base);
69+
FixPointer(&kern_dsc->values, base);
70+
} else {
71+
auto* kern_dsc = static_cast<lv_font_fmt_txt_kern_classes_t*>(const_cast<void*>(font_dsc->kern_dsc));
72+
FixPointer(&kern_dsc->class_pair_values, base);
73+
FixPointer(&kern_dsc->left_class_mapping, base);
74+
FixPointer(&kern_dsc->right_class_mapping, base);
75+
}
76+
}
77+
78+
return font;
79+
}

src/displayapp/fonts/FastFont.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#include "displayapp/LittleVgl.h"
2+
3+
namespace Pinetime {
4+
namespace Components {
5+
namespace FastFont {
6+
lv_font_t* LoadFont(Pinetime::Controllers::FS& filesystem, const char* fontPath);
7+
}
8+
}
9+
}

src/displayapp/screens/WatchFaceCasioStyleG7710.cpp

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "components/heartrate/HeartRateController.h"
1313
#include "components/motion/MotionController.h"
1414
#include "components/settings/Settings.h"
15+
#include "displayapp/fonts/FastFont.h"
1516
using namespace Pinetime::Applications::Screens;
1617

1718
WatchFaceCasioStyleG7710::WatchFaceCasioStyleG7710(Controllers::DateTime& dateTimeController,
@@ -32,21 +33,9 @@ WatchFaceCasioStyleG7710::WatchFaceCasioStyleG7710(Controllers::DateTime& dateTi
3233
heartRateController {heartRateController},
3334
motionController {motionController} {
3435

35-
lfs_file f = {};
36-
if (filesystem.FileOpen(&f, "/fonts/lv_font_dots_40.bin", LFS_O_RDONLY) >= 0) {
37-
filesystem.FileClose(&f);
38-
font_dot40 = lv_font_load("F:/fonts/lv_font_dots_40.bin");
39-
}
40-
41-
if (filesystem.FileOpen(&f, "/fonts/7segments_40.bin", LFS_O_RDONLY) >= 0) {
42-
filesystem.FileClose(&f);
43-
font_segment40 = lv_font_load("F:/fonts/7segments_40.bin");
44-
}
45-
46-
if (filesystem.FileOpen(&f, "/fonts/7segments_115.bin", LFS_O_RDONLY) >= 0) {
47-
filesystem.FileClose(&f);
48-
font_segment115 = lv_font_load("F:/fonts/7segments_115.bin");
49-
}
36+
font_dot40 = Components::FastFont::LoadFont(filesystem, "/fastfonts/lv_font_dots_40.bin");
37+
font_segment40 = Components::FastFont::LoadFont(filesystem, "/fastfonts/seven_segments_40.bin");
38+
font_segment115 = Components::FastFont::LoadFont(filesystem, "/fastfonts/seven_segments_115.bin");
5039

5140
label_battery_value = lv_label_create(lv_scr_act(), nullptr);
5241
lv_obj_align(label_battery_value, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 0);
@@ -179,15 +168,15 @@ WatchFaceCasioStyleG7710::~WatchFaceCasioStyleG7710() {
179168
lv_style_reset(&style_border);
180169

181170
if (font_dot40 != nullptr) {
182-
lv_font_free(font_dot40);
171+
free(font_dot40);
183172
}
184173

185174
if (font_segment40 != nullptr) {
186-
lv_font_free(font_segment40);
175+
free(font_segment40);
187176
}
188177

189178
if (font_segment115 != nullptr) {
190-
lv_font_free(font_segment115);
179+
free(font_segment115);
191180
}
192181

193182
lv_obj_clean(lv_scr_act());
@@ -315,17 +304,17 @@ void WatchFaceCasioStyleG7710::Refresh() {
315304
bool WatchFaceCasioStyleG7710::IsAvailable(Pinetime::Controllers::FS& filesystem) {
316305
lfs_file file = {};
317306

318-
if (filesystem.FileOpen(&file, "/fonts/lv_font_dots_40.bin", LFS_O_RDONLY) < 0) {
307+
if (filesystem.FileOpen(&file, "/fastfonts/lv_font_dots_40.bin", LFS_O_RDONLY) < 0) {
319308
return false;
320309
}
321310

322311
filesystem.FileClose(&file);
323-
if (filesystem.FileOpen(&file, "/fonts/7segments_40.bin", LFS_O_RDONLY) < 0) {
312+
if (filesystem.FileOpen(&file, "/fastfonts/seven_segments_40.bin", LFS_O_RDONLY) < 0) {
324313
return false;
325314
}
326315

327316
filesystem.FileClose(&file);
328-
if (filesystem.FileOpen(&file, "/fonts/7segments_115.bin", LFS_O_RDONLY) < 0) {
317+
if (filesystem.FileOpen(&file, "/fastfonts/seven_segments_115.bin", LFS_O_RDONLY) < 0) {
329318
return false;
330319
}
331320

src/displayapp/screens/WatchFaceInfineat.cpp

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "components/ble/BleController.h"
1010
#include "components/ble/NotificationManager.h"
1111
#include "components/motion/MotionController.h"
12+
#include "displayapp/fonts/FastFont.h"
1213

1314
using namespace Pinetime::Applications::Screens;
1415

@@ -133,16 +134,8 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController,
133134
notificationManager {notificationManager},
134135
settingsController {settingsController},
135136
motionController {motionController} {
136-
lfs_file f = {};
137-
if (filesystem.FileOpen(&f, "/fonts/teko.bin", LFS_O_RDONLY) >= 0) {
138-
filesystem.FileClose(&f);
139-
font_teko = lv_font_load("F:/fonts/teko.bin");
140-
}
141-
142-
if (filesystem.FileOpen(&f, "/fonts/bebas.bin", LFS_O_RDONLY) >= 0) {
143-
filesystem.FileClose(&f);
144-
font_bebas = lv_font_load("F:/fonts/bebas.bin");
145-
}
137+
font_teko = Components::FastFont::LoadFont(filesystem, "/fastfonts/teko.bin");
138+
font_bebas = Components::FastFont::LoadFont(filesystem, "/fastfonts/bebas.bin");
146139

147140
// Side Cover
148141
static constexpr lv_point_t linePoints[nLines][2] = {{{30, 25}, {68, -8}},
@@ -304,10 +297,10 @@ WatchFaceInfineat::~WatchFaceInfineat() {
304297
lv_task_del(taskRefresh);
305298

306299
if (font_bebas != nullptr) {
307-
lv_font_free(font_bebas);
300+
free(font_bebas);
308301
}
309302
if (font_teko != nullptr) {
310-
lv_font_free(font_teko);
303+
free(font_teko);
311304
}
312305

313306
lv_obj_clean(lv_scr_act());
@@ -493,12 +486,12 @@ void WatchFaceInfineat::ToggleBatteryIndicatorColor(bool showSideCover) {
493486
bool WatchFaceInfineat::IsAvailable(Pinetime::Controllers::FS& filesystem) {
494487
lfs_file file = {};
495488

496-
if (filesystem.FileOpen(&file, "/fonts/teko.bin", LFS_O_RDONLY) < 0) {
489+
if (filesystem.FileOpen(&file, "/fastfonts/teko.bin", LFS_O_RDONLY) < 0) {
497490
return false;
498491
}
499492

500493
filesystem.FileClose(&file);
501-
if (filesystem.FileOpen(&file, "/fonts/bebas.bin", LFS_O_RDONLY) < 0) {
494+
if (filesystem.FileOpen(&file, "/fastfonts/bebas.bin", LFS_O_RDONLY) < 0) {
502495
return false;
503496
}
504497

src/resources/CMakeLists.txt

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
set(FONTS teko bebas lv_font_dots_40
2+
seven_segments_40 seven_segments_115)
13

24
find_program(LV_FONT_CONV "lv_font_conv" NO_CACHE REQUIRED
35
HINTS "${CMAKE_SOURCE_DIR}/node_modules/.bin")
@@ -16,15 +18,37 @@ else()
1618
set(Python3_EXECUTABLE "python")
1719
endif()
1820

21+
add_library(compiled_fonts OBJECT)
22+
# add include directory to lvgl headers needed to compile the font files on its own
23+
target_include_directories(compiled_fonts PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../../libs")
24+
foreach(FONT ${FONTS})
25+
add_custom_command(
26+
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${FONT}.c
27+
COMMAND "${Python3_EXECUTABLE}" ${CMAKE_CURRENT_SOURCE_DIR}/generate-font-c.py
28+
--lv-font-conv "${LV_FONT_CONV}"
29+
--font ${FONT} ${CMAKE_CURRENT_SOURCE_DIR}/fonts.json
30+
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/fonts.json
31+
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
32+
)
33+
add_custom_target(compiled_fonts_${FONT}
34+
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${FONT}.c
35+
)
36+
target_sources(compiled_fonts PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/${FONT}.c")
37+
add_dependencies(compiled_fonts compiled_fonts_${FONT})
38+
endforeach()
39+
40+
1941
# generate fonts
2042
add_custom_target(GenerateResources
21-
COMMAND "${Python3_EXECUTABLE}" ${CMAKE_CURRENT_SOURCE_DIR}/generate-fonts.py --lv-font-conv "${LV_FONT_CONV}" ${CMAKE_CURRENT_SOURCE_DIR}/fonts.json
2243
COMMAND "${Python3_EXECUTABLE}" ${CMAKE_CURRENT_SOURCE_DIR}/generate-img.py --lv-img-conv "${LV_IMG_CONV}" ${CMAKE_CURRENT_SOURCE_DIR}/images.json
44+
COMMAND "${Python3_EXECUTABLE}" ${CMAKE_CURRENT_SOURCE_DIR}/generate-font-bin.py $<TARGET_OBJECTS:compiled_fonts>
2345
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
46+
COMMAND_EXPAND_LISTS
2447
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/fonts.json
2548
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/images.json
2649
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
2750

2851
COMMENT "Generate fonts and images for resource package"
2952
)
3053

54+
add_dependencies(GenerateResources compiled_fonts)

src/resources/fonts.json

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
],
99
"bpp": 1,
1010
"size": 28,
11-
"format": "bin",
12-
"target_path": "/fonts/"
11+
"target_path": "/fastfonts/"
1312
},
1413
"bebas" : {
1514
"sources": [
@@ -20,8 +19,7 @@
2019
],
2120
"bpp": 1,
2221
"size": 120,
23-
"format": "bin",
24-
"target_path": "/fonts/"
22+
"target_path": "/fastfonts/"
2523
},
2624
"lv_font_dots_40": {
2725
"sources": [
@@ -32,10 +30,9 @@
3230
],
3331
"bpp": 1,
3432
"size": 40,
35-
"format": "bin",
36-
"target_path": "/fonts/"
33+
"target_path": "/fastfonts/"
3734
},
38-
"7segments_40" : {
35+
"seven_segments_40" : {
3936
"sources": [
4037
{
4138
"file": "fonts/7segment.woff",
@@ -44,10 +41,9 @@
4441
],
4542
"bpp": 1,
4643
"size": 40,
47-
"format": "bin",
48-
"target_path": "/fonts/"
44+
"target_path": "/fastfonts/"
4945
},
50-
"7segments_115" : {
46+
"seven_segments_115" : {
5147
"sources": [
5248
{
5349
"file": "fonts/7segment.woff",
@@ -56,7 +52,6 @@
5652
],
5753
"bpp": 1,
5854
"size": 115,
59-
"format": "bin",
60-
"target_path": "/fonts/"
55+
"target_path": "/fastfonts/"
6156
}
6257
}

0 commit comments

Comments
 (0)