Feature: go to text/start reference in epub guide section at first start (#156)
This parses the guide section in the content.opf for text/start references and jumps to this on first open of the book. Currently, this behavior will be repeated in case the reader manually jumps to Chapter 0 and then re-opens the book. IMO, this is an acceptable edge case (for which I couldn't see a good fix other than to drag a "first open" boolean around). --------- Co-authored-by: Sam Davis <sam@sjd.co> Co-authored-by: Dave Allie <dave@daveallie.com>
This commit is contained in:
parent
be1b5bad21
commit
03f0ce04cc
@ -74,6 +74,7 @@ bool Epub::parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata) {
|
|||||||
bookMetadata.title = opfParser.title;
|
bookMetadata.title = opfParser.title;
|
||||||
bookMetadata.author = opfParser.author;
|
bookMetadata.author = opfParser.author;
|
||||||
bookMetadata.coverItemHref = opfParser.coverItemHref;
|
bookMetadata.coverItemHref = opfParser.coverItemHref;
|
||||||
|
bookMetadata.textReferenceHref = opfParser.textReferenceHref;
|
||||||
|
|
||||||
if (!opfParser.tocNcxPath.empty()) {
|
if (!opfParser.tocNcxPath.empty()) {
|
||||||
tocNcxItem = opfParser.tocNcxPath;
|
tocNcxItem = opfParser.tocNcxPath;
|
||||||
@ -426,6 +427,35 @@ size_t Epub::getBookSize() const {
|
|||||||
return getCumulativeSpineItemSize(getSpineItemsCount() - 1);
|
return getCumulativeSpineItemSize(getSpineItemsCount() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Epub::getSpineIndexForTextReference() const {
|
||||||
|
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
||||||
|
Serial.printf("[%lu] [EBP] getSpineIndexForTextReference called but cache not loaded\n", millis());
|
||||||
|
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());
|
||||||
|
|
||||||
|
if (bookMetadataCache->coreMetadata.textReferenceHref.empty()) {
|
||||||
|
// there was no textReference in epub, so we return 0 (the first chapter)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
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());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate progress in book
|
// Calculate progress in book
|
||||||
uint8_t Epub::calculateProgress(const int currentSpineIndex, const float currentSpineRead) const {
|
uint8_t Epub::calculateProgress(const int currentSpineIndex, const float currentSpineRead) const {
|
||||||
const size_t bookSize = getBookSize();
|
const size_t bookSize = getBookSize();
|
||||||
|
|||||||
@ -54,6 +54,7 @@ class Epub {
|
|||||||
int getSpineIndexForTocIndex(int tocIndex) const;
|
int getSpineIndexForTocIndex(int tocIndex) const;
|
||||||
int getTocIndexForSpineIndex(int spineIndex) const;
|
int getTocIndexForSpineIndex(int spineIndex) const;
|
||||||
size_t getCumulativeSpineItemSize(int spineIndex) const;
|
size_t getCumulativeSpineItemSize(int spineIndex) const;
|
||||||
|
int getSpineIndexForTextReference() const;
|
||||||
|
|
||||||
size_t getBookSize() const;
|
size_t getBookSize() const;
|
||||||
uint8_t calculateProgress(int currentSpineIndex, float currentSpineRead) const;
|
uint8_t calculateProgress(int currentSpineIndex, float currentSpineRead) const;
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
#include "FsHelpers.h"
|
#include "FsHelpers.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr uint8_t BOOK_CACHE_VERSION = 2;
|
constexpr uint8_t BOOK_CACHE_VERSION = 3;
|
||||||
constexpr char bookBinFile[] = "/book.bin";
|
constexpr char bookBinFile[] = "/book.bin";
|
||||||
constexpr char tmpSpineBinFile[] = "/spine.bin.tmp";
|
constexpr char tmpSpineBinFile[] = "/spine.bin.tmp";
|
||||||
constexpr char tmpTocBinFile[] = "/toc.bin.tmp";
|
constexpr char tmpTocBinFile[] = "/toc.bin.tmp";
|
||||||
@ -87,8 +87,8 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
|
|||||||
|
|
||||||
constexpr uint32_t headerASize =
|
constexpr uint32_t headerASize =
|
||||||
sizeof(BOOK_CACHE_VERSION) + /* LUT Offset */ sizeof(uint32_t) + sizeof(spineCount) + sizeof(tocCount);
|
sizeof(BOOK_CACHE_VERSION) + /* LUT Offset */ sizeof(uint32_t) + sizeof(spineCount) + sizeof(tocCount);
|
||||||
const uint32_t metadataSize =
|
const uint32_t metadataSize = metadata.title.size() + metadata.author.size() + metadata.coverItemHref.size() +
|
||||||
metadata.title.size() + metadata.author.size() + metadata.coverItemHref.size() + sizeof(uint32_t) * 3;
|
metadata.textReferenceHref.size() + sizeof(uint32_t) * 4;
|
||||||
const uint32_t lutSize = sizeof(uint32_t) * spineCount + sizeof(uint32_t) * tocCount;
|
const uint32_t lutSize = sizeof(uint32_t) * spineCount + sizeof(uint32_t) * tocCount;
|
||||||
const uint32_t lutOffset = headerASize + metadataSize;
|
const uint32_t lutOffset = headerASize + metadataSize;
|
||||||
|
|
||||||
@ -101,6 +101,7 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
|
|||||||
serialization::writeString(bookFile, metadata.title);
|
serialization::writeString(bookFile, metadata.title);
|
||||||
serialization::writeString(bookFile, metadata.author);
|
serialization::writeString(bookFile, metadata.author);
|
||||||
serialization::writeString(bookFile, metadata.coverItemHref);
|
serialization::writeString(bookFile, metadata.coverItemHref);
|
||||||
|
serialization::writeString(bookFile, metadata.textReferenceHref);
|
||||||
|
|
||||||
// Loop through spine entries, writing LUT positions
|
// Loop through spine entries, writing LUT positions
|
||||||
spineFile.seek(0);
|
spineFile.seek(0);
|
||||||
@ -289,6 +290,7 @@ bool BookMetadataCache::load() {
|
|||||||
serialization::readString(bookFile, coreMetadata.title);
|
serialization::readString(bookFile, coreMetadata.title);
|
||||||
serialization::readString(bookFile, coreMetadata.author);
|
serialization::readString(bookFile, coreMetadata.author);
|
||||||
serialization::readString(bookFile, coreMetadata.coverItemHref);
|
serialization::readString(bookFile, coreMetadata.coverItemHref);
|
||||||
|
serialization::readString(bookFile, coreMetadata.textReferenceHref);
|
||||||
|
|
||||||
loaded = true;
|
loaded = true;
|
||||||
Serial.printf("[%lu] [BMC] Loaded cache data: %d spine, %d TOC entries\n", millis(), spineCount, tocCount);
|
Serial.printf("[%lu] [BMC] Loaded cache data: %d spine, %d TOC entries\n", millis(), spineCount, tocCount);
|
||||||
|
|||||||
@ -10,6 +10,7 @@ class BookMetadataCache {
|
|||||||
std::string title;
|
std::string title;
|
||||||
std::string author;
|
std::string author;
|
||||||
std::string coverItemHref;
|
std::string coverItemHref;
|
||||||
|
std::string textReferenceHref;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SpineEntry {
|
struct SpineEntry {
|
||||||
|
|||||||
@ -127,6 +127,18 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
if (!SdMan.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());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (self->state == IN_METADATA && (strcmp(name, "meta") == 0 || strcmp(name, "opf:meta") == 0)) {
|
if (self->state == IN_METADATA && (strcmp(name, "meta") == 0 || strcmp(name, "opf:meta") == 0)) {
|
||||||
bool isCover = false;
|
bool isCover = false;
|
||||||
std::string coverItemId;
|
std::string coverItemId;
|
||||||
@ -205,6 +217,29 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// parse the guide
|
||||||
|
if (self->state == IN_GUIDE && (strcmp(name, "reference") == 0 || strcmp(name, "opf:reference") == 0)) {
|
||||||
|
std::string type;
|
||||||
|
std::string textHref;
|
||||||
|
for (int i = 0; atts[i]; i += 2) {
|
||||||
|
if (strcmp(atts[i], "type") == 0) {
|
||||||
|
type = atts[i + 1];
|
||||||
|
if (type == "text" || type == "start") {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
Serial.printf("[%lu] [COF] Skipping non-text reference in guide: %s\n", millis(), type.c_str());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (strcmp(atts[i], "href") == 0) {
|
||||||
|
textHref = self->baseContentPath + atts[i + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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());
|
||||||
|
self->textReferenceHref = textHref;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void XMLCALL ContentOpfParser::characterData(void* userData, const XML_Char* s, const int len) {
|
void XMLCALL ContentOpfParser::characterData(void* userData, const XML_Char* s, const int len) {
|
||||||
@ -231,6 +266,12 @@ void XMLCALL ContentOpfParser::endElement(void* userData, const XML_Char* name)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self->state == IN_GUIDE && (strcmp(name, "guide") == 0 || strcmp(name, "opf:guide") == 0)) {
|
||||||
|
self->state = IN_PACKAGE;
|
||||||
|
self->tempItemStore.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (self->state == IN_MANIFEST && (strcmp(name, "manifest") == 0 || strcmp(name, "opf:manifest") == 0)) {
|
if (self->state == IN_MANIFEST && (strcmp(name, "manifest") == 0 || strcmp(name, "opf:manifest") == 0)) {
|
||||||
self->state = IN_PACKAGE;
|
self->state = IN_PACKAGE;
|
||||||
self->tempItemStore.close();
|
self->tempItemStore.close();
|
||||||
|
|||||||
@ -15,6 +15,7 @@ class ContentOpfParser final : public Print {
|
|||||||
IN_BOOK_AUTHOR,
|
IN_BOOK_AUTHOR,
|
||||||
IN_MANIFEST,
|
IN_MANIFEST,
|
||||||
IN_SPINE,
|
IN_SPINE,
|
||||||
|
IN_GUIDE,
|
||||||
};
|
};
|
||||||
|
|
||||||
const std::string& cachePath;
|
const std::string& cachePath;
|
||||||
@ -35,6 +36,7 @@ class ContentOpfParser final : public Print {
|
|||||||
std::string author;
|
std::string author;
|
||||||
std::string tocNcxPath;
|
std::string tocNcxPath;
|
||||||
std::string coverItemHref;
|
std::string coverItemHref;
|
||||||
|
std::string textReferenceHref;
|
||||||
|
|
||||||
explicit ContentOpfParser(const std::string& cachePath, const std::string& baseContentPath, const size_t xmlSize,
|
explicit ContentOpfParser(const std::string& cachePath, const std::string& baseContentPath, const size_t xmlSize,
|
||||||
BookMetadataCache* cache)
|
BookMetadataCache* cache)
|
||||||
|
|||||||
@ -65,6 +65,16 @@ void EpubReaderActivity::onEnter() {
|
|||||||
}
|
}
|
||||||
f.close();
|
f.close();
|
||||||
}
|
}
|
||||||
|
// We may want a better condition to detect if we are opening for the first time.
|
||||||
|
// This will trigger if the book is re-opened at Chapter 0.
|
||||||
|
if (currentSpineIndex == 0) {
|
||||||
|
int textSpineIndex = epub->getSpineIndexForTextReference();
|
||||||
|
if (textSpineIndex != 0) {
|
||||||
|
currentSpineIndex = textSpineIndex;
|
||||||
|
Serial.printf("[%lu] [ERS] Opened for first time, navigating to text reference at index %d\n", millis(),
|
||||||
|
textSpineIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Save current epub as last opened epub
|
// Save current epub as last opened epub
|
||||||
APP_STATE.openEpubPath = epub->getPath();
|
APP_STATE.openEpubPath = epub->getPath();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user