mirror of
https://github.com/pelgraine/Meck.git
synced 2026-05-02 03:22:38 +02:00
fix word wrapping ereader for larger custom font selection
This commit is contained in:
@@ -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 <helpers/ui/DisplayDriver.h>
|
||||
|
||||
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<long>& 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<long>& 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,
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user