From fe2d4d4cef093d24da389fda29a556b15c84829b Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 2 Apr 2025 06:29:32 +1300 Subject: [PATCH 01/13] console: Support a format string for stderr output Add a console_printf_select_stderr() function so that it is not necessary for the caller to process the format string. Signed-off-by: Simon Glass Reviewed-by: Alexander Sverdlin --- common/console.c | 18 ++++++++++++++++++ include/console.h | 15 +++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/common/console.c b/common/console.c index 275da2f264d..48586fd2166 100644 --- a/common/console.c +++ b/common/console.c @@ -359,6 +359,24 @@ void console_puts_select_stderr(bool serial_only, const char *s) console_puts_select(stderr, serial_only, s); } +int console_printf_select_stderr(bool serial_only, const char *fmt, ...) +{ + char buf[CONFIG_SYS_PBSIZE]; + va_list args; + int ret; + + va_start(args, fmt); + + /* For this to work, buf must be larger than anything we ever want to + * print. + */ + ret = vscnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + console_puts_select_stderr(serial_only, buf); + + return ret; +} + static void console_puts(int file, const char *s) { int i; diff --git a/include/console.h b/include/console.h index 57fdb0834c1..8d0d7bb8a4c 100644 --- a/include/console.h +++ b/include/console.h @@ -169,6 +169,21 @@ int console_announce_r(void); */ void console_puts_select_stderr(bool serial_only, const char *s); +/** + * console_printf_select_stderr() - Output a formatted string to selected devs + * + * This writes to stderr only. It is useful for outputting errors. Note that it + * uses its own buffer, separate from the print buffer, to allow printing + * messages within console/stdio code + * + * @serial_only: true to output only to serial, false to output to everything + * else + * @fmt: Printf format string, followed by format arguments + * Return: number of characters written + */ +int console_printf_select_stderr(bool serial_only, const char *fmt, ...) + __attribute__ ((format (__printf__, 2, 3))); + /** * console_clear() - Clear the console * From 3ad5f49b5db4337626004629842d3dea30467c92 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 2 Apr 2025 06:29:33 +1300 Subject: [PATCH 02/13] video: Make white-on-black a video-device property The CONFIG_WHITE_ON_BLACK setting is hard-coded at build-time. It is useful to be able to control this when showing menus. Create a property to hold this information, using the CONFIG as the initial value. Signed-off-by: Simon Glass --- boot/expo.c | 2 +- boot/scene.c | 8 ++++---- drivers/video/video-uclass.c | 16 +++++++++++++++- include/video.h | 12 ++++++++++++ 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/boot/expo.c b/boot/expo.c index 786f665f53c..8ce645e5a8f 100644 --- a/boot/expo.c +++ b/boot/expo.c @@ -194,7 +194,7 @@ int expo_render(struct expo *exp) u32 colour; int ret; - back = CONFIG_IS_ENABLED(SYS_WHITE_ON_BLACK) ? VID_BLACK : VID_WHITE; + back = vid_priv->white_on_black ? VID_BLACK : VID_WHITE; colour = video_index_to_colour(vid_priv, back); ret = video_fill(dev, colour); if (ret) diff --git a/boot/scene.c b/boot/scene.c index 3290a40222a..15e7a8b3387 100644 --- a/boot/scene.c +++ b/boot/scene.c @@ -330,8 +330,9 @@ static void scene_render_background(struct scene_obj *obj, bool box_only) enum colour_idx fore, back; uint inset = theme->menu_inset; + vid_priv = dev_get_uclass_priv(dev); /* draw a background for the object */ - if (CONFIG_IS_ENABLED(SYS_WHITE_ON_BLACK)) { + if (vid_priv->white_on_black) { fore = VID_DARK_GREY; back = VID_WHITE; } else { @@ -344,7 +345,6 @@ static void scene_render_background(struct scene_obj *obj, bool box_only) return; vidconsole_push_colour(cons, fore, back, &old); - vid_priv = dev_get_uclass_priv(dev); video_fill_part(dev, label_bbox.x0 - inset, label_bbox.y0 - inset, label_bbox.x1 + inset, label_bbox.y1 + inset, vid_priv->colour_fg); @@ -408,7 +408,8 @@ static int scene_obj_render(struct scene_obj *obj, bool text_mode) struct vidconsole_colour old; enum colour_idx fore, back; - if (CONFIG_IS_ENABLED(SYS_WHITE_ON_BLACK)) { + vid_priv = dev_get_uclass_priv(dev); + if (vid_priv->white_on_black) { fore = VID_BLACK; back = VID_WHITE; } else { @@ -416,7 +417,6 @@ static int scene_obj_render(struct scene_obj *obj, bool text_mode) back = VID_BLACK; } - vid_priv = dev_get_uclass_priv(dev); if (obj->flags & SCENEOF_POINT) { vidconsole_push_colour(cons, fore, back, &old); video_fill_part(dev, x - theme->menu_inset, y, diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c index 503cdb9f025..1c372bd58b7 100644 --- a/drivers/video/video-uclass.c +++ b/drivers/video/video-uclass.c @@ -345,7 +345,7 @@ void video_set_default_colors(struct udevice *dev, bool invert) struct video_priv *priv = dev_get_uclass_priv(dev); int fore, back; - if (CONFIG_IS_ENABLED(SYS_WHITE_ON_BLACK)) { + if (priv->white_on_black) { /* White is used when switching to bold, use light gray here */ fore = VID_LIGHT_GRAY; back = VID_BLACK; @@ -582,6 +582,18 @@ static void video_idle(struct cyclic_info *cyc) video_sync_all(); } +void video_set_white_on_black(struct udevice *dev, bool white_on_black) +{ + struct video_priv *priv = dev_get_uclass_priv(dev); + + if (priv->white_on_black != white_on_black) { + priv->white_on_black = white_on_black; + video_set_default_colors(dev, false); + + video_clear(dev); + } +} + /* Set up the display ready for use */ static int video_post_probe(struct udevice *dev) { @@ -624,6 +636,8 @@ static int video_post_probe(struct udevice *dev) if (IS_ENABLED(CONFIG_VIDEO_COPY) && plat->copy_base) priv->copy_fb = map_sysmem(plat->copy_base, plat->size); + priv->white_on_black = CONFIG_IS_ENABLED(SYS_WHITE_ON_BLACK); + /* Set up colors */ video_set_default_colors(dev, false); diff --git a/include/video.h b/include/video.h index 2fe2f73a865..0ec6b1ca289 100644 --- a/include/video.h +++ b/include/video.h @@ -100,6 +100,7 @@ enum video_format { * @fg_col_idx: Foreground color code (bit 3 = bold, bit 0-2 = color) * @bg_col_idx: Background color code (bit 3 = bold, bit 0-2 = color) * @last_sync: Monotonic time of last video sync + * @white_on_black: Use a black background */ struct video_priv { /* Things set up by the driver: */ @@ -131,6 +132,7 @@ struct video_priv { u8 fg_col_idx; u8 bg_col_idx; ulong last_sync; + bool white_on_black; }; /** @@ -346,6 +348,16 @@ void video_set_flush_dcache(struct udevice *dev, bool flush); */ void video_set_default_colors(struct udevice *dev, bool invert); +/** + * video_set_white_on_black() - Change the setting for white-on-black + * + * This does nothing if the setting is already the same. + * + * @dev: video device + * @white_on_black: true to use white-on-black, false for black-on-white + */ +void video_set_white_on_black(struct udevice *dev, bool white_on_black); + /** * video_default_font_height() - Get the default font height * From 7dff3de38ad927d3d84caf549cec76f4d0272425 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 2 Apr 2025 06:29:34 +1300 Subject: [PATCH 03/13] sandbox: Select white-on-black Use white on black for the expo menu as it is easier on the eyes. Signed-off-by: Simon Glass --- arch/sandbox/dts/test.dts | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index 52e9ddbf50f..7026c73bc69 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -131,6 +131,7 @@ font-size = <30>; menu-inset = <3>; menuitem-gap-y = <1>; + white-on-black; }; cedit-theme { From 0d0b2341dd419c7c56def9e23f31625da2d31168 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 2 Apr 2025 06:29:35 +1300 Subject: [PATCH 04/13] video: Add a test for font measurement Add a simple test which measures a line of text using a Truetype font. Signed-off-by: Simon Glass --- test/dm/video.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/dm/video.c b/test/dm/video.c index 929fc16d0ef..6ce1a756e25 100644 --- a/test/dm/video.c +++ b/test/dm/video.c @@ -777,3 +777,32 @@ static int dm_test_video_damage(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_video_damage, UTF_SCAN_PDATA | UTF_SCAN_FDT); + +/* Test font measurement */ +static int dm_test_font_measure(struct unit_test_state *uts) +{ + const char *test_string = "There is always much to be said for not " + "attempting more than you can do and for making a certainty of " + "what you try. But this principle, like others in life and " + "war, has its exceptions."; + struct vidconsole_bbox bbox; + struct video_priv *priv; + struct udevice *dev, *con; + + ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev)); + priv = dev_get_uclass_priv(dev); + ut_asserteq(1366, priv->xsize); + ut_asserteq(768, priv->ysize); + + /* this is using the Nimbus font with size of 18 pixels */ + ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con)); + vidconsole_position_cursor(con, 0, 0); + ut_assertok(vidconsole_measure(con, NULL, 0, test_string, &bbox)); + ut_asserteq(0, bbox.x0); + ut_asserteq(0, bbox.y0); + ut_asserteq(0x47a, bbox.x1); + ut_asserteq(0x12, bbox.y1); + + return 0; +} +DM_TEST(dm_test_font_measure, UTF_SCAN_FDT); From 236ae39fb0c571d2553271a7fa75920348f9c5eb Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 2 Apr 2025 06:29:36 +1300 Subject: [PATCH 05/13] video: Begin support for measuring multiple lines of text Update the vidconsole API so that measure() can measure multiple lines of text. This will make it easier to implement multi-line fields in expo. Tidy up the function comments while we are here. Signed-off-by: Simon Glass --- boot/scene.c | 2 +- drivers/video/console_truetype.c | 3 +- drivers/video/vidconsole-uclass.c | 7 +++-- include/video_console.h | 50 +++++++++++++++++++++++-------- test/dm/video.c | 5 +++- 5 files changed, 49 insertions(+), 18 deletions(-) diff --git a/boot/scene.c b/boot/scene.c index 15e7a8b3387..d3ae5816bef 100644 --- a/boot/scene.c +++ b/boot/scene.c @@ -298,7 +298,7 @@ int scene_obj_get_hw(struct scene *scn, uint id, int *widthp) } ret = vidconsole_measure(scn->expo->cons, txt->font_name, - txt->font_size, str, &bbox); + txt->font_size, str, &bbox, NULL); if (ret) return log_msg_ret("mea", ret); if (widthp) diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c index 980baee83cf..7b9033818d3 100644 --- a/drivers/video/console_truetype.c +++ b/drivers/video/console_truetype.c @@ -733,7 +733,8 @@ static int truetype_select_font(struct udevice *dev, const char *name, } static int truetype_measure(struct udevice *dev, const char *name, uint size, - const char *text, struct vidconsole_bbox *bbox) + const char *text, struct vidconsole_bbox *bbox, + struct alist *lines) { struct console_tt_metrics *met; stbtt_fontinfo *font; diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c index a1dfd35b7b8..4ca41dc331e 100644 --- a/drivers/video/vidconsole-uclass.c +++ b/drivers/video/vidconsole-uclass.c @@ -608,14 +608,17 @@ int vidconsole_select_font(struct udevice *dev, const char *name, uint size) } int vidconsole_measure(struct udevice *dev, const char *name, uint size, - const char *text, struct vidconsole_bbox *bbox) + const char *text, struct vidconsole_bbox *bbox, + struct alist *lines) { struct vidconsole_priv *priv = dev_get_uclass_priv(dev); struct vidconsole_ops *ops = vidconsole_get_ops(dev); int ret; if (ops->measure) { - ret = ops->measure(dev, name, size, text, bbox); + if (lines) + alist_empty(lines); + ret = ops->measure(dev, name, size, text, bbox, lines); if (ret != -ENOSYS) return ret; } diff --git a/include/video_console.h b/include/video_console.h index 13197fa4518..ee9ce3c0e37 100644 --- a/include/video_console.h +++ b/include/video_console.h @@ -6,6 +6,7 @@ #ifndef __video_console_h #define __video_console_h +#include #include struct abuf; @@ -119,6 +120,19 @@ struct vidconsole_bbox { int y1; }; +/** + * vidconsole_mline - Holds information about a line of measured text + * + * @bbox: Bounding box of the line, assuming it starts at 0,0 + * @start: String index of the first character in the line + * @len: Number of characters in the line + */ +struct vidconsole_mline { + struct vidconsole_bbox bbox; + int start; + int len; +}; + /** * struct vidconsole_ops - Video console operations * @@ -228,18 +242,23 @@ struct vidconsole_ops { int (*select_font)(struct udevice *dev, const char *name, uint size); /** - * measure() - Measure the bounds of some text + * measure() - Measure the bounding box of some text * - * @dev: Device to adjust + * @dev: Console device to use * @name: Font name to use (NULL to use default) * @size: Font size to use (0 to use default) * @text: Text to measure * @bbox: Returns bounding box of text, assuming it is positioned * at 0,0 + * @lines: If non-NULL, this must be an alist of + * struct vidconsole_mline inited by caller. A separate + * record is added for each line of text + * * Returns: 0 on success, -ENOENT if no such font */ int (*measure)(struct udevice *dev, const char *name, uint size, - const char *text, struct vidconsole_bbox *bbox); + const char *text, struct vidconsole_bbox *bbox, + struct alist *lines); /** * nominal() - Measure the expected width of a line of text @@ -320,19 +339,24 @@ int vidconsole_get_font(struct udevice *dev, int seq, */ int vidconsole_select_font(struct udevice *dev, const char *name, uint size); -/* - * vidconsole_measure() - Measuring the bounding box of some text +/** + * vidconsole_measure() - Measure the bounding box of some text * - * @dev: Console device to use - * @name: Font name, NULL for default - * @size: Font size, ignored if @name is NULL - * @text: Text to measure - * @bbox: Returns nounding box of text - * Returns: 0 if OK, -ve on error + * @dev: Device to adjust + * @name: Font name to use (NULL to use default) + * @size: Font size to use (0 to use default) + * @text: Text to measure + * @bbox: Returns bounding box of text, assuming it is positioned + * at 0,0 + * @lines: If non-NULL, this must be an alist of + * struct vidconsole_mline inited by caller. The list is emptied + * and then a separate record is added for each line of text + * + * Returns: 0 on success, -ENOENT if no such font */ int vidconsole_measure(struct udevice *dev, const char *name, uint size, - const char *text, struct vidconsole_bbox *bbox); - + const char *text, struct vidconsole_bbox *bbox, + struct alist *lines); /** * vidconsole_nominal() - Measure the expected width of a line of text * diff --git a/test/dm/video.c b/test/dm/video.c index 6ce1a756e25..cfc831b6931 100644 --- a/test/dm/video.c +++ b/test/dm/video.c @@ -788,6 +788,7 @@ static int dm_test_font_measure(struct unit_test_state *uts) struct vidconsole_bbox bbox; struct video_priv *priv; struct udevice *dev, *con; + struct alist lines; ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev)); priv = dev_get_uclass_priv(dev); @@ -797,11 +798,13 @@ static int dm_test_font_measure(struct unit_test_state *uts) /* this is using the Nimbus font with size of 18 pixels */ ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con)); vidconsole_position_cursor(con, 0, 0); - ut_assertok(vidconsole_measure(con, NULL, 0, test_string, &bbox)); + ut_assertok(vidconsole_measure(con, NULL, 0, test_string, &bbox, + &lines)); ut_asserteq(0, bbox.x0); ut_asserteq(0, bbox.y0); ut_asserteq(0x47a, bbox.x1); ut_asserteq(0x12, bbox.y1); + ut_asserteq(0, lines.count); return 0; } From a7bbc59c31f4099a8da3acccf6919b886fb590f6 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 2 Apr 2025 06:29:37 +1300 Subject: [PATCH 06/13] video: truetype: Fill in the measured line Create a measured line for the (single) line of text. Signed-off-by: Simon Glass --- drivers/video/console_truetype.c | 36 +++++++++++++++++++++++--------- test/dm/video.c | 13 +++++++++++- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c index 7b9033818d3..39d77ad9818 100644 --- a/drivers/video/console_truetype.c +++ b/drivers/video/console_truetype.c @@ -737,11 +737,13 @@ static int truetype_measure(struct udevice *dev, const char *name, uint size, struct alist *lines) { struct console_tt_metrics *met; + struct vidconsole_mline mline; + const char *s; stbtt_fontinfo *font; int lsb, advance; - const char *s; int width; - int last; + int start; + int lastch; int ret; ret = get_metrics(dev, name, size, &met); @@ -754,25 +756,39 @@ static int truetype_measure(struct udevice *dev, const char *name, uint size, font = &met->font; width = 0; - for (last = 0, s = text; *s; s++) { + bbox->y1 = 0; + start = 0; + for (lastch = 0, s = text; *s; s++) { + int neww; int ch = *s; - /* Used kerning to fine-tune the position of this character */ - if (last) - width += stbtt_GetCodepointKernAdvance(font, last, ch); - /* First get some basic metrics about this character */ stbtt_GetCodepointHMetrics(font, ch, &advance, &lsb); + neww = width + advance; - width += advance; - last = ch; + /* Use kerning to fine-tune the position of this character */ + if (lastch) + neww += stbtt_GetCodepointKernAdvance(font, lastch, ch); + lastch = ch; + + width = neww; } + /* add the line */ + mline.bbox.x0 = 0; + mline.bbox.y0 = bbox->y1; + mline.bbox.x1 = tt_ceil((double)width * met->scale); + bbox->y1 += met->font_size; + mline.bbox.y1 = bbox->y1; + mline.start = start; + mline.len = (s - text) - start; + if (lines && !alist_add(lines, mline)) + return log_msg_ret("ttM", -ENOMEM); + bbox->valid = true; bbox->x0 = 0; bbox->y0 = 0; bbox->x1 = tt_ceil((double)width * met->scale); - bbox->y1 = met->font_size; return 0; } diff --git a/test/dm/video.c b/test/dm/video.c index cfc831b6931..d3fd74a9a9a 100644 --- a/test/dm/video.c +++ b/test/dm/video.c @@ -785,6 +785,7 @@ static int dm_test_font_measure(struct unit_test_state *uts) "attempting more than you can do and for making a certainty of " "what you try. But this principle, like others in life and " "war, has its exceptions."; + const struct vidconsole_mline *line; struct vidconsole_bbox bbox; struct video_priv *priv; struct udevice *dev, *con; @@ -798,13 +799,23 @@ static int dm_test_font_measure(struct unit_test_state *uts) /* this is using the Nimbus font with size of 18 pixels */ ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con)); vidconsole_position_cursor(con, 0, 0); + alist_init_struct(&lines, struct vidconsole_mline); ut_assertok(vidconsole_measure(con, NULL, 0, test_string, &bbox, &lines)); ut_asserteq(0, bbox.x0); ut_asserteq(0, bbox.y0); ut_asserteq(0x47a, bbox.x1); ut_asserteq(0x12, bbox.y1); - ut_asserteq(0, lines.count); + ut_asserteq(1, lines.count); + + line = alist_get(&lines, 0, struct vidconsole_mline); + ut_assertnonnull(line); + ut_asserteq(0, line->bbox.x0); + ut_asserteq(0, line->bbox.y0); + ut_asserteq(0x47a, line->bbox.x1); + ut_asserteq(0x12, line->bbox.y1); + ut_asserteq(0, line->start); + ut_asserteq(strlen(test_string), line->len); return 0; } From 030e53aaaa0f050a4059d4ae6297c224c9013d88 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 2 Apr 2025 06:29:38 +1300 Subject: [PATCH 07/13] video: truetype: Support newlines in the measured string It is useful to be able to embed newline characters in the string and have the text measured into multiple lines. Add support for this. Signed-off-by: Simon Glass --- drivers/video/console_truetype.c | 25 ++++++++++++++++++++++++- include/video_console.h | 4 ++++ test/dm/video.c | 26 ++++++++++++++++++++------ 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c index 39d77ad9818..6eca5d7f543 100644 --- a/drivers/video/console_truetype.c +++ b/drivers/video/console_truetype.c @@ -771,10 +771,33 @@ static int truetype_measure(struct udevice *dev, const char *name, uint size, neww += stbtt_GetCodepointKernAdvance(font, lastch, ch); lastch = ch; + /* see if we need to start a new line */ + if (ch == '\n') { + mline.bbox.x0 = 0; + mline.bbox.y0 = bbox->y1; + mline.bbox.x1 = tt_ceil((double)width * met->scale); + bbox->y1 += met->font_size; + mline.bbox.y1 = bbox->y1; + mline.bbox.valid = true; + mline.start = start; + mline.len = (s - text) - start; + if (lines && !alist_add(lines, mline)) + return log_msg_ret("ttm", -ENOMEM); + log_debug("line x1 %d y0 %d y1 %d start %d len %d text '%.*s'\n", + mline.bbox.x1, mline.bbox.y0, mline.bbox.y1, + mline.start, mline.len, mline.len, text + mline.start); + + start = s - text; + if (ch == '\n') + start++; + lastch = 0; + neww = 0; + } + width = neww; } - /* add the line */ + /* add the final line */ mline.bbox.x0 = 0; mline.bbox.y0 = bbox->y1; mline.bbox.x1 = tt_ceil((double)width * met->scale); diff --git a/include/video_console.h b/include/video_console.h index ee9ce3c0e37..a0ee9ab62e9 100644 --- a/include/video_console.h +++ b/include/video_console.h @@ -244,6 +244,8 @@ struct vidconsole_ops { /** * measure() - Measure the bounding box of some text * + * The text can include newlines + * * @dev: Console device to use * @name: Font name to use (NULL to use default) * @size: Font size to use (0 to use default) @@ -342,6 +344,8 @@ int vidconsole_select_font(struct udevice *dev, const char *name, uint size); /** * vidconsole_measure() - Measure the bounding box of some text * + * The text can include newlines + * * @dev: Device to adjust * @name: Font name to use (NULL to use default) * @size: Font size to use (0 to use default) diff --git a/test/dm/video.c b/test/dm/video.c index d3fd74a9a9a..a3f3b046882 100644 --- a/test/dm/video.c +++ b/test/dm/video.c @@ -781,7 +781,7 @@ DM_TEST(dm_test_video_damage, UTF_SCAN_PDATA | UTF_SCAN_FDT); /* Test font measurement */ static int dm_test_font_measure(struct unit_test_state *uts) { - const char *test_string = "There is always much to be said for not " + const char *test_string = "There is always much\nto be said for not " "attempting more than you can do and for making a certainty of " "what you try. But this principle, like others in life and " "war, has its exceptions."; @@ -790,6 +790,7 @@ static int dm_test_font_measure(struct unit_test_state *uts) struct video_priv *priv; struct udevice *dev, *con; struct alist lines; + int nl; ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev)); priv = dev_get_uclass_priv(dev); @@ -804,18 +805,31 @@ static int dm_test_font_measure(struct unit_test_state *uts) &lines)); ut_asserteq(0, bbox.x0); ut_asserteq(0, bbox.y0); - ut_asserteq(0x47a, bbox.x1); - ut_asserteq(0x12, bbox.y1); - ut_asserteq(1, lines.count); + ut_asserteq(0x3ea, bbox.x1); + ut_asserteq(0x24, bbox.y1); + ut_asserteq(2, lines.count); + + nl = strchr(test_string, '\n') - test_string; line = alist_get(&lines, 0, struct vidconsole_mline); ut_assertnonnull(line); ut_asserteq(0, line->bbox.x0); ut_asserteq(0, line->bbox.y0); - ut_asserteq(0x47a, line->bbox.x1); + ut_asserteq(0x8c, line->bbox.x1); ut_asserteq(0x12, line->bbox.y1); ut_asserteq(0, line->start); - ut_asserteq(strlen(test_string), line->len); + ut_asserteq(20, line->len); + ut_asserteq(nl, line->len); + + line++; + ut_asserteq(0x0, line->bbox.x0); + ut_asserteq(0x12, line->bbox.y0); + ut_asserteq(0x3ea, line->bbox.x1); + ut_asserteq(0x24, line->bbox.y1); + ut_asserteq(21, line->start); + ut_asserteq(nl + 1, line->start); + ut_asserteq(163, line->len); + ut_asserteq(strlen(test_string + nl + 1), line->len); return 0; } From cdd095e48a3934b18a3875c658cbb90d70aa8739 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 2 Apr 2025 06:29:39 +1300 Subject: [PATCH 08/13] video: truetype: Support a limit on the width of a line Expo needs to be able to word-wrap lines so that they are displayed as the user expects. Add a limit on the width of each line and support this in the measurement algorithm. Add a log category to truetype while we are here. Signed-off-by: Simon Glass --- boot/scene.c | 2 +- drivers/video/console_truetype.c | 40 ++++++++++++++++----- drivers/video/vidconsole-uclass.c | 6 ++-- include/video_console.h | 10 +++--- test/dm/video.c | 59 ++++++++++++++++++++++++++++++- 5 files changed, 100 insertions(+), 17 deletions(-) diff --git a/boot/scene.c b/boot/scene.c index d3ae5816bef..fb82ffe768c 100644 --- a/boot/scene.c +++ b/boot/scene.c @@ -298,7 +298,7 @@ int scene_obj_get_hw(struct scene *scn, uint id, int *widthp) } ret = vidconsole_measure(scn->expo->cons, txt->font_name, - txt->font_size, str, &bbox, NULL); + txt->font_size, str, -1, &bbox, NULL); if (ret) return log_msg_ret("mea", ret); if (widthp) diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c index 6eca5d7f543..2e3e6f07112 100644 --- a/drivers/video/console_truetype.c +++ b/drivers/video/console_truetype.c @@ -3,6 +3,8 @@ * Copyright (c) 2016 Google, Inc */ +#define LOG_CATEGORY UCLASS_VIDEO + #include #include #include @@ -733,16 +735,17 @@ static int truetype_select_font(struct udevice *dev, const char *name, } static int truetype_measure(struct udevice *dev, const char *name, uint size, - const char *text, struct vidconsole_bbox *bbox, - struct alist *lines) + const char *text, int pixel_limit, + struct vidconsole_bbox *bbox, struct alist *lines) { struct console_tt_metrics *met; struct vidconsole_mline mline; - const char *s; + const char *s, *last_space; + int width, last_width; stbtt_fontinfo *font; int lsb, advance; - int width; int start; + int limit; int lastch; int ret; @@ -754,14 +757,30 @@ static int truetype_measure(struct udevice *dev, const char *name, uint size, if (!*text) return 0; + limit = -1; + if (pixel_limit != -1) + limit = tt_ceil((double)pixel_limit / met->scale); + font = &met->font; width = 0; bbox->y1 = 0; + bbox->x1 = 0; start = 0; + last_space = NULL; + last_width = 0; for (lastch = 0, s = text; *s; s++) { int neww; int ch = *s; + if (ch == ' ') { + /* + * store the position and width so we can use it again + * if we need to word-wrap + */ + last_space = s; + last_width = width; + } + /* First get some basic metrics about this character */ stbtt_GetCodepointHMetrics(font, ch, &advance, &lsb); neww = width + advance; @@ -772,10 +791,16 @@ static int truetype_measure(struct udevice *dev, const char *name, uint size, lastch = ch; /* see if we need to start a new line */ - if (ch == '\n') { + if (ch == '\n' || (limit != -1 && neww >= limit)) { + if (ch != '\n' && last_space) { + s = last_space; + width = last_width; + } + last_space = NULL; mline.bbox.x0 = 0; mline.bbox.y0 = bbox->y1; mline.bbox.x1 = tt_ceil((double)width * met->scale); + bbox->x1 = max(bbox->x1, mline.bbox.x1); bbox->y1 += met->font_size; mline.bbox.y1 = bbox->y1; mline.bbox.valid = true; @@ -788,8 +813,7 @@ static int truetype_measure(struct udevice *dev, const char *name, uint size, mline.start, mline.len, mline.len, text + mline.start); start = s - text; - if (ch == '\n') - start++; + start++; lastch = 0; neww = 0; } @@ -811,7 +835,7 @@ static int truetype_measure(struct udevice *dev, const char *name, uint size, bbox->valid = true; bbox->x0 = 0; bbox->y0 = 0; - bbox->x1 = tt_ceil((double)width * met->scale); + bbox->x1 = max(bbox->x1, mline.bbox.x1); return 0; } diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c index 4ca41dc331e..3259bd2ef7d 100644 --- a/drivers/video/vidconsole-uclass.c +++ b/drivers/video/vidconsole-uclass.c @@ -608,8 +608,8 @@ int vidconsole_select_font(struct udevice *dev, const char *name, uint size) } int vidconsole_measure(struct udevice *dev, const char *name, uint size, - const char *text, struct vidconsole_bbox *bbox, - struct alist *lines) + const char *text, int limit, + struct vidconsole_bbox *bbox, struct alist *lines) { struct vidconsole_priv *priv = dev_get_uclass_priv(dev); struct vidconsole_ops *ops = vidconsole_get_ops(dev); @@ -618,7 +618,7 @@ int vidconsole_measure(struct udevice *dev, const char *name, uint size, if (ops->measure) { if (lines) alist_empty(lines); - ret = ops->measure(dev, name, size, text, bbox, lines); + ret = ops->measure(dev, name, size, text, limit, bbox, lines); if (ret != -ENOSYS) return ret; } diff --git a/include/video_console.h b/include/video_console.h index a0ee9ab62e9..1bb265dc9da 100644 --- a/include/video_console.h +++ b/include/video_console.h @@ -250,6 +250,7 @@ struct vidconsole_ops { * @name: Font name to use (NULL to use default) * @size: Font size to use (0 to use default) * @text: Text to measure + * @limit: Width limit for each line, or -1 if none * @bbox: Returns bounding box of text, assuming it is positioned * at 0,0 * @lines: If non-NULL, this must be an alist of @@ -259,8 +260,8 @@ struct vidconsole_ops { * Returns: 0 on success, -ENOENT if no such font */ int (*measure)(struct udevice *dev, const char *name, uint size, - const char *text, struct vidconsole_bbox *bbox, - struct alist *lines); + const char *text, int limit, + struct vidconsole_bbox *bbox, struct alist *lines); /** * nominal() - Measure the expected width of a line of text @@ -350,6 +351,7 @@ int vidconsole_select_font(struct udevice *dev, const char *name, uint size); * @name: Font name to use (NULL to use default) * @size: Font size to use (0 to use default) * @text: Text to measure + * @limit: Width limit for each line, or -1 if none * @bbox: Returns bounding box of text, assuming it is positioned * at 0,0 * @lines: If non-NULL, this must be an alist of @@ -359,8 +361,8 @@ int vidconsole_select_font(struct udevice *dev, const char *name, uint size); * Returns: 0 on success, -ENOENT if no such font */ int vidconsole_measure(struct udevice *dev, const char *name, uint size, - const char *text, struct vidconsole_bbox *bbox, - struct alist *lines); + const char *text, int limit, + struct vidconsole_bbox *bbox, struct alist *lines); /** * vidconsole_nominal() - Measure the expected width of a line of text * diff --git a/test/dm/video.c b/test/dm/video.c index a3f3b046882..c1b2a502b47 100644 --- a/test/dm/video.c +++ b/test/dm/video.c @@ -789,6 +789,7 @@ static int dm_test_font_measure(struct unit_test_state *uts) struct vidconsole_bbox bbox; struct video_priv *priv; struct udevice *dev, *con; + const int limit = 0x320; struct alist lines; int nl; @@ -801,7 +802,7 @@ static int dm_test_font_measure(struct unit_test_state *uts) ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con)); vidconsole_position_cursor(con, 0, 0); alist_init_struct(&lines, struct vidconsole_mline); - ut_assertok(vidconsole_measure(con, NULL, 0, test_string, &bbox, + ut_assertok(vidconsole_measure(con, NULL, 0, test_string, -1, &bbox, &lines)); ut_asserteq(0, bbox.x0); ut_asserteq(0, bbox.y0); @@ -831,6 +832,62 @@ static int dm_test_font_measure(struct unit_test_state *uts) ut_asserteq(163, line->len); ut_asserteq(strlen(test_string + nl + 1), line->len); + /* now use a limit on the width */ + ut_assertok(vidconsole_measure(con, NULL, 0, test_string, limit, &bbox, + &lines)); + ut_asserteq(0, bbox.x0); + ut_asserteq(0, bbox.y0); + ut_asserteq(0x31e, bbox.x1); + ut_asserteq(0x36, bbox.y1); + ut_asserteq(3, lines.count); + + nl = strchr(test_string, '\n') - test_string; + + line = alist_get(&lines, 0, struct vidconsole_mline); + ut_assertnonnull(line); + ut_asserteq(0, line->bbox.x0); + ut_asserteq(0, line->bbox.y0); + ut_asserteq(0x8c, line->bbox.x1); + ut_asserteq(0x12, line->bbox.y1); + ut_asserteq(0, line->start); + ut_asserteq(20, line->len); + ut_asserteq(nl, line->len); + printf("line0 '%.*s'\n", line->len, test_string + line->start); + ut_asserteq_strn("There is always much", + test_string + line->start); + + line++; + ut_asserteq(0x0, line->bbox.x0); + ut_asserteq(0x12, line->bbox.y0); + ut_asserteq(0x31e, line->bbox.x1); + ut_asserteq(0x24, line->bbox.y1); + ut_asserteq(21, line->start); + ut_asserteq(nl + 1, line->start); + ut_asserteq(129, line->len); + printf("line1 '%.*s'\n", line->len, test_string + line->start); + ut_asserteq_strn("to be said for not attempting more than you can do " + "and for making a certainty of what you try. But this " + "principle, like others in", + test_string + line->start); + + line++; + ut_asserteq(0x0, line->bbox.x0); + ut_asserteq(0x24, line->bbox.y0); + ut_asserteq(0xc8, line->bbox.x1); + ut_asserteq(0x36, line->bbox.y1); + ut_asserteq(21 + 130, line->start); + ut_asserteq(33, line->len); + printf("line2 '%.*s'\n", line->len, test_string + line->start); + ut_asserteq_strn("life and war, has its exceptions.", + test_string + line->start); + + /* + * all characters should be accounted for, except the newline and the + * space which is consumed in the wordwrap + */ + ut_asserteq(strlen(test_string) - 2, + line[-2].len + line[-1].len + line->len); + return 0; } DM_TEST(dm_test_font_measure, UTF_SCAN_FDT); From 8301239ad05b7b786e96c6a982c92ed06a4944c6 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 2 Apr 2025 06:29:40 +1300 Subject: [PATCH 09/13] video: Add a way to write a partial string to the console When writing multiple lines of text we need to be able to control which text goes on each line. Add a new vidconsole_put_stringn() function to help with this. Signed-off-by: Simon Glass --- drivers/video/vidconsole-uclass.c | 13 ++++++++++--- include/video_console.h | 17 +++++++++++++++++ test/dm/video.c | 3 ++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c index 3259bd2ef7d..fa329bd1b37 100644 --- a/drivers/video/vidconsole-uclass.c +++ b/drivers/video/vidconsole-uclass.c @@ -508,12 +508,14 @@ int vidconsole_put_char(struct udevice *dev, char ch) return 0; } -int vidconsole_put_string(struct udevice *dev, const char *str) +int vidconsole_put_stringn(struct udevice *dev, const char *str, int maxlen) { - const char *s; + const char *s, *end = NULL; int ret; - for (s = str; *s; s++) { + if (maxlen != -1) + end = str + maxlen; + for (s = str; *s && (maxlen == -1 || s < end); s++) { ret = vidconsole_put_char(dev, *s); if (ret) return ret; @@ -522,6 +524,11 @@ int vidconsole_put_string(struct udevice *dev, const char *str) return 0; } +int vidconsole_put_string(struct udevice *dev, const char *str) +{ + return vidconsole_put_stringn(dev, str, -1); +} + static void vidconsole_putc(struct stdio_dev *sdev, const char ch) { struct udevice *dev = sdev->priv; diff --git a/include/video_console.h b/include/video_console.h index 1bb265dc9da..e4fc776e2d3 100644 --- a/include/video_console.h +++ b/include/video_console.h @@ -499,6 +499,23 @@ int vidconsole_entry_start(struct udevice *dev); */ int vidconsole_put_char(struct udevice *dev, char ch); +/** + * vidconsole_put_stringn() - Output part of a string to the current console pos + * + * Outputs part of a string to the console and advances the cursor. This + * function handles wrapping to new lines and scrolling the console. Special + * characters are handled also: \n, \r, \b and \t. + * + * The device always starts with the cursor at position 0,0 (top left). It + * can be adjusted manually using vidconsole_position_cursor(). + * + * @dev: Device to adjust + * @str: String to write + * @maxlen: Maximum chars to output, or -1 for all + * Return: 0 if OK, -ve on error + */ +int vidconsole_put_stringn(struct udevice *dev, const char *str, int maxlen); + /** * vidconsole_put_string() - Output a string to the current console position * diff --git a/test/dm/video.c b/test/dm/video.c index c1b2a502b47..a9b2482631f 100644 --- a/test/dm/video.c +++ b/test/dm/video.c @@ -607,7 +607,8 @@ static int dm_test_video_truetype(struct unit_test_state *uts) ut_assertok(video_get_nologo(uts, &dev)); ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con)); vidconsole_put_string(con, test_string); - ut_asserteq(12174, compress_frame_buffer(uts, dev, false)); + vidconsole_put_stringn(con, test_string, 30); + ut_asserteq(13184, compress_frame_buffer(uts, dev, false)); ut_assertok(check_copy_frame_buffer(uts, dev)); return 0; From 7320a2cb9439c8bab3b8612ec37406cad5bb646c Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 2 Apr 2025 06:29:41 +1300 Subject: [PATCH 10/13] test: video: Export the video-checking functions We want to check the display contents in expo tests, so move the two needed functions to a new header file. Rename them to have a video_ prefix. Signed-off-by: Simon Glass --- include/test/video.h | 45 +++++++++++ test/dm/video.c | 179 ++++++++++++++++++------------------------- 2 files changed, 121 insertions(+), 103 deletions(-) create mode 100644 include/test/video.h diff --git a/include/test/video.h b/include/test/video.h new file mode 100644 index 00000000000..000fd708c86 --- /dev/null +++ b/include/test/video.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2013 Google, Inc. + */ + +#ifndef __TEST_VIDEO_H +#define __TEST_VIDEO_H + +#include + +struct udevice; +struct unit_test_state; + +/** + * video_compress_fb() - Compress the frame buffer and return its size + * + * We want to write tests which perform operations on the video console and + * check that the frame buffer ends up with the correct contents. But it is + * painful to store 'known good' images for comparison with the frame + * buffer. As an alternative, we can compress the frame buffer and check the + * size of the compressed data. This provides a pretty good level of + * certainty and the resulting tests need only check a single value. + * + * @uts: Test state + * @dev: Video device + * @use_copy: Use copy frame buffer if available + * Return: compressed size of the frame buffer, or -ve on error + */ +int video_compress_fb(struct unit_test_state *uts, struct udevice *dev, + bool use_copy); + +/** + * check_copy_frame_buffer() - Compare main frame buffer to copy + * + * If the copy frame buffer is enabled, this compares it to the main + * frame buffer. Normally they should have the same contents after a + * sync. + * + * @uts: Test state + * @dev: Video device + * Return: 0, or -ve on error + */ +int video_check_copy_fb(struct unit_test_state *uts, struct udevice *dev); + +#endif diff --git a/test/dm/video.c b/test/dm/video.c index a9b2482631f..737ab915f41 100644 --- a/test/dm/video.c +++ b/test/dm/video.c @@ -19,6 +19,7 @@ #include #include #include +#include /* * These tests use the standard sandbox frame buffer, the resolution of which @@ -44,24 +45,8 @@ static int dm_test_video_base(struct unit_test_state *uts) } DM_TEST(dm_test_video_base, UTF_SCAN_PDATA | UTF_SCAN_FDT); -/** - * compress_frame_buffer() - Compress the frame buffer and return its size - * - * We want to write tests which perform operations on the video console and - * check that the frame buffer ends up with the correct contents. But it is - * painful to store 'known good' images for comparison with the frame - * buffer. As an alternative, we can compress the frame buffer and check the - * size of the compressed data. This provides a pretty good level of - * certainty and the resulting tests need only check a single value. - * - * @uts: Test state - * @dev: Video device - * @use_copy: Use copy frame buffer if available - * Return: compressed size of the frame buffer, or -ve on error - */ -static int compress_frame_buffer(struct unit_test_state *uts, - struct udevice *dev, - bool use_copy) +int video_compress_fb(struct unit_test_state *uts, struct udevice *dev, + bool use_copy) { struct video_priv *priv = dev_get_uclass_priv(dev); uint destlen; @@ -86,19 +71,7 @@ static int compress_frame_buffer(struct unit_test_state *uts, return destlen; } -/** - * check_copy_frame_buffer() - Compare main frame buffer to copy - * - * If the copy frame buffer is enabled, this compares it to the main - * frame buffer. Normally they should have the same contents after a - * sync. - * - * @uts: Test state - * @dev: Video device - * Return: 0, or -ve on error - */ -static int check_copy_frame_buffer(struct unit_test_state *uts, - struct udevice *dev) +int video_check_copy_fb(struct unit_test_state *uts, struct udevice *dev) { struct video_priv *priv = dev_get_uclass_priv(dev); @@ -174,31 +147,31 @@ static int dm_test_video_text(struct unit_test_state *uts) ut_assertok(video_get_nologo(uts, &dev)); ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con)); ut_assertok(vidconsole_select_font(con, "8x16", 0)); - ut_asserteq(46, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(46, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con)); vidconsole_putc_xy(con, 0, 0, 'a'); - ut_asserteq(79, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(79, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); vidconsole_putc_xy(con, 0, 0, ' '); - ut_asserteq(46, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(46, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); for (i = 0; i < 20; i++) vidconsole_putc_xy(con, VID_TO_POS(i * 8), 0, ' ' + i); - ut_asserteq(273, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(273, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); vidconsole_set_row(con, 0, WHITE); - ut_asserteq(46, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(46, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); for (i = 0; i < 20; i++) vidconsole_putc_xy(con, VID_TO_POS(i * 8), 0, ' ' + i); - ut_asserteq(273, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(273, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); return 0; } @@ -216,31 +189,31 @@ static int dm_test_video_text_12x22(struct unit_test_state *uts) ut_assertok(video_get_nologo(uts, &dev)); ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con)); ut_assertok(vidconsole_select_font(con, "12x22", 0)); - ut_asserteq(46, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(46, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con)); vidconsole_putc_xy(con, 0, 0, 'a'); - ut_asserteq(89, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(89, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); vidconsole_putc_xy(con, 0, 0, ' '); - ut_asserteq(46, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(46, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); for (i = 0; i < 20; i++) vidconsole_putc_xy(con, VID_TO_POS(i * 8), 0, ' ' + i); - ut_asserteq(363, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(363, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); vidconsole_set_row(con, 0, WHITE); - ut_asserteq(46, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(46, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); for (i = 0; i < 20; i++) vidconsole_putc_xy(con, VID_TO_POS(i * 8), 0, ' ' + i); - ut_asserteq(363, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(363, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); return 0; } @@ -257,8 +230,8 @@ static int dm_test_video_chars(struct unit_test_state *uts) ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con)); ut_assertok(vidconsole_select_font(con, "8x16", 0)); vidconsole_put_string(con, test_string); - ut_asserteq(466, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(466, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); return 0; } @@ -279,24 +252,24 @@ static int dm_test_video_ansi(struct unit_test_state *uts) /* reference clear: */ video_clear(con->parent); video_sync(con->parent, false); - ut_asserteq(46, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(46, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); /* test clear escape sequence: [2J */ vidconsole_put_string(con, "A\tB\tC"ANSI_ESC"[2J"); - ut_asserteq(46, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(46, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); /* test set-cursor: [%d;%df */ vidconsole_put_string(con, "abc"ANSI_ESC"[2;2fab"ANSI_ESC"[4;4fcd"); - ut_asserteq(143, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(143, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); /* test colors (30-37 fg color, 40-47 bg color) */ vidconsole_put_string(con, ANSI_ESC"[30;41mfoo"); /* black on red */ vidconsole_put_string(con, ANSI_ESC"[33;44mbar"); /* yellow on blue */ - ut_asserteq(272, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(272, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); return 0; } @@ -328,28 +301,28 @@ static int check_vidconsole_output(struct unit_test_state *uts, int rot, ut_assertok(video_get_nologo(uts, &dev)); ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con)); ut_assertok(vidconsole_select_font(con, "8x16", 0)); - ut_asserteq(46, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(46, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); /* Check display wrap */ for (i = 0; i < 120; i++) vidconsole_put_char(con, 'A' + i % 50); - ut_asserteq(wrap_size, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(wrap_size, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); /* Check display scrolling */ for (i = 0; i < SCROLL_LINES; i++) { vidconsole_put_char(con, 'A' + i % 50); vidconsole_put_char(con, '\n'); } - ut_asserteq(scroll_size, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(scroll_size, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); /* If we scroll enough, the screen becomes blank again */ for (i = 0; i < SCROLL_LINES; i++) vidconsole_put_char(con, '\n'); - ut_asserteq(46, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(46, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); return 0; } @@ -423,8 +396,8 @@ static int dm_test_video_bmp(struct unit_test_state *uts) ut_assertok(read_file(uts, "tools/logos/denx.bmp", &addr)); ut_assertok(video_bmp_display(dev, addr, 0, 0, false)); - ut_asserteq(1368, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(1368, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); return 0; } @@ -443,8 +416,8 @@ static int dm_test_video_bmp8(struct unit_test_state *uts) ut_assertok(read_file(uts, "tools/logos/denx.bmp", &addr)); ut_assertok(video_bmp_display(dev, addr, 0, 0, false)); - ut_asserteq(1247, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(1247, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); return 0; } @@ -467,8 +440,8 @@ static int dm_test_video_bmp16(struct unit_test_state *uts) &src_len)); ut_assertok(video_bmp_display(dev, dst, 0, 0, false)); - ut_asserteq(3700, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(3700, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); return 0; } @@ -491,8 +464,8 @@ static int dm_test_video_bmp24(struct unit_test_state *uts) &src_len)); ut_assertok(video_bmp_display(dev, dst, 0, 0, false)); - ut_asserteq(3656, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(3656, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); return 0; } @@ -515,8 +488,8 @@ static int dm_test_video_bmp24_32(struct unit_test_state *uts) &src_len)); ut_assertok(video_bmp_display(dev, dst, 0, 0, false)); - ut_asserteq(6827, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(6827, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); return 0; } @@ -534,8 +507,8 @@ static int dm_test_video_bmp32(struct unit_test_state *uts) ut_assertok(read_file(uts, "tools/logos/denx.bmp", &addr)); ut_assertok(video_bmp_display(dev, addr, 0, 0, false)); - ut_asserteq(2024, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(2024, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); return 0; } @@ -551,8 +524,8 @@ static int dm_test_video_bmp_comp(struct unit_test_state *uts) ut_assertok(read_file(uts, "tools/logos/denx-comp.bmp", &addr)); ut_assertok(video_bmp_display(dev, addr, 0, 0, false)); - ut_asserteq(1368, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(1368, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); return 0; } @@ -571,8 +544,8 @@ static int dm_test_video_comp_bmp32(struct unit_test_state *uts) ut_assertok(read_file(uts, "tools/logos/denx.bmp", &addr)); ut_assertok(video_bmp_display(dev, addr, 0, 0, false)); - ut_asserteq(2024, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(2024, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); return 0; } @@ -591,8 +564,8 @@ static int dm_test_video_comp_bmp8(struct unit_test_state *uts) ut_assertok(read_file(uts, "tools/logos/denx.bmp", &addr)); ut_assertok(video_bmp_display(dev, addr, 0, 0, false)); - ut_asserteq(1247, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(1247, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); return 0; } @@ -608,8 +581,8 @@ static int dm_test_video_truetype(struct unit_test_state *uts) ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con)); vidconsole_put_string(con, test_string); vidconsole_put_stringn(con, test_string, 30); - ut_asserteq(13184, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(13184, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); return 0; } @@ -630,8 +603,8 @@ static int dm_test_video_truetype_scroll(struct unit_test_state *uts) ut_assertok(video_get_nologo(uts, &dev)); ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con)); vidconsole_put_string(con, test_string); - ut_asserteq(34287, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(34287, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); return 0; } @@ -652,8 +625,8 @@ static int dm_test_video_truetype_bs(struct unit_test_state *uts) ut_assertok(video_get_nologo(uts, &dev)); ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con)); vidconsole_put_string(con, test_string); - ut_asserteq(29471, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(29471, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); return 0; } @@ -691,8 +664,8 @@ static int dm_test_video_copy(struct unit_test_state *uts) vidconsole_put_string(con, test_string); vidconsole_put_string(con, test_string); - ut_asserteq(6678, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(6678, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); /* * Secretly clear the hardware frame buffer, but in a different @@ -716,8 +689,8 @@ static int dm_test_video_copy(struct unit_test_state *uts) vidconsole_put_string(con, test_string); vidconsole_put_string(con, test_string); video_sync(dev, true); - ut_asserteq(7589, compress_frame_buffer(uts, dev, false)); - ut_asserteq(7704, compress_frame_buffer(uts, dev, true)); + ut_asserteq(7589, video_compress_fb(uts, dev, false)); + ut_asserteq(7704, video_compress_fb(uts, dev, true)); return 0; } @@ -772,8 +745,8 @@ static int dm_test_video_damage(struct unit_test_state *uts) ut_asserteq(0, priv->damage.xend); ut_asserteq(0, priv->damage.yend); - ut_asserteq(7339, compress_frame_buffer(uts, dev, false)); - ut_assertok(check_copy_frame_buffer(uts, dev)); + ut_asserteq(7339, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); return 0; } From cb32266d4aeca4a730c1f8b85c981a8793d768c4 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 2 Apr 2025 06:29:42 +1300 Subject: [PATCH 11/13] video: Allow console output to be silenced When using expo we want to be able to control the information on the display and avoid other messages (such as USB scanning) appearing. Add a 'quiet' flag for the console, to help with this. The test is a little messy since stdio is still using the original vidconsole create on start-up. So take care to use the same. Signed-off-by: Simon Glass --- drivers/video/vidconsole-uclass.c | 13 +++++++++++ include/video_console.h | 10 +++++++++ test/dm/video.c | 37 +++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c index fa329bd1b37..6ba62ec348e 100644 --- a/drivers/video/vidconsole-uclass.c +++ b/drivers/video/vidconsole-uclass.c @@ -532,8 +532,11 @@ int vidconsole_put_string(struct udevice *dev, const char *str) static void vidconsole_putc(struct stdio_dev *sdev, const char ch) { struct udevice *dev = sdev->priv; + struct vidconsole_priv *priv = dev_get_uclass_priv(dev); int ret; + if (priv->quiet) + return; ret = vidconsole_put_char(dev, ch); if (ret) { #ifdef DEBUG @@ -551,8 +554,11 @@ static void vidconsole_putc(struct stdio_dev *sdev, const char ch) static void vidconsole_puts(struct stdio_dev *sdev, const char *s) { struct udevice *dev = sdev->priv; + struct vidconsole_priv *priv = dev_get_uclass_priv(dev); int ret; + if (priv->quiet) + return; ret = vidconsole_put_string(dev, s); if (ret) { #ifdef DEBUG @@ -794,3 +800,10 @@ void vidconsole_position_cursor(struct udevice *dev, unsigned col, unsigned row) y = min_t(short, row * priv->y_charsize, vid_priv->ysize - 1); vidconsole_set_cursor_pos(dev, x, y); } + +void vidconsole_set_quiet(struct udevice *dev, bool quiet) +{ + struct vidconsole_priv *priv = dev_get_uclass_priv(dev); + + priv->quiet = quiet; +} diff --git a/include/video_console.h b/include/video_console.h index e4fc776e2d3..8f3f58f3aa9 100644 --- a/include/video_console.h +++ b/include/video_console.h @@ -53,6 +53,7 @@ enum { * @row_saved: Saved Y position in pixels (0=top) * @escape_buf: Buffer to accumulate escape sequence * @utf8_buf: Buffer to accumulate UTF-8 byte sequence + * @quiet: Suppress all output from stdio */ struct vidconsole_priv { struct stdio_dev sdev; @@ -77,6 +78,7 @@ struct vidconsole_priv { int col_saved; char escape_buf[32]; char utf8_buf[5]; + bool quiet; }; /** @@ -584,4 +586,12 @@ void vidconsole_list_fonts(struct udevice *dev); */ int vidconsole_get_font_size(struct udevice *dev, const char **name, uint *sizep); +/** + * vidconsole_set_quiet() - Select whether the console should output stdio + * + * @dev: vidconsole device + * @quiet: true to suppress stdout/stderr output, false to enable it + */ +void vidconsole_set_quiet(struct udevice *dev, bool quiet); + #endif diff --git a/test/dm/video.c b/test/dm/video.c index 737ab915f41..dd06b2f58e8 100644 --- a/test/dm/video.c +++ b/test/dm/video.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -865,3 +866,39 @@ static int dm_test_font_measure(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_font_measure, UTF_SCAN_FDT); + +/* Test silencing the video console */ +static int dm_test_video_silence(struct unit_test_state *uts) +{ + struct udevice *dev, *con; + struct stdio_dev *sdev; + + ut_assertok(uclass_first_device_err(UCLASS_VIDEO, &dev)); + + /* + * use the old console device from before when dm_test_pre_run() was + * called, since that is what is in stdio / console + */ + sdev = stdio_get_by_name("vidconsole"); + ut_assertnonnull(sdev); + con = sdev->priv; + ut_assertok(vidconsole_clear_and_reset(con)); + ut_unsilence_console(uts); + + printf("message 1: console\n"); + vidconsole_put_string(con, "message 1: video\n"); + + vidconsole_set_quiet(con, true); + printf("second message: console\n"); + vidconsole_put_string(con, "second message: video\n"); + + vidconsole_set_quiet(con, false); + printf("final message: console\n"); + vidconsole_put_string(con, "final message: video\n"); + + ut_asserteq(3892, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); + + return 0; +} +DM_TEST(dm_test_video_silence, UTF_SCAN_FDT); From f94f1f4b8ce62855693b4b48356f7e8d229b3fa4 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 2 Apr 2025 06:29:43 +1300 Subject: [PATCH 12/13] video: Add a function to draw a rectangle Provide a way to draw an unfilled box of a certain width. This is useful for grouping menu items together. Add a comment showing how to see the copy-framebuffer, for testing. Signed-off-by: Simon Glass --- drivers/video/video-uclass.c | 36 ++++++++++++++++++++++++++++++++++++ include/video.h | 19 ++++++++++++++++++- test/dm/video.c | 21 +++++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c index 1c372bd58b7..53641fc28b6 100644 --- a/drivers/video/video-uclass.c +++ b/drivers/video/video-uclass.c @@ -26,6 +26,7 @@ #ifdef CONFIG_SANDBOX #include #endif +#include "vidconsole_internal.h" /* * Theory of operation: @@ -216,6 +217,40 @@ int video_fill_part(struct udevice *dev, int xstart, int ystart, int xend, return 0; } +int video_draw_box(struct udevice *dev, int x0, int y0, int x1, int y1, + int width, u32 colour) +{ + struct video_priv *priv = dev_get_uclass_priv(dev); + int pbytes = VNBYTES(priv->bpix); + void *start, *line; + int pixels = x1 - x0; + int row; + + start = priv->fb + y0 * priv->line_length; + start += x0 * pbytes; + line = start; + for (row = y0; row < y1; row++) { + void *ptr = line; + int i; + + for (i = 0; i < width; i++) + fill_pixel_and_goto_next(&ptr, colour, pbytes, pbytes); + if (row < y0 + width || row >= y1 - width) { + for (i = 0; i < pixels - width * 2; i++) + fill_pixel_and_goto_next(&ptr, colour, pbytes, + pbytes); + } else { + ptr += (pixels - width * 2) * pbytes; + } + for (i = 0; i < width; i++) + fill_pixel_and_goto_next(&ptr, colour, pbytes, pbytes); + line += priv->line_length; + } + video_damage(dev, x0, y0, x1 - x0, y1 - y0); + + return 0; +} + int video_reserve_from_bloblist(struct video_handoff *ho) { if (!ho->fb || ho->size == 0) @@ -481,6 +516,7 @@ int video_sync(struct udevice *vid, bool force) video_flush_dcache(vid, true); #if defined(CONFIG_VIDEO_SANDBOX_SDL) + /* to see the copy framebuffer, use priv->copy_fb */ sandbox_sdl_sync(priv->fb); #endif priv->last_sync = get_timer(0); diff --git a/include/video.h b/include/video.h index 0ec6b1ca289..9ea6b676463 100644 --- a/include/video.h +++ b/include/video.h @@ -249,7 +249,7 @@ int video_fill(struct udevice *dev, u32 colour); /** * video_fill_part() - Erase a region * - * Erase a rectangle of the display within the given bounds. + * Erase a rectangle on the display within the given bounds * * @dev: Device to update * @xstart: X start position in pixels from the left @@ -262,6 +262,23 @@ int video_fill(struct udevice *dev, u32 colour); int video_fill_part(struct udevice *dev, int xstart, int ystart, int xend, int yend, u32 colour); +/** + * video_draw_box() - Draw a box + * + * Draw a rectangle on the display within the given bounds + * + * @dev: Device to update + * @x0: X start position in pixels from the left + * @y0: Y start position in pixels from the top + * @x1: X end position in pixels from the left + * @y1: Y end position in pixels from the top + * @width: width in pixels + * @colour: Value to write + * Return: 0 if OK, -ENOSYS if the display depth is not supported + */ +int video_draw_box(struct udevice *dev, int x0, int y0, int x1, int y1, + int width, u32 colour); + /** * video_sync() - Sync a device's frame buffer with its hardware * diff --git a/test/dm/video.c b/test/dm/video.c index dd06b2f58e8..ecf74605b5c 100644 --- a/test/dm/video.c +++ b/test/dm/video.c @@ -902,3 +902,24 @@ static int dm_test_video_silence(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_video_silence, UTF_SCAN_FDT); + +/* test drawing a box */ +static int dm_test_video_box(struct unit_test_state *uts) +{ + struct video_priv *priv; + struct udevice *dev; + + ut_assertok(video_get_nologo(uts, &dev)); + priv = dev_get_uclass_priv(dev); + video_draw_box(dev, 100, 100, 200, 200, 3, + video_index_to_colour(priv, VID_LIGHT_BLUE)); + video_draw_box(dev, 300, 100, 400, 200, 1, + video_index_to_colour(priv, VID_MAGENTA)); + video_draw_box(dev, 500, 100, 600, 200, 20, + video_index_to_colour(priv, VID_LIGHT_RED)); + ut_asserteq(133, video_compress_fb(uts, dev, false)); + ut_assertok(video_check_copy_fb(uts, dev)); + + return 0; +} +DM_TEST(dm_test_video_box, UTF_SCAN_FDT); From 7703cfe025cbbb2277498483304b4db958521d9e Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 2 Apr 2025 06:29:44 +1300 Subject: [PATCH 13/13] vidconsole: Avoid kerning against an unrelated character When the cursor position changes, kerning should not be used for the next character, since it can make the first displayed character shuffle left or right a bit. Clear the kern character when setting the position. Signed-off-by: Simon Glass --- drivers/video/console_truetype.c | 2 ++ drivers/video/vidconsole-uclass.c | 3 +++ 2 files changed, 5 insertions(+) diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c index 2e3e6f07112..6d2c2c2e177 100644 --- a/drivers/video/console_truetype.c +++ b/drivers/video/console_truetype.c @@ -490,10 +490,12 @@ static int console_truetype_backspace(struct udevice *dev) static int console_truetype_entry_start(struct udevice *dev) { + struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev); struct console_tt_priv *priv = dev_get_priv(dev); /* A new input line has start, so clear our history */ priv->pos_ptr = 0; + vc_priv->last_ch = 0; return 0; } diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c index 6ba62ec348e..f1b2d61bd8f 100644 --- a/drivers/video/vidconsole-uclass.c +++ b/drivers/video/vidconsole-uclass.c @@ -127,6 +127,9 @@ void vidconsole_set_cursor_pos(struct udevice *dev, int x, int y) priv->xcur_frac = VID_TO_POS(x); priv->xstart_frac = priv->xcur_frac; priv->ycur = y; + + /* make sure not to kern against the previous character */ + priv->last_ch = 0; vidconsole_entry_start(dev); }