From 57267f5372034a7ff91f09e8ab05c3501fc9e368 Mon Sep 17 00:00:00 2001 From: Dave Allie Date: Fri, 20 Feb 2026 16:35:49 +1100 Subject: [PATCH] fix: Strip unused CSS rules (#1014) ## Summary * In a sample book I loaded, it had 900+ CSS rules, and took up 180kB of memory loading the cache in * Looking at the rules, a lot of them were completely useless as we only ever apply look for 3 kinds of CSS rules: * `tag` * `tag.class1` * `.class1` * Stripping out CSS rules with descendant, nested, attribute matching, sibling matching, pseudo element selection (as we never actually read these from the cache) reduced the rule count down to 200 ## Additional Context * I've left in `.class1.class2` rules for now, even though we technically can never match on them as they're likely to be addressed soonest out of the all the CSS expansion * Because we don't ever delete the CSS cache, users will need to delete the book cache through the menu in order to get this new logic * A new PR should be done up to address this - tracked here https://github.com/crosspoint-reader/crosspoint-reader/issues/1015 --- ### 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? No --- lib/Epub/Epub/css/CssParser.cpp | 54 ++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/lib/Epub/Epub/css/CssParser.cpp b/lib/Epub/Epub/css/CssParser.cpp index 8bdd0f1a..9415f155 100644 --- a/lib/Epub/Epub/css/CssParser.cpp +++ b/lib/Epub/Epub/css/CssParser.cpp @@ -74,7 +74,7 @@ std::string CssParser::normalized(const std::string& s) { } // Remove trailing space - if (!result.empty() && result.back() == ' ') { + while (!result.empty() && (result.back() == ' ' || result.back() == '\n')) { result.pop_back(); } return result; @@ -365,6 +365,56 @@ void CssParser::processRuleBlockWithStyle(const std::string& selectorGroup, cons std::string key = normalized(sel); if (key.empty()) continue; + // TODO: Consider adding support for sibling css selectors in the future + // Ensure no + in selector as we don't support adjacent CSS selectors for now + if (key.find('+') != std::string_view::npos) { + continue; + } + + // TODO: Consider adding support for direct nested css selectors in the future + // Ensure no > in selector as we don't support nested CSS selectors for now + if (key.find('>') != std::string_view::npos) { + continue; + } + + // TODO: Consider adding support for attribute css selectors in the future + // Ensure no [ in selector as we don't support attribute CSS selectors for now + if (key.find('[') != std::string_view::npos) { + continue; + } + + // TODO: Consider adding support for pseudo selectors in the future + // Ensure no : in selector as we don't support pseudo CSS selectors for now + if (key.find(':') != std::string_view::npos) { + continue; + } + + // TODO: Consider adding support for ID css selectors in the future + // Ensure no # in selector as we don't support ID CSS selectors for now + if (key.find('#') != std::string_view::npos) { + continue; + } + + // TODO: Consider adding support for general sibling combinator selectors in the future + // Ensure no ~ in selector as we don't support general sibling combinator CSS selectors for now + if (key.find('~') != std::string_view::npos) { + continue; + } + + // TODO: Consider adding support for wildcard css selectors in the future + // Ensure no * in selector as we don't support wildcard CSS selectors for now + if (key.find('*') != std::string_view::npos) { + continue; + } + + // TODO: Add support for more complex selectors in the future + // At the moment, we only ever check for `tag`, `tag.class1` or `.class1` + // If the selector has whitespace in it, then it's either a CSS selector for a descendant element (e.g. `tag1 tag2`) + // or some other slightly more advanced CSS selector which we don't support yet + if (key.find(' ') != std::string_view::npos) { + continue; + } + // Skip if this would exceed the rule limit if (rulesBySelector_.size() >= MAX_RULES) { LOG_DBG("CSS", "Reached max rules limit, stopping selector processing"); @@ -550,6 +600,7 @@ CssStyle CssParser::resolveStyle(const std::string& tagName, const std::string& result.applyOver(tagIt->second); } + // TODO: Support combinations of classes (e.g. style on .class1.class2) // 2. Apply class styles (medium priority) if (!classAttr.empty()) { const auto classes = splitWhitespace(classAttr); @@ -563,6 +614,7 @@ CssStyle CssParser::resolveStyle(const std::string& tagName, const std::string& } } + // TODO: Support combinations of classes (e.g. style on p.class1.class2) // 3. Apply element.class styles (higher priority) for (const auto& cls : classes) { std::string combinedKey = tag + "." + normalized(cls);