mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-04 14:47:37 +03:00
Compare commits
16 Commits
f9a4df695b
...
7a1601c77d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a1601c77d | ||
|
|
ce9ae77f79 | ||
|
|
c51d617368 | ||
|
|
0e46822a8b | ||
|
|
8b8cca2d9b | ||
|
|
9dc57baf19 | ||
|
|
6ff464ccb1 | ||
|
|
824607d3fd | ||
|
|
ff49d252ae | ||
|
|
aa5875244d | ||
|
|
1931cc6178 | ||
|
|
b64f2a941d | ||
|
|
11d30e7ca8 | ||
|
|
5fa58a002c | ||
|
|
e1849983b8 | ||
|
|
2df7a3124f |
@ -49,17 +49,25 @@ uint16_t measureWordWidth(const GfxRenderer& renderer, const int fontId, const s
|
||||
|
||||
} // 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;
|
||||
|
||||
words.push_back(std::move(word));
|
||||
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
|
||||
void ParsedText::layoutAndExtractLines(const GfxRenderer& renderer, const int fontId, const uint16_t viewportWidth,
|
||||
const std::function<void(std::shared_ptr<TextBlock>)>& processLine,
|
||||
const bool includeLastLine) {
|
||||
void ParsedText::layoutAndExtractLines(
|
||||
const GfxRenderer& renderer, const int fontId, const uint16_t viewportWidth,
|
||||
const std::function<void(std::shared_ptr<TextBlock>, const std::vector<FootnoteEntry>&)>& processLine,
|
||||
const bool includeLastLine) {
|
||||
if (words.empty()) {
|
||||
return;
|
||||
}
|
||||
@ -255,8 +263,8 @@ std::vector<size_t> ParsedText::computeHyphenatedLineBreaks(const GfxRenderer& r
|
||||
return lineBreakIndices;
|
||||
}
|
||||
|
||||
// Splits words[wordIndex] into prefix (adding a hyphen only when needed) and remainder when a legal breakpoint fits the
|
||||
// available width.
|
||||
// Splits words[wordIndex] into prefix (adding a hyphen only when needed)
|
||||
// and remainder when a legal breakpoint fits the available width.
|
||||
bool ParsedText::hyphenateWordAtIndex(const size_t wordIndex, const int availableWidth, const GfxRenderer& renderer,
|
||||
const int fontId, std::vector<uint16_t>& wordWidths,
|
||||
const bool allowFallbackBreaks) {
|
||||
@ -320,6 +328,13 @@ bool ParsedText::hyphenateWordAtIndex(const size_t wordIndex, const int availabl
|
||||
words.insert(insertWordIt, remainder);
|
||||
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.
|
||||
wordWidths[wordIndex] = static_cast<uint16_t>(chosenWidth);
|
||||
const uint16_t remainderWidth = measureWordWidth(renderer, fontId, remainder, style);
|
||||
@ -327,9 +342,10 @@ bool ParsedText::hyphenateWordAtIndex(const size_t wordIndex, const int availabl
|
||||
return true;
|
||||
}
|
||||
|
||||
void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const int spaceWidth,
|
||||
const std::vector<uint16_t>& wordWidths, const std::vector<size_t>& lineBreakIndices,
|
||||
const std::function<void(std::shared_ptr<TextBlock>)>& processLine) {
|
||||
void ParsedText::extractLine(
|
||||
const size_t breakIndex, const int pageWidth, const int spaceWidth, const std::vector<uint16_t>& wordWidths,
|
||||
const std::vector<size_t>& lineBreakIndices,
|
||||
const std::function<void(std::shared_ptr<TextBlock>, const std::vector<FootnoteEntry>&)>& processLine) {
|
||||
const size_t lineBreak = lineBreakIndices[breakIndex];
|
||||
const size_t lastBreakAt = breakIndex > 0 ? lineBreakIndices[breakIndex - 1] : 0;
|
||||
const size_t lineWordCount = lineBreak - lastBreakAt;
|
||||
@ -372,17 +388,35 @@ void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const
|
||||
std::advance(wordEndIt, lineWordCount);
|
||||
std::advance(wordStyleEndIt, lineWordCount);
|
||||
|
||||
// *** CRITICAL STEP: CONSUME DATA USING SPLICE ***
|
||||
std::list<std::string> lineWords;
|
||||
lineWords.splice(lineWords.begin(), words, words.begin(), wordEndIt);
|
||||
std::list<EpdFontFamily::Style> lineWordStyles;
|
||||
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) {
|
||||
if (containsSoftHyphen(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);
|
||||
}
|
||||
@ -2,12 +2,14 @@
|
||||
|
||||
#include <EpdFontFamily.h>
|
||||
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "FootnoteEntry.h"
|
||||
#include "blocks/TextBlock.h"
|
||||
|
||||
class GfxRenderer;
|
||||
@ -15,6 +17,8 @@ class GfxRenderer;
|
||||
class ParsedText {
|
||||
std::list<std::string> words;
|
||||
std::list<EpdFontFamily::Style> wordStyles;
|
||||
std::deque<uint8_t> wordHasFootnote;
|
||||
std::deque<FootnoteEntry> footnoteQueue;
|
||||
TextBlock::Style style;
|
||||
bool extraParagraphSpacing;
|
||||
bool hyphenationEnabled;
|
||||
@ -26,9 +30,10 @@ class ParsedText {
|
||||
int spaceWidth, std::vector<uint16_t>& wordWidths);
|
||||
bool hyphenateWordAtIndex(size_t wordIndex, int availableWidth, const GfxRenderer& renderer, int fontId,
|
||||
std::vector<uint16_t>& wordWidths, bool allowFallbackBreaks);
|
||||
void extractLine(size_t breakIndex, int pageWidth, int spaceWidth, const std::vector<uint16_t>& wordWidths,
|
||||
const std::vector<size_t>& lineBreakIndices,
|
||||
const std::function<void(std::shared_ptr<TextBlock>)>& processLine);
|
||||
void extractLine(
|
||||
size_t breakIndex, int pageWidth, int spaceWidth, const std::vector<uint16_t>& wordWidths,
|
||||
const std::vector<size_t>& lineBreakIndices,
|
||||
const std::function<void(std::shared_ptr<TextBlock>, const std::vector<FootnoteEntry>&)>& processLine);
|
||||
std::vector<uint16_t> calculateWordWidths(const GfxRenderer& renderer, int fontId);
|
||||
|
||||
public:
|
||||
@ -37,12 +42,13 @@ class ParsedText {
|
||||
: style(style), extraParagraphSpacing(extraParagraphSpacing), hyphenationEnabled(hyphenationEnabled) {}
|
||||
~ParsedText() = default;
|
||||
|
||||
void addWord(std::string word, EpdFontFamily::Style fontStyle);
|
||||
void addWord(std::string word, EpdFontFamily::Style fontStyle, std::unique_ptr<FootnoteEntry> footnote = nullptr);
|
||||
void setStyle(const TextBlock::Style style) { this->style = style; }
|
||||
TextBlock::Style getStyle() const { return style; }
|
||||
size_t size() const { return words.size(); }
|
||||
bool isEmpty() const { return words.empty(); }
|
||||
void layoutAndExtractLines(const GfxRenderer& renderer, int fontId, uint16_t viewportWidth,
|
||||
const std::function<void(std::shared_ptr<TextBlock>)>& processLine,
|
||||
bool includeLastLine = true);
|
||||
void layoutAndExtractLines(
|
||||
const GfxRenderer& renderer, int fontId, uint16_t viewportWidth,
|
||||
const std::function<void(std::shared_ptr<TextBlock>, const std::vector<FootnoteEntry>&)>& processLine,
|
||||
bool includeLastLine = true);
|
||||
};
|
||||
@ -116,14 +116,14 @@ void ChapterHtmlSlimParser::startNewTextBlock(const TextBlock::Style style) {
|
||||
currentTextBlock.reset(new ParsedText(style, extraParagraphSpacing, hyphenationEnabled));
|
||||
}
|
||||
|
||||
void ChapterHtmlSlimParser::addFootnoteToCurrentPage(const char* number, const char* href) {
|
||||
if (currentPageFootnoteCount >= 16) return;
|
||||
std::unique_ptr<FootnoteEntry> ChapterHtmlSlimParser::createFootnoteEntry(const char* number, const char* href) {
|
||||
auto entry = std::unique_ptr<FootnoteEntry>(new FootnoteEntry());
|
||||
|
||||
Serial.printf("[%lu] [ADDFT] Adding footnote: num=%s, href=%s\n", millis(), number, href);
|
||||
Serial.printf("[%lu] [ADDFT] Creating footnote: num=%s, href=%s\n", millis(), number, href);
|
||||
|
||||
// Copy number
|
||||
strncpy(currentPageFootnotes[currentPageFootnoteCount].number, number, 2);
|
||||
currentPageFootnotes[currentPageFootnoteCount].number[2] = '\0';
|
||||
strncpy(entry->number, number, 2);
|
||||
entry->number[2] = '\0';
|
||||
|
||||
// Check if this is an inline footnote reference
|
||||
const char* hashPos = strchr(href, '#');
|
||||
@ -138,8 +138,8 @@ void ChapterHtmlSlimParser::addFootnoteToCurrentPage(const char* number, const c
|
||||
char rewrittenHref[64];
|
||||
snprintf(rewrittenHref, sizeof(rewrittenHref), "inline_%s.html#%s", inlineId, inlineId);
|
||||
|
||||
strncpy(currentPageFootnotes[currentPageFootnoteCount].href, rewrittenHref, 63);
|
||||
currentPageFootnotes[currentPageFootnoteCount].href[63] = '\0';
|
||||
strncpy(entry->href, rewrittenHref, 63);
|
||||
entry->href[63] = '\0';
|
||||
|
||||
Serial.printf("[%lu] [ADDFT] Rewrote inline href to: %s\n", millis(), rewrittenHref);
|
||||
foundInline = true;
|
||||
@ -154,8 +154,8 @@ void ChapterHtmlSlimParser::addFootnoteToCurrentPage(const char* number, const c
|
||||
char rewrittenHref[64];
|
||||
snprintf(rewrittenHref, sizeof(rewrittenHref), "pnote_%s.html#%s", inlineId, inlineId);
|
||||
|
||||
strncpy(currentPageFootnotes[currentPageFootnoteCount].href, rewrittenHref, 63);
|
||||
currentPageFootnotes[currentPageFootnoteCount].href[63] = '\0';
|
||||
strncpy(entry->href, rewrittenHref, 63);
|
||||
entry->href[63] = '\0';
|
||||
|
||||
Serial.printf("[%lu] [ADDFT] Rewrote paragraph note href to: %s\n", millis(), rewrittenHref);
|
||||
foundInline = true;
|
||||
@ -166,20 +166,17 @@ void ChapterHtmlSlimParser::addFootnoteToCurrentPage(const char* number, const c
|
||||
|
||||
if (!foundInline) {
|
||||
// Normal href, just copy it
|
||||
strncpy(currentPageFootnotes[currentPageFootnoteCount].href, href, 63);
|
||||
currentPageFootnotes[currentPageFootnoteCount].href[63] = '\0';
|
||||
strncpy(entry->href, href, 63);
|
||||
entry->href[63] = '\0';
|
||||
}
|
||||
} else {
|
||||
// No anchor, just copy
|
||||
strncpy(currentPageFootnotes[currentPageFootnoteCount].href, href, 63);
|
||||
currentPageFootnotes[currentPageFootnoteCount].href[63] = '\0';
|
||||
strncpy(entry->href, href, 63);
|
||||
entry->href[63] = '\0';
|
||||
}
|
||||
|
||||
currentPageFootnoteCount++;
|
||||
|
||||
Serial.printf("[%lu] [ADDFT] Stored as: num=%s, href=%s\n", millis(),
|
||||
currentPageFootnotes[currentPageFootnoteCount - 1].number,
|
||||
currentPageFootnotes[currentPageFootnoteCount - 1].href);
|
||||
Serial.printf("[%lu] [ADDFT] Created as: num=%s, href=%s\n", millis(), entry->number, entry->href);
|
||||
return entry;
|
||||
}
|
||||
|
||||
void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) {
|
||||
@ -593,7 +590,10 @@ void XMLCALL ChapterHtmlSlimParser::characterData(void* userData, const XML_Char
|
||||
Serial.printf("[%lu] [EHP] Text block too long, splitting into multiple pages\n", millis());
|
||||
self->currentTextBlock->layoutAndExtractLines(
|
||||
self->renderer, self->fontId, self->viewportWidth,
|
||||
[self](const std::shared_ptr<TextBlock>& textBlock) { self->addLineToPage(textBlock); }, false);
|
||||
[self](const std::shared_ptr<TextBlock>& textBlock, const std::vector<FootnoteEntry>& footnotes) {
|
||||
self->addLineToPage(textBlock, footnotes);
|
||||
},
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -688,18 +688,17 @@ void XMLCALL ChapterHtmlSlimParser::endElement(void* userData, const XML_Char* n
|
||||
if (self->currentNoterefTextLen > 0) {
|
||||
Serial.printf("[%lu] [NOTEREF] %s -> %s\n", millis(), self->currentNoterefText, self->currentNoterefHref);
|
||||
|
||||
// Add footnote first (this does the rewriting)
|
||||
self->addFootnoteToCurrentPage(self->currentNoterefText, self->currentNoterefHref);
|
||||
// Create the footnote entry (this does the rewriting)
|
||||
std::unique_ptr<FootnoteEntry> footnote =
|
||||
self->createFootnoteEntry(self->currentNoterefText, self->currentNoterefHref);
|
||||
|
||||
// Then call callback with the REWRITTEN href from currentPageFootnotes
|
||||
if (self->noterefCallback && self->currentPageFootnoteCount > 0) {
|
||||
// Then call callback with the REWRITTEN href
|
||||
if (self->noterefCallback && footnote) {
|
||||
Noteref noteref;
|
||||
strncpy(noteref.number, self->currentNoterefText, 15);
|
||||
noteref.number[15] = '\0';
|
||||
|
||||
// Use the STORED href which has been rewritten
|
||||
FootnoteEntry* lastFootnote = &self->currentPageFootnotes[self->currentPageFootnoteCount - 1];
|
||||
strncpy(noteref.href, lastFootnote->href, 127);
|
||||
strncpy(noteref.href, footnote->href, 127);
|
||||
noteref.href[127] = '\0';
|
||||
|
||||
self->noterefCallback(noteref);
|
||||
@ -712,9 +711,9 @@ void XMLCALL ChapterHtmlSlimParser::endElement(void* userData, const XML_Char* n
|
||||
char formattedNoteref[32];
|
||||
snprintf(formattedNoteref, sizeof(formattedNoteref), "[%s]", self->currentNoterefText);
|
||||
|
||||
// Add it as a word to the current text block
|
||||
// Add it as a word to the current text block with the footnote attached
|
||||
if (self->currentTextBlock) {
|
||||
self->currentTextBlock->addWord(formattedNoteref, fontStyle);
|
||||
self->currentTextBlock->addWord(formattedNoteref, fontStyle, std::move(footnote));
|
||||
}
|
||||
}
|
||||
|
||||
@ -846,7 +845,6 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
||||
partWordBufferIndex = 0;
|
||||
insideNoteref = false;
|
||||
insideAsideFootnote = false;
|
||||
currentPageFootnoteCount = 0;
|
||||
isPass1CollectingAsides = false;
|
||||
|
||||
supDepth = -1;
|
||||
@ -928,10 +926,6 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
||||
makePages();
|
||||
|
||||
if (currentPage) {
|
||||
for (int i = 0; i < currentPageFootnoteCount; i++) {
|
||||
currentPage->addFootnote(currentPageFootnotes[i].number, currentPageFootnotes[i].href);
|
||||
}
|
||||
currentPageFootnoteCount = 0;
|
||||
completePageFn(std::move(currentPage));
|
||||
}
|
||||
|
||||
@ -942,17 +936,11 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
||||
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;
|
||||
|
||||
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));
|
||||
currentPage.reset(new Page());
|
||||
currentPageNextY = 0;
|
||||
@ -961,6 +949,11 @@ void ChapterHtmlSlimParser::addLineToPage(std::shared_ptr<TextBlock> line) {
|
||||
if (currentPage && currentPage->elements.size() < 24) { // Assuming generic capacity check or vector size
|
||||
currentPage->elements.push_back(std::make_shared<PageLine>(line, 0, currentPageNextY));
|
||||
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) {
|
||||
Serial.printf("[%lu] [EHP] WARNING: Page element capacity reached, skipping element\n", millis());
|
||||
}
|
||||
@ -980,7 +973,9 @@ void ChapterHtmlSlimParser::makePages() {
|
||||
const int lineHeight = renderer.getLineHeight(fontId) * lineCompression;
|
||||
currentTextBlock->layoutAndExtractLines(
|
||||
renderer, fontId, viewportWidth,
|
||||
[this](const std::shared_ptr<TextBlock>& textBlock) { addLineToPage(textBlock); });
|
||||
[this](const std::shared_ptr<TextBlock>& textBlock, const std::vector<FootnoteEntry>& footnotes) {
|
||||
addLineToPage(textBlock, footnotes);
|
||||
});
|
||||
// Extra paragraph spacing if enabled
|
||||
if (extraParagraphSpacing) {
|
||||
currentPageNextY += lineHeight / 2;
|
||||
|
||||
@ -77,10 +77,6 @@ class ChapterHtmlSlimParser {
|
||||
int currentNoterefHrefLen = 0;
|
||||
std::function<void(Noteref&)> noterefCallback = nullptr;
|
||||
|
||||
// Footnote tracking for current page
|
||||
FootnoteEntry currentPageFootnotes[16];
|
||||
int currentPageFootnoteCount = 0;
|
||||
|
||||
// Inline footnotes (aside) tracking
|
||||
bool insideAsideFootnote = false;
|
||||
int asideDepth = 0;
|
||||
@ -106,7 +102,7 @@ class ChapterHtmlSlimParser {
|
||||
int supDepth = -1;
|
||||
int anchorDepth = -1;
|
||||
|
||||
void addFootnoteToCurrentPage(const char* number, const char* href);
|
||||
std::unique_ptr<FootnoteEntry> createFootnoteEntry(const char* number, const char* href);
|
||||
void startNewTextBlock(TextBlock::Style style);
|
||||
EpdFontFamily::Style getCurrentFontStyle() const;
|
||||
void flushPartWordBuffer();
|
||||
@ -161,7 +157,7 @@ class ChapterHtmlSlimParser {
|
||||
}
|
||||
|
||||
bool parseAndBuildPages();
|
||||
void addLineToPage(std::shared_ptr<TextBlock> line);
|
||||
void addLineToPage(std::shared_ptr<TextBlock> line, const std::vector<FootnoteEntry>& footnotes);
|
||||
|
||||
void setNoterefCallback(const std::function<void(Noteref&)>& callback) { noterefCallback = callback; }
|
||||
};
|
||||
|
||||
@ -112,20 +112,17 @@ void EpubReaderTocActivity::loopChapters() {
|
||||
const int totalItems = getChaptersTotalItems();
|
||||
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||
if (isSyncItem(chaptersSelectorIndex)) {
|
||||
if (hasSyncOption() && (chaptersSelectorIndex == 0 || chaptersSelectorIndex == totalItems - 1)) {
|
||||
launchSyncActivity();
|
||||
return;
|
||||
}
|
||||
|
||||
int filteredIndex = chaptersSelectorIndex;
|
||||
|
||||
if (hasSyncOption() && chaptersSelectorIndex > 0) filteredIndex -= 1;
|
||||
if (hasSyncOption()) filteredIndex -= 1;
|
||||
|
||||
if (filteredIndex >= 0 && filteredIndex < static_cast<int>(filteredSpineIndices.size())) {
|
||||
onSelectSpineIndex(filteredSpineIndices[filteredIndex]);
|
||||
}
|
||||
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||
onGoBack();
|
||||
} else if (upReleased) {
|
||||
if (totalItems > 0) {
|
||||
if (skipPage) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user