fix: Implement guide-based cover image fallback (#830)
This partially fixes #769 but is dependent upon PR #827 being merged along side this @daveallie. I removed my PNG conversion code after finding out that PR was already created. Without this PR though that book in #769 will still fail to load because of how it's stored in the file --------- Co-authored-by: Dave Allie <dave@daveallie.com>
This commit is contained in:
@@ -77,6 +77,54 @@ bool Epub::parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata) {
|
|||||||
bookMetadata.author = opfParser.author;
|
bookMetadata.author = opfParser.author;
|
||||||
bookMetadata.language = opfParser.language;
|
bookMetadata.language = opfParser.language;
|
||||||
bookMetadata.coverItemHref = opfParser.coverItemHref;
|
bookMetadata.coverItemHref = opfParser.coverItemHref;
|
||||||
|
|
||||||
|
// Guide-based cover fallback: if no cover found via metadata/properties,
|
||||||
|
// try extracting the image reference from the guide's cover page XHTML
|
||||||
|
if (bookMetadata.coverItemHref.empty() && !opfParser.guideCoverPageHref.empty()) {
|
||||||
|
LOG_DBG("EBP", "No cover from metadata, trying guide cover page: %s", opfParser.guideCoverPageHref.c_str());
|
||||||
|
size_t coverPageSize;
|
||||||
|
uint8_t* coverPageData = readItemContentsToBytes(opfParser.guideCoverPageHref, &coverPageSize, true);
|
||||||
|
if (coverPageData) {
|
||||||
|
const std::string coverPageHtml(reinterpret_cast<char*>(coverPageData), coverPageSize);
|
||||||
|
free(coverPageData);
|
||||||
|
|
||||||
|
// Determine base path of the cover page for resolving relative image references
|
||||||
|
std::string coverPageBase;
|
||||||
|
const auto lastSlash = opfParser.guideCoverPageHref.rfind('/');
|
||||||
|
if (lastSlash != std::string::npos) {
|
||||||
|
coverPageBase = opfParser.guideCoverPageHref.substr(0, lastSlash + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for image references: xlink:href="..." (SVG) and src="..." (img)
|
||||||
|
std::string imageRef;
|
||||||
|
for (const char* pattern : {"xlink:href=\"", "src=\""}) {
|
||||||
|
auto pos = coverPageHtml.find(pattern);
|
||||||
|
while (pos != std::string::npos) {
|
||||||
|
pos += strlen(pattern);
|
||||||
|
const auto endPos = coverPageHtml.find('"', pos);
|
||||||
|
if (endPos != std::string::npos) {
|
||||||
|
const auto ref = coverPageHtml.substr(pos, endPos - pos);
|
||||||
|
// Check if it's an image file
|
||||||
|
if (ref.length() >= 4) {
|
||||||
|
const auto ext = ref.substr(ref.length() - 4);
|
||||||
|
if (ext == ".png" || ext == ".jpg" || ext == "jpeg" || ext == ".gif") {
|
||||||
|
imageRef = ref;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos = coverPageHtml.find(pattern, pos);
|
||||||
|
}
|
||||||
|
if (!imageRef.empty()) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!imageRef.empty()) {
|
||||||
|
bookMetadata.coverItemHref = FsHelpers::normalisePath(coverPageBase + imageRef);
|
||||||
|
LOG_DBG("EBP", "Found cover image from guide: %s", bookMetadata.coverItemHref.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bookMetadata.textReferenceHref = opfParser.textReferenceHref;
|
bookMetadata.textReferenceHref = opfParser.textReferenceHref;
|
||||||
|
|
||||||
if (!opfParser.tocNcxPath.empty()) {
|
if (!opfParser.tocNcxPath.empty()) {
|
||||||
|
|||||||
@@ -296,23 +296,22 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
|||||||
// parse the guide
|
// parse the guide
|
||||||
if (self->state == IN_GUIDE && (strcmp(name, "reference") == 0 || strcmp(name, "opf:reference") == 0)) {
|
if (self->state == IN_GUIDE && (strcmp(name, "reference") == 0 || strcmp(name, "opf:reference") == 0)) {
|
||||||
std::string type;
|
std::string type;
|
||||||
std::string textHref;
|
std::string guideHref;
|
||||||
for (int i = 0; atts[i]; i += 2) {
|
for (int i = 0; atts[i]; i += 2) {
|
||||||
if (strcmp(atts[i], "type") == 0) {
|
if (strcmp(atts[i], "type") == 0) {
|
||||||
type = atts[i + 1];
|
type = atts[i + 1];
|
||||||
if (type == "text" || type == "start") {
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
LOG_DBG("COF", "Skipping non-text reference in guide: %s", type.c_str());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (strcmp(atts[i], "href") == 0) {
|
} else if (strcmp(atts[i], "href") == 0) {
|
||||||
textHref = FsHelpers::normalisePath(self->baseContentPath + atts[i + 1]);
|
guideHref = FsHelpers::normalisePath(self->baseContentPath + atts[i + 1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((type == "text" || (type == "start" && !self->textReferenceHref.empty())) && (textHref.length() > 0)) {
|
if (!guideHref.empty()) {
|
||||||
LOG_DBG("COF", "Found %s reference in guide: %s.", type.c_str(), textHref.c_str());
|
if (type == "text" || (type == "start" && !self->textReferenceHref.empty())) {
|
||||||
self->textReferenceHref = textHref;
|
LOG_DBG("COF", "Found %s reference in guide: %s", type.c_str(), guideHref.c_str());
|
||||||
|
self->textReferenceHref = guideHref;
|
||||||
|
} else if ((type == "cover" || type == "cover-page") && self->guideCoverPageHref.empty()) {
|
||||||
|
LOG_DBG("COF", "Found cover reference in guide: %s", guideHref.c_str());
|
||||||
|
self->guideCoverPageHref = guideHref;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ class ContentOpfParser final : public Print {
|
|||||||
std::string tocNcxPath;
|
std::string tocNcxPath;
|
||||||
std::string tocNavPath; // EPUB 3 nav document path
|
std::string tocNavPath; // EPUB 3 nav document path
|
||||||
std::string coverItemHref;
|
std::string coverItemHref;
|
||||||
|
std::string guideCoverPageHref; // Guide reference with type="cover" or "cover-page" (points to XHTML wrapper)
|
||||||
std::string textReferenceHref;
|
std::string textReferenceHref;
|
||||||
std::vector<std::string> cssFiles; // CSS stylesheet paths
|
std::vector<std::string> cssFiles; // CSS stylesheet paths
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user