fix: Port upstream 1.1.0-rc PRs #1014, #1018, #990 and align #1002

Port three new upstream commits and align the existing #1002 port:

- PR #1014: Strip unused CSS rules by filtering unsupported selector
  types (+, >, [, :, #, ~, *, descendants) in processRuleBlockWithStyle.
  Fix normalized() trailing whitespace to also strip newlines.
- PR #1018: Add deleteCache() to CssParser, move CSS_CACHE_VERSION to
  static class member, remove stale cache on version mismatch, invalidate
  section caches (Storage.removeDir) when CSS is rebuilt. Refactor
  parseCssFiles() to early-return when cache exists.
- PR #990: Adapt classic theme continue-reading card width to cover
  aspect ratio (clamped to 90% screen width), increase homeTopPadding
  20->40, fix centering with rect.x offset for boxX/continueBoxX.
- #1002 alignment: Add tryInterpretLength() to skip non-numeric CSS
  values (auto, inherit), add "both width and height set" image sizing
  branch in ChapterHtmlSlimParser.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
cottongin
2026-02-20 15:52:30 -05:00
parent 18be265a4a
commit 55a1fef01a
6 changed files with 195 additions and 109 deletions

View File

@@ -213,74 +213,69 @@ bool Epub::parseTocNavFile() const {
}
void Epub::parseCssFiles() const {
// Maximum CSS file size we'll attempt to parse (uncompressed)
// Larger files risk memory exhaustion on ESP32
constexpr size_t MAX_CSS_FILE_SIZE = 128 * 1024; // 128KB
// Minimum heap required before attempting CSS parsing
constexpr size_t MIN_HEAP_FOR_CSS_PARSING = 64 * 1024; // 64KB
constexpr size_t MAX_CSS_FILE_SIZE = 128 * 1024;
constexpr size_t MIN_HEAP_FOR_CSS_PARSING = 64 * 1024;
if (cssFiles.empty()) {
LOG_DBG("EBP", "No CSS files to parse, but CssParser created for inline styles");
}
// See if we have a cached version of the CSS rules
if (!cssParser->hasCache()) {
// No cache yet - parse CSS files
for (const auto& cssPath : cssFiles) {
LOG_DBG("EBP", "Parsing CSS file: %s", cssPath.c_str());
LOG_DBG("EBP", "CSS files to parse: %zu", cssFiles.size());
// Check heap before parsing - CSS parsing allocates heavily
const uint32_t freeHeap = ESP.getFreeHeap();
if (freeHeap < MIN_HEAP_FOR_CSS_PARSING) {
LOG_ERR("EBP", "Insufficient heap for CSS parsing (%u bytes free, need %zu), skipping: %s", freeHeap,
MIN_HEAP_FOR_CSS_PARSING, cssPath.c_str());
if (cssParser->hasCache()) {
LOG_DBG("EBP", "CSS cache exists, skipping parseCssFiles");
return;
}
for (const auto& cssPath : cssFiles) {
LOG_DBG("EBP", "Parsing CSS file: %s", cssPath.c_str());
const uint32_t freeHeap = ESP.getFreeHeap();
if (freeHeap < MIN_HEAP_FOR_CSS_PARSING) {
LOG_ERR("EBP", "Insufficient heap for CSS parsing (%u bytes free, need %zu), skipping: %s", freeHeap,
MIN_HEAP_FOR_CSS_PARSING, cssPath.c_str());
continue;
}
size_t cssFileSize = 0;
if (getItemSize(cssPath, &cssFileSize)) {
if (cssFileSize > MAX_CSS_FILE_SIZE) {
LOG_ERR("EBP", "CSS file too large (%zu bytes > %zu max), skipping: %s", cssFileSize, MAX_CSS_FILE_SIZE,
cssPath.c_str());
continue;
}
}
// Check CSS file size before decompressing - skip files that are too large
size_t cssFileSize = 0;
if (getItemSize(cssPath, &cssFileSize)) {
if (cssFileSize > MAX_CSS_FILE_SIZE) {
LOG_ERR("EBP", "CSS file too large (%zu bytes > %zu max), skipping: %s", cssFileSize, MAX_CSS_FILE_SIZE,
cssPath.c_str());
continue;
}
}
// Extract CSS file to temp location
const auto tmpCssPath = getCachePath() + "/.tmp.css";
FsFile tempCssFile;
if (!Storage.openFileForWrite("EBP", tmpCssPath, tempCssFile)) {
LOG_ERR("EBP", "Could not create temp CSS file");
continue;
}
if (!readItemContentsToStream(cssPath, tempCssFile, 1024)) {
LOG_ERR("EBP", "Could not read CSS file: %s", cssPath.c_str());
tempCssFile.close();
Storage.remove(tmpCssPath.c_str());
continue;
}
tempCssFile.close();
// Parse the CSS file
if (!Storage.openFileForRead("EBP", tmpCssPath, tempCssFile)) {
LOG_ERR("EBP", "Could not open temp CSS file for reading");
Storage.remove(tmpCssPath.c_str());
continue;
}
cssParser->loadFromStream(tempCssFile);
const auto tmpCssPath = getCachePath() + "/.tmp.css";
FsFile tempCssFile;
if (!Storage.openFileForWrite("EBP", tmpCssPath, tempCssFile)) {
LOG_ERR("EBP", "Could not create temp CSS file");
continue;
}
if (!readItemContentsToStream(cssPath, tempCssFile, 1024)) {
LOG_ERR("EBP", "Could not read CSS file: %s", cssPath.c_str());
tempCssFile.close();
Storage.remove(tmpCssPath.c_str());
continue;
}
tempCssFile.close();
// Save to cache for next time
if (!cssParser->saveToCache()) {
LOG_ERR("EBP", "Failed to save CSS rules to cache");
if (!Storage.openFileForRead("EBP", tmpCssPath, tempCssFile)) {
LOG_ERR("EBP", "Could not open temp CSS file for reading");
Storage.remove(tmpCssPath.c_str());
continue;
}
cssParser->clear();
LOG_DBG("EBP", "Loaded %zu CSS style rules from %zu files", cssParser->ruleCount(), cssFiles.size());
cssParser->loadFromStream(tempCssFile);
tempCssFile.close();
Storage.remove(tmpCssPath.c_str());
}
if (!cssParser->saveToCache()) {
LOG_ERR("EBP", "Failed to save CSS rules to cache");
}
cssParser->clear();
LOG_DBG("EBP", "Loaded %zu CSS style rules from %zu files", cssParser->ruleCount(), cssFiles.size());
}
// load in the meta data for the epub file
@@ -294,14 +289,17 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
// Try to load existing cache first
if (bookMetadataCache->load()) {
if (!skipLoadingCss && !cssParser->hasCache()) {
LOG_DBG("EBP", "Warning: CSS rules cache not found, attempting to parse CSS files");
// to get CSS file list
if (!parseContentOpf(bookMetadataCache->coreMetadata)) {
LOG_ERR("EBP", "Could not parse content.opf from cached bookMetadata for CSS files");
// continue anyway - book will work without CSS and we'll still load any inline style CSS
if (!skipLoadingCss) {
if (!cssParser->hasCache() || !cssParser->loadFromCache()) {
LOG_DBG("EBP", "CSS rules cache missing or stale, attempting to parse CSS files");
cssParser->deleteCache();
if (!parseContentOpf(bookMetadataCache->coreMetadata)) {
LOG_ERR("EBP", "Could not parse content.opf from cached bookMetadata for CSS files");
}
parseCssFiles();
Storage.removeDir((cachePath + "/sections").c_str());
}
parseCssFiles();
}
LOG_DBG("EBP", "Loaded ePub: %s", filepath.c_str());
return true;
@@ -400,8 +398,8 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
}
if (!skipLoadingCss) {
// Parse CSS files after cache reload
parseCssFiles();
Storage.removeDir((cachePath + "/sections").c_str());
}
LOG_DBG("EBP", "Loaded ePub: %s", filepath.c_str());