fix: avoid zip-wide css scan for large epubs (#2213)

## Summary

* **What is the goal of this PR?** 
* This PR ports over Crossink's handling of large EPUBs that helps
prevent crashes during open after the book metadata cache is built.

* **What changes are included?**
* Removes the post-indexing ZIP-wide CSS discovery pass that built an
in-memory map of every ZIP entry.
* Reuses `content.opf` parsing to collect declared CSS files without
writing spine entries again.
* Temporarily releases the loaded book metadata cache while rebuilding
CSS for cached books.
* Parses CSS before reloading `book.bin` after a fresh cache build,
leaving more heap available during CSS rule parsing.

## Additional Context

* User reported their EPUB opening fine on Crossink but would crash on
Crosspoint. Verified this claim on my own devices.
* The crash this addresses happened after `book.bin` was successfully
built, when CSS discovery allocated a large `unordered_map` for ~3k EPUB
ZIP entries.
* Tradeoff: CSS files not declared in `content.opf` are no longer
discovered by scanning the full ZIP. This avoids the high-risk memory
allocation but improperly formatted EPUBs (ones that don't declare their
CSS styles in `content.opf` will render without styling and fallback to
inline styles.
* User provided epub that was crashing prior to this change:
https://www.mediafire.com/file/g57ea4mj13iunvh/Quang+%C3%82m+Chi+Ngo%E1%BA%A1i+-+Nh%C4%A9+C%C4%83n.epub/file

---

### 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 >**_
This commit is contained in:
Julia
2026-05-31 18:31:06 -04:00
committed by GitHub
parent 0d64980d25
commit 03f73fadc7
3 changed files with 87 additions and 41 deletions
+52 -3
View File
@@ -72,9 +72,58 @@ class ZipFile {
bool readFileToStream(const char* filename, Print& out, size_t chunkSize);
template <typename F>
void enumerateFilePaths(F&& callback) const {
for (const auto& entry : fileStatSlimCache) {
callback(std::string_view{entry.first});
bool enumerateFilePaths(F&& callback) {
if (!fileStatSlimCache.empty()) {
for (const auto& entry : fileStatSlimCache) {
callback(std::string_view{entry.first});
}
return true;
}
const bool wasOpen = isOpen();
if (!wasOpen && !open()) {
return false;
}
if (!loadZipDetails()) {
if (!wasOpen) {
close();
}
return false;
}
file.seek(zipDetails.centralDirOffset);
uint32_t sig;
char itemName[256];
while (file.available()) {
file.read(&sig, 4);
if (sig != 0x02014b50) {
break;
}
file.seekCur(24);
uint16_t nameLen, m, k;
file.read(&nameLen, 2);
file.read(&m, 2);
file.read(&k, 2);
file.seekCur(12);
if (nameLen < sizeof(itemName)) {
file.read(itemName, nameLen);
itemName[nameLen] = '\0';
callback(std::string_view{itemName, nameLen});
} else {
file.seekCur(nameLen);
}
file.seekCur(m + k);
}
if (!wasOpen) {
close();
}
return true;
}
};