Backup: Stable state with EPUB reader fixes (Freeze, OOM, Speed, State, Tooling)
This commit is contained in:
@@ -14,7 +14,7 @@ CrossPointSettings CrossPointSettings::instance;
|
||||
namespace {
|
||||
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
||||
// Increment this when adding new persisted settings fields
|
||||
constexpr uint8_t SETTINGS_COUNT = 18;
|
||||
constexpr uint8_t SETTINGS_COUNT = 20;
|
||||
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
|
||||
} // namespace
|
||||
|
||||
@@ -48,6 +48,8 @@ bool CrossPointSettings::saveToFile() const {
|
||||
serialization::writePod(outputFile, textAntiAliasing);
|
||||
serialization::writePod(outputFile, hideBatteryPercentage);
|
||||
serialization::writePod(outputFile, longPressChapterSkip);
|
||||
serialization::writeString(outputFile, std::string(customFontFamily));
|
||||
serialization::writePod(outputFile, customFontSize);
|
||||
outputFile.close();
|
||||
|
||||
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
|
||||
@@ -116,6 +118,15 @@ bool CrossPointSettings::loadFromFile() {
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
serialization::readPod(inputFile, longPressChapterSkip);
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
{
|
||||
std::string fontStr;
|
||||
serialization::readString(inputFile, fontStr);
|
||||
strncpy(customFontFamily, fontStr.c_str(), sizeof(customFontFamily) - 1);
|
||||
customFontFamily[sizeof(customFontFamily) - 1] = '\0';
|
||||
}
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
serialization::readPod(inputFile, customFontSize);
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
} while (false);
|
||||
|
||||
inputFile.close();
|
||||
@@ -191,7 +202,33 @@ int CrossPointSettings::getRefreshFrequency() const {
|
||||
}
|
||||
}
|
||||
|
||||
#include <EpdFontLoader.h>
|
||||
|
||||
int CrossPointSettings::getReaderFontId() const {
|
||||
if (fontFamily == FONT_CUSTOM) {
|
||||
uint8_t targetSize = customFontSize;
|
||||
if (targetSize == 0) {
|
||||
switch (fontSize) {
|
||||
case SMALL:
|
||||
targetSize = 12;
|
||||
break;
|
||||
case MEDIUM:
|
||||
default:
|
||||
targetSize = 14;
|
||||
break;
|
||||
case LARGE:
|
||||
targetSize = 16;
|
||||
break;
|
||||
case EXTRA_LARGE:
|
||||
targetSize = 18;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int id = EpdFontLoader::getBestFontId(customFontFamily, targetSize);
|
||||
if (id != -1) return id;
|
||||
// Fallback if custom font not found
|
||||
}
|
||||
|
||||
switch (fontFamily) {
|
||||
case BOOKERLY:
|
||||
default:
|
||||
|
||||
@@ -40,7 +40,7 @@ class CrossPointSettings {
|
||||
enum SIDE_BUTTON_LAYOUT { PREV_NEXT = 0, NEXT_PREV = 1 };
|
||||
|
||||
// Font family options
|
||||
enum FONT_FAMILY { BOOKERLY = 0, NOTOSANS = 1, OPENDYSLEXIC = 2 };
|
||||
enum FONT_FAMILY { BOOKERLY = 0, NOTOSANS = 1, OPENDYSLEXIC = 2, FONT_CUSTOM = 3 };
|
||||
// Font size options
|
||||
enum FONT_SIZE { SMALL = 0, MEDIUM = 1, LARGE = 2, EXTRA_LARGE = 3 };
|
||||
enum LINE_COMPRESSION { TIGHT = 0, NORMAL = 1, WIDE = 2 };
|
||||
@@ -77,7 +77,9 @@ class CrossPointSettings {
|
||||
uint8_t sideButtonLayout = PREV_NEXT;
|
||||
// Reader font settings
|
||||
uint8_t fontFamily = BOOKERLY;
|
||||
char customFontFamily[64] = "";
|
||||
uint8_t fontSize = MEDIUM;
|
||||
uint8_t customFontSize = 0; // 0 means use enum mapping
|
||||
uint8_t lineSpacing = NORMAL;
|
||||
uint8_t paragraphAlignment = JUSTIFIED;
|
||||
// Auto-sleep timeout setting (default 10 minutes)
|
||||
|
||||
@@ -38,7 +38,9 @@ bool CrossPointState::loadFromFile() {
|
||||
return false;
|
||||
}
|
||||
|
||||
Serial.printf("[%lu] [CPS] Reading OpenEpubPath\n", millis());
|
||||
serialization::readString(inputFile, openEpubPath);
|
||||
Serial.printf("[%lu] [CPS] Read OpenEpubPath: %s\n", millis(), openEpubPath.c_str());
|
||||
if (version >= 2) {
|
||||
serialization::readPod(inputFile, lastSleepImage);
|
||||
} else {
|
||||
|
||||
@@ -12,9 +12,15 @@ void BootActivity::onEnter() {
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
|
||||
renderer.clearScreen();
|
||||
Serial.println("[Boot] clearScreen done");
|
||||
renderer.drawImage(CrossLarge, (pageWidth + 128) / 2, (pageHeight - 128) / 2, 128, 128);
|
||||
Serial.println("[Boot] drawImage done");
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, EpdFontFamily::BOLD);
|
||||
Serial.println("[Boot] CrossPoint text done");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "BOOTING");
|
||||
Serial.println("[Boot] BOOTING text done");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, CROSSPOINT_VERSION);
|
||||
Serial.println("[Boot] Version text done");
|
||||
renderer.displayBuffer();
|
||||
Serial.println("[Boot] displayBuffer done");
|
||||
}
|
||||
|
||||
@@ -59,7 +59,14 @@ void EpubReaderActivity::onEnter() {
|
||||
if (f.read(data, 4) == 4) {
|
||||
currentSpineIndex = data[0] + (data[1] << 8);
|
||||
nextPageNumber = data[2] + (data[3] << 8);
|
||||
Serial.printf("[%lu] [ERS] Loaded cache: %d, %d\n", millis(), currentSpineIndex, nextPageNumber);
|
||||
|
||||
// Validation: If loaded index is invalid, reset to 0
|
||||
if (currentSpineIndex >= epub->getSpineItemsCount()) {
|
||||
Serial.printf("[%lu] [ERS] Loaded invalid spine index %d (max %d), resetting\n", millis(), currentSpineIndex,
|
||||
epub->getSpineItemsCount());
|
||||
currentSpineIndex = 0;
|
||||
nextPageNumber = 0;
|
||||
}
|
||||
}
|
||||
f.close();
|
||||
}
|
||||
@@ -69,8 +76,6 @@ void EpubReaderActivity::onEnter() {
|
||||
int textSpineIndex = epub->getSpineIndexForTextReference();
|
||||
if (textSpineIndex != 0) {
|
||||
currentSpineIndex = textSpineIndex;
|
||||
Serial.printf("[%lu] [ERS] Opened for first time, navigating to text reference at index %d\n", millis(),
|
||||
textSpineIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +265,7 @@ void EpubReaderActivity::renderScreen() {
|
||||
|
||||
if (!section) {
|
||||
const auto filepath = epub->getSpineItem(currentSpineIndex).href;
|
||||
Serial.printf("[%lu] [ERS] Loading file: %s, index: %d\n", millis(), filepath.c_str(), currentSpineIndex);
|
||||
|
||||
section = std::unique_ptr<Section>(new Section(epub, currentSpineIndex, renderer));
|
||||
|
||||
const uint16_t viewportWidth = renderer.getScreenWidth() - orientedMarginLeft - orientedMarginRight;
|
||||
@@ -269,8 +274,6 @@ void EpubReaderActivity::renderScreen() {
|
||||
if (!section->loadSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(),
|
||||
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
|
||||
viewportHeight)) {
|
||||
Serial.printf("[%lu] [ERS] Cache not found, building...\n", millis());
|
||||
|
||||
// Progress bar dimensions
|
||||
constexpr int barWidth = 200;
|
||||
constexpr int barHeight = 10;
|
||||
@@ -319,7 +322,6 @@ void EpubReaderActivity::renderScreen() {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Serial.printf("[%lu] [ERS] Cache found, skipping build...\n", millis());
|
||||
}
|
||||
|
||||
if (nextPageNumber == UINT16_MAX) {
|
||||
@@ -353,11 +355,17 @@ void EpubReaderActivity::renderScreen() {
|
||||
Serial.printf("[%lu] [ERS] Failed to load page from SD - clearing section cache\n", millis());
|
||||
section->clearCache();
|
||||
section.reset();
|
||||
return renderScreen();
|
||||
|
||||
// Prevent infinite recursion. If load fails, show error.
|
||||
renderer.clearScreen();
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Error loading page", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 330, "File system error or corruption", true);
|
||||
renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
const auto start = millis();
|
||||
renderContents(std::move(p), orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
||||
Serial.printf("[%lu] [ERS] Rendered page in %dms\n", millis(), millis() - start);
|
||||
}
|
||||
|
||||
FsFile f;
|
||||
@@ -386,11 +394,11 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
|
||||
}
|
||||
|
||||
// Save bw buffer to reset buffer state after grayscale data sync
|
||||
renderer.storeBwBuffer();
|
||||
bool bufferStored = renderer.storeBwBuffer();
|
||||
|
||||
// grayscale rendering
|
||||
// TODO: Only do this if font supports it
|
||||
if (SETTINGS.textAntiAliasing) {
|
||||
// Only do this if font supports it AND we successfully stored the backup buffer
|
||||
if (SETTINGS.textAntiAliasing && bufferStored) {
|
||||
renderer.clearScreen(0x00);
|
||||
renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB);
|
||||
page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop);
|
||||
|
||||
@@ -75,7 +75,7 @@ void FileSelectionActivity::onEnter() {
|
||||
updateRequired = true;
|
||||
|
||||
xTaskCreate(&FileSelectionActivity::taskTrampoline, "FileSelectionActivityTask",
|
||||
2048, // Stack size
|
||||
4096, // Stack size
|
||||
this, // Parameters
|
||||
1, // Priority
|
||||
&displayTaskHandle // Task handle
|
||||
|
||||
139
src/activities/settings/FontSelectionActivity.cpp
Normal file
139
src/activities/settings/FontSelectionActivity.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
#include "FontSelectionActivity.h"
|
||||
|
||||
#include <EpdFontLoader.h>
|
||||
#include <HardwareSerial.h>
|
||||
|
||||
#include "../../CrossPointSettings.h"
|
||||
#include "../../fontIds.h"
|
||||
#include "../../managers/FontManager.h"
|
||||
|
||||
FontSelectionActivity::FontSelectionActivity(GfxRenderer& renderer, MappedInputManager& inputManager,
|
||||
std::function<void()> onClose)
|
||||
: Activity("Font Selection", renderer, inputManager), onClose(onClose) {}
|
||||
|
||||
FontSelectionActivity::~FontSelectionActivity() {}
|
||||
|
||||
void FontSelectionActivity::onEnter() {
|
||||
Serial.println("[FSA] onEnter start");
|
||||
Activity::onEnter();
|
||||
Serial.println("[FSA] Getting available families...");
|
||||
fontFamilies = FontManager::getInstance().getAvailableFamilies();
|
||||
Serial.printf("[FSA] Got %d families\n", fontFamilies.size());
|
||||
|
||||
std::string current = SETTINGS.customFontFamily;
|
||||
Serial.printf("[FSA] Current setting: %s\n", current.c_str());
|
||||
|
||||
for (size_t i = 0; i < fontFamilies.size(); i++) {
|
||||
if (fontFamilies[i] == current) {
|
||||
selectedIndex = i;
|
||||
Serial.printf("[FSA] Found current family at index %d\n", i);
|
||||
// Adjust scroll
|
||||
if (selectedIndex >= itemsPerPage) {
|
||||
scrollOffset = selectedIndex - itemsPerPage / 2;
|
||||
if (scrollOffset > (int)fontFamilies.size() - itemsPerPage) {
|
||||
scrollOffset = std::max(0, (int)fontFamilies.size() - itemsPerPage);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
Serial.println("[FSA] Calling render()");
|
||||
render();
|
||||
Serial.println("[FSA] onEnter end");
|
||||
}
|
||||
|
||||
void FontSelectionActivity::loop() {
|
||||
bool update = false;
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Left) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::PageBack)) {
|
||||
if (selectedIndex > 0) {
|
||||
selectedIndex--;
|
||||
if (selectedIndex < scrollOffset) {
|
||||
scrollOffset = selectedIndex;
|
||||
update = true;
|
||||
} else {
|
||||
update = true;
|
||||
}
|
||||
}
|
||||
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Right) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::PageForward)) {
|
||||
if (selectedIndex < (int)fontFamilies.size() - 1) {
|
||||
selectedIndex++;
|
||||
if (selectedIndex >= scrollOffset + itemsPerPage) {
|
||||
scrollOffset = selectedIndex - itemsPerPage + 1;
|
||||
update = true;
|
||||
} else {
|
||||
update = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||
saveAndExit();
|
||||
return;
|
||||
}
|
||||
|
||||
if (update) {
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
void FontSelectionActivity::saveAndExit() {
|
||||
if (selectedIndex >= 0 && selectedIndex < (int)fontFamilies.size()) {
|
||||
strncpy(SETTINGS.customFontFamily, fontFamilies[selectedIndex].c_str(), sizeof(SETTINGS.customFontFamily) - 1);
|
||||
SETTINGS.customFontFamily[sizeof(SETTINGS.customFontFamily) - 1] = '\0';
|
||||
SETTINGS.fontFamily = CrossPointSettings::FONT_CUSTOM;
|
||||
SETTINGS.saveToFile();
|
||||
|
||||
// Reload fonts to make sure the newly selected font is loaded
|
||||
EpdFontLoader::loadFontsFromSd(renderer);
|
||||
}
|
||||
onClose();
|
||||
}
|
||||
|
||||
void FontSelectionActivity::render() const {
|
||||
renderer.clearScreen();
|
||||
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Select Font", true, EpdFontFamily::BOLD);
|
||||
|
||||
int y = 50;
|
||||
|
||||
if (fontFamilies.empty()) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 120, "No fonts found in /fonts", false);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 150, "Add .epdfont files to SD Card", false);
|
||||
renderer.displayBuffer(); // ensure update
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < itemsPerPage; i++) {
|
||||
int idx = scrollOffset + i;
|
||||
if (idx >= (int)fontFamilies.size()) break;
|
||||
|
||||
// Draw selection box
|
||||
if (idx == selectedIndex) {
|
||||
Serial.printf("[FSA] Drawing selected: %s at %d\n", fontFamilies[idx].c_str(), y);
|
||||
renderer.fillRect(10, y - 2, 460, 24);
|
||||
renderer.drawText(UI_10_FONT_ID, 20, y, fontFamilies[idx].c_str(), false); // false = white (on black box)
|
||||
} else {
|
||||
Serial.printf("[FSA] Drawing: %s at %d\n", fontFamilies[idx].c_str(), y);
|
||||
renderer.drawText(UI_10_FONT_ID, 20, y, fontFamilies[idx].c_str(), true); // true = black (on white bg)
|
||||
}
|
||||
|
||||
// Mark current active font
|
||||
if (fontFamilies[idx] == SETTINGS.customFontFamily) {
|
||||
renderer.drawText(UI_10_FONT_ID, 400, y, "*", idx != selectedIndex);
|
||||
}
|
||||
|
||||
y += 30;
|
||||
}
|
||||
|
||||
renderer.displayBuffer();
|
||||
}
|
||||
27
src/activities/settings/FontSelectionActivity.h
Normal file
27
src/activities/settings/FontSelectionActivity.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <MappedInputManager.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../Activity.h"
|
||||
|
||||
class FontSelectionActivity : public Activity {
|
||||
public:
|
||||
FontSelectionActivity(GfxRenderer& renderer, MappedInputManager& inputManager, std::function<void()> onClose);
|
||||
~FontSelectionActivity() override;
|
||||
void onEnter() override;
|
||||
void loop() override;
|
||||
void render() const;
|
||||
|
||||
private:
|
||||
std::function<void()> onClose;
|
||||
std::vector<std::string> fontFamilies;
|
||||
int selectedIndex = 0;
|
||||
int scrollOffset = 0;
|
||||
static constexpr int itemsPerPage = 8;
|
||||
void saveAndExit();
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "SettingsActivity.h"
|
||||
|
||||
#include <EpdFontLoader.h>
|
||||
#include <GfxRenderer.h>
|
||||
#include <HardwareSerial.h>
|
||||
|
||||
@@ -7,13 +8,14 @@
|
||||
|
||||
#include "CalibreSettingsActivity.h"
|
||||
#include "CrossPointSettings.h"
|
||||
#include "FontSelectionActivity.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "OtaUpdateActivity.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
// Define the static settings list
|
||||
namespace {
|
||||
constexpr int settingsCount = 20;
|
||||
constexpr int settingsCount = 21;
|
||||
const SettingInfo settingsList[settingsCount] = {
|
||||
// Should match with SLEEP_SCREEN_MODE
|
||||
SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}),
|
||||
@@ -31,7 +33,8 @@ const SettingInfo settingsList[settingsCount] = {
|
||||
{"Prev, Next", "Next, Prev"}),
|
||||
SettingInfo::Toggle("Long-press Chapter Skip", &CrossPointSettings::longPressChapterSkip),
|
||||
SettingInfo::Enum("Reader Font Family", &CrossPointSettings::fontFamily,
|
||||
{"Bookerly", "Noto Sans", "Open Dyslexic"}),
|
||||
{"Bookerly", "Noto Sans", "Open Dyslexic", "Custom"}),
|
||||
SettingInfo::Action("Set Custom Font Family"),
|
||||
SettingInfo::Enum("Reader Font Size", &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}),
|
||||
SettingInfo::Enum("Reader Line Spacing", &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}),
|
||||
SettingInfo::Value("Reader Screen Margin", &CrossPointSettings::screenMargin, {5, 40, 5}),
|
||||
@@ -112,6 +115,15 @@ void SettingsActivity::loop() {
|
||||
selectedSettingIndex = (selectedSettingIndex < settingsCount - 1) ? (selectedSettingIndex + 1) : 0;
|
||||
updateRequired = true;
|
||||
}
|
||||
|
||||
if (updateRequired) {
|
||||
// Ensure selected item is in view
|
||||
if (selectedSettingIndex < scrollOffset) {
|
||||
scrollOffset = selectedSettingIndex;
|
||||
} else if (selectedSettingIndex >= scrollOffset + itemsPerPage) {
|
||||
scrollOffset = selectedSettingIndex - itemsPerPage + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsActivity::toggleCurrentSetting() {
|
||||
@@ -121,6 +133,7 @@ void SettingsActivity::toggleCurrentSetting() {
|
||||
}
|
||||
|
||||
const auto& setting = settingsList[selectedSettingIndex];
|
||||
Serial.printf("[Settings] Toggling: '%s' (Type: %d)\n", setting.name, (int)setting.type);
|
||||
|
||||
if (setting.type == SettingType::TOGGLE && setting.valuePtr != nullptr) {
|
||||
// Toggle the boolean value using the member pointer
|
||||
@@ -129,6 +142,12 @@ 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<uint8_t>(setting.enumValues.size());
|
||||
|
||||
if (strcmp(setting.name, "Reader Font Family") == 0 || strcmp(setting.name, "Reader Font Size") == 0) {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
EpdFontLoader::loadFontsFromSd(renderer);
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
} else if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) {
|
||||
// Decreasing would also be nice for large ranges I think but oh well can't have everything
|
||||
const int8_t currentValue = SETTINGS.*(setting.valuePtr);
|
||||
@@ -155,6 +174,17 @@ void SettingsActivity::toggleCurrentSetting() {
|
||||
updateRequired = true;
|
||||
}));
|
||||
xSemaphoreGive(renderingMutex);
|
||||
} else if (strcmp(setting.name, "Set Custom Font Family") == 0) {
|
||||
Serial.println("[Settings] Launching FontSelectionActivity");
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
subActivity.reset(new FontSelectionActivity(renderer, mappedInput, [this] {
|
||||
subActivity.reset();
|
||||
updateRequired = true;
|
||||
}));
|
||||
subActivity->onEnter();
|
||||
xSemaphoreGive(renderingMutex);
|
||||
} else {
|
||||
Serial.printf("[Settings] Unknown action: %s\n", setting.name);
|
||||
}
|
||||
} else {
|
||||
// Only toggle if it's a toggle type and has a value pointer
|
||||
@@ -187,28 +217,41 @@ void SettingsActivity::render() const {
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Settings", true, EpdFontFamily::BOLD);
|
||||
|
||||
// Draw selection
|
||||
renderer.fillRect(0, 60 + selectedSettingIndex * 30 - 2, pageWidth - 1, 30);
|
||||
if (selectedSettingIndex >= scrollOffset && selectedSettingIndex < scrollOffset + itemsPerPage) {
|
||||
renderer.fillRect(0, 60 + (selectedSettingIndex - scrollOffset) * 30 - 2, pageWidth - 1, 30);
|
||||
}
|
||||
|
||||
// Draw visible settings
|
||||
for (int i = 0; i < itemsPerPage; i++) {
|
||||
int index = scrollOffset + i;
|
||||
if (index >= settingsCount) break;
|
||||
|
||||
// Draw all settings
|
||||
for (int i = 0; i < settingsCount; i++) {
|
||||
const int settingY = 60 + i * 30; // 30 pixels between settings
|
||||
|
||||
// Draw setting name
|
||||
renderer.drawText(UI_10_FONT_ID, 20, settingY, settingsList[i].name, i != selectedSettingIndex);
|
||||
renderer.drawText(UI_10_FONT_ID, 20, settingY, settingsList[index].name, index != selectedSettingIndex);
|
||||
|
||||
// Draw value based on setting type
|
||||
std::string valueText = "";
|
||||
if (settingsList[i].type == SettingType::TOGGLE && settingsList[i].valuePtr != nullptr) {
|
||||
const bool value = SETTINGS.*(settingsList[i].valuePtr);
|
||||
if (settingsList[index].type == SettingType::TOGGLE && settingsList[index].valuePtr != nullptr) {
|
||||
const bool value = SETTINGS.*(settingsList[index].valuePtr);
|
||||
valueText = value ? "ON" : "OFF";
|
||||
} else if (settingsList[i].type == SettingType::ENUM && settingsList[i].valuePtr != nullptr) {
|
||||
const uint8_t value = SETTINGS.*(settingsList[i].valuePtr);
|
||||
valueText = settingsList[i].enumValues[value];
|
||||
} else if (settingsList[i].type == SettingType::VALUE && settingsList[i].valuePtr != nullptr) {
|
||||
valueText = std::to_string(SETTINGS.*(settingsList[i].valuePtr));
|
||||
} else if (settingsList[index].type == SettingType::ENUM && settingsList[index].valuePtr != nullptr) {
|
||||
const uint8_t value = SETTINGS.*(settingsList[index].valuePtr);
|
||||
valueText = settingsList[index].enumValues[value];
|
||||
} else if (settingsList[index].type == SettingType::VALUE && settingsList[index].valuePtr != nullptr) {
|
||||
valueText = std::to_string(SETTINGS.*(settingsList[index].valuePtr));
|
||||
} else if (settingsList[index].type == SettingType::ACTION &&
|
||||
strcmp(settingsList[index].name, "Set Custom Font Family") == 0) {
|
||||
if (SETTINGS.fontFamily == CrossPointSettings::FONT_CUSTOM) {
|
||||
valueText = SETTINGS.customFontFamily;
|
||||
}
|
||||
}
|
||||
if (!valueText.empty()) {
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str());
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, valueText.c_str(),
|
||||
index != selectedSettingIndex);
|
||||
}
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str());
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, valueText.c_str(), i != selectedSettingIndex);
|
||||
}
|
||||
|
||||
// Draw version text above button hints
|
||||
|
||||
@@ -49,6 +49,8 @@ class SettingsActivity final : public ActivityWithSubactivity {
|
||||
SemaphoreHandle_t renderingMutex = nullptr;
|
||||
bool updateRequired = false;
|
||||
int selectedSettingIndex = 0; // Currently selected setting
|
||||
int scrollOffset = 0; // Index of the first visible setting
|
||||
static constexpr int itemsPerPage = 25;
|
||||
const std::function<void()> onGoHome;
|
||||
|
||||
static void taskTrampoline(void* param);
|
||||
|
||||
31
src/main.cpp
31
src/main.cpp
@@ -1,5 +1,6 @@
|
||||
#include <Arduino.h>
|
||||
#include <EInkDisplay.h>
|
||||
#include <EpdFontLoader.h>
|
||||
#include <Epub.h>
|
||||
#include <GfxRenderer.h>
|
||||
#include <InputManager.h>
|
||||
@@ -263,15 +264,20 @@ void setupDisplayAndFonts() {
|
||||
}
|
||||
|
||||
void setup() {
|
||||
// force serial for debugging
|
||||
Serial.begin(115200);
|
||||
delay(500);
|
||||
Serial.printf("[%lu] [DBG] setup() start - FIRMWARE DEBUG BUILD 001\n", millis());
|
||||
Serial.flush();
|
||||
|
||||
t1 = millis();
|
||||
|
||||
// Only start serial if USB connected
|
||||
pinMode(UART0_RXD, INPUT);
|
||||
if (digitalRead(UART0_RXD) == HIGH) {
|
||||
Serial.begin(115200);
|
||||
}
|
||||
|
||||
inputManager.begin();
|
||||
Serial.printf("[%lu] [DBG] inputManager initialized\n", millis());
|
||||
|
||||
// Initialize pins
|
||||
pinMode(BAT_GPIO0, INPUT);
|
||||
|
||||
@@ -287,21 +293,37 @@ void setup() {
|
||||
enterNewActivity(new FullScreenMessageActivity(renderer, mappedInputManager, "SD card error", EpdFontFamily::BOLD));
|
||||
return;
|
||||
}
|
||||
Serial.printf("[%lu] [DBG] SdMan.begin() success\n", millis());
|
||||
|
||||
SETTINGS.loadFromFile();
|
||||
Serial.printf("[%lu] [DBG] SETTINGS loaded\n", millis());
|
||||
|
||||
Serial.flush();
|
||||
|
||||
// verify power button press duration after we've read settings.
|
||||
verifyWakeupLongPress();
|
||||
// verifyWakeupLongPress(); // Disabled for debugging to prevent auto-shutdown
|
||||
// Serial.printf("[%lu] [DBG] Wakeup long press verified\n", millis());
|
||||
|
||||
// First serial output only here to avoid timing inconsistencies for power button press duration verification
|
||||
Serial.printf("[%lu] [ ] Starting CrossPoint version " CROSSPOINT_VERSION "\n", millis());
|
||||
Serial.flush();
|
||||
|
||||
setupDisplayAndFonts();
|
||||
Serial.printf("[%lu] [DBG] setupDisplayAndFonts done\n", millis());
|
||||
Serial.flush();
|
||||
|
||||
EpdFontLoader::loadFontsFromSd(renderer);
|
||||
Serial.printf("[%lu] [DBG] loadFontsFromSd done\n", millis());
|
||||
Serial.flush();
|
||||
|
||||
exitActivity();
|
||||
enterNewActivity(new BootActivity(renderer, mappedInputManager));
|
||||
Serial.printf("[%lu] [DBG] BootActivity entered\n", millis());
|
||||
Serial.flush();
|
||||
|
||||
APP_STATE.loadFromFile();
|
||||
Serial.printf("[%lu] [DBG] APP_STATE loaded\n", millis());
|
||||
|
||||
if (APP_STATE.openEpubPath.empty()) {
|
||||
onGoHome();
|
||||
} else {
|
||||
@@ -314,6 +336,7 @@ void setup() {
|
||||
}
|
||||
|
||||
// Ensure we're not still holding the power button before leaving setup
|
||||
Serial.printf("[%lu] [ ] Setup complete\n", millis());
|
||||
waitForPowerRelease();
|
||||
}
|
||||
|
||||
|
||||
212
src/managers/FontManager.cpp
Normal file
212
src/managers/FontManager.cpp
Normal file
@@ -0,0 +1,212 @@
|
||||
#include "FontManager.h"
|
||||
|
||||
#include <GfxRenderer.h> // for EpdFontData usage validation if needed
|
||||
#include <HardwareSerial.h>
|
||||
#include <SDCardManager.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "CustomEpdFont.h"
|
||||
|
||||
FontManager& FontManager::getInstance() {
|
||||
static FontManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
FontManager::~FontManager() {
|
||||
for (auto& familyPair : loadedFonts) {
|
||||
for (auto& sizePair : familyPair.second) {
|
||||
delete sizePair.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string>& FontManager::getAvailableFamilies() {
|
||||
if (!scanned) {
|
||||
scanFonts();
|
||||
}
|
||||
return availableFamilies;
|
||||
}
|
||||
|
||||
void FontManager::scanFonts() {
|
||||
Serial.println("[FM] Scanning fonts...");
|
||||
availableFamilies.clear();
|
||||
scanned = true;
|
||||
|
||||
FsFile fontDir;
|
||||
if (!SdMan.openFileForRead("FontScan", "/fonts", fontDir)) {
|
||||
Serial.println("[FM] Failed to open /fonts directory");
|
||||
// Even if failed, we proceed to sort empty list to avoid crashes
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fontDir.isDirectory()) {
|
||||
Serial.println("[FM] /fonts is not a directory");
|
||||
fontDir.close();
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.println("[FM] /fonts opened. Iterating files...");
|
||||
FsFile file;
|
||||
while (file.openNext(&fontDir, O_READ)) {
|
||||
if (!file.isDirectory()) {
|
||||
char filename[128];
|
||||
file.getName(filename, sizeof(filename));
|
||||
Serial.printf("[FM] Checking: %s\n", filename);
|
||||
|
||||
String name = String(filename);
|
||||
if (name.endsWith(".epdfont")) {
|
||||
// Expected format: Family-Style-Size.epdfont
|
||||
int firstDash = name.indexOf('-');
|
||||
if (firstDash > 0) {
|
||||
String family = name.substring(0, firstDash);
|
||||
if (std::find(availableFamilies.begin(), availableFamilies.end(), family.c_str()) ==
|
||||
availableFamilies.end()) {
|
||||
availableFamilies.push_back(family.c_str());
|
||||
Serial.printf("[FM] Added family: %s\n", family.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
fontDir.close();
|
||||
|
||||
std::sort(availableFamilies.begin(), availableFamilies.end());
|
||||
Serial.printf("[FM] Scan complete. Found %d families\n", availableFamilies.size());
|
||||
}
|
||||
|
||||
struct EpdfHeader {
|
||||
char magic[4];
|
||||
uint32_t intervalCount;
|
||||
uint32_t totalGlyphCount;
|
||||
uint8_t advanceY;
|
||||
int32_t ascender;
|
||||
int32_t descender;
|
||||
uint8_t is2Bit;
|
||||
};
|
||||
|
||||
// Helper to load a single font file
|
||||
CustomEpdFont* loadFontFile(const String& path) {
|
||||
Serial.printf("[FontMgr] Loading file: %s\n", path.c_str());
|
||||
Serial.flush();
|
||||
FsFile f;
|
||||
if (!SdMan.openFileForRead("FontLoading", path.c_str(), f)) {
|
||||
Serial.printf("[FontMgr] Failed to open: %s\n", path.c_str());
|
||||
Serial.flush();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Read custom header format (detected from file dump)
|
||||
// 0: Magic (4)
|
||||
// 4: IntervalCount (4)
|
||||
// 8: FileSize (4)
|
||||
// 12: Height (4) -> advanceY
|
||||
// 16: GlyphCount (4)
|
||||
// 20: Ascender (4)
|
||||
// 24: Unknown (4)
|
||||
// 28: Descender (4)
|
||||
// 32: Unknown (4)
|
||||
// 36: OffsetIntervals (4)
|
||||
// 40: OffsetGlyphs (4)
|
||||
// 44: OffsetBitmaps (4)
|
||||
|
||||
uint32_t buf[12]; // 48 bytes
|
||||
if (f.read(buf, 48) != 48) {
|
||||
Serial.printf("[FontMgr] Header read failed for %s\n", path.c_str());
|
||||
f.close();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (strncmp((char*)&buf[0], "EPDF", 4) != 0) {
|
||||
Serial.printf("[FontMgr] Invalid magic for %s\n", path.c_str());
|
||||
f.close();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint32_t intervalCount = buf[1];
|
||||
uint32_t fileSize = buf[2];
|
||||
uint32_t height = buf[3];
|
||||
uint32_t glyphCount = buf[4];
|
||||
int32_t ascender = (int32_t)buf[5];
|
||||
int32_t descender = (int32_t)buf[7];
|
||||
|
||||
uint32_t offsetIntervals = buf[9];
|
||||
uint32_t offsetGlyphs = buf[10];
|
||||
uint32_t offsetBitmaps = buf[11];
|
||||
|
||||
Serial.printf("[FontMgr] parsed header: intv=%u, glyphs=%u, fileSz=%u, h=%u, asc=%d, desc=%d\n", intervalCount,
|
||||
glyphCount, fileSize, height, ascender, descender);
|
||||
Serial.printf("[FontMgr] offsets: intv=%u, gly=%u, bmp=%u\n", offsetIntervals, offsetGlyphs, offsetBitmaps);
|
||||
|
||||
// Validation
|
||||
if (offsetIntervals >= fileSize || offsetGlyphs >= fileSize || offsetBitmaps >= fileSize) {
|
||||
Serial.println("[FontMgr] Invalid offsets in header");
|
||||
f.close();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// We need to load intervals into RAM
|
||||
EpdUnicodeInterval* intervals = new (std::nothrow) EpdUnicodeInterval[intervalCount];
|
||||
if (!intervals) {
|
||||
Serial.printf("[FontMgr] Failed to allocate intervals: %d\n", intervalCount);
|
||||
f.close();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!f.seekSet(offsetIntervals)) {
|
||||
Serial.println("[FontMgr] Failed to seek to intervals");
|
||||
delete[] intervals;
|
||||
f.close();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
f.read((uint8_t*)intervals, intervalCount * sizeof(EpdUnicodeInterval));
|
||||
|
||||
f.close();
|
||||
|
||||
// Create EpdFontData
|
||||
EpdFontData* fontData = new (std::nothrow) EpdFontData();
|
||||
if (!fontData) {
|
||||
Serial.println("[FontMgr] Failed to allocate EpdFontData! OOM.");
|
||||
delete[] intervals;
|
||||
return nullptr;
|
||||
}
|
||||
fontData->intervalCount = intervalCount;
|
||||
fontData->intervals = intervals;
|
||||
fontData->glyph = nullptr;
|
||||
fontData->advanceY = (uint8_t)height;
|
||||
fontData->ascender = ascender;
|
||||
fontData->descender = descender;
|
||||
fontData->descender = descender;
|
||||
fontData->is2Bit = (buf[8] != 0);
|
||||
fontData->bitmap = nullptr;
|
||||
|
||||
return new CustomEpdFont(path, fontData, offsetIntervals, offsetGlyphs, offsetBitmaps);
|
||||
}
|
||||
|
||||
EpdFontFamily* FontManager::getCustomFontFamily(const std::string& familyName, int fontSize) {
|
||||
if (loadedFonts[familyName][fontSize]) {
|
||||
return loadedFonts[familyName][fontSize];
|
||||
}
|
||||
|
||||
String basePath = "/fonts/" + String(familyName.c_str()) + "-";
|
||||
String sizeStr = String(fontSize);
|
||||
|
||||
CustomEpdFont* regular = loadFontFile(basePath + "Regular-" + sizeStr + ".epdfont");
|
||||
CustomEpdFont* bold = loadFontFile(basePath + "Bold-" + sizeStr + ".epdfont");
|
||||
CustomEpdFont* italic = loadFontFile(basePath + "Italic-" + sizeStr + ".epdfont");
|
||||
CustomEpdFont* boldItalic = loadFontFile(basePath + "BoldItalic-" + sizeStr + ".epdfont");
|
||||
|
||||
if (!regular) {
|
||||
if (bold) regular = bold;
|
||||
}
|
||||
|
||||
if (regular) {
|
||||
EpdFontFamily* family = new EpdFontFamily(regular, bold, italic, boldItalic);
|
||||
loadedFonts[familyName][fontSize] = family;
|
||||
return family;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
31
src/managers/FontManager.h
Normal file
31
src/managers/FontManager.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "EpdFontFamily.h"
|
||||
|
||||
class FontManager {
|
||||
public:
|
||||
static FontManager& getInstance();
|
||||
|
||||
// Scan SD card for fonts
|
||||
void scanFonts();
|
||||
|
||||
// Get list of available font family names
|
||||
const std::vector<std::string>& getAvailableFamilies();
|
||||
|
||||
// Load a specific family and size (returns pointer to cached family or new one)
|
||||
EpdFontFamily* getCustomFontFamily(const std::string& familyName, int fontSize);
|
||||
|
||||
private:
|
||||
FontManager() = default;
|
||||
~FontManager();
|
||||
|
||||
std::vector<std::string> availableFamilies;
|
||||
bool scanned = false;
|
||||
|
||||
// Map: FamilyName -> Size -> EpdFontFamily*
|
||||
std::map<std::string, std::map<int, EpdFontFamily*>> loadedFonts;
|
||||
};
|
||||
Reference in New Issue
Block a user