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.
This commit is contained in:
Eunchurn Park 2026-01-14 02:40:13 +09:00
parent 1d9d8b6a7f
commit 9ab69fb1ff
No known key found for this signature in database
GPG Key ID: 29D94D9C697E3F92

View File

@ -110,6 +110,8 @@ void TxtReaderActivity::loop() {
const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::PageBack) || const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::PageBack) ||
mappedInput.wasReleased(MappedInputManager::Button::Left); mappedInput.wasReleased(MappedInputManager::Button::Left);
const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::PageForward) || 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); mappedInput.wasReleased(MappedInputManager::Button::Right);
if (!prevReleased && !nextReleased) { if (!prevReleased && !nextReleased) {
@ -227,9 +229,9 @@ void TxtReaderActivity::buildPageIndex() {
pageOffsets.push_back(offset); pageOffsets.push_back(offset);
} }
// Update progress bar every 2% // Update progress bar every 10% (matching EpubReaderActivity logic)
int progressPercent = (offset * 100) / fileSize; int progressPercent = (offset * 100) / fileSize;
if (progressPercent != lastProgressPercent && progressPercent % 2 == 0) { if (lastProgressPercent / 10 != progressPercent / 10) {
lastProgressPercent = progressPercent; lastProgressPercent = progressPercent;
// Fill progress bar // Fill progress bar
@ -272,7 +274,6 @@ bool TxtReaderActivity::loadPageAtOffset(size_t offset, std::vector<std::string>
// Parse lines from buffer // Parse lines from buffer
size_t pos = 0; size_t pos = 0;
size_t bytesConsumed = 0;
while (pos < chunkSize && static_cast<int>(outLines.size()) < linesPerPage) { while (pos < chunkSize && static_cast<int>(outLines.size()) < linesPerPage) {
// Find end of line // Find end of line
@ -289,13 +290,18 @@ bool TxtReaderActivity::loadPageAtOffset(size_t offset, std::vector<std::string>
break; break;
} }
// Extract line (without newline) // Calculate the actual length of line content in the buffer (excluding newline)
std::string line(reinterpret_cast<char*>(buffer + pos), lineEnd - pos); size_t lineContentLen = lineEnd - pos;
// Remove carriage return if present // Check for carriage return
if (!line.empty() && line.back() == '\r') { bool hasCR = (lineContentLen > 0 && buffer[pos + lineContentLen - 1] == '\r');
line.pop_back(); size_t displayLen = hasCR ? lineContentLen - 1 : lineContentLen;
}
// Extract line content for display (without CR/LF)
std::string line(reinterpret_cast<char*>(buffer + pos), displayLen);
// Track position within this source line (in bytes from pos)
size_t lineBytePos = 0;
// Word wrap if needed // Word wrap if needed
while (!line.empty() && static_cast<int>(outLines.size()) < linesPerPage) { while (!line.empty() && static_cast<int>(outLines.size()) < linesPerPage) {
@ -303,6 +309,8 @@ bool TxtReaderActivity::loadPageAtOffset(size_t offset, std::vector<std::string>
if (lineWidth <= viewportWidth) { if (lineWidth <= viewportWidth) {
outLines.push_back(line); outLines.push_back(line);
lineBytePos = displayLen; // Consumed entire display content
line.clear();
break; break;
} }
@ -330,30 +338,39 @@ bool TxtReaderActivity::loadPageAtOffset(size_t offset, std::vector<std::string>
outLines.push_back(line.substr(0, breakPos)); outLines.push_back(line.substr(0, breakPos));
// Skip space at break point // Skip space at break point
size_t skipChars = breakPos;
if (breakPos < line.length() && line[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 // Determine how much of the source buffer we consumed
if (!line.empty() && static_cast<int>(outLines.size()) >= linesPerPage) { 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; break;
} }
// Move past the newline
bytesConsumed = lineEnd + 1;
pos = lineEnd + 1;
} }
// Handle case where we filled the page mid-line (word wrap) // Ensure we make progress even if calculations go wrong
if (bytesConsumed == 0 && !outLines.empty()) { if (pos == 0 && !outLines.empty()) {
// We processed some wrapped content, estimate bytes consumed // Fallback: at minimum, consume something to avoid infinite loop
// This is approximate - we need to track actual byte positions pos = 1;
bytesConsumed = pos; }
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); free(buffer);
return !outLines.empty(); return !outLines.empty();
@ -455,11 +472,11 @@ void TxtReaderActivity::renderPage() {
pagesUntilFullRefresh--; pagesUntilFullRefresh--;
} }
// Grayscale rendering pass (for anti-aliased fonts)
if (SETTINGS.textAntiAliasing) {
// Save BW buffer for restoration after grayscale pass // Save BW buffer for restoration after grayscale pass
renderer.storeBwBuffer(); renderer.storeBwBuffer();
// Grayscale rendering pass (for anti-aliased fonts)
if (SETTINGS.textAntiAliasing) {
renderer.clearScreen(0x00); renderer.clearScreen(0x00);
renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB); renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB);
renderLines(); renderLines();
@ -472,11 +489,11 @@ void TxtReaderActivity::renderPage() {
renderer.displayGrayBuffer(); renderer.displayGrayBuffer();
renderer.setRenderMode(GfxRenderer::BW); renderer.setRenderMode(GfxRenderer::BW);
}
// Restore BW buffer // Restore BW buffer
renderer.restoreBwBuffer(); renderer.restoreBwBuffer();
} }
}
void TxtReaderActivity::renderStatusBar(const int orientedMarginRight, const int orientedMarginBottom, void TxtReaderActivity::renderStatusBar(const int orientedMarginRight, const int orientedMarginBottom,
const int orientedMarginLeft) const { const int orientedMarginLeft) const {