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:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {};
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
76
lib/Epub/Epub/htmlEntities.cpp
Normal file
76
lib/Epub/Epub/htmlEntities.cpp
Normal 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[] = {
|
||||
{""", "\""}, {"⁄", "⁄"}, {"&", "&"}, {"<", "<"}, {">", ">"},
|
||||
{"À", "À"}, {"Á", "Á"}, {"Â", "Â"}, {"Ã", "Ã"}, {"Ä", "Ä"},
|
||||
{"Å", "Å"}, {"Æ", "Æ"}, {"Ç", "Ç"}, {"È", "È"}, {"É", "É"},
|
||||
{"Ê", "Ê"}, {"Ë", "Ë"}, {"Ì", "Ì"}, {"Í", "Í"}, {"Î", "Î"},
|
||||
{"Ï", "Ï"}, {"Ð", "Ð"}, {"Ñ", "Ñ"}, {"Ò", "Ò"}, {"Ó", "Ó"},
|
||||
{"Ô", "Ô"}, {"Õ", "Õ"}, {"Ö", "Ö"}, {"Ø", "Ø"}, {"Ù", "Ù"},
|
||||
{"Ú", "Ú"}, {"Û", "Û"}, {"Ü", "Ü"}, {"Ý", "Ý"}, {"Þ", "Þ"},
|
||||
{"ß", "ß"}, {"à", "à"}, {"á", "á"}, {"â", "â"}, {"ã", "ã"},
|
||||
{"ä", "ä"}, {"å", "å"}, {"æ", "æ"}, {"ç", "ç"}, {"è", "è"},
|
||||
{"é", "é"}, {"ê", "ê"}, {"ë", "ë"}, {"ì", "ì"}, {"í", "í"},
|
||||
{"î", "î"}, {"ï", "ï"}, {"ð", "ð"}, {"ñ", "ñ"}, {"ò", "ò"},
|
||||
{"ó", "ó"}, {"ô", "ô"}, {"õ", "õ"}, {"ö", "ö"}, {"ø", "ø"},
|
||||
{"ù", "ù"}, {"ú", "ú"}, {"û", "û"}, {"ü", "ü"}, {"ý", "ý"},
|
||||
{"þ", "þ"}, {"ÿ", "ÿ"}, {" ", "\xC2\xA0"}, {"¡", "¡"}, {"¢", "¢"},
|
||||
{"£", "£"}, {"¤", "¤"}, {"¥", "¥"}, {"¦", "¦"}, {"§", "§"},
|
||||
{"¨", "¨"}, {"©", "©"}, {"ª", "ª"}, {"«", "«"}, {"¬", "¬"},
|
||||
{"­", ""}, {"®", "®"}, {"¯", "¯"}, {"°", "°"}, {"±", "±"},
|
||||
{"²", "²"}, {"³", "³"}, {"´", "´"}, {"µ", "µ"}, {"¶", "¶"},
|
||||
{"¸", "¸"}, {"¹", "¹"}, {"º", "º"}, {"»", "»"}, {"¼", "¼"},
|
||||
{"½", "½"}, {"¾", "¾"}, {"¿", "¿"}, {"×", "×"}, {"÷", "÷"},
|
||||
{"∀", "∀"}, {"∂", "∂"}, {"∃", "∃"}, {"∅", "∅"}, {"∇", "∇"},
|
||||
{"∈", "∈"}, {"∉", "∉"}, {"∋", "∋"}, {"∏", "∏"}, {"∑", "∑"},
|
||||
{"−", "−"}, {"∗", "∗"}, {"√", "√"}, {"∝", "∝"}, {"∞", "∞"},
|
||||
{"∠", "∠"}, {"∧", "∧"}, {"∨", "∨"}, {"∩", "∩"}, {"∪", "∪"},
|
||||
{"∫", "∫"}, {"∴", "∴"}, {"∼", "∼"}, {"≅", "≅"}, {"≈", "≈"},
|
||||
{"≠", "≠"}, {"≡", "≡"}, {"≤", "≤"}, {"≥", "≥"}, {"⊂", "⊂"},
|
||||
{"⊃", "⊃"}, {"⊄", "⊄"}, {"⊆", "⊆"}, {"⊇", "⊇"}, {"⊕", "⊕"},
|
||||
{"⊗", "⊗"}, {"⊥", "⊥"}, {"⋅", "⋅"}, {"Α", "Α"}, {"Β", "Β"},
|
||||
{"Γ", "Γ"}, {"Δ", "Δ"}, {"Ε", "Ε"}, {"Ζ", "Ζ"}, {"Η", "Η"},
|
||||
{"Θ", "Θ"}, {"Ι", "Ι"}, {"Κ", "Κ"}, {"Λ", "Λ"}, {"Μ", "Μ"},
|
||||
{"Ν", "Ν"}, {"Ξ", "Ξ"}, {"Ο", "Ο"}, {"Π", "Π"}, {"Ρ", "Ρ"},
|
||||
{"Σ", "Σ"}, {"Τ", "Τ"}, {"Υ", "Υ"}, {"Φ", "Φ"}, {"Χ", "Χ"},
|
||||
{"Ψ", "Ψ"}, {"Ω", "Ω"}, {"α", "α"}, {"β", "β"}, {"γ", "γ"},
|
||||
{"δ", "δ"}, {"ε", "ε"}, {"ζ", "ζ"}, {"η", "η"}, {"θ", "θ"},
|
||||
{"ι", "ι"}, {"κ", "κ"}, {"λ", "λ"}, {"μ", "μ"}, {"ν", "ν"},
|
||||
{"ξ", "ξ"}, {"ο", "ο"}, {"π", "π"}, {"ρ", "ρ"}, {"ς", "ς"},
|
||||
{"σ", "σ"}, {"τ", "τ"}, {"υ", "υ"}, {"φ", "φ"}, {"χ", "χ"},
|
||||
{"ψ", "ψ"}, {"ω", "ω"}, {"ϑ", "ϑ"}, {"ϒ", "ϒ"}, {"ϖ", "ϖ"},
|
||||
{"Œ", "Œ"}, {"œ", "œ"}, {"Š", "Š"}, {"š", "š"}, {"Ÿ", "Ÿ"},
|
||||
{"ƒ", "ƒ"}, {"ˆ", "ˆ"}, {"˜", "˜"}, {" ", " "}, {" ", " "},
|
||||
{" ", " "}, {"‌", ""}, {"‍", ""}, {"‎", ""}, {"‏", ""},
|
||||
{"–", "–"}, {"—", "—"}, {"‘", "‘"}, {"’", "’"}, {"‚", "‚"},
|
||||
{"“", "“"}, {"”", "”"}, {"„", "„"}, {"†", "†"}, {"‡", "‡"},
|
||||
{"•", "•"}, {"…", "…"}, {"‰", "‰"}, {"′", "′"}, {"″", "″"},
|
||||
{"‹", "‹"}, {"›", "›"}, {"‾", "‾"}, {"€", "€"}, {"™", "™"},
|
||||
{"←", "←"}, {"↑", "↑"}, {"→", "→"}, {"↓", "↓"}, {"↔", "↔"},
|
||||
{"↵", "↵"}, {"⌈", "⌈"}, {"⌉", "⌉"}, {"⌊", "⌊"}, {"⌋", "⌋"},
|
||||
{"◊", "◊"}, {"♠", "♠"}, {"♣", "♣"}, {"♥", "♥"}, {"♦", "♦"}};
|
||||
|
||||
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
|
||||
}
|
||||
9
lib/Epub/Epub/htmlEntities.h
Normal file
9
lib/Epub/Epub/htmlEntities.h
Normal 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);
|
||||
@@ -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 ) 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user