## Summary Closes #766. Thank you for the help @bramschulting! **What is the goal of this PR?** - First and foremost, fix issue #766. - Through working on that, I realized the current CSS parsing/loading code can be improved dramatically for large files and still had additional performance improvements to be made, even with EPUBs with small CSS. **What changes are included?** - Stream CSS parsing and reuse normalization buffers to cut allocations - Add rule limits and selector validation to release rules and free up memory when needed - Skip CSS parsing/loading entirely when "Book's Embedded Style" is off ## Additional Context - My test EPUB has been updated [here](https://github.com/jdk2pq/css-test-epub) to include a very large CSS file to test this out --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**YES**_, Codex
140 lines
4.0 KiB
C++
140 lines
4.0 KiB
C++
#include "ReaderActivity.h"
|
|
|
|
#include <HalStorage.h>
|
|
|
|
#include "CrossPointSettings.h"
|
|
#include "Epub.h"
|
|
#include "EpubReaderActivity.h"
|
|
#include "Txt.h"
|
|
#include "TxtReaderActivity.h"
|
|
#include "Xtc.h"
|
|
#include "XtcReaderActivity.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('/');
|
|
if (lastSlash == std::string::npos || lastSlash == 0) {
|
|
return "/";
|
|
}
|
|
return filePath.substr(0, lastSlash);
|
|
}
|
|
|
|
bool ReaderActivity::isXtcFile(const std::string& path) {
|
|
return StringUtils::checkFileExtension(path, ".xtc") || StringUtils::checkFileExtension(path, ".xtch");
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
std::unique_ptr<Epub> ReaderActivity::loadEpub(const std::string& path) {
|
|
if (!Storage.exists(path.c_str())) {
|
|
LOG_ERR("READER", "File does not exist: %s", path.c_str());
|
|
return nullptr;
|
|
}
|
|
|
|
auto epub = std::unique_ptr<Epub>(new Epub(path, "/.crosspoint"));
|
|
if (epub->load(true, SETTINGS.embeddedStyle == 0)) {
|
|
return epub;
|
|
}
|
|
|
|
LOG_ERR("READER", "Failed to load epub");
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<Xtc> ReaderActivity::loadXtc(const std::string& path) {
|
|
if (!Storage.exists(path.c_str())) {
|
|
LOG_ERR("READER", "File does not exist: %s", path.c_str());
|
|
return nullptr;
|
|
}
|
|
|
|
auto xtc = std::unique_ptr<Xtc>(new Xtc(path, "/.crosspoint"));
|
|
if (xtc->load()) {
|
|
return xtc;
|
|
}
|
|
|
|
LOG_ERR("READER", "Failed to load XTC");
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<Txt> ReaderActivity::loadTxt(const std::string& path) {
|
|
if (!Storage.exists(path.c_str())) {
|
|
LOG_ERR("READER", "File does not exist: %s", path.c_str());
|
|
return nullptr;
|
|
}
|
|
|
|
auto txt = std::unique_ptr<Txt>(new Txt(path, "/.crosspoint"));
|
|
if (txt->load()) {
|
|
return txt;
|
|
}
|
|
|
|
LOG_ERR("READER", "Failed to load TXT");
|
|
return nullptr;
|
|
}
|
|
|
|
void ReaderActivity::goToLibrary(const std::string& fromBookPath) {
|
|
// If coming from a book, start in that book's folder; otherwise start from root
|
|
const auto initialPath = fromBookPath.empty() ? "/" : extractFolderPath(fromBookPath);
|
|
onGoToLibrary(initialPath);
|
|
}
|
|
|
|
void ReaderActivity::onGoToEpubReader(std::unique_ptr<Epub> epub) {
|
|
const auto epubPath = epub->getPath();
|
|
currentBookPath = epubPath;
|
|
exitActivity();
|
|
enterNewActivity(new EpubReaderActivity(
|
|
renderer, mappedInput, std::move(epub), [this, epubPath] { goToLibrary(epubPath); }, [this] { onGoBack(); }));
|
|
}
|
|
|
|
void ReaderActivity::onGoToXtcReader(std::unique_ptr<Xtc> xtc) {
|
|
const auto xtcPath = xtc->getPath();
|
|
currentBookPath = xtcPath;
|
|
exitActivity();
|
|
enterNewActivity(new XtcReaderActivity(
|
|
renderer, mappedInput, std::move(xtc), [this, xtcPath] { goToLibrary(xtcPath); }, [this] { onGoBack(); }));
|
|
}
|
|
|
|
void ReaderActivity::onGoToTxtReader(std::unique_ptr<Txt> txt) {
|
|
const auto txtPath = txt->getPath();
|
|
currentBookPath = txtPath;
|
|
exitActivity();
|
|
enterNewActivity(new TxtReaderActivity(
|
|
renderer, mappedInput, std::move(txt), [this, txtPath] { goToLibrary(txtPath); }, [this] { onGoBack(); }));
|
|
}
|
|
|
|
void ReaderActivity::onEnter() {
|
|
ActivityWithSubactivity::onEnter();
|
|
|
|
if (initialBookPath.empty()) {
|
|
goToLibrary(); // Start from root when entering via Browse
|
|
return;
|
|
}
|
|
|
|
currentBookPath = initialBookPath;
|
|
|
|
if (isXtcFile(initialBookPath)) {
|
|
auto xtc = loadXtc(initialBookPath);
|
|
if (!xtc) {
|
|
onGoBack();
|
|
return;
|
|
}
|
|
onGoToXtcReader(std::move(xtc));
|
|
} else if (isTxtFile(initialBookPath)) {
|
|
auto txt = loadTxt(initialBookPath);
|
|
if (!txt) {
|
|
onGoBack();
|
|
return;
|
|
}
|
|
onGoToTxtReader(std::move(txt));
|
|
} else {
|
|
auto epub = loadEpub(initialBookPath);
|
|
if (!epub) {
|
|
onGoBack();
|
|
return;
|
|
}
|
|
onGoToEpubReader(std::move(epub));
|
|
}
|
|
}
|