Deal with invalid/incomplete toc.ncx files
This commit is contained in:
+87
-1
@@ -7,11 +7,13 @@
|
|||||||
#include <PngToBmpConverter.h>
|
#include <PngToBmpConverter.h>
|
||||||
#include <ZipFile.h>
|
#include <ZipFile.h>
|
||||||
|
|
||||||
|
#include "CrossPointSettings.h"
|
||||||
#include "Epub/parsers/ContainerParser.h"
|
#include "Epub/parsers/ContainerParser.h"
|
||||||
#include "Epub/parsers/ContentOpfParser.h"
|
#include "Epub/parsers/ContentOpfParser.h"
|
||||||
#include "Epub/parsers/TocNavParser.h"
|
#include "Epub/parsers/TocNavParser.h"
|
||||||
#include "Epub/parsers/TocNcxParser.h"
|
#include "Epub/parsers/TocNcxParser.h"
|
||||||
|
|
||||||
|
|
||||||
bool Epub::findContentOpfFile(std::string* contentOpfFile) const {
|
bool Epub::findContentOpfFile(std::string* contentOpfFile) const {
|
||||||
const auto containerPath = "META-INF/container.xml";
|
const auto containerPath = "META-INF/container.xml";
|
||||||
size_t containerSize;
|
size_t containerSize;
|
||||||
@@ -333,6 +335,7 @@ void Epub::parseCssFiles() const {
|
|||||||
// load in the meta data for the epub file
|
// load in the meta data for the epub file
|
||||||
bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
|
bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
|
||||||
LOG_DBG("EBP", "Loading ePub: %s", filepath.c_str());
|
LOG_DBG("EBP", "Loading ePub: %s", filepath.c_str());
|
||||||
|
tocReliabilityState = -1;
|
||||||
|
|
||||||
// Initialize spine/TOC cache
|
// Initialize spine/TOC cache
|
||||||
bookMetadataCache.reset(new BookMetadataCache(cachePath));
|
bookMetadataCache.reset(new BookMetadataCache(cachePath));
|
||||||
@@ -767,6 +770,18 @@ BookMetadataCache::TocEntry Epub::getTocItem(const int tocIndex) const {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SETTINGS.syntheticTocFallback && !hasReliableToc()) {
|
||||||
|
const int spineCount = bookMetadataCache->getSpineCount();
|
||||||
|
if (tocIndex < 0 || tocIndex >= spineCount) {
|
||||||
|
LOG_DBG("EBP", "getTocItem synthetic index:%d is out of range", tocIndex);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto spine = bookMetadataCache->getSpineEntry(tocIndex);
|
||||||
|
return BookMetadataCache::TocEntry("Section " + std::to_string(tocIndex + 1), spine.href, "", 1,
|
||||||
|
static_cast<int16_t>(tocIndex));
|
||||||
|
}
|
||||||
|
|
||||||
if (tocIndex < 0 || tocIndex >= bookMetadataCache->getTocCount()) {
|
if (tocIndex < 0 || tocIndex >= bookMetadataCache->getTocCount()) {
|
||||||
LOG_DBG("EBP", "getTocItem index:%d is out of range", tocIndex);
|
LOG_DBG("EBP", "getTocItem index:%d is out of range", tocIndex);
|
||||||
return {};
|
return {};
|
||||||
@@ -780,6 +795,10 @@ int Epub::getTocItemsCount() const {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SETTINGS.syntheticTocFallback && !hasReliableToc()) {
|
||||||
|
return bookMetadataCache->getSpineCount();
|
||||||
|
}
|
||||||
|
|
||||||
return bookMetadataCache->getTocCount();
|
return bookMetadataCache->getTocCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -790,6 +809,14 @@ int Epub::getSpineIndexForTocIndex(const int tocIndex) const {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SETTINGS.syntheticTocFallback && !hasReliableToc()) {
|
||||||
|
if (tocIndex < 0 || tocIndex >= bookMetadataCache->getSpineCount()) {
|
||||||
|
LOG_ERR("EBP", "getSpineIndexForTocIndex synthetic tocIndex %d out of range", tocIndex);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return tocIndex;
|
||||||
|
}
|
||||||
|
|
||||||
if (tocIndex < 0 || tocIndex >= bookMetadataCache->getTocCount()) {
|
if (tocIndex < 0 || tocIndex >= bookMetadataCache->getTocCount()) {
|
||||||
LOG_ERR("EBP", "getSpineIndexForTocIndex: tocIndex %d out of range", tocIndex);
|
LOG_ERR("EBP", "getSpineIndexForTocIndex: tocIndex %d out of range", tocIndex);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -804,7 +831,66 @@ int Epub::getSpineIndexForTocIndex(const int tocIndex) const {
|
|||||||
return spineIndex;
|
return spineIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Epub::getTocIndexForSpineIndex(const int spineIndex) const { return getSpineItem(spineIndex).tocIndex; }
|
bool Epub::hasReliableToc() const {
|
||||||
|
if (tocReliabilityState != -1) {
|
||||||
|
return tocReliabilityState == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
||||||
|
tocReliabilityState = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int spineCount = bookMetadataCache->getSpineCount();
|
||||||
|
const int tocCount = bookMetadataCache->getTocCount();
|
||||||
|
|
||||||
|
if (spineCount <= 0 || tocCount <= 0) {
|
||||||
|
tocReliabilityState = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a larger book only exposes one TOC entry, treat TOC as unusable for chapter UX.
|
||||||
|
if (spineCount >= 8 && tocCount <= 1) {
|
||||||
|
tocReliabilityState = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<bool> spineReferenced(static_cast<size_t>(spineCount), false);
|
||||||
|
int distinctSpinesReferenced = 0;
|
||||||
|
for (int i = 0; i < tocCount; i++) {
|
||||||
|
const auto toc = bookMetadataCache->getTocEntry(i);
|
||||||
|
if (toc.spineIndex >= 0 && toc.spineIndex < spineCount) {
|
||||||
|
const size_t idx = static_cast<size_t>(toc.spineIndex);
|
||||||
|
if (!spineReferenced[idx]) {
|
||||||
|
spineReferenced[idx] = true;
|
||||||
|
distinctSpinesReferenced++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Require at least 25% spine coverage from TOC references.
|
||||||
|
const bool reliable = (distinctSpinesReferenced * 4 >= spineCount);
|
||||||
|
tocReliabilityState = reliable ? 1 : 0;
|
||||||
|
return reliable;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Epub::getTocIndexForSpineIndex(const int spineIndex) const {
|
||||||
|
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
||||||
|
LOG_ERR("EBP", "getTocIndexForSpineIndex called but cache not loaded");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spineIndex < 0 || spineIndex >= bookMetadataCache->getSpineCount()) {
|
||||||
|
LOG_ERR("EBP", "getTocIndexForSpineIndex: spineIndex %d out of range", spineIndex);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SETTINGS.syntheticTocFallback && !hasReliableToc()) {
|
||||||
|
return spineIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bookMetadataCache->getSpineEntry(spineIndex).tocIndex;
|
||||||
|
}
|
||||||
|
|
||||||
size_t Epub::getBookSize() const {
|
size_t Epub::getBookSize() const {
|
||||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded() || bookMetadataCache->getSpineCount() == 0) {
|
if (!bookMetadataCache || !bookMetadataCache->isLoaded() || bookMetadataCache->getSpineCount() == 0) {
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ class Epub {
|
|||||||
std::unique_ptr<CssParser> cssParser;
|
std::unique_ptr<CssParser> cssParser;
|
||||||
// CSS files
|
// CSS files
|
||||||
std::vector<std::string> cssFiles;
|
std::vector<std::string> cssFiles;
|
||||||
|
// -1 unknown, 0 unreliable, 1 reliable
|
||||||
|
mutable int tocReliabilityState = -1;
|
||||||
|
|
||||||
bool findContentOpfFile(std::string* contentOpfFile) const;
|
bool findContentOpfFile(std::string* contentOpfFile) const;
|
||||||
bool parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata);
|
bool parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata);
|
||||||
@@ -66,6 +68,7 @@ class Epub {
|
|||||||
int getTocItemsCount() const;
|
int getTocItemsCount() const;
|
||||||
int getSpineIndexForTocIndex(int tocIndex) const;
|
int getSpineIndexForTocIndex(int tocIndex) const;
|
||||||
int getTocIndexForSpineIndex(int spineIndex) const;
|
int getTocIndexForSpineIndex(int spineIndex) const;
|
||||||
|
bool hasReliableToc() const;
|
||||||
size_t getCumulativeSpineItemSize(int spineIndex) const;
|
size_t getCumulativeSpineItemSize(int spineIndex) const;
|
||||||
int getSpineIndexForTextReference() const;
|
int getSpineIndexForTextReference() const;
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ STR_IMAGES: "Images"
|
|||||||
STR_IMAGES_DISPLAY: "Display"
|
STR_IMAGES_DISPLAY: "Display"
|
||||||
STR_IMAGES_PLACEHOLDER: "Placeholder"
|
STR_IMAGES_PLACEHOLDER: "Placeholder"
|
||||||
STR_IMAGES_SUPPRESS: "Suppress"
|
STR_IMAGES_SUPPRESS: "Suppress"
|
||||||
|
STR_CREATE_FALLBACK_FOR_INVALID_TOC: "Create fallback for invalid TOC"
|
||||||
STR_SHORT_PWR_BTN: "Short Power Button Click"
|
STR_SHORT_PWR_BTN: "Short Power Button Click"
|
||||||
STR_ORIENTATION: "Reading Orientation"
|
STR_ORIENTATION: "Reading Orientation"
|
||||||
STR_SIDE_BTN_LAYOUT: "Side Button Layout (reader)"
|
STR_SIDE_BTN_LAYOUT: "Side Button Layout (reader)"
|
||||||
|
|||||||
@@ -199,6 +199,8 @@ class CrossPointSettings {
|
|||||||
uint8_t showHiddenFiles = 0;
|
uint8_t showHiddenFiles = 0;
|
||||||
// Image rendering mode in EPUB reader
|
// Image rendering mode in EPUB reader
|
||||||
uint8_t imageRendering = IMAGES_DISPLAY;
|
uint8_t imageRendering = IMAGES_DISPLAY;
|
||||||
|
// Enable synthetic TOC fallback for malformed/sparse TOC books (1 = enabled, 0 = disabled)
|
||||||
|
uint8_t syntheticTocFallback = 1;
|
||||||
|
|
||||||
~CrossPointSettings() = default;
|
~CrossPointSettings() = default;
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ inline const std::vector<SettingInfo>& getSettingsList() {
|
|||||||
SettingInfo::Enum(StrId::STR_IMAGES, &CrossPointSettings::imageRendering,
|
SettingInfo::Enum(StrId::STR_IMAGES, &CrossPointSettings::imageRendering,
|
||||||
{StrId::STR_IMAGES_DISPLAY, StrId::STR_IMAGES_PLACEHOLDER, StrId::STR_IMAGES_SUPPRESS},
|
{StrId::STR_IMAGES_DISPLAY, StrId::STR_IMAGES_PLACEHOLDER, StrId::STR_IMAGES_SUPPRESS},
|
||||||
"imageRendering", StrId::STR_CAT_READER),
|
"imageRendering", StrId::STR_CAT_READER),
|
||||||
|
SettingInfo::Toggle(StrId::STR_CREATE_FALLBACK_FOR_INVALID_TOC, &CrossPointSettings::syntheticTocFallback,
|
||||||
|
"syntheticTocFallback", StrId::STR_CAT_READER),
|
||||||
// --- Controls ---
|
// --- Controls ---
|
||||||
SettingInfo::Enum(StrId::STR_SIDE_BTN_LAYOUT, &CrossPointSettings::sideButtonLayout,
|
SettingInfo::Enum(StrId::STR_SIDE_BTN_LAYOUT, &CrossPointSettings::sideButtonLayout,
|
||||||
{StrId::STR_PREV_NEXT, StrId::STR_NEXT_PREV}, "sideButtonLayout", StrId::STR_CAT_CONTROLS),
|
{StrId::STR_PREV_NEXT, StrId::STR_NEXT_PREV}, "sideButtonLayout", StrId::STR_CAT_CONTROLS),
|
||||||
|
|||||||
Reference in New Issue
Block a user