| 46 | } |
| 47 | |
| 48 | bool Epub::parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata, const bool writeSpineEntries) { |
| 49 | std::string contentOpfFilePath; |
| 50 | if (!findContentOpfFile(&contentOpfFilePath)) { |
| 51 | LOG_ERR("EBP", "Could not find content.opf in zip"); |
| 52 | return false; |
| 53 | } |
| 54 | |
| 55 | contentBasePath = contentOpfFilePath.substr(0, contentOpfFilePath.find_last_of('/') + 1); |
| 56 | |
| 57 | LOG_DBG("EBP", "Parsing content.opf: %s", contentOpfFilePath.c_str()); |
| 58 | |
| 59 | size_t contentOpfSize; |
| 60 | if (!getItemSize(contentOpfFilePath, &contentOpfSize)) { |
| 61 | LOG_ERR("EBP", "Could not get size of content.opf"); |
| 62 | return false; |
| 63 | } |
| 64 | |
| 65 | ContentOpfParser opfParser(getCachePath(), getBasePath(), contentOpfSize, |
| 66 | writeSpineEntries ? bookMetadataCache.get() : nullptr); |
| 67 | if (!opfParser.setup()) { |
| 68 | LOG_ERR("EBP", "Could not setup content.opf parser"); |
| 69 | return false; |
| 70 | } |
| 71 | |
| 72 | if (!readItemContentsToStream(contentOpfFilePath, opfParser, 1024)) { |
| 73 | LOG_ERR("EBP", "Could not read content.opf"); |
| 74 | return false; |
| 75 | } |
| 76 | |
| 77 | // Grab data from opfParser into epub. Normalize titles to NFC so NFD (combining |
| 78 | // mark) text renders correctly — the device fonts have no mark positioning. |
| 79 | bookMetadata.title = utf8ComposeNfc(opfParser.title); |
| 80 | bookMetadata.author = opfParser.author; |
| 81 | bookMetadata.language = opfParser.language; |
| 82 | bookMetadata.coverItemHref = opfParser.coverItemHref; |
| 83 | |
| 84 | // Guide-based cover fallback: if no cover found via metadata/properties, |
| 85 | // try extracting the image reference from the guide's cover page XHTML |
| 86 | if (bookMetadata.coverItemHref.empty() && !opfParser.guideCoverPageHref.empty()) { |
| 87 | LOG_DBG("EBP", "No cover from metadata, trying guide cover page: %s", opfParser.guideCoverPageHref.c_str()); |
| 88 | size_t coverPageSize; |
| 89 | uint8_t* coverPageData = readItemContentsToBytes(opfParser.guideCoverPageHref, &coverPageSize, true); |
| 90 | if (coverPageData) { |
| 91 | const std::string coverPageHtml(reinterpret_cast<char*>(coverPageData), coverPageSize); |
| 92 | free(coverPageData); |
| 93 | |
| 94 | // Determine base path of the cover page for resolving relative image references |
| 95 | std::string coverPageBase; |
| 96 | const auto lastSlash = opfParser.guideCoverPageHref.rfind('/'); |
| 97 | if (lastSlash != std::string::npos) { |
| 98 | coverPageBase = opfParser.guideCoverPageHref.substr(0, lastSlash + 1); |
| 99 | } |
| 100 | |
| 101 | // Search for image references: xlink:href="..." (SVG) and src="..." (img) |
| 102 | std::string imageRef; |
| 103 | for (const char* pattern : {"xlink:href=\"", "src=\""}) { |
| 104 | auto pos = coverPageHtml.find(pattern); |
| 105 | while (pos != std::string::npos) { |
nothing calls this directly
no test coverage detected