diff --git a/lib/Epub/Epub.cpp b/lib/Epub/Epub.cpp index b135da11..5dc02358 100644 --- a/lib/Epub/Epub.cpp +++ b/lib/Epub/Epub.cpp @@ -86,8 +86,9 @@ bool Epub::parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata) { tocNavItem = opfParser.tocNavPath; } - // Copy CSS files to metadata - bookMetadata.cssFiles = opfParser.cssFiles; + if (!opfParser.cssFiles.empty()) { + cssFiles = opfParser.cssFiles; + } Serial.printf("[%lu] [EBP] Successfully parsed content.opf\n", millis()); return true; @@ -207,66 +208,91 @@ bool Epub::parseTocNavFile() const { return true; } -bool Epub::parseCssFiles() { - if (!bookMetadataCache || !bookMetadataCache->isLoaded()) { - Serial.printf("[%lu] [EBP] Cannot parse CSS, cache not loaded\n", millis()); - return false; +std::string Epub::getCssRulesCache() const { return cachePath + "/css_rules.cache"; } + +bool Epub::loadCssRulesFromCache() const { + FsFile cssCacheFile; + if (SdMan.openFileForRead("EBP", getCssRulesCache(), cssCacheFile)) { + if (cssParser->loadFromCache(cssCacheFile)) { + cssCacheFile.close(); + Serial.printf("[%lu] [EBP] Loaded CSS rules from cache\n", millis()); + return true; + } + cssCacheFile.close(); + Serial.printf("[%lu] [EBP] CSS cache invalid, reparsing\n", millis()); } + return false; +} - // Always create CssParser - needed for inline style parsing even without CSS files - cssParser.reset(new CssParser()); - - const auto& cssFiles = bookMetadataCache->coreMetadata.cssFiles; +void Epub::parseCssFiles() const { if (cssFiles.empty()) { Serial.printf("[%lu] [EBP] No CSS files to parse, but CssParser created for inline styles\n", millis()); - return true; } - for (const auto& cssPath : cssFiles) { - Serial.printf("[%lu] [EBP] Parsing CSS file: %s\n", millis(), cssPath.c_str()); + // Try to load from CSS cache first + if (!loadCssRulesFromCache()) { + // Cache miss - parse CSS files + for (const auto& cssPath : cssFiles) { + Serial.printf("[%lu] [EBP] Parsing CSS file: %s\n", millis(), cssPath.c_str()); - // Extract CSS file to temp location - const auto tmpCssPath = getCachePath() + "/.tmp.css"; - FsFile tempCssFile; - if (!SdMan.openFileForWrite("EBP", tmpCssPath, tempCssFile)) { - Serial.printf("[%lu] [EBP] Could not create temp CSS file\n", millis()); - continue; - } - if (!readItemContentsToStream(cssPath, tempCssFile, 1024)) { - Serial.printf("[%lu] [EBP] Could not read CSS file: %s\n", millis(), cssPath.c_str()); + // Extract CSS file to temp location + const auto tmpCssPath = getCachePath() + "/.tmp.css"; + FsFile tempCssFile; + if (!SdMan.openFileForWrite("EBP", tmpCssPath, tempCssFile)) { + Serial.printf("[%lu] [EBP] Could not create temp CSS file\n", millis()); + continue; + } + if (!readItemContentsToStream(cssPath, tempCssFile, 1024)) { + Serial.printf("[%lu] [EBP] Could not read CSS file: %s\n", millis(), cssPath.c_str()); + tempCssFile.close(); + SdMan.remove(tmpCssPath.c_str()); + continue; + } + tempCssFile.close(); + + // Parse the CSS file + if (!SdMan.openFileForRead("EBP", tmpCssPath, tempCssFile)) { + Serial.printf("[%lu] [EBP] Could not open temp CSS file for reading\n", millis()); + SdMan.remove(tmpCssPath.c_str()); + continue; + } + cssParser->loadFromStream(tempCssFile); tempCssFile.close(); SdMan.remove(tmpCssPath.c_str()); - continue; } - tempCssFile.close(); - // Parse the CSS file - if (!SdMan.openFileForRead("EBP", tmpCssPath, tempCssFile)) { - Serial.printf("[%lu] [EBP] Could not open temp CSS file for reading\n", millis()); - SdMan.remove(tmpCssPath.c_str()); - continue; + // Save to cache for next time + FsFile cssCacheFile; + if (SdMan.openFileForWrite("EBP", getCssRulesCache(), cssCacheFile)) { + cssParser->saveToCache(cssCacheFile); + cssCacheFile.close(); } - cssParser->loadFromStream(tempCssFile); - tempCssFile.close(); - SdMan.remove(tmpCssPath.c_str()); + + Serial.printf("[%lu] [EBP] Loaded %zu CSS style rules from %zu files\n", millis(), cssParser->ruleCount(), + cssFiles.size()); } - - Serial.printf("[%lu] [EBP] Loaded %zu CSS style rules from %zu files\n", millis(), cssParser->ruleCount(), - cssFiles.size()); - return true; } // load in the meta data for the epub file -bool Epub::load(const bool buildIfMissing) { +bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) { Serial.printf("[%lu] [EBP] Loading ePub: %s\n", millis(), filepath.c_str()); // Initialize spine/TOC cache bookMetadataCache.reset(new BookMetadataCache(cachePath)); + // Always create CssParser - needed for inline style parsing even without CSS files + cssParser.reset(new CssParser()); // Try to load existing cache first if (bookMetadataCache->load()) { - // Parse CSS files from loaded cache - parseCssFiles(); + if (!skipLoadingCss && !loadCssRulesFromCache()) { + Serial.printf("[%lu] [EBP] Warning: CSS rules cache not found, attempting to parse CSS files\n", millis()); + // to get CSS file list + if (!parseContentOpf(bookMetadataCache->coreMetadata)) { + Serial.printf("[%lu] [EBP] Could not parse content.opf from cached bookMetadata for CSS files\n", millis()); + // continue anyway - book will work without CSS and we'll still load any inline style CSS + } + parseCssFiles(); + } Serial.printf("[%lu] [EBP] Loaded ePub: %s\n", millis(), filepath.c_str()); return true; } @@ -363,8 +389,10 @@ bool Epub::load(const bool buildIfMissing) { return false; } - // Parse CSS files after cache reload - parseCssFiles(); + if (!skipLoadingCss) { + // Parse CSS files after cache reload + parseCssFiles(); + } Serial.printf("[%lu] [EBP] Loaded ePub: %s\n", millis(), filepath.c_str()); return true; diff --git a/lib/Epub/Epub.h b/lib/Epub/Epub.h index 4f948c77..85a601a5 100644 --- a/lib/Epub/Epub.h +++ b/lib/Epub/Epub.h @@ -27,12 +27,16 @@ class Epub { std::unique_ptr bookMetadataCache; // CSS parser for styling std::unique_ptr cssParser; + // CSS files + std::vector cssFiles; bool findContentOpfFile(std::string* contentOpfFile) const; bool parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata); bool parseTocNcxFile() const; bool parseTocNavFile() const; - bool parseCssFiles(); + void parseCssFiles() const; + std::string getCssRulesCache() const; + bool loadCssRulesFromCache() const; public: explicit Epub(std::string filepath, const std::string& cacheDir) : filepath(std::move(filepath)) { @@ -41,7 +45,7 @@ class Epub { } ~Epub() = default; std::string& getBasePath() { return contentBasePath; } - bool load(bool buildIfMissing = true); + bool load(bool buildIfMissing = true, bool skipLoadingCss = false); bool clearCache() const; void setupCacheDir() const; const std::string& getCachePath() const; diff --git a/lib/Epub/Epub/BookMetadataCache.cpp b/lib/Epub/Epub/BookMetadataCache.cpp index 01dc87e4..e7242138 100644 --- a/lib/Epub/Epub/BookMetadataCache.cpp +++ b/lib/Epub/Epub/BookMetadataCache.cpp @@ -9,7 +9,7 @@ #include "FsHelpers.h" namespace { -constexpr uint8_t BOOK_CACHE_VERSION = 6; +constexpr uint8_t BOOK_CACHE_VERSION = 5; constexpr char bookBinFile[] = "/book.bin"; constexpr char tmpSpineBinFile[] = "/spine.bin.tmp"; constexpr char tmpTocBinFile[] = "/toc.bin.tmp"; @@ -115,14 +115,9 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta constexpr uint32_t headerASize = sizeof(BOOK_CACHE_VERSION) + /* LUT Offset */ sizeof(uint32_t) + sizeof(spineCount) + sizeof(tocCount); - // Calculate CSS files size: count + each string (length + data) - uint32_t cssFilesSize = sizeof(uint16_t); // count - for (const auto& css : metadata.cssFiles) { - cssFilesSize += sizeof(uint32_t) + css.size(); - } const uint32_t metadataSize = metadata.title.size() + metadata.author.size() + metadata.language.size() + metadata.coverItemHref.size() + metadata.textReferenceHref.size() + - sizeof(uint32_t) * 5 + cssFilesSize; + sizeof(uint32_t) * 5; const uint32_t lutSize = sizeof(uint32_t) * spineCount + sizeof(uint32_t) * tocCount; const uint32_t lutOffset = headerASize + metadataSize; @@ -137,11 +132,6 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta serialization::writeString(bookFile, metadata.language); serialization::writeString(bookFile, metadata.coverItemHref); serialization::writeString(bookFile, metadata.textReferenceHref); - // CSS files - serialization::writePod(bookFile, static_cast(metadata.cssFiles.size())); - for (const auto& css : metadata.cssFiles) { - serialization::writeString(bookFile, css); - } // Loop through spine entries, writing LUT positions spineFile.seek(0); @@ -395,16 +385,6 @@ bool BookMetadataCache::load() { serialization::readString(bookFile, coreMetadata.language); serialization::readString(bookFile, coreMetadata.coverItemHref); serialization::readString(bookFile, coreMetadata.textReferenceHref); - // CSS files - uint16_t cssCount; - serialization::readPod(bookFile, cssCount); - coreMetadata.cssFiles.clear(); - coreMetadata.cssFiles.reserve(cssCount); - for (uint16_t i = 0; i < cssCount; i++) { - std::string cssPath; - serialization::readString(bookFile, cssPath); - coreMetadata.cssFiles.push_back(std::move(cssPath)); - } loaded = true; Serial.printf("[%lu] [BMC] Loaded cache data: %d spine, %d TOC entries\n", millis(), spineCount, tocCount); diff --git a/lib/Epub/Epub/BookMetadataCache.h b/lib/Epub/Epub/BookMetadataCache.h index b5ac9385..20ce6559 100644 --- a/lib/Epub/Epub/BookMetadataCache.h +++ b/lib/Epub/Epub/BookMetadataCache.h @@ -14,7 +14,6 @@ class BookMetadataCache { std::string language; std::string coverItemHref; std::string textReferenceHref; - std::vector cssFiles; }; struct SpineEntry {