merge upstream/master: logging pragma, screenshot retrieval, nbsp fix

Merge 3 upstream commits into mod/master:
- feat: Allow screenshot retrieval from device (#820)
- feat: Add central logging pragma (#843)
- fix: Account for nbsp character as non-breaking space (#757)

Conflict resolution:
- src/main.cpp: kept mod's HalPowerManager + upstream's Logging/screenshot
- SleepActivity.cpp: kept mod's letterbox fill rework, applied LOG_* pattern

Additional changes for logging compatibility:
- Converted remaining Serial.printf calls in mod files to LOG_* macros
  (HalPowerManager, BookSettings, BookmarkStore, GfxRenderer)
- Added ENABLE_SERIAL_LOG and LOG_LEVEL=2 to [env:mod] build flags

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
cottongin
2026-02-13 16:27:58 -05:00
58 changed files with 1188 additions and 768 deletions

View File

@@ -2,8 +2,8 @@
#include <FsHelpers.h>
#include <HalStorage.h>
#include <HardwareSerial.h>
#include <JpegToBmpConverter.h>
#include <Logging.h>
#include <ZipFile.h>
#include "Epub/parsers/ContainerParser.h"
@@ -17,7 +17,7 @@ bool Epub::findContentOpfFile(std::string* contentOpfFile) const {
// Get file size without loading it all into heap
if (!getItemSize(containerPath, &containerSize)) {
Serial.printf("[%lu] [EBP] Could not find or size META-INF/container.xml\n", millis());
LOG_ERR("EBP", "Could not find or size META-INF/container.xml");
return false;
}
@@ -29,13 +29,13 @@ bool Epub::findContentOpfFile(std::string* contentOpfFile) const {
// Stream read (reusing your existing stream logic)
if (!readItemContentsToStream(containerPath, containerParser, 512)) {
Serial.printf("[%lu] [EBP] Could not read META-INF/container.xml\n", millis());
LOG_ERR("EBP", "Could not read META-INF/container.xml");
return false;
}
// Extract the result
if (containerParser.fullPath.empty()) {
Serial.printf("[%lu] [EBP] Could not find valid rootfile in container.xml\n", millis());
LOG_ERR("EBP", "Could not find valid rootfile in container.xml");
return false;
}
@@ -46,28 +46,28 @@ bool Epub::findContentOpfFile(std::string* contentOpfFile) const {
bool Epub::parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata) {
std::string contentOpfFilePath;
if (!findContentOpfFile(&contentOpfFilePath)) {
Serial.printf("[%lu] [EBP] Could not find content.opf in zip\n", millis());
LOG_ERR("EBP", "Could not find content.opf in zip");
return false;
}
contentBasePath = contentOpfFilePath.substr(0, contentOpfFilePath.find_last_of('/') + 1);
Serial.printf("[%lu] [EBP] Parsing content.opf: %s\n", millis(), contentOpfFilePath.c_str());
LOG_DBG("EBP", "Parsing content.opf: %s", contentOpfFilePath.c_str());
size_t contentOpfSize;
if (!getItemSize(contentOpfFilePath, &contentOpfSize)) {
Serial.printf("[%lu] [EBP] Could not get size of content.opf\n", millis());
LOG_ERR("EBP", "Could not get size of content.opf");
return false;
}
ContentOpfParser opfParser(getCachePath(), getBasePath(), contentOpfSize, bookMetadataCache.get());
if (!opfParser.setup()) {
Serial.printf("[%lu] [EBP] Could not setup content.opf parser\n", millis());
LOG_ERR("EBP", "Could not setup content.opf parser");
return false;
}
if (!readItemContentsToStream(contentOpfFilePath, opfParser, 1024)) {
Serial.printf("[%lu] [EBP] Could not read content.opf\n", millis());
LOG_ERR("EBP", "Could not read content.opf");
return false;
}
@@ -90,18 +90,18 @@ bool Epub::parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata) {
cssFiles = opfParser.cssFiles;
}
Serial.printf("[%lu] [EBP] Successfully parsed content.opf\n", millis());
LOG_DBG("EBP", "Successfully parsed content.opf");
return true;
}
bool Epub::parseTocNcxFile() const {
// the ncx file should have been specified in the content.opf file
if (tocNcxItem.empty()) {
Serial.printf("[%lu] [EBP] No ncx file specified\n", millis());
LOG_DBG("EBP", "No ncx file specified");
return false;
}
Serial.printf("[%lu] [EBP] Parsing toc ncx file: %s\n", millis(), tocNcxItem.c_str());
LOG_DBG("EBP", "Parsing toc ncx file: %s", tocNcxItem.c_str());
const auto tmpNcxPath = getCachePath() + "/toc.ncx";
FsFile tempNcxFile;
@@ -118,14 +118,14 @@ bool Epub::parseTocNcxFile() const {
TocNcxParser ncxParser(contentBasePath, ncxSize, bookMetadataCache.get());
if (!ncxParser.setup()) {
Serial.printf("[%lu] [EBP] Could not setup toc ncx parser\n", millis());
LOG_ERR("EBP", "Could not setup toc ncx parser");
tempNcxFile.close();
return false;
}
const auto ncxBuffer = static_cast<uint8_t*>(malloc(1024));
if (!ncxBuffer) {
Serial.printf("[%lu] [EBP] Could not allocate memory for toc ncx parser\n", millis());
LOG_ERR("EBP", "Could not allocate memory for toc ncx parser");
tempNcxFile.close();
return false;
}
@@ -136,7 +136,7 @@ bool Epub::parseTocNcxFile() const {
const auto processedSize = ncxParser.write(ncxBuffer, readSize);
if (processedSize != readSize) {
Serial.printf("[%lu] [EBP] Could not process all toc ncx data\n", millis());
LOG_ERR("EBP", "Could not process all toc ncx data");
free(ncxBuffer);
tempNcxFile.close();
return false;
@@ -147,18 +147,18 @@ bool Epub::parseTocNcxFile() const {
tempNcxFile.close();
Storage.remove(tmpNcxPath.c_str());
Serial.printf("[%lu] [EBP] Parsed TOC items\n", millis());
LOG_DBG("EBP", "Parsed TOC items");
return true;
}
bool Epub::parseTocNavFile() const {
// the nav file should have been specified in the content.opf file (EPUB 3)
if (tocNavItem.empty()) {
Serial.printf("[%lu] [EBP] No nav file specified\n", millis());
LOG_DBG("EBP", "No nav file specified");
return false;
}
Serial.printf("[%lu] [EBP] Parsing toc nav file: %s\n", millis(), tocNavItem.c_str());
LOG_DBG("EBP", "Parsing toc nav file: %s", tocNavItem.c_str());
const auto tmpNavPath = getCachePath() + "/toc.nav";
FsFile tempNavFile;
@@ -178,13 +178,13 @@ bool Epub::parseTocNavFile() const {
TocNavParser navParser(navContentBasePath, navSize, bookMetadataCache.get());
if (!navParser.setup()) {
Serial.printf("[%lu] [EBP] Could not setup toc nav parser\n", millis());
LOG_ERR("EBP", "Could not setup toc nav parser");
return false;
}
const auto navBuffer = static_cast<uint8_t*>(malloc(1024));
if (!navBuffer) {
Serial.printf("[%lu] [EBP] Could not allocate memory for toc nav parser\n", millis());
LOG_ERR("EBP", "Could not allocate memory for toc nav parser");
return false;
}
@@ -193,7 +193,7 @@ bool Epub::parseTocNavFile() const {
const auto processedSize = navParser.write(navBuffer, readSize);
if (processedSize != readSize) {
Serial.printf("[%lu] [EBP] Could not process all toc nav data\n", millis());
LOG_ERR("EBP", "Could not process all toc nav data");
free(navBuffer);
tempNavFile.close();
return false;
@@ -204,7 +204,7 @@ bool Epub::parseTocNavFile() const {
tempNavFile.close();
Storage.remove(tmpNavPath.c_str());
Serial.printf("[%lu] [EBP] Parsed TOC nav items\n", millis());
LOG_DBG("EBP", "Parsed TOC nav items");
return true;
}
@@ -215,35 +215,35 @@ bool Epub::loadCssRulesFromCache() const {
if (Storage.openFileForRead("EBP", getCssRulesCache(), cssCacheFile)) {
if (cssParser->loadFromCache(cssCacheFile)) {
cssCacheFile.close();
Serial.printf("[%lu] [EBP] Loaded CSS rules from cache\n", millis());
LOG_DBG("EBP", "Loaded CSS rules from cache");
return true;
}
cssCacheFile.close();
Serial.printf("[%lu] [EBP] CSS cache invalid, reparsing\n", millis());
LOG_DBG("EBP", "CSS cache invalid, reparsing");
}
return false;
}
void Epub::parseCssFiles() const {
if (cssFiles.empty()) {
Serial.printf("[%lu] [EBP] No CSS files to parse, but CssParser created for inline styles\n", millis());
LOG_DBG("EBP", "No CSS files to parse, but CssParser created for inline styles");
}
// 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());
LOG_DBG("EBP", "Parsing CSS file: %s", cssPath.c_str());
// Extract CSS file to temp location
const auto tmpCssPath = getCachePath() + "/.tmp.css";
FsFile tempCssFile;
if (!Storage.openFileForWrite("EBP", tmpCssPath, tempCssFile)) {
Serial.printf("[%lu] [EBP] Could not create temp CSS file\n", millis());
LOG_ERR("EBP", "Could not create temp CSS file");
continue;
}
if (!readItemContentsToStream(cssPath, tempCssFile, 1024)) {
Serial.printf("[%lu] [EBP] Could not read CSS file: %s\n", millis(), cssPath.c_str());
LOG_ERR("EBP", "Could not read CSS file: %s", cssPath.c_str());
tempCssFile.close();
Storage.remove(tmpCssPath.c_str());
continue;
@@ -252,7 +252,7 @@ void Epub::parseCssFiles() const {
// Parse the CSS file
if (!Storage.openFileForRead("EBP", tmpCssPath, tempCssFile)) {
Serial.printf("[%lu] [EBP] Could not open temp CSS file for reading\n", millis());
LOG_ERR("EBP", "Could not open temp CSS file for reading");
Storage.remove(tmpCssPath.c_str());
continue;
}
@@ -268,14 +268,13 @@ void Epub::parseCssFiles() const {
cssCacheFile.close();
}
Serial.printf("[%lu] [EBP] Loaded %zu CSS style rules from %zu files\n", millis(), cssParser->ruleCount(),
cssFiles.size());
LOG_DBG("EBP", "Loaded %zu CSS style rules from %zu files", cssParser->ruleCount(), cssFiles.size());
}
}
// load in the meta data for the epub file
bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
Serial.printf("[%lu] [EBP] Loading ePub: %s\n", millis(), filepath.c_str());
LOG_DBG("EBP", "Loading ePub: %s", filepath.c_str());
// Initialize spine/TOC cache
bookMetadataCache.reset(new BookMetadataCache(cachePath));
@@ -285,15 +284,15 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
// Try to load existing cache first
if (bookMetadataCache->load()) {
if (!skipLoadingCss && !loadCssRulesFromCache()) {
Serial.printf("[%lu] [EBP] Warning: CSS rules cache not found, attempting to parse CSS files\n", millis());
LOG_DBG("EBP", "Warning: CSS rules cache not found, attempting to parse CSS files");
// 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());
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
}
parseCssFiles();
}
Serial.printf("[%lu] [EBP] Loaded ePub: %s\n", millis(), filepath.c_str());
LOG_DBG("EBP", "Loaded ePub: %s", filepath.c_str());
return true;
}
@@ -303,14 +302,14 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
}
// Cache doesn't exist or is invalid, build it
Serial.printf("[%lu] [EBP] Cache not found, building spine/TOC cache\n", millis());
LOG_DBG("EBP", "Cache not found, building spine/TOC cache");
setupCacheDir();
const uint32_t indexingStart = millis();
// Begin building cache - stream entries to disk immediately
if (!bookMetadataCache->beginWrite()) {
Serial.printf("[%lu] [EBP] Could not begin writing cache\n", millis());
LOG_ERR("EBP", "Could not begin writing cache");
return false;
}
@@ -318,23 +317,23 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
const uint32_t opfStart = millis();
BookMetadataCache::BookMetadata bookMetadata;
if (!bookMetadataCache->beginContentOpfPass()) {
Serial.printf("[%lu] [EBP] Could not begin writing content.opf pass\n", millis());
LOG_ERR("EBP", "Could not begin writing content.opf pass");
return false;
}
if (!parseContentOpf(bookMetadata)) {
Serial.printf("[%lu] [EBP] Could not parse content.opf\n", millis());
LOG_ERR("EBP", "Could not parse content.opf");
return false;
}
if (!bookMetadataCache->endContentOpfPass()) {
Serial.printf("[%lu] [EBP] Could not end writing content.opf pass\n", millis());
LOG_ERR("EBP", "Could not end writing content.opf pass");
return false;
}
Serial.printf("[%lu] [EBP] OPF pass completed in %lu ms\n", millis(), millis() - opfStart);
LOG_DBG("EBP", "OPF pass completed in %lu ms", millis() - opfStart);
// TOC Pass - try EPUB 3 nav first, fall back to NCX
const uint32_t tocStart = millis();
if (!bookMetadataCache->beginTocPass()) {
Serial.printf("[%lu] [EBP] Could not begin writing toc pass\n", millis());
LOG_ERR("EBP", "Could not begin writing toc pass");
return false;
}
@@ -342,50 +341,50 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
// Try EPUB 3 nav document first (preferred)
if (!tocNavItem.empty()) {
Serial.printf("[%lu] [EBP] Attempting to parse EPUB 3 nav document\n", millis());
LOG_DBG("EBP", "Attempting to parse EPUB 3 nav document");
tocParsed = parseTocNavFile();
}
// Fall back to NCX if nav parsing failed or wasn't available
if (!tocParsed && !tocNcxItem.empty()) {
Serial.printf("[%lu] [EBP] Falling back to NCX TOC\n", millis());
LOG_DBG("EBP", "Falling back to NCX TOC");
tocParsed = parseTocNcxFile();
}
if (!tocParsed) {
Serial.printf("[%lu] [EBP] Warning: Could not parse any TOC format\n", millis());
LOG_ERR("EBP", "Warning: Could not parse any TOC format");
// Continue anyway - book will work without TOC
}
if (!bookMetadataCache->endTocPass()) {
Serial.printf("[%lu] [EBP] Could not end writing toc pass\n", millis());
LOG_ERR("EBP", "Could not end writing toc pass");
return false;
}
Serial.printf("[%lu] [EBP] TOC pass completed in %lu ms\n", millis(), millis() - tocStart);
LOG_DBG("EBP", "TOC pass completed in %lu ms", millis() - tocStart);
// Close the cache files
if (!bookMetadataCache->endWrite()) {
Serial.printf("[%lu] [EBP] Could not end writing cache\n", millis());
LOG_ERR("EBP", "Could not end writing cache");
return false;
}
// Build final book.bin
const uint32_t buildStart = millis();
if (!bookMetadataCache->buildBookBin(filepath, bookMetadata)) {
Serial.printf("[%lu] [EBP] Could not update mappings and sizes\n", millis());
LOG_ERR("EBP", "Could not update mappings and sizes");
return false;
}
Serial.printf("[%lu] [EBP] buildBookBin completed in %lu ms\n", millis(), millis() - buildStart);
Serial.printf("[%lu] [EBP] Total indexing completed in %lu ms\n", millis(), millis() - indexingStart);
LOG_DBG("EBP", "buildBookBin completed in %lu ms", millis() - buildStart);
LOG_DBG("EBP", "Total indexing completed in %lu ms", millis() - indexingStart);
if (!bookMetadataCache->cleanupTmpFiles()) {
Serial.printf("[%lu] [EBP] Could not cleanup tmp files - ignoring\n", millis());
LOG_DBG("EBP", "Could not cleanup tmp files - ignoring");
}
// Reload the cache from disk so it's in the correct state
bookMetadataCache.reset(new BookMetadataCache(cachePath));
if (!bookMetadataCache->load()) {
Serial.printf("[%lu] [EBP] Failed to reload cache after writing\n", millis());
LOG_ERR("EBP", "Failed to reload cache after writing");
return false;
}
@@ -394,22 +393,22 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
parseCssFiles();
}
Serial.printf("[%lu] [EBP] Loaded ePub: %s\n", millis(), filepath.c_str());
LOG_DBG("EBP", "Loaded ePub: %s", filepath.c_str());
return true;
}
bool Epub::clearCache() const {
if (!Storage.exists(cachePath.c_str())) {
Serial.printf("[%lu] [EPB] Cache does not exist, no action needed\n", millis());
LOG_DBG("EPB", "Cache does not exist, no action needed");
return true;
}
if (!Storage.removeDir(cachePath.c_str())) {
Serial.printf("[%lu] [EPB] Failed to clear cache\n", millis());
LOG_ERR("EPB", "Failed to clear cache");
return false;
}
Serial.printf("[%lu] [EPB] Cache cleared successfully\n", millis());
LOG_DBG("EPB", "Cache cleared successfully");
return true;
}
@@ -464,19 +463,19 @@ bool Epub::generateCoverBmp(bool cropped) const {
}
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
Serial.printf("[%lu] [EBP] Cannot generate cover BMP, cache not loaded\n", millis());
LOG_ERR("EBP", "Cannot generate cover BMP, cache not loaded");
return false;
}
const auto coverImageHref = bookMetadataCache->coreMetadata.coverItemHref;
if (coverImageHref.empty()) {
Serial.printf("[%lu] [EBP] No known cover image\n", millis());
LOG_ERR("EBP", "No known cover image");
return false;
}
if (coverImageHref.substr(coverImageHref.length() - 4) == ".jpg" ||
coverImageHref.substr(coverImageHref.length() - 5) == ".jpeg") {
Serial.printf("[%lu] [EBP] Generating BMP from JPG cover image (%s mode)\n", millis(), cropped ? "cropped" : "fit");
LOG_DBG("EBP", "Generating BMP from JPG cover image (%s mode)", cropped ? "cropped" : "fit");
const auto coverJpgTempPath = getCachePath() + "/.cover.jpg";
FsFile coverJpg;
@@ -501,13 +500,13 @@ bool Epub::generateCoverBmp(bool cropped) const {
Storage.remove(coverJpgTempPath.c_str());
if (!success) {
Serial.printf("[%lu] [EBP] Failed to generate BMP from JPG cover image\n", millis());
LOG_ERR("EBP", "Failed to generate BMP from cover image");
Storage.remove(getCoverBmpPath(cropped).c_str());
}
Serial.printf("[%lu] [EBP] Generated BMP from JPG cover image, success: %s\n", millis(), success ? "yes" : "no");
LOG_DBG("EBP", "Generated BMP from cover image, success: %s", success ? "yes" : "no");
return success;
} else {
Serial.printf("[%lu] [EBP] Cover image is not a JPG, skipping\n", millis());
LOG_ERR("EBP", "Cover image is not a supported format, skipping");
}
return false;
@@ -523,16 +522,16 @@ bool Epub::generateThumbBmp(int height) const {
}
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
Serial.printf("[%lu] [EBP] Cannot generate thumb BMP, cache not loaded\n", millis());
LOG_ERR("EBP", "Cannot generate thumb BMP, cache not loaded");
return false;
}
const auto coverImageHref = bookMetadataCache->coreMetadata.coverItemHref;
if (coverImageHref.empty()) {
Serial.printf("[%lu] [EBP] No known cover image for thumbnail\n", millis());
LOG_DBG("EBP", "No known cover image for thumbnail");
} else if (coverImageHref.substr(coverImageHref.length() - 4) == ".jpg" ||
coverImageHref.substr(coverImageHref.length() - 5) == ".jpeg") {
Serial.printf("[%lu] [EBP] Generating thumb BMP from JPG cover image\n", millis());
LOG_DBG("EBP", "Generating thumb BMP from JPG cover image");
const auto coverJpgTempPath = getCachePath() + "/.cover.jpg";
FsFile coverJpg;
@@ -562,14 +561,13 @@ bool Epub::generateThumbBmp(int height) const {
Storage.remove(coverJpgTempPath.c_str());
if (!success) {
Serial.printf("[%lu] [EBP] Failed to generate thumb BMP from JPG cover image\n", millis());
LOG_ERR("EBP", "Failed to generate thumb BMP from JPG cover image");
Storage.remove(getThumbBmpPath(height).c_str());
}
Serial.printf("[%lu] [EBP] Generated thumb BMP from JPG cover image, success: %s\n", millis(),
success ? "yes" : "no");
LOG_DBG("EBP", "Generated thumb BMP from JPG cover image, success: %s", success ? "yes" : "no");
return success;
} else {
Serial.printf("[%lu] [EBP] Cover image is not a JPG, skipping thumbnail\n", millis());
LOG_ERR("EBP", "Cover image is not a supported format, skipping thumbnail");
}
// Write an empty bmp file to avoid generation attempts in the future
@@ -581,7 +579,7 @@ bool Epub::generateThumbBmp(int height) const {
uint8_t* Epub::readItemContentsToBytes(const std::string& itemHref, size_t* size, const bool trailingNullByte) const {
if (itemHref.empty()) {
Serial.printf("[%lu] [EBP] Failed to read item, empty href\n", millis());
LOG_DBG("EBP", "Failed to read item, empty href");
return nullptr;
}
@@ -589,7 +587,7 @@ uint8_t* Epub::readItemContentsToBytes(const std::string& itemHref, size_t* size
const auto content = ZipFile(filepath).readFileToMemory(path.c_str(), size, trailingNullByte);
if (!content) {
Serial.printf("[%lu] [EBP] Failed to read item %s\n", millis(), path.c_str());
LOG_DBG("EBP", "Failed to read item %s", path.c_str());
return nullptr;
}
@@ -598,7 +596,7 @@ uint8_t* Epub::readItemContentsToBytes(const std::string& itemHref, size_t* size
bool Epub::readItemContentsToStream(const std::string& itemHref, Print& out, const size_t chunkSize) const {
if (itemHref.empty()) {
Serial.printf("[%lu] [EBP] Failed to read item, empty href\n", millis());
LOG_DBG("EBP", "Failed to read item, empty href");
return false;
}
@@ -622,12 +620,12 @@ size_t Epub::getCumulativeSpineItemSize(const int spineIndex) const { return get
BookMetadataCache::SpineEntry Epub::getSpineItem(const int spineIndex) const {
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
Serial.printf("[%lu] [EBP] getSpineItem called but cache not loaded\n", millis());
LOG_ERR("EBP", "getSpineItem called but cache not loaded");
return {};
}
if (spineIndex < 0 || spineIndex >= bookMetadataCache->getSpineCount()) {
Serial.printf("[%lu] [EBP] getSpineItem index:%d is out of range\n", millis(), spineIndex);
LOG_ERR("EBP", "getSpineItem index:%d is out of range", spineIndex);
return bookMetadataCache->getSpineEntry(0);
}
@@ -636,12 +634,12 @@ BookMetadataCache::SpineEntry Epub::getSpineItem(const int spineIndex) const {
BookMetadataCache::TocEntry Epub::getTocItem(const int tocIndex) const {
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
Serial.printf("[%lu] [EBP] getTocItem called but cache not loaded\n", millis());
LOG_DBG("EBP", "getTocItem called but cache not loaded");
return {};
}
if (tocIndex < 0 || tocIndex >= bookMetadataCache->getTocCount()) {
Serial.printf("[%lu] [EBP] getTocItem index:%d is out of range\n", millis(), tocIndex);
LOG_DBG("EBP", "getTocItem index:%d is out of range", tocIndex);
return {};
}
@@ -659,18 +657,18 @@ int Epub::getTocItemsCount() const {
// work out the section index for a toc index
int Epub::getSpineIndexForTocIndex(const int tocIndex) const {
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
Serial.printf("[%lu] [EBP] getSpineIndexForTocIndex called but cache not loaded\n", millis());
LOG_ERR("EBP", "getSpineIndexForTocIndex called but cache not loaded");
return 0;
}
if (tocIndex < 0 || tocIndex >= bookMetadataCache->getTocCount()) {
Serial.printf("[%lu] [EBP] getSpineIndexForTocIndex: tocIndex %d out of range\n", millis(), tocIndex);
LOG_ERR("EBP", "getSpineIndexForTocIndex: tocIndex %d out of range", tocIndex);
return 0;
}
const int spineIndex = bookMetadataCache->getTocEntry(tocIndex).spineIndex;
if (spineIndex < 0) {
Serial.printf("[%lu] [EBP] Section not found for TOC index %d\n", millis(), tocIndex);
LOG_DBG("EBP", "Section not found for TOC index %d", tocIndex);
return 0;
}
@@ -688,14 +686,13 @@ size_t Epub::getBookSize() const {
int Epub::getSpineIndexForTextReference() const {
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
Serial.printf("[%lu] [EBP] getSpineIndexForTextReference called but cache not loaded\n", millis());
LOG_ERR("EBP", "getSpineIndexForTextReference called but cache not loaded");
return 0;
}
Serial.printf("[%lu] [ERS] Core Metadata: cover(%d)=%s, textReference(%d)=%s\n", millis(),
bookMetadataCache->coreMetadata.coverItemHref.size(),
bookMetadataCache->coreMetadata.coverItemHref.c_str(),
bookMetadataCache->coreMetadata.textReferenceHref.size(),
bookMetadataCache->coreMetadata.textReferenceHref.c_str());
LOG_DBG("EBP", "Core Metadata: cover(%d)=%s, textReference(%d)=%s",
bookMetadataCache->coreMetadata.coverItemHref.size(), bookMetadataCache->coreMetadata.coverItemHref.c_str(),
bookMetadataCache->coreMetadata.textReferenceHref.size(),
bookMetadataCache->coreMetadata.textReferenceHref.c_str());
if (bookMetadataCache->coreMetadata.textReferenceHref.empty()) {
// there was no textReference in epub, so we return 0 (the first chapter)
@@ -705,13 +702,13 @@ int Epub::getSpineIndexForTextReference() const {
// loop through spine items to get the correct index matching the text href
for (size_t i = 0; i < getSpineItemsCount(); i++) {
if (getSpineItem(i).href == bookMetadataCache->coreMetadata.textReferenceHref) {
Serial.printf("[%lu] [ERS] Text reference %s found at index %d\n", millis(),
bookMetadataCache->coreMetadata.textReferenceHref.c_str(), i);
LOG_DBG("EBP", "Text reference %s found at index %d", bookMetadataCache->coreMetadata.textReferenceHref.c_str(),
i);
return i;
}
}
// This should not happen, as we checked for empty textReferenceHref earlier
Serial.printf("[%lu] [EBP] Section not found for text reference\n", millis());
LOG_DBG("EBP", "Section not found for text reference");
return 0;
}

View File

@@ -1,6 +1,6 @@
#include "BookMetadataCache.h"
#include <HardwareSerial.h>
#include <Logging.h>
#include <Serialization.h>
#include <ZipFile.h>
@@ -21,12 +21,12 @@ bool BookMetadataCache::beginWrite() {
buildMode = true;
spineCount = 0;
tocCount = 0;
Serial.printf("[%lu] [BMC] Entering write mode\n", millis());
LOG_DBG("BMC", "Entering write mode");
return true;
}
bool BookMetadataCache::beginContentOpfPass() {
Serial.printf("[%lu] [BMC] Beginning content opf pass\n", millis());
LOG_DBG("BMC", "Beginning content opf pass");
// Open spine file for writing
return Storage.openFileForWrite("BMC", cachePath + tmpSpineBinFile, spineFile);
@@ -38,7 +38,7 @@ bool BookMetadataCache::endContentOpfPass() {
}
bool BookMetadataCache::beginTocPass() {
Serial.printf("[%lu] [BMC] Beginning toc pass\n", millis());
LOG_DBG("BMC", "Beginning toc pass");
if (!Storage.openFileForRead("BMC", cachePath + tmpSpineBinFile, spineFile)) {
return false;
@@ -66,7 +66,7 @@ bool BookMetadataCache::beginTocPass() {
});
spineFile.seek(0);
useSpineHrefIndex = true;
Serial.printf("[%lu] [BMC] Using fast index for %d spine items\n", millis(), spineCount);
LOG_DBG("BMC", "Using fast index for %d spine items", spineCount);
} else {
useSpineHrefIndex = false;
}
@@ -87,12 +87,12 @@ bool BookMetadataCache::endTocPass() {
bool BookMetadataCache::endWrite() {
if (!buildMode) {
Serial.printf("[%lu] [BMC] endWrite called but not in build mode\n", millis());
LOG_DBG("BMC", "endWrite called but not in build mode");
return false;
}
buildMode = false;
Serial.printf("[%lu] [BMC] Wrote %d spine, %d TOC entries\n", millis(), spineCount, tocCount);
LOG_DBG("BMC", "Wrote %d spine, %d TOC entries", spineCount, tocCount);
return true;
}
@@ -167,7 +167,7 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
ZipFile zip(epubPath);
// Pre-open zip file to speed up size calculations
if (!zip.open()) {
Serial.printf("[%lu] [BMC] Could not open EPUB zip for size calculations\n", millis());
LOG_ERR("BMC", "Could not open EPUB zip for size calculations");
bookFile.close();
spineFile.close();
tocFile.close();
@@ -185,7 +185,7 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
bool useBatchSizes = false;
if (spineCount >= LARGE_SPINE_THRESHOLD) {
Serial.printf("[%lu] [BMC] Using batch size lookup for %d spine items\n", millis(), spineCount);
LOG_DBG("BMC", "Using batch size lookup for %d spine items", spineCount);
std::vector<ZipFile::SizeTarget> targets;
targets.reserve(spineCount);
@@ -208,7 +208,7 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
spineSizes.resize(spineCount, 0);
int matched = zip.fillUncompressedSizes(targets, spineSizes);
Serial.printf("[%lu] [BMC] Batch lookup matched %d/%d spine items\n", millis(), matched, spineCount);
LOG_DBG("BMC", "Batch lookup matched %d/%d spine items", matched, spineCount);
targets.clear();
targets.shrink_to_fit();
@@ -227,9 +227,8 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
// Not a huge deal if we don't fine a TOC entry for the spine entry, this is expected behaviour for EPUBs
// Logging here is for debugging
if (spineEntry.tocIndex == -1) {
Serial.printf(
"[%lu] [BMC] Warning: Could not find TOC entry for spine item %d: %s, using title from last section\n",
millis(), i, spineEntry.href.c_str());
LOG_DBG("BMC", "Warning: Could not find TOC entry for spine item %d: %s, using title from last section", i,
spineEntry.href.c_str());
spineEntry.tocIndex = lastSpineTocIndex;
}
lastSpineTocIndex = spineEntry.tocIndex;
@@ -240,13 +239,13 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
if (itemSize == 0) {
const std::string path = FsHelpers::normalisePath(spineEntry.href);
if (!zip.getInflatedFileSize(path.c_str(), &itemSize)) {
Serial.printf("[%lu] [BMC] Warning: Could not get size for spine item: %s\n", millis(), path.c_str());
LOG_ERR("BMC", "Warning: Could not get size for spine item: %s", path.c_str());
}
}
} else {
const std::string path = FsHelpers::normalisePath(spineEntry.href);
if (!zip.getInflatedFileSize(path.c_str(), &itemSize)) {
Serial.printf("[%lu] [BMC] Warning: Could not get size for spine item: %s\n", millis(), path.c_str());
LOG_ERR("BMC", "Warning: Could not get size for spine item: %s", path.c_str());
}
}
@@ -270,7 +269,7 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
spineFile.close();
tocFile.close();
Serial.printf("[%lu] [BMC] Successfully built book.bin\n", millis());
LOG_DBG("BMC", "Successfully built book.bin");
return true;
}
@@ -306,7 +305,7 @@ uint32_t BookMetadataCache::writeTocEntry(FsFile& file, const TocEntry& entry) c
// this is because in this function we're marking positions of the items
void BookMetadataCache::createSpineEntry(const std::string& href) {
if (!buildMode || !spineFile) {
Serial.printf("[%lu] [BMC] createSpineEntry called but not in build mode\n", millis());
LOG_DBG("BMC", "createSpineEntry called but not in build mode");
return;
}
@@ -318,7 +317,7 @@ void BookMetadataCache::createSpineEntry(const std::string& href) {
void BookMetadataCache::createTocEntry(const std::string& title, const std::string& href, const std::string& anchor,
const uint8_t level) {
if (!buildMode || !tocFile || !spineFile) {
Serial.printf("[%lu] [BMC] createTocEntry called but not in build mode\n", millis());
LOG_DBG("BMC", "createTocEntry called but not in build mode");
return;
}
@@ -340,7 +339,7 @@ void BookMetadataCache::createTocEntry(const std::string& title, const std::stri
}
if (spineIndex == -1) {
Serial.printf("[%lu] [BMC] createTocEntry: Could not find spine item for TOC href %s\n", millis(), href.c_str());
LOG_DBG("BMC", "createTocEntry: Could not find spine item for TOC href %s", href.c_str());
}
} else {
spineFile.seek(0);
@@ -352,7 +351,7 @@ void BookMetadataCache::createTocEntry(const std::string& title, const std::stri
}
}
if (spineIndex == -1) {
Serial.printf("[%lu] [BMC] createTocEntry: Could not find spine item for TOC href %s\n", millis(), href.c_str());
LOG_DBG("BMC", "createTocEntry: Could not find spine item for TOC href %s", href.c_str());
}
}
@@ -371,7 +370,7 @@ bool BookMetadataCache::load() {
uint8_t version;
serialization::readPod(bookFile, version);
if (version != BOOK_CACHE_VERSION) {
Serial.printf("[%lu] [BMC] Cache version mismatch: expected %d, got %d\n", millis(), BOOK_CACHE_VERSION, version);
LOG_DBG("BMC", "Cache version mismatch: expected %d, got %d", BOOK_CACHE_VERSION, version);
bookFile.close();
return false;
}
@@ -387,18 +386,18 @@ bool BookMetadataCache::load() {
serialization::readString(bookFile, coreMetadata.textReferenceHref);
loaded = true;
Serial.printf("[%lu] [BMC] Loaded cache data: %d spine, %d TOC entries\n", millis(), spineCount, tocCount);
LOG_DBG("BMC", "Loaded cache data: %d spine, %d TOC entries", spineCount, tocCount);
return true;
}
BookMetadataCache::SpineEntry BookMetadataCache::getSpineEntry(const int index) {
if (!loaded) {
Serial.printf("[%lu] [BMC] getSpineEntry called but cache not loaded\n", millis());
LOG_ERR("BMC", "getSpineEntry called but cache not loaded");
return {};
}
if (index < 0 || index >= static_cast<int>(spineCount)) {
Serial.printf("[%lu] [BMC] getSpineEntry index %d out of range\n", millis(), index);
LOG_ERR("BMC", "getSpineEntry index %d out of range", index);
return {};
}
@@ -412,12 +411,12 @@ BookMetadataCache::SpineEntry BookMetadataCache::getSpineEntry(const int index)
BookMetadataCache::TocEntry BookMetadataCache::getTocEntry(const int index) {
if (!loaded) {
Serial.printf("[%lu] [BMC] getTocEntry called but cache not loaded\n", millis());
LOG_ERR("BMC", "getTocEntry called but cache not loaded");
return {};
}
if (index < 0 || index >= static_cast<int>(tocCount)) {
Serial.printf("[%lu] [BMC] getTocEntry index %d out of range\n", millis(), index);
LOG_ERR("BMC", "getTocEntry index %d out of range", index);
return {};
}

View File

@@ -1,6 +1,6 @@
#include "Page.h"
#include <HardwareSerial.h>
#include <Logging.h>
#include <Serialization.h>
void PageLine::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) {
@@ -60,7 +60,7 @@ std::unique_ptr<Page> Page::deserialize(FsFile& file) {
auto pl = PageLine::deserialize(file);
page->elements.push_back(std::move(pl));
} else {
Serial.printf("[%lu] [PGE] Deserialization failed: Unknown tag %u\n", millis(), tag);
LOG_ERR("PGE", "Deserialization failed: Unknown tag %u", tag);
return nullptr;
}
}

View File

@@ -31,6 +31,9 @@ void stripSoftHyphensInPlace(std::string& word) {
// Returns the rendered width for a word while ignoring soft hyphen glyphs and optionally appending a visible hyphen.
uint16_t measureWordWidth(const GfxRenderer& renderer, const int fontId, const std::string& word,
const EpdFontFamily::Style style, const bool appendHyphen = false) {
if (word.size() == 1 && word[0] == ' ' && !appendHyphen) {
return renderer.getSpaceWidth(fontId);
}
const bool hasSoftHyphen = containsSoftHyphen(word);
if (!hasSoftHyphen && !appendHyphen) {
return renderer.getTextWidth(fontId, word.c_str(), style);

View File

@@ -1,6 +1,7 @@
#include "Section.h"
#include <HalStorage.h>
#include <Logging.h>
#include <Serialization.h>
#include "Page.h"
@@ -16,16 +17,16 @@ constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + sizeof(int) + sizeof(float) +
uint32_t Section::onPageComplete(std::unique_ptr<Page> page) {
if (!file) {
Serial.printf("[%lu] [SCT] File not open for writing page %d\n", millis(), pageCount);
LOG_ERR("SCT", "File not open for writing page %d", pageCount);
return 0;
}
const uint32_t position = file.position();
if (!page->serialize(file)) {
Serial.printf("[%lu] [SCT] Failed to serialize page %d\n", millis(), pageCount);
LOG_ERR("SCT", "Failed to serialize page %d", pageCount);
return 0;
}
Serial.printf("[%lu] [SCT] Page %d processed\n", millis(), pageCount);
LOG_DBG("SCT", "Page %d processed", pageCount);
pageCount++;
return position;
@@ -36,7 +37,7 @@ void Section::writeSectionFileHeader(const int fontId, const float lineCompressi
const uint16_t viewportHeight, const bool hyphenationEnabled,
const bool embeddedStyle) {
if (!file) {
Serial.printf("[%lu] [SCT] File not open for writing header\n", millis());
LOG_DBG("SCT", "File not open for writing header");
return;
}
static_assert(HEADER_SIZE == sizeof(SECTION_FILE_VERSION) + sizeof(fontId) + sizeof(lineCompression) +
@@ -70,7 +71,7 @@ bool Section::loadSectionFile(const int fontId, const float lineCompression, con
serialization::readPod(file, version);
if (version != SECTION_FILE_VERSION) {
file.close();
Serial.printf("[%lu] [SCT] Deserialization failed: Unknown version %u\n", millis(), version);
LOG_ERR("SCT", "Deserialization failed: Unknown version %u", version);
clearCache();
return false;
}
@@ -96,7 +97,7 @@ bool Section::loadSectionFile(const int fontId, const float lineCompression, con
viewportWidth != fileViewportWidth || viewportHeight != fileViewportHeight ||
hyphenationEnabled != fileHyphenationEnabled || embeddedStyle != fileEmbeddedStyle) {
file.close();
Serial.printf("[%lu] [SCT] Deserialization failed: Parameters do not match\n", millis());
LOG_ERR("SCT", "Deserialization failed: Parameters do not match");
clearCache();
return false;
}
@@ -104,23 +105,23 @@ bool Section::loadSectionFile(const int fontId, const float lineCompression, con
serialization::readPod(file, pageCount);
file.close();
Serial.printf("[%lu] [SCT] Deserialization succeeded: %d pages\n", millis(), pageCount);
LOG_DBG("SCT", "Deserialization succeeded: %d pages", pageCount);
return true;
}
// Your updated class method (assuming you are using the 'SD' object, which is a wrapper for a specific filesystem)
bool Section::clearCache() const {
if (!Storage.exists(filePath.c_str())) {
Serial.printf("[%lu] [SCT] Cache does not exist, no action needed\n", millis());
LOG_DBG("SCT", "Cache does not exist, no action needed");
return true;
}
if (!Storage.remove(filePath.c_str())) {
Serial.printf("[%lu] [SCT] Failed to clear cache\n", millis());
LOG_ERR("SCT", "Failed to clear cache");
return false;
}
Serial.printf("[%lu] [SCT] Cache cleared successfully\n", millis());
LOG_DBG("SCT", "Cache cleared successfully");
return true;
}
@@ -142,7 +143,7 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
uint32_t fileSize = 0;
for (int attempt = 0; attempt < 3 && !success; attempt++) {
if (attempt > 0) {
Serial.printf("[%lu] [SCT] Retrying stream (attempt %d)...\n", millis(), attempt + 1);
LOG_DBG("SCT", "Retrying stream (attempt %d)...", attempt + 1);
delay(50); // Brief delay before retry
}
@@ -162,16 +163,16 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
// If streaming failed, remove the incomplete file immediately
if (!success && Storage.exists(tmpHtmlPath.c_str())) {
Storage.remove(tmpHtmlPath.c_str());
Serial.printf("[%lu] [SCT] Removed incomplete temp file after failed attempt\n", millis());
LOG_DBG("SCT", "Removed incomplete temp file after failed attempt");
}
}
if (!success) {
Serial.printf("[%lu] [SCT] Failed to stream item contents to temp file after retries\n", millis());
LOG_ERR("SCT", "Failed to stream item contents to temp file after retries");
return false;
}
Serial.printf("[%lu] [SCT] Streamed temp HTML to %s (%d bytes)\n", millis(), tmpHtmlPath.c_str(), fileSize);
LOG_DBG("SCT", "Streamed temp HTML to %s (%d bytes)", tmpHtmlPath.c_str(), fileSize);
if (!Storage.openFileForWrite("SCT", filePath, file)) {
return false;
@@ -190,7 +191,7 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
Storage.remove(tmpHtmlPath.c_str());
if (!success) {
Serial.printf("[%lu] [SCT] Failed to parse XML and build pages\n", millis());
LOG_ERR("SCT", "Failed to parse XML and build pages");
file.close();
Storage.remove(filePath.c_str());
return false;
@@ -208,7 +209,7 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
}
if (hasFailedLutRecords) {
Serial.printf("[%lu] [SCT] Failed to write LUT due to invalid page positions\n", millis());
LOG_ERR("SCT", "Failed to write LUT due to invalid page positions");
file.close();
Storage.remove(filePath.c_str());
return false;

View File

@@ -1,13 +1,14 @@
#include "TextBlock.h"
#include <GfxRenderer.h>
#include <Logging.h>
#include <Serialization.h>
void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int x, const int y) const {
// Validate iterator bounds before rendering
if (words.size() != wordXpos.size() || words.size() != wordStyles.size()) {
Serial.printf("[%lu] [TXB] Render skipped: size mismatch (words=%u, xpos=%u, styles=%u)\n", millis(),
(uint32_t)words.size(), (uint32_t)wordXpos.size(), (uint32_t)wordStyles.size());
LOG_ERR("TXB", "Render skipped: size mismatch (words=%u, xpos=%u, styles=%u)\n", (uint32_t)words.size(),
(uint32_t)wordXpos.size(), (uint32_t)wordStyles.size());
return;
}
@@ -42,8 +43,8 @@ void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int
bool TextBlock::serialize(FsFile& file) const {
if (words.size() != wordXpos.size() || words.size() != wordStyles.size()) {
Serial.printf("[%lu] [TXB] Serialization failed: size mismatch (words=%u, xpos=%u, styles=%u)\n", millis(),
words.size(), wordXpos.size(), wordStyles.size());
LOG_ERR("TXB", "Serialization failed: size mismatch (words=%u, xpos=%u, styles=%u)\n", words.size(),
wordXpos.size(), wordStyles.size());
return false;
}
@@ -82,7 +83,7 @@ std::unique_ptr<TextBlock> TextBlock::deserialize(FsFile& file) {
// Sanity check: prevent allocation of unreasonably large vectors (max 10000 words per block)
if (wc > 10000) {
Serial.printf("[%lu] [TXB] Deserialization failed: word count %u exceeds maximum\n", millis(), wc);
LOG_ERR("TXB", "Deserialization failed: word count %u exceeds maximum", wc);
return nullptr;
}

View File

@@ -1,6 +1,6 @@
#include "CssParser.h"
#include <HardwareSerial.h>
#include <Logging.h>
#include <algorithm>
#include <cctype>
@@ -449,7 +449,7 @@ void CssParser::processRuleBlock(const std::string& selectorGroup, const std::st
bool CssParser::loadFromStream(FsFile& source) {
if (!source) {
Serial.printf("[%lu] [CSS] Cannot read from invalid file\n", millis());
LOG_ERR("CSS", "Cannot read from invalid file");
return false;
}
@@ -470,7 +470,7 @@ bool CssParser::loadFromStream(FsFile& source) {
processRuleBlock(selector, body);
}
Serial.printf("[%lu] [CSS] Parsed %zu rules\n", millis(), rulesBySelector_.size());
LOG_DBG("CSS", "Parsed %zu rules", rulesBySelector_.size());
return true;
}
@@ -582,7 +582,7 @@ bool CssParser::saveToCache(FsFile& file) const {
file.write(reinterpret_cast<const uint8_t*>(&definedBits), sizeof(definedBits));
}
Serial.printf("[%lu] [CSS] Saved %u rules to cache\n", millis(), ruleCount);
LOG_DBG("CSS", "Saved %u rules to cache", ruleCount);
return true;
}
@@ -597,7 +597,7 @@ bool CssParser::loadFromCache(FsFile& file) {
// Read and verify version
uint8_t version = 0;
if (file.read(&version, 1) != 1 || version != CSS_CACHE_VERSION) {
Serial.printf("[%lu] [CSS] Cache version mismatch (got %u, expected %u)\n", millis(), version, CSS_CACHE_VERSION);
LOG_DBG("CSS", "Cache version mismatch (got %u, expected %u)", version, CSS_CACHE_VERSION);
return false;
}
@@ -694,6 +694,6 @@ bool CssParser::loadFromCache(FsFile& file) {
rulesBySelector_[selector] = style;
}
Serial.printf("[%lu] [CSS] Loaded %u rules from cache\n", millis(), ruleCount);
LOG_DBG("CSS", "Loaded %u rules from cache", ruleCount);
return true;
}

View File

@@ -0,0 +1,76 @@
// from
// https://github.com/atomic14/diy-esp32-epub-reader/blob/2c2f57fdd7e2a788d14a0bcb26b9e845a47aac42/lib/Epub/RubbishHtmlParser/htmlEntities.cpp
#include "htmlEntities.h"
#include <cstring>
struct EntityPair {
const char* key;
const char* value;
};
static const EntityPair ENTITY_LOOKUP[] = {
{"&quot;", "\""}, {"&frasl;", ""}, {"&amp;", "&"}, {"&lt;", "<"}, {"&gt;", ">"},
{"&Agrave;", "À"}, {"&Aacute;", "Á"}, {"&Acirc;", "Â"}, {"&Atilde;", "Ã"}, {"&Auml;", "Ä"},
{"&Aring;", "Å"}, {"&AElig;", "Æ"}, {"&Ccedil;", "Ç"}, {"&Egrave;", "È"}, {"&Eacute;", "É"},
{"&Ecirc;", "Ê"}, {"&Euml;", "Ë"}, {"&Igrave;", "Ì"}, {"&Iacute;", "Í"}, {"&Icirc;", "Î"},
{"&Iuml;", "Ï"}, {"&ETH;", "Ð"}, {"&Ntilde;", "Ñ"}, {"&Ograve;", "Ò"}, {"&Oacute;", "Ó"},
{"&Ocirc;", "Ô"}, {"&Otilde;", "Õ"}, {"&Ouml;", "Ö"}, {"&Oslash;", "Ø"}, {"&Ugrave;", "Ù"},
{"&Uacute;", "Ú"}, {"&Ucirc;", "Û"}, {"&Uuml;", "Ü"}, {"&Yacute;", "Ý"}, {"&THORN;", "Þ"},
{"&szlig;", "ß"}, {"&agrave;", "à"}, {"&aacute;", "á"}, {"&acirc;", "â"}, {"&atilde;", "ã"},
{"&auml;", "ä"}, {"&aring;", "å"}, {"&aelig;", "æ"}, {"&ccedil;", "ç"}, {"&egrave;", "è"},
{"&eacute;", "é"}, {"&ecirc;", "ê"}, {"&euml;", "ë"}, {"&igrave;", "ì"}, {"&iacute;", "í"},
{"&icirc;", "î"}, {"&iuml;", "ï"}, {"&eth;", "ð"}, {"&ntilde;", "ñ"}, {"&ograve;", "ò"},
{"&oacute;", "ó"}, {"&ocirc;", "ô"}, {"&otilde;", "õ"}, {"&ouml;", "ö"}, {"&oslash;", "ø"},
{"&ugrave;", "ù"}, {"&uacute;", "ú"}, {"&ucirc;", "û"}, {"&uuml;", "ü"}, {"&yacute;", "ý"},
{"&thorn;", "þ"}, {"&yuml;", "ÿ"}, {"&nbsp;", "\xC2\xA0"}, {"&iexcl;", "¡"}, {"&cent;", "¢"},
{"&pound;", "£"}, {"&curren;", "¤"}, {"&yen;", "¥"}, {"&brvbar;", "¦"}, {"&sect;", "§"},
{"&uml;", "¨"}, {"&copy;", "©"}, {"&ordf;", "ª"}, {"&laquo;", "«"}, {"&not;", "¬"},
{"&shy;", "­"}, {"&reg;", "®"}, {"&macr;", "¯"}, {"&deg;", "°"}, {"&plusmn;", "±"},
{"&sup2;", "²"}, {"&sup3;", "³"}, {"&acute;", "´"}, {"&micro;", "µ"}, {"&para;", ""},
{"&cedil;", "¸"}, {"&sup1;", "¹"}, {"&ordm;", "º"}, {"&raquo;", "»"}, {"&frac14;", "¼"},
{"&frac12;", "½"}, {"&frac34;", "¾"}, {"&iquest;", "¿"}, {"&times;", "×"}, {"&divide;", "÷"},
{"&forall;", ""}, {"&part;", ""}, {"&exist;", ""}, {"&empty;", ""}, {"&nabla;", ""},
{"&isin;", ""}, {"&notin;", ""}, {"&ni;", ""}, {"&prod;", ""}, {"&sum;", ""},
{"&minus;", ""}, {"&lowast;", ""}, {"&radic;", ""}, {"&prop;", ""}, {"&infin;", ""},
{"&ang;", ""}, {"&and;", ""}, {"&or;", ""}, {"&cap;", ""}, {"&cup;", ""},
{"&int;", ""}, {"&there4;", ""}, {"&sim;", ""}, {"&cong;", ""}, {"&asymp;", ""},
{"&ne;", ""}, {"&equiv;", ""}, {"&le;", ""}, {"&ge;", ""}, {"&sub;", ""},
{"&sup;", ""}, {"&nsub;", ""}, {"&sube;", ""}, {"&supe;", ""}, {"&oplus;", ""},
{"&otimes;", ""}, {"&perp;", ""}, {"&sdot;", ""}, {"&Alpha;", "Α"}, {"&Beta;", "Β"},
{"&Gamma;", "Γ"}, {"&Delta;", "Δ"}, {"&Epsilon;", "Ε"}, {"&Zeta;", "Ζ"}, {"&Eta;", "Η"},
{"&Theta;", "Θ"}, {"&Iota;", "Ι"}, {"&Kappa;", "Κ"}, {"&Lambda;", "Λ"}, {"&Mu;", "Μ"},
{"&Nu;", "Ν"}, {"&Xi;", "Ξ"}, {"&Omicron;", "Ο"}, {"&Pi;", "Π"}, {"&Rho;", "Ρ"},
{"&Sigma;", "Σ"}, {"&Tau;", "Τ"}, {"&Upsilon;", "Υ"}, {"&Phi;", "Φ"}, {"&Chi;", "Χ"},
{"&Psi;", "Ψ"}, {"&Omega;", "Ω"}, {"&alpha;", "α"}, {"&beta;", "β"}, {"&gamma;", "γ"},
{"&delta;", "δ"}, {"&epsilon;", "ε"}, {"&zeta;", "ζ"}, {"&eta;", "η"}, {"&theta;", "θ"},
{"&iota;", "ι"}, {"&kappa;", "κ"}, {"&lambda;", "λ"}, {"&mu;", "μ"}, {"&nu;", "ν"},
{"&xi;", "ξ"}, {"&omicron;", "ο"}, {"&pi;", "π"}, {"&rho;", "ρ"}, {"&sigmaf;", "ς"},
{"&sigma;", "σ"}, {"&tau;", "τ"}, {"&upsilon;", "υ"}, {"&phi;", "φ"}, {"&chi;", "χ"},
{"&psi;", "ψ"}, {"&omega;", "ω"}, {"&thetasym;", "ϑ"}, {"&upsih;", "ϒ"}, {"&piv;", "ϖ"},
{"&OElig;", "Œ"}, {"&oelig;", "œ"}, {"&Scaron;", "Š"}, {"&scaron;", "š"}, {"&Yuml;", "Ÿ"},
{"&fnof;", "ƒ"}, {"&circ;", "ˆ"}, {"&tilde;", "˜"}, {"&ensp;", ""}, {"&emsp;", ""},
{"&thinsp;", ""}, {"&zwnj;", ""}, {"&zwj;", ""}, {"&lrm;", ""}, {"&rlm;", ""},
{"&ndash;", ""}, {"&mdash;", ""}, {"&lsquo;", ""}, {"&rsquo;", ""}, {"&sbquo;", ""},
{"&ldquo;", ""}, {"&rdquo;", ""}, {"&bdquo;", ""}, {"&dagger;", ""}, {"&Dagger;", ""},
{"&bull;", ""}, {"&hellip;", ""}, {"&permil;", ""}, {"&prime;", ""}, {"&Prime;", ""},
{"&lsaquo;", ""}, {"&rsaquo;", ""}, {"&oline;", ""}, {"&euro;", ""}, {"&trade;", ""},
{"&larr;", ""}, {"&uarr;", ""}, {"&rarr;", ""}, {"&darr;", ""}, {"&harr;", ""},
{"&crarr;", ""}, {"&lceil;", ""}, {"&rceil;", ""}, {"&lfloor;", ""}, {"&rfloor;", ""},
{"&loz;", ""}, {"&spades;", ""}, {"&clubs;", ""}, {"&hearts;", ""}, {"&diams;", ""}};
static const size_t ENTITY_LOOKUP_COUNT = sizeof(ENTITY_LOOKUP) / sizeof(ENTITY_LOOKUP[0]);
// Lookup a single HTML entity and return its UTF-8 value
const char* lookupHtmlEntity(const char* entity, int len) {
for (size_t i = 0; i < ENTITY_LOOKUP_COUNT; i++) {
const char* key = ENTITY_LOOKUP[i].key;
const size_t keyLen = strlen(key);
if (static_cast<size_t>(len) == keyLen && memcmp(entity, key, keyLen) == 0) {
return ENTITY_LOOKUP[i].value;
}
}
return nullptr; // Entity not found
}

View File

@@ -0,0 +1,9 @@
// from
// https://github.com/atomic14/diy-esp32-epub-reader/blob/2c2f57fdd7e2a788d14a0bcb26b9e845a47aac42/lib/Epub/RubbishHtmlParser/htmlEntities.cpp
#pragma once
#include <string>
// Lookup a single HTML entity (including & and ;) and return its UTF-8 value
// Returns nullptr if entity is not found
const char* lookupHtmlEntity(const char* entity, int len);

View File

@@ -2,10 +2,11 @@
#include <GfxRenderer.h>
#include <HalStorage.h>
#include <HardwareSerial.h>
#include <Logging.h>
#include <expat.h>
#include "../Page.h"
#include "../htmlEntities.h"
const char* HEADER_TAGS[] = {"h1", "h2", "h3", "h4", "h5", "h6"};
constexpr int NUM_HEADER_TAGS = sizeof(HEADER_TAGS) / sizeof(HEADER_TAGS[0]);
@@ -168,7 +169,7 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
}
}
Serial.printf("[%lu] [EHP] Image alt: %s\n", millis(), alt.c_str());
LOG_DBG("EHP", "Image alt: %s", alt.c_str());
self->startNewTextBlock(centeredBlockStyle);
self->italicUntilDepth = min(self->italicUntilDepth, self->depth);
@@ -359,6 +360,28 @@ void XMLCALL ChapterHtmlSlimParser::characterData(void* userData, const XML_Char
continue;
}
// Detect U+00A0 (non-breaking space): UTF-8 encoding is 0xC2 0xA0
// Render a visible space without allowing a line break around it.
if (static_cast<uint8_t>(s[i]) == 0xC2 && i + 1 < len && static_cast<uint8_t>(s[i + 1]) == 0xA0) {
// Flush any pending text so style is applied correctly.
if (self->partWordBufferIndex > 0) {
self->flushPartWordBuffer();
}
// Add a standalone space that attaches to the previous word.
self->partWordBuffer[0] = ' ';
self->partWordBuffer[1] = '\0';
self->partWordBufferIndex = 1;
self->nextWordContinues = true; // Attach space to previous word (no break).
self->flushPartWordBuffer();
// Ensure the next real word attaches to this space (no break).
self->nextWordContinues = true;
i++; // Skip the second byte (0xA0)
continue;
}
// Skip Zero Width No-Break Space / BOM (U+FEFF) = 0xEF 0xBB 0xBF
const XML_Char FEFF_BYTE_1 = static_cast<XML_Char>(0xEF);
const XML_Char FEFF_BYTE_2 = static_cast<XML_Char>(0xBB);
@@ -386,13 +409,29 @@ void XMLCALL ChapterHtmlSlimParser::characterData(void* userData, const XML_Char
// memory.
// Spotted when reading Intermezzo, there are some really long text blocks in there.
if (self->currentTextBlock->size() > 750) {
Serial.printf("[%lu] [EHP] Text block too long, splitting into multiple pages\n", millis());
LOG_DBG("EHP", "Text block too long, splitting into multiple pages");
self->currentTextBlock->layoutAndExtractLines(
self->renderer, self->fontId, self->viewportWidth,
[self](const std::shared_ptr<TextBlock>& textBlock) { self->addLineToPage(textBlock); }, false);
}
}
void XMLCALL ChapterHtmlSlimParser::defaultHandlerExpand(void* userData, const XML_Char* s, const int len) {
// Check if this looks like an entity reference (&...;)
if (len >= 3 && s[0] == '&' && s[len - 1] == ';') {
const char* utf8Value = lookupHtmlEntity(s, len);
if (utf8Value != nullptr) {
// Known entity: expand to its UTF-8 value
characterData(userData, utf8Value, strlen(utf8Value));
return;
}
// Unknown entity: preserve original &...; sequence
characterData(userData, s, len);
return;
}
// Not an entity we recognize - skip it
}
void XMLCALL ChapterHtmlSlimParser::endElement(void* userData, const XML_Char* name) {
auto* self = static_cast<ChapterHtmlSlimParser*>(userData);
@@ -477,10 +516,14 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
int done;
if (!parser) {
Serial.printf("[%lu] [EHP] Couldn't allocate memory for parser\n", millis());
LOG_ERR("EHP", "Couldn't allocate memory for parser");
return false;
}
// Handle HTML entities (like &nbsp;) that aren't in XML spec or DTD
// Using DefaultHandlerExpand preserves normal entity expansion from DOCTYPE
XML_SetDefaultHandlerExpand(parser, defaultHandlerExpand);
FsFile file;
if (!Storage.openFileForRead("EHP", filepath, file)) {
XML_ParserFree(parser);
@@ -499,7 +542,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
do {
void* const buf = XML_GetBuffer(parser, 1024);
if (!buf) {
Serial.printf("[%lu] [EHP] Couldn't allocate memory for buffer\n", millis());
LOG_ERR("EHP", "Couldn't allocate memory for buffer");
XML_StopParser(parser, XML_FALSE); // Stop any pending processing
XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks
XML_SetCharacterDataHandler(parser, nullptr);
@@ -511,7 +554,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
const size_t len = file.read(buf, 1024);
if (len == 0 && file.available() > 0) {
Serial.printf("[%lu] [EHP] File read error\n", millis());
LOG_ERR("EHP", "File read error");
XML_StopParser(parser, XML_FALSE); // Stop any pending processing
XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks
XML_SetCharacterDataHandler(parser, nullptr);
@@ -523,8 +566,8 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
done = file.available() == 0;
if (XML_ParseBuffer(parser, static_cast<int>(len), done) == XML_STATUS_ERROR) {
Serial.printf("[%lu] [EHP] Parse error at line %lu:\n%s\n", millis(), XML_GetCurrentLineNumber(parser),
XML_ErrorString(XML_GetErrorCode(parser)));
LOG_ERR("EHP", "Parse error at line %lu:\n%s", XML_GetCurrentLineNumber(parser),
XML_ErrorString(XML_GetErrorCode(parser)));
XML_StopParser(parser, XML_FALSE); // Stop any pending processing
XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks
XML_SetCharacterDataHandler(parser, nullptr);
@@ -568,7 +611,7 @@ void ChapterHtmlSlimParser::addLineToPage(std::shared_ptr<TextBlock> line) {
void ChapterHtmlSlimParser::makePages() {
if (!currentTextBlock) {
Serial.printf("[%lu] [EHP] !! No text block to make pages for !!\n", millis());
LOG_ERR("EHP", "!! No text block to make pages for !!");
return;
}

View File

@@ -64,6 +64,7 @@ class ChapterHtmlSlimParser {
// XML callbacks
static void XMLCALL startElement(void* userData, const XML_Char* name, const XML_Char** atts);
static void XMLCALL characterData(void* userData, const XML_Char* s, int len);
static void XMLCALL defaultHandlerExpand(void* userData, const XML_Char* s, int len);
static void XMLCALL endElement(void* userData, const XML_Char* name);
public:

View File

@@ -1,11 +1,11 @@
#include "ContainerParser.h"
#include <HardwareSerial.h>
#include <Logging.h>
bool ContainerParser::setup() {
parser = XML_ParserCreate(nullptr);
if (!parser) {
Serial.printf("[%lu] [CTR] Couldn't allocate memory for parser\n", millis());
LOG_ERR("CTR", "Couldn't allocate memory for parser");
return false;
}
@@ -34,7 +34,7 @@ size_t ContainerParser::write(const uint8_t* buffer, const size_t size) {
while (remainingInBuffer > 0) {
void* const buf = XML_GetBuffer(parser, 1024);
if (!buf) {
Serial.printf("[%lu] [CTR] Couldn't allocate buffer\n", millis());
LOG_DBG("CTR", "Couldn't allocate buffer");
return 0;
}
@@ -42,7 +42,7 @@ size_t ContainerParser::write(const uint8_t* buffer, const size_t size) {
memcpy(buf, currentBufferPos, toRead);
if (XML_ParseBuffer(parser, static_cast<int>(toRead), remainingSize == toRead) == XML_STATUS_ERROR) {
Serial.printf("[%lu] [CTR] Parse error: %s\n", millis(), XML_ErrorString(XML_GetErrorCode(parser)));
LOG_ERR("CTR", "Parse error: %s", XML_ErrorString(XML_GetErrorCode(parser)));
return 0;
}

View File

@@ -1,7 +1,7 @@
#include "ContentOpfParser.h"
#include <FsHelpers.h>
#include <HardwareSerial.h>
#include <Logging.h>
#include <Serialization.h>
#include "../BookMetadataCache.h"
@@ -15,7 +15,7 @@ constexpr char itemCacheFile[] = "/.items.bin";
bool ContentOpfParser::setup() {
parser = XML_ParserCreate(nullptr);
if (!parser) {
Serial.printf("[%lu] [COF] Couldn't allocate memory for parser\n", millis());
LOG_DBG("COF", "Couldn't allocate memory for parser");
return false;
}
@@ -56,7 +56,7 @@ size_t ContentOpfParser::write(const uint8_t* buffer, const size_t size) {
void* const buf = XML_GetBuffer(parser, 1024);
if (!buf) {
Serial.printf("[%lu] [COF] Couldn't allocate memory for buffer\n", millis());
LOG_ERR("COF", "Couldn't allocate memory for buffer");
XML_StopParser(parser, XML_FALSE); // Stop any pending processing
XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks
XML_SetCharacterDataHandler(parser, nullptr);
@@ -69,8 +69,8 @@ size_t ContentOpfParser::write(const uint8_t* buffer, const size_t size) {
memcpy(buf, currentBufferPos, toRead);
if (XML_ParseBuffer(parser, static_cast<int>(toRead), remainingSize == toRead) == XML_STATUS_ERROR) {
Serial.printf("[%lu] [COF] Parse error at line %lu: %s\n", millis(), XML_GetCurrentLineNumber(parser),
XML_ErrorString(XML_GetErrorCode(parser)));
LOG_DBG("COF", "Parse error at line %lu: %s", XML_GetCurrentLineNumber(parser),
XML_ErrorString(XML_GetErrorCode(parser)));
XML_StopParser(parser, XML_FALSE); // Stop any pending processing
XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks
XML_SetCharacterDataHandler(parser, nullptr);
@@ -119,9 +119,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
if (self->state == IN_PACKAGE && (strcmp(name, "manifest") == 0 || strcmp(name, "opf:manifest") == 0)) {
self->state = IN_MANIFEST;
if (!Storage.openFileForWrite("COF", self->cachePath + itemCacheFile, self->tempItemStore)) {
Serial.printf(
"[%lu] [COF] Couldn't open temp items file for writing. This is probably going to be a fatal error.\n",
millis());
LOG_ERR("COF", "Couldn't open temp items file for writing. This is probably going to be a fatal error.");
}
return;
}
@@ -129,9 +127,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
if (self->state == IN_PACKAGE && (strcmp(name, "spine") == 0 || strcmp(name, "opf:spine") == 0)) {
self->state = IN_SPINE;
if (!Storage.openFileForRead("COF", self->cachePath + itemCacheFile, self->tempItemStore)) {
Serial.printf(
"[%lu] [COF] Couldn't open temp items file for reading. This is probably going to be a fatal error.\n",
millis());
LOG_ERR("COF", "Couldn't open temp items file for reading. This is probably going to be a fatal error.");
}
// Sort item index for binary search if we have enough items
@@ -140,7 +136,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
return a.idHash < b.idHash || (a.idHash == b.idHash && a.idLen < b.idLen);
});
self->useItemIndex = true;
Serial.printf("[%lu] [COF] Using fast index for %zu manifest items\n", millis(), self->itemIndex.size());
LOG_DBG("COF", "Using fast index for %zu manifest items", self->itemIndex.size());
}
return;
}
@@ -148,11 +144,9 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
if (self->state == IN_PACKAGE && (strcmp(name, "guide") == 0 || strcmp(name, "opf:guide") == 0)) {
self->state = IN_GUIDE;
// TODO Remove print
Serial.printf("[%lu] [COF] Entering guide state.\n", millis());
LOG_DBG("COF", "Entering guide state.");
if (!Storage.openFileForRead("COF", self->cachePath + itemCacheFile, self->tempItemStore)) {
Serial.printf(
"[%lu] [COF] Couldn't open temp items file for reading. This is probably going to be a fatal error.\n",
millis());
LOG_ERR("COF", "Couldn't open temp items file for reading. This is probably going to be a fatal error.");
}
return;
}
@@ -214,8 +208,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
if (self->tocNcxPath.empty()) {
self->tocNcxPath = href;
} else {
Serial.printf("[%lu] [COF] Warning: Multiple NCX files found in manifest. Ignoring duplicate: %s\n", millis(),
href.c_str());
LOG_DBG("COF", "Warning: Multiple NCX files found in manifest. Ignoring duplicate: %s", href.c_str());
}
}
@@ -229,7 +222,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
// Properties is space-separated, check if "nav" is present as a word
if (properties == "nav" || properties.find("nav ") == 0 || properties.find(" nav") != std::string::npos) {
self->tocNavPath = href;
Serial.printf("[%lu] [COF] Found EPUB 3 nav document: %s\n", millis(), href.c_str());
LOG_DBG("COF", "Found EPUB 3 nav document: %s", href.c_str());
}
}
@@ -310,7 +303,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
if (type == "text" || type == "start") {
continue;
} else {
Serial.printf("[%lu] [COF] Skipping non-text reference in guide: %s\n", millis(), type.c_str());
LOG_DBG("COF", "Skipping non-text reference in guide: %s", type.c_str());
break;
}
} else if (strcmp(atts[i], "href") == 0) {
@@ -318,7 +311,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
}
}
if ((type == "text" || (type == "start" && !self->textReferenceHref.empty())) && (textHref.length() > 0)) {
Serial.printf("[%lu] [COF] Found %s reference in guide: %s.\n", millis(), type.c_str(), textHref.c_str());
LOG_DBG("COF", "Found %s reference in guide: %s.", type.c_str(), textHref.c_str());
self->textReferenceHref = textHref;
}
return;

View File

@@ -1,14 +1,14 @@
#include "TocNavParser.h"
#include <FsHelpers.h>
#include <HardwareSerial.h>
#include <Logging.h>
#include "../BookMetadataCache.h"
bool TocNavParser::setup() {
parser = XML_ParserCreate(nullptr);
if (!parser) {
Serial.printf("[%lu] [NAV] Couldn't allocate memory for parser\n", millis());
LOG_DBG("NAV", "Couldn't allocate memory for parser");
return false;
}
@@ -39,7 +39,7 @@ size_t TocNavParser::write(const uint8_t* buffer, const size_t size) {
while (remainingInBuffer > 0) {
void* const buf = XML_GetBuffer(parser, 1024);
if (!buf) {
Serial.printf("[%lu] [NAV] Couldn't allocate memory for buffer\n", millis());
LOG_DBG("NAV", "Couldn't allocate memory for buffer");
XML_StopParser(parser, XML_FALSE);
XML_SetElementHandler(parser, nullptr, nullptr);
XML_SetCharacterDataHandler(parser, nullptr);
@@ -52,8 +52,8 @@ size_t TocNavParser::write(const uint8_t* buffer, const size_t size) {
memcpy(buf, currentBufferPos, toRead);
if (XML_ParseBuffer(parser, static_cast<int>(toRead), remainingSize == toRead) == XML_STATUS_ERROR) {
Serial.printf("[%lu] [NAV] Parse error at line %lu: %s\n", millis(), XML_GetCurrentLineNumber(parser),
XML_ErrorString(XML_GetErrorCode(parser)));
LOG_DBG("NAV", "Parse error at line %lu: %s", XML_GetCurrentLineNumber(parser),
XML_ErrorString(XML_GetErrorCode(parser)));
XML_StopParser(parser, XML_FALSE);
XML_SetElementHandler(parser, nullptr, nullptr);
XML_SetCharacterDataHandler(parser, nullptr);
@@ -88,7 +88,7 @@ void XMLCALL TocNavParser::startElement(void* userData, const XML_Char* name, co
for (int i = 0; atts[i]; i += 2) {
if ((strcmp(atts[i], "epub:type") == 0 || strcmp(atts[i], "type") == 0) && strcmp(atts[i + 1], "toc") == 0) {
self->state = IN_NAV_TOC;
Serial.printf("[%lu] [NAV] Found nav toc element\n", millis());
LOG_DBG("NAV", "Found nav toc element");
return;
}
}
@@ -179,7 +179,7 @@ void XMLCALL TocNavParser::endElement(void* userData, const XML_Char* name) {
if (strcmp(name, "nav") == 0 && self->state >= IN_NAV_TOC) {
self->state = IN_BODY;
Serial.printf("[%lu] [NAV] Finished parsing nav toc\n", millis());
LOG_DBG("NAV", "Finished parsing nav toc");
return;
}
}

View File

@@ -1,14 +1,14 @@
#include "TocNcxParser.h"
#include <FsHelpers.h>
#include <HardwareSerial.h>
#include <Logging.h>
#include "../BookMetadataCache.h"
bool TocNcxParser::setup() {
parser = XML_ParserCreate(nullptr);
if (!parser) {
Serial.printf("[%lu] [TOC] Couldn't allocate memory for parser\n", millis());
LOG_DBG("TOC", "Couldn't allocate memory for parser");
return false;
}
@@ -39,7 +39,7 @@ size_t TocNcxParser::write(const uint8_t* buffer, const size_t size) {
while (remainingInBuffer > 0) {
void* const buf = XML_GetBuffer(parser, 1024);
if (!buf) {
Serial.printf("[%lu] [TOC] Couldn't allocate memory for buffer\n", millis());
LOG_DBG("TOC", "Couldn't allocate memory for buffer");
XML_StopParser(parser, XML_FALSE); // Stop any pending processing
XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks
XML_SetCharacterDataHandler(parser, nullptr);
@@ -52,8 +52,8 @@ size_t TocNcxParser::write(const uint8_t* buffer, const size_t size) {
memcpy(buf, currentBufferPos, toRead);
if (XML_ParseBuffer(parser, static_cast<int>(toRead), remainingSize == toRead) == XML_STATUS_ERROR) {
Serial.printf("[%lu] [TOC] Parse error at line %lu: %s\n", millis(), XML_GetCurrentLineNumber(parser),
XML_ErrorString(XML_GetErrorCode(parser)));
LOG_DBG("TOC", "Parse error at line %lu: %s", XML_GetCurrentLineNumber(parser),
XML_ErrorString(XML_GetErrorCode(parser)));
XML_StopParser(parser, XML_FALSE); // Stop any pending processing
XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks
XML_SetCharacterDataHandler(parser, nullptr);