splash, rework word wrap, support some escape characters

Fixes issues with multiple spaces
adds support for \n and multiple \n
adds support for \t (only at start of line)

adds utf8 aware string splitting

locks to max width/height

adds \f to reset min width / height

larger buffer for units with ram > 8mb

make splash buffer static

Needs testing
Change-Id: I46915043719dce0d696c5ab0da3ed92e4755829c
This commit is contained in:
William Wilgus 2026-05-06 09:21:57 -04:00
parent 89cb41c322
commit b4c308d698
2 changed files with 182 additions and 79 deletions

View file

@ -8,6 +8,7 @@
* $Id$ * $Id$
* *
* Copyright (C) Daniel Stenberg (2002) * Copyright (C) Daniel Stenberg (2002)
* Copyright (C) William Wilgus (2026)
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -39,109 +40,196 @@
static long progress_next_tick, talked_tick; static long progress_next_tick, talked_tick;
#define MAXLINES (LCD_HEIGHT/6) #define MAXLINES (LCD_HEIGHT/6)
#define MAXBUFFER 512
#if MEMORYSIZE > 8
#define MAXBUFFER MAX(1024, (LCD_WIDTH/SYSFONT_WIDTH) * MAXLINES + 1)
#else
#define MAXBUFFER MAX(512, (LCD_WIDTH/SYSFONT_WIDTH) * MAXLINES + 1)
#endif
#define RECT_SPACING 3 #define RECT_SPACING 3
#define SPLASH_MEMORY_INTERVAL (HZ) #define SPLASH_MEMORY_INTERVAL (HZ)
static bool splash_internal(struct screen * screen, const char *fmt, va_list ap, static bool splash_internal(struct screen * screen, const char *fmt, va_list ap,
struct viewport *vp, int addl_lines) struct viewport *vp, int addl_lines)
{ {
static int max_width[NB_SCREENS] = {2*RECT_SPACING}; static const char matchstr[] = "\r\n\f\v\t";
static int max_width[NB_SCREENS] = {0};
static int max_height[NB_SCREENS] = {0};
static char splash_buf[MAXBUFFER] = {0};
struct splash_lines {
const char *str;
uint16_t x;
uint16_t len;
} lines[MAXLINES];
memset(lines, 0, sizeof(lines));
#ifndef BOOTLOADER #ifndef BOOTLOADER
static enum current_activity last_act = ACTIVITY_UNKNOWN; static enum current_activity last_act = ACTIVITY_UNKNOWN;
enum current_activity act = get_current_activity(); enum current_activity act = get_current_activity();
if (last_act != act) /* changed activities reset max_width */ if (last_act != act) /* changed activities reset width / height */
{ {
FOR_NB_SCREENS(i) FOR_NB_SCREENS(i)
max_width[i] = 2*RECT_SPACING; {
max_width[i] = 0;
max_height[i] = 0;
}
last_act = act; last_act = act;
} }
#endif #endif
/* prevent screen artifacts by keeping the max width seen */
int min_width = max_width[screen->screen_type]; char *buf = splash_buf;
char splash_buf[MAXBUFFER]; const char *next, *store;
struct splash_lines {
const char *str;
size_t len;
} lines[MAXLINES];
const char *next;
const char *lastbreak = NULL;
const char *store = NULL;
int line = 0; int line = 0;
int x = 0; int x = 0, w = 0;
int y, i; int y;
int space_w, w, chr_h; int space_w, chr_h;
int width, height; int width = vp->width - RECT_SPACING*2;
int maxw = min_width - 2*RECT_SPACING; int height = vp->height;
/* prevent screen artifacts by keeping the max width / height seen */
int maxw = max_width[screen->screen_type];
int maxh = max_height[screen->screen_type];
int fontnum = vp->font; int fontnum = vp->font;
char lastbrkchr; size_t next_len;
size_t len, next_len;
const char matchstr[] = "\r\n\f\v\t ";
font_getstringsize(" ", &space_w, &chr_h, fontnum);
y = chr_h + (addl_lines * chr_h);
vsnprintf(splash_buf, sizeof(splash_buf), fmt, ap); font_getstringsize(" ", &space_w, &chr_h, fontnum);
y = (addl_lines * chr_h);
int res = vsnprintf(splash_buf, sizeof(splash_buf), fmt, ap); /*-1 to prevent sanitizer issues */
va_end(ap); va_end(ap);
/* break splash string into display lines, doing proper word wrap */ if (res <= 0)
next = strptokspn_r(splash_buf, matchstr, &next_len, &store);
if (!next)
return false; /* nothing to display */
lines[line].len = next_len;
lines[line].str = next;
while (true)
{ {
w = font_getstringnsize(next, next_len, NULL, NULL, fontnum); #ifdef SIMULATOR
if (lastbreak) printf("ERR\n");
#endif
return false; /* nothing to display */
}
/* break splash string into display lines, doing proper word wrap */
const char *lastbreak = splash_buf;
while(true)
{
while (*lastbreak != '\0') /* handle escape chars*/
{ {
len = next - lastbreak; switch (*lastbreak) /* all chars in matchstr */
int next_w = len * space_w;
if (x + next_w + w > vp->width - RECT_SPACING*2 || lastbrkchr != ' ')
{ /* too wide, or control character wrap */
if (x > maxw)
maxw = x;
if ((y + chr_h * 2 > vp->height) || (line >= (MAXLINES-1)))
break; /* screen full or out of lines */
x = 0;
y += chr_h;
/* split when it fits since we didn't find a valid token to break on */
size_t nl = next_len;
while (w > vp->width && --nl > 0)
w = font_getstringnsize(next, nl, NULL, NULL, fontnum);
if (nl > 1 && nl != next_len)
{
next_len = nl;
store = next + nl; /* move the start pos for the next token read */
}
lines[++line].len = next_len;
lines[line].str = next;
}
else
{ {
/* restore & calculate spacing */ case '\t': /*fallthrough*/
lines[line].len += next_len + 1; case '\n':
x += next_w; {
if (lines[line].len > 0 || *lastbreak != '\t')
{
x += w;
if (x > maxw)
maxw = x;
x = 0;
y += chr_h;
if (y >= height || line >= MAXLINES-1)
{
break; /* out of lines */
}
line++;
}
if (*lastbreak == '\t')
{
/* add offset for tab */
lines[line].x += space_w * 2;
x += space_w * 4;
}
break;
}
case '\f':
{
if (lastbreak == splash_buf) /* only as first character */
{
/* Reset splash box size */
maxw = 0;
maxh = 0;
}
break; /* acts the same as a space character */
}
case '\v': /*fallthrough*/
case '\r':
break; /* acts the same as a space character */
default: /* No valid break chars */
lastbreak = " "; /* exit */
break;
} }
lastbreak++;
} }
x += w;
lastbreak = next + next_len; if (lines[line].len > 0)
lastbrkchr = *lastbreak; {
x += w;
next = strptokspn_r(NULL, matchstr, &next_len, &store);
if (!next)
{ /* no more words */
if (x > maxw) if (x > maxw)
maxw = x; maxw = x;
x = 0;
y += chr_h;
if (y >= height || line >= MAXLINES-1)
{
if (y > maxh)
maxh = y;
break; /* out of lines */
}
line++;
}
next = strptokspn_r(buf, matchstr, &next_len, &store);
if (!next)
{
x += w;
if (x > maxw)
maxw = x;
if (y > maxh)
maxh = y;
break; break;
} }
buf = NULL; /* uses store for continuation */
lines[line].str = next;
lines[line].len = next_len;
w = font_getstringnsize(next, next_len, NULL, NULL, fontnum);
if (w > width)
{
const char *nxp, *nx = next;
int nw, newlen, oldlen = next_len;
while (nx - next < oldlen) /* try to split at a space char */
{
nxp = nx;
nx++;
if (*nxp != ' ')
continue;
newlen = nxp - next;
nw = font_getstringnsize(next, newlen, NULL, NULL, fontnum);
if (nw > width)
{
/* is room left on this line & next word large enough? */
if (w + space_w * 5 < width && nw - space_w * 8 > w)
w = nw;
break;
}
w = nw;
next_len = newlen;
store = nx; /* we want to skip the space char */
}
if (w > width) /* split when it fits */
{
w = width;
next_len = font_measurestring(next, oldlen, &w, fontnum);
store = next + next_len;
}
lines[line].len = next_len;
}
lastbreak = next + next_len;
} }
/* prepare viewport /* prepare viewport
@ -151,7 +239,7 @@ static bool splash_internal(struct screen * screen, const char *fmt, va_list ap,
screen->scroll_stop(); screen->scroll_stop();
width = maxw + 2*RECT_SPACING; width = maxw + 2*RECT_SPACING;
height = y + 2*RECT_SPACING; height = maxh + 2*RECT_SPACING;
if (width > vp->width) if (width > vp->width)
width = vp->width; width = vp->width;
@ -162,11 +250,12 @@ static bool splash_internal(struct screen * screen, const char *fmt, va_list ap,
vp->y += (vp->height - height) / 2; vp->y += (vp->height - height) / 2;
vp->width = width; vp->width = width;
vp->height = height; vp->height = height;
/* prevent artifacts by locking to max width observed on repeated calls */
max_width[screen->screen_type] = width;
vp->flags |= VP_FLAG_ALIGN_CENTER; vp->flags |= VP_FLAG_ALIGN_CENTER;
/* prevent artifacts by locking to max width & height observed on repeated calls */
max_width[screen->screen_type] = width - 2*RECT_SPACING;
max_height[screen->screen_type] = height - 2*RECT_SPACING;
#if LCD_DEPTH > 1 #if LCD_DEPTH > 1
unsigned fg = 0, bg = 0; unsigned fg = 0, bg = 0;
bool broken = false; bool broken = false;
@ -203,11 +292,22 @@ static bool splash_internal(struct screen * screen, const char *fmt, va_list ap,
screen->draw_border_viewport(); screen->draw_border_viewport();
/* center y within the available splash box */
if (y < maxh)
y = (maxh - y) / 2;
else
y = RECT_SPACING;
/* print the message to screen */ /* print the message to screen */
for(i = 0, y = RECT_SPACING; i <= line; i++, y+= chr_h) for(int i = 0; i < line && y < height; i++, y+= chr_h)
{ {
screen->putsxyf(0, y, "%.*s", lines[i].len, lines[i].str); if (lines[i].len > 0)
{
x = lines[i].x;
screen->putsxyf(x, y, "%.*s", lines[i].len, lines[i].str);
}
} }
return true; /* needs update */ return true; /* needs update */
} }

View file

@ -29,6 +29,9 @@
* Puts a splash message centered on all the screens for a given period * Puts a splash message centered on all the screens for a given period
* - ticks : how long the splash is displayed (in rb ticks) * - ticks : how long the splash is displayed (in rb ticks)
* - fmt : what to say *printf style * - fmt : what to say *printf style
* /f at the beginning of a string will reset the max width and height seen
* /n will end a line /n/n will add a blank line
* /t will start a new line and indent the string on that line
*/ */
extern void splashf(int ticks, const char *fmt, ...) ATTRIBUTE_PRINTF(2, 3); extern void splashf(int ticks, const char *fmt, ...) ATTRIBUTE_PRINTF(2, 3);