Compare commits

..

1 Commits

Author SHA1 Message Date
Uri Tauber
f9a4df695b
Merge 94411049c8 into da4d3b5ea5 2026-01-27 21:14:52 +00:00
5 changed files with 73 additions and 101 deletions

View File

@ -49,25 +49,17 @@ uint16_t measureWordWidth(const GfxRenderer& renderer, const int fontId, const s
} // namespace } // namespace
void ParsedText::addWord(std::string word, const EpdFontFamily::Style fontStyle, void ParsedText::addWord(std::string word, const EpdFontFamily::Style fontStyle) {
std::unique_ptr<FootnoteEntry> footnote) {
if (word.empty()) return; if (word.empty()) return;
words.push_back(std::move(word)); words.push_back(std::move(word));
wordStyles.push_back(fontStyle); wordStyles.push_back(fontStyle);
if (footnote) {
wordHasFootnote.push_back(1);
footnoteQueue.push_back(*footnote);
} else {
wordHasFootnote.push_back(0);
}
} }
// Consumes data to minimize memory usage // Consumes data to minimize memory usage
void ParsedText::layoutAndExtractLines( void ParsedText::layoutAndExtractLines(const GfxRenderer& renderer, const int fontId, const uint16_t viewportWidth,
const GfxRenderer& renderer, const int fontId, const uint16_t viewportWidth, const std::function<void(std::shared_ptr<TextBlock>)>& processLine,
const std::function<void(std::shared_ptr<TextBlock>, const std::vector<FootnoteEntry>&)>& processLine, const bool includeLastLine) {
const bool includeLastLine) {
if (words.empty()) { if (words.empty()) {
return; return;
} }
@ -263,8 +255,8 @@ std::vector<size_t> ParsedText::computeHyphenatedLineBreaks(const GfxRenderer& r
return lineBreakIndices; return lineBreakIndices;
} }
// Splits words[wordIndex] into prefix (adding a hyphen only when needed) // Splits words[wordIndex] into prefix (adding a hyphen only when needed) and remainder when a legal breakpoint fits the
// and remainder when a legal breakpoint fits the available width. // available width.
bool ParsedText::hyphenateWordAtIndex(const size_t wordIndex, const int availableWidth, const GfxRenderer& renderer, bool ParsedText::hyphenateWordAtIndex(const size_t wordIndex, const int availableWidth, const GfxRenderer& renderer,
const int fontId, std::vector<uint16_t>& wordWidths, const int fontId, std::vector<uint16_t>& wordWidths,
const bool allowFallbackBreaks) { const bool allowFallbackBreaks) {
@ -328,13 +320,6 @@ bool ParsedText::hyphenateWordAtIndex(const size_t wordIndex, const int availabl
words.insert(insertWordIt, remainder); words.insert(insertWordIt, remainder);
wordStyles.insert(insertStyleIt, style); wordStyles.insert(insertStyleIt, style);
// Split wordHasFootnote as well. The footnote (if any) is associated with the remainder word.
auto wordHasFootnoteIt = wordHasFootnote.begin();
std::advance(wordHasFootnoteIt, wordIndex);
uint8_t hasFootnote = *wordHasFootnoteIt;
*wordHasFootnoteIt = 0; // First part doesn't have it anymore
wordHasFootnote.insert(std::next(wordHasFootnoteIt), hasFootnote);
// Update cached widths to reflect the new prefix/remainder pairing. // Update cached widths to reflect the new prefix/remainder pairing.
wordWidths[wordIndex] = static_cast<uint16_t>(chosenWidth); wordWidths[wordIndex] = static_cast<uint16_t>(chosenWidth);
const uint16_t remainderWidth = measureWordWidth(renderer, fontId, remainder, style); const uint16_t remainderWidth = measureWordWidth(renderer, fontId, remainder, style);
@ -342,10 +327,9 @@ bool ParsedText::hyphenateWordAtIndex(const size_t wordIndex, const int availabl
return true; return true;
} }
void ParsedText::extractLine( void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const int spaceWidth,
const size_t breakIndex, const int pageWidth, const int spaceWidth, const std::vector<uint16_t>& wordWidths, const std::vector<uint16_t>& wordWidths, const std::vector<size_t>& lineBreakIndices,
const std::vector<size_t>& lineBreakIndices, const std::function<void(std::shared_ptr<TextBlock>)>& processLine) {
const std::function<void(std::shared_ptr<TextBlock>, const std::vector<FootnoteEntry>&)>& processLine) {
const size_t lineBreak = lineBreakIndices[breakIndex]; const size_t lineBreak = lineBreakIndices[breakIndex];
const size_t lastBreakAt = breakIndex > 0 ? lineBreakIndices[breakIndex - 1] : 0; const size_t lastBreakAt = breakIndex > 0 ? lineBreakIndices[breakIndex - 1] : 0;
const size_t lineWordCount = lineBreak - lastBreakAt; const size_t lineWordCount = lineBreak - lastBreakAt;
@ -388,35 +372,17 @@ void ParsedText::extractLine(
std::advance(wordEndIt, lineWordCount); std::advance(wordEndIt, lineWordCount);
std::advance(wordStyleEndIt, lineWordCount); std::advance(wordStyleEndIt, lineWordCount);
// *** CRITICAL STEP: CONSUME DATA USING SPLICE ***
std::list<std::string> lineWords; std::list<std::string> lineWords;
lineWords.splice(lineWords.begin(), words, words.begin(), wordEndIt); lineWords.splice(lineWords.begin(), words, words.begin(), wordEndIt);
std::list<EpdFontFamily::Style> lineWordStyles; std::list<EpdFontFamily::Style> lineWordStyles;
lineWordStyles.splice(lineWordStyles.begin(), wordStyles, wordStyles.begin(), wordStyleEndIt); lineWordStyles.splice(lineWordStyles.begin(), wordStyles, wordStyles.begin(), wordStyleEndIt);
// Extract footnote flags from deque
std::vector<FootnoteEntry> lineFootnotes;
for (size_t i = 0; i < lineWordCount; i++) {
if (!wordHasFootnote.empty()) {
uint8_t hasFn = wordHasFootnote.front();
wordHasFootnote.pop_front();
if (hasFn) {
if (footnoteQueue.empty()) {
Serial.printf("[%lu] [ERROR] Footnote flag set but queue empty! Flags/queue out of sync.\n", millis());
break;
}
lineFootnotes.push_back(footnoteQueue.front());
footnoteQueue.pop_front();
}
}
}
for (auto& word : lineWords) { for (auto& word : lineWords) {
if (containsSoftHyphen(word)) { if (containsSoftHyphen(word)) {
stripSoftHyphensInPlace(word); stripSoftHyphensInPlace(word);
} }
} }
processLine(std::make_shared<TextBlock>(std::move(lineWords), std::move(lineXPos), std::move(lineWordStyles), style), processLine(std::make_shared<TextBlock>(std::move(lineWords), std::move(lineXPos), std::move(lineWordStyles), style));
lineFootnotes); }
}

View File

@ -2,14 +2,12 @@
#include <EpdFontFamily.h> #include <EpdFontFamily.h>
#include <deque>
#include <functional> #include <functional>
#include <list> #include <list>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include "FootnoteEntry.h"
#include "blocks/TextBlock.h" #include "blocks/TextBlock.h"
class GfxRenderer; class GfxRenderer;
@ -17,8 +15,6 @@ class GfxRenderer;
class ParsedText { class ParsedText {
std::list<std::string> words; std::list<std::string> words;
std::list<EpdFontFamily::Style> wordStyles; std::list<EpdFontFamily::Style> wordStyles;
std::deque<uint8_t> wordHasFootnote;
std::deque<FootnoteEntry> footnoteQueue;
TextBlock::Style style; TextBlock::Style style;
bool extraParagraphSpacing; bool extraParagraphSpacing;
bool hyphenationEnabled; bool hyphenationEnabled;
@ -30,10 +26,9 @@ class ParsedText {
int spaceWidth, std::vector<uint16_t>& wordWidths); int spaceWidth, std::vector<uint16_t>& wordWidths);
bool hyphenateWordAtIndex(size_t wordIndex, int availableWidth, const GfxRenderer& renderer, int fontId, bool hyphenateWordAtIndex(size_t wordIndex, int availableWidth, const GfxRenderer& renderer, int fontId,
std::vector<uint16_t>& wordWidths, bool allowFallbackBreaks); std::vector<uint16_t>& wordWidths, bool allowFallbackBreaks);
void extractLine( void extractLine(size_t breakIndex, int pageWidth, int spaceWidth, const std::vector<uint16_t>& wordWidths,
size_t breakIndex, int pageWidth, int spaceWidth, const std::vector<uint16_t>& wordWidths, const std::vector<size_t>& lineBreakIndices,
const std::vector<size_t>& lineBreakIndices, const std::function<void(std::shared_ptr<TextBlock>)>& processLine);
const std::function<void(std::shared_ptr<TextBlock>, const std::vector<FootnoteEntry>&)>& processLine);
std::vector<uint16_t> calculateWordWidths(const GfxRenderer& renderer, int fontId); std::vector<uint16_t> calculateWordWidths(const GfxRenderer& renderer, int fontId);
public: public:
@ -42,13 +37,12 @@ class ParsedText {
: style(style), extraParagraphSpacing(extraParagraphSpacing), hyphenationEnabled(hyphenationEnabled) {} : style(style), extraParagraphSpacing(extraParagraphSpacing), hyphenationEnabled(hyphenationEnabled) {}
~ParsedText() = default; ~ParsedText() = default;
void addWord(std::string word, EpdFontFamily::Style fontStyle, std::unique_ptr<FootnoteEntry> footnote = nullptr); void addWord(std::string word, EpdFontFamily::Style fontStyle);
void setStyle(const TextBlock::Style style) { this->style = style; } void setStyle(const TextBlock::Style style) { this->style = style; }
TextBlock::Style getStyle() const { return style; } TextBlock::Style getStyle() const { return style; }
size_t size() const { return words.size(); } size_t size() const { return words.size(); }
bool isEmpty() const { return words.empty(); } bool isEmpty() const { return words.empty(); }
void layoutAndExtractLines( void layoutAndExtractLines(const GfxRenderer& renderer, int fontId, uint16_t viewportWidth,
const GfxRenderer& renderer, int fontId, uint16_t viewportWidth, const std::function<void(std::shared_ptr<TextBlock>)>& processLine,
const std::function<void(std::shared_ptr<TextBlock>, const std::vector<FootnoteEntry>&)>& processLine, bool includeLastLine = true);
bool includeLastLine = true); };
};

View File

@ -116,14 +116,14 @@ void ChapterHtmlSlimParser::startNewTextBlock(const TextBlock::Style style) {
currentTextBlock.reset(new ParsedText(style, extraParagraphSpacing, hyphenationEnabled)); currentTextBlock.reset(new ParsedText(style, extraParagraphSpacing, hyphenationEnabled));
} }
std::unique_ptr<FootnoteEntry> ChapterHtmlSlimParser::createFootnoteEntry(const char* number, const char* href) { void ChapterHtmlSlimParser::addFootnoteToCurrentPage(const char* number, const char* href) {
auto entry = std::unique_ptr<FootnoteEntry>(new FootnoteEntry()); if (currentPageFootnoteCount >= 16) return;
Serial.printf("[%lu] [ADDFT] Creating footnote: num=%s, href=%s\n", millis(), number, href); Serial.printf("[%lu] [ADDFT] Adding footnote: num=%s, href=%s\n", millis(), number, href);
// Copy number // Copy number
strncpy(entry->number, number, 2); strncpy(currentPageFootnotes[currentPageFootnoteCount].number, number, 2);
entry->number[2] = '\0'; currentPageFootnotes[currentPageFootnoteCount].number[2] = '\0';
// Check if this is an inline footnote reference // Check if this is an inline footnote reference
const char* hashPos = strchr(href, '#'); const char* hashPos = strchr(href, '#');
@ -138,8 +138,8 @@ std::unique_ptr<FootnoteEntry> ChapterHtmlSlimParser::createFootnoteEntry(const
char rewrittenHref[64]; char rewrittenHref[64];
snprintf(rewrittenHref, sizeof(rewrittenHref), "inline_%s.html#%s", inlineId, inlineId); snprintf(rewrittenHref, sizeof(rewrittenHref), "inline_%s.html#%s", inlineId, inlineId);
strncpy(entry->href, rewrittenHref, 63); strncpy(currentPageFootnotes[currentPageFootnoteCount].href, rewrittenHref, 63);
entry->href[63] = '\0'; currentPageFootnotes[currentPageFootnoteCount].href[63] = '\0';
Serial.printf("[%lu] [ADDFT] Rewrote inline href to: %s\n", millis(), rewrittenHref); Serial.printf("[%lu] [ADDFT] Rewrote inline href to: %s\n", millis(), rewrittenHref);
foundInline = true; foundInline = true;
@ -154,8 +154,8 @@ std::unique_ptr<FootnoteEntry> ChapterHtmlSlimParser::createFootnoteEntry(const
char rewrittenHref[64]; char rewrittenHref[64];
snprintf(rewrittenHref, sizeof(rewrittenHref), "pnote_%s.html#%s", inlineId, inlineId); snprintf(rewrittenHref, sizeof(rewrittenHref), "pnote_%s.html#%s", inlineId, inlineId);
strncpy(entry->href, rewrittenHref, 63); strncpy(currentPageFootnotes[currentPageFootnoteCount].href, rewrittenHref, 63);
entry->href[63] = '\0'; currentPageFootnotes[currentPageFootnoteCount].href[63] = '\0';
Serial.printf("[%lu] [ADDFT] Rewrote paragraph note href to: %s\n", millis(), rewrittenHref); Serial.printf("[%lu] [ADDFT] Rewrote paragraph note href to: %s\n", millis(), rewrittenHref);
foundInline = true; foundInline = true;
@ -166,17 +166,20 @@ std::unique_ptr<FootnoteEntry> ChapterHtmlSlimParser::createFootnoteEntry(const
if (!foundInline) { if (!foundInline) {
// Normal href, just copy it // Normal href, just copy it
strncpy(entry->href, href, 63); strncpy(currentPageFootnotes[currentPageFootnoteCount].href, href, 63);
entry->href[63] = '\0'; currentPageFootnotes[currentPageFootnoteCount].href[63] = '\0';
} }
} else { } else {
// No anchor, just copy // No anchor, just copy
strncpy(entry->href, href, 63); strncpy(currentPageFootnotes[currentPageFootnoteCount].href, href, 63);
entry->href[63] = '\0'; currentPageFootnotes[currentPageFootnoteCount].href[63] = '\0';
} }
Serial.printf("[%lu] [ADDFT] Created as: num=%s, href=%s\n", millis(), entry->number, entry->href); currentPageFootnoteCount++;
return entry;
Serial.printf("[%lu] [ADDFT] Stored as: num=%s, href=%s\n", millis(),
currentPageFootnotes[currentPageFootnoteCount - 1].number,
currentPageFootnotes[currentPageFootnoteCount - 1].href);
} }
void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) { void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) {
@ -590,10 +593,7 @@ void XMLCALL ChapterHtmlSlimParser::characterData(void* userData, const XML_Char
Serial.printf("[%lu] [EHP] Text block too long, splitting into multiple pages\n", millis()); Serial.printf("[%lu] [EHP] Text block too long, splitting into multiple pages\n", millis());
self->currentTextBlock->layoutAndExtractLines( self->currentTextBlock->layoutAndExtractLines(
self->renderer, self->fontId, self->viewportWidth, self->renderer, self->fontId, self->viewportWidth,
[self](const std::shared_ptr<TextBlock>& textBlock, const std::vector<FootnoteEntry>& footnotes) { [self](const std::shared_ptr<TextBlock>& textBlock) { self->addLineToPage(textBlock); }, false);
self->addLineToPage(textBlock, footnotes);
},
false);
} }
} }
@ -688,17 +688,18 @@ void XMLCALL ChapterHtmlSlimParser::endElement(void* userData, const XML_Char* n
if (self->currentNoterefTextLen > 0) { if (self->currentNoterefTextLen > 0) {
Serial.printf("[%lu] [NOTEREF] %s -> %s\n", millis(), self->currentNoterefText, self->currentNoterefHref); Serial.printf("[%lu] [NOTEREF] %s -> %s\n", millis(), self->currentNoterefText, self->currentNoterefHref);
// Create the footnote entry (this does the rewriting) // Add footnote first (this does the rewriting)
std::unique_ptr<FootnoteEntry> footnote = self->addFootnoteToCurrentPage(self->currentNoterefText, self->currentNoterefHref);
self->createFootnoteEntry(self->currentNoterefText, self->currentNoterefHref);
// Then call callback with the REWRITTEN href // Then call callback with the REWRITTEN href from currentPageFootnotes
if (self->noterefCallback && footnote) { if (self->noterefCallback && self->currentPageFootnoteCount > 0) {
Noteref noteref; Noteref noteref;
strncpy(noteref.number, self->currentNoterefText, 15); strncpy(noteref.number, self->currentNoterefText, 15);
noteref.number[15] = '\0'; noteref.number[15] = '\0';
strncpy(noteref.href, footnote->href, 127); // Use the STORED href which has been rewritten
FootnoteEntry* lastFootnote = &self->currentPageFootnotes[self->currentPageFootnoteCount - 1];
strncpy(noteref.href, lastFootnote->href, 127);
noteref.href[127] = '\0'; noteref.href[127] = '\0';
self->noterefCallback(noteref); self->noterefCallback(noteref);
@ -711,9 +712,9 @@ void XMLCALL ChapterHtmlSlimParser::endElement(void* userData, const XML_Char* n
char formattedNoteref[32]; char formattedNoteref[32];
snprintf(formattedNoteref, sizeof(formattedNoteref), "[%s]", self->currentNoterefText); snprintf(formattedNoteref, sizeof(formattedNoteref), "[%s]", self->currentNoterefText);
// Add it as a word to the current text block with the footnote attached // Add it as a word to the current text block
if (self->currentTextBlock) { if (self->currentTextBlock) {
self->currentTextBlock->addWord(formattedNoteref, fontStyle, std::move(footnote)); self->currentTextBlock->addWord(formattedNoteref, fontStyle);
} }
} }
@ -845,6 +846,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
partWordBufferIndex = 0; partWordBufferIndex = 0;
insideNoteref = false; insideNoteref = false;
insideAsideFootnote = false; insideAsideFootnote = false;
currentPageFootnoteCount = 0;
isPass1CollectingAsides = false; isPass1CollectingAsides = false;
supDepth = -1; supDepth = -1;
@ -926,6 +928,10 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
makePages(); makePages();
if (currentPage) { if (currentPage) {
for (int i = 0; i < currentPageFootnoteCount; i++) {
currentPage->addFootnote(currentPageFootnotes[i].number, currentPageFootnotes[i].href);
}
currentPageFootnoteCount = 0;
completePageFn(std::move(currentPage)); completePageFn(std::move(currentPage));
} }
@ -936,11 +942,17 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
return true; return true;
} }
void ChapterHtmlSlimParser::addLineToPage(std::shared_ptr<TextBlock> line, void ChapterHtmlSlimParser::addLineToPage(std::shared_ptr<TextBlock> line) {
const std::vector<FootnoteEntry>& footnotes) {
const int lineHeight = renderer.getLineHeight(fontId) * lineCompression; const int lineHeight = renderer.getLineHeight(fontId) * lineCompression;
if (currentPageNextY + lineHeight > viewportHeight) { if (currentPageNextY + lineHeight > viewportHeight) {
if (currentPage) {
for (int i = 0; i < currentPageFootnoteCount; i++) {
currentPage->addFootnote(currentPageFootnotes[i].number, currentPageFootnotes[i].href);
}
currentPageFootnoteCount = 0;
}
completePageFn(std::move(currentPage)); completePageFn(std::move(currentPage));
currentPage.reset(new Page()); currentPage.reset(new Page());
currentPageNextY = 0; currentPageNextY = 0;
@ -949,11 +961,6 @@ void ChapterHtmlSlimParser::addLineToPage(std::shared_ptr<TextBlock> line,
if (currentPage && currentPage->elements.size() < 24) { // Assuming generic capacity check or vector size if (currentPage && currentPage->elements.size() < 24) { // Assuming generic capacity check or vector size
currentPage->elements.push_back(std::make_shared<PageLine>(line, 0, currentPageNextY)); currentPage->elements.push_back(std::make_shared<PageLine>(line, 0, currentPageNextY));
currentPageNextY += lineHeight; currentPageNextY += lineHeight;
// Add footnotes for this line to the current page
for (const auto& fn : footnotes) {
currentPage->addFootnote(fn.number, fn.href);
}
} else if (currentPage) { } else if (currentPage) {
Serial.printf("[%lu] [EHP] WARNING: Page element capacity reached, skipping element\n", millis()); Serial.printf("[%lu] [EHP] WARNING: Page element capacity reached, skipping element\n", millis());
} }
@ -973,9 +980,7 @@ void ChapterHtmlSlimParser::makePages() {
const int lineHeight = renderer.getLineHeight(fontId) * lineCompression; const int lineHeight = renderer.getLineHeight(fontId) * lineCompression;
currentTextBlock->layoutAndExtractLines( currentTextBlock->layoutAndExtractLines(
renderer, fontId, viewportWidth, renderer, fontId, viewportWidth,
[this](const std::shared_ptr<TextBlock>& textBlock, const std::vector<FootnoteEntry>& footnotes) { [this](const std::shared_ptr<TextBlock>& textBlock) { addLineToPage(textBlock); });
addLineToPage(textBlock, footnotes);
});
// Extra paragraph spacing if enabled // Extra paragraph spacing if enabled
if (extraParagraphSpacing) { if (extraParagraphSpacing) {
currentPageNextY += lineHeight / 2; currentPageNextY += lineHeight / 2;

View File

@ -77,6 +77,10 @@ class ChapterHtmlSlimParser {
int currentNoterefHrefLen = 0; int currentNoterefHrefLen = 0;
std::function<void(Noteref&)> noterefCallback = nullptr; std::function<void(Noteref&)> noterefCallback = nullptr;
// Footnote tracking for current page
FootnoteEntry currentPageFootnotes[16];
int currentPageFootnoteCount = 0;
// Inline footnotes (aside) tracking // Inline footnotes (aside) tracking
bool insideAsideFootnote = false; bool insideAsideFootnote = false;
int asideDepth = 0; int asideDepth = 0;
@ -102,7 +106,7 @@ class ChapterHtmlSlimParser {
int supDepth = -1; int supDepth = -1;
int anchorDepth = -1; int anchorDepth = -1;
std::unique_ptr<FootnoteEntry> createFootnoteEntry(const char* number, const char* href); void addFootnoteToCurrentPage(const char* number, const char* href);
void startNewTextBlock(TextBlock::Style style); void startNewTextBlock(TextBlock::Style style);
EpdFontFamily::Style getCurrentFontStyle() const; EpdFontFamily::Style getCurrentFontStyle() const;
void flushPartWordBuffer(); void flushPartWordBuffer();
@ -157,7 +161,7 @@ class ChapterHtmlSlimParser {
} }
bool parseAndBuildPages(); bool parseAndBuildPages();
void addLineToPage(std::shared_ptr<TextBlock> line, const std::vector<FootnoteEntry>& footnotes); void addLineToPage(std::shared_ptr<TextBlock> line);
void setNoterefCallback(const std::function<void(Noteref&)>& callback) { noterefCallback = callback; } void setNoterefCallback(const std::function<void(Noteref&)>& callback) { noterefCallback = callback; }
}; };

View File

@ -112,17 +112,20 @@ void EpubReaderTocActivity::loopChapters() {
const int totalItems = getChaptersTotalItems(); const int totalItems = getChaptersTotalItems();
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
if (hasSyncOption() && (chaptersSelectorIndex == 0 || chaptersSelectorIndex == totalItems - 1)) { if (isSyncItem(chaptersSelectorIndex)) {
launchSyncActivity(); launchSyncActivity();
return; return;
} }
int filteredIndex = chaptersSelectorIndex; int filteredIndex = chaptersSelectorIndex;
if (hasSyncOption()) filteredIndex -= 1;
if (hasSyncOption() && chaptersSelectorIndex > 0) filteredIndex -= 1;
if (filteredIndex >= 0 && filteredIndex < static_cast<int>(filteredSpineIndices.size())) { if (filteredIndex >= 0 && filteredIndex < static_cast<int>(filteredSpineIndices.size())) {
onSelectSpineIndex(filteredSpineIndices[filteredIndex]); onSelectSpineIndex(filteredSpineIndices[filteredIndex]);
} }
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
onGoBack();
} else if (upReleased) { } else if (upReleased) {
if (totalItems > 0) { if (totalItems > 0) {
if (skipPage) { if (skipPage) {