Merge pull request #147 from jpirnay/feat-fonts-sd

feat: Add sd fonts (integrate and improve upstream #1327 by adriancaruana)
This commit is contained in:
jpirnay
2026-04-28 19:09:51 +02:00
committed by GitHub
31 changed files with 3603 additions and 124 deletions
+5
View File
@@ -9,6 +9,7 @@
#include "CrossPointState.h"
#include "OpdsServerStore.h"
#include "SdCardFontGlobals.h"
#include "boot_sleep/BootActivity.h"
#include "boot_sleep/SleepActivity.h"
#include "browser/OpdsBookBrowserActivity.h"
@@ -282,6 +283,8 @@ void ActivityManager::goToBrowser() {
}
void ActivityManager::goToReader(std::string path) {
RenderLock lock;
ensureSdFontLoaded();
replaceActivity(std::make_unique<ReaderActivity>(renderer, mappedInput, std::move(path)));
}
@@ -301,6 +304,8 @@ void ActivityManager::goToKOReaderSync() {
void ActivityManager::replaceWithReader(std::string path, ReturnHint hint) {
returnHint = std::move(hint);
hasReturnHint = true;
RenderLock lock;
ensureSdFontLoaded();
replaceActivity(std::make_unique<ReaderActivity>(renderer, mappedInput, std::move(path)));
}
+106 -50
View File
@@ -30,6 +30,7 @@
#include "QrDisplayActivity.h"
#include "ReaderUtils.h"
#include "RecentBooksStore.h"
#include "SdCardFontGlobals.h"
#include "StarredPagesActivity.h"
#include "components/UITheme.h"
#include "fontIds.h"
@@ -1072,49 +1073,68 @@ uint8_t EpubReaderActivity::getEffectiveImageRendering() const {
return SETTINGS.imageRendering;
}
int EpubReaderActivity::getEffectiveReaderFontId() const {
const uint8_t fontFamily =
(bookFontFamilyOverride >= 0) ? static_cast<uint8_t>(bookFontFamilyOverride) : SETTINGS.fontFamily;
float EpubReaderActivity::getEffectiveReaderLineCompression() const {
const uint8_t fontSize = (bookFontSizeOverride >= 0) ? static_cast<uint8_t>(bookFontSizeOverride) : SETTINGS.fontSize;
switch (fontFamily) {
case CrossPointSettings::NOTOSANS:
switch (fontSize) {
case CrossPointSettings::SMALL:
return NOTOSANS_12_FONT_ID;
case CrossPointSettings::MEDIUM:
default:
return NOTOSANS_14_FONT_ID;
case CrossPointSettings::LARGE:
return NOTOSANS_16_FONT_ID;
case CrossPointSettings::EXTRA_LARGE:
return NOTOSANS_18_FONT_ID;
}
case CrossPointSettings::OPENDYSLEXIC:
switch (fontSize) {
case CrossPointSettings::SMALL:
return OPENDYSLEXIC_8_FONT_ID;
case CrossPointSettings::MEDIUM:
default:
return OPENDYSLEXIC_10_FONT_ID;
case CrossPointSettings::LARGE:
return OPENDYSLEXIC_12_FONT_ID;
case CrossPointSettings::EXTRA_LARGE:
return OPENDYSLEXIC_14_FONT_ID;
}
case CrossPointSettings::BOOKERLY:
default:
switch (fontSize) {
case CrossPointSettings::SMALL:
return BOOKERLY_12_FONT_ID;
case CrossPointSettings::MEDIUM:
default:
return BOOKERLY_14_FONT_ID;
case CrossPointSettings::LARGE:
return BOOKERLY_16_FONT_ID;
case CrossPointSettings::EXTRA_LARGE:
return BOOKERLY_18_FONT_ID;
}
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) {
case CrossPointSettings::TIGHT:
return 0.90f;
case CrossPointSettings::NORMAL:
default:
return 0.95f;
case CrossPointSettings::WIDE:
return 1.0f;
}
}
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;
case CrossPointSettings::NORMAL:
default:
return 1.0f;
case CrossPointSettings::WIDE:
return 1.1f;
}
}
int EpubReaderActivity::getEffectiveReaderFontId() const {
// Per-book font override: when set, force a specific BUILT-IN family even if
// an SD card font is the global default. This makes the override predictable
// ("override forces back to a known built-in") and avoids surprising users
// who set the override before they had any SD fonts.
const uint8_t fontSize = (bookFontSizeOverride >= 0) ? static_cast<uint8_t>(bookFontSizeOverride) : SETTINGS.fontSize;
if (bookFontFamilyOverride >= 0) {
return CrossPointSettings::getBuiltinReaderFontId(static_cast<uint8_t>(bookFontFamilyOverride), fontSize);
}
// 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.
if (bookFontSizeOverride >= 0) {
if (SETTINGS.sdFontFamilyName[0] != '\0') {
const int id = resolveSdCardFontId(SETTINGS.sdFontFamilyName, fontSize);
if (id != 0) return id;
}
return CrossPointSettings::getBuiltinReaderFontId(SETTINGS.fontFamily, fontSize);
}
return SETTINGS.getReaderFontId();
}
bool EpubReaderActivity::stepPageState(const bool isForwardTurn) {
@@ -1229,7 +1249,7 @@ void EpubReaderActivity::render(RenderLock&& lock) {
section = std::make_unique<Section>(epub, currentSpineIndex, renderer);
const unsigned long sectionStart = millis();
if (!section->loadSectionFile(getEffectiveReaderFontId(), SETTINGS.getReaderLineCompression(),
if (!section->loadSectionFile(getEffectiveReaderFontId(), getEffectiveReaderLineCompression(),
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering)) {
LOG_DBG("ERS", "Cache not found, building...");
@@ -1246,7 +1266,11 @@ void EpubReaderActivity::render(RenderLock&& lock) {
GUI.fillPopupProgress(renderer, popupRect, progress);
};
if (!section->createSectionFile(getEffectiveReaderFontId(), SETTINGS.getReaderLineCompression(),
// Reset cumulative SD font metadata cache so this section starts fresh.
// Pagination will rebuild only the cps it actually encounters, bounded
// by MAX_PAGE_GLYPHS per style.
renderer.clearSdCardFontAccumulation();
if (!section->createSectionFile(getEffectiveReaderFontId(), getEffectiveReaderLineCompression(),
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering,
progressFn)) {
@@ -1401,14 +1425,16 @@ void EpubReaderActivity::silentIndexNextChapterIfNeeded(const uint16_t viewportW
const uint8_t imageRendering = getEffectiveImageRendering();
Section nextSection(epub, nextSpineIndex, renderer);
if (nextSection.loadSectionFile(getEffectiveReaderFontId(), SETTINGS.getReaderLineCompression(),
if (nextSection.loadSectionFile(getEffectiveReaderFontId(), getEffectiveReaderLineCompression(),
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering)) {
return;
}
LOG_DBG("ERS", "Silently indexing next chapter: %d", nextSpineIndex);
if (!nextSection.createSectionFile(getEffectiveReaderFontId(), SETTINGS.getReaderLineCompression(),
// Reset cumulative SD font metadata cache for the new section.
renderer.clearSdCardFontAccumulation();
if (!nextSection.createSectionFile(getEffectiveReaderFontId(), getEffectiveReaderLineCompression(),
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering)) {
LOG_ERR("ERS", "Failed silent indexing for chapter: %d", nextSpineIndex);
@@ -1759,14 +1785,44 @@ bool EpubReaderActivity::drawCurrentPageToBuffer(const std::string& filePath, Gf
}
};
const int effectiveFontId = getEffectiveFontId(effectiveFontFamily, effectiveFontSize);
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) {
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;
case CrossPointSettings::NORMAL:
default:
return 1.0f;
case CrossPointSettings::WIDE:
return 1.1f;
}
};
const float effectiveLineCompression = getEffectiveLineCompression(effectiveFontId);
auto section = std::make_unique<Section>(epub, spineIndex, renderer);
if (!section->loadSectionFile(getEffectiveFontId(effectiveFontFamily, effectiveFontSize),
SETTINGS.getReaderLineCompression(), SETTINGS.extraParagraphSpacing,
SETTINGS.paragraphAlignment, viewportWidth, viewportHeight, SETTINGS.hyphenationEnabled,
SETTINGS.embeddedStyle, SETTINGS.imageRendering)) {
if (!section->loadSectionFile(getEffectiveFontId(effectiveFontFamily, effectiveFontSize), effectiveLineCompression,
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle,
SETTINGS.imageRendering)) {
LOG_DBG("SLP", "EPUB: section cache not found for spine %d, rebuilding", spineIndex);
if (!section->createSectionFile(getEffectiveFontId(effectiveFontFamily, effectiveFontSize),
SETTINGS.getReaderLineCompression(), SETTINGS.extraParagraphSpacing,
effectiveLineCompression, SETTINGS.extraParagraphSpacing,
SETTINGS.paragraphAlignment, viewportWidth, viewportHeight,
SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, SETTINGS.imageRendering)) {
LOG_ERR("SLP", "EPUB: failed to rebuild section cache for spine %d", spineIndex);
@@ -181,6 +181,7 @@ class EpubReaderActivity final : public Activity {
bool getEffectiveEmbeddedStyle() const;
uint8_t getEffectiveImageRendering() const;
int getEffectiveReaderFontId() const;
float getEffectiveReaderLineCompression() const;
bool stepPageState(bool isForwardTurn);
void pageTurn(bool isForwardTurn);
void runRenderBenchmark();
@@ -5,10 +5,31 @@
#include "KOReaderCredentialStore.h"
#include "MappedInputManager.h"
#include "SdCardFontGlobals.h"
#include "activities/settings/SettingsSubmenuActivity.h"
#include "components/UITheme.h"
#include "fontIds.h"
namespace {
// Returns the localized name of the family currently used as the global default
// for the reader. When the user has selected an SD card font globally, the
// override menu's "Default" label should reflect that family by name even
// though the per-book override list itself is built-in only.
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).
const auto idx = static_cast<size_t>(SETTINGS.fontFamily + 1);
if (idx < item.enumValues.size()) {
return I18N.get(item.enumValues[idx]);
}
return {};
}
} // namespace
EpubReaderMenuActivity::EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
const std::string& title, const int currentPage, const int totalPages,
const int bookProgressPercent, const uint8_t currentOrientation,
@@ -290,9 +311,9 @@ std::string EpubReaderMenuActivity::getItemValueString(int index) const {
}
}
if (item.nameId == StrId::STR_FONT_FAMILY && pendingFontFamilyOverride < 0) {
const auto defaultIndex = static_cast<size_t>(SETTINGS.fontFamily + 1);
if (defaultIndex < item.enumValues.size()) {
return std::string(tr(STR_DEFAULT_VALUE)) + " (" + I18N.get(item.enumValues[defaultIndex]) + ")";
const auto label = defaultFontFamilyLabel(item);
if (!label.empty()) {
return std::string(tr(STR_DEFAULT_VALUE)) + " (" + label + ")";
}
}
if (item.nameId == StrId::STR_FONT_SIZE && pendingFontSizeOverride < 0) {
@@ -324,9 +345,9 @@ void EpubReaderMenuActivity::openSubmenu(const SettingInfo& submenuEntry) {
}
}
if (item.nameId == StrId::STR_FONT_FAMILY && pendingFontFamilyOverride < 0) {
const auto valueIndex = static_cast<size_t>(SETTINGS.fontFamily + 1);
if (valueIndex < item.enumValues.size()) {
return std::string(tr(STR_DEFAULT_VALUE)) + " (" + I18N.get(item.enumValues[valueIndex]) + ")";
const auto label = defaultFontFamilyLabel(item);
if (!label.empty()) {
return std::string(tr(STR_DEFAULT_VALUE)) + " (" + label + ")";
}
}
if (item.nameId == StrId::STR_FONT_SIZE && pendingFontSizeOverride < 0) {
+19 -8
View File
@@ -6,6 +6,8 @@
#include <HalGPIO.h>
#include <Logging.h>
#include <cstring>
#include "CrossPointSettings.h"
#include "MappedInputManager.h"
#include "SettingActionDispatch.h"
@@ -66,14 +68,23 @@ void SettingsActivity::onEnter() {
setting.nameId == StrId::STR_TIMEZONE)) {
continue;
}
if (setting.category == StrId::STR_CAT_DISPLAY) {
addTo(displaySettings, lastDisplaySub, setting);
} else if (setting.category == StrId::STR_CAT_READER) {
addTo(readerSettings, lastReaderSub, setting);
} else if (setting.category == StrId::STR_CAT_CONTROLS) {
addTo(controlsSettings, lastControlsSub, setting);
} else if (setting.category == StrId::STR_CAT_SYSTEM) {
addTo(systemSettings, lastSystemSub, setting);
// Enrich the font-family entry with SD card families discovered at boot.
// The list itself is a namespace-static; we only mutate our local copy here.
SettingInfo enriched = setting;
if (setting.key && std::strcmp(setting.key, "fontFamily") == 0) {
const uint8_t n = fontFamilyOptionCount();
enriched.enumLabels.clear();
enriched.enumLabels.reserve(n);
for (uint8_t i = 0; i < n; i++) enriched.enumLabels.push_back(fontFamilyOptionLabel(i));
}
if (enriched.category == StrId::STR_CAT_DISPLAY) {
addTo(displaySettings, lastDisplaySub, enriched);
} else if (enriched.category == StrId::STR_CAT_READER) {
addTo(readerSettings, lastReaderSub, enriched);
} else if (enriched.category == StrId::STR_CAT_CONTROLS) {
addTo(controlsSettings, lastControlsSub, enriched);
} else if (enriched.category == StrId::STR_CAT_SYSTEM) {
addTo(systemSettings, lastSystemSub, enriched);
}
// Web-only categories (KOReader Sync, OPDS Browser) are skipped for device UI
}