splash, rework word wrap, support some escape characters

rockbox-gerrit-noreply--- via rockbox-cvs <[email protected]> Fri, 15 May 2026 11:13:12 -0400
Newsgroups gmane.comp.systems.archos.rockbox.cvs
Message-ID <[email protected]>
commit b4c308d6989c7666203499066d8b40173344d053
Author: William Wilgus <[email protected]>
Date:   Wed May 6 09:21:57 2026 -0400

    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

diff --git a/apps/gui/splash.c b/apps/gui/splash.c
index 3df6c547f6..9e8724e41c 100644
--- a/apps/gui/splash.c
+++ b/apps/gui/splash.c
@@ -8,6 +8,7 @@
  * $Id$
  *
  * Copyright (C) Daniel Stenberg (2002)
+ * Copyright (C) William Wilgus (2026)
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -39,109 +40,196 @@
 static long progress_next_tick, talked_tick;
 
 #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 SPLASH_MEMORY_INTERVAL (HZ)
 
 static bool splash_internal(struct screen * screen, const char *fmt, va_list ap,
                             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
     static enum current_activity last_act = ACTIVITY_UNKNOWN;
     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)
-            max_width[i] = 2*RECT_SPACING;
+        {
+            max_width[i] = 0;
+            max_height[i] = 0;
+        }
         last_act = act;
     }
 #endif
-    /* prevent screen artifacts by keeping the max width seen */
-    int min_width = max_width[screen->screen_type];
-    char splash_buf[MAXBUFFER];
-    struct splash_lines {
-        const char *str;
-        size_t len;
-    } lines[MAXLINES];
-    const char *next;
-    const char *lastbreak = NULL;
-    const char *store = NULL;
+
+    char *buf = splash_buf;
+    const char *next, *store;
+
     int line = 0;
-    int x = 0;
-    int y, i;
-    int space_w, w, chr_h;
-    int width, height;
-    int maxw = min_width - 2*RECT_SPACING;
+    int x = 0, w = 0;
+    int y;
+    int space_w, chr_h;
+    int width = vp->width - RECT_SPACING*2;
+    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;
 
-    char lastbrkchr;
-    size_t len, next_len;
-    const char matchstr[] = "\r\n\f\v\t ";
+    size_t next_len;
+
     font_getstringsize(" ", &space_w, &chr_h, fontnum);
-    y = chr_h + (addl_lines * chr_h);
+    y = (addl_lines * chr_h);
 
-    vsnprintf(splash_buf, sizeof(splash_buf), fmt, ap);
+    int res = vsnprintf(splash_buf, sizeof(splash_buf), fmt, ap); /*-1 to prevent sanitizer issues */
     va_end(ap);
 
-    /* break splash string into display lines, doing proper word wrap */
-    next = strptokspn_r(splash_buf, matchstr, &next_len, &store);
-    if (!next)
+    if (res <= 0)
+    {
+#ifdef SIMULATOR
+        printf("ERR\n");
+#endif
         return false; /* nothing to display */
-
-    lines[line].len = next_len;
-    lines[line].str = next;
-    while (true)
+    }
+    /* break splash string into display lines, doing proper word wrap */
+    const char *lastbreak = splash_buf;
+    while(true)
     {
-        w = font_getstringnsize(next, next_len, NULL, NULL, fontnum);
-        if (lastbreak)
+        while (*lastbreak != '\0') /* handle escape chars*/
         {
-            len = next - lastbreak;
-            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)
+            switch (*lastbreak) /* all chars in matchstr */
+            {
+                case '\t': /*fallthrough*/
+                case '\n':
                 {
-                    next_len = nl;
-                    store = next + nl; /* move the start pos for the next token read */
+                    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;
                 }
-
-                lines[++line].len = next_len;
-                lines[line].str = next;
+                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;
             }
-            else
+            lastbreak++;
+        }
+
+        if (lines[line].len > 0)
+        {
+            x += w;
+            if (x > maxw)
+                maxw = x;
+            x = 0;
+            y += chr_h;
+            if (y >= height || line >= MAXLINES-1)
             {
-                /*  restore & calculate spacing */
-                lines[line].len += next_len + 1;
-                x += next_w;
+                if (y > maxh)
+                    maxh = y;
+                break;  /* out of lines */
             }
+            line++;
         }
-        x += w;
 
-        lastbreak = next + next_len;
-        lastbrkchr = *lastbreak;
-
-        next = strptokspn_r(NULL, matchstr, &next_len, &store);
+        next = strptokspn_r(buf, matchstr, &next_len, &store);
 
         if (!next)
-        {   /* no more words */
+        {
+            x += w;
             if (x > maxw)
                 maxw = x;
+            if (y > maxh)
+                maxh = y;
             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
@@ -151,7 +239,7 @@ static bool splash_internal(struct screen * screen, const char *fmt, va_list ap,
     screen->scroll_stop();
 
     width = maxw + 2*RECT_SPACING;
-    height = y + 2*RECT_SPACING;
+    height = maxh + 2*RECT_SPACING;
 
     if (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->width = width;
     vp->height = height;
+    vp->flags |=  VP_FLAG_ALIGN_CENTER;
 
-    /* prevent artifacts by locking to max width observed on repeated calls */
-    max_width[screen->screen_type] = width;
+    /* 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;
 
-    vp->flags |=  VP_FLAG_ALIGN_CENTER;
 #if LCD_DEPTH > 1
     unsigned fg = 0, bg = 0;
     bool broken = false;
@@ -203,11 +292,22 @@ static bool splash_internal(struct screen * screen, const char *fmt, va_list ap,
 
     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 */
-    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 */
 }
 
diff --git a/apps/gui/splash.h b/apps/gui/splash.h
index a42ad29140..7540cfbe7c 100644
--- a/apps/gui/splash.h
+++ b/apps/gui/splash.h
@@ -29,6 +29,9 @@
  * Puts a splash message centered on all the screens for a given period
  *  - ticks : how long the splash is displayed (in rb ticks)
  *  - 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);
 
-- 
rockbox-cvs mailing list
[email protected]
https://lists.haxx.se/mailman/listinfo/rockbox-cvs