mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 14:58:12 +00:00
perf(ui): O(n) FullscreenMsgView wrap via getCodepointWidth virtual
The wrap loop used to memcpy a growing substring and call getTextWidth on it for every appended codepoint — O(n²) UTF-8 decoding with Lemon font, ~5-20 ms on a full e-ink redraw of a long message. Add DisplayDriver::getCodepointWidth(cp) (default: build a UTF-8 buffer and forward to getTextWidth) plus DisplayDriver::decodeCodepoint() as the shared UTF-8 walker. Override getCodepointWidth in GxEPDDisplay (direct lemonXAdvance for Lemon size 1) and SH1106Display (lemonXAdvance for Lemon, fixed 6*sz for built-in font). FullscreenMsgView now walks codepoints once and sums widths incrementally. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,33 +16,33 @@ struct FullscreenMsgView {
|
||||
void begin() { scroll = 0; active = true; }
|
||||
|
||||
// Pixel-accurate word-wrap: breaks at last space that fits within max_px,
|
||||
// hard-breaks long words. Works for both fixed- and variable-width fonts.
|
||||
// hard-breaks long words. Walks the string codepoint-by-codepoint, summing
|
||||
// per-codepoint widths via DisplayDriver::getCodepointWidth — O(n) instead
|
||||
// of O(n²) substring re-measurement. Works for both fixed- and
|
||||
// variable-width fonts.
|
||||
static int wrapLines(DisplayDriver& display, const char* text, int max_px,
|
||||
char out[][FS_CHARS_MAX], int max_lines) {
|
||||
int count = 0;
|
||||
const char* seg = text;
|
||||
const uint8_t* seg = (const uint8_t*)text;
|
||||
while (*seg && count < max_lines) {
|
||||
const char* p = seg;
|
||||
const char* last_sp = nullptr;
|
||||
const char* last_fit = seg;
|
||||
const uint8_t* p = seg;
|
||||
const uint8_t* last_sp = nullptr; // pointer to space byte (cut here, drop the space)
|
||||
const uint8_t* last_fit = seg; // farthest point that still fits
|
||||
int width = 0;
|
||||
|
||||
while (*p && (p - seg) < FS_CHARS_MAX - 1) {
|
||||
uint8_t b0 = (uint8_t)*p;
|
||||
int cb = (b0 < 0x80) ? 1 : (b0 < 0xE0) ? 2 : (b0 < 0xF0) ? 3 : 4;
|
||||
if ((p - seg) + cb >= FS_CHARS_MAX) break;
|
||||
|
||||
char buf[FS_CHARS_MAX];
|
||||
int n = (int)(p - seg) + cb;
|
||||
memcpy(buf, seg, n); buf[n] = '\0';
|
||||
if (display.getTextWidth(buf) > max_px && p > seg) break;
|
||||
|
||||
if (*p == ' ') last_sp = p;
|
||||
p += cb;
|
||||
const uint8_t* cp_start = p;
|
||||
uint32_t cp = DisplayDriver::decodeCodepoint(p);
|
||||
if ((p - seg) >= FS_CHARS_MAX) { p = cp_start; break; }
|
||||
uint16_t cw = display.getCodepointWidth(cp);
|
||||
if (width + cw > max_px && cp_start > seg) { p = cp_start; break; }
|
||||
if (cp == ' ') last_sp = cp_start;
|
||||
width += cw;
|
||||
last_fit = p;
|
||||
}
|
||||
|
||||
const char* end;
|
||||
const char* next_seg;
|
||||
const uint8_t* end;
|
||||
const uint8_t* next_seg;
|
||||
if (!*p) {
|
||||
end = p; next_seg = p;
|
||||
} else if (last_sp && last_sp > seg) {
|
||||
|
||||
@@ -160,7 +160,48 @@ public:
|
||||
virtual void translateUTF8ToBlocks(char* dest, const char* src, size_t dest_size) {
|
||||
translateUTF8Static(dest, src, dest_size);
|
||||
}
|
||||
|
||||
|
||||
// Advance a UTF-8 pointer by one codepoint, returning the decoded value.
|
||||
// Invalid sequences return 0xFFFD and consume trailing continuation bytes.
|
||||
static uint32_t decodeCodepoint(const uint8_t*& p) {
|
||||
uint8_t c = *p++;
|
||||
if (c < 0x80) return c;
|
||||
if ((c & 0xE0) == 0xC0) {
|
||||
uint32_t cp = c & 0x1F;
|
||||
if (*p) cp = (cp << 6) | (*p++ & 0x3F);
|
||||
return cp;
|
||||
}
|
||||
if ((c & 0xF0) == 0xE0) {
|
||||
uint32_t cp = c & 0x0F;
|
||||
if (*p) cp = (cp << 6) | (*p++ & 0x3F);
|
||||
if (*p) cp = (cp << 6) | (*p++ & 0x3F);
|
||||
return cp;
|
||||
}
|
||||
if ((c & 0xF8) == 0xF0) {
|
||||
uint32_t cp = c & 0x07;
|
||||
if (*p) cp = (cp << 6) | (*p++ & 0x3F);
|
||||
if (*p) cp = (cp << 6) | (*p++ & 0x3F);
|
||||
if (*p) cp = (cp << 6) | (*p++ & 0x3F);
|
||||
return cp;
|
||||
}
|
||||
while (*p && (*p & 0xC0) == 0x80) p++;
|
||||
return 0xFFFD;
|
||||
}
|
||||
|
||||
// Width of a single codepoint in pixels. Default: fall back to getTextWidth
|
||||
// on a one-codepoint UTF-8 string. Drivers can override for O(1) lookup.
|
||||
virtual uint16_t getCodepointWidth(uint32_t cp) {
|
||||
char buf[5];
|
||||
int n = 0;
|
||||
if (cp < 0x80) { buf[n++] = (char)cp; }
|
||||
else if (cp < 0x800) { buf[n++] = 0xC0 | (cp >> 6); buf[n++] = 0x80 | (cp & 0x3F); }
|
||||
else if (cp < 0x10000) { buf[n++] = 0xE0 | (cp >> 12); buf[n++] = 0x80 | ((cp >> 6) & 0x3F); buf[n++] = 0x80 | (cp & 0x3F); }
|
||||
else { buf[n++] = 0xF0 | (cp >> 18); buf[n++] = 0x80 | ((cp >> 12) & 0x3F); buf[n++] = 0x80 | ((cp >> 6) & 0x3F); buf[n++] = 0x80 | (cp & 0x3F); }
|
||||
buf[n] = '\0';
|
||||
return getTextWidth(buf);
|
||||
}
|
||||
|
||||
|
||||
// draw text with ellipsis if it exceeds max_width
|
||||
virtual void drawTextEllipsized(int x, int y, int max_width, const char* str) {
|
||||
char temp_str[256]; // reasonable buffer size
|
||||
|
||||
@@ -114,6 +114,10 @@ public:
|
||||
void drawRect(int x, int y, int w, int h) override;
|
||||
void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override;
|
||||
uint16_t getTextWidth(const char* str) override;
|
||||
uint16_t getCodepointWidth(uint32_t cp) override {
|
||||
if (_use_lemon && _text_sz == 1) return lemonXAdvance(cp, scale());
|
||||
return DisplayDriver::getCodepointWidth(cp);
|
||||
}
|
||||
void setDisplayRotation(uint8_t rot) override;
|
||||
void setFullRefreshInterval(uint8_t n) override { _full_refresh_interval = n; _partial_count = 0; }
|
||||
void endFrame() override;
|
||||
|
||||
@@ -48,6 +48,10 @@ public:
|
||||
void drawRect(int x, int y, int w, int h) override;
|
||||
void drawXbm(int x, int y, const uint8_t *bits, int w, int h) override;
|
||||
uint16_t getTextWidth(const char *str) override;
|
||||
uint16_t getCodepointWidth(uint32_t cp) override {
|
||||
if (_use_lemon) return lemonXAdvance(cp);
|
||||
return 6 * _text_sz; // built-in 5x7 font: 6 px advance per glyph
|
||||
}
|
||||
int getCharWidth() const override { return (_use_lemon ? 5 : 6) * _text_sz; }
|
||||
int getLineHeight() const override { return (_use_lemon ? 9 : 8) * _text_sz; }
|
||||
void setLemonFont(bool enabled) override { _use_lemon = enabled; _vw_dirty = true; }
|
||||
|
||||
Reference in New Issue
Block a user