From b536e24f8e649a4e92e70862eaeed623b57924fe Mon Sep 17 00:00:00 2001 From: pelgraine <140762863+pelgraine@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:57:29 +1000 Subject: [PATCH] fix word wrapping ereader for larger custom font selection --- .../companion_radio/ui-new/Textreaderscreen.h | 69 ++++++++++++------- examples/companion_radio/ui-new/UITask.cpp | 14 +++- 2 files changed, 57 insertions(+), 26 deletions(-) diff --git a/examples/companion_radio/ui-new/Textreaderscreen.h b/examples/companion_radio/ui-new/Textreaderscreen.h index 4692f14f..1a653b96 100644 --- a/examples/companion_radio/ui-new/Textreaderscreen.h +++ b/examples/companion_radio/ui-new/Textreaderscreen.h @@ -106,8 +106,6 @@ inline WrapResult findLineBreak(const char* buffer, int bufLen, int lineStart, i // width variation in proportional fonts like FreeSans12pt. // maxChars is a safety upper bound to prevent runaway on spaceless lines. // ============================================================================ -#if defined(LilyGo_T5S3_EPaper_Pro) -#include inline WrapResult findLineBreakPixel(const char* buffer, int bufLen, int lineStart, DisplayDriver* display, int maxChars) { @@ -235,7 +233,6 @@ inline WrapResult findLineBreakPixel(const char* buffer, int bufLen, int lineSta result.nextStart = bufLen; return result; } -#endif // LilyGo_T5S3_EPaper_Pro // ============================================================================ // Page Indexer (word-wrap aware, matches display rendering) @@ -247,7 +244,8 @@ inline int indexPagesWordWrap(File& file, long startPos, std::vector& pagePositions, int linesPerPage, int charsPerLine, int maxPages, - int textAreaHeight = 0, int lineHeight = 0) { + int textAreaHeight = 0, int lineHeight = 0, + DisplayDriver* pixelDisplay = nullptr) { const int BUF_SIZE = READER_BUF_SIZE; // Match page buffer to avoid chunk boundary wrap mismatches char buffer[BUF_SIZE]; @@ -269,7 +267,10 @@ inline int indexPagesWordWrap(File& file, long startPos, int pos = 0; while (pos < bufLen) { int lineStart = pos; - WrapResult wrap = findLineBreak(buffer, bufLen, pos, charsPerLine); + // Pixel-based wrapping for proportional fonts; char-count for monospaced + WrapResult wrap = pixelDisplay + ? findLineBreakPixel(buffer, bufLen, pos, pixelDisplay, charsPerLine) + : findLineBreak(buffer, bufLen, pos, charsPerLine); if (wrap.nextStart <= pos && wrap.lineEnd >= bufLen) break; // Blank line = newline at line start (no printable content before it) @@ -322,9 +323,8 @@ inline int indexPagesWordWrap(File& file, long startPos, } // ============================================================================ -// Pixel-based Page Indexer for T5S3 (proportional font word wrap) +// Pixel-based Page Indexer (proportional font word wrap) // ============================================================================ -#if defined(LilyGo_T5S3_EPaper_Pro) inline int indexPagesWordWrapPixel(File& file, long startPos, std::vector& pagePositions, int linesPerPage, int maxChars, @@ -377,7 +377,6 @@ inline int indexPagesWordWrapPixel(File& file, long startPos, display->setTextSize(1); // Restore return pagesAdded; } -#endif // LilyGo_T5S3_EPaper_Pro // ============================================================================ // TextReaderScreen @@ -946,17 +945,19 @@ private: } drawSplash("Indexing...", "Please wait", shortName); + DisplayDriver* pxd = (_prefs->large_font || _prefs->ui_font_style > 0) ? _display : nullptr; + if (pxd) pxd->setTextSize(_prefs->smallTextSize()); if (_pagePositions.empty()) { // Cache had no pages (e.g. dummy entry) — full index from scratch _pagePositions.push_back(0); indexPagesWordWrap(_file, 0, _pagePositions, _linesPerPage, _charsPerLine, 0, - _textAreaHeight, _lineHeight); + _textAreaHeight, _lineHeight, pxd); } else { long lastPos = cache->pagePositions.back(); indexPagesWordWrap(_file, lastPos, _pagePositions, _linesPerPage, _charsPerLine, 0, - _textAreaHeight, _lineHeight); + _textAreaHeight, _lineHeight, pxd); } } else { // No cache — full index from scratch @@ -974,9 +975,11 @@ private: drawSplash("Indexing...", "Please wait", shortName); _pagePositions.push_back(0); + DisplayDriver* pxd = (_prefs->large_font || _prefs->ui_font_style > 0) ? _display : nullptr; + if (pxd) pxd->setTextSize(_prefs->smallTextSize()); indexPagesWordWrap(_file, 0, _pagePositions, _linesPerPage, _charsPerLine, 0, - _textAreaHeight, _lineHeight); + _textAreaHeight, _lineHeight, pxd); } // Save complete index @@ -1189,18 +1192,29 @@ private: int y = 0; int lineCount = 0; int pos = 0; - int maxY = display.height() - _footerHeight - _lineHeight; + int textArea = display.height() - _footerHeight; // total usable height (matches indexer's textAreaHeight) // Render all lines in the page buffer using word wrap. // The buffer contains exactly the bytes for this page (from indexed positions), // so we render everything in it. - while (pos < _pageBufLen && y <= maxY) { + // Proportional fonts use pixel-based wrapping to match the indexer exactly. + bool usePixelWrap = (_prefs->large_font || display.getFontStyle() > 0); + while (pos < _pageBufLen) { int oldPos = pos; - WrapResult wrap = findLineBreak(_pageBuf, _pageBufLen, pos, _charsPerLine); + WrapResult wrap = usePixelWrap + ? findLineBreakPixel(_pageBuf, _pageBufLen, pos, &display, _charsPerLine) + : findLineBreak(_pageBuf, _pageBufLen, pos, _charsPerLine); - // Safety: stop if findLineBreak made no progress (stuck at end of buffer) + // Safety: stop if wrap made no progress (stuck at end of buffer) if (wrap.nextStart <= oldPos && wrap.lineEnd >= _pageBufLen) break; + // Height-aware stop check — must match the indexer exactly. + // Blank lines (lineEnd == lineStart) get reduced height. + // Check BEFORE rendering: does this line fit on the current page? + bool isBlankLine = (wrap.lineEnd == pos); + int thisH = isBlankLine ? max(2, _lineHeight * 2 / 5) : _lineHeight; + if (y > 0 && y + thisH > textArea) break; + display.setCursor(0, y); // Print line with UTF-8 decoding: multi-byte sequences are decoded // to Unicode codepoints, then mapped to CP437 for the built-in font. @@ -1359,15 +1373,16 @@ public: if (_charsPerLine < 15) _charsPerLine = 15; if (_charsPerLine > 80) _charsPerLine = 80; #else - // T-Deck Pro: large_font or custom proportional font — measure average - // character width from a sample sentence (M is widest glyph, ~40% wider - // than average, so M-based measurement leaves half the line empty). + // T-Deck Pro: proportional font — measure average character width from + // a sample sentence (M is widest glyph, ~40% wider than average). + // Large font (9pt) uses 70% safety margin; custom tiny (7pt) uses 85%. if (_prefs && (_prefs->large_font || display.getFontStyle() > 0)) { const char* sample = "the quick brown fox jumps over lazy dog"; uint16_t sampleW = display.getTextWidth(sample); int sampleLen = strlen(sample); if (sampleW > 0 && sampleLen > 0) { - _charsPerLine = (display.width() * sampleLen * 85) / ((int)sampleW * 100); + int pct = _prefs->large_font ? 70 : 85; + _charsPerLine = (display.width() * sampleLen * pct) / ((int)sampleW * 100); } } if (_charsPerLine < 15) _charsPerLine = 15; @@ -1502,10 +1517,12 @@ public: cache.pagePositions.clear(); cache.pagePositions.push_back(0); + DisplayDriver* pxd = (_prefs->large_font || _prefs->ui_font_style > 0) ? _display : nullptr; + if (pxd) pxd->setTextSize(_prefs->smallTextSize()); indexPagesWordWrap(file, 0, cache.pagePositions, _linesPerPage, _charsPerLine, PREINDEX_PAGES - 1, - _textAreaHeight, _lineHeight); + _textAreaHeight, _lineHeight, pxd); cache.fullyIndexed = !file.available(); file.close(); @@ -1632,10 +1649,12 @@ public: cache.pagePositions.clear(); cache.pagePositions.push_back(0); + DisplayDriver* pxd = (_prefs->large_font || _prefs->ui_font_style > 0) ? _display : nullptr; + if (pxd) pxd->setTextSize(_prefs->smallTextSize()); int added = indexPagesWordWrap(file, 0, cache.pagePositions, _linesPerPage, _charsPerLine, PREINDEX_PAGES - 1, - _textAreaHeight, _lineHeight); + _textAreaHeight, _lineHeight, pxd); cache.fullyIndexed = !file.available(); file.close(); @@ -1698,9 +1717,11 @@ public: // Layout was invalidated (orientation change) — reindex the open book Serial.println("TextReader: Reindexing after layout change"); _pagePositions.push_back(0); + DisplayDriver* pxd = (_prefs->large_font || _prefs->ui_font_style > 0) ? _display : nullptr; + if (pxd) pxd->setTextSize(_prefs->smallTextSize()); indexPagesWordWrap(_file, 0, _pagePositions, _linesPerPage, _charsPerLine, 0, - _textAreaHeight, _lineHeight); + _textAreaHeight, _lineHeight, pxd); _totalPages = _pagePositions.size(); if (_currentPage >= _totalPages) _currentPage = 0; _mode = READING; @@ -1869,10 +1890,12 @@ public: cache.lastReadPage = 0; cache.pagePositions.clear(); cache.pagePositions.push_back(0); + DisplayDriver* pxd = (_prefs->large_font || _prefs->ui_font_style > 0) ? _display : nullptr; + if (pxd) pxd->setTextSize(_prefs->smallTextSize()); indexPagesWordWrap(file, 0, cache.pagePositions, _linesPerPage, _charsPerLine, PREINDEX_PAGES - 1, - _textAreaHeight, _lineHeight); + _textAreaHeight, _lineHeight, pxd); cache.fullyIndexed = !file.available(); file.close(); saveIndex(cache.filename, cache.pagePositions, cache.fileSize, diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 41822ade..350e19a7 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -484,10 +484,18 @@ public: if (_node_prefs->large_font || display.getFontStyle() > 0) { // Proportional font: two-column layout with fixed X positions - // Centered to match Classic layout's visual weight (~16-unit margins) y += 2; - int col1 = display.width() / 10; // ~12 - int col2 = display.width() * 11 / 20; // ~70 + int col1, col2; + if (_node_prefs->large_font) { + // 9pt font: measure widest left entry and place col2 just past it + col1 = 2; + int leftW = display.getTextWidth("[M] Messages"); + col2 = col1 + leftW + 3; + } else { + // Custom tiny (7pt): centered layout + col1 = display.width() / 10; + col2 = display.width() * 11 / 20; + } display.setCursor(col1, y); display.print("[M] Messages"); display.setCursor(col2, y); display.print("[C] Contacts");