From fa4c63e54b0f07dd460398fc552762b88c923ed3 Mon Sep 17 00:00:00 2001 From: Eunchurn Park Date: Sun, 18 Jan 2026 19:01:36 +0900 Subject: [PATCH] perf: Add binary search for TXT word wrap Use binary search instead of linear search for finding line break positions. This significantly improves TXT file indexing performance, especially with SD card fonts where getTextWidth() calls are slower. --- src/activities/reader/TxtReaderActivity.cpp | 93 ++++++++++++++++----- 1 file changed, 71 insertions(+), 22 deletions(-) diff --git a/src/activities/reader/TxtReaderActivity.cpp b/src/activities/reader/TxtReaderActivity.cpp index db725320..79b3829b 100644 --- a/src/activities/reader/TxtReaderActivity.cpp +++ b/src/activities/reader/TxtReaderActivity.cpp @@ -19,6 +19,70 @@ constexpr size_t CHUNK_SIZE = 8 * 1024; // 8KB chunk for reading // Cache file magic and version constexpr uint32_t CACHE_MAGIC = 0x54585449; // "TXTI" constexpr uint8_t CACHE_VERSION = 2; // Increment when cache format changes + +// Find UTF-8 character boundary at or before pos +size_t findUtf8Boundary(const std::string& str, size_t pos) { + if (pos >= str.length()) return str.length(); + // Move back if we're in the middle of a UTF-8 sequence + while (pos > 0 && (str[pos] & 0xC0) == 0x80) { + pos--; + } + return pos; +} + +// Binary search to find max characters that fit in width +// Returns the position (byte offset) where to break the string +size_t findBreakPosition(GfxRenderer& renderer, int fontId, const std::string& line, int maxWidth) { + if (line.empty()) return 0; + + // First check if the whole line fits + int fullWidth = renderer.getTextWidth(fontId, line.c_str()); + if (fullWidth <= maxWidth) { + return line.length(); + } + + // Binary search for the break point + size_t low = 1; // At minimum 1 character + size_t high = line.length(); + size_t bestFit = 1; + + while (low < high) { + size_t mid = (low + high + 1) / 2; + mid = findUtf8Boundary(line, mid); + + if (mid <= low) { + // Can't make progress, exit + break; + } + + std::string substr = line.substr(0, mid); + int width = renderer.getTextWidth(fontId, substr.c_str()); + + if (width <= maxWidth) { + bestFit = mid; + low = mid; + } else { + high = mid - 1; + if (high > 0) { + high = findUtf8Boundary(line, high); + } + } + } + + // Try to break at word boundary (space) if possible + if (bestFit > 0 && bestFit < line.length()) { + size_t spacePos = line.rfind(' ', bestFit); + if (spacePos != std::string::npos && spacePos > 0) { + // Check if breaking at space still fits + std::string atSpace = line.substr(0, spacePos); + if (renderer.getTextWidth(fontId, atSpace.c_str()) <= maxWidth) { + return spacePos; + } + } + } + + return bestFit > 0 ? bestFit : 1; // At minimum, consume 1 character +} } // namespace void TxtReaderActivity::taskTrampoline(void* param) { @@ -303,36 +367,21 @@ bool TxtReaderActivity::loadPageAtOffset(size_t offset, std::vector // Track position within this source line (in bytes from pos) size_t lineBytePos = 0; - // Word wrap if needed + // Word wrap if needed - use binary search for performance with SD fonts while (!line.empty() && static_cast(outLines.size()) < linesPerPage) { - int lineWidth = renderer.getTextWidth(cachedFontId, line.c_str()); + // Use binary search to find break position (much faster than linear search) + size_t breakPos = findBreakPosition(renderer, cachedFontId, line, viewportWidth); - if (lineWidth <= viewportWidth) { + if (breakPos >= line.length()) { + // Whole line fits outLines.push_back(line); - lineBytePos = displayLen; // Consumed entire display content + lineBytePos = displayLen; line.clear(); break; } - // Find break point - size_t breakPos = line.length(); - while (breakPos > 0 && renderer.getTextWidth(cachedFontId, line.substr(0, breakPos).c_str()) > viewportWidth) { - // Try to break at space - size_t spacePos = line.rfind(' ', breakPos - 1); - if (spacePos != std::string::npos && spacePos > 0) { - breakPos = spacePos; - } else { - // Break at character boundary for UTF-8 - breakPos--; - // Make sure we don't break in the middle of a UTF-8 sequence - while (breakPos > 0 && (line[breakPos] & 0xC0) == 0x80) { - breakPos--; - } - } - } - if (breakPos == 0) { - breakPos = 1; + breakPos = 1; // Ensure progress } outLines.push_back(line.substr(0, breakPos));