feat: Add per-book letterbox fill override

Introduce BookSettings utility for per-book settings stored in
the book's cache directory (book_settings.bin). Add "Letterbox Fill"
option to the EPUB reader menu that cycles Default/Dithered/Solid/None.
At sleep time, the per-book override is loaded and takes precedence
over the global setting for all book types (EPUB, XTC, TXT).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
cottongin
2026-02-13 16:07:38 -05:00
parent 0c71e0b13f
commit 6aa0b865c2
7 changed files with 167 additions and 8 deletions

View File

@@ -12,6 +12,7 @@
#include "CrossPointSettings.h"
#include "CrossPointState.h"
#include "util/BookSettings.h"
#include "components/UITheme.h"
#include "fontIds.h"
#include "images/Logo120.h"
@@ -450,7 +451,8 @@ void SleepActivity::renderDefaultSleepScreen() const {
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
}
void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap, const std::string& edgeCachePath) const {
void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap, const std::string& edgeCachePath,
uint8_t fillModeOverride) const {
int x, y;
const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();
@@ -494,8 +496,11 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap, const std::str
const float scale =
std::min(static_cast<float>(pageWidth) / effectiveWidth, static_cast<float>(pageHeight) / effectiveHeight);
// Determine letterbox fill settings
const uint8_t fillMode = SETTINGS.sleepScreenLetterboxFill;
// Determine letterbox fill settings (per-book override takes precedence)
const uint8_t fillMode = (fillModeOverride != BookSettings::USE_GLOBAL &&
fillModeOverride < CrossPointSettings::SLEEP_SCREEN_LETTERBOX_FILL_COUNT)
? fillModeOverride
: SETTINGS.sleepScreenLetterboxFill;
const bool wantFill = (fillMode != CrossPointSettings::SLEEP_SCREEN_LETTERBOX_FILL::LETTERBOX_NONE);
static const char* fillModeNames[] = {"dithered", "solid", "none"};
@@ -582,6 +587,7 @@ void SleepActivity::renderCoverSleepScreen() const {
}
std::string coverBmpPath;
std::string bookCachePath;
bool cropped = SETTINGS.sleepScreenCoverMode == CrossPointSettings::SLEEP_SCREEN_COVER_MODE::CROP;
// Check if the current book is XTC, TXT, or EPUB
@@ -600,6 +606,7 @@ void SleepActivity::renderCoverSleepScreen() const {
}
coverBmpPath = lastXtc.getCoverBmpPath();
bookCachePath = lastXtc.getCachePath();
} else if (StringUtils::checkFileExtension(APP_STATE.openEpubPath, ".txt")) {
// Handle TXT file - looks for cover image in the same folder
Txt lastTxt(APP_STATE.openEpubPath, "/.crosspoint");
@@ -614,6 +621,7 @@ void SleepActivity::renderCoverSleepScreen() const {
}
coverBmpPath = lastTxt.getCoverBmpPath();
bookCachePath = lastTxt.getCachePath();
} else if (StringUtils::checkFileExtension(APP_STATE.openEpubPath, ".epub")) {
// Handle EPUB file
Epub lastEpub(APP_STATE.openEpubPath, "/.crosspoint");
@@ -629,10 +637,18 @@ void SleepActivity::renderCoverSleepScreen() const {
}
coverBmpPath = lastEpub.getCoverBmpPath(cropped);
bookCachePath = lastEpub.getCachePath();
} else {
return (this->*renderNoCoverSleepScreen)();
}
// Load per-book letterbox fill override (falls back to global if not set)
uint8_t fillModeOverride = BookSettings::USE_GLOBAL;
if (!bookCachePath.empty()) {
auto bookSettings = BookSettings::load(bookCachePath);
fillModeOverride = bookSettings.letterboxFillOverride;
}
FsFile file;
if (Storage.openFileForRead("SLP", coverBmpPath, file)) {
Bitmap bitmap(file);
@@ -644,7 +660,7 @@ void SleepActivity::renderCoverSleepScreen() const {
if (dotPos != std::string::npos) {
edgeCachePath = coverBmpPath.substr(0, dotPos) + "_edges.bin";
}
renderBitmapSleepScreen(bitmap, edgeCachePath);
renderBitmapSleepScreen(bitmap, edgeCachePath, fillModeOverride);
return;
}
}