diff --git a/lib/Epub/Epub/ParsedText.cpp b/lib/Epub/Epub/ParsedText.cpp index 82ddaecd..22162594 100644 --- a/lib/Epub/Epub/ParsedText.cpp +++ b/lib/Epub/Epub/ParsedText.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include @@ -77,37 +76,26 @@ void ParsedText::layoutAndExtractLines(const GfxRenderer& renderer, const int fo const int spaceWidth = renderer.getSpaceWidth(fontId); auto wordWidths = calculateWordWidths(renderer, fontId); - // Build indexed continues vector from the parallel list for O(1) access during layout - std::vector continuesVec(wordContinues.begin(), wordContinues.end()); - std::vector lineBreakIndices; if (hyphenationEnabled) { // Use greedy layout that can split words mid-loop when a hyphenated prefix fits. - lineBreakIndices = computeHyphenatedLineBreaks(renderer, fontId, pageWidth, spaceWidth, wordWidths, continuesVec); + lineBreakIndices = computeHyphenatedLineBreaks(renderer, fontId, pageWidth, spaceWidth, wordWidths, wordContinues); } else { - lineBreakIndices = computeLineBreaks(renderer, fontId, pageWidth, spaceWidth, wordWidths, continuesVec); + lineBreakIndices = computeLineBreaks(renderer, fontId, pageWidth, spaceWidth, wordWidths, wordContinues); } const size_t lineCount = includeLastLine ? lineBreakIndices.size() : lineBreakIndices.size() - 1; for (size_t i = 0; i < lineCount; ++i) { - extractLine(i, pageWidth, spaceWidth, wordWidths, continuesVec, lineBreakIndices, processLine); + extractLine(i, pageWidth, spaceWidth, wordWidths, wordContinues, lineBreakIndices, processLine); } } std::vector ParsedText::calculateWordWidths(const GfxRenderer& renderer, const int fontId) { - const size_t totalWordCount = words.size(); - std::vector wordWidths; - wordWidths.reserve(totalWordCount); + wordWidths.reserve(words.size()); - auto wordsIt = words.begin(); - auto wordStylesIt = wordStyles.begin(); - - while (wordsIt != words.end()) { - wordWidths.push_back(measureWordWidth(renderer, fontId, *wordsIt, *wordStylesIt)); - - std::advance(wordsIt, 1); - std::advance(wordStylesIt, 1); + for (size_t i = 0; i < words.size(); ++i) { + wordWidths.push_back(measureWordWidth(renderer, fontId, words[i], wordStyles[i])); } return wordWidths; @@ -132,8 +120,7 @@ std::vector ParsedText::computeLineBreaks(const GfxRenderer& renderer, c // First word needs to fit in reduced width if there's an indent const int effectiveWidth = i == 0 ? pageWidth - firstLineIndent : pageWidth; while (wordWidths[i] > effectiveWidth) { - if (!hyphenateWordAtIndex(i, effectiveWidth, renderer, fontId, wordWidths, /*allowFallbackBreaks=*/true, - &continuesVec)) { + if (!hyphenateWordAtIndex(i, effectiveWidth, renderer, fontId, wordWidths, /*allowFallbackBreaks=*/true)) { break; } } @@ -279,8 +266,8 @@ std::vector ParsedText::computeHyphenatedLineBreaks(const GfxRenderer& r const int availableWidth = effectivePageWidth - lineWidth - spacing; const bool allowFallbackBreaks = isFirstWord; // Only for first word on line - if (availableWidth > 0 && hyphenateWordAtIndex(currentIndex, availableWidth, renderer, fontId, wordWidths, - allowFallbackBreaks, &continuesVec)) { + if (availableWidth > 0 && + hyphenateWordAtIndex(currentIndex, availableWidth, renderer, fontId, wordWidths, allowFallbackBreaks)) { // Prefix now fits; append it to this line and move to next line lineWidth += spacing + wordWidths[currentIndex]; ++currentIndex; @@ -312,20 +299,14 @@ std::vector ParsedText::computeHyphenatedLineBreaks(const GfxRenderer& r // available width. bool ParsedText::hyphenateWordAtIndex(const size_t wordIndex, const int availableWidth, const GfxRenderer& renderer, const int fontId, std::vector& wordWidths, - const bool allowFallbackBreaks, std::vector* continuesVec) { + const bool allowFallbackBreaks) { // Guard against invalid indices or zero available width before attempting to split. if (availableWidth <= 0 || wordIndex >= words.size()) { return false; } - // Get iterators to target word and style. - auto wordIt = words.begin(); - auto styleIt = wordStyles.begin(); - std::advance(wordIt, wordIndex); - std::advance(styleIt, wordIndex); - - const std::string& word = *wordIt; - const auto style = *styleIt; + const std::string& word = words[wordIndex]; + const auto style = wordStyles[wordIndex]; // Collect candidate breakpoints (byte offsets and hyphen requirements). auto breakInfos = Hyphenator::breakOffsets(word, allowFallbackBreaks); @@ -362,32 +343,20 @@ bool ParsedText::hyphenateWordAtIndex(const size_t wordIndex, const int availabl // Split the word at the selected breakpoint and append a hyphen if required. std::string remainder = word.substr(chosenOffset); - wordIt->resize(chosenOffset); + words[wordIndex].resize(chosenOffset); if (chosenNeedsHyphen) { - wordIt->push_back('-'); + words[wordIndex].push_back('-'); } // Insert the remainder word (with matching style and continuation flag) directly after the prefix. - auto insertWordIt = std::next(wordIt); - auto insertStyleIt = std::next(styleIt); - words.insert(insertWordIt, remainder); - wordStyles.insert(insertStyleIt, style); + words.insert(words.begin() + wordIndex + 1, remainder); + wordStyles.insert(wordStyles.begin() + wordIndex + 1, style); // The remainder inherits whatever continuation status the original word had with the word after it. - // Find the continues entry for the original word and insert the remainder's entry after it. - auto continuesIt = wordContinues.begin(); - std::advance(continuesIt, wordIndex); - const bool originalContinuedToNext = *continuesIt; + const bool originalContinuedToNext = wordContinues[wordIndex]; // The original word (now prefix) does NOT continue to remainder (hyphen separates them) - *continuesIt = false; - const auto insertContinuesIt = std::next(continuesIt); - wordContinues.insert(insertContinuesIt, originalContinuedToNext); - - // Keep the indexed vector in sync if provided - if (continuesVec) { - (*continuesVec)[wordIndex] = false; - continuesVec->insert(continuesVec->begin() + wordIndex + 1, originalContinuedToNext); - } + wordContinues[wordIndex] = false; + wordContinues.insert(wordContinues.begin() + wordIndex + 1, originalContinuedToNext); // Update cached widths to reflect the new prefix/remainder pairing. wordWidths[wordIndex] = static_cast(chosenWidth); @@ -447,7 +416,8 @@ void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const // Pre-calculate X positions for words // Continuation words attach to the previous word with no space before them - std::list lineXPos; + std::vector lineXPos; + lineXPos.reserve(lineWordCount); for (size_t wordIdx = 0; wordIdx < lineWordCount; wordIdx++) { const uint16_t currentWordWidth = wordWidths[lastBreakAt + wordIdx]; @@ -460,23 +430,10 @@ void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const xpos += currentWordWidth + (nextIsContinuation ? 0 : spacing); } - // Iterators always start at the beginning as we are moving content with splice below - auto wordEndIt = words.begin(); - auto wordStyleEndIt = wordStyles.begin(); - auto wordContinuesEndIt = wordContinues.begin(); - std::advance(wordEndIt, lineWordCount); - std::advance(wordStyleEndIt, lineWordCount); - std::advance(wordContinuesEndIt, lineWordCount); - - // *** CRITICAL STEP: CONSUME DATA USING SPLICE *** - std::list lineWords; - lineWords.splice(lineWords.begin(), words, words.begin(), wordEndIt); - std::list lineWordStyles; - lineWordStyles.splice(lineWordStyles.begin(), wordStyles, wordStyles.begin(), wordStyleEndIt); - - // Consume continues flags (not passed to TextBlock, but must be consumed to stay in sync) - std::list lineContinues; - lineContinues.splice(lineContinues.begin(), wordContinues, wordContinues.begin(), wordContinuesEndIt); + // Build line data by moving from the original vectors using index range + std::vector lineWords(std::make_move_iterator(words.begin() + lastBreakAt), + std::make_move_iterator(words.begin() + lineBreak)); + std::vector lineWordStyles(wordStyles.begin() + lastBreakAt, wordStyles.begin() + lineBreak); for (auto& word : lineWords) { if (containsSoftHyphen(word)) { diff --git a/lib/Epub/Epub/ParsedText.h b/lib/Epub/Epub/ParsedText.h index 8dd040cb..39e6bfe8 100644 --- a/lib/Epub/Epub/ParsedText.h +++ b/lib/Epub/Epub/ParsedText.h @@ -3,7 +3,6 @@ #include #include -#include #include #include #include @@ -14,9 +13,9 @@ class GfxRenderer; class ParsedText { - std::list words; - std::list wordStyles; - std::list wordContinues; // true = word attaches to previous (no space before it) + std::vector words; + std::vector wordStyles; + std::vector wordContinues; // true = word attaches to previous (no space before it) BlockStyle blockStyle; bool extraParagraphSpacing; bool hyphenationEnabled; @@ -28,8 +27,7 @@ class ParsedText { int spaceWidth, std::vector& wordWidths, std::vector& continuesVec); bool hyphenateWordAtIndex(size_t wordIndex, int availableWidth, const GfxRenderer& renderer, int fontId, - std::vector& wordWidths, bool allowFallbackBreaks, - std::vector* continuesVec = nullptr); + std::vector& wordWidths, bool allowFallbackBreaks); void extractLine(size_t breakIndex, int pageWidth, int spaceWidth, const std::vector& wordWidths, const std::vector& continuesVec, const std::vector& lineBreakIndices, const std::function)>& processLine); diff --git a/lib/Epub/Epub/blocks/TextBlock.cpp b/lib/Epub/Epub/blocks/TextBlock.cpp index 3ab25558..e9ed0f7b 100644 --- a/lib/Epub/Epub/blocks/TextBlock.cpp +++ b/lib/Epub/Epub/blocks/TextBlock.cpp @@ -11,16 +11,13 @@ void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int return; } - auto wordIt = words.begin(); - auto wordStylesIt = wordStyles.begin(); - auto wordXposIt = wordXpos.begin(); for (size_t i = 0; i < words.size(); i++) { - const int wordX = *wordXposIt + x; - const EpdFontFamily::Style currentStyle = *wordStylesIt; - renderer.drawText(fontId, wordX, y, wordIt->c_str(), true, currentStyle); + const int wordX = wordXpos[i] + x; + const EpdFontFamily::Style currentStyle = wordStyles[i]; + renderer.drawText(fontId, wordX, y, words[i].c_str(), true, currentStyle); if ((currentStyle & EpdFontFamily::UNDERLINE) != 0) { - const std::string& w = *wordIt; + const std::string& w = words[i]; const int fullWordWidth = renderer.getTextWidth(fontId, w.c_str(), currentStyle); // y is the top of the text line; add ascender to reach baseline, then offset 2px below const int underlineY = y + renderer.getFontAscenderSize(fontId) + 2; @@ -40,10 +37,6 @@ void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int renderer.drawLine(startX, underlineY, startX + underlineWidth, underlineY, true); } - - std::advance(wordIt, 1); - std::advance(wordStylesIt, 1); - std::advance(wordXposIt, 1); } } @@ -79,15 +72,15 @@ bool TextBlock::serialize(FsFile& file) const { std::unique_ptr TextBlock::deserialize(FsFile& file) { uint16_t wc; - std::list words; - std::list wordXpos; - std::list wordStyles; + std::vector words; + std::vector wordXpos; + std::vector wordStyles; BlockStyle blockStyle; // Word count serialization::readPod(file, wc); - // Sanity check: prevent allocation of unreasonably large lists (max 10000 words per block) + // Sanity check: prevent allocation of unreasonably large vectors (max 10000 words per block) if (wc > 10000) { Serial.printf("[%lu] [TXB] Deserialization failed: word count %u exceeds maximum\n", millis(), wc); return nullptr; diff --git a/lib/Epub/Epub/blocks/TextBlock.h b/lib/Epub/Epub/blocks/TextBlock.h index 059d136a..536471a9 100644 --- a/lib/Epub/Epub/blocks/TextBlock.h +++ b/lib/Epub/Epub/blocks/TextBlock.h @@ -2,9 +2,9 @@ #include #include -#include #include #include +#include #include "Block.h" #include "BlockStyle.h" @@ -12,14 +12,14 @@ // Represents a line of text on a page class TextBlock final : public Block { private: - std::list words; - std::list wordXpos; - std::list wordStyles; + std::vector words; + std::vector wordXpos; + std::vector wordStyles; BlockStyle blockStyle; public: - explicit TextBlock(std::list words, std::list word_xpos, - std::list word_styles, const BlockStyle& blockStyle = BlockStyle()) + explicit TextBlock(std::vector words, std::vector word_xpos, + std::vector word_styles, const BlockStyle& blockStyle = BlockStyle()) : words(std::move(words)), wordXpos(std::move(word_xpos)), wordStyles(std::move(word_styles)),