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:
Justin Mitchell
2026-02-16 07:24:30 -05:00
committed by GitHub
parent 0bc6747483
commit 6702060960
3 changed files with 59 additions and 11 deletions

View File

@@ -77,6 +77,54 @@ bool Epub::parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata) {
bookMetadata.author = opfParser.author;
bookMetadata.language = opfParser.language;
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;
if (!opfParser.tocNcxPath.empty()) {

View File

@@ -296,23 +296,22 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
// parse the guide
if (self->state == IN_GUIDE && (strcmp(name, "reference") == 0 || strcmp(name, "opf:reference") == 0)) {
std::string type;
std::string textHref;
std::string guideHref;
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 {
LOG_DBG("COF", "Skipping non-text reference in guide: %s", type.c_str());
break;
}
} 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)) {
LOG_DBG("COF", "Found %s reference in guide: %s.", type.c_str(), textHref.c_str());
self->textReferenceHref = textHref;
if (!guideHref.empty()) {
if (type == "text" || (type == "start" && !self->textReferenceHref.empty())) {
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;
}

View File

@@ -63,6 +63,7 @@ class ContentOpfParser final : public Print {
std::string tocNcxPath;
std::string tocNavPath; // EPUB 3 nav document path
std::string coverItemHref;
std::string guideCoverPageHref; // Guide reference with type="cover" or "cover-page" (points to XHTML wrapper)
std::string textReferenceHref;
std::vector<std::string> cssFiles; // CSS stylesheet paths