/* * Copyright (c) 2016 Google, Inc * * SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #include /* 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: free(data); 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, \ } FONT_DECL(nimbus_sans_l_regular); FONT_DECL(ankacoder_c75_r); FONT_DECL(rufscript010); FONT_DECL(cantoraone_regular); static struct font_info font_table[] = { #ifdef CONFIG_CONSOLE_TRUETYPE_NIMBUS FONT_ENTRY(nimbus_sans_l_regular), #endif #ifdef CONFIG_CONSOLE_TRUETYPE_ANKACODER FONT_ENTRY(ankacoder_c75_r), #endif #ifdef CONFIG_CONSOLE_TRUETYPE_RUFSCRIPT FONT_ENTRY(rufscript010), #endif #ifdef CONFIG_CONSOLE_TRUETYPE_CANTORAONE FONT_ENTRY(cantoraone_regular), #endif {} /* 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), };