parent
fecadb3821
commit
80d5635e95
@ -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.
|
||||
|
@ -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; |
||||
} |
@ -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
|
@ -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); |
||||
} |
@ -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
|
@ -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 <stdint.h> |
||||
|
||||
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
|
@ -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 <avr/pgmspace.h>
|
||||
#define PROGMEM |
||||
#define pgm_read_byte(adr) (*adr) |
||||
#define pgm_read_dword(adr) (*adr) |
||||
|
||||
#endif //UFB_PROGMEM_H
|
@ -0,0 +1,129 @@ |
||||
#include <stdint.h> |
||||
#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; |
||||
} |
@ -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 <stddef.h> |
||||
#include <stdint.h> |
||||
#include <stdbool.h> |
||||
#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
|
Loading…
Reference in new issue