From 06dfc6c50bdf871a8ce1edb941bb955306e065fb Mon Sep 17 00:00:00 2001 From: jpirnay Date: Sat, 2 May 2026 18:19:23 +0200 Subject: [PATCH] Recover parts of the regression fiasco Co-authored-by: Copilot --- lib/EpdFont/builtinFonts/all.h | 32 ++--- lib/EpdFont/scripts/build-font-ids.sh | 54 +++----- lib/EpdFont/scripts/convert-builtin-fonts.sh | 45 ++++--- .../converters/JpegToFramebufferConverter.cpp | 99 ++++++++++++--- lib/GfxRenderer/FontCacheManager.cpp | 5 +- lib/I18n/translations/english.yaml | 1 + src/CrossPointSettings.cpp | 36 +----- src/CrossPointSettings.h | 4 +- src/JsonSettingsIO.cpp | 8 ++ src/RecentBooksStore.cpp | 38 +++++- src/RecentBooksStore.h | 7 ++ src/SdCardFontSystem.cpp | 6 +- src/SettingsList.h | 7 +- src/activities/ActivityResult.h | 1 + src/activities/reader/EpubReaderActivity.cpp | 108 +++++----------- src/activities/reader/EpubReaderActivity.h | 4 +- .../reader/EpubReaderMenuActivity.cpp | 119 ++++++++++++------ .../reader/EpubReaderMenuActivity.h | 4 +- src/fontIds.h | 28 ++--- src/main.cpp | 50 +++----- src/network/CrossPointWebServer.cpp | 1 - 21 files changed, 354 insertions(+), 303 deletions(-) diff --git a/lib/EpdFont/builtinFonts/all.h b/lib/EpdFont/builtinFonts/all.h index e7b0a172..efc8b2c6 100644 --- a/lib/EpdFont/builtinFonts/all.h +++ b/lib/EpdFont/builtinFonts/all.h @@ -1,5 +1,9 @@ #pragma once +#include +#include +#include +#include #include #include #include @@ -16,6 +20,14 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -33,23 +45,3 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include diff --git a/lib/EpdFont/scripts/build-font-ids.sh b/lib/EpdFont/scripts/build-font-ids.sh index 8ecdd698..77d0af4f 100755 --- a/lib/EpdFont/scripts/build-font-ids.sh +++ b/lib/EpdFont/scripts/build-font-ids.sh @@ -8,6 +8,15 @@ echo "// The contents of this file are generated by ./lib/EpdFont/scripts/build- echo "#pragma once" echo "" +echo "#define BOOKERLY_10_FONT_ID ($( +ruby -rdigest -e 'puts [ + "./bookerly_10_regular.h", + "./bookerly_10_bold.h", + "./bookerly_10_bolditalic.h", + "./bookerly_10_italic.h", +].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)' +))" + echo "#define BOOKERLY_12_FONT_ID ($( ruby -rdigest -e 'puts [ "./bookerly_12_regular.h", @@ -80,53 +89,26 @@ ruby -rdigest -e 'puts [ ].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)' ))" -echo "#define OPENDYSLEXIC_8_FONT_ID ($( +echo "#define NOTOSANS_10_FONT_ID ($( ruby -rdigest -e 'puts [ - "./opendyslexic_8_regular.h", - "./opendyslexic_8_bold.h", - "./opendyslexic_8_bolditalic.h", - "./opendyslexic_8_italic.h", -].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)' -))" - -echo "#define OPENDYSLEXIC_10_FONT_ID ($( -ruby -rdigest -e 'puts [ - "./opendyslexic_10_regular.h", - "./opendyslexic_10_bold.h", - "./opendyslexic_10_bolditalic.h", - "./opendyslexic_10_italic.h", -].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)' -))" - -echo "#define OPENDYSLEXIC_12_FONT_ID ($( -ruby -rdigest -e 'puts [ - "./opendyslexic_12_regular.h", - "./opendyslexic_12_bold.h", - "./opendyslexic_12_bolditalic.h", - "./opendyslexic_12_italic.h", -].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)' -))" - -echo "#define OPENDYSLEXIC_14_FONT_ID ($( -ruby -rdigest -e 'puts [ - "./opendyslexic_14_regular.h", - "./opendyslexic_14_bold.h", - "./opendyslexic_14_bolditalic.h", - "./opendyslexic_14_italic.h", + "./notosans_10_regular.h", + "./notosans_10_bold.h", + "./notosans_10_bolditalic.h", + "./notosans_10_italic.h", ].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)' ))" echo "#define UI_10_FONT_ID ($( ruby -rdigest -e 'puts [ - "./ubuntu_10_regular.h", - "./ubuntu_10_bold.h", + "./inter_ui_10_regular.h", + "./inter_ui_10_bold.h", ].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)' ))" echo "#define UI_12_FONT_ID ($( ruby -rdigest -e 'puts [ - "./ubuntu_12_regular.h", - "./ubuntu_12_bold.h", + "./inter_ui_12_regular.h", + "./inter_ui_12_bold.h", ].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)' ))" diff --git a/lib/EpdFont/scripts/convert-builtin-fonts.sh b/lib/EpdFont/scripts/convert-builtin-fonts.sh index 26037060..84d5cfe6 100755 --- a/lib/EpdFont/scripts/convert-builtin-fonts.sh +++ b/lib/EpdFont/scripts/convert-builtin-fonts.sh @@ -5,9 +5,8 @@ set -e cd "$(dirname "$0")" READER_FONT_STYLES=("Regular" "Italic" "Bold" "BoldItalic") -BOOKERLY_FONT_SIZES=(12 14 16 18) -NOTOSANS_FONT_SIZES=(12 14 16 18) -OPENDYSLEXIC_FONT_SIZES=(8 10 12 14) +BOOKERLY_FONT_SIZES=(10 12 14 16 18) +NOTOSANS_FONT_SIZES=(10 12 14 16 18) for size in ${BOOKERLY_FONT_SIZES[@]}; do for style in ${READER_FONT_STYLES[@]}; do @@ -29,28 +28,36 @@ for size in ${NOTOSANS_FONT_SIZES[@]}; do done done -for size in ${OPENDYSLEXIC_FONT_SIZES[@]}; do - for style in ${READER_FONT_STYLES[@]}; do - font_name="opendyslexic_${size}_$(echo $style | tr '[:upper:]' '[:lower:]')" - font_path="../builtinFonts/source/OpenDyslexic/OpenDyslexic-${style}.otf" - output_path="../builtinFonts/${font_name}.h" - python fontconvert.py $font_name $size $font_path --2bit --compress > $output_path - echo "Generated $output_path" - done -done - UI_FONT_SIZES=(10 12) UI_FONT_STYLES=("Regular" "Bold") +UI_LANG_INTERVALS=( + "0x0000,0x007F" + "0x0080,0x00FF" + "0x0100,0x017F" + "0x01A0,0x01A1" + "0x01AF,0x01B0" + "0x01C4,0x021F" + "0x0300,0x036F" + "0x0400,0x04FF" + "0x1EA0,0x1EF9" + "0x2010,0x206F" + "0x20A0,0x20CF" + "0xFB00,0xFB06" + "0xFFFD,0xFFFD" +) for size in ${UI_FONT_SIZES[@]}; do for style in ${UI_FONT_STYLES[@]}; do - font_name="ubuntu_${size}_$(echo $style | tr '[:upper:]' '[:lower:]')" - ubuntu_path="../builtinFonts/source/Ubuntu/Ubuntu-${style}.ttf" - # NotoSans used as fallback to fill missing glyphs (e.g. Vietnamese U+1EA0-1EF9) - # that are absent from the Ubuntu TTF source. - noto_path="../builtinFonts/source/NotoSans/NotoSans-${style}.ttf" + font_name="inter_ui_${size}_$(echo $style | tr '[:upper:]' '[:lower:]')" + inter_path="../builtinFonts/source/Inter/Inter-${style}.ttf" output_path="../builtinFonts/${font_name}.h" - python fontconvert.py $font_name $size $ubuntu_path $noto_path > $output_path + + cmd=(python fontconvert.py "$font_name" "$size" "$inter_path") + for interval in "${UI_LANG_INTERVALS[@]}"; do + cmd+=(--additional-intervals "$interval") + done + "${cmd[@]}" > "$output_path" + echo "Generated $output_path" done done diff --git a/lib/Epub/Epub/converters/JpegToFramebufferConverter.cpp b/lib/Epub/Epub/converters/JpegToFramebufferConverter.cpp index fdf29129..da8b64fd 100644 --- a/lib/Epub/Epub/converters/JpegToFramebufferConverter.cpp +++ b/lib/Epub/Epub/converters/JpegToFramebufferConverter.cpp @@ -173,6 +173,84 @@ int32_t jpegSeek(JPEGFILE* pFile, int32_t pos) { constexpr size_t JPEG_DECODER_APPROX_SIZE = 20 * 1024; constexpr size_t MIN_FREE_HEAP_FOR_JPEG = JPEG_DECODER_APPROX_SIZE + 16 * 1024; +bool readJpegDimensionsFromHeader(const std::string& imagePath, ImageDimensions& out) { + FsFile f; + if (!Storage.openFileForRead("JPG", imagePath, f)) { + LOG_ERR("JPG", "Failed to open file for dimensions: %s", imagePath.c_str()); + return false; + } + + auto readByte = [&f](uint8_t& b) -> bool { return f.read(&b, 1) == 1; }; + auto readU16BE = [&f](uint16_t& v) -> bool { + uint8_t b[2]; + if (f.read(b, 2) != 2) return false; + v = static_cast((static_cast(b[0]) << 8) | b[1]); + return true; + }; + + uint8_t b0 = 0; + uint8_t b1 = 0; + if (!readByte(b0) || !readByte(b1) || b0 != 0xFF || b1 != 0xD8) { + f.close(); + LOG_ERR("JPG", "Not a JPEG file: %s", imagePath.c_str()); + return false; + } + + while (f.available()) { + uint8_t prefix = 0; + if (!readByte(prefix)) break; + if (prefix != 0xFF) continue; + + uint8_t marker = 0; + do { + if (!readByte(marker)) { + f.close(); + return false; + } + } while (marker == 0xFF); + + if (marker == 0x00 || marker == 0xD8 || marker == 0xD9 || (marker >= 0xD0 && marker <= 0xD7)) { + continue; + } + + uint16_t segLen = 0; + if (!readU16BE(segLen) || segLen < 2) { + f.close(); + return false; + } + + const bool isSof = (marker >= 0xC0 && marker <= 0xC3) || (marker >= 0xC5 && marker <= 0xC7) || + (marker >= 0xC9 && marker <= 0xCB) || (marker >= 0xCD && marker <= 0xCF); + if (isSof) { + uint8_t sof[5]; + if (segLen < 7 || f.read(sof, sizeof(sof)) != static_cast(sizeof(sof))) { + f.close(); + return false; + } + uint16_t height = static_cast((static_cast(sof[1]) << 8) | sof[2]); + uint16_t width = static_cast((static_cast(sof[3]) << 8) | sof[4]); + f.close(); + if (width == 0 || height == 0) { + LOG_ERR("JPG", "Invalid JPEG dimensions %ux%u: %s", width, height, imagePath.c_str()); + return false; + } + out.width = static_cast(width); + out.height = static_cast(height); + return true; + } + + const int32_t skip = static_cast(segLen) - 2; + if (!f.seek(f.position() + skip)) { + f.close(); + return false; + } + } + + f.close(); + LOG_ERR("JPG", "No SOF marker found for dimensions: %s", imagePath.c_str()); + return false; +} + // Choose JPEGDEC's built-in scale factor for coarse downscaling. // Returns the scale denominator (1, 2, 4, or 8) and sets jpegScaleOption. int chooseJpegScale(float targetScale, int& jpegScaleOption) { @@ -401,29 +479,10 @@ int jpegDrawCallback(JPEGDRAW* pDraw) { } // namespace bool JpegToFramebufferConverter::getDimensionsStatic(const std::string& imagePath, ImageDimensions& out) { - size_t freeHeap = ESP.getFreeHeap(); - if (freeHeap < MIN_FREE_HEAP_FOR_JPEG) { - LOG_ERR("JPG", "Not enough heap for JPEG decoder (%u free, need %u)", freeHeap, MIN_FREE_HEAP_FOR_JPEG); + if (!readJpegDimensionsFromHeader(imagePath, out)) { return false; } - - std::unique_ptr jpeg(new (std::nothrow) JPEGDEC()); - if (!jpeg) { - LOG_ERR("JPG", "Failed to allocate JPEG decoder for dimensions"); - return false; - } - - int rc = jpeg->open(imagePath.c_str(), jpegOpen, jpegClose, jpegRead, jpegSeek, nullptr); - if (rc != 1) { - LOG_ERR("JPG", "Failed to open JPEG for dimensions (err=%d): %s", jpeg->getLastError(), imagePath.c_str()); - return false; - } - - out.width = jpeg->getWidth(); - out.height = jpeg->getHeight(); LOG_DBG("JPG", "Image dimensions: %dx%d", out.width, out.height); - - jpeg->close(); return true; } diff --git a/lib/GfxRenderer/FontCacheManager.cpp b/lib/GfxRenderer/FontCacheManager.cpp index ded021da..01841ad6 100644 --- a/lib/GfxRenderer/FontCacheManager.cpp +++ b/lib/GfxRenderer/FontCacheManager.cpp @@ -89,7 +89,6 @@ void FontCacheManager::recordText(const char* text, int fontId, EpdFontFamily::S FontCacheManager::PrewarmScope::PrewarmScope(FontCacheManager& manager) : manager_(&manager) { manager_->scanMode_ = ScanMode::Scanning; - manager_->clearCache(); manager_->resetStats(); manager_->scanText_.clear(); manager_->scanText_.reserve(2048); // Pre-allocate to avoid heap fragmentation from repeated concat @@ -110,15 +109,13 @@ void FontCacheManager::PrewarmScope::endScanAndPrewarm() { manager_->prewarmCache(manager_->scanFontId_, manager_->scanText_.c_str(), styleMask); - // Free scan string memory + // Keep reserved capacity to avoid repeated alloc/free churn between pages. manager_->scanText_.clear(); - manager_->scanText_.shrink_to_fit(); } FontCacheManager::PrewarmScope::~PrewarmScope() { if (active_) { endScanAndPrewarm(); // no-op if already called (scanText_ is empty) - manager_->clearCache(); } } diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index 9e5f874b..da3455f4 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -219,6 +219,7 @@ STR_SMALL: "Small" STR_MEDIUM: "Medium" STR_LARGE: "Large" STR_X_LARGE: "X Large" +STR_TINY: "Tiny" STR_TIGHT: "Tight" STR_NORMAL: "Normal" STR_WIDE: "Wide" diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index 880b42fd..c93d9f08 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -18,14 +18,12 @@ static_assert(BOOKERLY_12_FONT_ID != 0, "Font ID collision with sentinel"); static_assert(BOOKERLY_14_FONT_ID != 0, "Font ID collision with sentinel"); static_assert(BOOKERLY_16_FONT_ID != 0, "Font ID collision with sentinel"); static_assert(BOOKERLY_18_FONT_ID != 0, "Font ID collision with sentinel"); +static_assert(BOOKERLY_10_FONT_ID != 0, "Font ID collision with sentinel"); static_assert(NOTOSANS_12_FONT_ID != 0, "Font ID collision with sentinel"); static_assert(NOTOSANS_14_FONT_ID != 0, "Font ID collision with sentinel"); static_assert(NOTOSANS_16_FONT_ID != 0, "Font ID collision with sentinel"); static_assert(NOTOSANS_18_FONT_ID != 0, "Font ID collision with sentinel"); -static_assert(OPENDYSLEXIC_8_FONT_ID != 0, "Font ID collision with sentinel"); -static_assert(OPENDYSLEXIC_10_FONT_ID != 0, "Font ID collision with sentinel"); -static_assert(OPENDYSLEXIC_12_FONT_ID != 0, "Font ID collision with sentinel"); -static_assert(OPENDYSLEXIC_14_FONT_ID != 0, "Font ID collision with sentinel"); +static_assert(NOTOSANS_10_FONT_ID != 0, "Font ID collision with sentinel"); static_assert(UI_10_FONT_ID != 0, "Font ID collision with sentinel"); static_assert(UI_12_FONT_ID != 0, "Font ID collision with sentinel"); static_assert(SMALL_FONT_ID != 0, "Font ID collision with sentinel"); @@ -265,9 +263,7 @@ bool CrossPointSettings::loadFromBinaryFile() { float CrossPointSettings::getReaderLineCompression() const { const int effectiveFontId = getReaderFontId(); - const int bookerlyId = getBuiltinReaderFontId(BOOKERLY, fontSize); const int notosansId = getBuiltinReaderFontId(NOTOSANS, fontSize); - const int opendyslexicId = getBuiltinReaderFontId(OPENDYSLEXIC, fontSize); if (effectiveFontId == notosansId) { switch (lineSpacing) { @@ -281,18 +277,6 @@ float CrossPointSettings::getReaderLineCompression() const { } } - if (effectiveFontId == opendyslexicId) { - switch (lineSpacing) { - case TIGHT: - return 0.90f; - case NORMAL: - default: - return 0.95f; - case WIDE: - return 1.0f; - } - } - // Bookerly or any SD card font: use the Bookerly-style neutral values. switch (lineSpacing) { case TIGHT: @@ -342,6 +326,8 @@ int CrossPointSettings::getBuiltinReaderFontId(uint8_t family, uint8_t size) { case BOOKERLY: default: switch (size) { + case TINY: + return BOOKERLY_10_FONT_ID; case SMALL: return BOOKERLY_12_FONT_ID; case MEDIUM: @@ -354,6 +340,8 @@ int CrossPointSettings::getBuiltinReaderFontId(uint8_t family, uint8_t size) { } case NOTOSANS: switch (size) { + case TINY: + return NOTOSANS_10_FONT_ID; case SMALL: return NOTOSANS_12_FONT_ID; case MEDIUM: @@ -364,18 +352,6 @@ int CrossPointSettings::getBuiltinReaderFontId(uint8_t family, uint8_t size) { case EXTRA_LARGE: return NOTOSANS_18_FONT_ID; } - case OPENDYSLEXIC: - switch (size) { - case SMALL: - return OPENDYSLEXIC_8_FONT_ID; - case MEDIUM: - default: - return OPENDYSLEXIC_10_FONT_ID; - case LARGE: - return OPENDYSLEXIC_12_FONT_ID; - case EXTRA_LARGE: - return OPENDYSLEXIC_14_FONT_ID; - } } } diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index d970db53..ad072209 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -89,10 +89,10 @@ class CrossPointSettings { }; // Font family options (built-in fonts only; SD card fonts use sdFontFamilyName) - enum FONT_FAMILY { BOOKERLY = 0, NOTOSANS = 1, OPENDYSLEXIC = 2, FONT_FAMILY_COUNT }; + enum FONT_FAMILY { BOOKERLY = 0, NOTOSANS = 1, FONT_FAMILY_COUNT }; static constexpr uint8_t BUILTIN_FONT_COUNT = FONT_FAMILY_COUNT; // Font size options - enum FONT_SIZE { SMALL = 0, MEDIUM = 1, LARGE = 2, EXTRA_LARGE = 3, FONT_SIZE_COUNT }; + enum FONT_SIZE { SMALL = 0, MEDIUM = 1, LARGE = 2, EXTRA_LARGE = 3, TINY = 4, FONT_SIZE_COUNT }; enum LINE_COMPRESSION { TIGHT = 0, NORMAL = 1, WIDE = 2, LINE_COMPRESSION_COUNT }; enum PARAGRAPH_ALIGNMENT { JUSTIFIED = 0, diff --git a/src/JsonSettingsIO.cpp b/src/JsonSettingsIO.cpp index d743c016..aaa1fa87 100644 --- a/src/JsonSettingsIO.cpp +++ b/src/JsonSettingsIO.cpp @@ -417,6 +417,9 @@ bool JsonSettingsIO::saveRecentBooks(const RecentBooksStore& store, const char* obj["embeddedStyleOverride"] = book.embeddedStyleOverride; obj["imageRenderingOverride"] = book.imageRenderingOverride; obj["fontFamilyOverride"] = book.fontFamilyOverride; + if (!book.sdFontFamilyOverride.empty()) { + obj["sdFontFamilyOverride"] = book.sdFontFamilyOverride; + } obj["fontSizeOverride"] = book.fontSizeOverride; obj["bionicReadingOverride"] = book.bionicReadingOverride; } @@ -455,6 +458,11 @@ bool JsonSettingsIO::loadRecentBooks(RecentBooksStore& store, const char* json) book.imageRenderingOverride = clampInt8(obj["imageRenderingOverride"] | -1, -1, 2, -1); book.fontFamilyOverride = clampInt8(obj["fontFamilyOverride"] | -1, -1, CrossPointSettings::FONT_FAMILY_COUNT - 1, -1); + book.sdFontFamilyOverride = obj["sdFontFamilyOverride"] | std::string(""); + if (!book.sdFontFamilyOverride.empty()) { + // Keep built-in and SD font overrides mutually exclusive. + book.fontFamilyOverride = -1; + } book.fontSizeOverride = clampInt8(obj["fontSizeOverride"] | -1, -1, CrossPointSettings::FONT_SIZE_COUNT - 1, -1); book.bionicReadingOverride = clampInt8(obj["bionicReadingOverride"] | -1, -1, 1, -1); store.recentBooks.push_back(book); diff --git a/src/RecentBooksStore.cpp b/src/RecentBooksStore.cpp index 7dc1d2e0..d53a281b 100644 --- a/src/RecentBooksStore.cpp +++ b/src/RecentBooksStore.cpp @@ -25,6 +25,7 @@ void RecentBooksStore::addBook(const std::string& path, const std::string& title int8_t embeddedStyleOverride = -1; int8_t imageRenderingOverride = -1; int8_t fontFamilyOverride = -1; + std::string sdFontFamilyOverride; int8_t fontSizeOverride = -1; int8_t bionicReadingOverride = -1; @@ -35,6 +36,7 @@ void RecentBooksStore::addBook(const std::string& path, const std::string& title embeddedStyleOverride = it->embeddedStyleOverride; imageRenderingOverride = it->imageRenderingOverride; fontFamilyOverride = it->fontFamilyOverride; + sdFontFamilyOverride = it->sdFontFamilyOverride; fontSizeOverride = it->fontSizeOverride; bionicReadingOverride = it->bionicReadingOverride; recentBooks.erase(it); @@ -43,7 +45,7 @@ void RecentBooksStore::addBook(const std::string& path, const std::string& title // Add to front recentBooks.insert(recentBooks.begin(), {path, title, author, series, coverBmpPath, embeddedStyleOverride, imageRenderingOverride, - fontFamilyOverride, fontSizeOverride, bionicReadingOverride}); + fontFamilyOverride, sdFontFamilyOverride, fontSizeOverride, bionicReadingOverride}); // Trim to max size if (recentBooks.size() > MAX_RECENT_BOOKS) { @@ -93,7 +95,7 @@ bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t return false; } return setReaderOverrides(path, embeddedStyleOverride, imageRenderingOverride, it->fontFamilyOverride, - it->fontSizeOverride, it->bionicReadingOverride); + it->sdFontFamilyOverride, it->fontSizeOverride, it->bionicReadingOverride); } bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride, @@ -104,8 +106,20 @@ bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t if (it == recentBooks.end()) { return false; } - return setReaderOverrides(path, embeddedStyleOverride, imageRenderingOverride, fontFamilyOverride, fontSizeOverride, - it->bionicReadingOverride); + return setReaderOverrides(path, embeddedStyleOverride, imageRenderingOverride, fontFamilyOverride, + it->sdFontFamilyOverride, fontSizeOverride, it->bionicReadingOverride); +} + +bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride, + const int8_t imageRenderingOverride, const int8_t fontFamilyOverride, + const std::string& sdFontFamilyOverride, const int8_t fontSizeOverride) { + auto it = + std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; }); + if (it == recentBooks.end()) { + return false; + } + return setReaderOverrides(path, embeddedStyleOverride, imageRenderingOverride, fontFamilyOverride, + sdFontFamilyOverride, fontSizeOverride, it->bionicReadingOverride); } bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride, @@ -116,7 +130,7 @@ bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t return false; } return setReaderOverrides(path, embeddedStyleOverride, imageRenderingOverride, it->fontFamilyOverride, - it->fontSizeOverride, bionicReadingOverride); + it->sdFontFamilyOverride, it->fontSizeOverride, bionicReadingOverride); } bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride, @@ -127,10 +141,24 @@ bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t if (it == recentBooks.end()) { return false; } + return setReaderOverrides(path, embeddedStyleOverride, imageRenderingOverride, fontFamilyOverride, + it->sdFontFamilyOverride, fontSizeOverride, bionicReadingOverride); +} + +bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride, + const int8_t imageRenderingOverride, const int8_t fontFamilyOverride, + const std::string& sdFontFamilyOverride, const int8_t fontSizeOverride, + const bool bionicReadingOverride) { + auto it = + std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; }); + if (it == recentBooks.end()) { + return false; + } it->embeddedStyleOverride = embeddedStyleOverride; it->imageRenderingOverride = imageRenderingOverride; it->fontFamilyOverride = fontFamilyOverride; + it->sdFontFamilyOverride = sdFontFamilyOverride; it->fontSizeOverride = fontSizeOverride; it->bionicReadingOverride = bionicReadingOverride; return saveToFile(); diff --git a/src/RecentBooksStore.h b/src/RecentBooksStore.h index 53652c42..6940006a 100644 --- a/src/RecentBooksStore.h +++ b/src/RecentBooksStore.h @@ -15,6 +15,8 @@ struct RecentBook { int8_t imageRenderingOverride = -1; // -1 = use global setting, otherwise CrossPointSettings::FONT_FAMILY value. int8_t fontFamilyOverride = -1; + // Empty = use global setting, otherwise explicit SD-card family name override. + std::string sdFontFamilyOverride; // -1 = use global setting, otherwise CrossPointSettings::FONT_SIZE value. int8_t fontSizeOverride = -1; // -1 = use global default, otherwise explicit per-book override (0 = off, 1 = on). @@ -66,10 +68,15 @@ class RecentBooksStore { bool setReaderOverrides(const std::string& path, int8_t embeddedStyleOverride, int8_t imageRenderingOverride); bool setReaderOverrides(const std::string& path, int8_t embeddedStyleOverride, int8_t imageRenderingOverride, int8_t fontFamilyOverride, int8_t fontSizeOverride); + bool setReaderOverrides(const std::string& path, int8_t embeddedStyleOverride, int8_t imageRenderingOverride, + int8_t fontFamilyOverride, const std::string& sdFontFamilyOverride, int8_t fontSizeOverride); bool setReaderOverrides(const std::string& path, int8_t embeddedStyleOverride, int8_t imageRenderingOverride, bool bionicReadingOverride); bool setReaderOverrides(const std::string& path, int8_t embeddedStyleOverride, int8_t imageRenderingOverride, int8_t fontFamilyOverride, int8_t fontSizeOverride, bool bionicReadingOverride); + bool setReaderOverrides(const std::string& path, int8_t embeddedStyleOverride, int8_t imageRenderingOverride, + int8_t fontFamilyOverride, const std::string& sdFontFamilyOverride, int8_t fontSizeOverride, + bool bionicReadingOverride); private: bool loadFromBinaryFile(); diff --git a/src/SdCardFontSystem.cpp b/src/SdCardFontSystem.cpp index da00eade..ca9e7df3 100644 --- a/src/SdCardFontSystem.cpp +++ b/src/SdCardFontSystem.cpp @@ -60,7 +60,7 @@ uint8_t fontFamilyOptionCount() { std::string fontFamilyOptionLabel(uint8_t i) { if (i < CrossPointSettings::BUILTIN_FONT_COUNT) { - static const StrId BUILTIN_LABELS[] = {StrId::STR_BOOKERLY, StrId::STR_NOTO_SANS, StrId::STR_OPEN_DYSLEXIC}; + static const StrId BUILTIN_LABELS[] = {StrId::STR_BOOKERLY, StrId::STR_NOTO_SANS}; return I18N.get(BUILTIN_LABELS[i]); } const auto& families = sdFontSystem.registry().getFamilies(); @@ -68,8 +68,8 @@ std::string fontFamilyOptionLabel(uint8_t i) { return sdIdx < families.size() ? families[sdIdx].name : std::string(); } -// Map fontSize enum (SMALL=0, MEDIUM=1, LARGE=2, EXTRA_LARGE=3) to point sizes. -static constexpr uint8_t FONT_SIZE_TO_PT[] = {12, 14, 16, 18}; +// Map fontSize enum (SMALL=0, MEDIUM=1, LARGE=2, EXTRA_LARGE=3, TINY=4) to point sizes. +static constexpr uint8_t FONT_SIZE_TO_PT[] = {12, 14, 16, 18, 10}; static uint8_t targetPtSizeFromSettings() { uint8_t e = SETTINGS.fontSize; diff --git a/src/SettingsList.h b/src/SettingsList.h index f57fe0f6..1fb7002a 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -91,13 +91,12 @@ inline const std::vector list = { // side (SettingsActivity / CrossPointWebServer enrich enumLabels before // iterating). The built-in StrIds are kept as a fallback for code paths that // don't enrich enumLabels. - SettingInfo::DynamicEnum(StrId::STR_FONT_FAMILY, - {StrId::STR_BOOKERLY, StrId::STR_NOTO_SANS, StrId::STR_OPEN_DYSLEXIC}, + SettingInfo::DynamicEnum(StrId::STR_FONT_FAMILY, {StrId::STR_BOOKERLY, StrId::STR_NOTO_SANS}, fontFamilyDynamicGetter, fontFamilyDynamicSetter, "fontFamily", StrId::STR_CAT_READER) .withSubcategory(StrId::STR_MENU_READER_FONT), SettingInfo::Enum(StrId::STR_FONT_SIZE, &CrossPointSettings::fontSize, - {StrId::STR_SMALL, StrId::STR_MEDIUM, StrId::STR_LARGE, StrId::STR_X_LARGE}, "fontSize", - StrId::STR_CAT_READER) + {StrId::STR_SMALL, StrId::STR_MEDIUM, StrId::STR_LARGE, StrId::STR_X_LARGE, StrId::STR_TINY}, + "fontSize", StrId::STR_CAT_READER) .withSubmenu(StrId::STR_MENU_READER_FONT_SETTINGS), SettingInfo::Toggle(StrId::STR_TEXT_AA, &CrossPointSettings::textAntiAliasing, "textAntiAliasing", StrId::STR_CAT_READER) diff --git a/src/activities/ActivityResult.h b/src/activities/ActivityResult.h index c535d671..dfb3d769 100644 --- a/src/activities/ActivityResult.h +++ b/src/activities/ActivityResult.h @@ -26,6 +26,7 @@ struct MenuResult { int8_t embeddedStyleOverride = -1; int8_t imageRenderingOverride = -1; int8_t fontFamilyOverride = -1; + std::string sdFontFamilyOverride; int8_t fontSizeOverride = -1; uint8_t textDarkness = 1; uint8_t bionicReadingOverride = 0; diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index bc08abbf..69aa3983 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -190,6 +190,7 @@ void EpubReaderActivity::onEnter() { bookEmbeddedStyleOverride = currentBook.embeddedStyleOverride; bookImageRenderingOverride = currentBook.imageRenderingOverride; bookFontFamilyOverride = currentBook.fontFamilyOverride; + bookSdFontFamilyOverride = currentBook.sdFontFamilyOverride; bookFontSizeOverride = currentBook.fontSizeOverride; bookBionicReadingOverride = (currentBook.bionicReadingOverride >= 0) ? static_cast(currentBook.bionicReadingOverride) @@ -1036,24 +1037,27 @@ void EpubReaderActivity::toggleAutoPageTurn(const uint8_t selectedPageTurnOption void EpubReaderActivity::applyBookReaderOverrides(const int8_t embeddedStyleOverride, const int8_t imageRenderingOverride, const int8_t fontFamilyOverride, + const std::string& sdFontFamilyOverride, const int8_t fontSizeOverride, const bool bionicReadingOverride) { if (!epub) { return; } if (bookEmbeddedStyleOverride == embeddedStyleOverride && bookImageRenderingOverride == imageRenderingOverride && - bookFontFamilyOverride == fontFamilyOverride && bookFontSizeOverride == fontSizeOverride && - bookBionicReadingOverride == bionicReadingOverride) { + bookFontFamilyOverride == fontFamilyOverride && bookSdFontFamilyOverride == sdFontFamilyOverride && + bookFontSizeOverride == fontSizeOverride && bookBionicReadingOverride == bionicReadingOverride) { return; } bookEmbeddedStyleOverride = embeddedStyleOverride; bookImageRenderingOverride = imageRenderingOverride; bookFontFamilyOverride = fontFamilyOverride; + bookSdFontFamilyOverride = sdFontFamilyOverride; bookFontSizeOverride = fontSizeOverride; bookBionicReadingOverride = bionicReadingOverride; RECENT_BOOKS.setReaderOverrides(epub->getPath(), bookEmbeddedStyleOverride, bookImageRenderingOverride, - bookFontFamilyOverride, bookFontSizeOverride, bookBionicReadingOverride); + bookFontFamilyOverride, bookSdFontFamilyOverride, bookFontSizeOverride, + bookBionicReadingOverride); RenderLock lock(*this); if (section) { @@ -1081,9 +1085,7 @@ uint8_t EpubReaderActivity::getEffectiveImageRendering() const { float EpubReaderActivity::getEffectiveReaderLineCompression() const { const uint8_t fontSize = (bookFontSizeOverride >= 0) ? static_cast(bookFontSizeOverride) : SETTINGS.fontSize; const int effectiveFontId = getEffectiveReaderFontId(); - const int bookerlyId = CrossPointSettings::getBuiltinReaderFontId(CrossPointSettings::BOOKERLY, fontSize); const int notosansId = CrossPointSettings::getBuiltinReaderFontId(CrossPointSettings::NOTOSANS, fontSize); - const int opendyslexicId = CrossPointSettings::getBuiltinReaderFontId(CrossPointSettings::OPENDYSLEXIC, fontSize); if (effectiveFontId == notosansId) { switch (SETTINGS.lineSpacing) { @@ -1097,18 +1099,6 @@ float EpubReaderActivity::getEffectiveReaderLineCompression() const { } } - if (effectiveFontId == opendyslexicId) { - switch (SETTINGS.lineSpacing) { - case CrossPointSettings::TIGHT: - return 0.90f; - case CrossPointSettings::NORMAL: - default: - return 0.95f; - case CrossPointSettings::WIDE: - return 1.0f; - } - } - switch (SETTINGS.lineSpacing) { case CrossPointSettings::TIGHT: return 0.95f; @@ -1129,6 +1119,10 @@ int EpubReaderActivity::getEffectiveReaderFontId() const { if (bookFontFamilyOverride >= 0) { return CrossPointSettings::getBuiltinReaderFontId(static_cast(bookFontFamilyOverride), fontSize); } + if (!bookSdFontFamilyOverride.empty()) { + const int id = resolveSdCardFontId(bookSdFontFamilyOverride.c_str(), fontSize); + if (id != 0) return id; + } // No override: defer to global resolution (which honors SD card font selection). // We synthesize a temporary lookup using the override fontSize if it's set; otherwise // SETTINGS.getReaderFontId() is the canonical answer. @@ -1747,59 +1741,25 @@ bool EpubReaderActivity::drawCurrentPageToBuffer(const std::string& filePath, Gf // Load or rebuild the section cache. Rebuilding is needed when the cache is missing or stale // (e.g. after a firmware update). A no-op popup callback avoids any UI during sleep preparation. const RecentBook currentBook = RECENT_BOOKS.getBookByPath(filePath); + const bool hasLocalSdOverride = !currentBook.sdFontFamilyOverride.empty(); const uint8_t effectiveFontFamily = currentBook.fontFamilyOverride >= 0 ? static_cast(currentBook.fontFamilyOverride) : SETTINGS.fontFamily; const uint8_t effectiveFontSize = currentBook.fontSizeOverride >= 0 ? static_cast(currentBook.fontSizeOverride) : SETTINGS.fontSize; - auto getEffectiveFontId = [&](uint8_t family, uint8_t size) { - switch (family) { - case CrossPointSettings::NOTOSANS: - switch (size) { - case CrossPointSettings::SMALL: - return NOTOSANS_12_FONT_ID; - case CrossPointSettings::LARGE: - return NOTOSANS_16_FONT_ID; - case CrossPointSettings::EXTRA_LARGE: - return NOTOSANS_18_FONT_ID; - case CrossPointSettings::MEDIUM: - default: - return NOTOSANS_14_FONT_ID; - } - case CrossPointSettings::OPENDYSLEXIC: - switch (size) { - case CrossPointSettings::SMALL: - return OPENDYSLEXIC_8_FONT_ID; - case CrossPointSettings::LARGE: - return OPENDYSLEXIC_12_FONT_ID; - case CrossPointSettings::EXTRA_LARGE: - return OPENDYSLEXIC_14_FONT_ID; - case CrossPointSettings::MEDIUM: - default: - return OPENDYSLEXIC_10_FONT_ID; - } - case CrossPointSettings::BOOKERLY: - default: - switch (size) { - case CrossPointSettings::SMALL: - return BOOKERLY_12_FONT_ID; - case CrossPointSettings::LARGE: - return BOOKERLY_16_FONT_ID; - case CrossPointSettings::EXTRA_LARGE: - return BOOKERLY_18_FONT_ID; - case CrossPointSettings::MEDIUM: - default: - return BOOKERLY_14_FONT_ID; - } - } - }; - - const int effectiveFontId = getEffectiveFontId(effectiveFontFamily, effectiveFontSize); + int effectiveFontId = 0; + if (hasLocalSdOverride) { + effectiveFontId = resolveSdCardFontId(currentBook.sdFontFamilyOverride.c_str(), effectiveFontSize); + } + if (effectiveFontId == 0 && currentBook.fontFamilyOverride >= 0) { + effectiveFontId = CrossPointSettings::getBuiltinReaderFontId(effectiveFontFamily, effectiveFontSize); + } + if (effectiveFontId == 0) { + effectiveFontId = SETTINGS.getReaderFontId(); + } const auto getEffectiveLineCompression = [&](int fontId) { const int notosansId = CrossPointSettings::getBuiltinReaderFontId(CrossPointSettings::NOTOSANS, effectiveFontSize); - const int opendyslexicId = - CrossPointSettings::getBuiltinReaderFontId(CrossPointSettings::OPENDYSLEXIC, effectiveFontSize); - if (fontId == notosansId || fontId == opendyslexicId) { + if (fontId == notosansId) { switch (SETTINGS.lineSpacing) { case CrossPointSettings::TIGHT: return 0.90f; @@ -1824,13 +1784,12 @@ bool EpubReaderActivity::drawCurrentPageToBuffer(const std::string& filePath, Gf const float effectiveLineCompression = getEffectiveLineCompression(effectiveFontId); auto section = std::make_unique
(epub, spineIndex, renderer); - if (!section->loadSectionFile(getEffectiveFontId(effectiveFontFamily, effectiveFontSize), effectiveLineCompression, - SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, - viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, - static_cast(SETTINGS.bionicReading), SETTINGS.imageRendering)) { + if (!section->loadSectionFile(effectiveFontId, effectiveLineCompression, SETTINGS.extraParagraphSpacing, + SETTINGS.paragraphAlignment, viewportWidth, viewportHeight, SETTINGS.hyphenationEnabled, + SETTINGS.embeddedStyle, static_cast(SETTINGS.bionicReading), + SETTINGS.imageRendering)) { LOG_DBG("SLP", "EPUB: section cache not found for spine %d, rebuilding", spineIndex); - if (!section->createSectionFile(getEffectiveFontId(effectiveFontFamily, effectiveFontSize), - effectiveLineCompression, SETTINGS.extraParagraphSpacing, + if (!section->createSectionFile(effectiveFontId, effectiveLineCompression, SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, static_cast(SETTINGS.bionicReading), SETTINGS.imageRendering)) { @@ -1849,7 +1808,7 @@ bool EpubReaderActivity::drawCurrentPageToBuffer(const std::string& filePath, Gf } renderer.clearScreen(); - page->render(renderer, getEffectiveFontId(effectiveFontFamily, effectiveFontSize), marginLeft, marginTop); + page->render(renderer, effectiveFontId, marginLeft, marginTop); // No displayBuffer call — caller (SleepActivity) handles that after compositing the overlay return true; } @@ -1870,15 +1829,16 @@ void EpubReaderActivity::openReaderMenu() { std::make_unique( renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent, SETTINGS.orientation, !currentPageFootnotes.empty(), bookEmbeddedStyleOverride, bookImageRenderingOverride, bookFontFamilyOverride, - bookFontSizeOverride, SETTINGS.textDarkness, bookBionicReadingOverride, !bookmarkStore.isEmpty(), - isCurrentPageStarred), + bookSdFontFamilyOverride, bookFontSizeOverride, SETTINGS.textDarkness, bookBionicReadingOverride, + !bookmarkStore.isEmpty(), isCurrentPageStarred), [this](const ActivityResult& result) { const auto& menu = std::get(result.data); applyOrientation(menu.orientation); applyTextDarkness(menu.textDarkness); toggleAutoPageTurn(menu.pageTurnOption); applyBookReaderOverrides(menu.embeddedStyleOverride, menu.imageRenderingOverride, menu.fontFamilyOverride, - menu.fontSizeOverride, static_cast(menu.bionicReadingOverride)); + menu.sdFontFamilyOverride, menu.fontSizeOverride, + static_cast(menu.bionicReadingOverride)); if (!result.isCancelled) { onReaderMenuConfirm(static_cast(menu.action)); } @@ -2010,7 +1970,7 @@ void EpubReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION case BA::BTN_TOGGLE_BIONIC_READING: if (epub) { applyBookReaderOverrides(bookEmbeddedStyleOverride, bookImageRenderingOverride, bookFontFamilyOverride, - bookFontSizeOverride, !bookBionicReadingOverride); + bookSdFontFamilyOverride, bookFontSizeOverride, !bookBionicReadingOverride); requestUpdate(); } break; diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index 37429356..9186d801 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -134,6 +134,7 @@ class EpubReaderActivity final : public Activity { int8_t bookEmbeddedStyleOverride = -1; int8_t bookImageRenderingOverride = -1; int8_t bookFontFamilyOverride = -1; + std::string bookSdFontFamilyOverride; int8_t bookFontSizeOverride = -1; bool bookBionicReadingOverride = false; @@ -177,7 +178,8 @@ class EpubReaderActivity final : public Activity { void applyTextDarkness(uint8_t textDarkness); void toggleAutoPageTurn(uint8_t selectedPageTurnOption); void applyBookReaderOverrides(int8_t embeddedStyleOverride, int8_t imageRenderingOverride, int8_t fontFamilyOverride, - int8_t fontSizeOverride, bool bionicReadingOverride); + const std::string& sdFontFamilyOverride, int8_t fontSizeOverride, + bool bionicReadingOverride); void openReaderMenu(); bool getEffectiveEmbeddedStyle() const; uint8_t getEffectiveImageRendering() const; diff --git a/src/activities/reader/EpubReaderMenuActivity.cpp b/src/activities/reader/EpubReaderMenuActivity.cpp index 44524993..aa6b7549 100644 --- a/src/activities/reader/EpubReaderMenuActivity.cpp +++ b/src/activities/reader/EpubReaderMenuActivity.cpp @@ -19,9 +19,8 @@ std::string defaultFontFamilyLabel(const SettingInfo& item) { if (SETTINGS.sdFontFamilyName[0] != '\0') { return std::string(SETTINGS.sdFontFamilyName); } - // Built-in: enumValues[0] is STR_DEFAULT_VALUE, [1..3] are the three families - // in the same order as CrossPointSettings::FONT_FAMILY (BOOKERLY, NOTOSANS, - // OPENDYSLEXIC). + // Built-in: enumValues[0] is STR_DEFAULT_VALUE, [1..] are built-in families + // in CrossPointSettings::FONT_FAMILY order. const auto idx = static_cast(SETTINGS.fontFamily + 1); if (idx < item.enumValues.size()) { return I18N.get(item.enumValues[idx]); @@ -34,14 +33,16 @@ EpubReaderMenuActivity::EpubReaderMenuActivity( GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& title, const int currentPage, const int totalPages, const int bookProgressPercent, const uint8_t currentOrientation, const bool hasFootnotes, const int8_t initialEmbeddedStyleOverride, const int8_t initialImageRenderingOverride, - const int8_t initialFontFamilyOverride, const int8_t initialFontSizeOverride, const uint8_t initialTextDarkness, - const bool initialBionicReadingOverride, const bool hasStarredPages, const bool isCurrentPageStarred) + const int8_t initialFontFamilyOverride, const std::string& initialSdFontFamilyOverride, + const int8_t initialFontSizeOverride, const uint8_t initialTextDarkness, const bool initialBionicReadingOverride, + const bool hasStarredPages, const bool isCurrentPageStarred) : MenuListActivity("EpubReaderMenu", renderer, mappedInput), currentPageStarred(isCurrentPageStarred), pendingOrientation(currentOrientation), pendingEmbeddedStyleOverride(initialEmbeddedStyleOverride), pendingImageRenderingOverride(initialImageRenderingOverride), pendingFontFamilyOverride(initialFontFamilyOverride), + pendingSdFontFamilyOverride(initialSdFontFamilyOverride), pendingFontSizeOverride(initialFontSizeOverride), pendingTextDarkness(initialTextDarkness), pendingBionicReading(initialBionicReadingOverride), @@ -119,35 +120,79 @@ void EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes, bool hasStarredPa }) .withSubmenu(StrId::STR_READER_OVERRIDES)); - // Reader font family: cycles default(-1) -> Bookerly(0) -> Noto Sans(1) -> Open Dyslexic(2) - menuItems.push_back( - SettingInfo::DynamicEnumCtx( - StrId::STR_FONT_FAMILY, - {StrId::STR_DEFAULT_VALUE, StrId::STR_BOOKERLY, StrId::STR_NOTO_SANS, StrId::STR_OPEN_DYSLEXIC}, self, - [](const void* ctx) -> uint8_t { - const auto* s = static_cast(ctx); - return (s->pendingFontFamilyOverride < 0) ? 0 : static_cast(s->pendingFontFamilyOverride + 1); - }, - [](void* ctx, uint8_t v) { - auto* s = static_cast(ctx); - s->pendingFontFamilyOverride = (v == 0) ? -1 : static_cast(v - 1); - }) - .withSubmenu(StrId::STR_READER_OVERRIDES)); + // Reader font family: default + built-ins + discovered SD families. + { + std::vector values = {StrId::STR_DEFAULT_VALUE, StrId::STR_BOOKERLY, StrId::STR_NOTO_SANS}; + const auto& families = sdFontSystem.registry().getFamilies(); + values.insert(values.end(), families.size(), StrId::STR_NONE_OPT); - // Reader font size: cycles default(-1) -> Small(0) -> Medium(1) -> Large(2) -> X Large(3) - menuItems.push_back( - SettingInfo::DynamicEnumCtx( - StrId::STR_FONT_SIZE, - {StrId::STR_DEFAULT_VALUE, StrId::STR_SMALL, StrId::STR_MEDIUM, StrId::STR_LARGE, StrId::STR_X_LARGE}, self, - [](const void* ctx) -> uint8_t { - const auto* s = static_cast(ctx); - return (s->pendingFontSizeOverride < 0) ? 0 : static_cast(s->pendingFontSizeOverride + 1); - }, - [](void* ctx, uint8_t v) { - auto* s = static_cast(ctx); - s->pendingFontSizeOverride = (v == 0) ? -1 : static_cast(v - 1); - }) - .withSubmenu(StrId::STR_READER_OVERRIDES)); + auto familySetting = SettingInfo::DynamicEnumCtx( + StrId::STR_FONT_FAMILY, std::move(values), self, + [](const void* ctx) -> uint8_t { + const auto* s = static_cast(ctx); + if (s->pendingFontFamilyOverride >= 0) { + return static_cast(s->pendingFontFamilyOverride + 1); + } + if (!s->pendingSdFontFamilyOverride.empty()) { + const auto& fs = sdFontSystem.registry().getFamilies(); + for (size_t i = 0; i < fs.size(); i++) { + if (fs[i].name == s->pendingSdFontFamilyOverride) { + return static_cast(CrossPointSettings::BUILTIN_FONT_COUNT + 1 + i); + } + } + } + return 0; + }, + [](void* ctx, uint8_t v) { + auto* s = static_cast(ctx); + if (v == 0) { + s->pendingFontFamilyOverride = -1; + s->pendingSdFontFamilyOverride.clear(); + return; + } + + const uint8_t builtinCount = CrossPointSettings::BUILTIN_FONT_COUNT; + if (v <= builtinCount) { + s->pendingFontFamilyOverride = static_cast(v - 1); + s->pendingSdFontFamilyOverride.clear(); + return; + } + + const size_t sdIdx = static_cast(v - (builtinCount + 1)); + const auto& fs = sdFontSystem.registry().getFamilies(); + s->pendingFontFamilyOverride = -1; + if (sdIdx < fs.size()) { + s->pendingSdFontFamilyOverride = fs[sdIdx].name; + } else { + s->pendingSdFontFamilyOverride.clear(); + } + }) + .withSubmenu(StrId::STR_READER_OVERRIDES); + + familySetting.enumLabels = {tr(STR_DEFAULT_VALUE), tr(STR_BOOKERLY), tr(STR_NOTO_SANS)}; + for (const auto& fam : families) { + familySetting.enumLabels.push_back(fam.name); + } + menuItems.push_back(std::move(familySetting)); + } + + // Reader font size: cycles default(-1) -> Small(0) -> Medium(1) -> Large(2) -> X Large(3) -> Tiny(4) + menuItems.push_back(SettingInfo::DynamicEnumCtx( + StrId::STR_FONT_SIZE, + {StrId::STR_DEFAULT_VALUE, StrId::STR_SMALL, StrId::STR_MEDIUM, StrId::STR_LARGE, + StrId::STR_X_LARGE, StrId::STR_TINY}, + self, + [](const void* ctx) -> uint8_t { + const auto* s = static_cast(ctx); + return (s->pendingFontSizeOverride < 0) + ? 0 + : static_cast(s->pendingFontSizeOverride + 1); + }, + [](void* ctx, uint8_t v) { + auto* s = static_cast(ctx); + s->pendingFontSizeOverride = (v == 0) ? -1 : static_cast(v - 1); + }) + .withSubmenu(StrId::STR_READER_OVERRIDES)); // Text darkness: straightforward 0-3 cycle menuItems.push_back( @@ -248,7 +293,8 @@ EpubReaderMenuActivity::MenuAction EpubReaderMenuActivity::actionForSettingActio void EpubReaderMenuActivity::finishWithAction(MenuAction action) { setResult(MenuResult{static_cast(action), -1, pendingOrientation, selectedPageTurnOption, pendingEmbeddedStyleOverride, pendingImageRenderingOverride, pendingFontFamilyOverride, - pendingFontSizeOverride, pendingTextDarkness, static_cast(pendingBionicReading)}); + pendingSdFontFamilyOverride, pendingFontSizeOverride, pendingTextDarkness, + static_cast(pendingBionicReading)}); finish(); } @@ -280,6 +326,7 @@ void EpubReaderMenuActivity::onBackPressed() { pendingEmbeddedStyleOverride, pendingImageRenderingOverride, pendingFontFamilyOverride, + pendingSdFontFamilyOverride, pendingFontSizeOverride, pendingTextDarkness, static_cast(pendingBionicReading)}; @@ -319,7 +366,7 @@ std::string EpubReaderMenuActivity::getItemValueString(int index) const { return std::string(tr(STR_DEFAULT_VALUE)) + " (" + I18N.get(item.enumValues[defaultIndex]) + ")"; } } - if (item.nameId == StrId::STR_FONT_FAMILY && pendingFontFamilyOverride < 0) { + if (item.nameId == StrId::STR_FONT_FAMILY && pendingFontFamilyOverride < 0 && pendingSdFontFamilyOverride.empty()) { const auto label = defaultFontFamilyLabel(item); if (!label.empty()) { return std::string(tr(STR_DEFAULT_VALUE)) + " (" + label + ")"; @@ -353,7 +400,7 @@ void EpubReaderMenuActivity::openSubmenu(const SettingInfo& submenuEntry) { return std::string(tr(STR_DEFAULT_VALUE)) + " (" + I18N.get(item.enumValues[valueIndex]) + ")"; } } - if (item.nameId == StrId::STR_FONT_FAMILY && pendingFontFamilyOverride < 0) { + if (item.nameId == StrId::STR_FONT_FAMILY && pendingFontFamilyOverride < 0 && pendingSdFontFamilyOverride.empty()) { const auto label = defaultFontFamilyLabel(item); if (!label.empty()) { return std::string(tr(STR_DEFAULT_VALUE)) + " (" + label + ")"; diff --git a/src/activities/reader/EpubReaderMenuActivity.h b/src/activities/reader/EpubReaderMenuActivity.h index f119b8d3..0a2f0bfc 100644 --- a/src/activities/reader/EpubReaderMenuActivity.h +++ b/src/activities/reader/EpubReaderMenuActivity.h @@ -36,7 +36,8 @@ class EpubReaderMenuActivity final : public MenuListActivity { const int currentPage, const int totalPages, const int bookProgressPercent, const uint8_t currentOrientation, const bool hasFootnotes, const int8_t initialEmbeddedStyleOverride, const int8_t initialImageRenderingOverride, - const int8_t initialFontFamilyOverride, const int8_t initialFontSizeOverride, + const int8_t initialFontFamilyOverride, + const std::string& initialSdFontFamilyOverride, const int8_t initialFontSizeOverride, const uint8_t initialTextDarkness, const bool initialBionicReadingOverride, const bool hasStarredPages, const bool isCurrentPageStarred); @@ -67,6 +68,7 @@ class EpubReaderMenuActivity final : public MenuListActivity { int8_t pendingEmbeddedStyleOverride = -1; int8_t pendingImageRenderingOverride = -1; int8_t pendingFontFamilyOverride = -1; + std::string pendingSdFontFamilyOverride; int8_t pendingFontSizeOverride = -1; uint8_t pendingTextDarkness = 1; bool pendingBionicReading = false; diff --git a/src/fontIds.h b/src/fontIds.h index a8211db1..c3e04b29 100644 --- a/src/fontIds.h +++ b/src/fontIds.h @@ -1,18 +1,16 @@ // The contents of this file are generated by ./lib/EpdFont/scripts/build-font-ids.sh #pragma once -#define BOOKERLY_12_FONT_ID (-1905494168) -#define BOOKERLY_14_FONT_ID (1233852315) -#define BOOKERLY_16_FONT_ID (1588566790) -#define BOOKERLY_18_FONT_ID (681638548) -#define NOTOSANS_12_FONT_ID (-1559651934) -#define NOTOSANS_14_FONT_ID (-1014561631) -#define NOTOSANS_16_FONT_ID (-1422711852) -#define NOTOSANS_18_FONT_ID (1237754772) -#define OPENDYSLEXIC_8_FONT_ID (1331369208) -#define OPENDYSLEXIC_10_FONT_ID (-1374689004) -#define OPENDYSLEXIC_12_FONT_ID (-795539541) -#define OPENDYSLEXIC_14_FONT_ID (-1676627620) -#define UI_10_FONT_ID (-1246724383) -#define UI_12_FONT_ID (-359249323) -#define SMALL_FONT_ID (1073217904) +#define BOOKERLY_10_FONT_ID (825122367) +#define BOOKERLY_12_FONT_ID (-1084788859) +#define BOOKERLY_14_FONT_ID (-1298909628) +#define BOOKERLY_16_FONT_ID (-1580381194) +#define BOOKERLY_18_FONT_ID (1499238407) +#define NOTOSANS_10_FONT_ID (1337583050) +#define NOTOSANS_12_FONT_ID (-846830324) +#define NOTOSANS_14_FONT_ID (1528995761) +#define NOTOSANS_16_FONT_ID (-1135152666) +#define NOTOSANS_18_FONT_ID (-1632292195) +#define UI_10_FONT_ID (1883578066) +#define UI_12_FONT_ID (376639728) +#define SMALL_FONT_ID (-1860378437) diff --git a/src/main.cpp b/src/main.cpp index d84a64e4..2bd5ac83 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -52,6 +52,12 @@ EpdFont bookerly14BoldItalicFont(&bookerly_14_bolditalic); EpdFontFamily bookerly14FontFamily(&bookerly14RegularFont, &bookerly14BoldFont, &bookerly14ItalicFont, &bookerly14BoldItalicFont); #ifndef OMIT_FONTS +EpdFont bookerly10RegularFont(&bookerly_10_regular); +EpdFont bookerly10BoldFont(&bookerly_10_bold); +EpdFont bookerly10ItalicFont(&bookerly_10_italic); +EpdFont bookerly10BoldItalicFont(&bookerly_10_bolditalic); +EpdFontFamily bookerly10FontFamily(&bookerly10RegularFont, &bookerly10BoldFont, &bookerly10ItalicFont, + &bookerly10BoldItalicFont); EpdFont bookerly12RegularFont(&bookerly_12_regular); EpdFont bookerly12BoldFont(&bookerly_12_bold); EpdFont bookerly12ItalicFont(&bookerly_12_italic); @@ -71,6 +77,12 @@ EpdFont bookerly18BoldItalicFont(&bookerly_18_bolditalic); EpdFontFamily bookerly18FontFamily(&bookerly18RegularFont, &bookerly18BoldFont, &bookerly18ItalicFont, &bookerly18BoldItalicFont); +EpdFont notosans10RegularFont(¬osans_10_regular); +EpdFont notosans10BoldFont(¬osans_10_bold); +EpdFont notosans10ItalicFont(¬osans_10_italic); +EpdFont notosans10BoldItalicFont(¬osans_10_bolditalic); +EpdFontFamily notosans10FontFamily(¬osans10RegularFont, ¬osans10BoldFont, ¬osans10ItalicFont, + ¬osans10BoldItalicFont); EpdFont notosans12RegularFont(¬osans_12_regular); EpdFont notosans12BoldFont(¬osans_12_bold); EpdFont notosans12ItalicFont(¬osans_12_italic); @@ -96,41 +108,17 @@ EpdFont notosans18BoldItalicFont(¬osans_18_bolditalic); EpdFontFamily notosans18FontFamily(¬osans18RegularFont, ¬osans18BoldFont, ¬osans18ItalicFont, ¬osans18BoldItalicFont); -EpdFont opendyslexic8RegularFont(&opendyslexic_8_regular); -EpdFont opendyslexic8BoldFont(&opendyslexic_8_bold); -EpdFont opendyslexic8ItalicFont(&opendyslexic_8_italic); -EpdFont opendyslexic8BoldItalicFont(&opendyslexic_8_bolditalic); -EpdFontFamily opendyslexic8FontFamily(&opendyslexic8RegularFont, &opendyslexic8BoldFont, &opendyslexic8ItalicFont, - &opendyslexic8BoldItalicFont); -EpdFont opendyslexic10RegularFont(&opendyslexic_10_regular); -EpdFont opendyslexic10BoldFont(&opendyslexic_10_bold); -EpdFont opendyslexic10ItalicFont(&opendyslexic_10_italic); -EpdFont opendyslexic10BoldItalicFont(&opendyslexic_10_bolditalic); -EpdFontFamily opendyslexic10FontFamily(&opendyslexic10RegularFont, &opendyslexic10BoldFont, &opendyslexic10ItalicFont, - &opendyslexic10BoldItalicFont); -EpdFont opendyslexic12RegularFont(&opendyslexic_12_regular); -EpdFont opendyslexic12BoldFont(&opendyslexic_12_bold); -EpdFont opendyslexic12ItalicFont(&opendyslexic_12_italic); -EpdFont opendyslexic12BoldItalicFont(&opendyslexic_12_bolditalic); -EpdFontFamily opendyslexic12FontFamily(&opendyslexic12RegularFont, &opendyslexic12BoldFont, &opendyslexic12ItalicFont, - &opendyslexic12BoldItalicFont); -EpdFont opendyslexic14RegularFont(&opendyslexic_14_regular); -EpdFont opendyslexic14BoldFont(&opendyslexic_14_bold); -EpdFont opendyslexic14ItalicFont(&opendyslexic_14_italic); -EpdFont opendyslexic14BoldItalicFont(&opendyslexic_14_bolditalic); -EpdFontFamily opendyslexic14FontFamily(&opendyslexic14RegularFont, &opendyslexic14BoldFont, &opendyslexic14ItalicFont, - &opendyslexic14BoldItalicFont); #endif // OMIT_FONTS EpdFont smallFont(¬osans_8_regular); EpdFontFamily smallFontFamily(&smallFont); -EpdFont ui10RegularFont(&ubuntu_10_regular); -EpdFont ui10BoldFont(&ubuntu_10_bold); +EpdFont ui10RegularFont(&inter_ui_10_regular); +EpdFont ui10BoldFont(&inter_ui_10_bold); EpdFontFamily ui10FontFamily(&ui10RegularFont, &ui10BoldFont); -EpdFont ui12RegularFont(&ubuntu_12_regular); -EpdFont ui12BoldFont(&ubuntu_12_bold); +EpdFont ui12RegularFont(&inter_ui_12_regular); +EpdFont ui12BoldFont(&inter_ui_12_bold); EpdFontFamily ui12FontFamily(&ui12RegularFont, &ui12BoldFont); // Enter deep sleep mode @@ -168,18 +156,16 @@ void setupDisplayAndFonts() { renderer.setFontCacheManager(&fontCacheManager); renderer.insertFont(BOOKERLY_14_FONT_ID, bookerly14FontFamily); #ifndef OMIT_FONTS + renderer.insertFont(BOOKERLY_10_FONT_ID, bookerly10FontFamily); renderer.insertFont(BOOKERLY_12_FONT_ID, bookerly12FontFamily); renderer.insertFont(BOOKERLY_16_FONT_ID, bookerly16FontFamily); renderer.insertFont(BOOKERLY_18_FONT_ID, bookerly18FontFamily); + renderer.insertFont(NOTOSANS_10_FONT_ID, notosans10FontFamily); 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); - 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_FONTS renderer.insertFont(UI_10_FONT_ID, ui10FontFamily); renderer.insertFont(UI_12_FONT_ID, ui12FontFamily); diff --git a/src/network/CrossPointWebServer.cpp b/src/network/CrossPointWebServer.cpp index 9f3b7bc8..f110cf2e 100644 --- a/src/network/CrossPointWebServer.cpp +++ b/src/network/CrossPointWebServer.cpp @@ -27,7 +27,6 @@ #include "html/WelcomePageHtml.generated.h" #include "html/js/jszip_minJs.generated.h" - namespace { // Folders/files to hide from the web interface file browser // Note: Items starting with "." are automatically hidden