From 80d5635e95af7c06a69046e203a7b8b9661b7d3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Tue, 15 Nov 2022 00:13:54 +0100 Subject: [PATCH] tiny font ver --- Makefile | 8 +- fb_7seg.c | 72 ++++++++++++++ fb_7seg.h | 32 +++++++ fb_text.c | 78 +++++++++++++++ fb_text.h | 27 ++++++ font.c | 224 +++++++++++++++++++++++++++++++++++++++++++ font.h | 29 ++++++ framebuffer.c | 158 +++++++++++++++--------------- framebuffer.h | 51 +++++----- framebuffer_config.h | 7 +- main.c | 48 +++++++++- progmem.h | 14 +++ utf8.c | 129 +++++++++++++++++++++++++ utf8.h | 93 ++++++++++++++++++ 14 files changed, 848 insertions(+), 122 deletions(-) create mode 100644 fb_7seg.c create mode 100644 fb_7seg.h create mode 100644 fb_text.c create mode 100644 fb_text.h create mode 100644 font.c create mode 100644 font.h create mode 100644 progmem.h create mode 100644 utf8.c create mode 100644 utf8.h diff --git a/Makefile b/Makefile index 1db62aa..e3f9d45 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,12 @@ .PHONY: build run -build: ufb-test +all: ufb-test -run: build +run: ufb-test ./ufb-test clean: rm -f ufb-test -ufb-test: main.c framebuffer.c framebuffer.h framebuffer_config.h - cc -o $@ $^ -I. +ufb-test: main.c framebuffer.c framebuffer.h framebuffer_config.h utf8.c utf8.h progmem.h font.c font.h fb_7seg.c fb_7seg.h fb_text.c fb_text.h + $(CC) -o $@ $^ -I. diff --git a/fb_7seg.c b/fb_7seg.c new file mode 100644 index 0000000..d14003d --- /dev/null +++ b/fb_7seg.c @@ -0,0 +1,72 @@ +#include "fb_7seg.h" +#include "progmem.h" + +enum SevenSegBars { + T = 1, RT = 2, RB = 4, B = 8, LB = 16, LT = 32, M = 64 +}; + +static const uint8_t PROGMEM seven[] = { + [0] = T | RT | RB | B | LB | LT, + [1] = RT | RB, + [2] = T | RT | M | LB | B, + [3] = T | RT | M | RB | B, + [4] = RT | RB | M | LT, + [5] = T | LT | M | RB | B, + [6] = T | LT | LB | B | RB | M, + [7] = T | RT | RB, + [8] = T | LT | RT | LB | RB | B | M, + [9] = T | LT | RT | RB | B | M, +}; + +fbpos_t fb_7seg_dig(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, fbpos_t th, uint8_t digit, fbcolor_t color) +{ + const uint8_t mask = digit > 9 ? 0 : pgm_read_byte(&seven[digit]); + const fbpos_t wi = w - th * 2; + const fbpos_t hi = (h - th * 3) / 2; + + bool bcolor = !color; // changed for XOR + + fb_rect(x + th, + y, + wi, + th, bcolor ^ (bool) (mask & T)); + + fb_rect(x + th, + y + th + hi, + wi, + th, bcolor ^ (bool) (mask & M)); + + fb_rect(x + th, + y + th * 2 + hi * 2, + wi, + th, bcolor ^ (bool) (mask & B)); + + fb_rect(x, + y + th, + th, + hi, bcolor ^ (bool) (mask & LT)); + + fb_rect(x + th + wi, + y + hi + th, + th, + hi, bcolor ^ (bool) (mask & LB)); + + fb_rect(x + th + wi, + y + th, + th, + hi, bcolor ^ (bool) (mask & RT)); + + fb_rect(x + th + wi, + y + th * 2 + hi, + th, + hi, bcolor ^ (bool) (mask & RB)); + + return w; +} + +fbpos_t fb_7seg_period(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, fbpos_t th, fbcolor_t color) +{ + const fbpos_t hi = (h - th * 3) / 2; + fb_rect(x, y + hi * 2 + th * 2, th, th, color); + return th; +} diff --git a/fb_7seg.h b/fb_7seg.h new file mode 100644 index 0000000..e4ffe19 --- /dev/null +++ b/fb_7seg.h @@ -0,0 +1,32 @@ +/** + * Draw 7-seg digits to the framebuffer + */ + +#ifndef FB_7SEG_H +#define FB_7SEG_H + +#include "framebuffer.h" + +/// Draw a 7-segment digit. Returns its width (without spacing) +/// +/// \param x - pos X (left top) +/// \param y - pos Y (left top) +/// \param w - full digit width +/// \param h - full digit height; will be adjusted down if needed +/// \param th - thickness +/// \param digit - digit 0-9 +/// \return width taken +fbpos_t fb_7seg_dig(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, fbpos_t th, uint8_t digit, fbcolor_t color); + +/// Draw a 7-segment period. Returns its width (without spacing). +/// Digit height is (w * 2 - th) +/// +/// \param x - pos X (digit left top) +/// \param y - pos Y (digit left top) +/// \param w - full digit width +/// \param h - full digit height; will be adjusted down if needed +/// \param th - thickness +/// \return width taken +fbpos_t fb_7seg_period(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, fbpos_t th, fbcolor_t color); + +#endif //FB_7SEG_H diff --git a/fb_text.c b/fb_text.c new file mode 100644 index 0000000..91487d5 --- /dev/null +++ b/fb_text.c @@ -0,0 +1,78 @@ +/** + * TODO file description + */ + +#include "fb_text.h" +#include "framebuffer.h" +#include "utf8.h" +#include "font.h" + +void fb_text_P_or_RAM(fbpos_t x, fbpos_t y, const char *text, uint8_t flags, fbcolor_t color, bool is_pgmem) { + struct Utf8Iterator iter; + Utf8Iterator_Init(&iter, text); + iter.is_progmem = is_pgmem; + + struct Utf8Char uchar; + + uint8_t symw; + uint8_t symh; + if (flags & FONT_TINY) { + symw = 4; + symh = 5; + } else { + symw = 5; + symh = 7; + } + + while ((uchar = Utf8Iterator_Next(&iter)).uint) { + const uint8_t *data; + if (flags & FONT_TINY) { + const font4x_bitmap_t *sym = font45_getsym(&uchar); + data = sym->data; + } else { + const font5x_bitmap_t *sym = font57_getsym(&uchar); + data = sym->data; + } + + if (0 == (flags & FONT_DOUBLE)) { + // no double, using normal format + fb_bitmap_P(x, y, symw, symh, data, color); + if (flags & FONT_BOLD) { + x++; + fb_bitmap_P(x, y, symw, symh, data, color); + x += symw+2; + } else { + x += symw+1; + } + } else { + // slow, pixel-by-pixel drawing + const uint8_t pxw = (flags & FONT_DOUBLEW) ? 2 : 1; + const uint8_t pxh = (flags & FONT_DOUBLEH) ? 2 : 1; + for(fbpos_t xx = 0; xx < symw; xx++) { + uint8_t column = pgm_read_byte(&data[xx]); + for(fbpos_t yy = 0; yy < symh; yy++) { + if (column & (1 << yy)) { + fb_rect(x + xx * pxw, y + yy * pxh, pxw, pxh, color); + if (flags & FONT_BOLD) { + fb_rect(x + (xx + 1) * pxw, y + yy * pxh, pxw, pxh, color); + } + } + } + } + if (flags & FONT_BOLD) { + x += (symw+2) * pxw; + } else { + x += (symw+1) * pxw; + } + } + } +} + +void fb_text_P(fbpos_t x, fbpos_t y, const char *text, uint8_t flags, fbcolor_t color) +{ + fb_text_P_or_RAM(x, y, text, flags, color, true); +} + +void fb_text(fbpos_t x, fbpos_t y, const char *text, uint8_t flags, fbcolor_t color) { + fb_text_P_or_RAM(x, y, text, flags, color, false); +} diff --git a/fb_text.h b/fb_text.h new file mode 100644 index 0000000..c6edea3 --- /dev/null +++ b/fb_text.h @@ -0,0 +1,27 @@ +/** + * Draw text + */ + +#ifndef UFB_FB_TEXT_H +#define UFB_FB_TEXT_H + +#include "framebuffer.h" + +/// Use tiny font 5x4, supports only digits and select symbols +#define FONT_TINY 8 +/// Use bold variant (each character is printed twice, 1px offset) +#define FONT_BOLD 1 +/// Print characters 2x wider +#define FONT_DOUBLEW 2 +/// Print characters 2x taller +#define FONT_DOUBLEH 4 +/// Print characters 2x wider and taller +#define FONT_DOUBLE (FONT_DOUBLEW | FONT_DOUBLEH) + +/// Print text stored in flash +void fb_text_P(fbpos_t x, fbpos_t y, const char *text, uint8_t flags, fbcolor_t color); + +/// Print text stored in RAM +void fb_text(fbpos_t x, fbpos_t y, const char *text, uint8_t flags, fbcolor_t color); + +#endif //UFB_FB_TEXT_H diff --git a/font.c b/font.c new file mode 100644 index 0000000..ac551cf --- /dev/null +++ b/font.c @@ -0,0 +1,224 @@ +#include "font.h" +#include "utf8.h" +#include "progmem.h" +#include + +#define FONT_EXTRAS_START (0x7e - 0x20 + 1) + +struct utf_glyph5x { + union { + const char symbol[4]; + /// symbol as uint, but not decoded - just for matching! + const uint32_t uint; + }; + font5x_bitmap_t graphic; +}; + +struct utf_glyph4x { + union { + const char symbol[4]; + const uint32_t uint; + }; + font4x_bitmap_t graphic; +}; + +#define F57_NUM_ASCII 95 +#define F57_ASCII_START 0x20 +#define F57_ASCII_END 0x7e + +// ASCII symbols are stored as bare graphic to reduce ROM size +static const font5x_bitmap_t PROGMEM font57_ascii[F57_NUM_ASCII] = { + {{0x00, 0x00, 0x00, 0x00, 0x00}}, // 0x20 + {{0x00, 0x00, 0x5f, 0x00, 0x00}}, // ! 0x21 + {{0x00, 0x07, 0x00, 0x07, 0x00}}, // \" 0x22 + {{0x14, 0x7f, 0x14, 0x7f, 0x14}}, // # 0x23 + {{0x24, 0x2a, 0x7f, 0x2a, 0x12}}, // $ 0x24 + {{0x23, 0x13, 0x08, 0x64, 0x62}}, // % 0x25 + {{0x36, 0x49, 0x55, 0x22, 0x50}}, // & 0x26 + {{0x00, 0x05, 0x03, 0x00, 0x00}}, // ' 0x27 + {{0x00, 0x1c, 0x22, 0x41, 0x00}}, // ( 0x28 + {{0x00, 0x41, 0x22, 0x1c, 0x00}}, // ) 0x29 + {{0x14, 0x08, 0x3e, 0x08, 0x14}}, // * 0x2a + {{0x08, 0x08, 0x3e, 0x08, 0x08}}, // + 0x2b + {{0x00, 0x50, 0x30, 0x00, 0x00}}, // , 0x2c + {{0x08, 0x08, 0x08, 0x08, 0x08}}, // - 0x2d + {{0x00, 0x60, 0x60, 0x00, 0x00}}, // . 0x2e + {{0x20, 0x10, 0x08, 0x04, 0x02}}, // / 0x2f + {{0x3e, 0x51, 0x49, 0x45, 0x3e}}, // 0 0x30 + {{0x00, 0x42, 0x7f, 0x40, 0x00}}, // 1 0x31 + {{0x42, 0x61, 0x51, 0x49, 0x46}}, // 2 0x32 + {{0x21, 0x41, 0x45, 0x4b, 0x31}}, // 3 0x33 + {{0x18, 0x14, 0x12, 0x7f, 0x10}}, // 4 0x34 + {{0x27, 0x45, 0x45, 0x45, 0x39}}, // 5 0x35 + {{0x3c, 0x4a, 0x49, 0x49, 0x30}}, // 6 0x36 + {{0x01, 0x71, 0x09, 0x05, 0x03}}, // 7 0x37 + {{0x36, 0x49, 0x49, 0x49, 0x36}}, // 8 0x38 + {{0x06, 0x49, 0x49, 0x29, 0x1e}}, // 9 0x39 + {{0x00, 0x36, 0x36, 0x00, 0x00}}, // : 0x3a + {{0x00, 0x56, 0x36, 0x00, 0x00}}, // ; 0x3b + {{0x08, 0x14, 0x22, 0x41, 0x00}}, // < 0x3c + {{0x14, 0x14, 0x14, 0x14, 0x14}}, // = 0x3d + {{0x00, 0x41, 0x22, 0x14, 0x08}}, // > 0x3e + {{0x02, 0x01, 0x51, 0x09, 0x06}}, // ? 0x3f + {{0x32, 0x49, 0x79, 0x41, 0x3e}}, // @ 0x40 + {{0x7e, 0x11, 0x11, 0x11, 0x7e}}, // A 0x41 + {{0x7f, 0x49, 0x49, 0x49, 0x36}}, // B 0x42 + {{0x3e, 0x41, 0x41, 0x41, 0x22}}, // C 0x43 + {{0x7f, 0x41, 0x41, 0x22, 0x1c}}, // D 0x44 + {{0x7f, 0x49, 0x49, 0x49, 0x41}}, // E 0x45 + {{0x7f, 0x09, 0x09, 0x09, 0x01}}, // F 0x46 + {{0x3e, 0x41, 0x49, 0x49, 0x7a}}, // G 0x47 + {{0x7f, 0x08, 0x08, 0x08, 0x7f}}, // H 0x48 + {{0x00, 0x41, 0x7f, 0x41, 0x00}}, // I 0x49 + {{0x20, 0x40, 0x41, 0x3f, 0x01}}, // J 0x4a + {{0x7f, 0x08, 0x14, 0x22, 0x41}}, // K 0x4b + {{0x7f, 0x40, 0x40, 0x40, 0x40}}, // L 0x4c + {{0x7f, 0x02, 0x0c, 0x02, 0x7f}}, // M 0x4d + {{0x7f, 0x04, 0x08, 0x10, 0x7f}}, // N 0x4e + {{0x3e, 0x41, 0x41, 0x41, 0x3e}}, // O 0x4f + {{0x7f, 0x09, 0x09, 0x09, 0x06}}, // P 0x50 + {{0x3e, 0x41, 0x51, 0x21, 0x5e}}, // Q 0x51 + {{0x7f, 0x09, 0x19, 0x29, 0x46}}, // R 0x52 + {{0x46, 0x49, 0x49, 0x49, 0x31}}, // S 0x53 + {{0x01, 0x01, 0x7f, 0x01, 0x01}}, // T 0x54 + {{0x3f, 0x40, 0x40, 0x40, 0x3f}}, // U 0x55 + {{0x1f, 0x20, 0x40, 0x20, 0x1f}}, // V 0x56 + {{0x3f, 0x40, 0x38, 0x40, 0x3f}}, // W 0x57 + {{0x63, 0x14, 0x08, 0x14, 0x63}}, // X 0x58 + {{0x07, 0x08, 0x70, 0x08, 0x07}}, // Y 0x59 + {{0x61, 0x51, 0x49, 0x45, 0x43}}, // Z 0x5a + {{0x00, 0x7f, 0x41, 0x41, 0x00}}, // [ 0x5b + {{0x02, 0x04, 0x08, 0x10, 0x20}}, // \\ 0x5c + {{0x00, 0x41, 0x41, 0x7f, 0x00}}, // ] 0x5d + {{0x04, 0x02, 0x01, 0x02, 0x04}}, // ^ 0x5e + {{0x40, 0x40, 0x40, 0x40, 0x40}}, // _ 0x5f + {{0x00, 0x01, 0x02, 0x04, 0x00}}, // ` 0x60 + {{0x20, 0x54, 0x54, 0x54, 0x78}}, // a 0x61 + {{0x7f, 0x48, 0x44, 0x44, 0x38}}, // b 0x62 + {{0x38, 0x44, 0x44, 0x44, 0x20}}, // c 0x63 + {{0x38, 0x44, 0x44, 0x48, 0x7f}}, // d 0x64 + {{0x38, 0x54, 0x54, 0x54, 0x18}}, // e 0x65 + {{0x08, 0x7e, 0x09, 0x01, 0x02}}, // f 0x66 + {{0x0c, 0x52, 0x52, 0x52, 0x3e}}, // g 0x67 + {{0x7f, 0x08, 0x04, 0x04, 0x78}}, // h 0x68 + {{0x00, 0x44, 0x7d, 0x40, 0x00}}, // i 0x69 + {{0x20, 0x40, 0x44, 0x3d, 0x00}}, // j 0x6a + {{0x7f, 0x10, 0x28, 0x44, 0x00}}, // k 0x6b + {{0x00, 0x41, 0x7f, 0x40, 0x00}}, // l 0x6c + {{0x7c, 0x04, 0x18, 0x04, 0x78}}, // m 0x6d + {{0x7c, 0x08, 0x04, 0x04, 0x78}}, // n 0x6e + {{0x38, 0x44, 0x44, 0x44, 0x38}}, // o 0x6f + {{0x7c, 0x14, 0x14, 0x14, 0x08}}, // p 0x70 + {{0x08, 0x14, 0x14, 0x18, 0x7c}}, // q 0x71 + {{0x7c, 0x08, 0x04, 0x04, 0x08}}, // r 0x72 + {{0x48, 0x54, 0x54, 0x54, 0x20}}, // s 0x73 + {{0x04, 0x3f, 0x44, 0x40, 0x20}}, // t 0x74 + {{0x3c, 0x40, 0x40, 0x20, 0x7c}}, // u 0x75 + {{0x1c, 0x20, 0x40, 0x20, 0x1c}}, // v 0x76 + {{0x3c, 0x40, 0x30, 0x40, 0x3c}}, // w 0x77 + {{0x44, 0x28, 0x10, 0x28, 0x44}}, // x 0x78 + {{0x0c, 0x50, 0x50, 0x50, 0x3c}}, // y 0x79 + {{0x44, 0x64, 0x54, 0x4c, 0x44}}, // z 0x7a + {{0x00, 0x08, 0x36, 0x41, 0x00}}, // { 0x7b + {{0x00, 0x00, 0x7f, 0x00, 0x00}}, // | 0x7c + {{0x00, 0x41, 0x36, 0x08, 0x00}}, // } 0x7d + {{0x10, 0x08, 0x08, 0x10, 0x08}}, // ~ 0x7e +}; + +#define F57_NUM_EXTRA 16 + +// utf8 characters list ending with empty struct +static const struct utf_glyph5x PROGMEM font57_extra[F57_NUM_EXTRA] = { + {.symbol="�", {{0xFE, 0x82, 0x82, 0x82, 0xFE}}}, // box - error code + {.symbol="×", {{0x22, 0x14, 0x08, 0x14, 0x22}}}, // cross + {.symbol="↑", {{0x08, 0x04, 0x3e, 0x04, 0x08}}}, // arrow_up + {.symbol="↓", {{0x08, 0x10, 0x3e, 0x10, 0x08}}}, // arrow_down + {.symbol="←", {{0x08, 0x1c, 0x2a, 0x08, 0x08}}}, // arrow_left + {.symbol="→", {{0x08, 0x08, 0x2a, 0x1c, 0x08}}}, // arrow_right + {.symbol="⏰", {{0x1c, 0x22, 0x2e, 0x2a, 0x1c}}}, // clock + {.symbol="⌛", {{0x63, 0x55, 0x4d, 0x55, 0x63}}}, // hourglass + {.symbol="☸", {{0x1c, 0x22, 0x2a, 0x22, 0x1c}}}, // wheel + {.symbol="⏎", {{0x10, 0x38, 0x54, 0x10, 0x1e}}}, // return + {.symbol="🌡", {{0x60, 0x9e, 0x81, 0x9e, 0x6a}}}, // thermometer + {.symbol="°", {{0x00, 0x07, 0x05, 0x07, 0x00}}}, // degree + {.symbol="μ", {{0x7C, 0x20, 0x20, 0x10, 0x3C}}}, // micro + {.symbol="🔙", {{0x04, 0x4e, 0x55, 0x44, 0x38}}}, // back + {.symbol="▶", {{0x7f, 0x3e, 0x1c, 0x08, 0x00}}}, // tri_right + {.symbol="◀", {{0x00, 0x08, 0x1c, 0x3e, 0x7f}}}, // tri_left +}; + +#define F45_NUM_ASCII +static const font4x_bitmap_t PROGMEM font45_ascii[F45_NUM_ASCII] = { + {{0x0e, 0x11, 0x11, 0x0e}}, // 0 + {{0x04, 0x12, 0x1f, 0x10}}, // 1 + {{0x12, 0x19, 0x15, 0x12}}, // 2 + {{0x0a, 0x11, 0x15, 0x0a}}, // 3 + {{0x07, 0x04, 0x1e, 0x04}}, // 4 + {{0x17, 0x15, 0x15, 0x09}}, // 5 + {{0x0e, 0x15, 0x15, 0x09}}, // 6 + {{0x11, 0x09, 0x05, 0x03}}, // 7 + {{0x0a, 0x15, 0x15, 0x0a}}, // 8 + {{0x12, 0x15, 0x15, 0x0a}}, // 9 + {{0x00, 0x00, 0x00, 0x00}}, // ' ' +}; + +#define F45_NUM_EXTRA 7 +static const struct utf_glyph4x PROGMEM font45_extra[F45_NUM_EXTRA] = { + {.symbol="�", {{0x1f, 0x11, 0x11, 0x1f}}}, // box - error code + {.symbol="°", {{0x02, 0x05, 0x02, 0x00}}}, + {.symbol="-", {{0x00, 0x04, 0x04, 0x04}}}, + {.symbol="C", {{0x0e, 0x11, 0x11, 0x0a}}}, + {.symbol="P", {{0x1f, 0x05, 0x05, 0x02}}}, + {.symbol="M", {{0x1f, 0x01, 0x02, 0x1f}}}, + {.symbol=".", {{0x00, 0x18, 0x18, 0x00}}}, +}; + +const font5x_bitmap_t *font57_getsym(const struct Utf8Char *ch) +{ + const uint8_t byte0 = ch->bytes[0]; + if (byte0 < F57_ASCII_START) { + // low ASCII is not supported. + goto fail; + } + + if (byte0 <= F57_ASCII_END) { + // valid ASCII at hard positions. + // In this case only the first byte is significant. + return &font57_ascii[byte0 - F57_ASCII_START]; + } + + // search UTF8 + for (uint8_t i = 0; i < F57_NUM_EXTRA; i++) { + const struct utf_glyph5x *sym = &font57_extra[i]; + const uint32_t table_ch = pgm_read_dword(&sym->uint); + if (table_ch == ch->uint) { + return &sym->graphic; + } + } + + fail: + return &font57_extra[0].graphic; // replacement character +} + +const font4x_bitmap_t *font45_getsym(const struct Utf8Char *ch) +{ + const uint8_t byte0 = ch->bytes[0]; + if (byte0 == ' ') { + return &font45_ascii[10]; + } + if (byte0 >= '0' && byte0 <= '9') { + return &font45_ascii[byte0 - '0']; + } + + // search UTF8 + for (uint8_t i = 0; i < F45_NUM_EXTRA; i++) { + const struct utf_glyph4x *sym = &font45_extra[i]; + const uint32_t table_ch = pgm_read_dword(&sym->uint); + if (table_ch == ch->uint) { + return &sym->graphic; + } + } + + fail: + return &font45_extra[0].graphic; // replacement character +} diff --git a/font.h b/font.h new file mode 100644 index 0000000..5457c52 --- /dev/null +++ b/font.h @@ -0,0 +1,29 @@ +/** + * UTF-8 capable bitmap font + * + * Created on 2020/01/04. + */ + +#ifndef GFX_FONT_H +#define GFX_FONT_H + +#include "font.h" +#include "utf8.h" +#include + +typedef struct { + uint8_t data[5]; +} font5x_bitmap_t; + +typedef struct { + uint8_t data[4]; +} font4x_bitmap_t; + +/// Get font graphic for a character. +/// +/// The returned pointer is PROGMEM! +const font5x_bitmap_t *font57_getsym(const struct Utf8Char *ch); + +const font4x_bitmap_t *font45_getsym(const struct Utf8Char *ch); + +#endif //GFX_FONT_H diff --git a/framebuffer.c b/framebuffer.c index 37bdb9b..bef1ae2 100644 --- a/framebuffer.c +++ b/framebuffer.c @@ -3,13 +3,14 @@ // #include "framebuffer.h" +#include "progmem.h" #define MIN(a, b) ((a)>(b)?(b):(a)) #define MAX(a, b) ((a)>(b)?(a):(b)) #include -uint8_t fb[(FBH / 8) * FBW]; +uint8_t fb[FB_LEN]; /** Fill with a vertical pattern, 1 byte */ void fb_fill(uint8_t pattern) @@ -17,7 +18,7 @@ void fb_fill(uint8_t pattern) memset(fb, pattern, sizeof(fb)); } -static void draw_mask(fbsize_t idx, uint8_t mask, uint8_t color) +static void draw_mask(fbsize_t idx, uint8_t mask, fbcolor_t color) { if (color != 0) { fb[idx] |= mask; @@ -26,7 +27,26 @@ static void draw_mask(fbsize_t idx, uint8_t mask, uint8_t color) } } -void fb_px(fbpos_t x, fbpos_t y, uint8_t color) +void fb_fill_pattern(const uint8_t *pattern, uint8_t pattern_len, fbcolor_t color) +{ + uint8_t p = 0; + for (fbsize_t i = 0; i < FB_LEN; i++) { + draw_mask(i, pattern[p], color); + p++; + if (p >= pattern_len) { + p = 0; + } + } +} + +void fb_invert() +{ + for (fbsize_t i = 0; i < FB_LEN; i++) { + fb[i] ^= 0xFF; + } +} + +void fb_px(fbpos_t x, fbpos_t y, fbcolor_t color) { if (x >= FBW || y >= FBH) { return; } const fbpos_t row = y / 8; @@ -48,7 +68,7 @@ uint8_t fb_getpx(fbpos_t x, fbpos_t y) } } -void fb_hline(fbpos_t x, fbpos_t y, fbpos_t w, uint8_t color) +void fb_hline(fbpos_t x, fbpos_t y, fbpos_t w, fbcolor_t color) { if (x >= FBW || y >= FBH) { return; } w = MIN(FBW - x, w); @@ -61,7 +81,7 @@ void fb_hline(fbpos_t x, fbpos_t y, fbpos_t w, uint8_t color) } } -void fb_vline(fbpos_t x, fbpos_t y, fbpos_t h, uint8_t color) +void fb_vline(fbpos_t x, fbpos_t y, fbpos_t h, fbcolor_t color) { if (x >= FBW || y >= FBH) { return; } h = MIN(FBH - y - 1, h); @@ -94,7 +114,7 @@ void fb_vline(fbpos_t x, fbpos_t y, fbpos_t h, uint8_t color) } } -void fb_rect(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, uint8_t color) +void fb_rect(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, fbcolor_t color) { if (x >= FBW || y >= FBH) { return; } w = MIN(FBW - x, w); @@ -146,7 +166,7 @@ void fb_rect(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, uint8_t color) } } -void fb_frame(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, fbpos_t thickness, uint8_t color) +void fb_frame(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, fbpos_t thickness, fbcolor_t color) { if (thickness == 0) { return; @@ -163,7 +183,53 @@ void fb_frame(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, fbpos_t thickness, uin } } -void fb_bitmap(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, const uint8_t *map, uint8_t color) +// setCircle draws a circle centered around x0,y0 with a defined +// radius. The circle can be black or white. And have a line +// thickness ranging from 1 to the radius of the circle. +// This function was grabbed from the SparkFun ColorLCDShield +// library. +void fb_circle(fbpos_t x0, fbpos_t y0, fbpos_t radius, uint8_t thickness, fbcolor_t color) +{ + for (uint8_t r = 0; r < thickness; r++) { + int8_t f = 1 - radius; + fbpos_t ddF_x = 0; + int8_t ddF_y = -2 * radius; + fbpos_t x = 0; + fbpos_t y = radius; + + fb_px(x0, y0 + radius, color); + fb_px(x0 + radius, y0, color); + if (y0 >= radius) { + fb_px(x0, y0 - radius, color); + } + if (x0 >= radius) { + fb_px(x0 - radius, y0, color); + } + + while (x < y) { + if (f >= 0) { + y--; + ddF_y += 2; + f += ddF_y; + } + x++; + ddF_x += 2; + f += ddF_x + 1; + + fb_px(x0 + x, y0 + y, color); + fb_px(x0 - x, y0 + y, color); + fb_px(x0 + x, y0 - y, color); + fb_px(x0 - x, y0 - y, color); + fb_px(x0 + y, y0 + x, color); + fb_px(x0 - y, y0 + x, color); + fb_px(x0 + y, y0 - x, color); + fb_px(x0 - y, y0 - x, color); + } + radius--; + } +} + +void fb_bitmap_P(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, const uint8_t *map, fbcolor_t color) { if (x >= FBW || y >= FBH) { return; } const fbpos_t w0 = w; @@ -176,7 +242,7 @@ void fb_bitmap(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, const uint8_t *map, u if (rowrem + h <= 8) { for (fbpos_t i = 0; i < w; i++) { // all within one cell - const uint8_t mask = (map[i] & (0xFF >> (8 - h))) << rowrem; + const uint8_t mask = (pgm_read_byte(&map[i]) & (0xFF >> (8 - h))) << rowrem; draw_mask(cell + i, mask, color); } return; @@ -187,7 +253,7 @@ void fb_bitmap(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, const uint8_t *map, u // This could be optimized to walk each row of the canvas only once, but the code would get bigger. while (h > 0) { for (fbpos_t i = 0; i < w; i++) { - const uint8_t mask = (map[i + mapc0] & (0xFF >> rowrem)) << rowrem; + const uint8_t mask = (pgm_read_byte(&map[i + mapc0]) & (0xFF >> rowrem)) << rowrem; draw_mask(cell + i, mask, color); } @@ -195,7 +261,7 @@ void fb_bitmap(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, const uint8_t *map, u if (rowrem != 0) { for (fbpos_t i = 0; i < w; i++) { - const uint8_t mask = (map[i + mapc0] & (0xFF << (8 - rowrem))) >> (8 - rowrem); + const uint8_t mask = (pgm_read_byte(&map[i + mapc0]) & (0xFF << (8 - rowrem))) >> (8 - rowrem); draw_mask(cell + i, mask, color); } } @@ -210,73 +276,3 @@ void fb_bitmap(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, const uint8_t *map, u } } - -enum SevenSegBars { - T = 1, RT = 2, RB = 4, B = 8, LB = 16, LT = 32, M = 64 -}; - -static const uint8_t PROGMEM seven[] = { - [0] = T | RT | RB | B | LB | LT, - [1] = RT | RB, - [2] = T | RT | M | LB | B, - [3] = T | RT | M | RB | B, - [4] = RT | RB | M | LT, - [5] = T | LT | M | RB | B, - [6] = T | LT | LB | B | RB | M, - [7] = T | RT | RB, - [8] = T | LT | RT | LB | RB | B | M, - [9] = T | LT | RT | RB | B | M, -}; - -fbpos_t fb_7seg_dig(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, fbpos_t th, uint8_t digit, uint8_t color) -{ - const uint8_t mask = digit > 9 ? 0 : pgm_read_byte(&seven[digit]); - const fbpos_t wi = w - th * 2; - const fbpos_t hi = (h - th * 3) / 2; - - bool bcolor = !color; // changed for XOR - - fb_rect(x + th, - y, - wi, - th, bcolor ^ (bool) (mask & T)); - - fb_rect(x + th, - y + th + hi, - wi, - th, bcolor ^ (bool) (mask & M)); - - fb_rect(x + th, - y + th * 2 + hi * 2, - wi, - th, bcolor ^ (bool) (mask & B)); - - fb_rect(x, - y + th, - th, - hi, bcolor ^ (bool) (mask & LT)); - - fb_rect(x + th + wi, - y + hi + th, - th, - hi, bcolor ^ (bool) (mask & LB)); - - fb_rect(x + th + wi, - y + th, - th, - hi, bcolor ^ (bool) (mask & RT)); - - fb_rect(x + th + wi, - y + th * 2 + hi, - th, - hi, bcolor ^ (bool) (mask & RB)); - - return w; -} - -fbpos_t fb_7seg_period(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, fbpos_t th, uint8_t color) -{ - const fbpos_t hi = (h - th * 3) / 2; - fb_rect(x, y + hi * 2 + th * 2, th, th, color); - return th; -} diff --git a/framebuffer.h b/framebuffer.h index c0cb1f4..f433e5c 100644 --- a/framebuffer.h +++ b/framebuffer.h @@ -15,6 +15,9 @@ typedef uint16_t fbsize_t; typedef uint8_t fbpos_t; +typedef uint8_t fbcolor_t; + +#define FB_LEN ((FBH / 8) * FBW) /// Framebuffer backing array. /// @@ -27,60 +30,50 @@ typedef uint8_t fbpos_t; /// a7 b7 /// /// and more bytes continue rows 8-15 and so on -extern uint8_t fb[(FBH / 8) * FBW]; +extern uint8_t fb[ FB_LEN ]; /// Fill the entire screen with a byte pattern. /// Use 0xFF or 0x00 for a solid fill. Other patterns may be used to create horizontal stripes. void fb_fill(uint8_t pattern); +/// Invert the entire screen +void fb_invert(); + /// Clear the display (fill with 0x00) static inline void fb_clear(void) { fb_fill(0); } +/// Fill screen with a repeating pattern +/// +/// \param pattern - bytes to repeat, PROGMEM +/// \param pattern_len - len of the pattern +void fb_fill_pattern(const uint8_t* pattern, uint8_t pattern_len, fbcolor_t color); + /// Set a single pixel -void fb_px(fbpos_t x, fbpos_t y, uint8_t color); +void fb_px(fbpos_t x, fbpos_t y, fbcolor_t color); /// Get pixel color uint8_t fb_getpx(fbpos_t x, fbpos_t y); /// Draw a horizontal line -void fb_hline(fbpos_t x, fbpos_t y, fbpos_t w, uint8_t color); +void fb_hline(fbpos_t x, fbpos_t y, fbpos_t w, fbcolor_t color); /// Draw a vertical line -void fb_vline(fbpos_t x, fbpos_t y, fbpos_t h, uint8_t color); +void fb_vline(fbpos_t x, fbpos_t y, fbpos_t h, fbcolor_t color); /// Draw a filled rect -void fb_rect(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, uint8_t color); +void fb_rect(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, fbcolor_t color); /// Draw a frame (unfilled rect) -void fb_frame(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, fbpos_t thickness, uint8_t color); +void fb_frame(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, fbpos_t thickness, fbcolor_t color); -/// Draw a bitmap -void fb_bitmap(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, const uint8_t *map, uint8_t color); +/// Draw a bitmap from progmem +void fb_bitmap_P(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, const uint8_t *map, fbcolor_t color); -/// Draw a 7-segment digit. Returns its width (without spacing) -/// -/// \param x - pos X (left top) -/// \param y - pos Y (left top) -/// \param w - full digit width -/// \param h - full digit height; will be adjusted down if needed -/// \param th - thickness -/// \param digit - digit 0-9 -/// \return width taken -fbpos_t fb_7seg_dig(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, fbpos_t th, uint8_t digit, uint8_t color); - -/// Draw a 7-segment period. Returns its width (without spacing). -/// Digit height is (w * 2 - th) -/// -/// \param x - pos X (digit left top) -/// \param y - pos Y (digit left top) -/// \param w - full digit width -/// \param h - full digit height; will be adjusted down if needed -/// \param th - thickness -/// \return width taken -fbpos_t fb_7seg_period(fbpos_t x, fbpos_t y, fbpos_t w, fbpos_t h, fbpos_t th, uint8_t color); +/// Draw a circle +void fb_circle(fbpos_t x, fbpos_t y, fbpos_t r, uint8_t thickness, fbcolor_t color); /// Output the framebuffer array `fb` to the display device. /// diff --git a/framebuffer_config.h b/framebuffer_config.h index 7055619..fb3cc36 100644 --- a/framebuffer_config.h +++ b/framebuffer_config.h @@ -2,12 +2,7 @@ #define FRAMEBUFFER_CONFIG_H /* Tiny framebuffer */ -#define FBW 48 +#define FBW 128 #define FBH 32 -// if built for AVR, uncommend the include and comment the fake defines: -// #include -#define PROGMEM -#define pgm_read_byte(adr) (*adr) - #endif /* FRAMEBUFFER_CONFIG_H */ diff --git a/main.c b/main.c index a00f003..6c1cbdc 100644 --- a/main.c +++ b/main.c @@ -1,6 +1,9 @@ #include #include #include "framebuffer.h" +#include "font.h" +#include "fb_7seg.h" +#include "fb_text.h" void main() { fb_clear(); @@ -72,7 +75,7 @@ void main() { // fb_bitmap(ZIR_W+2, 12, ZIR_W, ZIR_H, zirafa, 1); // fb_bitmap((ZIR_W + 2) * 2, 2, ZIR_W, ZIR_H, zirafa, 1); - fb_frame(0, 0, 48, 32, 2, 1); + fb_frame(0, 0, 128, 32, 2, 1); uint8_t x = 3; uint8_t w = 7; @@ -84,7 +87,48 @@ void main() { x += fb_7seg_period(x, 3, w, h, th, /*color*/ 1) + sp; x += fb_7seg_dig(x, 3, w, h, th, /*val*/ 5, /*color*/ 1) + sp; - //fb_frame(10, 7, 20, 20, 2, 1); + //fb_circle(60, 15, 10, 1, 1); + // fb_circle(60, 15, 5, 1, 1); + + //fb_fill_pattern("\xAA\x55", 2, 0); + + //fb_invert(); + + fb_text(3, 15, "Ahoj→! 43.15 μSv/h", 0, 1); + fb_text(36, 5, "-123456789.0 CPM°", FONT_TINY, 1); + +// struct Utf8Iterator iter; +//// Utf8Iterator_Init_P(&iter, str); +// Utf8Iterator_Init(&iter, str); +// +// struct Utf8Char uchar; +// x = 3; +// while ((uchar = Utf8Iterator_Next(&iter)).uint) { +// const font_bitmap_t *sym = font_getsym(&uchar); +// fb_bitmap_P(x, 15, 5, 8, sym->data, 1); +// x += 7; +// } +// +//uint8_t xx[] = { +// 0x1f, 0x11, 0x11, 0x1f, +// 0x02, 0x05, 0x02, 0x00, +// 0x00, 0x04, 0x04, 0x04, +// 0x0e, 0x11, 0x11, 0x0a, +// 0x1f, 0x05, 0x05, 0x02, +// 0x1f, 0x01, 0x02, 0x1f, +// 0x00, 0x18, 0x18, 0x00, +//}; +// +//for(int i=0;i>i) & 1) << (7-i); +// } +// +// if(i%4==0) printf("\n"); +// printf("0x%02x, ", out); +//} fb_blit(); } diff --git a/progmem.h b/progmem.h new file mode 100644 index 0000000..ed538f1 --- /dev/null +++ b/progmem.h @@ -0,0 +1,14 @@ +/** + * Progmem support + */ + +#ifndef UFB_PROGMEM_H +#define UFB_PROGMEM_H + +// if built for AVR, uncommend the include and comment the fake defines: +// #include +#define PROGMEM +#define pgm_read_byte(adr) (*adr) +#define pgm_read_dword(adr) (*adr) + +#endif //UFB_PROGMEM_H diff --git a/utf8.c b/utf8.c new file mode 100644 index 0000000..f41f196 --- /dev/null +++ b/utf8.c @@ -0,0 +1,129 @@ +#include +#include "utf8.h" + +// +// Created by MightyPork on 2017/08/20. +// +// UTF-8 parser - collects bytes of a code point before writing them +// into a screen cell. +// + +const struct Utf8Char EMPTY_CHAR = (struct Utf8Char) {.uint = 0}; + +// Code Points First Byte Second Byte Third Byte Fourth Byte +// U+0000 - U+007F 00 - 7F +// U+0080 - U+07FF C2 - DF 80 - BF +// U+0800 - U+0FFF E0 *A0 - BF 80 - BF +// U+1000 - U+CFFF E1 - EC 80 - BF 80 - BF +// U+D000 - U+D7FF ED 80 - *9F 80 - BF +// U+E000 - U+FFFF EE - EF 80 - BF 80 - BF +// U+10000 - U+3FFFF F0 *90 - BF 80 - BF 80 - BF +// U+40000 - U+FFFFF F1 - F3 80 - BF 80 - BF 80 - BF +// U+100000 - U+10FFFF F4 80 - *8F 80 - BF 80 - BF + +size_t utf8_strlen(const char *text) +{ + // TODO optimize + struct Utf8Iterator iter; + Utf8Iterator_Init(&iter, text); + size_t num = 0; + while ((Utf8Iterator_Next(&iter)).uint) { + num++; + } + return num; +} + +/** + * Handle a received character + */ +struct Utf8Char Utf8Parser_Handle(struct Utf8Parser *self, char c) +{ + uint8_t *bytes = self->buffer.bytes; + + uint8_t uc = (uint8_t) c; + // collecting unicode glyphs... + if (uc & 0x80) { + if (self->utf_len == 0) { + bytes[0] = uc; + self->utf_j = 1; + + // start + if (uc == 0xC0 || uc == 0xC1 || uc > 0xF4) { + // forbidden start codes + goto fail; + } + + if ((uc & 0xE0) == 0xC0) { + self->utf_len = 2; + } + else if ((uc & 0xF0) == 0xE0) { + self->utf_len = 3; + } + else if ((uc & 0xF8) == 0xF0) { + self->utf_len = 4; + } + else { + // chars over 127 that don't start unicode sequences + goto fail; + } + } + else { + if ((uc & 0xC0) != 0x80) { + bytes[self->utf_j++] = uc; + goto fail; + } + else { + bytes[self->utf_j++] = uc; + if (self->utf_j >= self->utf_len) { + // check for bad sequences - overlong or some other problem + if (bytes[0] == 0xF4 && bytes[1] > 0x8F) goto fail; + if (bytes[0] == 0xF0 && bytes[1] < 0x90) goto fail; + if (bytes[0] == 0xED && bytes[1] > 0x9F) goto fail; + if (bytes[0] == 0xE0 && bytes[1] < 0xA0) goto fail; + + // trap for surrogates - those break javascript + if (bytes[0] == 0xED && bytes[1] >= 0xA0 && bytes[1] <= 0xBF) goto fail; + + goto success; + } + } + } + } + else { + bytes[0] = uc; + goto success; + } + + return EMPTY_CHAR; + +success:; + struct Utf8Char result = self->buffer; + self->buffer.uint = 0; // erase the buffer + self->utf_len = 0; + return result; + +fail: + self->buffer.uint = 0; // erase the buffer + self->utf_len = 0; + return EMPTY_CHAR; +} + +struct Utf8Char Utf8Iterator_Next(struct Utf8Iterator *self) +{ + char c; + struct Utf8Char uchar; + while (1) { + if (self->is_progmem) { + c = pgm_read_byte(self->source++); + } else { + c = *self->source++; + } + if (!c) break; + + uchar = Utf8Parser_Handle(&self->parser, c); + if (uchar.uint) { + return uchar; + } + } + return EMPTY_CHAR; +} diff --git a/utf8.h b/utf8.h new file mode 100644 index 0000000..b85bdb1 --- /dev/null +++ b/utf8.h @@ -0,0 +1,93 @@ +/** + * UTF-8 string parsing and character iteration + * + * Created on 2020/01/04. + */ + +#ifndef LIQUIDTYPE_UTF8_H +#define LIQUIDTYPE_UTF8_H + +#include +#include +#include +#include "progmem.h" + +/** + * UTF-8 encoded character. + */ +struct Utf8Char { + union { + /** character bytes; padded by zero bytes if shorter than 4 */ + uint8_t bytes[4]; + /** u32 view of the bytes */ + uint32_t uint; + }; +}; + +/** UTF8 string parser internal state */ +struct Utf8Parser { + /** UTF-8 bytes buffer */ + struct Utf8Char buffer; + /** Currently collected UTF-8 character length */ + uint8_t utf_len; + /** Position in the current character */ + uint8_t utf_j; +}; + +static inline void Utf8Parser_Clear(struct Utf8Parser *self) { + self->buffer.uint = 0; + self->utf_j = 0; + self->utf_len = 0; +} + +/** + * Utf8 character iterator. + * + * Usage: + * struct Utf8Iterator iter; + * Utf8Iterator_Init(&iter, myString); + * + * union Utf8Char uchar; + * while ((uchar = Utf8Iterator_Next(&iter)).uint) { + * // do something with the char + * } + * + * // Free myString if needed, it is not mutated. + */ +struct Utf8Iterator { + /* Characters to parse. The pointer is advanced as the iterator progresses. */ + const char *source; + struct Utf8Parser parser; + bool is_progmem; +}; + +static inline void Utf8Iterator_Init(struct Utf8Iterator *self, const char *source) { + Utf8Parser_Clear(&self->parser); + self->source = source; + self->is_progmem = false; +} + +static inline void Utf8Iterator_Init_P(struct Utf8Iterator *self, const char *source) { + Utf8Iterator_Init(self, source); + self->is_progmem = true; +} + +size_t utf8_strlen(const char *text); + +/** + * Get the next character from the iterator; Returns empty character if there are no more characters to parse. + * + * Invalid characters are skipped. + */ +struct Utf8Char Utf8Iterator_Next(struct Utf8Iterator *self); + +/** + * Parse a character. + * + * The returned struct contains NIL (uint == 0) if no character is yet available. + * + * ASCII is passed through, utf-8 is collected and returned in one piece. + */ +struct Utf8Char Utf8Parser_Handle(struct Utf8Parser *self, char c); + +#endif //LIQUIDTYPE_UTF8_H