diff options
Diffstat (limited to 'src/gdisp/mcufont')
-rw-r--r-- | src/gdisp/mcufont/mcufont.h | 14 | ||||
-rw-r--r-- | src/gdisp/mcufont/mcufont.mk | 19 | ||||
-rw-r--r-- | src/gdisp/mcufont/mf_bwfont.c | 134 | ||||
-rw-r--r-- | src/gdisp/mcufont/mf_bwfont.h | 77 | ||||
-rw-r--r-- | src/gdisp/mcufont/mf_config.h | 148 | ||||
-rw-r--r-- | src/gdisp/mcufont/mf_encoding.c | 69 | ||||
-rw-r--r-- | src/gdisp/mcufont/mf_encoding.h | 53 | ||||
-rw-r--r-- | src/gdisp/mcufont/mf_font.c | 77 | ||||
-rw-r--r-- | src/gdisp/mcufont/mf_font.h | 118 | ||||
-rw-r--r-- | src/gdisp/mcufont/mf_justify.c | 321 | ||||
-rw-r--r-- | src/gdisp/mcufont/mf_justify.h | 74 | ||||
-rw-r--r-- | src/gdisp/mcufont/mf_kerning.c | 118 | ||||
-rw-r--r-- | src/gdisp/mcufont/mf_kerning.h | 29 | ||||
-rw-r--r-- | src/gdisp/mcufont/mf_rlefont.c | 282 | ||||
-rw-r--r-- | src/gdisp/mcufont/mf_rlefont.h | 74 | ||||
-rw-r--r-- | src/gdisp/mcufont/mf_scaledfont.c | 83 | ||||
-rw-r--r-- | src/gdisp/mcufont/mf_scaledfont.h | 23 | ||||
-rw-r--r-- | src/gdisp/mcufont/mf_wordwrap.c | 290 | ||||
-rw-r--r-- | src/gdisp/mcufont/mf_wordwrap.h | 32 |
19 files changed, 2035 insertions, 0 deletions
diff --git a/src/gdisp/mcufont/mcufont.h b/src/gdisp/mcufont/mcufont.h new file mode 100644 index 00000000..8a81ff42 --- /dev/null +++ b/src/gdisp/mcufont/mcufont.h @@ -0,0 +1,14 @@ +/* Tiny library for rendering compressed bitmap fonts on microcontrollers. */ + +#ifndef _MCUFONT_H_ +#define _MCUFONT_H_ + +#include "mf_config.h" +#include "mf_encoding.h" +#include "mf_justify.h" +#include "mf_kerning.h" +#include "mf_rlefont.h" +#include "mf_scaledfont.h" +#include "mf_wordwrap.h" + +#endif diff --git a/src/gdisp/mcufont/mcufont.mk b/src/gdisp/mcufont/mcufont.mk new file mode 100644 index 00000000..0b402877 --- /dev/null +++ b/src/gdisp/mcufont/mcufont.mk @@ -0,0 +1,19 @@ +# Makefile fragment listing the source files for the mcufont decoder. + +# Directory where the decoder source code recides +# Usually you'll want to set this in your own Makefile. +MFDIR ?= mcufont/decoder + +# Name of the include directory +MFINC = $(MFDIR) + +# Source code files to include +MFSRC = \ + $(MFDIR)/mf_encoding.c \ + $(MFDIR)/mf_font.c \ + $(MFDIR)/mf_justify.c \ + $(MFDIR)/mf_kerning.c \ + $(MFDIR)/mf_rlefont.c \ + $(MFDIR)/mf_bwfont.c \ + $(MFDIR)/mf_scaledfont.c \ + $(MFDIR)/mf_wordwrap.c diff --git a/src/gdisp/mcufont/mf_bwfont.c b/src/gdisp/mcufont/mf_bwfont.c new file mode 100644 index 00000000..918fbb29 --- /dev/null +++ b/src/gdisp/mcufont/mf_bwfont.c @@ -0,0 +1,134 @@ +#include "mf_bwfont.h" +#include <stdbool.h> + +/* Find the character range and index that contains a given glyph.. */ +static const struct mf_bwfont_char_range_s *find_char_range( + const struct mf_bwfont_s *font, uint16_t character, uint16_t *index_ret) +{ + unsigned i, index; + const struct mf_bwfont_char_range_s *range; + for (i = 0; i < font->char_range_count; i++) + { + range = &font->char_ranges[i]; + index = character - range->first_char; + if (character >= range->first_char && index < range->char_count) + { + *index_ret = index; + return range; + } + } + + return 0; +} + +static uint8_t get_width(const struct mf_bwfont_char_range_s *r, uint16_t index) +{ + if (r->width) + { + return r->width + r->offset_x; + } + else + { + return r->glyph_widths[index]; + } +} + +static uint8_t render_char(const struct mf_bwfont_char_range_s *r, + int16_t x0, int16_t y0, uint16_t index, + mf_pixel_callback_t callback, + void *state) +{ + const uint8_t *data, *p; + uint8_t stride, runlen; + uint8_t x, y, height, num_cols; + uint8_t bit, byte, mask; + bool oldstate, newstate; + + if (r->width) + { + data = r->glyph_data + r->width * index * r->height_bytes; + num_cols = r->width; + } + else + { + data = r->glyph_data + r->glyph_offsets[index] * r->height_bytes; + num_cols = r->glyph_offsets[index + 1] - r->glyph_offsets[index]; + } + + stride = r->height_bytes; + height = r->height_pixels; + y0 += r->offset_y; + x0 += r->offset_x; + bit = 0; + byte = 0; + + for (y = 0; y < height; y++) + { + mask = (1 << bit); + + oldstate = false; + runlen = 0; + p = data + byte; + for (x = 0; x < num_cols; x++, p += stride) + { + newstate = *p & mask; + if (newstate != oldstate) + { + if (oldstate && runlen) + { + callback(x0 + x - runlen, y0 + y, runlen, 255, state); + } + + oldstate = newstate; + runlen = 0; + } + + runlen++; + } + + if (oldstate && runlen) + { + callback(x0 + x - runlen, y0 + y, runlen, 255, state); + } + + bit++; + if (bit > 7) + { + bit = 0; + byte++; + } + } + + return get_width(r, index); +} + +uint8_t mf_bwfont_render_character(const struct mf_font_s *font, + int16_t x0, int16_t y0, + uint16_t character, + mf_pixel_callback_t callback, + void *state) +{ + const struct mf_bwfont_s *bwfont = (const struct mf_bwfont_s*)font; + const struct mf_bwfont_char_range_s *range; + uint16_t index; + + range = find_char_range(bwfont, character, &index); + if (!range) + return 0; + + return render_char(range, x0, y0, index, callback, state); +} + +uint8_t mf_bwfont_character_width(const struct mf_font_s *font, + uint16_t character) +{ + const struct mf_bwfont_s *bwfont = (const struct mf_bwfont_s*)font; + const struct mf_bwfont_char_range_s *range; + uint16_t index; + + range = find_char_range(bwfont, character, &index); + if (!range) + return 0; + + return get_width(range, index); +} diff --git a/src/gdisp/mcufont/mf_bwfont.h b/src/gdisp/mcufont/mf_bwfont.h new file mode 100644 index 00000000..9aea2ac9 --- /dev/null +++ b/src/gdisp/mcufont/mf_bwfont.h @@ -0,0 +1,77 @@ +/* Uncompressed font format for storing black & white fonts. Very efficient + * to decode and works well for small font sizes. + */ + +#ifndef _MF_BWFONT_H_ +#define _MF_BWFONT_H_ + +#include "mf_font.h" + +/* Versions of the BW font format that are supported. */ +#define MF_BWFONT_VERSION_4_SUPPORTED 1 + +/* Structure for a range of characters. */ +struct mf_bwfont_char_range_s +{ + /* The number of the first character in this range. */ + uint16_t first_char; + + /* The total count of characters in this range. */ + uint16_t char_count; + + /* The left and top skips of the characters in this range. + * This is the number of empty rows at left and at top. */ + uint8_t offset_x; + uint8_t offset_y; + + /* Column height for glyphs in this range, in bytes and pixels. */ + uint8_t height_bytes; + uint8_t height_pixels; + + /* Positive value if the width of all glyphs in this range is the + * same, or zero if it is not. */ + uint8_t width; + + /* Lookup table for the character widths. NULL if width is specified. */ + const uint8_t *glyph_widths; + + /* Lookup table for the character offsets. Multiply by height_bytes + * to get the byte offset. Also allows lookup of the number of columns. + * NULL if width is specified. */ + const uint16_t *glyph_offsets; + + /* Table for the glyph data. + * The data for each glyph is column-by-column, with N bytes per each + * column. The LSB of the first byte is the top left pixel. + */ + const uint8_t *glyph_data; +}; + +/* Structure for the font */ +struct mf_bwfont_s +{ + struct mf_font_s font; + + /* Version of the font format. */ + const uint8_t version; + + /* Number of character ranges. */ + const uint8_t char_range_count; + + /* Array of the character ranges */ + const struct mf_bwfont_char_range_s *char_ranges; +}; + +#ifdef MF_BWFONT_INTERNALS +/* Internal functions, don't use these directly. */ +MF_EXTERN uint8_t mf_bwfont_render_character(const struct mf_font_s *font, + int16_t x0, int16_t y0, + mf_char character, + mf_pixel_callback_t callback, + void *state); + +MF_EXTERN uint8_t mf_bwfont_character_width(const struct mf_font_s *font, + mf_char character); +#endif + +#endif diff --git a/src/gdisp/mcufont/mf_config.h b/src/gdisp/mcufont/mf_config.h new file mode 100644 index 00000000..7e58c8f6 --- /dev/null +++ b/src/gdisp/mcufont/mf_config.h @@ -0,0 +1,148 @@ +/* Configuration constants for mcufont. */ + +#ifndef _MF_CONFIG_H_ +#define _MF_CONFIG_H_ + +#include <gfx.h> + +/* Mapping from uGFX settings to mcufont settings */ +#if GDISP_NEED_UTF8 +#define MF_ENCODING MF_ENCODING_UTF8 +#else +#define MF_ENCODING MF_ENCODING_ASCII +#endif + +#define MF_USE_KERNING GDISP_NEED_TEXT_KERNING + +/* These are not used for now */ +#define MF_USE_ADVANCED_WORDWRAP 0 +#define MF_USE_JUSTIFY 0 + + +/******************************************************* + * Configuration settings related to build environment * + *******************************************************/ + +/* Name of the file that contains all the included fonts. */ +#ifndef MF_FONT_FILE_NAME +#define MF_FONT_FILE_NAME "fonts.h" +#endif + + +/***************************************** + * Configuration settings related to API * + *****************************************/ + +/* Encoding for the input data. + * With the unicode encodings, the library supports the range of unicode + * characters 0x0000-0xFFFF (the Basic Multilingual Plane). + * + * ASCII: Plain ascii (somewhat works with ISO8859-1 also) + * UTF8: UTF8 encoding (variable number of bytes) + * UTF16: UTF16 encoding (2 bytes per character, compatible with UCS-2) + * WCHAR: Use compiler's wchar_t (usually same as UTF16) + */ +#define MF_ENCODING_ASCII 0 +#define MF_ENCODING_UTF8 1 +#define MF_ENCODING_UTF16 2 +#define MF_ENCODING_WCHAR 3 +#ifndef MF_ENCODING +#define MF_ENCODING MF_ENCODING_UTF8 +#endif + + +/************************************************************************ + * Configuration settings related to visual appearance of rendered text * + ************************************************************************/ + +/* Minimum space between characters, in percents of the glyph width. + * Increasing this causes the kerning module to leave more space between + * characters. + */ +#ifndef MF_KERNING_SPACE_PERCENT +#define MF_KERNING_SPACE_PERCENT 15 +#endif + +/* Minimum space between characters, in pixels. Added to the percentual + * spacing. This pixel-based value guarantees enough space even with small + * fonts. + */ +#ifndef MF_KERNING_SPACE_PIXELS +#define MF_KERNING_SPACE_PIXELS 3 +#endif + +/* Maximum adjustment done by the kerning algorithm, as percent of the + * glyph width. + */ +#ifndef MF_KERNING_LIMIT +#define MF_KERNING_LIMIT 20 +#endif + +/* Spacing of tabulator stops. The value is multiplied by the width of the + * 'm' character in the current font. + */ +#ifndef MF_TABSIZE +#define MF_TABSIZE 8 +#endif + + +/************************************************************************* + * Configuration settings to strip down library to reduce resource usage * + *************************************************************************/ + +/* Enable or disable the kerning module. + * Disabling it saves some code size and run time, but causes the spacing + * between characters to be less consistent. + */ +#ifndef MF_USE_KERNING +#define MF_USE_KERNING 1 +#endif + +/* Enable or disable the advanced word wrap algorithm. + * If disabled, uses a simpler algorithm. + */ +#ifndef MF_USE_ADVANCED_WORDWRAP +#define MF_USE_ADVANCED_WORDWRAP 1 +#endif + +/* Enable of disable the justification algorithm. + * If disabled, mf_render_justified renders just left-aligned. + */ +#ifndef MF_USE_JUSTIFY +#define MF_USE_JUSTIFY 1 +#endif + +/* Enable or disable the center and right alignment code. + * If disabled, any alignment results in MF_ALIGN_LEFT. + */ +#ifndef MF_USE_ALIGN +#define MF_USE_ALIGN 1 +#endif + +/* Enable or disable the support for tab alignment. + * If disabled, tabs will be rendered as regular space character. + */ +#ifndef MF_USE_TABS +#define MF_USE_TABS 1 +#endif + +/* Number of vertical zones to use when computing kerning. + * Larger values give more accurate kerning, but are slower and use somewhat + * more memory. There is no point to increase this beyond the height of the + * font. + */ +#ifndef MF_KERNING_ZONES +#define MF_KERNING_ZONES 16 +#endif + + + +/* Add extern "C" when used from C++. */ +#ifdef __cplusplus +#define MF_EXTERN extern "C" +#else +#define MF_EXTERN extern +#endif + +#endif + diff --git a/src/gdisp/mcufont/mf_encoding.c b/src/gdisp/mcufont/mf_encoding.c new file mode 100644 index 00000000..4e3975ae --- /dev/null +++ b/src/gdisp/mcufont/mf_encoding.c @@ -0,0 +1,69 @@ +#include "mf_encoding.h" + +#if MF_ENCODING == MF_ENCODING_UTF8 + +mf_char mf_getchar(mf_str *str) +{ + uint8_t c; + uint8_t tmp, seqlen; + uint16_t result; + + c = **str; + if (!c) + return 0; + + (*str)++; + + if ((c & 0x80) == 0) + { + /* Just normal ASCII character. */ + return c; + } + else if ((c & 0xC0) == 0x80) + { + /* Dangling piece of corrupted multibyte sequence. + * Did you cut the string in the wrong place? + */ + return c; + } + else if ((**str & 0xC0) == 0xC0) + { + /* Start of multibyte sequence without any following bytes. + * Silly. Maybe you are using the wrong encoding. + */ + return c; + } + else + { + /* Beginning of a multi-byte sequence. + * Find out how many characters and combine them. + */ + seqlen = 2; + tmp = 0x20; + result = 0; + while ((c & tmp) && (seqlen < 5)) + { + seqlen++; + tmp >>= 1; + + result = (result << 6) | (**str & 0x3F); + (*str)++; + } + + result = (result << 6) | (**str & 0x3F); + (*str)++; + + result |= (c & (tmp - 1)) << ((seqlen - 1) * 6); + return result; + } +} + +void mf_rewind(mf_str *str) +{ + (*str)--; + + while ((**str & 0x80) != 0x00 && (**str & 0xC0) != 0xC0) + (*str)--; +} + +#endif diff --git a/src/gdisp/mcufont/mf_encoding.h b/src/gdisp/mcufont/mf_encoding.h new file mode 100644 index 00000000..e96718b7 --- /dev/null +++ b/src/gdisp/mcufont/mf_encoding.h @@ -0,0 +1,53 @@ +/* Simple UTF-8 decoder. Also implements the much simpler ASCII and UTF16 + * input encodings. + */ + +#ifndef _MF_ENCODING_H_ +#define _MF_ENCODING_H_ + +#include "mf_config.h" +#include <stdint.h> + +/* Type used to represent characters internally. */ +#if MF_ENCODING == MF_ENCODING_ASCII +typedef char mf_char; +#else +typedef uint16_t mf_char; +#endif + +/* Type used to represent input strings. */ +#if MF_ENCODING == MF_ENCODING_ASCII +typedef const char * mf_str; +#elif MF_ENCODING == MF_ENCODING_UTF8 +typedef const char * mf_str; +#elif MF_ENCODING == MF_ENCODING_UTF16 +typedef const uint16_t * mf_str; +#elif MF_ENCODING == MF_ENCODING_WCHAR +#include <stddef.h> +typedef const wchar_t * mf_str; +#endif + +/* Returns the next character in the string and advances the pointer. + * When the string ends, returns 0 and leaves the pointer at the 0 byte. + * + * str: Pointer to variable holding current location in string. + * Initialize it to the start of the string. + * + * Returns: The next character, as unicode codepoint. + */ +#if MF_ENCODING == MF_ENCODING_UTF8 +MF_EXTERN mf_char mf_getchar(mf_str *str); +#else +static mf_char mf_getchar(mf_str *str) { return *(*str)++; } +#endif + +/* Moves back the pointer to the beginning of the previous character. + * Be careful not to go beyond the start of the string. + */ +#if MF_ENCODING == MF_ENCODING_UTF8 +MF_EXTERN void mf_rewind(mf_str *str); +#else +static void mf_rewind(mf_str *str) { (*str)--; } +#endif + +#endif diff --git a/src/gdisp/mcufont/mf_font.c b/src/gdisp/mcufont/mf_font.c new file mode 100644 index 00000000..698a3d8e --- /dev/null +++ b/src/gdisp/mcufont/mf_font.c @@ -0,0 +1,77 @@ +#include "mf_font.h" +#include <stdbool.h> + +/* This will be made into a list of included fonts using macro magic. */ +#define MF_INCLUDED_FONTS 0 + +/* Included fonts begin here */ +#include MF_FONT_FILE_NAME +/* Include fonts end here */ + +uint8_t mf_render_character(const struct mf_font_s *font, + int16_t x0, int16_t y0, + mf_char character, + mf_pixel_callback_t callback, + void *state) +{ + uint8_t width; + width = font->render_character(font, x0, y0, character, callback, state); + + if (!width) + { + width = font->render_character(font, x0, y0, font->fallback_character, + callback, state); + } + + return width; +} + +uint8_t mf_character_width(const struct mf_font_s *font, + mf_char character) +{ + uint8_t width; + width = font->character_width(font, character); + + if (!width) + { + width = font->character_width(font, font->fallback_character); + } + + return width; +} + +/* Avoids a dependency on libc */ +static bool strequals(const char *a, const char *b) +{ + while (*a) + { + if (*a++ != *b++) + return false; + } + return (!*b); +} + +const struct mf_font_s *mf_find_font(const char *name) +{ + const struct mf_font_list_s *f; + f = MF_INCLUDED_FONTS; + + while (f) + { + if (strequals(f->font->full_name, name) || + strequals(f->font->short_name, name)) + { + return f->font; + } + + f = f->next; + } + + return 0; +} + +const struct mf_font_list_s *mf_get_font_list() +{ + return MF_INCLUDED_FONTS; +} + diff --git a/src/gdisp/mcufont/mf_font.h b/src/gdisp/mcufont/mf_font.h new file mode 100644 index 00000000..591ebab6 --- /dev/null +++ b/src/gdisp/mcufont/mf_font.h @@ -0,0 +1,118 @@ +/* Generic font type that supports fonts with multiple kinds of compression. + * Provides an interface for decoding and rendering single characters. + */ + +#ifndef _MF_FONT_H_ +#define _MF_FONT_H_ + +#include "mf_encoding.h" + +/* Callback function that writes pixels to screen / buffer / whatever. + * + * x: X coordinate of the first pixel to write. + * y: Y coordinate of the first pixel to write. + * count: Number of pixels to fill (horizontally). + * alpha: The "opaqueness" of the pixels, 0 for background, 255 for text. + * state: Free variable that was passed to render_character(). + */ +typedef void (*mf_pixel_callback_t) (int16_t x, int16_t y, uint8_t count, + uint8_t alpha, void *state); + +/* General information about a font. */ +struct mf_font_s +{ + /* Full name of the font, comes from the original font file. */ + const char *full_name; + + /* Short name of the font, comes from file name. */ + const char *short_name; + + /* Width and height of the character bounding box. */ + uint8_t width; + uint8_t height; + + /* Minimum and maximum tracking width of characters. */ + uint8_t min_x_advance; + uint8_t max_x_advance; + + /* Location of the text baseline relative to character. */ + uint8_t baseline_x; + uint8_t baseline_y; + + /* Line height of the font (vertical advance). */ + uint8_t line_height; + + /* Flags identifying various aspects of the font. */ + uint8_t flags; + + /* Fallback character to use for missing glyphs. */ + mf_char fallback_character; + + /* Function to get character width. Should return 0 if character is + * not found. */ + uint8_t (*character_width)(const struct mf_font_s *font, mf_char character); + + /* Function to render a character. Returns the character width or 0 if + * character is not found. */ + uint8_t (*render_character)(const struct mf_font_s *font, + int16_t x0, int16_t y0, + mf_char character, + mf_pixel_callback_t callback, + void *state); +}; + +/* The flag definitions for the font.flags field. */ +#define MF_FONT_FLAG_MONOSPACE 0x01 +#define MF_FONT_FLAG_BW 0x02 + +/* Lookup structure for searching fonts by name. */ +struct mf_font_list_s +{ + const struct mf_font_list_s *next; + const struct mf_font_s *font; +}; + + +/* Function to decode and render a single character. + * + * font: Pointer to the font definition. + * x0, y0: Upper left corner of the target area. + * character: The character code (unicode) to render. + * callback: Callback function to write out the pixels. + * state: Free variable for caller to use (can be NULL). + * + * Returns width of the character. + */ +MF_EXTERN uint8_t mf_render_character(const struct mf_font_s *font, + int16_t x0, int16_t y0, + mf_char character, + mf_pixel_callback_t callback, + void *state); + +/* Function to get the width of a single character. + * This is not necessarily the bounding box of the character + * data, but rather the tracking width. + * + * font: Pointer to the font definition. + * character: The character code (unicode) to render. + * + * Returns width of the character in pixels. + */ +MF_EXTERN uint8_t mf_character_width(const struct mf_font_s *font, + mf_char character); + +/* Find a font based on name. The name can be either short name or full name. + * Note: You can pass MF_INCLUDED_FONTS to search among all the included .h + * files. + * + * name: Font name to search for. + * fonts: Pointer to the first font search entry. + * + * Returns a pointer to the font or NULL if not found. + */ +MF_EXTERN const struct mf_font_s *mf_find_font(const char *name); + +/* Get the list of included fonts */ +MF_EXTERN const struct mf_font_list_s *mf_get_font_list(); + +#endif
\ No newline at end of file diff --git a/src/gdisp/mcufont/mf_justify.c b/src/gdisp/mcufont/mf_justify.c new file mode 100644 index 00000000..1938bb6a --- /dev/null +++ b/src/gdisp/mcufont/mf_justify.c @@ -0,0 +1,321 @@ +#include "mf_justify.h" +#include "mf_kerning.h" + +#if MF_USE_TABS +/* Round the X coordinate up to the nearest tab stop. */ +static int16_t mf_round_to_tab(const struct mf_font_s *font, + int16_t x0, int16_t x) +{ + int16_t tabw, dx; + + tabw = mf_character_width(font, 'm') * MF_TABSIZE; + + /* Always atleast 1 space */ + x += mf_character_width(font, ' '); + + /* Round to next tab stop */ + dx = x - x0 + font->baseline_x; + x += tabw - (dx % tabw); + + return x; +} + +/* Round the X coordinate down to the nearest tab stop. */ +static int16_t mf_round_to_prev_tab(const struct mf_font_s *font, + int16_t x0, int16_t x) +{ + int16_t tabw, dx; + + tabw = mf_character_width(font, 'm') * MF_TABSIZE; + + /* Always atleast 1 space */ + x -= mf_character_width(font, ' '); + + /* Round to previous tab stop */ + dx = x0 - x + font->baseline_x; + x -= tabw - (dx % tabw); + + return x; +} +#endif + +int16_t mf_get_string_width(const struct mf_font_s *font, mf_str text, + uint16_t count, bool kern) +{ + int16_t result = 0; + uint16_t c1 = 0, c2; + + if (!count) + count = 0xFFFF; + + while (count-- && *text) + { + c2 = mf_getchar(&text); + + if (kern && c1 != 0) + result += mf_compute_kerning(font, c1, c2); + + result += mf_character_width(font, c2); + c1 = c2; + } + + return result; +} + +/* Return the length of the string without trailing spaces. */ +static uint16_t strip_spaces(mf_str text, uint16_t count, mf_char *last_char) +{ + uint16_t i = 0, result = 0; + mf_char tmp = 0; + + if (!count) + count = 0xFFFF; + + while (count-- && *text) + { + i++; + tmp = mf_getchar(&text); + if (tmp != ' ' && tmp != 0xA0 && tmp != '\n' && + tmp != '\r' && tmp != '\t') + { + result = i; + } + } + + if (last_char) + { + if (!*text) + *last_char = 0; + else + *last_char = tmp; + } + + return result; +} + +/* Render left-aligned string, left edge at x0. */ +static void render_left(const struct mf_font_s *font, + int16_t x0, int16_t y0, + mf_str text, uint16_t count, + mf_character_callback_t callback, + void *state) +{ + int16_t x; + mf_char c1 = 0, c2; + + x = x0 - font->baseline_x; + while (count--) + { + c2 = mf_getchar(&text); + + if (c2 == '\t') + { +#if MF_USE_TABS + x = mf_round_to_tab(font, x0, x); + c1 = ' '; + continue; +#else + c2 = ' '; +#endif + } + + if (c1 != 0) + x += mf_compute_kerning(font, c1, c2); + + x += callback(x, y0, c2, state); + c1 = c2; + } +} + +#if !MF_USE_ALIGN + +void mf_render_aligned(const struct mf_font_s *font, + int16_t x0, int16_t y0, + enum mf_align_t align, + mf_str text, uint16_t count, + mf_character_callback_t callback, + void *state) +{ + int16_t string_width; + count = strip_spaces(text, count, 0); + render_left(font, x0, y0, text, count, callback, state); +} + +#else + +/* Render right-aligned string, right edge at x0. */ +static void render_right(const struct mf_font_s *font, + int16_t x0, int16_t y0, + mf_str text, uint16_t count, + mf_character_callback_t callback, + void *state) +{ + int16_t x; + uint16_t i; + mf_char c1, c2 = 0; + mf_str tmp; + + /* Go to the end of the line. */ + for (i = 0; i < count; i++) + mf_getchar(&text); + + x = x0 - font->baseline_x; + for (i = 0; i < count; i++) + { + mf_rewind(&text); + tmp = text; + c1 = mf_getchar(&tmp); + + /* Perform tab alignment */ + if (c1 == '\t') + { +#if MF_USE_TABS + x = mf_round_to_prev_tab(font, x0, x); + c2 = ' '; + continue; +#else + c1 = ' '; +#endif + } + + /* Apply the nominal character width */ + x -= mf_character_width(font, c1); + + /* Apply kerning */ + if (c2 != 0) + x -= mf_compute_kerning(font, c1, c2); + + callback(x, y0, c1, state); + c2 = c1; + } +} + +void mf_render_aligned(const struct mf_font_s *font, + int16_t x0, int16_t y0, + enum mf_align_t align, + mf_str text, uint16_t count, + mf_character_callback_t callback, + void *state) +{ + int16_t string_width; + count = strip_spaces(text, count, 0); + + if (align == MF_ALIGN_LEFT) + { + render_left(font, x0, y0, text, count, callback, state); + } + if (align == MF_ALIGN_CENTER) + { + string_width = mf_get_string_width(font, text, count, false); + x0 -= string_width / 2; + render_left(font, x0, y0, text, count, callback, state); + } + else if (align == MF_ALIGN_RIGHT) + { + render_right(font, x0, y0, text, count, callback, state); + } +} + +#endif + + +#if !MF_USE_JUSTIFY + +void mf_render_justified(const struct mf_font_s *font, + int16_t x0, int16_t y0, int16_t width, + mf_str text, uint16_t count, + mf_character_callback_t callback, + void *state) +{ + mf_render_aligned(font, x0, y0, MF_ALIGN_LEFT, text, count, callback, state); +} + +#else + +/* Returns true if the character is a justification point, i.e. expands + * when the text is being justified. */ +static bool is_justify_space(uint16_t c) +{ + return c == ' ' || c == 0xA0; +} + +/* Count the number of space characters in string */ +static uint16_t count_spaces(mf_str text, uint16_t count) +{ + uint16_t spaces = 0; + while (count-- && *text) + { + if (is_justify_space(mf_getchar(&text))) + spaces++; + } + return spaces; +} + +void mf_render_justified(const struct mf_font_s *font, + int16_t x0, int16_t y0, int16_t width, + mf_str text, uint16_t count, + mf_character_callback_t callback, + void *state) +{ + int16_t string_width, adjustment; + uint16_t num_spaces; + mf_char last_char; + + count = strip_spaces(text, count, &last_char); + + if (last_char == '\n' || last_char == 0) + { + /* Line ends in linefeed, do not justify. */ + render_left(font, x0, y0, text, count, callback, state); + return; + } + + string_width = mf_get_string_width(font, text, count, false); + adjustment = width - string_width; + num_spaces = count_spaces(text, count); + + { + int16_t x, tmp; + uint16_t c1 = 0, c2; + + x = x0 - font->baseline_x; + while (count--) + { + c2 = mf_getchar(&text); + + if (c2 == '\t') + { +#if MF_USE_TABS + tmp = x; + x = mf_round_to_tab(font, x0, x); + adjustment -= x - tmp - mf_character_width(font, '\t'); + c1 = c2; + continue; +#else + c2 = ' '; +#endif + } + + if (is_justify_space(c2)) + { + tmp = (adjustment + num_spaces / 2) / num_spaces; + adjustment -= tmp; + num_spaces--; + x += tmp; + } + + if (c1 != 0) + { + tmp = mf_compute_kerning(font, c1, c2); + x += tmp; + adjustment -= tmp; + } + + x += callback(x, y0, c2, state); + c1 = c2; + } + } +} + +#endif + diff --git a/src/gdisp/mcufont/mf_justify.h b/src/gdisp/mcufont/mf_justify.h new file mode 100644 index 00000000..c3e8ba75 --- /dev/null +++ b/src/gdisp/mcufont/mf_justify.h @@ -0,0 +1,74 @@ +/* Text alignment and justification algorithm. Supports left, right, center + * alignment and justify. Supports tab stops and kerning. + */ + +#ifndef _MF_JUSTIFY_H_ +#define _MF_JUSTIFY_H_ + +#include "mf_rlefont.h" +#include <stdbool.h> + +enum mf_align_t +{ + MF_ALIGN_LEFT = 0, + MF_ALIGN_CENTER, + MF_ALIGN_RIGHT +}; + +/* Callback for rendering a single character. + * x0: Left edge of the target position of character. + * y0: Upper edge of the target position of character. + * character: Character to render. + * state: Free state variable for use by the callback. + * Returns the width of the character. + */ +typedef uint8_t (*mf_character_callback_t) (int16_t x0, int16_t y0, + mf_char character, void *state); + +/* Get width of a string in pixels. + * + * font: Pointer to the font definition. + * text: Pointer to start of the text to measure. + * count: Number of characters on the line or 0 to read until end of string. + * kern: True to consider kerning (slower). + */ +MF_EXTERN int16_t mf_get_string_width(const struct mf_font_s *font, + mf_str text, uint16_t count, bool kern); + +/* Render a single line of aligned text. + * + * font: Pointer to the font definition. + * x0: Depending on aligned, either left, center or right edge of target. + * y0: Upper edge of the target area. + * align: Type of alignment. + * text: Pointer to start of the text to render. + * count: Number of characters on the line or 0 to read until end of string. + * callback: Callback to call for each character. + * state: Free variable for use in the callback. + */ +MF_EXTERN void mf_render_aligned(const struct mf_font_s *font, + int16_t x0, int16_t y0, + enum mf_align_t align, + mf_str text, uint16_t count, + mf_character_callback_t callback, + void *state); + +/* Render a single line of justified text. + * + * font: Pointer to the font definition. + * x0: Left edge of the target area. + * y0: Upper edge of the target area. + * width: Width of the target area. + * text: Pointer to start of the text to render. + * count: Number of characters on the line or 0 to read until end of string. + * callback: Callback to call for each character. + * state: Free variable for use in the callback. + */ +MF_EXTERN void mf_render_justified(const struct mf_font_s *font, + int16_t x0, int16_t y0, int16_t width, + mf_str text, uint16_t count, + mf_character_callback_t callback, + void *state); + + +#endif diff --git a/src/gdisp/mcufont/mf_kerning.c b/src/gdisp/mcufont/mf_kerning.c new file mode 100644 index 00000000..bd5afc1a --- /dev/null +++ b/src/gdisp/mcufont/mf_kerning.c @@ -0,0 +1,118 @@ +#include "mf_kerning.h" +#include <stdbool.h> + +#if MF_USE_KERNING + +/* Structure for keeping track of the edge of the glyph as it is rendered. */ +struct kerning_state_s +{ + uint8_t edgepos[MF_KERNING_ZONES]; + uint8_t zoneheight; +}; + +/* Pixel callback for analyzing the left edge of a glyph. */ +static void fit_leftedge(int16_t x, int16_t y, uint8_t count, uint8_t alpha, + void *state) +{ + struct kerning_state_s *s = state; + + if (alpha > 7) + { + uint8_t zone = y / s->zoneheight; + if (x < s->edgepos[zone]) + s->edgepos[zone] = x; + } +} + +/* Pixel callback for analyzing the right edge of a glyph. */ +static void fit_rightedge(int16_t x, int16_t y, uint8_t count, uint8_t alpha, + void *state) +{ + struct kerning_state_s *s = state; + + if (alpha > 7) + { + uint8_t zone = y / s->zoneheight; + x += count - 1; + if (x > s->edgepos[zone]) + s->edgepos[zone] = x; + } +} + +/* Should kerning be done against this character? */ +static bool do_kerning(mf_char c) +{ + /* Just a speed optimization, spaces would be ignored anyway. */ + if (c == ' ' || c == '\n' || c == '\r' || c == '\t') + return false; + + /* Do not kern against digits, in order to keep values in tables nicely + * aligned. Most fonts have constant width for digits. */ + if (c >= '0' && c <= '9') + return false; + + return true; +} + +static int16_t min16(int16_t a, int16_t b) { return (a < b) ? a : b; } +static int16_t max16(int16_t a, int16_t b) { return (a > b) ? a : b; } +static int16_t avg16(int16_t a, int16_t b) { return (a + b) / 2; } + +int8_t mf_compute_kerning(const struct mf_font_s *font, + mf_char c1, mf_char c2) +{ + struct kerning_state_s leftedge, rightedge; + uint8_t w1, w2, i, min_space; + int16_t normal_space, adjust, max_adjust; + + if (font->flags & MF_FONT_FLAG_MONOSPACE) + return 0; /* No kerning for monospace fonts */ + + if (!do_kerning(c1) || !do_kerning(c2)) + return 0; + + /* Compute the height of one kerning zone in pixels */ + i = (font->height + MF_KERNING_ZONES - 1) / MF_KERNING_ZONES; + if (i < 1) i = 1; + + /* Initialize structures */ + leftedge.zoneheight = rightedge.zoneheight = i; + for (i = 0; i < MF_KERNING_ZONES; i++) + { + leftedge.edgepos[i] = 255; + rightedge.edgepos[i] = 0; + } + + /* Analyze the edges of both glyphs. */ + w1 = mf_render_character(font, 0, 0, c1, fit_rightedge, &rightedge); + w2 = mf_render_character(font, 0, 0, c2, fit_leftedge, &leftedge); + + /* Find the minimum horizontal space between the glyphs. */ + min_space = 255; + for (i = 0; i < MF_KERNING_ZONES; i++) + { + uint8_t space; + if (leftedge.edgepos[i] == 255 || rightedge.edgepos[i] == 0) + continue; /* Outside glyph area. */ + + space = w1 - rightedge.edgepos[i] + leftedge.edgepos[i]; + if (space < min_space) + min_space = space; + } + + if (min_space == 255) + return 0; /* One of the characters is space, or both are punctuation. */ + + /* Compute the adjustment of the glyph position. */ + normal_space = avg16(w1, w2) * MF_KERNING_SPACE_PERCENT / 100; + normal_space += MF_KERNING_SPACE_PIXELS; + adjust = normal_space - min_space; + max_adjust = -max16(w1, w2) * MF_KERNING_LIMIT / 100; + + if (adjust > 0) adjust = 0; + if (adjust < max_adjust) adjust = max_adjust; + + return adjust; +} + +#endif diff --git a/src/gdisp/mcufont/mf_kerning.h b/src/gdisp/mcufont/mf_kerning.h new file mode 100644 index 00000000..ed885162 --- /dev/null +++ b/src/gdisp/mcufont/mf_kerning.h @@ -0,0 +1,29 @@ +/* Automatic kerning for font rendering. This solves the issue where some + * fonts (especially serif fonts) have too much space between specific + * character pairs, like WA or L'. + */ + +#ifndef _MF_KERNING_H_ +#define _MF_KERNING_H_ + +#include "mf_config.h" +#include "mf_rlefont.h" + +/* Compute the kerning adjustment when c1 is followed by c2. + * + * font: Pointer to the font definition. + * c1: The previous character. + * c2: The next character to render. + * + * Returns the offset to add to the x position for c2. + */ +#if MF_USE_KERNING +MF_EXTERN int8_t mf_compute_kerning(const struct mf_font_s *font, + mf_char c1, mf_char c2); +#else +static int8_t mf_compute_kerning(const struct mf_font_s *font, + mf_char c1, mf_char c2) +{ return 0; } +#endif + +#endif
\ No newline at end of file diff --git a/src/gdisp/mcufont/mf_rlefont.c b/src/gdisp/mcufont/mf_rlefont.c new file mode 100644 index 00000000..8afaa9c2 --- /dev/null +++ b/src/gdisp/mcufont/mf_rlefont.c @@ -0,0 +1,282 @@ +#include "mf_rlefont.h" + +/* Number of reserved codes before the dictionary entries. */ +#define DICT_START 24 + +/* Special reference to mean "fill with zeros to the end of the glyph" */ +#define REF_FILLZEROS 16 + +/* RLE codes */ +#define RLE_CODEMASK 0xC0 +#define RLE_VALMASK 0x3F +#define RLE_ZEROS 0x00 +#define RLE_64ZEROS 0x40 +#define RLE_ONES 0x80 +#define RLE_SHADE 0xC0 + +/* Dictionary "fill entries" for encoding bits directly. */ +#define DICT_START7BIT 4 +#define DICT_START6BIT 132 +#define DICT_START5BIT 196 +#define DICT_START4BIT 228 +#define DICT_START3BIT 244 +#define DICT_START2BIT 252 + +/* Find a pointer to the glyph matching a given character by searching + * through the character ranges. If the character is not found, return + * pointer to the default glyph. + */ +static const uint8_t *find_glyph(const struct mf_rlefont_s *font, + uint16_t character) +{ + unsigned i, index; + const struct mf_rlefont_char_range_s *range; + for (i = 0; i < font->char_range_count; i++) + { + range = &font->char_ranges[i]; + index = character - range->first_char; + if (character >= range->first_char && index < range->char_count) + { + uint16_t offset = range->glyph_offsets[index]; + return &range->glyph_data[offset]; + } + } + + return 0; +} + +/* Structure to keep track of coordinates of the next pixel to be written, + * and also the bounds of the character. */ +struct renderstate_r +{ + int16_t x_begin; + int16_t x_end; + int16_t x; + int16_t y; + int16_t y_end; + mf_pixel_callback_t callback; + void *state; +}; + +/* Call the callback to write one pixel to screen, and advance to next + * pixel position. */ +static void write_pixels(struct renderstate_r *rstate, uint16_t count, + uint8_t alpha) +{ + uint8_t rowlen; + + /* Write row-by-row if the run spans multiple rows. */ + while (rstate->x + count >= rstate->x_end) + { + rowlen = rstate->x_end - rstate->x; + rstate->callback(rstate->x, rstate->y, rowlen, alpha, rstate->state); + count -= rowlen; + rstate->x = rstate->x_begin; + rstate->y++; + } + + /* Write the remaining part */ + if (count) + { + rstate->callback(rstate->x, rstate->y, count, alpha, rstate->state); + rstate->x += count; + } +} + +/* Skip the given number of pixels (0 alpha) */ +static void skip_pixels(struct renderstate_r *rstate, uint16_t count) +{ + rstate->x += count; + while (rstate->x >= rstate->x_end) + { + rstate->x -= rstate->x_end - rstate->x_begin; + rstate->y++; + } +} + +/* Decode and write out a RLE-encoded dictionary entry. */ +static void write_rle_dictentry(const struct mf_rlefont_s *font, + struct renderstate_r *rstate, + uint8_t index) +{ + uint16_t offset = font->dictionary_offsets[index]; + uint16_t length = font->dictionary_offsets[index + 1] - offset; + uint16_t i; + + for (i = 0; i < length; i++) + { + uint8_t code = font->dictionary_data[offset + i]; + if ((code & RLE_CODEMASK) == RLE_ZEROS) + { + skip_pixels(rstate, code & RLE_VALMASK); + } + else if ((code & RLE_CODEMASK) == RLE_64ZEROS) + { + skip_pixels(rstate, ((code & RLE_VALMASK) + 1) * 64); + } + else if ((code & RLE_CODEMASK) == RLE_ONES) + { + write_pixels(rstate, (code & RLE_VALMASK) + 1, 255); + } + else if ((code & RLE_CODEMASK) == RLE_SHADE) + { + uint8_t count, alpha; + count = ((code & RLE_VALMASK) >> 4) + 1; + alpha = ((code & RLE_VALMASK) & 0xF) * 0x11; + write_pixels(rstate, count, alpha); + } + } +} + +/* Get bit count for the "fill entries" */ +static uint8_t fillentry_bitcount(uint8_t index) +{ + if (index >= DICT_START2BIT) + return 2; + else if (index >= DICT_START3BIT) + return 3; + else if (index >= DICT_START4BIT) + return 4; + else if (index >= DICT_START5BIT) + return 5; + else if (index >= DICT_START6BIT) + return 6; + else + return 7; +} + +/* Decode and write out a direct binary codeword */ +static void write_bin_codeword(const struct mf_rlefont_s *font, + struct renderstate_r *rstate, + uint8_t code) +{ + uint8_t bitcount = fillentry_bitcount(code); + uint8_t byte = code - DICT_START7BIT; + uint8_t runlen = 0; + + while (bitcount--) + { + if (byte & 1) + { + runlen++; + } + else + { + if (runlen) + { + write_pixels(rstate, runlen, 255); + runlen = 0; + } + + skip_pixels(rstate, 1); + } + + byte >>= 1; + } + + if (runlen) + write_pixels(rstate, runlen, 255); +} + +/* Decode and write out a reference codeword */ +static void write_ref_codeword(const struct mf_rlefont_s *font, + struct renderstate_r *rstate, + uint8_t code) +{ + if (code <= 15) + { + write_pixels(rstate, 1, 0x11 * code); + } + else if (code == REF_FILLZEROS) + { + /* Fill with zeroes to end */ + rstate->y = rstate->y_end; + } + else if (code < DICT_START) + { + /* Reserved */ + } + else if (code < DICT_START + font->rle_entry_count) + { + write_rle_dictentry(font, rstate, code - DICT_START); + } + else + { + write_bin_codeword(font, rstate, code); + } +} + +/* Decode and write out a reference encoded dictionary entry. */ +static void write_ref_dictentry(const struct mf_rlefont_s *font, + struct renderstate_r *rstate, + uint8_t index) +{ + uint16_t offset = font->dictionary_offsets[index]; + uint16_t length = font->dictionary_offsets[index + 1] - offset; + uint16_t i; + + for (i = 0; i < length; i++) + { + uint8_t code = font->dictionary_data[offset + i]; + write_ref_codeword(font, rstate, code); + } +} + +/* Decode and write out an arbitrary glyph codeword */ +static void write_glyph_codeword(const struct mf_rlefont_s *font, + struct renderstate_r *rstate, + uint8_t code) +{ + if (code >= DICT_START + font->rle_entry_count && + code < DICT_START + font->dict_entry_count) + { + write_ref_dictentry(font, rstate, code - DICT_START); + } + else + { + write_ref_codeword(font, rstate, code); + } +} + + +uint8_t mf_rlefont_render_character(const struct mf_font_s *font, + int16_t x0, int16_t y0, + uint16_t character, + mf_pixel_callback_t callback, + void *state) +{ + const uint8_t *p; + uint8_t width; + + struct renderstate_r rstate; + rstate.x_begin = x0; + rstate.x_end = x0 + font->width; + rstate.x = x0; + rstate.y = y0; + rstate.y_end = y0 + font->height; + rstate.callback = callback; + rstate.state = state; + + p = find_glyph((struct mf_rlefont_s*)font, character); + if (!p) + return 0; + + width = *p++; + while (rstate.y < rstate.y_end) + { + write_glyph_codeword((struct mf_rlefont_s*)font, &rstate, *p++); + } + + return width; +} + +uint8_t mf_rlefont_character_width(const struct mf_font_s *font, + uint16_t character) +{ + const uint8_t *p; + p = find_glyph((struct mf_rlefont_s*)font, character); + if (!p) + return 0; + + return *p; +} diff --git a/src/gdisp/mcufont/mf_rlefont.h b/src/gdisp/mcufont/mf_rlefont.h new file mode 100644 index 00000000..d8275d5c --- /dev/null +++ b/src/gdisp/mcufont/mf_rlefont.h @@ -0,0 +1,74 @@ +/* A compressed font format based on run length encoding and dictionary + * compression. + */ + +#ifndef _MF_RLEFONT_H_ +#define _MF_RLEFONT_H_ + +#include "mf_font.h" + +/* Versions of the RLE font format that are supported. */ +#define MF_RLEFONT_VERSION_4_SUPPORTED 1 + +/* Structure for a range of characters. This implements a sparse storage of + * character indices, so that you can e.g. pick a 100 characters in the middle + * of the UTF16 range and just store them. */ +struct mf_rlefont_char_range_s +{ + /* The number of the first character in this range. */ + uint16_t first_char; + + /* The total count of characters in this range. */ + uint16_t char_count; + + /* Lookup table with the start indices into glyph_data. */ + const uint16_t *glyph_offsets; + + /* The encoded glyph data for glyphs in this range. */ + const uint8_t *glyph_data; +}; + +/* Structure for a single encoded font. */ +struct mf_rlefont_s +{ + struct mf_font_s font; + + /* Version of the font definition used. */ + const uint8_t version; + + /* Big array of the data for all the dictionary entries. */ + const uint8_t *dictionary_data; + + /* Lookup table with the start indices into dictionary_data. + * Contains N+1 entries, so that the length of the entry can + * be determined by subtracting from the next offset. */ + const uint16_t *dictionary_offsets; + + /* Number of dictionary entries using the RLE encoding. + * Entries starting at this index use the dictionary encoding. */ + const uint8_t rle_entry_count; + + /* Total number of dictionary entries. + * Entries after this are nonexistent. */ + const uint8_t dict_entry_count; + + /* Number of discontinuous character ranges */ + const uint8_t char_range_count; + + /* Array of the character ranges */ + const struct mf_rlefont_char_range_s *char_ranges; +}; + +#ifdef MF_RLEFONT_INTERNALS +/* Internal functions, don't use these directly. */ +MF_EXTERN uint8_t mf_rlefont_render_character(const struct mf_font_s *font, + int16_t x0, int16_t y0, + mf_char character, + mf_pixel_callback_t callback, + void *state); + +MF_EXTERN uint8_t mf_rlefont_character_width(const struct mf_font_s *font, + mf_char character); +#endif + +#endif diff --git a/src/gdisp/mcufont/mf_scaledfont.c b/src/gdisp/mcufont/mf_scaledfont.c new file mode 100644 index 00000000..da2b31fe --- /dev/null +++ b/src/gdisp/mcufont/mf_scaledfont.c @@ -0,0 +1,83 @@ +#include "mf_scaledfont.h" + +struct scaled_renderstate +{ + mf_pixel_callback_t orig_callback; + void *orig_state; + uint8_t x_scale; + uint8_t y_scale; + int16_t x0; + int16_t y0; +}; + +static void scaled_pixel_callback(int16_t x, int16_t y, uint8_t count, + uint8_t alpha, void *state) +{ + struct scaled_renderstate *rstate = state; + uint8_t dy; + + count *= rstate->x_scale; + x = rstate->x0 + x * rstate->x_scale; + y = rstate->y0 + y * rstate->y_scale; + + for (dy = 0; dy < rstate->y_scale; dy++) + { + rstate->orig_callback(x, y + dy, count, alpha, rstate->orig_state); + } +} + +static uint8_t scaled_character_width(const struct mf_font_s *font, + mf_char character) +{ + struct mf_scaledfont_s *sfont = (struct mf_scaledfont_s*)font; + uint8_t basewidth; + + basewidth = sfont->basefont->character_width(sfont->basefont, character); + + return sfont->x_scale * basewidth; +} + +static uint8_t scaled_render_character(const struct mf_font_s *font, + int16_t x0, int16_t y0, + mf_char character, + mf_pixel_callback_t callback, + void *state) +{ + struct mf_scaledfont_s *sfont = (struct mf_scaledfont_s*)font; + struct scaled_renderstate rstate; + uint8_t basewidth; + + rstate.orig_callback = callback; + rstate.orig_state = state; + rstate.x_scale = sfont->x_scale; + rstate.y_scale = sfont->y_scale; + rstate.x0 = x0; + rstate.y0 = y0; + + basewidth = sfont->basefont->render_character(sfont->basefont, 0, 0, + character, scaled_pixel_callback, &rstate); + + return sfont->x_scale * basewidth; +} + +void mf_scale_font(struct mf_scaledfont_s *newfont, + const struct mf_font_s *basefont, + uint8_t x_scale, uint8_t y_scale) +{ + newfont->font = *basefont; + newfont->basefont = basefont; + + newfont->font.width *= x_scale; + newfont->font.height *= y_scale; + newfont->font.baseline_x *= x_scale; + newfont->font.baseline_y *= y_scale; + newfont->font.min_x_advance *= x_scale; + newfont->font.max_x_advance *= x_scale; + newfont->font.line_height *= y_scale; + newfont->font.character_width = &scaled_character_width; + newfont->font.render_character = &scaled_render_character; + + newfont->x_scale = x_scale; + newfont->y_scale = y_scale; +} + diff --git a/src/gdisp/mcufont/mf_scaledfont.h b/src/gdisp/mcufont/mf_scaledfont.h new file mode 100644 index 00000000..f6607010 --- /dev/null +++ b/src/gdisp/mcufont/mf_scaledfont.h @@ -0,0 +1,23 @@ +/* Generate scaled (nearest-neighbor) fonts. This can be used for displaying + * larger text without spending the memory required for including larger fonts. + */ + +#ifndef _MF_SCALEDFONT_H_ +#define _MF_SCALEDFONT_H_ + +#include "mf_font.h" + +struct mf_scaledfont_s +{ + struct mf_font_s font; + + const struct mf_font_s *basefont; + uint8_t x_scale; + uint8_t y_scale; +}; + +MF_EXTERN void mf_scale_font(struct mf_scaledfont_s *newfont, + const struct mf_font_s *basefont, + uint8_t x_scale, uint8_t y_scale); + +#endif diff --git a/src/gdisp/mcufont/mf_wordwrap.c b/src/gdisp/mcufont/mf_wordwrap.c new file mode 100644 index 00000000..cc735a4e --- /dev/null +++ b/src/gdisp/mcufont/mf_wordwrap.c @@ -0,0 +1,290 @@ +#include "mf_wordwrap.h" + +/* Returns true if the line can be broken at this character. */ +static bool is_wrap_space(uint16_t c) +{ + return c == ' ' || c == '\n' || c == '\t' || c == '\r' || c == '-'; +} + +#if MF_USE_ADVANCED_WORDWRAP + +/* Represents a single word and the whitespace after it. */ +struct wordlen_s +{ + int16_t word; /* Length of the word in pixels. */ + int16_t space; /* Length of the whitespace in pixels. */ + uint16_t chars; /* Number of characters in word + space, combined. */ +}; + +/* Take the next word from the string and compute its width. + * Returns true if the word ends in a linebreak. */ +static bool get_wordlen(const struct mf_font_s *font, mf_str *text, + struct wordlen_s *result) +{ + mf_char c; + mf_str prev; + + result->word = 0; + result->space = 0; + result->chars = 0; + + c = mf_getchar(text); + while (c && !is_wrap_space(c)) + { + result->chars++; + result->word += mf_character_width(font, c); + c = mf_getchar(text); + } + + prev = *text; + while (c && is_wrap_space(c)) + { + result->chars++; + + if (c == ' ') + result->space += mf_character_width(font, c); + else if (c == '\t') + result->space += mf_character_width(font, 'm') * MF_TABSIZE; + else if (c == '\n') + break; + + prev = *text; + c = mf_getchar(text); + } + + /* The last loop reads the first character of next word, put it back. */ + if (c) + *text = prev; + + return (c == '\0' || c == '\n'); +} + +/* Represents the rendered length for a single line. */ +struct linelen_s +{ + mf_str start; /* Start of the text for line. */ + uint16_t chars; /* Total number of characters on the line. */ + int16_t width; /* Total length of all words + whitespace on the line in pixels. */ + bool linebreak; /* True if line ends in a linebreak */ + struct wordlen_s last_word; /* Last word on the line. */ + struct wordlen_s last_word_2; /* Second to last word on the line. */ +}; + +/* Append word onto the line if it fits. If it would overflow, don't add and + * return false. */ +static bool append_word(const struct mf_font_s *font, int16_t width, + struct linelen_s *current, mf_str *text) +{ + mf_str tmp = *text; + struct wordlen_s wordlen; + bool linebreak; + + linebreak = get_wordlen(font, &tmp, &wordlen); + + if (current->width + wordlen.word <= width) + { + *text = tmp; + current->last_word_2 = current->last_word; + current->last_word = wordlen; + current->linebreak = linebreak; + current->chars += wordlen.chars; + current->width += wordlen.word + wordlen.space; + return true; + } + else + { + return false; + } +} + +/* Append a character to the line if it fits. */ +static bool append_char(const struct mf_font_s *font, int16_t width, + struct linelen_s *current, mf_str *text) +{ + mf_str tmp = *text; + mf_char c; + uint16_t w; + + c = mf_getchar(&tmp); + w = mf_character_width(font, c); + + if (current->width + w <= width) + { + *text = tmp; + current->chars++; + current->width += w; + return true; + } + else + { + return false; + } +} + +static int16_t abs16(int16_t x) { return (x > 0) ? x : -x; } +static int32_t sq16(int16_t x) { return (int32_t)x * x; } + +/* Try to balance the lines by potentially moving one word from the previous + * line to the the current one. */ +static void tune_lines(struct linelen_s *current, struct linelen_s *previous, + int16_t max_width) +{ + int16_t curw1, prevw1; + int16_t curw2, prevw2; + int32_t delta1, delta2; + + /* If the lines are rendered as is */ + curw1 = current->width - current->last_word.space; + prevw1 = previous->width - previous->last_word.space; + delta1 = sq16(max_width - prevw1) + sq16(max_width - curw1); + + /* If the last word is moved */ + curw2 = current->width + previous->last_word.word; + prevw2 = previous->width - previous->last_word.word + - previous->last_word.space + - previous->last_word_2.space; + delta2 = sq16(max_width - prevw2) + sq16(max_width - curw2); + + if (delta1 > delta2 && curw2 <= max_width) + { + /* Do the change. */ + uint16_t chars; + + chars = previous->last_word.chars; + previous->chars -= chars; + current->chars += chars; + previous->width -= previous->last_word.word + previous->last_word.space; + current->width += previous->last_word.word + previous->last_word.space; + previous->last_word = previous->last_word_2; + + while (chars--) mf_rewind(¤t->start); + } +} + +void mf_wordwrap(const struct mf_font_s *font, int16_t width, + mf_str text, mf_line_callback_t callback, void *state) +{ + struct linelen_s current = {}; + struct linelen_s previous = {}; + bool full; + + current.start = text; + + while (*text) + { + full = !append_word(font, width, ¤t, &text); + + if (full || current.linebreak) + { + if (!current.chars) + { + /* We have a very long word. We must just cut it off at some + * point. */ + while (append_char(font, width, ¤t, &text)); + } + + if (previous.chars) + { + /* Tune the length and dispatch the previous line. */ + if (!previous.linebreak && !current.linebreak) + tune_lines(¤t, &previous, width); + + if (!callback(previous.start, previous.chars, state)) + return; + } + + previous = current; + current.start = text; + current.chars = 0; + current.width = 0; + current.linebreak = false; + current.last_word.word = 0; + current.last_word.space = 0; + current.last_word.chars = 0; + } + } + + /* Dispatch the last lines. */ + if (previous.chars) + { + if (!callback(previous.start, previous.chars, state)) + return; + } + + if (current.chars) + callback(current.start, current.chars, state); +} + +#else + +void mf_wordwrap(const struct mf_font_s *font, int16_t width, + mf_str text, mf_line_callback_t callback, void *state) +{ + mf_str orig = text; + mf_str linestart; + + /* Current line width and character count */ + int16_t lw_cur = 0, cc_cur = 0; + + /* Previous wrap point */ + int16_t cc_prev; + mf_str ls_prev; + + linestart = text; + + while (*text) + { + cc_prev = 0; + ls_prev = text; + + while (*text) + { + mf_char c; + int16_t new_width; + mf_str tmp; + + tmp = text; + c = mf_getchar(&text); + new_width = lw_cur + mf_character_width(font, c); + + if (c == '\n') + { + cc_prev = cc_cur + 1; + ls_prev = text; + break; + } + + if (new_width > width) + { + text = tmp; + break; + } + + cc_cur++; + lw_cur = new_width; + + if (is_wrap_space(c)) + { + cc_prev = cc_cur; + ls_prev = text; + } + } + + /* Handle unbreakable words */ + if (cc_prev == 0) + { + cc_prev = cc_cur; + ls_prev = text; + } + + if (!callback(linestart, cc_prev, state)) + return; + + linestart = ls_prev; + text = linestart; + lw_cur = 0; + cc_cur = 0; + } +} + +#endif diff --git a/src/gdisp/mcufont/mf_wordwrap.h b/src/gdisp/mcufont/mf_wordwrap.h new file mode 100644 index 00000000..bf3dbcb7 --- /dev/null +++ b/src/gdisp/mcufont/mf_wordwrap.h @@ -0,0 +1,32 @@ +/* Word wrapping algorithm with UTF-8 support. More than just a basic greedy + * word-wrapper: it attempts to balance consecutive lines as pairs. + */ + +#ifndef _MF_WORDWRAP_H_ +#define _MF_WORDWRAP_H_ + +#include "mf_rlefont.h" +#include <stdbool.h> + +/* Callback function for handling each line. + * + * line: Pointer to the beginning of the string for this line. + * count: Number of characters on the line. + * state: Free variable that was passed to wordwrap(). + * + * Returns: true to continue, false to stop after this line. + */ +typedef bool (*mf_line_callback_t) (mf_str line, uint16_t count, + void *state); + +/* Word wrap a piece of text. Calls the callback function for each line. + * + * font: Font to use for metrics. + * width: Maximum line width in pixels. + * text: Pointer to the start of the text to process. + * state: Free variable for caller to use (can be NULL). + */ +MF_EXTERN void mf_wordwrap(const struct mf_font_s *font, int16_t width, + mf_str text, mf_line_callback_t callback, void *state); + +#endif |