A fork of https://github.com/crosspoint-reader/crosspoint-reader
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: Fixed book title in home screen (#1013)

## Summary

* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
* The goal is to fix the title of books in the Home Screen.

Before

![IMG_8867](https://github.com/user-attachments/assets/6cc9ca22-b95b-4863-872d-ef427c42f833)

After:

![IMG_8868](https://github.com/user-attachments/assets/585031b1-2348-444c-8f32-073fed3b6582)

* **What changes are included?**

## Additional Context

* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? YES, Cursor

authored by

DestinySpeaker and committed by
GitHub
10a26785 e32d41a3

+121 -37
+4 -1
lib/Epub/Epub/parsers/ContentOpfParser.cpp
··· 102 102 } 103 103 104 104 if (self->state == IN_METADATA && strcmp(name, "dc:title") == 0) { 105 - self->state = IN_BOOK_TITLE; 105 + // Only capture the first dc:title element; subsequent ones are subtitles 106 + if (self->title.empty()) { 107 + self->state = IN_BOOK_TITLE; 108 + } 106 109 return; 107 110 } 108 111
+42 -27
src/RecentBooksStore.cpp
··· 85 85 86 86 LOG_DBG("RBS", "Loading recent book: %s", path.c_str()); 87 87 88 - // If epub, try to load the metadata for title/author and cover 88 + // If epub, try to load the metadata for title/author and cover. 89 + // Use buildIfMissing=false to avoid heavy epub loading on boot; getTitle()/getAuthor() may be 90 + // blank until the book is opened, and entries with missing title are omitted from recent list. 89 91 if (StringUtils::checkFileExtension(lastBookFileName, ".epub")) { 90 92 Epub epub(path, "/.crosspoint"); 91 93 epub.load(false, true); ··· 112 114 113 115 uint8_t version; 114 116 serialization::readPod(inputFile, version); 115 - if (version != RECENT_BOOKS_FILE_VERSION) { 116 - if (version == 1 || version == 2) { 117 - // Old version, just read paths 118 - uint8_t count; 119 - serialization::readPod(inputFile, count); 120 - recentBooks.clear(); 121 - recentBooks.reserve(count); 122 - for (uint8_t i = 0; i < count; i++) { 123 - std::string path; 124 - serialization::readString(inputFile, path); 117 + if (version == 1 || version == 2) { 118 + // Old version, just read paths 119 + uint8_t count; 120 + serialization::readPod(inputFile, count); 121 + recentBooks.clear(); 122 + recentBooks.reserve(count); 123 + for (uint8_t i = 0; i < count; i++) { 124 + std::string path; 125 + serialization::readString(inputFile, path); 125 126 126 - // load book to get missing data 127 - RecentBook book = getDataFromBook(path); 128 - if (book.title.empty() && book.author.empty() && version == 2) { 129 - // Fall back to loading what we can from the store 130 - std::string title, author; 131 - serialization::readString(inputFile, title); 132 - serialization::readString(inputFile, author); 133 - recentBooks.push_back({path, title, author, ""}); 134 - } else { 135 - recentBooks.push_back(book); 136 - } 127 + // load book to get missing data 128 + RecentBook book = getDataFromBook(path); 129 + if (book.title.empty() && book.author.empty() && version == 2) { 130 + // Fall back to loading what we can from the store 131 + std::string title, author; 132 + serialization::readString(inputFile, title); 133 + serialization::readString(inputFile, author); 134 + recentBooks.push_back({path, title, author, ""}); 135 + } else { 136 + recentBooks.push_back(book); 137 137 } 138 - } else { 139 - LOG_ERR("RBS", "Deserialization failed: Unknown version %u", version); 140 - inputFile.close(); 141 - return false; 142 138 } 143 - } else { 139 + } else if (version == 3) { 144 140 uint8_t count; 145 141 serialization::readPod(inputFile, count); 146 142 147 143 recentBooks.clear(); 148 144 recentBooks.reserve(count); 145 + uint8_t omitted = 0; 149 146 150 147 for (uint8_t i = 0; i < count; i++) { 151 148 std::string path, title, author, coverBmpPath; ··· 153 150 serialization::readString(inputFile, title); 154 151 serialization::readString(inputFile, author); 155 152 serialization::readString(inputFile, coverBmpPath); 153 + 154 + // Omit books with missing title (e.g. saved before metadata was available) 155 + if (title.empty()) { 156 + omitted++; 157 + continue; 158 + } 159 + 156 160 recentBooks.push_back({path, title, author, coverBmpPath}); 157 161 } 162 + 163 + if (omitted > 0) { 164 + inputFile.close(); 165 + saveToFile(); 166 + LOG_DBG("RBS", "Omitted %u recent book(s) with missing title", omitted); 167 + return true; 168 + } 169 + } else { 170 + LOG_ERR("RBS", "Deserialization failed: Unknown version %u", version); 171 + inputFile.close(); 172 + return false; 158 173 } 159 174 160 175 inputFile.close();
+7 -3
src/components/themes/BaseTheme.cpp
··· 519 519 // Still have words left, so add ellipsis to last line 520 520 lines.back().append("..."); 521 521 522 - while (!lines.back().empty() && renderer.getTextWidth(UI_12_FONT_ID, lines.back().c_str()) > maxLineWidth) { 522 + while (!lines.back().empty() && lines.back().size() > 3 && 523 + renderer.getTextWidth(UI_12_FONT_ID, lines.back().c_str()) > maxLineWidth) { 523 524 // Remove "..." first, then remove one UTF-8 char, then add "..." back 524 525 lines.back().resize(lines.back().size() - 3); // Remove "..." 525 526 utf8RemoveLastChar(lines.back()); ··· 540 541 break; 541 542 } 542 543 } 544 + if (i.empty()) continue; // Skip words that couldn't fit even truncated 543 545 544 - int newLineWidth = renderer.getTextWidth(UI_12_FONT_ID, currentLine.c_str()); 546 + int newLineWidth = renderer.getTextAdvanceX(UI_12_FONT_ID, currentLine.c_str(), EpdFontFamily::REGULAR); 545 547 if (newLineWidth > 0) { 546 548 newLineWidth += spaceWidth; 547 549 } 548 - newLineWidth += wordWidth; 550 + newLineWidth += renderer.getTextAdvanceX(UI_12_FONT_ID, i.c_str(), EpdFontFamily::REGULAR); 549 551 550 552 if (newLineWidth > maxLineWidth && !currentLine.empty()) { 551 553 // New line too long, push old line 552 554 lines.push_back(currentLine); 555 + currentLine = i; 556 + } else if (currentLine.empty()) { 553 557 currentLine = i; 554 558 } else { 555 559 currentLine.append(" ").append(i);
+68 -6
src/components/themes/lyra/LyraTheme.cpp
··· 4 4 #include <HalPowerManager.h> 5 5 #include <HalStorage.h> 6 6 #include <I18n.h> 7 + #include <Utf8.h> 7 8 8 9 #include <cstdint> 9 10 #include <string> 11 + #include <vector> 10 12 11 13 #include "RecentBooksStore.h" 12 14 #include "components/UITheme.h" ··· 483 485 hPaddingInSelection, cornerRadius, false, false, true, true, Color::LightGray); 484 486 } 485 487 486 - auto title = renderer.truncatedText(UI_12_FONT_ID, book.title.c_str(), textWidth, EpdFontFamily::BOLD); 488 + // Wrap title to up to 3 lines (word-wrap by advance width) 489 + const std::string& lastBookTitle = book.title; 490 + std::vector<std::string> words; 491 + words.reserve(8); 492 + std::string::size_type wordStart = 0; 493 + std::string::size_type wordEnd = 0; 494 + // find_first_not_of skips leading/interstitial spaces 495 + while ((wordStart = lastBookTitle.find_first_not_of(' ', wordEnd)) != std::string::npos) { 496 + wordEnd = lastBookTitle.find(' ', wordStart); 497 + if (wordEnd == std::string::npos) wordEnd = lastBookTitle.size(); 498 + words.emplace_back(lastBookTitle.substr(wordStart, wordEnd - wordStart)); 499 + } 500 + const int maxLineWidth = textWidth; 501 + const int spaceWidth = renderer.getSpaceWidth(UI_12_FONT_ID, EpdFontFamily::BOLD); 502 + std::vector<std::string> titleLines; 503 + std::string currentLine; 504 + for (auto& w : words) { 505 + if (titleLines.size() >= 3) { 506 + titleLines.back().append("..."); 507 + while (!titleLines.back().empty() && titleLines.back().size() > 3 && 508 + renderer.getTextWidth(UI_12_FONT_ID, titleLines.back().c_str(), EpdFontFamily::BOLD) > maxLineWidth) { 509 + titleLines.back().resize(titleLines.back().size() - 3); 510 + utf8RemoveLastChar(titleLines.back()); 511 + titleLines.back().append("..."); 512 + } 513 + break; 514 + } 515 + int wordW = renderer.getTextWidth(UI_12_FONT_ID, w.c_str(), EpdFontFamily::BOLD); 516 + while (wordW > maxLineWidth && !w.empty()) { 517 + utf8RemoveLastChar(w); 518 + std::string withE = w + "..."; 519 + wordW = renderer.getTextWidth(UI_12_FONT_ID, withE.c_str(), EpdFontFamily::BOLD); 520 + if (wordW <= maxLineWidth) { 521 + w = withE; 522 + break; 523 + } 524 + } 525 + if (w.empty()) continue; // Skip words that couldn't fit even truncated 526 + int newW = renderer.getTextAdvanceX(UI_12_FONT_ID, currentLine.c_str(), EpdFontFamily::BOLD); 527 + if (newW > 0) newW += spaceWidth; 528 + newW += renderer.getTextAdvanceX(UI_12_FONT_ID, w.c_str(), EpdFontFamily::BOLD); 529 + if (newW > maxLineWidth && !currentLine.empty()) { 530 + titleLines.push_back(currentLine); 531 + currentLine = w; 532 + } else if (currentLine.empty()) { 533 + currentLine = w; 534 + } else { 535 + currentLine.append(" ").append(w); 536 + } 537 + } 538 + if (!currentLine.empty() && titleLines.size() < 3) titleLines.push_back(currentLine); 539 + 487 540 auto author = renderer.truncatedText(UI_10_FONT_ID, book.author.c_str(), textWidth); 488 - auto bookTitleHeight = renderer.getTextHeight(UI_12_FONT_ID); 489 - renderer.drawText(UI_12_FONT_ID, tileX + hPaddingInSelection + coverWidth + LyraMetrics::values.verticalSpacing, 490 - tileY + tileHeight / 2 - bookTitleHeight, title.c_str(), true, EpdFontFamily::BOLD); 491 - renderer.drawText(UI_10_FONT_ID, tileX + hPaddingInSelection + coverWidth + LyraMetrics::values.verticalSpacing, 492 - tileY + tileHeight / 2 + 5, author.c_str(), true); 541 + const int titleLineHeight = renderer.getLineHeight(UI_12_FONT_ID); 542 + const int titleBlockHeight = titleLineHeight * static_cast<int>(titleLines.size()); 543 + const int authorHeight = book.author.empty() ? 0 : (renderer.getLineHeight(UI_10_FONT_ID) * 3 / 2); 544 + const int totalBlockHeight = titleBlockHeight + authorHeight; 545 + int titleY = tileY + tileHeight / 2 - totalBlockHeight / 2; 546 + const int textX = tileX + hPaddingInSelection + coverWidth + LyraMetrics::values.verticalSpacing; 547 + for (const auto& line : titleLines) { 548 + renderer.drawText(UI_12_FONT_ID, textX, titleY, line.c_str(), true, EpdFontFamily::BOLD); 549 + titleY += titleLineHeight; 550 + } 551 + if (!book.author.empty()) { 552 + titleY += renderer.getLineHeight(UI_10_FONT_ID) / 2; 553 + renderer.drawText(UI_10_FONT_ID, textX, titleY, author.c_str(), true); 554 + } 493 555 } else { 494 556 drawEmptyRecents(renderer, rect); 495 557 }