#include "ParsedText.h" #include #include #include #include #include #include constexpr int MAX_COST = std::numeric_limits::max(); void ParsedText::addWord(std::string word, const EpdFontStyle fontStyle) { if (word.empty()) return; words.push_back(std::move(word)); wordStyles.push_back(fontStyle); } // Consumes data to minimize memory usage void ParsedText::layoutAndExtractLines(const GfxRenderer& renderer, const int fontId, const int horizontalMargin, const std::function)>& processLine, const bool includeLastLine) { if (words.empty()) { return; } const int pageWidth = renderer.getScreenWidth() - horizontalMargin; const int spaceWidth = renderer.getSpaceWidth(fontId); const auto wordWidths = calculateWordWidths(renderer, fontId); const auto lineBreakIndices = computeLineBreaks(pageWidth, spaceWidth, wordWidths); const size_t lineCount = includeLastLine ? lineBreakIndices.size() : lineBreakIndices.size() - 1; for (size_t i = 0; i < lineCount; ++i) { extractLine(i, pageWidth, spaceWidth, wordWidths, lineBreakIndices, processLine); } } std::vector ParsedText::calculateWordWidths(const GfxRenderer& renderer, const int fontId) { const size_t totalWordCount = words.size(); std::vector wordWidths; wordWidths.reserve(totalWordCount); // add em-space at the beginning of first word in paragraph to indent if (!extraParagraphSpacing) { std::string& first_word = words.front(); first_word.insert(0, "\xe2\x80\x83"); } auto wordsIt = words.begin(); auto wordStylesIt = wordStyles.begin(); while (wordsIt != words.end()) { wordWidths.push_back(renderer.getTextWidth(fontId, wordsIt->c_str(), *wordStylesIt)); std::advance(wordsIt, 1); std::advance(wordStylesIt, 1); } return wordWidths; } std::vector ParsedText::computeLineBreaks(const int pageWidth, const int spaceWidth, const std::vector& wordWidths) const { const size_t totalWordCount = words.size(); // DP table to store the minimum badness (cost) of lines starting at index i std::vector dp(totalWordCount); // 'ans[i]' stores the index 'j' of the *last word* in the optimal line starting at 'i' std::vector ans(totalWordCount); // Base Case dp[totalWordCount - 1] = 0; ans[totalWordCount - 1] = totalWordCount - 1; for (int i = totalWordCount - 2; i >= 0; --i) { int currlen = -spaceWidth; dp[i] = MAX_COST; for (size_t j = i; j < totalWordCount; ++j) { // Current line length: previous width + space + current word width currlen += wordWidths[j] + spaceWidth; if (currlen > pageWidth) { break; } int cost; if (j == totalWordCount - 1) { cost = 0; // Last line } else { const int remainingSpace = pageWidth - currlen; // Use long long for the square to prevent overflow const long long cost_ll = static_cast(remainingSpace) * remainingSpace + dp[j + 1]; if (cost_ll > MAX_COST) { cost = MAX_COST; } else { cost = static_cast(cost_ll); } } if (cost < dp[i]) { dp[i] = cost; ans[i] = j; // j is the index of the last word in this optimal line } } } // Stores the index of the word that starts the next line (last_word_index + 1) std::vector lineBreakIndices; size_t currentWordIndex = 0; constexpr size_t MAX_LINES = 1000; while (currentWordIndex < totalWordCount) { if (lineBreakIndices.size() >= MAX_LINES) { break; } size_t nextBreakIndex = ans[currentWordIndex] + 1; lineBreakIndices.push_back(nextBreakIndex); currentWordIndex = nextBreakIndex; } return lineBreakIndices; } void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const int spaceWidth, const std::vector& wordWidths, const std::vector& lineBreakIndices, const std::function)>& processLine) { const size_t lineBreak = lineBreakIndices[breakIndex]; const size_t lastBreakAt = breakIndex > 0 ? lineBreakIndices[breakIndex - 1] : 0; const size_t lineWordCount = lineBreak - lastBreakAt; // Calculate total word width for this line int lineWordWidthSum = 0; for (size_t i = lastBreakAt; i < lineBreak; i++) { lineWordWidthSum += wordWidths[i]; } // Calculate spacing const int spareSpace = pageWidth - lineWordWidthSum; int spacing = spaceWidth; const bool isLastLine = breakIndex == lineBreakIndices.size() - 1; if (style == TextBlock::JUSTIFIED && !isLastLine && lineWordCount >= 2) { spacing = spareSpace / (lineWordCount - 1); } // Calculate initial x position uint16_t xpos = 0; if (style == TextBlock::RIGHT_ALIGN) { xpos = spareSpace - (lineWordCount - 1) * spaceWidth; } else if (style == TextBlock::CENTER_ALIGN) { xpos = (spareSpace - (lineWordCount - 1) * spaceWidth) / 2; } // Pre-calculate X positions for words std::list lineXPos; for (size_t i = lastBreakAt; i < lineBreak; i++) { const uint16_t currentWordWidth = wordWidths[i]; lineXPos.push_back(xpos); xpos += currentWordWidth + spacing; } // Iterators always start at the beginning as we are moving content with splice below auto wordEndIt = words.begin(); auto wordStyleEndIt = wordStyles.begin(); std::advance(wordEndIt, lineWordCount); std::advance(wordStyleEndIt, 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); processLine(std::make_shared(std::move(lineWords), std::move(lineXPos), std::move(lineWordStyles), style)); }