Merge branch 'master' of https://github.com/crosspoint-reader/crosspoint-reader into feat-page-overlay

This commit is contained in:
jpirnay
2026-03-07 10:22:18 +01:00
41 changed files with 924 additions and 193 deletions
+5 -6
View File
@@ -1,6 +1,7 @@
#include "SleepActivity.h"
#include <Epub.h>
#include <FsHelpers.h>
#include <GfxRenderer.h>
#include <HalStorage.h>
#include <I18n.h>
@@ -18,7 +19,6 @@
#include "components/UITheme.h"
#include "fontIds.h"
#include "images/Logo120.h"
#include "util/StringUtils.h"
namespace {
@@ -211,7 +211,7 @@ void SleepActivity::renderCustomSleepScreen() const {
continue;
}
if (filename.substr(filename.length() - 4) != ".bmp") {
if (!FsHelpers::hasBmpExtension(filename)) {
LOG_DBG("SLP", "Skipping non-.bmp file name: %s", name);
file.close();
continue;
@@ -378,8 +378,7 @@ void SleepActivity::renderCoverSleepScreen() const {
bool cropped = SETTINGS.sleepScreenCoverMode == CrossPointSettings::SLEEP_SCREEN_COVER_MODE::CROP;
// Check if the current book is XTC, TXT, or EPUB
if (StringUtils::checkFileExtension(APP_STATE.openEpubPath, ".xtc") ||
StringUtils::checkFileExtension(APP_STATE.openEpubPath, ".xtch")) {
if (FsHelpers::hasXtcExtension(APP_STATE.openEpubPath)) {
// Handle XTC file
Xtc lastXtc(APP_STATE.openEpubPath, "/.crosspoint");
if (!lastXtc.load()) {
@@ -393,7 +392,7 @@ void SleepActivity::renderCoverSleepScreen() const {
}
coverBmpPath = lastXtc.getCoverBmpPath();
} else if (StringUtils::checkFileExtension(APP_STATE.openEpubPath, ".txt")) {
} else if (FsHelpers::hasTxtExtension(APP_STATE.openEpubPath)) {
// Handle TXT file - looks for cover image in the same folder
Txt lastTxt(APP_STATE.openEpubPath, "/.crosspoint");
if (!lastTxt.load()) {
@@ -407,7 +406,7 @@ void SleepActivity::renderCoverSleepScreen() const {
}
coverBmpPath = lastTxt.getCoverBmpPath();
} else if (StringUtils::checkFileExtension(APP_STATE.openEpubPath, ".epub")) {
} else if (FsHelpers::hasEpubExtension(APP_STATE.openEpubPath)) {
// Handle EPUB file
Epub lastEpub(APP_STATE.openEpubPath, "/.crosspoint");
// Skip loading css since we only need metadata here
+6 -6
View File
@@ -1,6 +1,7 @@
#include "FileBrowserActivity.h"
#include <Epub.h>
#include <FsHelpers.h>
#include <GfxRenderer.h>
#include <HalStorage.h>
#include <I18n.h>
@@ -11,7 +12,6 @@
#include "MappedInputManager.h"
#include "components/UITheme.h"
#include "fontIds.h"
#include "util/StringUtils.h"
namespace {
constexpr unsigned long GO_HOME_MS = 1000;
@@ -91,10 +91,10 @@ void FileBrowserActivity::loadFiles() {
if (file.isDirectory()) {
files.emplace_back(std::string(name) + "/");
} else {
auto filename = std::string(name);
if (StringUtils::checkFileExtension(filename, ".epub") || StringUtils::checkFileExtension(filename, ".xtch") ||
StringUtils::checkFileExtension(filename, ".xtc") || StringUtils::checkFileExtension(filename, ".txt") ||
StringUtils::checkFileExtension(filename, ".md") || StringUtils::checkFileExtension(filename, ".bmp")) {
std::string_view filename{name};
if (FsHelpers::hasEpubExtension(filename) || FsHelpers::hasXtcExtension(filename) ||
FsHelpers::hasTxtExtension(filename) || FsHelpers::hasMarkdownExtension(filename) ||
FsHelpers::hasBmpExtension(filename)) {
files.emplace_back(filename);
}
}
@@ -120,7 +120,7 @@ void FileBrowserActivity::onExit() {
void FileBrowserActivity::clearFileMetadata(const std::string& fullPath) {
// Only clear cache for .epub files
if (StringUtils::checkFileExtension(fullPath, ".epub")) {
if (FsHelpers::hasEpubExtension(fullPath)) {
Epub(fullPath, "/.crosspoint").clearCache();
LOG_DBG("FileBrowser", "Cleared metadata cache for: %s", fullPath.c_str());
}
+3 -4
View File
@@ -2,6 +2,7 @@
#include <Bitmap.h>
#include <Epub.h>
#include <FsHelpers.h>
#include <GfxRenderer.h>
#include <HalStorage.h>
#include <I18n.h>
@@ -17,7 +18,6 @@
#include "RecentBooksStore.h"
#include "components/UITheme.h"
#include "fontIds.h"
#include "util/StringUtils.h"
int HomeActivity::getMenuItemCount() const {
int count = 4; // File Browser, Recents, File transfer, Settings
@@ -61,7 +61,7 @@ void HomeActivity::loadRecentCovers(int coverHeight) {
std::string coverPath = UITheme::getCoverThumbPath(book.coverBmpPath, coverHeight);
if (!Storage.exists(coverPath.c_str())) {
// If epub, try to load the metadata for title/author and cover
if (StringUtils::checkFileExtension(book.path, ".epub")) {
if (FsHelpers::hasEpubExtension(book.path)) {
Epub epub(book.path, "/.crosspoint");
// Skip loading css since we only need metadata here
epub.load(false, true);
@@ -79,8 +79,7 @@ void HomeActivity::loadRecentCovers(int coverHeight) {
}
coverRendered = false;
requestUpdate();
} else if (StringUtils::checkFileExtension(book.path, ".xtch") ||
StringUtils::checkFileExtension(book.path, ".xtc")) {
} else if (FsHelpers::hasXtcExtension(book.path)) {
// Handle XTC file
Xtc xtc(book.path, "/.crosspoint");
if (xtc.load()) {
@@ -10,7 +10,6 @@
#include "RecentBooksStore.h"
#include "components/UITheme.h"
#include "fontIds.h"
#include "util/StringUtils.h"
namespace {
constexpr unsigned long GO_HOME_MS = 1000;
+18 -1
View File
@@ -597,6 +597,16 @@ void EpubReaderActivity::render(RenderLock&& lock) {
section->currentPage = nextPageNumber;
}
if (!pendingAnchor.empty()) {
if (const auto page = section->getPageForAnchor(pendingAnchor)) {
section->currentPage = *page;
LOG_DBG("ERS", "Resolved anchor '%s' to page %d", pendingAnchor.c_str(), *page);
} else {
LOG_DBG("ERS", "Anchor '%s' not found in section %d", pendingAnchor.c_str(), currentSpineIndex);
}
pendingAnchor.clear();
}
// handles changes in reader settings and reset to approximate position based on cached progress
if (cachedChapterTotalPageCount > 0) {
// only goes to relative position if spine index matches cached value
@@ -792,12 +802,18 @@ void EpubReaderActivity::navigateToHref(const std::string& hrefStr, const bool s
LOG_DBG("ERS", "Saved position [%d]: spine %d, page %d", footnoteDepth, currentSpineIndex, section->currentPage);
}
// Extract fragment anchor (e.g. "#note1" or "chapter2.xhtml#note1")
std::string anchor;
const auto hashPos = hrefStr.find('#');
if (hashPos != std::string::npos && hashPos + 1 < hrefStr.size()) {
anchor = hrefStr.substr(hashPos + 1);
}
// Check for same-file anchor reference (#anchor only)
bool sameFile = !hrefStr.empty() && hrefStr[0] == '#';
int targetSpineIndex;
if (sameFile) {
// Same file — navigate to page 0 of current spine item
targetSpineIndex = currentSpineIndex;
} else {
targetSpineIndex = epub->resolveHrefToSpineIndex(hrefStr);
@@ -811,6 +827,7 @@ void EpubReaderActivity::navigateToHref(const std::string& hrefStr, const bool s
{
RenderLock lock(*this);
pendingAnchor = std::move(anchor);
currentSpineIndex = targetSpineIndex;
nextPageNumber = 0;
section.reset();
@@ -11,6 +11,9 @@ class EpubReaderActivity final : public Activity {
std::unique_ptr<Section> section = nullptr;
int currentSpineIndex = 0;
int nextPageNumber = 0;
// Set when navigating to a footnote href with a fragment (e.g. #note1).
// Cleared on the next render after the new section loads and resolves it to a page.
std::string pendingAnchor;
int pagesUntilFullRefresh = 0;
int cachedSpineIndex = 0;
int cachedChapterTotalPageCount = 0;
+5 -7
View File
@@ -1,5 +1,6 @@
#include "ReaderActivity.h"
#include <FsHelpers.h>
#include <HalStorage.h>
#include "CrossPointSettings.h"
@@ -11,7 +12,6 @@
#include "XtcReaderActivity.h"
#include "activities/util/BmpViewerActivity.h"
#include "activities/util/FullScreenMessageActivity.h"
#include "util/StringUtils.h"
std::string ReaderActivity::extractFolderPath(const std::string& filePath) {
const auto lastSlash = filePath.find_last_of('/');
@@ -21,16 +21,14 @@ std::string ReaderActivity::extractFolderPath(const std::string& filePath) {
return filePath.substr(0, lastSlash);
}
bool ReaderActivity::isXtcFile(const std::string& path) {
return StringUtils::checkFileExtension(path, ".xtc") || StringUtils::checkFileExtension(path, ".xtch");
}
bool ReaderActivity::isXtcFile(const std::string& path) { return FsHelpers::hasXtcExtension(path); }
bool ReaderActivity::isTxtFile(const std::string& path) {
return StringUtils::checkFileExtension(path, ".txt") ||
StringUtils::checkFileExtension(path, ".md"); // Treat .md as txt files (until we have a markdown reader)
return FsHelpers::hasTxtExtension(path) ||
FsHelpers::hasMarkdownExtension(path); // Treat .md as txt files (until we have a markdown reader)
}
bool ReaderActivity::isBmpFile(const std::string& path) { return StringUtils::checkFileExtension(path, ".bmp"); }
bool ReaderActivity::isBmpFile(const std::string& path) { return FsHelpers::hasBmpExtension(path); }
std::unique_ptr<Epub> ReaderActivity::loadEpub(const std::string& path) {
if (!Storage.exists(path.c_str())) {