Recognize and use printed pages info

This commit is contained in:
jpirnay
2026-05-23 17:53:45 +02:00
parent 6717c02173
commit 3671bdba0b
20 changed files with 869 additions and 39 deletions
+112
View File
@@ -6,6 +6,7 @@
#include <JpegToBmpConverter.h>
#include <Logging.h>
#include <PngToBmpConverter.h>
#include <Serialization.h>
#include <ZipFile.h>
#include <cctype>
@@ -13,11 +14,37 @@
#include "Epub/parsers/ContainerParser.h"
#include "Epub/parsers/ContentOpfParser.h"
#include "Epub/parsers/PageMapParser.h"
#include "Epub/parsers/TocNavParser.h"
#include "Epub/parsers/TocNcxParser.h"
namespace {
// Serialise a list of printed-page entries (href, anchor, label) to pagelist.bin in the
// book cache. Templated on the parser's entry type so both NCX <pageList> and EPUB 3
// <nav epub:type="page-list"> share the same writer.
template <typename Entry>
void writePageListBin(const std::string& cachePath, const std::vector<Entry>& pageList) {
if (pageList.empty()) {
return;
}
const auto pageListPath = cachePath + "/pagelist.bin";
FsFile pageListFile;
if (!Storage.openFileForWrite("EBP", pageListPath, pageListFile)) {
LOG_ERR("EBP", "Could not write pagelist.bin");
return;
}
serialization::writePod(pageListFile, static_cast<uint16_t>(pageList.size()));
for (const auto& entry : pageList) {
serialization::writeString(pageListFile, entry.href);
serialization::writeString(pageListFile, entry.anchor);
serialization::writeString(pageListFile, entry.label);
}
pageListFile.flush();
pageListFile.close();
LOG_DBG("EBP", "Wrote pagelist.bin with %u entries", static_cast<unsigned>(pageList.size()));
}
enum class CoverImageFormat { Unknown, Jpeg, Png };
CoverImageFormat detectCoverImageFormat(FsFile& imageFile) {
@@ -311,6 +338,10 @@ bool Epub::parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata, OpfCac
tocNavItem = opfParser.tocNavPath;
}
if (!opfParser.pageMapPath.empty()) {
pageMapItem = opfParser.pageMapPath;
}
if (!opfParser.cssFiles.empty()) {
cssFiles = opfParser.cssFiles;
}
@@ -373,6 +404,12 @@ bool Epub::parseTocNcxFile() const {
tempNcxFile.close();
Storage.remove(tmpNcxPath.c_str());
// Persist the printed-page list (NCX <pageList>) to a small cache file so the
// section builder can stamp printed-page labels onto rendered pages without
// re-parsing the NCX. Format: u16 count, then per entry: writeString(href),
// writeString(anchor), writeString(label).
writePageListBin(getCachePath(), ncxParser.getPageList());
LOG_DBG("EBP", "Parsed TOC items");
return true;
}
@@ -430,10 +467,74 @@ bool Epub::parseTocNavFile() const {
tempNavFile.close();
Storage.remove(tmpNavPath.c_str());
// Persist EPUB 3 <nav epub:type="page-list"> entries to pagelist.bin (same format
// as the NCX writer); the section builder consumes either source uniformly.
writePageListBin(getCachePath(), navParser.getPageList());
LOG_DBG("EBP", "Parsed TOC nav items");
return true;
}
bool Epub::parsePageMapFile() const {
// EPUB 2.01 page-map.xml: a separate top-level manifest item with media-type
// "application/oebps-page-map+xml". Structure is a flat list of <page name="..." href="..."/>.
if (pageMapItem.empty()) {
LOG_DBG("EBP", "No page-map file specified");
return false;
}
LOG_DBG("EBP", "Parsing page-map file: %s", pageMapItem.c_str());
const auto tmpPageMapPath = getCachePath() + "/page-map.xml";
FsFile tempPageMapFile;
if (!Storage.openFileForWrite("EBP", tmpPageMapPath, tempPageMapFile)) {
return false;
}
readItemContentsToStream(pageMapItem, tempPageMapFile, 1024);
tempPageMapFile.close();
if (!Storage.openFileForRead("EBP", tmpPageMapPath, tempPageMapFile)) {
return false;
}
const auto pageMapSize = tempPageMapFile.size();
// page-map hrefs are relative to the page-map file itself (typically content.opf's dir).
const std::string pageMapBasePath = pageMapItem.substr(0, pageMapItem.find_last_of('/') + 1);
PageMapParser pageMapParser(pageMapBasePath, pageMapSize);
if (!pageMapParser.setup()) {
LOG_ERR("EBP", "Could not setup page-map parser");
tempPageMapFile.close();
return false;
}
const auto pageMapBuffer = static_cast<uint8_t*>(malloc(1024));
if (!pageMapBuffer) {
LOG_ERR("EBP", "Could not allocate memory for page-map parser");
tempPageMapFile.close();
return false;
}
while (tempPageMapFile.available()) {
const auto readSize = tempPageMapFile.read(pageMapBuffer, 1024);
if (readSize == 0) break;
const auto processedSize = pageMapParser.write(pageMapBuffer, readSize);
if (processedSize != readSize) {
LOG_ERR("EBP", "Could not process all page-map data");
free(pageMapBuffer);
tempPageMapFile.close();
return false;
}
}
free(pageMapBuffer);
tempPageMapFile.close();
Storage.remove(tmpPageMapPath.c_str());
writePageListBin(getCachePath(), pageMapParser.getPageList());
LOG_DBG("EBP", "Parsed page-map entries");
return true;
}
void Epub::parseCssFiles() const {
// Maximum CSS file size we'll attempt to parse (uncompressed)
// Larger files risk memory exhaustion on ESP32
@@ -607,6 +708,17 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
// Continue anyway - book will work without TOC
}
// EPUB 2.01 page-map.xml — only parse if neither NCX <pageList> nor nav page-list
// wrote a pagelist.bin already (so an explicit NCX/nav printed-page list always wins).
if (!pageMapItem.empty()) {
const auto pageListPath = getCachePath() + "/pagelist.bin";
if (!Storage.exists(pageListPath.c_str())) {
parsePageMapFile();
} else {
LOG_DBG("EBP", "page-map.xml present but pagelist.bin already written by NCX/nav; skipping");
}
}
if (!bookMetadataCache->endTocPass()) {
LOG_ERR("EBP", "Could not end writing toc pass");
return false;