From 9ab69fb1ff261fe3b9498bd51851dbb78d04d5d8 Mon Sep 17 00:00:00 2001 From: Eunchurn Park Date: Wed, 14 Jan 2026 02:40:13 +0900 Subject: [PATCH] fix(TxtReader): Address PR review feedback - Fix progress update logic: Use lastProgress / 10 != progress / 10 instead of progressPercent % 2 == 0 to ensure progress updates even when percentage jumps odd numbers (matching EpubReaderActivity) - Fix BW buffer store/restore: Only perform buffer operations when textAntiAliasing is enabled (grayscale passes are being rendered) - Add power button page turn support: Respect the new shortPwrBtn PAGE_TURN setting for next page navigation - Fix word wrap byte tracking: Properly track consumed bytes during word wrapping to prevent repeated sections and ensure file end is reachable. The previous implementation had incorrect byte offset calculations when lines were split across pages. --- src/activities/reader/TxtReaderActivity.cpp | 75 +++++++++++++-------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/src/activities/reader/TxtReaderActivity.cpp b/src/activities/reader/TxtReaderActivity.cpp index 95524e0c..db725320 100644 --- a/src/activities/reader/TxtReaderActivity.cpp +++ b/src/activities/reader/TxtReaderActivity.cpp @@ -110,6 +110,8 @@ void TxtReaderActivity::loop() { const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::PageBack) || mappedInput.wasReleased(MappedInputManager::Button::Left); const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::PageForward) || + (SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::PAGE_TURN && + mappedInput.wasReleased(MappedInputManager::Button::Power)) || mappedInput.wasReleased(MappedInputManager::Button::Right); if (!prevReleased && !nextReleased) { @@ -227,9 +229,9 @@ void TxtReaderActivity::buildPageIndex() { pageOffsets.push_back(offset); } - // Update progress bar every 2% + // Update progress bar every 10% (matching EpubReaderActivity logic) int progressPercent = (offset * 100) / fileSize; - if (progressPercent != lastProgressPercent && progressPercent % 2 == 0) { + if (lastProgressPercent / 10 != progressPercent / 10) { lastProgressPercent = progressPercent; // Fill progress bar @@ -272,7 +274,6 @@ bool TxtReaderActivity::loadPageAtOffset(size_t offset, std::vector // Parse lines from buffer size_t pos = 0; - size_t bytesConsumed = 0; while (pos < chunkSize && static_cast(outLines.size()) < linesPerPage) { // Find end of line @@ -289,13 +290,18 @@ bool TxtReaderActivity::loadPageAtOffset(size_t offset, std::vector break; } - // Extract line (without newline) - std::string line(reinterpret_cast(buffer + pos), lineEnd - pos); + // Calculate the actual length of line content in the buffer (excluding newline) + size_t lineContentLen = lineEnd - pos; - // Remove carriage return if present - if (!line.empty() && line.back() == '\r') { - line.pop_back(); - } + // Check for carriage return + bool hasCR = (lineContentLen > 0 && buffer[pos + lineContentLen - 1] == '\r'); + size_t displayLen = hasCR ? lineContentLen - 1 : lineContentLen; + + // Extract line content for display (without CR/LF) + std::string line(reinterpret_cast(buffer + pos), displayLen); + + // Track position within this source line (in bytes from pos) + size_t lineBytePos = 0; // Word wrap if needed while (!line.empty() && static_cast(outLines.size()) < linesPerPage) { @@ -303,6 +309,8 @@ bool TxtReaderActivity::loadPageAtOffset(size_t offset, std::vector if (lineWidth <= viewportWidth) { outLines.push_back(line); + lineBytePos = displayLen; // Consumed entire display content + line.clear(); break; } @@ -330,30 +338,39 @@ bool TxtReaderActivity::loadPageAtOffset(size_t offset, std::vector outLines.push_back(line.substr(0, breakPos)); // Skip space at break point + size_t skipChars = breakPos; if (breakPos < line.length() && line[breakPos] == ' ') { - breakPos++; + skipChars++; } - line = line.substr(breakPos); + lineBytePos += skipChars; + line = line.substr(skipChars); } - // If we still have remaining wrapped text but no room, don't consume this source line - if (!line.empty() && static_cast(outLines.size()) >= linesPerPage) { + // Determine how much of the source buffer we consumed + if (line.empty()) { + // Fully consumed this source line, move past the newline + pos = lineEnd + 1; + } else { + // Partially consumed - page is full mid-line + // Move pos to where we stopped in the line (NOT past the line) + pos = pos + lineBytePos; break; } - - // Move past the newline - bytesConsumed = lineEnd + 1; - pos = lineEnd + 1; } - // Handle case where we filled the page mid-line (word wrap) - if (bytesConsumed == 0 && !outLines.empty()) { - // We processed some wrapped content, estimate bytes consumed - // This is approximate - we need to track actual byte positions - bytesConsumed = pos; + // Ensure we make progress even if calculations go wrong + if (pos == 0 && !outLines.empty()) { + // Fallback: at minimum, consume something to avoid infinite loop + pos = 1; + } + + nextOffset = offset + pos; + + // Make sure we don't go past the file + if (nextOffset > fileSize) { + nextOffset = fileSize; } - nextOffset = offset + (bytesConsumed > 0 ? bytesConsumed : chunkSize); free(buffer); return !outLines.empty(); @@ -455,11 +472,11 @@ void TxtReaderActivity::renderPage() { pagesUntilFullRefresh--; } - // Save BW buffer for restoration after grayscale pass - renderer.storeBwBuffer(); - // Grayscale rendering pass (for anti-aliased fonts) if (SETTINGS.textAntiAliasing) { + // Save BW buffer for restoration after grayscale pass + renderer.storeBwBuffer(); + renderer.clearScreen(0x00); renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB); renderLines(); @@ -472,10 +489,10 @@ void TxtReaderActivity::renderPage() { renderer.displayGrayBuffer(); renderer.setRenderMode(GfxRenderer::BW); - } - // Restore BW buffer - renderer.restoreBwBuffer(); + // Restore BW buffer + renderer.restoreBwBuffer(); + } } void TxtReaderActivity::renderStatusBar(const int orientedMarginRight, const int orientedMarginBottom,