[U-Boot] [PATCH v2 09/19] video: Add a console driver that uses TrueType fonts

Anatolij Gustschin
2016-01-23T00:21:50Z

From: Simon Glass <sjg@chromium.org>

The existing 8x16 font is adequate for most purposes. It is small and fast.
However for boot screens where information must be presented to the user,
the console font is not ideal. Common requirements are larger and
better-looking fonts.

This console driver can use TrueType fonts built into U-Boot, and render
them at any size. This can be used in scripts to place text as needed on
the display.

This driver is not really designed to operate with the command line. Much
of U-Boot expects a fixed-width font. But to keep things working correctly,
rudimentary support for the console is provided. The main missing feature is
support for command-line editing.

Signed-off-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Anatolij Gustschin <agust@denx.de>
---
Changes in v2: rebased

drivers/video/Kconfig | 24 ++
drivers/video/Makefile | 1 +
drivers/video/console_truetype.c | 533 ++++++++++++++++++++++++++++++++++++++
drivers/video/fonts/Kconfig | 7 +
drivers/video/fonts/Makefile | 6 +
drivers/video/video-uclass.c | 11 +-
6 files changed, 580 insertions(+), 2 deletions(-)
create mode 100644 drivers/video/console_truetype.c
create mode 100644 drivers/video/fonts/Kconfig
create mode 100644 drivers/video/fonts/Makefile

diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index f06ecfe..279c5f9 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -67,6 +67,30 @@ config CONSOLE_ROTATION
struct video_priv: 0=unrotated, 1=90 degrees clockwise, 2=180
degrees, 3=270 degrees.

+config CONSOLE_TRUETYPE
+ bool "Support a console that uses TrueType fonts"
+ depends on DM_VIDEO
+ help
+ TrueTrype fonts can provide outline-drawing capability rather than
+ needing to provide a bitmap for each font and size that is needed.
+ With this option you can adjust the text size and use a variety of
+ fonts. Note that this is noticeably slower than with normal console.
+
+config CONSOLE_TRUETYPE_SIZE
+ int "TrueType font size"
+ depends on CONSOLE_TRUETYPE
+ default 18
+ help
+ This sets the font size for the console. The size is measured in
+ pixels and is the nominal height of a character. Note that fonts
+ are commonly measured in 'points', being 1/72 inch (about 3.52mm).
+ However that measurement depends on the size of your display and
+ there is no standard display density. At present there is not a
+ method to select the display's physical size, which would allow
+ U-Boot to calculate the correct font size.
+
+source "drivers/video/fonts/Kconfig"
+
config VIDEO_VESA
bool "Enable VESA video driver support"
default n
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 01f4be5..b2ee40d 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_DM_VIDEO) += video-uclass.o vidconsole-uclass.o
obj-$(CONFIG_DM_VIDEO) += video_bmp.o
obj-$(CONFIG_CONSOLE_NORMAL) += console_normal.o
obj-$(CONFIG_CONSOLE_ROTATION) += console_rotate.o
+obj-$(CONFIG_CONSOLE_TRUETYPE) += console_truetype.o fonts/
endif

obj-$(CONFIG_ATI_RADEON_FB) += ati_radeon_fb.o videomodes.o
diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c
new file mode 100644
index 0000000..b770ad4
--- /dev/null
+++ b/drivers/video/console_truetype.c
@@ -0,0 +1,533 @@
+/*
+ * Copyright (c) 2016 Google, Inc
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <video.h>
+#include <video_console.h>
+
+/* Functions needed by stb_truetype.h */
+static int tt_floor(double val)
+{
+ if (val < 0)
+ return (int)(val - 0.999);
+
+ return (int)val;
+}
+
+static int tt_ceil(double val)
+{
+ if (val < 0)
+ return (int)val;
+
+ return (int)(val + 0.999);
+}
+
+static double frac(double val)
+{
+ return val - tt_floor(val);
+}
+
+static double tt_fabs(double x)
+{
+ return x < 0 ? -x : x;
+}
+
+ /*
+ * Simple square root algorithm. This is from:
+ * http://stackoverflow.com/questions/1623375/writing-your-own-square-root-function
+ * Written by Chihung Yu
+ * Creative Commons license
+ * http://creativecommons.org/licenses/by-sa/3.0/legalcode
+ * It has been modified to compile correctly, and for U-Boot style.
+ */
+static double tt_sqrt(double value)
+{
+ double lo = 1.0;
+ double hi = value;
+
+ while (hi - lo > 0.00001) {
+ double mid = lo + (hi - lo) / 2;
+
+ if (mid * mid - value > 0.00001)
+ hi = mid;
+ else
+ lo = mid;
+ }
+
+ return lo;
+}
+
+#define STBTT_ifloor tt_floor
+#define STBTT_iceil tt_ceil
+#define STBTT_fabs tt_fabs
+#define STBTT_sqrt tt_sqrt
+#define STBTT_malloc(size, u) ((void)(u), malloc(size))
+#define STBTT_free(size, u) ((void)(u), free(size))
+#define STBTT_assert(x)
+#define STBTT_strlen(x) strlen(x)
+#define STBTT_memcpy memcpy
+#define STBTT_memset memset
+
+#define STB_TRUETYPE_IMPLEMENTATION
+#include "stb_truetype.h"
+
+/**
+ * struct pos_info - Records a cursor position
+ *
+ * @xpos_frac: Fractional X position in pixels (multiplied by VID_FRAC_DIV)
+ * @ypos: Y position (pixels from the top)
+ */
+struct pos_info {
+ int xpos_frac;
+ int ypos;
+};
+
+/*
+ * Allow one for each character on the command line plus one for each newline.
+ * This is just an estimate, but it should not be exceeded.
+ */
+#define POS_HISTORY_SIZE (CONFIG_SYS_CBSIZE * 11 / 10)
+
+/**
+ * struct console_tt_priv - Private data for this driver
+ *
+ * @font_size: Vertical font size in pixels
+ * @font_data: Pointer to TrueType font file contents
+ * @font: TrueType font information for the current font
+ * @pos: List of cursor positions for each character written. This is
+ * used to handle backspace. We clear the frame buffer between
+ * the last position and the current position, thus erasing the
+ * last character. We record enough characters to go back to the
+ * start of the current command line.
+ * @pos_ptr: Current position in the position history
+ * @baseline: Pixel offset of the font's baseline from the cursor position.
+ * This is the 'ascent' of the font, scaled to pixel coordinates.
+ * It measures the distance from the baseline to the top of the
+ * font.
+ * @scale: Scale of the font. This is calculated from the pixel height
+ * of the font. It is used by the STB library to generate images
+ * of the correct size.
+ */
+struct console_tt_priv {
+ int font_size;
+ u8 *font_data;
+ stbtt_fontinfo font;
+ struct pos_info pos[POS_HISTORY_SIZE];
+ int pos_ptr;
+ int baseline;
+ double scale;
+};
+
+static int console_truetype_set_row(struct udevice *dev, uint row, int clr)
+{
+ struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
+ struct console_tt_priv *priv = dev_get_priv(dev);
+ void *line;
+ int pixels = priv->font_size * vid_priv->line_length;
+ int i;
+
+ line = vid_priv->fb + row * priv->font_size * vid_priv->line_length;
+ switch (vid_priv->bpix) {
+#ifdef CONFIG_VIDEO_BPP8
+ case VIDEO_BPP8: {
+ uint8_t *dst = line;
+
+ for (i = 0; i < pixels; i++)
+ *dst++ = clr;
+ break;
+ }
+#endif
+#ifdef CONFIG_VIDEO_BPP16
+ case VIDEO_BPP16: {
+ uint16_t *dst = line;
+
+ for (i = 0; i < pixels; i++)
+ *dst++ = clr;
+ break;
+ }
+#endif
+#ifdef CONFIG_VIDEO_BPP32
+ case VIDEO_BPP32: {
+ uint32_t *dst = line;
+
+ for (i = 0; i < pixels; i++)
+ *dst++ = clr;
+ break;
+ }
+#endif
+ default:
+ return -ENOSYS;
+ }
+
+ return 0;
+}
+
+static int console_truetype_move_rows(struct udevice *dev, uint rowdst,
+ uint rowsrc, uint count)
+{
+ struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
+ struct console_tt_priv *priv = dev_get_priv(dev);
+ void *dst;
+ void *src;
+ int i, diff;
+
+ dst = vid_priv->fb + rowdst * priv->font_size * vid_priv->line_length;
+ src = vid_priv->fb + rowsrc * priv->font_size * vid_priv->line_length;
+ memmove(dst, src, priv->font_size * vid_priv->line_length * count);
+
+ /* Scroll up our position history */
+ diff = (rowsrc - rowdst) * priv->font_size;
+ for (i = 0; i < priv->pos_ptr; i++)
+ priv->pos[i].ypos -= diff;
+
+ return 0;
+}
+
+static int console_truetype_putc_xy(struct udevice *dev, uint x, uint y,
+ char ch)
+{
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
+ struct udevice *vid = dev->parent;
+ struct video_priv *vid_priv = dev_get_uclass_priv(vid);
+ struct console_tt_priv *priv = dev_get_priv(dev);
+ stbtt_fontinfo *font = &priv->font;
+ int width, height, xoff, yoff;
+ double xpos, x_shift;
+ int lsb;
+ int width_frac, linenum;
+ struct pos_info *pos;
+ u8 *bits, *data;
+ int advance;
+ void *line;
+ int row;
+
+ /* First get some basic metrics about this character */
+ stbtt_GetCodepointHMetrics(font, ch, &advance, &lsb);
+
+ /*
+ * First out our current X position in fractional pixels. If we wrote
+ * a character previously, using kerning to fine-tune the position of
+ * this character */
+ xpos = frac(VID_TO_PIXEL((double)x));
+ if (vc_priv->last_ch) {
+ xpos += priv->scale * stbtt_GetCodepointKernAdvance(font,
+ vc_priv->last_ch, ch);
+ }
+
+ /*
+ * Figure out where the cursor will move to after this character, and
+ * abort if we are out of space on this line. Also calculate the
+ * effective width of this character, which will be our return value:
+ * it dictates how much the cursor will move forward on the line.
+ */
+ x_shift = xpos - (double)tt_floor(xpos);
+ xpos += advance * priv->scale;
+ width_frac = (int)VID_TO_POS(xpos);
+ if (x + width_frac >= vc_priv->xsize_frac)
+ return -EAGAIN;
+
+ /* Write the current cursor position into history */
+ if (priv->pos_ptr < POS_HISTORY_SIZE) {
+ pos = &priv->pos[priv->pos_ptr];
+ pos->xpos_frac = vc_priv->xcur_frac;
+ pos->ypos = vc_priv->ycur;
+ priv->pos_ptr++;
+ }
+
+ /*
+ * Figure out how much past the start of a pixel we are, and pass this
+ * information into the render, which will return a 8-bit-per-pixel
+ * image of the character. For empty characters, like ' ', data will
+ * return NULL;
+ */
+ data = stbtt_GetCodepointBitmapSubpixel(font, priv->scale, priv->scale,
+ x_shift, 0, ch, &width, &height,
+ &xoff, &yoff);
+ if (!data)
+ return width_frac;
+
+ /* Figure out where to write the character in the frame buffer */
+ bits = data;
+ line = vid_priv->fb + y * vid_priv->line_length +
+ VID_TO_PIXEL(x) * VNBYTES(vid_priv->bpix);
+ linenum = priv->baseline + yoff;
+ if (linenum > 0)
+ line += linenum * vid_priv->line_length;
+
+ /*
+ * Write a row at a time, converting the 8bpp image into the colour
+ * depth of the display. We only expect white-on-black or the reverse
+ * so the code only handles this simple case.
+ */
+ for (row = 0; row < height; row++) {
+ switch (vid_priv->bpix) {
+#ifdef CONFIG_VIDEO_BPP16
+ case VIDEO_BPP16: {
+ uint16_t *dst = (uint16_t *)line + xoff;
+ int i;
+
+ for (i = 0; i < width; i++) {
+ int val = *bits;
+ int out;
+
+ if (vid_priv->colour_bg)
+ val = 255 - val;
+ out = val >> 3 |
+ (val >> 2) << 5 |
+ (val >> 3) << 11;
+ if (vid_priv->colour_fg)
+ *dst++ |= out;
+ else
+ *dst++ &= out;
+ bits++;
+ }
+ break;
+ }
+#endif
+ default:
+ return -ENOSYS;
+ }
+
+ line += vid_priv->line_length;
+ }
+ free(data);
+
+ return width_frac;
+}
+
+/**
+ * console_truetype_erase() - Erase a character
+ *
+ * This is used for backspace. We erase a square of the display within the
+ * given bounds.
+ *
+ * @dev: Device to update
+ * @xstart: X start position in pixels from the left
+ * @ystart: Y start position in pixels from the top
+ * @xend: X end position in pixels from the left
+ * @yend: Y end position in pixels from the top
+ * @clr: Value to write
+ * @return 0 if OK, -ENOSYS if the display depth is not supported
+ */
+static int console_truetype_erase(struct udevice *dev, int xstart, int ystart,
+ int xend, int yend, int clr)
+{
+ struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
+ void *line;
+ int pixels = xend - xstart;
+ int row, i;
+
+ line = vid_priv->fb + ystart * vid_priv->line_length;
+ line += xstart * VNBYTES(vid_priv->bpix);
+ for (row = ystart; row < yend; row++) {
+ switch (vid_priv->bpix) {
+#ifdef CONFIG_VIDEO_BPP8
+ case VIDEO_BPP8: {
+ uint8_t *dst = line;
+
+ for (i = 0; i < pixels; i++)
+ *dst++ = clr;
+ break;
+ }
+#endif
+#ifdef CONFIG_VIDEO_BPP16
+ case VIDEO_BPP16: {
+ uint16_t *dst = line;
+
+ for (i = 0; i < pixels; i++)
+ *dst++ = clr;
+ break;
+ }
+#endif
+#ifdef CONFIG_VIDEO_BPP32
+ case VIDEO_BPP32: {
+ uint32_t *dst = line;
+
+ for (i = 0; i < pixels; i++)
+ *dst++ = clr;
+ break;
+ }
+#endif
+ default:
+ return -ENOSYS;
+ }
+ line += vid_priv->line_length;
+ }
+
+ return 0;
+}
+
+/**
+ * console_truetype_backspace() - Handle a backspace operation
+ *
+ * This clears the previous character so that the console looks as if it had
+ * not been entered.
+ *
+ * @dev: Device to update
+ * @return 0 if OK, -ENOSYS if not supported
+ */
+static int console_truetype_backspace(struct udevice *dev)
+{
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
+ struct console_tt_priv *priv = dev_get_priv(dev);
+ struct udevice *vid_dev = dev->parent;
+ struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev);
+ struct pos_info *pos;
+ int xend;
+
+ /*
+ * This indicates a very strange error higher in the stack. The caller
+ * has sent out n character and n + 1 backspaces.
+ */
+ if (!priv->pos_ptr)
+ return -ENOSYS;
+
+ /* Pop the last cursor position off the stack */
+ pos = &priv->pos[--priv->pos_ptr];
+
+ /*
+ * Figure out the end position for clearing. Normlly it is the current
+ * cursor position, but if we are clearing a character on the previous
+ * line, we clear from the end of the line.
+ */
+ if (pos->ypos == vc_priv->ycur)
+ xend = VID_TO_PIXEL(vc_priv->xcur_frac);
+ else
+ xend = vid_priv->xsize;
+
+ console_truetype_erase(dev, VID_TO_PIXEL(pos->xpos_frac), pos->ypos,
+ xend, pos->ypos + vc_priv->y_charsize,
+ vid_priv->colour_bg);
+
+ /* Move the cursor back to where it was when we pushed this record */
+ vc_priv->xcur_frac = pos->xpos_frac;
+ vc_priv->ycur = pos->ypos;
+
+ return 0;
+}
+
+static int console_truetype_entry_start(struct udevice *dev)
+{
+ struct console_tt_priv *priv = dev_get_priv(dev);
+
+ /* A new input line has start, so clear our history */
+ priv->pos_ptr = 0;
+
+ return 0;
+}
+
+/*
+ * Provides a list of fonts which can be obtained at run-time in U-Boot. These
+ * are compiled in by the Makefile.
+ *
+ * At present there is no mechanism to select a particular font - the first
+ * one found is the one that is used. But the build system and the code here
+ * supports multiple fonts, which may be useful for certain firmware screens.
+ */
+struct font_info {
+ char *name;
+ u8 *begin;
+ u8 *end;
+};
+
+#define FONT_DECL(_name) \
+ extern u8 __ttf_ ## _name ## _begin[]; \
+ extern u8 __ttf_ ## _name ## _end[];
+
+#define FONT_ENTRY(_name) { \
+ .name = #_name, \
+ .begin = __ttf_ ## _name ## _begin, \
+ .end = __ttf_ ## _name ## _end, \
+ }
+
+static struct font_info font_table[] = {
+ {} /* sentinel */
+};
+
+#define FONT_BEGIN(name) __ttf_ ## name ## _begin
+#define FONT_END(name) __ttf_ ## name ## _end
+#define FONT_IS_VALID(name) (abs(FONT_END(name) - FONT_BEGIN) > 4)
+
+/**
+ * console_truetype_find_font() - Find a suitable font
+ *
+ * This searched for the first available font.
+ *
+ * @return pointer to the font, or NULL if none is found
+ */
+static u8 *console_truetype_find_font(void)
+{
+ struct font_info *tab;
+
+ for (tab = font_table; tab->begin; tab++) {
+ if (abs(tab->begin - tab->end) > 4) {
+ debug("%s: Font '%s', at %p, size %lx\n", __func__,
+ tab->name, tab->begin,
+ (ulong)(tab->end - tab->begin));
+ return tab->begin;
+ }
+ }
+
+ return NULL;
+}
+
+static int console_truetype_probe(struct udevice *dev)
+{
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
+ struct console_tt_priv *priv = dev_get_priv(dev);
+ struct udevice *vid_dev = dev->parent;
+ struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev);
+ stbtt_fontinfo *font = &priv->font;
+ int ascent;
+
+ debug("%s: start\n", __func__);
+ if (vid_priv->font_size)
+ priv->font_size = vid_priv->font_size;
+ else
+ priv->font_size = CONFIG_CONSOLE_TRUETYPE_SIZE;
+ priv->font_data = console_truetype_find_font();
+ if (!priv->font_data) {
+ debug("%s: Could not find any fonts\n", __func__);
+ return -EBFONT;
+ }
+
+ vc_priv->x_charsize = priv->font_size;
+ vc_priv->y_charsize = priv->font_size;
+ vc_priv->xstart_frac = VID_TO_POS(2);
+ vc_priv->cols = vid_priv->xsize / priv->font_size;
+ vc_priv->rows = vid_priv->ysize / priv->font_size;
+ vc_priv->tab_width_frac = VID_TO_POS(priv->font_size) * 8 / 2;
+
+ if (!stbtt_InitFont(font, priv->font_data, 0)) {
+ debug("%s: Font init failed\n", __func__);
+ return -EPERM;
+ }
+
+ /* Pre-calculate some things we will need regularly */
+ priv->scale = stbtt_ScaleForPixelHeight(font, priv->font_size);
+ stbtt_GetFontVMetrics(font, &ascent, 0, 0);
+ priv->baseline = (int)(ascent * priv->scale);
+ debug("%s: ready\n", __func__);
+
+ return 0;
+}
+
+struct vidconsole_ops console_truetype_ops = {
+ .putc_xy = console_truetype_putc_xy,
+ .move_rows = console_truetype_move_rows,
+ .set_row = console_truetype_set_row,
+ .backspace = console_truetype_backspace,
+ .entry_start = console_truetype_entry_start,
+};
+
+U_BOOT_DRIVER(vidconsole_truetype) = {
+ .name = "vidconsole_tt",
+ .id = UCLASS_VIDEO_CONSOLE,
+ .ops = &console_truetype_ops,
+ .probe = console_truetype_probe,
+ .priv_auto_alloc_size = sizeof(struct console_tt_priv),
+};
diff --git a/drivers/video/fonts/Kconfig b/drivers/video/fonts/Kconfig
new file mode 100644
index 0000000..ad16ce6
--- /dev/null
+++ b/drivers/video/fonts/Kconfig
@@ -0,0 +1,7 @@
+#
+# Video fonts
+#
+
+menu "TrueType Fonts"
+
+endmenu
diff --git a/drivers/video/fonts/Makefile b/drivers/video/fonts/Makefile
new file mode 100644
index 0000000..6ab4647
--- /dev/null
+++ b/drivers/video/fonts/Makefile
@@ -0,0 +1,6 @@
+#
+# (C) Copyright 2000-2007
+# Wolfgang Denk, DENX Software Engineering, wd at denx.de.
+#
+# SPDX-License-Identifier: GPL-2.0+
+#
diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c
index 63d0d9d..13e8a88 100644
--- a/drivers/video/video-uclass.c
+++ b/drivers/video/video-uclass.c
@@ -194,11 +194,18 @@ static int video_post_probe(struct udevice *dev)
* it might be useful to support only bitmap drawing on the device
* for boards that don't need to display text.
*/
- snprintf(name, sizeof(name), "%s.vidconsole", dev->name);
+ if (IS_ENABLED(CONFIG_CONSOLE_TRUETYPE)) {
+ snprintf(name, sizeof(name), "%s.vidconsole_tt", dev->name);
+ strcpy(drv, "vidconsole_tt");
+ } else {
+ snprintf(name, sizeof(name), "%s.vidconsole%d", dev->name,
+ priv->rot);
+ snprintf(drv, sizeof(drv), "vidconsole%d", priv->rot);
+ }
+
str = strdup(name);
if (!str)
return -ENOMEM;
- snprintf(drv, sizeof(drv), "vidconsole%d", priv->rot);
ret = device_bind_driver(dev, drv, str, &cons);
if (ret) {
debug("%s: Cannot bind console driver\n", __func__);
--
1.7.9.5