From 1383d75c84715df4932c95acc94bb1ae9ccbbe57 Mon Sep 17 00:00:00 2001 From: cottongin Date: Sun, 15 Feb 2026 00:48:23 -0500 Subject: [PATCH] feat: Add per-family font and per-language hyphenation build flags Add OMIT_BOOKERLY, OMIT_NOTOSANS, OMIT_OPENDYSLEXIC flags to selectively exclude font families, and OMIT_HYPH_DE/EN/ES/FR/IT/RU flags to exclude individual hyphenation language tries. The mod build environment excludes OpenDyslexic (~1.03 MB) and all hyphenation tries (~282 KB), reducing flash usage by ~1.3 MB. Font Family setting switched from Enum to DynamicEnum with index-to-value mapping to handle arbitrary font exclusion without breaking the settings UI or persisted values. Co-authored-by: Cursor --- lib/EpdFont/builtinFonts/all.h | 9 +++ .../Epub/hyphenation/LanguageRegistry.cpp | 64 +++++++++++++++---- platformio.ini | 6 ++ src/CrossPointSettings.cpp | 48 +++++++++++++- src/SettingsList.h | 42 +++++++++++- src/activities/settings/SettingsActivity.cpp | 8 +++ src/main.cpp | 16 +++++ 7 files changed, 175 insertions(+), 18 deletions(-) diff --git a/lib/EpdFont/builtinFonts/all.h b/lib/EpdFont/builtinFonts/all.h index e7b0a172..c413fcc0 100644 --- a/lib/EpdFont/builtinFonts/all.h +++ b/lib/EpdFont/builtinFonts/all.h @@ -1,5 +1,6 @@ #pragma once +#ifndef OMIT_BOOKERLY #include #include #include @@ -16,7 +17,10 @@ #include #include #include +#endif // OMIT_BOOKERLY + #include +#ifndef OMIT_NOTOSANS #include #include #include @@ -33,6 +37,9 @@ #include #include #include +#endif // OMIT_NOTOSANS + +#ifndef OMIT_OPENDYSLEXIC #include #include #include @@ -49,6 +56,8 @@ #include #include #include +#endif // OMIT_OPENDYSLEXIC + #include #include #include diff --git a/lib/Epub/Epub/hyphenation/LanguageRegistry.cpp b/lib/Epub/Epub/hyphenation/LanguageRegistry.cpp index c36ea64e..c3bdd262 100644 --- a/lib/Epub/Epub/hyphenation/LanguageRegistry.cpp +++ b/lib/Epub/Epub/hyphenation/LanguageRegistry.cpp @@ -1,48 +1,84 @@ #include "LanguageRegistry.h" #include -#include +#include #include "HyphenationCommon.h" +#ifndef OMIT_HYPH_DE #include "generated/hyph-de.trie.h" +#endif +#ifndef OMIT_HYPH_EN #include "generated/hyph-en.trie.h" +#endif +#ifndef OMIT_HYPH_ES #include "generated/hyph-es.trie.h" +#endif +#ifndef OMIT_HYPH_FR #include "generated/hyph-fr.trie.h" +#endif +#ifndef OMIT_HYPH_IT #include "generated/hyph-it.trie.h" +#endif +#ifndef OMIT_HYPH_RU #include "generated/hyph-ru.trie.h" +#endif namespace { +#ifndef OMIT_HYPH_EN // English hyphenation patterns (3/3 minimum prefix/suffix length) LanguageHyphenator englishHyphenator(en_us_patterns, isLatinLetter, toLowerLatin, 3, 3); +#endif +#ifndef OMIT_HYPH_FR LanguageHyphenator frenchHyphenator(fr_patterns, isLatinLetter, toLowerLatin); +#endif +#ifndef OMIT_HYPH_DE LanguageHyphenator germanHyphenator(de_patterns, isLatinLetter, toLowerLatin); +#endif +#ifndef OMIT_HYPH_RU LanguageHyphenator russianHyphenator(ru_ru_patterns, isCyrillicLetter, toLowerCyrillic); +#endif +#ifndef OMIT_HYPH_ES LanguageHyphenator spanishHyphenator(es_patterns, isLatinLetter, toLowerLatin); +#endif +#ifndef OMIT_HYPH_IT LanguageHyphenator italianHyphenator(it_patterns, isLatinLetter, toLowerLatin); +#endif -using EntryArray = std::array; - -const EntryArray& entries() { - static const EntryArray kEntries = {{{"english", "en", &englishHyphenator}, - {"french", "fr", &frenchHyphenator}, - {"german", "de", &germanHyphenator}, - {"russian", "ru", &russianHyphenator}, - {"spanish", "es", &spanishHyphenator}, - {"italian", "it", &italianHyphenator}}}; - return kEntries; +const LanguageEntryView entries() { + static const std::vector kEntries = { +#ifndef OMIT_HYPH_EN + {"english", "en", &englishHyphenator}, +#endif +#ifndef OMIT_HYPH_FR + {"french", "fr", &frenchHyphenator}, +#endif +#ifndef OMIT_HYPH_DE + {"german", "de", &germanHyphenator}, +#endif +#ifndef OMIT_HYPH_RU + {"russian", "ru", &russianHyphenator}, +#endif +#ifndef OMIT_HYPH_ES + {"spanish", "es", &spanishHyphenator}, +#endif +#ifndef OMIT_HYPH_IT + {"italian", "it", &italianHyphenator}, +#endif + }; + static const LanguageEntryView view{kEntries.data(), kEntries.size()}; + return view; } } // namespace const LanguageHyphenator* getLanguageHyphenatorForPrimaryTag(const std::string& primaryTag) { - const auto& allEntries = entries(); + const auto allEntries = entries(); const auto it = std::find_if(allEntries.begin(), allEntries.end(), [&primaryTag](const LanguageEntry& entry) { return primaryTag == entry.primaryTag; }); return (it != allEntries.end()) ? it->hyphenator : nullptr; } LanguageEntryView getLanguageEntries() { - const auto& allEntries = entries(); - return LanguageEntryView{allEntries.data(), allEntries.size()}; + return entries(); } diff --git a/platformio.ini b/platformio.ini index 3c117f51..f1a33574 100644 --- a/platformio.ini +++ b/platformio.ini @@ -65,6 +65,12 @@ extra_scripts = pre:scripts/inject_mod_version.py build_flags = ${base.build_flags} + -DOMIT_OPENDYSLEXIC + -DOMIT_HYPH_DE + -DOMIT_HYPH_ES + -DOMIT_HYPH_FR + -DOMIT_HYPH_IT + -DOMIT_HYPH_RU -DENABLE_SERIAL_LOG -DLOG_LEVEL=2 ; Set log level to debug for mod builds diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index 23bcc978..170adf79 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -244,8 +244,8 @@ bool CrossPointSettings::loadFromFile() { float CrossPointSettings::getReaderLineCompression() const { switch (fontFamily) { +#ifndef OMIT_BOOKERLY case BOOKERLY: - default: switch (lineSpacing) { case TIGHT: return 0.95f; @@ -255,6 +255,8 @@ float CrossPointSettings::getReaderLineCompression() const { case WIDE: return 1.1f; } +#endif // OMIT_BOOKERLY +#ifndef OMIT_NOTOSANS case NOTOSANS: switch (lineSpacing) { case TIGHT: @@ -265,6 +267,8 @@ float CrossPointSettings::getReaderLineCompression() const { case WIDE: return 1.0f; } +#endif // OMIT_NOTOSANS +#ifndef OMIT_OPENDYSLEXIC case OPENDYSLEXIC: switch (lineSpacing) { case TIGHT: @@ -275,6 +279,30 @@ float CrossPointSettings::getReaderLineCompression() const { case WIDE: return 1.0f; } +#endif // OMIT_OPENDYSLEXIC + default: + // Fallback: use Bookerly-style compression, or Noto Sans if Bookerly is omitted +#if !defined(OMIT_BOOKERLY) + switch (lineSpacing) { + case TIGHT: + return 0.95f; + case NORMAL: + default: + return 1.0f; + case WIDE: + return 1.1f; + } +#else + switch (lineSpacing) { + case TIGHT: + return 0.90f; + case NORMAL: + default: + return 0.95f; + case WIDE: + return 1.0f; + } +#endif } } @@ -312,8 +340,8 @@ int CrossPointSettings::getRefreshFrequency() const { int CrossPointSettings::getReaderFontId() const { switch (fontFamily) { +#ifndef OMIT_BOOKERLY case BOOKERLY: - default: switch (fontSize) { case SMALL: return BOOKERLY_12_FONT_ID; @@ -325,6 +353,8 @@ int CrossPointSettings::getReaderFontId() const { case EXTRA_LARGE: return BOOKERLY_18_FONT_ID; } +#endif // OMIT_BOOKERLY +#ifndef OMIT_NOTOSANS case NOTOSANS: switch (fontSize) { case SMALL: @@ -337,6 +367,8 @@ int CrossPointSettings::getReaderFontId() const { case EXTRA_LARGE: return NOTOSANS_18_FONT_ID; } +#endif // OMIT_NOTOSANS +#ifndef OMIT_OPENDYSLEXIC case OPENDYSLEXIC: switch (fontSize) { case SMALL: @@ -349,5 +381,17 @@ int CrossPointSettings::getReaderFontId() const { case EXTRA_LARGE: return OPENDYSLEXIC_14_FONT_ID; } +#endif // OMIT_OPENDYSLEXIC + default: + // Fallback to first available font family at medium size +#if !defined(OMIT_BOOKERLY) + return BOOKERLY_14_FONT_ID; +#elif !defined(OMIT_NOTOSANS) + return NOTOSANS_14_FONT_ID; +#elif !defined(OMIT_OPENDYSLEXIC) + return OPENDYSLEXIC_10_FONT_ID; +#else +#error "At least one font family must be available" +#endif } } diff --git a/src/SettingsList.h b/src/SettingsList.h index 7c4db46d..8efe6893 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -6,10 +6,36 @@ #include "KOReaderCredentialStore.h" #include "activities/settings/SettingsActivity.h" +// Compile-time table of available font families and their enum values. +// Used by the DynamicEnum getter/setter to map between list indices and stored FONT_FAMILY values. +struct FontFamilyMapping { + const char* name; + uint8_t value; +}; +inline constexpr FontFamilyMapping kFontFamilyMappings[] = { +#ifndef OMIT_BOOKERLY + {"Bookerly", CrossPointSettings::BOOKERLY}, +#endif +#ifndef OMIT_NOTOSANS + {"Noto Sans", CrossPointSettings::NOTOSANS}, +#endif +#ifndef OMIT_OPENDYSLEXIC + {"Open Dyslexic", CrossPointSettings::OPENDYSLEXIC}, +#endif +}; +inline constexpr size_t kFontFamilyMappingCount = sizeof(kFontFamilyMappings) / sizeof(kFontFamilyMappings[0]); +static_assert(kFontFamilyMappingCount > 0, "At least one font family must be available"); + // Shared settings list used by both the device settings UI and the web settings API. // Each entry has a key (for JSON API) and category (for grouping). // ACTION-type entries and entries without a key are device-only. inline std::vector getSettingsList() { + // Build font family options from the compile-time mapping table + std::vector fontFamilyOptions; + for (size_t i = 0; i < kFontFamilyMappingCount; i++) { + fontFamilyOptions.push_back(kFontFamilyMappings[i].name); + } + return { // --- Display --- SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, @@ -32,8 +58,20 @@ inline std::vector getSettingsList() { SettingInfo::Toggle("Sunlight Fading Fix", &CrossPointSettings::fadingFix, "fadingFix", "Display"), // --- Reader --- - SettingInfo::Enum("Font Family", &CrossPointSettings::fontFamily, {"Bookerly", "Noto Sans", "Open Dyslexic"}, - "fontFamily", "Reader"), + SettingInfo::DynamicEnum( + "Font Family", std::move(fontFamilyOptions), + []() -> uint8_t { + for (uint8_t i = 0; i < kFontFamilyMappingCount; i++) { + if (kFontFamilyMappings[i].value == SETTINGS.fontFamily) return i; + } + return 0; // fallback to first available family + }, + [](uint8_t idx) { + if (idx < kFontFamilyMappingCount) { + SETTINGS.fontFamily = kFontFamilyMappings[idx].value; + } + }, + "fontFamily", "Reader"), SettingInfo::Enum("Font Size", &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}, "fontSize", "Reader"), SettingInfo::Enum("Line Spacing", &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}, "lineSpacing", diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 20383334..c8d95802 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -173,6 +173,9 @@ void SettingsActivity::toggleCurrentSetting() { } else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) { const uint8_t currentValue = SETTINGS.*(setting.valuePtr); SETTINGS.*(setting.valuePtr) = (currentValue + 1) % static_cast(setting.enumValues.size()); + } else if (setting.type == SettingType::ENUM && setting.valueGetter && setting.valueSetter) { + const uint8_t currentValue = setting.valueGetter(); + setting.valueSetter((currentValue + 1) % static_cast(setting.enumValues.size())); } else if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) { const int8_t currentValue = SETTINGS.*(setting.valuePtr); if (currentValue + setting.valueRange.step > setting.valueRange.max) { @@ -274,6 +277,11 @@ void SettingsActivity::render() const { } else if (settings[i].type == SettingType::ENUM && settings[i].valuePtr != nullptr) { const uint8_t value = SETTINGS.*(settings[i].valuePtr); valueText = settings[i].enumValues[value]; + } else if (settings[i].type == SettingType::ENUM && settings[i].valueGetter) { + const uint8_t value = settings[i].valueGetter(); + if (value < settings[i].enumValues.size()) { + valueText = settings[i].enumValues[value]; + } } else if (settings[i].type == SettingType::VALUE && settings[i].valuePtr != nullptr) { valueText = std::to_string(SETTINGS.*(settings[i].valuePtr)); } diff --git a/src/main.cpp b/src/main.cpp index 5aa9bca8..d99faea5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -39,13 +39,16 @@ GfxRenderer renderer(display); Activity* currentActivity; // Fonts +#ifndef OMIT_BOOKERLY EpdFont bookerly14RegularFont(&bookerly_14_regular); EpdFont bookerly14BoldFont(&bookerly_14_bold); EpdFont bookerly14ItalicFont(&bookerly_14_italic); EpdFont bookerly14BoldItalicFont(&bookerly_14_bolditalic); EpdFontFamily bookerly14FontFamily(&bookerly14RegularFont, &bookerly14BoldFont, &bookerly14ItalicFont, &bookerly14BoldItalicFont); +#endif // OMIT_BOOKERLY #ifndef OMIT_FONTS +#ifndef OMIT_BOOKERLY EpdFont bookerly12RegularFont(&bookerly_12_regular); EpdFont bookerly12BoldFont(&bookerly_12_bold); EpdFont bookerly12ItalicFont(&bookerly_12_italic); @@ -64,7 +67,9 @@ EpdFont bookerly18ItalicFont(&bookerly_18_italic); EpdFont bookerly18BoldItalicFont(&bookerly_18_bolditalic); EpdFontFamily bookerly18FontFamily(&bookerly18RegularFont, &bookerly18BoldFont, &bookerly18ItalicFont, &bookerly18BoldItalicFont); +#endif // OMIT_BOOKERLY +#ifndef OMIT_NOTOSANS EpdFont notosans12RegularFont(¬osans_12_regular); EpdFont notosans12BoldFont(¬osans_12_bold); EpdFont notosans12ItalicFont(¬osans_12_italic); @@ -89,7 +94,9 @@ EpdFont notosans18ItalicFont(¬osans_18_italic); EpdFont notosans18BoldItalicFont(¬osans_18_bolditalic); EpdFontFamily notosans18FontFamily(¬osans18RegularFont, ¬osans18BoldFont, ¬osans18ItalicFont, ¬osans18BoldItalicFont); +#endif // OMIT_NOTOSANS +#ifndef OMIT_OPENDYSLEXIC EpdFont opendyslexic8RegularFont(&opendyslexic_8_regular); EpdFont opendyslexic8BoldFont(&opendyslexic_8_bold); EpdFont opendyslexic8ItalicFont(&opendyslexic_8_italic); @@ -114,6 +121,7 @@ EpdFont opendyslexic14ItalicFont(&opendyslexic_14_italic); EpdFont opendyslexic14BoldItalicFont(&opendyslexic_14_bolditalic); EpdFontFamily opendyslexic14FontFamily(&opendyslexic14RegularFont, &opendyslexic14BoldFont, &opendyslexic14ItalicFont, &opendyslexic14BoldItalicFont); +#endif // OMIT_OPENDYSLEXIC #endif // OMIT_FONTS EpdFont smallFont(¬osans_8_regular); @@ -259,20 +267,28 @@ void setupDisplayAndFonts() { display.begin(); renderer.begin(); LOG_DBG("MAIN", "Display initialized"); +#ifndef OMIT_BOOKERLY renderer.insertFont(BOOKERLY_14_FONT_ID, bookerly14FontFamily); +#endif #ifndef OMIT_FONTS +#ifndef OMIT_BOOKERLY renderer.insertFont(BOOKERLY_12_FONT_ID, bookerly12FontFamily); renderer.insertFont(BOOKERLY_16_FONT_ID, bookerly16FontFamily); renderer.insertFont(BOOKERLY_18_FONT_ID, bookerly18FontFamily); +#endif // OMIT_BOOKERLY +#ifndef OMIT_NOTOSANS renderer.insertFont(NOTOSANS_12_FONT_ID, notosans12FontFamily); renderer.insertFont(NOTOSANS_14_FONT_ID, notosans14FontFamily); renderer.insertFont(NOTOSANS_16_FONT_ID, notosans16FontFamily); renderer.insertFont(NOTOSANS_18_FONT_ID, notosans18FontFamily); +#endif // OMIT_NOTOSANS +#ifndef OMIT_OPENDYSLEXIC renderer.insertFont(OPENDYSLEXIC_8_FONT_ID, opendyslexic8FontFamily); renderer.insertFont(OPENDYSLEXIC_10_FONT_ID, opendyslexic10FontFamily); renderer.insertFont(OPENDYSLEXIC_12_FONT_ID, opendyslexic12FontFamily); renderer.insertFont(OPENDYSLEXIC_14_FONT_ID, opendyslexic14FontFamily); +#endif // OMIT_OPENDYSLEXIC #endif // OMIT_FONTS renderer.insertFont(UI_10_FONT_ID, ui10FontFamily); renderer.insertFont(UI_12_FONT_ID, ui12FontFamily);