Ovladač a knihovna v C pro OLED displej SSD1363 na I²c. Verze 0.2.

Displej má napájení 3.3V, odběr až 180mA při bílé ploše.

Práce programu

Zapojení

pin na displeji pin na Picu

GND

libovolná GND (třeba pin 8)

VCC

3V3(OUT) (pin 36)

SDA

GPIO4 (pin 6)

SCL

GPIO5 (pin 7)

RST

GPIO6 (pin 9)

Program interface

Knihovna nepoužívá proměnné na zásobníku, takže je kompatibilní s Protothreads. Nealokuje žádná data na heapu pomocí malloc().

Funkce pro hardware

Globální proměnné a konstanty
#define OLED_WIDTH  256     // šířka displeje v pixelech
#define OLED_HEIGHT 128     // výška displeje v pixelech
#define I2C_ADDRESS 0x3c    // I2C adresa displeje
#define I2C_SDA_PIN 4       // datový pin na Picu
#define I2C_SCL_PIN 5       // hodinový pin na Picu
#define RESET_PIN   6       // resetovací pin na Picu

// definice barev
typedef enum colors {
    BLACK = 0x0,
    GREY1 = 0x1,
    GREY2 = 0x2,
    GREY3 = 0x3,
    GREY4 = 0x4,
    GREY5 = 0x5,
    GREY6 = 0x6,
    GREY7 = 0x7,
    GREY8 = 0x8,
    GREY9 = 0x9,
    GREYA = 0xa,
    GREYB = 0xb,
    GREYC = 0xc,
    GREYD = 0xd,
    GREYE = 0xe,
    WHITE = 0xf
} color_t;

// použité UTF-8 fonty
extern bitmapFONT font_spleen_8x16;
extern bitmapFONT font_spleen_6x12;

// framebuffer
static uint8_t pageBuffer[OLED_WIDTH/2*OLED_HEIGHT+10];
uint8_t *pb = &pageBuffer[1];

Inicializace I2C sběrnice, musí být nastaveny pull-up rezistory na pinech SDA a SCL.

// nastavení I2C sběrnice
void setup_i2c( void )
{
    i2c_init(i2c0, 3300000); // minimum 400kHz podle datasheetu, dosažené maximum 3.3 MHz
    // při 1MHz je přenos framebufferu do displeje 166 ms
    // při 3.3MHz je přenos framebufferu do displeje 62 ms
    gpio_set_function(I2C_SDA_PIN, GPIO_FUNC_I2C);
    gpio_set_function(I2C_SCL_PIN, GPIO_FUNC_I2C);
    gpio_pull_up(I2C_SDA_PIN);
    gpio_pull_up(I2C_SCL_PIN);
}

Inicializace displeje:

void ssd1363_init( void );

Vymazání framebuferu:

// vymazání framebufferu (nastavení na černou barvu)
void ssd1363_clear( void );

Po vymazání framebufferu je potřeba zavolat funkci ssd1363_show(), která dopraví data do displeje.

Zobrazení dat framebufferu na displej:

// přesun dat z framebufferu do displeje
void ssd1363_show( void );

Inverze displeje (prohození černé za bílou, více šedou za méně šedou):

// invertování displeje
void ssd1363_invert( void );

Nastvení displej na normální zobrazení:

// přepnutí na normální displej
void ssd1363_normal( void );

Uspání displeje (program může běžet, ale na displeji nebude nic zobrazeno).

// uspání displeje
void ssd1363_sleep( void );

Probuzení displeje z režimu spánku:

// probuzení displeje
void ssd1363_on( void );

V normálním režimu může být spotřeba displeje a Pica až 186 mA z 5V zdroje (tedy 0.95W). V režimu spánku je spotřeba displeje a Pica okolo 24mA z 5V zdroje (tedy 0,12W).

Funkce pro kreslení

Počátek souřadné soustavy displeje [0,0] je v levém horním rohu. Souřadnice x se zvětšuje doprava a souřadnice y se zvětšuje dolů.

Kreslení bodu o souřadnicích [x,y] do framebufferu, kdo kreslí později je vidět (předchozí data jsou přepsána).

// kreslení do framebufferu natvrdo, kdo kreslí později vyhrál
// parametry: x - souřadnice na displeji
//            y - souřadnice na dislpeji
//            color -- stupěň šedé (0x0 - bílá až 0xf černá)
int drawPixel(int x, int y, color_t color);

Funkce nedovolí zapisovat za šířku displeje (OLED_WIDTH) a za výšku displeje (OLED_HEIGHT). Mimo rozsah nekreslí nic.

Získání barevné hodnoty bodu [x,y] z framebufferu

// získání barvy bodu [x,y] z framebufferu
uint8_t getPixel(int x, int y);

Pokud jsou souřadnice x,y mimo rozsah, funkce vrací 0.

Kreslení libovolné úsečky Bresenhamovým algoritmem:

// kreslení čáry (Bresenham)
// parametry: x0, y0 - souřadnice 1. bodu úsečky
//            x1, y1 - souřadnice 2. bodu úsečky
void drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, color_t color);

Kreslení vodorovné úsečky (je trochu rychlejší než u obecné úsečky):

// kreslení vodorovné čáry
// parametry: x,y - souřadnice levého bodu
//            len - délka v bodech
//            color - barva čáry
void drawHLine( uint16_t x, uint16_t y, uint16_t len, color_t color );

Kreslení svislé úsečky (je trochu rychlejší než u obecné úsečky):

// kreslení svislé čáry
// parametry: x,y - souřadnice horního bodu
//            len - výška v bodech
//            color - barva čáry
void drawVLine( uint16_t x, uint16_t y, uint16_t len, color_t color );

Kreslení obdélníku:

// kreslení prázdného obdélníku
// parametry: x,y - souřadnice levého horního rohu
//            w - šířka obdélníku
//            h - výška obdélníku
//            color - barva obdélníka
void drawRectangle( uint16_t x, uint16_t y, uint16_t w, uint16_t h, color_t color );

Kreslení plného obdélníku:

// kreslení plného obdélníku
// parametry: x0, y0 - souřadnice levého horního rohu
//            x1, y1 - souřadnice pravého dolního rohu
//            color  - barva obdélníka včetně výplně
void drawSquare( int x0, int y0, int x1, int y1, color_t color );

Kreslení kružnice:

// kreslení kružnice
// parametry: xm, ym - souřadnice středu kružnice
//            r - poloměr kružnice
//            color - barva kružnice (0x0 až 0xf)
void drawCircle(int xm, int ym, int r, color_t color);

Kreslení kruhu:

// kreslení plného kruhu
// parametry: xm, ym - souřadnice středu kruhu
//            r - poloměr kruhu
//            color - barva (0x0 až 0xf)
void drawFilledCircle(int xm, int ym, int r, color_t color);

Kreslení UTF-8 znaků a řetězců

Použitý font musí být vložen do CMakeLists.txt a do zdrojového kódu pomocí extení proměnné, např. pro font Spleen 8x16:

CMakeLists.txt
add_executable(ssd1363_test
		ssd1363_test.c
		utf8.c
		font_spleen_8x16.c
    )
extern bitmapFONT font_spleen_8x16;

Kreslení UTF-8 znaku:

// kreslení UTF-8 znaku
// parametry: x, y - souřadníce levého horního rohu písmenka
//            Font - ukazatel na bitmapový font
//            Index - UTF-8 codepoint
//            fcolor - barva písma
//            bcolor - barva pozadí
// vrací šířku písmena
uint16_t drawChar(uint32_t x, uint32_t y, const bitmapFONT* Font, uint32_t Index, color_t fcolor, color_t bcolor );

Kreslení UTF-8 řetězce znaků:

// kreslení  UTF-8 řetězce
// parametry: x,y - levý horní roh řetězce
//            *Font - ukazatel na bitmapový font
//            *pstring - ukazatel naa řetězec (UTF-8)
//            fcolor - barva písma  (0x0 - 0xf)
//            bcolor - barva pozadí (0x0 - 0xf)
// vrací šířku řetězce
uint16_t drawString(uint32_t x, uint32_t y, const bitmapFONT * Font, const char * pString, color_t fcolor, color_t bcolor );

Zdrojové kódy

Knihovna i test knihovny v jednom. Pokud potřebujete jenom knihovnu, odstraňte všechno pod // testování displeje.

ssd1363_test.c
/* ssd1363_test.c
 * (c) Jirka Chráska 2026, <jirka@lixis.cz>
 * BSD 3 clause licence
 * Grafická knihovna a ovladač pro displej SSD1363 256x128 bodů, 16 stupňů šedé
 * verze 0.2
 */

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "font.h"
#include "utf8.h"

#define OLED_WIDTH  256     // šířka displeje v pixelech
#define OLED_HEIGHT 128     // výška displeje v pixelech
#define I2C_ADDRESS 0x3c    // I2C adresa displeje
#define I2C_SDA_PIN 4       // datový pin na Picu
#define I2C_SCL_PIN 5       // hodinový pin na Picu
#define RESET_PIN   6       // resetovací pin na Picu

// definice barev
typedef enum colors {
    BLACK = 0x0, 
    GREY1 = 0x1,
    GREY2 = 0x2,
    GREY3 = 0x3,
    GREY4 = 0x4,
    GREY5 = 0x5,
    GREY6 = 0x6,
    GREY7 = 0x7,
    GREY8 = 0x8,
    GREY9 = 0x9,
    GREYA = 0xa,
    GREYB = 0xb,
    GREYC = 0xc,
    GREYD = 0xd,
    GREYE = 0xe,
    WHITE = 0xf
} color_t;

// příkazy pro displej ssd1363
#define SET_COL_ADDR        0x15
#define SET_ROW_ADDR        0x75
#define CMD_RAM_WRITE       0x5c
#define CMD_RAM_READ        0x5d
#define CMD_RAM_COL_INC     0xa0
#define CMD_RAM_ROW_INC     0xa0


// použité UTF-8 fonty
extern bitmapFONT font_spleen_5x8;
extern bitmapFONT font_spleen_6x12;
extern bitmapFONT font_spleen_8x16;
extern bitmapFONT font_spleen_12x24;
extern bitmapFONT font_spleen_16x32;
extern bitmapFONT font_spleen_32x64;
extern bitmapFONT font_9x15;
extern bitmapFONT font_10x20;
extern bitmapFONT font_tahoma_8;
extern bitmapFONT font_tahoma_10;
extern bitmapFONT font_tahoma_12;
extern bitmapFONT font_tahoma_16;

// framebuffer
static uint8_t pageBuffer[OLED_WIDTH/2*OLED_HEIGHT+10];
uint8_t *pb = &pageBuffer[1];

void ssd1363_show(void);
void ssd1363_clear(void);


// nastavení I2C sběrnice
void setup_i2c( void )
{
    i2c_init(i2c0, 3000000); // minimum 400kHz podle datasheetu, dosažené maximum 3.3 MHz
    // při 1MHz je přenos framebufferu do displeje 166 ms
    // při 3.3MHz je přenos framebufferu do displeje 62 ms
    // pokud displej špatně vykresluje nebo se objevují chyby, snižte frekvenci
    gpio_set_function(I2C_SDA_PIN, GPIO_FUNC_I2C);
    gpio_set_function(I2C_SCL_PIN, GPIO_FUNC_I2C);
    gpio_pull_up(I2C_SDA_PIN);
    gpio_pull_up(I2C_SCL_PIN);
}

// zápis příkazu po I2c sběrnici na displej bez parametru
int wc( uint8_t val )
{
static uint8_t c[2];
    c[0] = 0x0;
    c[1] = val;
return i2c_write_blocking(i2c0, I2C_ADDRESS, c, 2, false);        
}
// zápis příkazu s jedním parametrem
int wca( uint8_t val, uint8_t arg )
{
static uint8_t c[2];
    c[0] = 0x0;
    c[1] = val;
    i2c_write_blocking(i2c0, I2C_ADDRESS, c, 2, true);
    c[0] = 0b01000000;
    c[1] = arg;
return i2c_write_blocking(i2c0, I2C_ADDRESS, c, 2, false);
}

// zápis příkazu se dvěma parametry
int wcaa( uint8_t val, uint8_t arg1, uint8_t arg2 )
{
static uint8_t c[2]; 
    c[0] = 0x00;
    c[1] = val;
    i2c_write_blocking(i2c0, I2C_ADDRESS, c, 2, true);
    c[0] = 0x40;
    c[1] = arg1;
    i2c_write_blocking(i2c0, I2C_ADDRESS, c, 2, true);
    c[0] = 0x40;
    c[1] = arg2;
return   i2c_write_blocking(i2c0, I2C_ADDRESS, c, 2, false);
}
// zápis dat na displej
// parametry: *buf - ukazatel na framebuffer
//            len - délka přenášených dat
int wd( uint8_t *buf, size_t len )
{
static uint8_t c[2] = {0x00,CMD_RAM_WRITE};    
static int ret  = 0;
    ret  = i2c_write_blocking(i2c0, I2C_ADDRESS, c, 2, false);
    ret += i2c_write_blocking(i2c0, I2C_ADDRESS, buf, len, false);
return ret;
}

// nastavení displeje: funguje
void ssd1363_init(void)
{
    wca(0xfa, 0x12);         // command unlock
    wc(0xae);                // display off
    wca(0xb3,0x30);          // clock/oscilator freqency
    wca(0xca,0x7f);          // mux ratio
    wca(0xa2,0x20);          // display offset
    wca(0xa1,0);             // set display start line
    wcaa(0xa0, 0x32, 0x00);  // remap
    wcaa(0xb4, 0x32, 0x0c);  // display enhancement A
    wca(0xc1, 0xfa);         // kontrast
    wca(0xba, 0x03);         // VP
    wc(0xb9);                // normální gamma
    wca(0xad, 0x90);         // interní zdoj svícení
    wca(0xb1, 0x74);         // fáze
    wca(0xba, 0x1);          // pre charge voltage config
    wca(0xbb, 0x0c);         // pre charge voltage level
    wca(0xb6, 0xc0);         // second precharge
    wca(0xbe, 0x04);         // VCOMH
    wc(0xa6);                // normal display 
    wc(0xa9);                // exit partial
    sleep_ms(10);
    // vymazání displeje (černý)
    memset(pageBuffer,0b00000000,sizeof(pageBuffer));
    ssd1363_show();
    wc(0xaf);               // display ON
}

// invertování displeje
void ssd1363_invert( void )
{
    wc(0xa7);                
}

// přepnutí na normální displej
void ssd1363_normal( void )
{
    wc(0xa6);                     
}


// uspání displeje
void ssd1363_sleep( void )
{
    wc(0xae);
}
// probuzení displeje
void ssd1363_on( void )
{
    wc(0xaf);
}

// přesun dat z framebufferu do displeje
void ssd1363_show(void) 
{
    wcaa(SET_COL_ADDR, 0x08, 0x47); // column start address 0  end 79
    wcaa(SET_ROW_ADDR, 0x00, 0x7f); // row start address 0, end address  127
    pageBuffer[0] = 0x40; // před daty musí být bajt 0x40
    wd(&pageBuffer[0],OLED_WIDTH/2*OLED_HEIGHT+1);
}

// zatím nefunguje
void ssd1363_showpartial(void) 
{
    wcaa(0x15, 0x08, 0x47); // column start address 0  end 79
    wcaa(0x75, 0x00, 0x7f); // row start address 0, end address  127
    pageBuffer[0] = 0x40;
    wd(&pageBuffer[0],OLED_WIDTH/2*OLED_HEIGHT+1);
}


// vymazání framebufferu (nastavení na černou barvu)
void ssd1363_clear(void) 
{
    memset(&pageBuffer[1],0,OLED_WIDTH/2*OLED_HEIGHT);
}


// kreslení do framebufferu: světlejší barva má přednost (nepoužito)
int drawPixelN(int x, int y, color_t color)
{
    if( x < 0 || x > OLED_WIDTH  - 1 ) return -1;
    if( y < 0 || y > OLED_HEIGHT - 1 ) return -1;
    switch( x%4 ) {
        case 0: 
            *(pb+(OLED_WIDTH*y+x)/2+1)   |= color;  
            return 0;
        case 1: 
            *(pb+(OLED_WIDTH*y+x)/2+1)   |= color<<4; 
            return 1;
        case 2: 
            *(pb+(OLED_WIDTH*y+x)/2-1)   |= color;  
            return 2;
        case 3: 
            *(pb+(OLED_WIDTH*y+x)/2-1)   |= color<<4; 
            return 3;
        
    }
}

// kreslení do framebufferu natvrdo, kdo kreslí později vyhrál
// parametry: x - souřadnice na displeji 
//            y - souřadnice na dislpeji
//            color -- stupěň šedé (0x0 - bílá až 0xf černá)
int drawPixel(int x, int y, color_t color)
{
    if( x < 0 || x > OLED_WIDTH  - 1 ) return -1;
    if( y < 0 || y > OLED_HEIGHT - 1 ) return -1;
    switch( x%4 ) {
        case 0: 
            *(pb+(OLED_WIDTH*y+x)/2+1)   = (*(pb+(OLED_WIDTH*y+x)/2+1) & 0xf0) | color;  
            return 0;
        case 1: 
            *(pb+(OLED_WIDTH*y+x)/2+1)   = (*(pb+(OLED_WIDTH*y+x)/2+1) & 0x0f) | color<<4; 
            return 1;
        case 2: 
            *(pb+(OLED_WIDTH*y+x)/2-1)   = (*(pb+(OLED_WIDTH*y+x)/2-1) & 0xf0) | color;  
            return 2;
        case 3: 
            *(pb+(OLED_WIDTH*y+x)/2-1)   = (*(pb+(OLED_WIDTH*y+x)/2-1) & 0x0f) | color<<4; 
            return 3;
    }
}

// získání barvy bodu [x,y] z framebufferu
uint8_t getPixel(int x, int y)
{
static uint8_t color = 0;

    if( x < 0 || x > OLED_WIDTH  - 1 ) return 0;
    if( y < 0 || y > OLED_HEIGHT - 1 ) return 0;
    switch( x%4 ) {
        case 0: 
            color =  (*(pb+((OLED_WIDTH*y+x)/2+1)) & 0x0f);
            break;
        case 1: 
            color =  (*(pb+((OLED_WIDTH*y+x)/2+1)) & 0xf0)>>4;
            break;
        case 2: 
            color = (*(pb+((OLED_WIDTH*y+x)/2-1)) & 0x0f);
            break;
        case 3: 
            color =  (*(pb+((OLED_WIDTH*y+x)/2-1)) & 0xf0)>>4;
            break;
    }
return color;
}

// kreslení bodu [x,y] s ohledem na předchozí barvu bodu
// dají se s tím dělat zajímavé efekty
// void drawPixelInv(int x, int y, color_t ncolor)
// {
// static uint8_t color;
// 
//     color = getPixel(x,y);
//     color = (ncolor ^ color) & 0x0f;
//     drawPixel(x,y,color);
// }

void drawPixelInv(int x, int y, color_t ncolor)
{
    drawPixel(x,y,(ncolor ^ getPixel(x,y)) & 0x0f);
}

// kreslení čáry (Bresenham)
// parametry: x0, y0 - souřadnice 1. bodu úsečky
//            x1, y1 - souřadnice 2. bodu úsečky
//            color  - barva úsečky (4bity: 0x0 - černá 0xf - bílá)
void drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, color_t color)
{
static int dx, sx;
static int dy, sy;
static int err, e2; /* error value e_xy */
    dx = abs(x1-x0);
    sx = x0<x1 ? 1 : -1;
    dy = -abs(y1-y0);
    sy = y0<y1 ? 1 : -1;
    err = dx+dy;

   for(;;){  /* loop */
      drawPixel(x0, y0, color);
      if (x0==x1 && y0==y1) break;
      e2 = 2*err;
      if (e2 >= dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */
      if (e2 <= dx) { err += dx; y0 += sy; } /* e_xy+e_y < 0 */
   }
}

// kreslení čáry s antialiasingem
// parametry: x0, y0 - souřadnice 1. bodu úsečky
//            x1, y1 - souřadnice 2. bodu úsečky
//            color  - barva úsečky (4bity: 0x0 - černá 0xf - bílá)
void drawLineAA(int32_t x0, int32_t y0, int32_t x1, int32_t y1, color_t color, int blend)
{
static int dx, sx;
static int dy, sy;
static int x2, err, e2; /* error value e_xy */
static int ed;
    dx = abs( x1-x0 );
    sx = x0<x1 ? 1 : -1;
    dy = abs( y1-y0 );
    sy = y0<y1 ? 1 : -1;
    err = dx-dy;
    ed = (dx+dy) == 0 ? 1 : sqrt((float)dx*dx + (float)dy*dy);

   for( ; ; ) {  
      drawPixel(x0, y0, ((blend*getPixel(x0,y0+sy)+(16-blend)*color)/16) ); // color*abs(err-dx+dy)/ed
      e2 = err; x2 = x0;
      if( 2*e2 >= -dx ) {   // krok x
          if( x0==x1 ) break;
          if( e2+dy < ed ) drawPixel(x0, y0+sy, ((blend*getPixel(x0,y0+sy)+(16-blend)*color)/16) ); // color*(e2+dy)/ed
          err -= dy; x0 += sx;
      }
      if( 2*e2 <= dy ) {  // krok y
          if( y0 == y1 ) break;
          if( dx-e2 < ed ) drawPixel(x2+sx, y0, ((blend*getPixel(x2+sx, y0)+(16-blend)*color)/16) ); // color*(dx-e2)/ed
          err += dx; y0 +=sy;
      }
   }
}

// kreslení vodorovné čáry
// parametry: x,y - souřadnice levého bodu
//            len - délka v bodech
//            color - barva čáry
void drawHLine( uint16_t x, uint16_t y, uint16_t len, color_t color )
{
    for(int i=x ; i<=x+len; i++) {
        int ret = drawPixel(i,y,color);
    }
}

// kreslení svislé čáry
// parametry: x,y - souřadnice horního bodu
//            len - výška v bodech
//            color - barva čáry
void drawVLine( uint16_t x, uint16_t y, uint16_t len, color_t color )
{
    for(int i=y ; i<=y+len; i++) {
        int ret = drawPixel(x,i,color);
    }
}

// kreslení kružnice
// parametry: xm, ym - souřadnice středu kružnice
//            r - poloměr kružnice
//            color - barva kružnice (0x0 až 0xf)
void drawCircle(int xm, int ym, int r, color_t color)
{
static int x, y, err; 
    x = -r;
    y = 0;
    err = 2-2*r; /* II. Quadrant */ 
   do {
      drawPixel(xm-x, ym+y, color); /*   I. Quadrant */
      drawPixel(xm-y, ym-x, color); /*  II. Quadrant */
      drawPixel(xm+x, ym-y, color); /* III. Quadrant */
      drawPixel(xm+y, ym+x, color); /*  IV. Quadrant */
      r = err;
      if (r <= y) err += ++y*2+1;           /* e_xy+e_y < 0 */
      if (r > x || err > y) err += ++x*2+1; /* e_xy+e_x > 0 or no 2nd y-step */
   } while (x < 0);
}
// kreslení plného kruhu
// parametry: xm, ym - souřadnice středu kruhu
//            r - poloměr kruhu
//            color - barva (0x0 až 0xf)
void drawFilledCircle(int xm, int ym, int r, color_t color)
{
static int x, y, err;
    x = -r;
    y = 0;
    err = 2-2*r;  /* II. Quadrant */ 
   do {
       drawLine(xm-x,ym+y,xm+x,ym+y, color);
       drawLine(xm-x,ym-y,xm+x,ym-y, color);
      r = err;
      if (r <= y) err += ++y*2+1;           /* e_xy+e_y < 0 */
      if (r > x || err > y) err += ++x*2+1; /* e_xy+e_x > 0 or no 2nd y-step */
   } while (x < 0);
}

// kreslení plného obdélníku
// parametry: x0, y0 - souřadnice levého horního rohu
//            x1, y1 - souřadnice pravého dolního rohu
//            color  - barva obdélníka včetně výplně
void drawSquare( int x0, int y0, int x1, int y1, color_t color )
{
static int x = 0;
static int y = 0;

    if( x0 > x1 ) {
        x = x1;
        x1 = x0;
        x0 = x;
    }
    if( y0 > y1 ) {
        y = y1;
        y1 = y0;
        y0 = y;
    }
    
    for(x=x0 ; x <= x1; x++) {
        for(y=y0; y <= y1; y++) {
            drawPixel(x,y,color);
        }
    }
}

// kreslení prázdného obdélníku
// parametry: x,y - souřadnice levého horního rohu
//            w - šířka obdélníku
//            h - výška obdélníku
//            color - barva obdélníka
void drawRectangle( uint16_t x, uint16_t y, uint16_t w, uint16_t h, color_t color )
{
    drawHLine(x  , y  , w, color);
    drawHLine(x  , y+h, w, color);
    drawVLine(x  , y  , h, color);
    drawVLine(x+w, y  , h, color);
}

// kreslení UTF-8 znaku
// parametry: x, y - souřadníce levého horního rohu písmenka
//            Font - ukazatel na bitmapový font
//            Index - UTF-8 codepoint
//            fcolor - barva písma
//            bcolor - barva pozadí
// vrací šířku písmena
uint16_t drawChar(uint32_t x, uint32_t y, const bitmapFONT* Font, 
                  uint32_t Index, color_t fcolor, color_t bcolor ) 
{
static uint8_t  row, column, forecolor;  
static uint8_t  col, fcol, zb;
static uint8_t  chwidth;
static uint16_t chsize;
static uint32_t bindex;
static uint8_t  chw;
static int      j = 0;

    if( x > OLED_WIDTH || y > OLED_HEIGHT ) {
	    return 0;
    }
    col    = Font->Width/8; // sloupce v bytech
    fcol   = col;
    zb     = Font->Width%8;

    if( zb != 0) {
        col ++;
    }
    if( Index > Font->Chars ) Index = 0;
    
    chwidth = Font->Widths[Index]; // šířka znaku
    chsize = Font->Height * col;
    bindex = Index*chsize;

    for (row = 0; row < Font->Height; row++ ) {
        // lezeme po bytech (po osmičkách)
        chw = chwidth;
        for (column = 0; column < col; column++, chw-- ) {
            for( j = 0; j<8; j++ ) {
                if( chw == 0 ) break;
                forecolor = ((uint8_t) Font->Bitmap[bindex+row*col+column]) & (0x80 >> j);
                if( forecolor ) {
                    drawPixel(x + column*8 + j, y + row, fcolor);
                } else {
                    drawPixel(x + column*8 + j, y + row, bcolor);
                }
            }
        } 
    }
return chwidth;
}
// nalezení indexu pole znaků metodou půlení intervalu
int32_t getUTF8Index(const bitmapFONT *font, uint32_t codepoint)
{
static int32_t l = 0;
static int32_t r = 0;
static int32_t m = 0;
    for(l=0, r= font->Chars; l<=r; ) {
        m = (l+r)/2;
        if(font->Index[m] == codepoint ) return m;
        if(codepoint < font->Index[m]    ) { // jdeme vlevo
            r = m - 1;
        } else { // jdeme vpravo
            l = m + 1;
        }
    }
return -1;
}

// kreslení  UTF-8 řetězce
// parametry: x,y - levý horní roh řetězce
//            *Font - ukazatel na bitmapový font
//            *pstring - ukazatel naa řetězec (UTF-8)
//            fcolor - barva písma  (0x0 - 0xf)
//            bcolor - barva pozadí (0x0 - 0xf)
// vrací šířku řetězce
uint16_t drawString(uint32_t x, uint32_t y, const bitmapFONT * Font, 
                    const char * pString, color_t fcolor, color_t bcolor )
{
static uint32_t Xpoint;
static uint32_t Ypoint;
static uint16_t index; 
static uint16_t str_width  = 0;
static uint16_t char_width = 0;
static uint32_t codepoint;
static int i = 0;

    Xpoint = x;
    Ypoint = y;
    str_width = 0;
    char_width = 0;

    if (x > OLED_WIDTH || y > OLED_HEIGHT) {
        return 0;
    }

    utf8_string    ustr = make_utf8_string(pString);
    utf8_char_iter iter = make_utf8_char_iter(ustr);
    utf8_char c;
    while ((c = next_utf8_char(&iter)).byte_len > 0 ) {
        codepoint = unicode_code_point(c);
        index = 0;
        if( (index = getUTF8Index(Font, codepoint)) == -1) continue; // nemáme znak ve fontu
        // test zda se to vejde na displej
        if((Xpoint + Font->Width ) > OLED_WIDTH ) {
            // zalomíme na další řádek
            Xpoint = x;
            Ypoint += Font->Height;
        }
        if ((Ypoint  + Font->Height ) > OLED_HEIGHT ) {
            // už se to nikam nevejde
            return str_width;
            // Xpoint = x;
            // Ypoint = y;
        }
        char_width = drawChar(Xpoint, Ypoint, Font, index, fcolor, bcolor);
        Xpoint    += char_width;
        str_width += char_width;
    }
return str_width;
}
// konec knihovny -------------------------------------------------------------------------
// testování displeje
#define FONT_SPLEEN_5x8_TEST    0      // ve fontu chybí znaky
#define FONT_SPLEEN_6x12_TEST   0
#define FONT_SPLEEN_8x16_TEST   0
#define FONT_SPLEEN_12x24_TEST  0
#define FONT_SPLEEN_16x32_TEST  1
#define FONT_SPLEEN_32x64_TEST  1
#define FONT_9x15_TEST          1
#define FONT_10x20_TEST         1
#define FONT_TAHOMA_8_TEST      1       // font je chybný
#define FONT_TAHOMA_10_TEST     1
#define FONT_TAHOMA_12_TEST     1
#define FONT_TAHOMA_16_TEST     1
#define HODINY_TEST             0
#define TEXT_TEST               0
#define SLEEP_TEST              0
#define LOMENA_TEST             0
#define SINUS_TEST              0
#define TROJUHELNIKY_TEST       0
#define KRUZNICE_TEST           0
#define KRUHY_TEST              0
#define SQUARE_TEST             0
#define RECTANGLE_TEST          0

#if HODINY_TEST

#define PI 3.1415926
#define CX 127          // střed hodin
#define CY 63
#define R  63           // poloměr hodin
#define HR 40           // délka hodinové ručičky
#define MR 50           // délka minutové ručičky
#define SR 53           // délka sekundové ručičky

// hodiny s ručičkami
// h - hodiny (0-12)
// m - minuty (0-59)
// s - sekundy (0-59)
void analogClock( uint8_t h, uint8_t m, uint8_t s )
{
static float ang = 0.0;
static int x0 = 0, y0 = 0, x1 = 0, y1 = 0, i = 0;
static char buf[20];

    // ciferník
    drawCircle(CX, CY, R, GREY9);
    drawCircle(CX, CY, R-1, GREY8);
    // minuty stupnice
    for( i=0; i<60; i++ ) {
        ang = i * 12 * PI/360 - PI/2;
        x0 = CX + R * cos(ang);
        y0 = CY + R * sin(ang);
        x1 = CX + (R-4) * cos(ang);
        y1 = CY + (R-4) * sin(ang);
        drawLine(x0, y0, x1, y1, GREY7);  
    }    
    // hodiny stupnice
    for( i=0; i<12; i++ ) {
        ang = i * 60 * PI/360 - PI/2;
        x0 = CX + R * cos(ang);
        y0 = CY + R * sin(ang);
        x1 = CX + (R-6) * cos(ang);
        y1 = CY + (R-6) * sin(ang);
        drawLine(x0, y0, x1, y1, GREYA);  
    }    
    // hodinová ručička
    ang = ((h % 12)*60 + m)*PI/360 - PI/2;
    x0  = CX + HR * cos(ang);
    y0  = CY + HR * sin(ang);
    x1  = CX;
    y1  = CY;
    drawLine(x0, y0, x1, y1, GREYA);  
    drawLine(x0-1, y0-1, x1-1, y1-1, GREYA);
    drawLine(x0+1, y0+1, x1+1, y1+1, GREYA);
    // minutová ručička
    ang = (m*60+s)*PI/1800 - PI/2;
    x0  = CX + MR * cos(ang);
    y0  = CY + MR * sin(ang);
    drawLineAA( x0, y0, x1, y1, GREYD, GREY2);
    // sekundová ručička
    ang = (s*PI)/30.0 - PI/2;
    x0  = CX + SR * cos(ang);
    y0  = CY + SR * sin(ang);
    drawLineAA(x0, y0, x1, y1, WHITE, GREY4);
    // střed
    drawFilledCircle(CX,CY,3,GREY9);
    // čas v číslicích
    sprintf(buf,"%2d:%02d:%02d",h,m,s);
    drawString(CX+64, 111, &font_spleen_8x16, buf, WHITE, BLACK);
    ssd1363_show();
}
// ----------------------------------------------------------------------------------
#endif

#define RANDOM_MIN 0
#define RANDOM_MAX 255

int main ( void )
{
int ret = 0;
char buf[128];
uint64_t t, tk, tp;

    stdio_init_all();
    memset(buf,0,sizeof(buf));
    
    // resetování displeje
    gpio_set_function(RESET_PIN, GPIO_FUNC_SIO);
    gpio_set_dir(RESET_PIN, GPIO_OUT);
    gpio_put(RESET_PIN, 0);
    sleep_ms(50);
    gpio_put(RESET_PIN, 1);
    sleep_ms(100);
    // nastavení parametrů I2C
    setup_i2c();
    // display_init
    ssd1363_init();
    printf("Display init a smazání obrazovky.\n");
    // testování správnosti ovladače
    for(int j=0; ; j++) {
        int x, y, r, h, w, i, k, color;
        // -----------------------------------------------------------
        // test fontu font_spleen_5x8 je mizerný
#if FONT_SPLEEN_5x8_TEST
        for( k=0; k<font_spleen_5x8.Chars; k++) {
            ssd1363_clear();
            drawChar(1,1,&font_spleen_5x8,k,GREYE,GREY4);
            sprintf(buf,"font_spleen_5x8", k);
            drawString(20,1,&font_spleen_5x8,buf,GREYE,GREY4);
            sprintf(buf,"Character index: %d", k);
            drawString(0,20,&font_spleen_8x16,buf,WHITE,BLACK);
            sprintf(buf,"UTF-8 codepoint: %04lx", font_spleen_5x8.Index[k]);
            drawString(0,40,&font_spleen_8x16,buf,WHITE,BLACK);
            sprintf(buf,"Počet znaků: %d", font_spleen_5x8.Chars);
            drawString(0,60,&font_spleen_8x16,buf,WHITE,BLACK);
            if( k < font_spleen_5x8.Chars) {
                for(i=0; i<42; i++) {
                    drawChar(0+i*6,80,&font_spleen_5x8,k+i,GREYE,GREY4);    
                }
            }
            ssd1363_show();
            sleep_ms(500);
        }
#endif
        // -----------------------------------------------------------
        // test fontu font_spleen_6x12
#if FONT_SPLEEN_6x12_TEST
        for( k=0; k<font_spleen_6x12.Chars; k++) {
            ssd1363_clear();
            drawChar(1,1,&font_spleen_6x12,k,GREYE,GREY4);
            sprintf(buf,"font_spleen_6x12", k);
            drawString(20,1,&font_spleen_6x12,buf,GREYE,GREY4);
            sprintf(buf,"Character index: %d", k);
            drawString(0,20,&font_spleen_8x16,buf,WHITE,BLACK);
            sprintf(buf,"UTF-8 codepoint: %04lx", font_spleen_6x12.Index[k]);
            drawString(0,40,&font_spleen_8x16,buf,WHITE,BLACK);
            sprintf(buf,"Počet znaků: %d", font_spleen_6x12.Chars);
            drawString(0,60,&font_spleen_8x16,buf,WHITE,BLACK);
            if( k < font_spleen_6x12.Chars) {
                for(i=0; i<42; i++) {
                    drawChar(0+i*6,80,&font_spleen_6x12,k+i,GREYE,GREY4);    
                }
            }
            ssd1363_show();
            sleep_ms(500);
        }
#endif
        // -----------------------------------------------------------
        // test fontu font_spleen_8x16
#if FONT_SPLEEN_8x16_TEST
        for( k=0; k<font_spleen_8x16.Chars; k++) {
            ssd1363_clear();
            drawChar(1,1,&font_spleen_8x16,k,GREYE,GREY4);
            sprintf(buf,"font_spleen_8x16", k);
            drawString(20,1,&font_spleen_8x16,buf,GREYE,GREY4);
            sprintf(buf,"Character index: %d", k);
            drawString(0,20,&font_spleen_8x16,buf,WHITE,BLACK);
            sprintf(buf,"UTF-8 codepoint: %04lx", font_spleen_8x16.Index[k]);
            drawString(0,40,&font_spleen_8x16,buf,WHITE,BLACK);
            sprintf(buf,"Počet znaků: %d", font_spleen_8x16.Chars);
            drawString(0,60,&font_spleen_8x16,buf,WHITE,BLACK);
            if( k < font_spleen_8x16.Chars) {
                for(i=0; i<32; i++) {
                    drawChar(0+i*8,80,&font_spleen_8x16,k+i,GREYE,GREY4);    
                }
            }
            ssd1363_show();
            sleep_ms(500);
        }
#endif
        // -----------------------------------------------------------
        // test fontu font_spleen_12x24 
#if FONT_SPLEEN_12x24_TEST
        for( k=0; k<font_spleen_12x24.Chars; k++) {
            ssd1363_clear();
            drawChar(0,0,&font_spleen_12x24,k,GREYE,GREY4);
            sprintf(buf,"font_spleen_12x24", k);
            drawString(30,0,&font_spleen_12x24,buf,GREYE,GREY4);
            sprintf(buf,"Character index: %d", k);
            drawString(0,26,&font_spleen_8x16,buf,WHITE,BLACK);
            sprintf(buf,"UTF-8 codepoint: %04lx", font_spleen_12x24.Index[k]);
            drawString(0,42,&font_spleen_8x16,buf,WHITE,BLACK);
            sprintf(buf,"Počet znaků: %d", font_spleen_12x24.Chars);
            drawString(0,60,&font_spleen_8x16,buf,WHITE,BLACK);
            if( k < font_spleen_12x24.Chars) {
                for(i=0; i<20; i++) {
                    drawChar(0+i*12,80,&font_spleen_12x24,k+i,GREYE,GREY4);    
                }
            }
            ssd1363_show();
            sleep_ms(500);
        }
#endif
        // -----------------------------------------------------------
        // test fontu font_spleen_16x32
#if FONT_SPLEEN_16x32_TEST
        for( k=0; k<font_spleen_16x32.Chars; k++) {
            ssd1363_clear();
            drawChar(0,0,&font_spleen_16x32,k,GREYE,GREY4);
            sprintf(buf,"spleen_16x32", k);
            drawString(20,0,&font_spleen_16x32,buf,GREYE,GREY4);
            sprintf(buf,"Character index: %d", k);
            drawString(0,33,&font_spleen_8x16,buf,WHITE,BLACK);
            sprintf(buf,"UTF-8 codepoint: %04lx", font_spleen_16x32.Index[k]);
            drawString(0,49,&font_spleen_8x16,buf,WHITE,BLACK);
            sprintf(buf,"Počet znaků: %d", font_spleen_16x32.Chars);
            drawString(0,66,&font_spleen_8x16,buf,WHITE,BLACK);
            if( k < font_spleen_16x32.Chars) {
                for(i=0; i<16; i++) {
                    drawChar(0+i*16,86,&font_spleen_16x32,k+i,GREYE,GREY4);    
                }
            }
            ssd1363_show();
            sleep_ms(500);
        }
#endif
        // -----------------------------------------------------------
        // test fontu font_spleen_32x64
#if FONT_SPLEEN_32x64_TEST
        for( k=0; k<font_spleen_32x64.Chars; k++) {
            ssd1363_clear();
            drawChar(1,1,&font_spleen_32x64,k,GREYE,GREY4);
            sprintf(buf,"font_spleen_32x64", k);
            drawString(40,1,&font_spleen_6x12,buf,GREYE,GREY4);
            sprintf(buf,"Character index: %d", k);
            drawString(0,72,&font_spleen_8x16,buf,WHITE,BLACK);
            sprintf(buf,"UTF-8 codepoint: %04lx", font_spleen_32x64.Index[k]);
            drawString(0,88,&font_spleen_8x16,buf,WHITE,BLACK);
            sprintf(buf,"Počet znaků: %d", font_spleen_32x64.Chars);
            drawString(0,104,&font_spleen_8x16,buf,WHITE,BLACK);
            ssd1363_show();
            sleep_ms(500);
        }
#endif
        // -----------------------------------------------------------
        // test fontu font_10x20
#if FONT_10x20_TEST
        for( k=0; k<font_10x20.Chars; k++) {
            ssd1363_clear();
            drawChar(1,1,&font_10x20,k,GREYE,GREY4);
            sprintf(buf,"font_10x20", k);
            drawString(40,1,&font_10x20,buf,GREYE,GREY4);
            sprintf(buf,"Character index: %d", k);
            drawString(0,26,&font_spleen_8x16,buf,WHITE,BLACK);
            sprintf(buf,"UTF-8 codepoint: %04lx", font_10x20.Index[k]);
            drawString(0,42,&font_spleen_8x16,buf,WHITE,BLACK);
            sprintf(buf,"Počet znaků: %d", font_10x20.Chars);
            drawString(0,60,&font_spleen_8x16,buf,WHITE,BLACK);
            if( k < font_10x20.Chars) {
                for(i=0; i<25; i++) {
                    drawChar(0+i*10,80,&font_10x20,k+i,GREYE,GREY4);    
                }
            }
            ssd1363_show();
            sleep_ms(500);
        }
#endif
       // test fontu font_9x15
#if FONT_9x15_TEST
        for( k=0; k<font_9x15.Chars; k++) {
            ssd1363_clear();
            drawChar(1,1,&font_9x15,k,GREYE,GREY4);
            sprintf(buf,"font_9x15", k);
            drawString(40,1,&font_9x15,buf,GREYE,GREY4);
            sprintf(buf,"Character index: %d", k);
            drawString(0,26,&font_spleen_8x16,buf,WHITE,BLACK);
            sprintf(buf,"UTF-8 codepoint: %04lx", font_9x15.Index[k]);
            drawString(0,42,&font_spleen_8x16,buf,WHITE,BLACK);
            sprintf(buf,"Počet znaků: %d", font_9x15.Chars);
            drawString(0,60,&font_spleen_8x16,buf,WHITE,BLACK);
            if( k < font_9x15.Chars) {
                for(i=0; i<28; i++) {
                    drawChar(0+i*9,80,&font_9x15,k+i,GREYE,GREY4);    
                }
            }
            ssd1363_show();
            sleep_ms(500);
        }
#endif
        // -----------------------------------------------------------
#if FONT_TAHOMA_8_TEST
        for( k=0; k<font_tahoma_8.Chars; k++) {
            ssd1363_clear();
            drawChar(1,1,&font_tahoma_8,k,GREYE,GREY4);
            sprintf(buf,"font_tahoma_8", k);
            drawString(40,1,&font_tahoma_8,buf,GREYE,GREY4);
            sprintf(buf,"Character index: %d", k);
            drawString(0,26,&font_spleen_8x16,buf,WHITE,BLACK);
            sprintf(buf,"UTF-8 codepoint: %04lx", font_tahoma_8.Index[k]);
            drawString(0,42,&font_spleen_8x16,buf,WHITE,BLACK);
            sprintf(buf,"Počet znaků: %d", font_tahoma_8.Chars);
            drawString(0,60,&font_spleen_8x16,buf,WHITE,BLACK);
            if( k < font_tahoma_8.Chars) {
                for(i=0; i<28; i++) {
                    drawChar(0+i*11,80,&font_tahoma_8,k+i,GREYE,GREY4);    
                }
            }
            ssd1363_show();
            sleep_ms(500);
        }
#endif
        // -----------------------------------------------------------
#if FONT_TAHOMA_10_TEST
        for( k=0; k<font_tahoma_10.Chars; k++) {
            ssd1363_clear();
            drawChar(1,1,&font_tahoma_10,k,GREYE,GREY4);
            sprintf(buf,"font_tahoma_10", k);
            drawString(40,1,&font_tahoma_10,buf,GREYE,GREY4);
            sprintf(buf,"Character index: %d", k);
            drawString(0,26,&font_spleen_8x16,buf,WHITE,BLACK);
            sprintf(buf,"UTF-8 codepoint: %04lx", font_tahoma_10.Index[k]);
            drawString(0,42,&font_spleen_8x16,buf,WHITE,BLACK);
            sprintf(buf,"Počet znaků: %d", font_tahoma_10.Chars);
            drawString(0,60,&font_spleen_8x16,buf,WHITE,BLACK);
            if( k < font_tahoma_10.Chars) {
                for(i=0; i<28; i++) {
                    drawChar(0+i*14,80,&font_tahoma_10,k+i,GREYE,GREY4);    
                }
            }
            ssd1363_show();
            sleep_ms(500);
        }
#endif
        // -----------------------------------------------------------
        // hodiny       
#if HODINY_TEST
        for( k=0; k<10; k++) {
            for( i=0; i<60; i++ ) {
                t = time_us_64() + 100000;
                ssd1363_clear();
                analogClock(8,20+k,i);
                sleep_until(t);
            }
        }
#endif
        // -----------------------------------------------------------
        // testování drawPixel a getPixel
        // ssd1363_clear();
        // for(k = 0, color=WHITE; k<16; k++, color-- ) {
        //     drawPixel(k,0,color);
        //     printf("Kreslil jsem x=%d, y=%d, c=%x, dostal jsem d=%x\n", k, 0, color, getPixel(k, 0));
        // }
        // for(k = 0, color=WHITE; k<16; k++, color-- ) {
        //     drawPixel(k,k+1,color);
        //     printf("Kreslil jsem x=%d, y=%d, c=%x, dostal jsem d=%x\n", k, k+1, color, getPixel(k, k+1));
        // }
        // ssd1363_clear();
        // -----------------------------------------------------------
#if AA_TEST        
        // test antialiased line
        for(i=0; i<16; i++ ) {
            for(k=0; k<128; k++) {
                t = time_us_64() + 100000;
                ssd1363_clear();
                drawLineAA( 0, 0, 255, k, WHITE,i);
                drawLine(0,40, 255, k+40, WHITE);
                sprintf(buf,"drawLineAA(0,0,255,%d,WHITE,%d);",k,i);
                drawString(0,111,&font_spleen_6x12,buf,WHITE,BLACK);
                ssd1363_show();
                sleep_until(t);
            }
        }
#endif
        // for(i=0; i<10; i++) {
        //     printf("x=%d ",i);
        //     for( k=2;k<127; k++) {
        //         printf("%x,",getPixel(k,i));
        //     }
        //     printf("\n");    
        // }
        // -----------------------------------------------------------
#if TEXT_TEST        
        // text
        ssd1363_clear();
        t = time_us_64();
        drawString(0,0,&font_spleen_8x16,"Test displeje SSD1363, s rozměry "
                                         "256x128 pixelů, 16 stupňů šedi.",WHITE,BLACK);
        drawSquare(0,38,255,90,GREYD);
        drawString(0,40,&font_spleen_6x12,"Grafická knihovna kompletně z nuly.       "
                                          "(c) Jirka Chráska 2026, <jirka@lixis.cz>  "
                                          "BSD 3 licence pro studenty Střední školy  "
                                          "Podorlické vzdělávací centrum Dobruška.",
                   GREY1,GREYD);
        drawString(0,96,&font_spleen_8x16,"Raspberry Pi Pico je skvělý     "
                                          "mikropočítač. I²C je pomalá.", WHITE,BLACK);
        tk = time_us_64() - t;
        t = time_us_64();
        ssd1363_show();
        tp = time_us_64() - t;
        sleep_ms(8000);
#endif
#if SLEEP_TEST
        ssd1363_clear();
        sprintf(buf,"Kreslení %llu μs", tk);
        drawString(1,0,&font_spleen_8x16, buf, WHITE, BLACK);
        sprintf(buf,"Přenos %llu μs", tp);
        drawString(1,17,&font_spleen_8x16, buf, WHITE, BLACK);
        sprintf(buf,"Frekvence I²C sběrnice 3.3 MHz");
        drawString(1,33,&font_spleen_8x16, buf, WHITE, BLACK);
        // test uspání displeje 
        if( j%2 == 1 ) { // budeme spát
            sprintf(buf,"Za chvilku se displej uspí.");
            drawString(1,49,&font_spleen_8x16, buf, WHITE, BLACK);
            sprintf(buf,"Spát bude celý cyklus.");
            drawString(1,65,&font_spleen_8x16, buf, WHITE, BLACK);
        } 
        ssd1363_show();
        sleep_ms(4000);
        ssd1363_clear();
        if( j%2 == 1 ) {
            ssd1363_sleep();
        } else {
            ssd1363_on();
        }
#endif
#if LOMENA_TEST
        //------------------------------------------------------------
        printf("Lomená čára.\n");
        drawString(140,0,&font_spleen_8x16,"Lomená čára",GREYD,GREY2);
        
        bool xplus = true;
        bool yplus = true;
        // x bude náhodné v rozsahu RANDOM_MIN až RANDOM_MAX
        x = rand() % (RANDOM_MAX + 1 - RANDOM_MIN) + RANDOM_MIN;
        sprintf(buf,"Náhoda: %d",x);
        drawString(0,0,&font_spleen_8x16, buf, GREYD, GREY2);
        ssd1363_show();
        sleep_ms(1500);
        ssd1363_clear();
        ssd1363_show();
        h = x; w = 21;
        color = GREY3;
        t = time_us_64();
        for( i=0, y=21; i<8001 ; i++  ) {
            drawPixelInv(x,y,WHITE);
            drawPixelInv(h,w,color);
            h = x; w = y;
            sprintf(buf,"drawPixelI(%3d, %3d, 0x%x);i=%4d %5.2f ms",
                    x,y,color,i, (float)tp*0.001);
            drawString(0,0,&font_spleen_6x12, buf, GREYD, GREY2);
            // posíláme kresbu na displej po 8. pixelu
            // poslátní dat na displej trvá 62 ms
            if( x%8==0 ) { 
                ssd1363_show();
                tp = time_us_64() - t;
                t =  time_us_64();
            }
            i%1000==0 ? color++: color;
            if( x<=0 ) xplus = true;
            if( x>=OLED_WIDTH-1) xplus = false;
            if( y<=20 ) yplus = true;
            if( y>=OLED_HEIGHT-1) yplus=false;
            xplus?++x:--x;
            yplus?++y:--y;
        }       
        ssd1363_show();
        sleep_ms(5000); 
#endif
#if SINUS_TEST
        // -----------------------------------------------------------
        // sinusovka
        printf("Sinusovka a kosinusovka.\n");
        ssd1363_clear();
        ssd1363_show();
        drawString(10,110,&font_spleen_8x16,"y=sin(x)", GREYE,BLACK);
        drawString(100,10,&font_spleen_8x16,"y=cos(x)", GREY8,BLACK);
        // popisky os
        drawString(3,2,&font_spleen_6x12,"y",GREY8,BLACK);
        drawString(248,63,&font_spleen_6x12,"x",GREY8,BLACK);
        // osy
        drawLine(0,0,0,127,GREY8);
        drawLine(0,63,255,63,GREY8);
        // sinusovka a kosinusovka
        for( x=0; x<256; x++) {
            y = (-(sin(x*3.1415926/100) * 64)+64);
            r = drawPixel(x,y,WHITE);
            r = drawPixel(x,y-1,WHITE);
            
            y = (-(cos(x*3.1415926/100) * 64)+64);
            r = drawPixel(x,y,GREY5);
            r = drawPixel(x,y-1,GREY5);
            if(x==50) {
                drawLine(x,62,x,66,GREY8);
                drawString(x+1,65,&font_spleen_8x16,"π/2",GREY8,BLACK);
            }
            if(x==100) {
                drawLine(x,62,x,66,GREY8);
                drawString(x+10,65,&font_spleen_8x16,"π",GREY8,BLACK);
            }
            if(x==150) {
                drawLine(x,62,x,66,GREY8);
                drawString(x+1,65,&font_spleen_8x16,"3π/2",GREY8,BLACK);
            }
            if(x==200) {
                drawLine(x,62,x,66,GREY8);
                drawString(x+1,65,&font_spleen_8x16,"2π",GREY8,BLACK);
            }
            // občerstvujeme displej po 4 nakreslených bodech
            x%4==0?ssd1363_show():sleep_us(1);
        }
        sleep_ms(5000);
        // přepneme displej do invezního zobrazení
        ssd1363_invert();
        sleep_ms(5000);
        // přepneme ho zpátky do normálního zobrazení
        ssd1363_normal();
#endif
#if TROJUHELNIKY_TEST
        // -----------------------------------------------------------
        // trojúhelníky
        printf("Trojúhelníky.\n");
        ssd1363_clear();
        for(x=0, y=0; y<32; y++) {
            drawLine(x,0,255,y,GREY4);
        }
        for(x=0, y=32; y<64; y++) {
            drawLine(x,0,255,y,GREY8);
        }        
        for(x=0, y=64; y<96; y++) {
            drawLine(x,0,255,y,GREYA);
        }        
        for(x=0, y=96; y<128; y++) {
            drawLine(x,0,255,y,GREYD);
        }        
        for(x=255, y=127; y>=0; y--) {
            drawLine(x,255,0,y,WHITE);
        }
        drawString(160,0,&font_spleen_8x16,"Trojúhelníky",WHITE,BLACK);
        ssd1363_show();
        sleep_ms(5000);
#endif
#if KRUZNICE_TEST
        // -----------------------------------------------------------
        // test kružnic
        printf("Kružnice\n");
        ssd1363_clear();
        drawString(180,0,&font_spleen_8x16,"Kružnice",GREYA,BLACK);
        // kružnice zvětšuje svůj poloměr
        for(int r=10; r<64; r++) {
            drawCircle(127,64,r,GREYA);
            sprintf(buf,"drawCircle(%d, %d, %d, GREYA); ",127,64,r);
            drawString(0,115,&font_spleen_6x12,buf, WHITE, BLACK);
            ssd1363_show();
        }
        // kružnice zmenšuje svůj poloměr
        for(int r=63; r>10; r--) {
            drawCircle(127,64,r,BLACK);
            sprintf(buf,"drawCircle(%d, %d, %d, BLACK); ",127,64,r);
            drawString(0,115,&font_spleen_6x12,buf, WHITE, BLACK);
            ssd1363_show();
        }
        sleep_ms(1000);
#endif
#if KRUHY_TEST
        // -----------------------------------------------------------
        // plné kruhy
        ssd1363_clear();
        drawFilledCircle(127,64,128,GREY4);
        drawString(180,0,&font_spleen_8x16,"Kruhy",GREYA,BLACK);
        sprintf(buf,"drawFilledCircle(%d, %d, %d, GREY4);  ",127,64,128);
        drawString(6,115,&font_spleen_6x12,buf, WHITE, GREYA);
        ssd1363_show();
        sleep_ms(1000);
        
        drawFilledCircle(127,64,64,GREY7);
        sprintf(buf,"drawFilledCircle(%d, %d, %d, GREY7);  ",127,64,64);
        drawString(6,115,&font_spleen_6x12,buf, WHITE, GREYA);
        ssd1363_show();
        sleep_ms(1000);

        drawFilledCircle(127,64,32,GREYA);
        sprintf(buf,"drawFilledCircle(%d, %d, %d, GREYA);  ",127,64,32);
        drawString(6,115,&font_spleen_6x12,buf, WHITE, GREYA);
        ssd1363_show();
        sleep_ms(1000);

        drawFilledCircle(127,64,16,WHITE);
        sprintf(buf,"drawFilledCircle(%d, %d, %d, WHITE);  ",127,64,16);
        drawString(6,115,&font_spleen_6x12,buf, WHITE, GREYA);
        ssd1363_show();
        sleep_ms(1000);

        drawFilledCircle(127,64,8,BLACK);
        sprintf(buf,"drawFilledCircle(%d, %d, %d, BLACK);  ",127,64,8);
        drawString(6,115,&font_spleen_6x12,buf, WHITE, GREYA);
        ssd1363_show();
        sleep_ms(5000);
#endif
#if GRAFIKA_TEST
        // -----------------------------------------------------------
        // složitá grafika
        printf("Grafika\n");
        ssd1363_clear();
        
        // obdélník kolem displeje
        drawRectangle(0,0,255,127,WHITE);
        sprintf(buf,"drawRectangle(%d, %d, %d, %d, WHITE);",0,0,255,127);
        drawString(5,115,&font_spleen_6x12,buf, WHITE, BLACK);
        ssd1363_show();
        sleep_ms(1500);
        // šedý čtverec
        drawSquare(78,1,178,100,GREY8);
        sprintf(buf,"drawSquare(%d, %d, %d, %d, GREY8);",78,1,178,100);
        drawString(2,115,&font_spleen_6x12,buf, WHITE, BLACK);
        ssd1363_show();
        sleep_ms(1500);
        
        drawSquare(15,10,56,51,GREY4);
        sprintf(buf,"drawSquare(%d, %d, %d, %d, GREY4);",15,10,56,51);
        drawString(2,115,&font_spleen_6x12,buf, WHITE, BLACK);
        ssd1363_show();
        sleep_ms(1500);

        for( int x=200; x<240; x++) {
            for( int y=10; y<51; y++) {
                drawPixel(x,y,GREY7);
            }
        }
        drawSquare(200,10,240,51,GREY7);
        sprintf(buf,"drawSquare(%d, %d, %d, %d, GREY7);",200,10,240,51);
        drawString(2,115,&font_spleen_6x12,buf, WHITE, BLACK);
        ssd1363_show();
        sleep_ms(1500);

        drawString(190,20,&font_spleen_8x16,"Grafika",BLACK,WHITE);
        sprintf(buf,"drawString(190,20,&font,\"Gra..\",0x0,0xf);");
        drawString(2,115,&font_spleen_6x12,buf, WHITE, BLACK);
        ssd1363_show();
        sleep_ms(2500);
        
        drawString(10,20,&font_spleen_8x16,"Grafika",WHITE,BLACK);
        sprintf(buf,"drawString(10,20,&font,\"Graf..\",0xf,0x0);");
        ret = drawString(2,115,&font_spleen_6x12,buf, WHITE, BLACK);
        ssd1363_show();
        sleep_ms(2500);
        drawSquare(1,115,ret+1,126,BLACK);
        for( int x=0, y=60; x<128; x++ ) {
            drawLine(0,127,x,y,GREY4);
            drawLine(255,127,255-x,y,WHITE); 
            sprintf(buf,"drawLine(%d, %d, %d, %d, %x);",0,127,x,y,GREY4);
            drawString(40,103,&font_spleen_6x12,buf, WHITE, BLACK);
            sprintf(buf,"drawLine(%d, %d, %d, %d, %x);",255,127,255-x,y,WHITE);
            drawString(36,115,&font_spleen_6x12,buf, WHITE, BLACK);
            drawLine(0,0,0,127,WHITE);
            ssd1363_show();
        }
        ssd1363_show();
        sleep_ms(5000); 
#endif
#if SQUARE_TEST
        // -----------------------------------------------------------
        // plné obdélníky
        ssd1363_clear();
        ssd1363_show();
        drawString(180,1,&font_spleen_8x16,"Obdélníky",BLACK,WHITE);
        drawSquare(0,     0,  20,  20, GREY5);
        drawSquare(80,   80,  40,  40, GREY9);
        drawSquare(100,  20, 250,  60, GREYE);
        drawSquare(0,   100,  16, 127, GREY4);
        drawSquare(16,   90,  40, 127, GREY5);
        drawSquare(40,  112,  60, 127, GREY6);
        drawSquare(60,   70,  80, 127, GREY7);
        drawSquare(80,   60, 120, 127, GREYA);
        
        // panelák s okénky
        drawSquare(120,  90, 142, 127, GREY8);
        
        drawSquare(122,  92, 124,  94, BLACK);
        drawSquare(126,  92, 128,  94, BLACK);
        drawSquare(130,  92, 132,  94, BLACK);
        drawSquare(134,  92, 136,  94, BLACK);
        drawSquare(138,  92, 140,  94, BLACK);

        drawSquare(122,  96, 124,  98, BLACK);
        drawSquare(126,  96, 128,  98, BLACK);
        drawSquare(130,  96, 132,  98, BLACK);
        drawSquare(134,  96, 136,  98, BLACK);
        drawSquare(138,  96, 140,  98, BLACK);

        drawSquare(122, 100, 124, 102, BLACK);
        drawSquare(126, 100, 128, 102, BLACK);
        drawSquare(130, 100, 132, 102, BLACK);
        drawSquare(134, 100, 136, 102, BLACK);
        drawSquare(138, 100, 140, 102, BLACK);

        drawSquare(122, 104, 124, 106, BLACK);
        drawSquare(126, 104, 128, 106, BLACK);
        drawSquare(130, 104, 132, 106, BLACK);
        drawSquare(134, 104, 136, 106, BLACK);
        drawSquare(138, 104, 140, 106, BLACK);
        
        drawSquare(122, 108, 124, 110, BLACK);
        drawSquare(126, 108, 128, 110, BLACK);
        drawSquare(130, 108, 132, 110, BLACK);
        drawSquare(134, 108, 136, 110, BLACK);
        drawSquare(138, 108, 140, 110, BLACK);

        drawSquare(122, 110, 124, 112, BLACK);
        drawSquare(126, 110, 128, 112, BLACK);
        drawSquare(130, 110, 132, 112, BLACK);
        drawSquare(134, 110, 136, 112, BLACK);
        drawSquare(138, 110, 140, 112, BLACK);
        // konec paneláku
        
        drawSquare(142, 100, 180, 127, GREYD);

        ssd1363_show();
        sleep_ms(5000); 
#endif
#if RECTANGLE_TEST        
        // -----------------------------------------------------------
        // prázdné obdélníky
        ssd1363_clear();
        ssd1363_show();
        t = time_us_64();
        drawString(108,17,&font_spleen_8x16,"Obdélníky prázdné",BLACK,GREYA);
        drawRectangle(100, 10, 151,  60,WHITE);
        drawRectangle(101, 11, 149,  58,WHITE);
        drawRectangle(102, 12, 147,  56,GREYD);
        drawRectangle(103, 13, 145,  54,GREYD);
        drawRectangle(104, 14, 143,  52,GREYB);
        drawRectangle(105, 15, 141,  50,GREYB);
        drawRectangle( 10, 10,  20, 100, GREYC);
        color_t c = WHITE;
        for(x=50, y=90, h=32, w=60; h>4; x--, y--, w -= 2, h -= 2 ) {
            drawRectangle(x,y,w,h, c--);
        }
        tk = time_us_64() - t;
        t = time_us_64();
        ssd1363_show();
        tp = time_us_64() - t;
        sprintf(buf,"Kreslení %llu μs", tk);
        drawString(114,96,&font_spleen_8x16, buf, WHITE, BLACK);
        ssd1363_show();
        sleep_ms(2000);
        sprintf(buf,"Přenos  %llu μs ", tp);
        drawString(114,111,&font_spleen_8x16, buf, WHITE, BLACK);
        ssd1363_show();
        sleep_ms(8000); 
#endif
        printf("Konec smyčky.\n");
    }
}
// eof ---------------------------------------------------------------------------

Nepoužité fonty jsou v CMakeLists.txt zakomentovány.

CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)
set(PICO_BOARD pico)

project(sh1363_test C CXX ASM)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()

add_executable(ssd1363_test
		ssd1363_test.c
		utf8.c
		font_9x15.c
		font_10x20.c
		font_spleen_5x8.c
		font_spleen_6x12.c
		font_spleen_8x16.c
		font_spleen_12x24.c
		font_spleen_16x32.c
        font_spleen_32x64.c
		font_tahoma_8.c
		font_tahoma_10.c
		font_tahoma_16.c
    )

target_link_libraries(ssd1363_test pico_stdlib hardware_i2c)

# create map/bin/hex file etc.
pico_enable_stdio_usb(ssd1363_test 1)
pico_enable_stdio_uart(ssd1363_test 0)
pico_add_extra_outputs(ssd1363_test)
utf8.h
/**
 * @file utf8.h
 * @brief simple library for working with UTF-8 encoded strings
 *
 * @code
 * #include "utf8.h"
 * #include <stdio.h>
 *
 * int main() {
 *     const char* str = "Hello, こんにちは, Здравствуйте";
 *     utf8_string ustr = make_utf8_string(str);
 *     utf8_string_slice slice = make_utf8_string_slice(ustr, 2, 11);
 *     utf8_char_iter iter = make_utf8_char_iter(ustr);
 *
 *     printf("string: %s\n", ustr.str);
 *     printf("slice: %.*s\n", (int)slice.byte_len, slice.str);
 *
 *     utf8_char ch;
 *     while ((ch = next_utf8_char(&iter)).byte_len > 0) {
 *         printf("character: %.*s\t", (int)ch.byte_len, ch.str);
 *         printf("unicode code point: U+%04X\n", unicode_code_point(ch));
 *     }
 *
 *     return 0;
 * }
 * @endcode
 */

#ifndef ZAHASH_UTF8_H
#define ZAHASH_UTF8_H

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

/**
 * @brief Represents the validity of a UTF-8 encoded string.
 *
 * @details The `utf8_validity` struct indicates whether a given UTF-8 encoded string is valid or not,
 * along with the position up to which it is valid.
 *
 * - Invalid case: "hello\xC0\xC0" => { .valid = false, .valid_upto = 5  }
 * - Valid case:   "hello world"   => { .valid = true,  .valid_upto = 11 }
 */
typedef struct {
    bool valid;          ///< Flag indicating the validity of the UTF-8 string.
    size_t valid_upto;   ///< The position up to which the string is valid.
} utf8_validity;

/**
 * @brief Represents a non-owning UTF-8 encoded string. (just a wrapper type).
 *
 * @details The `utf8_string` struct holds a pointer to a UTF-8 encoded string along with its byte length,
 */
typedef struct {
    const char* str;     ///< Pointer to the UTF-8 encoded string.
    size_t byte_len;     ///< Byte length of the UTF-8 string ('\0' not counted).
} utf8_string;

/**
 * @brief Represents a UTF-8 encoded string that fully owns its data.
 *
 * @details The `owned_utf8_string` struct holds a pointer to a UTF-8 encoded string that is dynamically allocated
 *          and therefore is owned by the struct, which means the caller is responsible for freeing the memory when
 *          it is no longer needed using the `free_owned_utf8_string` function.
 */
typedef struct {
    char* str;          ///< Pointer to the UTF-8 encoded string (owned). This memory is dynamically allocated.
    size_t byte_len;    ///< Byte length of the UTF-8 string ('\0' not counted).
} owned_utf8_string;

/**
 * @brief Represents an iterator for traversing UTF-8 characters in a string.
 *
 * @details The `utf8_char_iter` struct serves as an iterator for traversing UTF-8 characters
 * within a UTF-8 encoded string.
 */
typedef struct {
    const char* str;     ///< Pointer to the current position of the iterator.
} utf8_char_iter;

/**
 * @brief Represents a UTF-8 character.
 *
 * @details The `utf8_char` struct encapsulates a UTF-8 character, including its pointer and byte length.
 * The byte length represents the number of bytes occupied by the UTF-8 character.
 */
typedef struct {
    const char* str;     ///< Pointer to the UTF-8 character.
    uint8_t byte_len;    ///< Byte length of the UTF-8 character.
} utf8_char;

/**
 * @brief Validates whether a given string is UTF-8 compliant in O(n) time.
 *
 * @param str The input string to validate.
 * @return The validity of the UTF-8 string along with the position up to which it is valid.
 */
utf8_validity validate_utf8(const char* str);

/**
 * @brief Wraps a C-style string in a UTF-8 string structure after verifying its UTF-8 compliance.
 *
 * @param str The input C-style string to wrap.
 * @return A UTF-8 string structure containing the wrapped string if valid; otherwise, a structure with NULL string pointer.
 *
 * @code
 * // Example usage:
 * const char *str = "definitely utf8 string こんにちは नमस्ते Здравствуйте";
 * utf8_string ustr = make_utf8_string(str);
 * assert( ustr.str != NULL );
 *
 * const char *s = "non-utf8 sequence \xC0\xC0";
 * utf8_string ustr = make_utf8_string(str);
 * assert( ustr.str == NULL );
 * @endcode
 */
utf8_string make_utf8_string(const char* str);

/**
 * @brief Converts a C-style string to a UTF-8 string, replacing invalid sequences with U+FFFD REPLACEMENT CHARACTER (�).
 *
 * @details It takes a C-style string as input and converts it to a UTF-8 encoded string.
 *          Any invalid UTF-8 sequences in the input string are replaced with the U+FFFD REPLACEMENT CHARACTER (�) to ensure
 *          that the resulting string is valid UTF-8. The resulting string is dynamically allocated and the caller
 *          is responsible for freeing the memory when no longer needed using `free_owned_utf8_string`.
 *
 * @param str The input C-style string to convert. The string can contain invalid UTF-8 sequences.
 * @return An `owned_utf8_string` structure containing the resulting UTF-8 string. If memory allocation fails, the structure
 *         will contain a `NULL` pointer and a `byte_len` of 0.
 *
 * @code
 * // Example usage:
 * const char* str = "hello\xC0\xC0 world!";
 * owned_utf8_string owned_ustr = make_utf8_string_lossy(str);
 * @endcode
 */
owned_utf8_string make_utf8_string_lossy(const char* str);

/**
 * @brief Creates the non-owning UTF-8 encoded string `utf8_string` from an `owned_utf8_string`.
 *
 * @details The resulting `utf8_string` will point to the same underlying string without taking ownership.
 *          The caller must ensure the original `owned_utf8_string` remains valid as long as the reference is used.
 *
 * @param owned_str The owned UTF-8 string from which to create a non-owning reference.
 * @return utf8_string A non-owning UTF-8 string reference (`utf8_string`) pointing to the same data.
 *
 * @note This function does not free or transfer ownership of the `owned_utf8_string`.
 *       The caller is responsible for managing the lifetime of the owned string.
 */
utf8_string as_utf8_string(const owned_utf8_string* owned_str);

/**
 * @brief Frees the memory allocated for an `owned_utf8_string`.
 *
 * @details The `free_owned_utf8_string` function deallocates the memory used by an `owned_utf8_string`
 *          and sets the `str` pointer to `NULL` and `byte_len` to 0.
 *
 * @param owned_str A pointer to the `owned_utf8_string` structure to be freed.
 *
 * @code
 * // Example usage:
 * owned_utf8_string owned_ustr = make_utf8_string_lossy("hello\xC0\xC0 world!");
 * free_owned_utf8_string(&owned_ustr);
 * @endcode
 */
void free_owned_utf8_string(owned_utf8_string* owned_str);

/**
 * @brief Creates a UTF-8 string slice from a specified range of bytes in the original string.
 *
 * @param ustr The original UTF-8 string.
 * @param byte_index The starting byte index of the slice.
 * @param byte_len The byte length of the slice.
 * @return A UTF-8 string representing the specified byte range [offset, offset + byte_len) if valid (range between UTF-8 char boundaries);
 * otherwise { .str = NULL, .byte_len = 0 }
 *
 * @note if `byte_index` >= strlen(ustr.str) then returns terminating '\0' of ustr.str { .str = '\0', .byte_len = 0 }
 * @note if `byte_index` + `byte_len` >= strlen(ustr.str) then only chars till terminating '\0' are considered.
 */
utf8_string slice_utf8_string(utf8_string ustr, size_t byte_index, size_t byte_len);

/**
 * @brief Creates an iterator for traversing UTF-8 characters within a string. (see next_utf8_char( .. ) for traversal)
 *
 * @param ustr The UTF-8 string to iterate over.
 * @return An iterator structure initialized to the start of the string.
 */
utf8_char_iter make_utf8_char_iter(utf8_string ustr);

/**
 * @brief Retrieves the next UTF-8 character from the iterator.
 *
 * @param iter Pointer to the UTF-8 character iterator.
 * @return The next UTF-8 character from the iterator.
 * @note If the iterator reaches the end, it keeps returning terminating '\0' of iter.str { .str = '\0', .byte_len = 0 }
 */
utf8_char next_utf8_char(utf8_char_iter* iter);

/**
 * @brief Retrieves the UTF-8 character at the specified character index within a UTF-8 string in O(n) time.
 *
 * @details The `nth_utf8_char` function returns the UTF-8 character located at the specified character index
 * within the given UTF-8 string. The character index is zero-based, indicating the position of
 * the character in the string. If the index is out of bounds or invalid, the function returns
 * { .str = NULL, .byte_len = 0 }
 *
 * @param ustr The UTF-8 string from which to retrieve the character.
 * @param char_index The zero-based index of the character to retrieve.
 * @return The UTF-8 character at the specified index within the string.
 *
 * @code
 * // Example usage:
 * utf8_string str = make_utf8_string("Hello Здравствуйте こんにちは");
 * utf8_char char_at_index = nth_utf8_char(str, 7);    // д
 * @endcode
 */
utf8_char nth_utf8_char(utf8_string ustr, size_t char_index);

/**
 * @brief Counts the number of UTF-8 characters in the given utf8_string.
 *
 * @param ustr The UTF-8 string whose characters are to be counted.
 * @return The total number of characters in the UTF-8 string.
 */
size_t utf8_char_count(utf8_string ustr);

/**
 * @brief Checks if a given byte is the start of a UTF-8 character. ('\0' is also a valid character boundary)
 *
 * @param str Pointer to the byte to check.
 * @return `true` if the byte is the start of a UTF-8 character; otherwise, `false`.
 */
bool is_utf8_char_boundary(const char* str);

/**
 * @brief Converts a UTF-8 character to its corresponding Unicode code point (which is the same as a UTF-32 value).
 *
 * @param uchar The UTF-8 character to convert.
 * @return The Unicode code point.
 */
uint32_t unicode_code_point(utf8_char uchar);

#endif
utf8.c
#include "utf8.h"

#include <stdlib.h>
#include <string.h>

typedef struct {
    bool valid;
    size_t next_offset;
} utf8_char_validity;

utf8_char_validity validate_utf8_char(const char* str, size_t offset) {
    // Single-byte UTF-8 characters have the form 0xxxxxxx
    if (((uint8_t)str[offset] & 0b10000000) == 0b00000000)
        return (utf8_char_validity) { .valid = true, .next_offset = offset + 1 };

    // Two-byte UTF-8 characters have the form 110xxxxx 10xxxxxx
    if (((uint8_t)str[offset + 0] & 0b11100000) == 0b11000000 &&
        ((uint8_t)str[offset + 1] & 0b11000000) == 0b10000000) {

        // Check for overlong encoding
        // 0(xxxxxxx)
        // 0(1111111)
        // 110(xxxxx) 10(xxxxxx)
        // 110(00001) 10(111111)
        // 110(00010) 10(000000)
        if (((uint8_t)str[offset] & 0b00011111) < 0b00000010)
            return (utf8_char_validity) { .valid = false, .next_offset = offset };

        return (utf8_char_validity) { .valid = true, .next_offset = offset + 2 };
    }

    // Three-byte UTF-8 characters have the form 1110xxxx 10xxxxxx 10xxxxxx
    if (((uint8_t)str[offset + 0] & 0b11110000) == 0b11100000 &&
        ((uint8_t)str[offset + 1] & 0b11000000) == 0b10000000 &&
        ((uint8_t)str[offset + 2] & 0b11000000) == 0b10000000) {

        // Check for overlong encoding
        // 110(xxxxx) 10(xxxxxx)
        // 110(11111) 10(111111)
        // 1110(xxxx) 10(xxxxxx) 10(xxxxxx)
        // 1110(0000) 10(011111) 10(111111)
        // 1110(0000) 10(100000) 10(000000)
        if (((uint8_t)str[offset + 0] & 0b00001111) == 0b00000000 &&
            ((uint8_t)str[offset + 1] & 0b00111111) < 0b00100000)
            return (utf8_char_validity) { .valid = false, .next_offset = offset };

        // Reject UTF-16 surrogates
        // U+D800 to U+DFFF
        // 1110(1101) 10(100000) 10(000000) ED A0 80 to 1110(1101) 10(111111) 10(111111) ED BF BF
        if ((uint8_t)str[offset + 0] == 0b11101101 &&
            (uint8_t)str[offset + 1] >= 0b10100000 &&
            (uint8_t)str[offset + 1] <= 0b10111111)
            return (utf8_char_validity) { .valid = false, .next_offset = offset };

        return (utf8_char_validity) { .valid = true, .next_offset = offset + 3 };
    }

    // Four-byte UTF-8 characters have the form 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
    if (((uint8_t)str[offset + 0] & 0b11111000) == 0b11110000 &&
        ((uint8_t)str[offset + 1] & 0b11000000) == 0b10000000 &&
        ((uint8_t)str[offset + 2] & 0b11000000) == 0b10000000 &&
        ((uint8_t)str[offset + 3] & 0b11000000) == 0b10000000) {

        // Check for overlong encoding
        // 1110(xxxx) 10(xxxxxx) 10(xxxxxx)
        // 1110(1111) 10(111111) 10(111111)
        // 11110(xxx) 10(xxxxxx) 10(xxxxxx) 10(xxxxxx)
        // 11110(000) 10(001111) 10(111111) 10(111111)
        // 11110(000) 10(010000) 10(000000) 10(000000)
        if (((uint8_t)str[offset + 0] & 0b00000111) == 0b00000000 &&
            ((uint8_t)str[offset + 1] & 0b00111111) < 0b00010000)
            return (utf8_char_validity) { .valid = false, .next_offset = offset };

        return (utf8_char_validity) { .valid = true, .next_offset = offset + 4 };
    }

    return (utf8_char_validity) { .valid = false, .next_offset = offset };
}

utf8_validity validate_utf8(const char* str) {
    if (str == NULL) return (utf8_validity) { .valid = false, .valid_upto = 0 };

    size_t offset = 0;
    utf8_char_validity char_validity;

    while (str[offset] != '\0') {
        char_validity = validate_utf8_char(str, offset);
        if (char_validity.valid) offset = char_validity.next_offset;
        else return (utf8_validity) { .valid = false, .valid_upto = offset };
    }

    return (utf8_validity) { .valid = true, .valid_upto = offset };
}

utf8_string make_utf8_string(const char* str) {
    utf8_validity validity = validate_utf8(str);
    if (validity.valid) return (utf8_string) { .str = str, .byte_len = validity.valid_upto };
    return (utf8_string) { .str = NULL, .byte_len = 0 };
}

owned_utf8_string make_utf8_string_lossy(const char* str) {
    if (str == NULL) return (owned_utf8_string) { .str = NULL, .byte_len = 0 };

    size_t len = strlen(str);

    // Worst case scenario: every byte is invalid and is replaced with 3 bytes for U+FFFD
    size_t worst_case_size = len * 3 + 1;

    // Allocate buffer for the lossy UTF-8 string
    char* buffer = (char*)malloc(worst_case_size);
    if (!buffer) return (owned_utf8_string) { .str = NULL, .byte_len = 0 }; // failed allocation

    size_t buffer_offset = 0;
    size_t offset = 0;
    utf8_char_validity char_validity;

    while (offset < len) {
        char_validity = validate_utf8_char(str, offset);

        if (char_validity.valid) {
            // Copy valid UTF-8 character sequence to the buffer
            size_t char_len = char_validity.next_offset - offset;
            memcpy(buffer + buffer_offset, str + offset, char_len);
            buffer_offset += char_len;
            offset = char_validity.next_offset;
        } else {
            // Insert the UTF-8 bytes for U+FFFD (�)
            // FFFD = 1111111111111101
            //      = (1111) (111111) (111101)
            //      = 1110(1111) 10(111111) 10(111101)
            //      = EF BF BD
            buffer[buffer_offset++] = 0xEF;
            buffer[buffer_offset++] = 0xBF;
            buffer[buffer_offset++] = 0xBD;
            offset++;
        }
    }

    buffer[buffer_offset] = '\0';

    return (owned_utf8_string) { .str = buffer, .byte_len = buffer_offset };
}

utf8_string as_utf8_string(const owned_utf8_string* owned_str) {
    return (utf8_string) { .str = owned_str->str, .byte_len = owned_str->byte_len };
}

void free_owned_utf8_string(owned_utf8_string* owned_str) {
    if (owned_str->str) {
        free(owned_str->str);
        owned_str->str = NULL;
        owned_str->byte_len = 0;
    }
}

utf8_char_iter make_utf8_char_iter(utf8_string ustr) {
    return (utf8_char_iter) { .str = ustr.str };
}

bool is_utf8_char_boundary(const char* str) {
    return (uint8_t)*str <= 0b01111111 || (uint8_t)*str >= 0b11000000;
}

utf8_string slice_utf8_string(utf8_string ustr, size_t start_byte_index, size_t byte_len) {
    if (start_byte_index > ustr.byte_len) start_byte_index = ustr.byte_len;

    size_t excl_end_byte_index = start_byte_index + byte_len;
    if (excl_end_byte_index > ustr.byte_len) excl_end_byte_index = ustr.byte_len;

    if (is_utf8_char_boundary(ustr.str + start_byte_index) && is_utf8_char_boundary(ustr.str + excl_end_byte_index))
        return (utf8_string) { .str = ustr.str + start_byte_index, .byte_len = excl_end_byte_index - start_byte_index };

    return (utf8_string) { .str = NULL, .byte_len = 0 };
}

utf8_char next_utf8_char(utf8_char_iter* iter) {
    if (*iter->str == '\0') return (utf8_char) { .str = iter->str, .byte_len = 0 };

    // iter->str is at the current char's starting byte (char boundary).
    const char* curr_boundary = iter->str;

    iter->str++;
    uint8_t byte_len = 1;

    // find the next char's starting byte (next char boundary) and set the iter->str to that.
    while (!is_utf8_char_boundary(iter->str)) {
        iter->str++;
        byte_len++;
    }

    return (utf8_char) { .str = curr_boundary, .byte_len = byte_len };
}

utf8_char nth_utf8_char(utf8_string ustr, size_t char_index) {
    utf8_char_iter iter = make_utf8_char_iter(ustr);

    utf8_char ch;
    while ((ch = next_utf8_char(&iter)).byte_len != 0 && char_index-- != 0) {}

    if (ch.byte_len == 0) return (utf8_char) { .str = NULL, .byte_len = 0 };
    return ch;
}

size_t utf8_char_count(utf8_string ustr) {
    utf8_char_iter iter = make_utf8_char_iter(ustr);

    size_t count = 0;
    while (next_utf8_char(&iter).byte_len > 0) count++;
    return count;
}

uint32_t unicode_code_point(utf8_char uchar) {
    switch (uchar.byte_len) {
    case 1: return uchar.str[0] & 0b01111111;
    case 2: return
        (uchar.str[0] & 0b00011111) << 6 |
        (uchar.str[1] & 0b00111111);
    case 3: return
        (uchar.str[0] & 0b00001111) << 12 |
        (uchar.str[1] & 0b00111111) << 6 |
        (uchar.str[2] & 0b00111111);
    case 4: return
        (uchar.str[0] & 0b00000111) << 18 |
        (uchar.str[1] & 0b00111111) << 12 |
        (uchar.str[2] & 0b00111111) << 6 |
        (uchar.str[3] & 0b00111111);
    }

    return 0; // unreachable
}
font.h
/* font.h
 * pro utf8 bitmapové fonty
 */
#ifndef __FONT_H
#define __FONT_H

#include "pico/stdlib.h"

/// bitmap font structure
typedef struct bitmap_font {
	unsigned char 		Width;		///< max. character width
	unsigned char 		Height;		///< character height
	unsigned int        Chars;		///< number of characters in font
	const unsigned char *Widths;	///< width of each character
	const uint32_t      *Index;		///< encoding to character index
	const unsigned char *Bitmap;	///< bitmap of all characters
} bitmapFONT;

#define ________ 0x00
#define _______X 0x01
#define ______X_ 0x02
#define ______XX 0x03
#define _____X__ 0x04
#define _____X_X 0x05
#define _____XX_ 0x06
#define _____XXX 0x07
#define ____X___ 0x08
#define ____X__X 0x09
#define ____X_X_ 0x0A
#define ____X_XX 0x0B
#define ____XX__ 0x0C
#define ____XX_X 0x0D
#define ____XXX_ 0x0E
#define ____XXXX 0x0F
#define ___X____ 0x10
#define ___X___X 0x11
#define ___X__X_ 0x12
#define ___X__XX 0x13
#define ___X_X__ 0x14
#define ___X_X_X 0x15
#define ___X_XX_ 0x16
#define ___X_XXX 0x17
#define ___XX___ 0x18
#define ___XX__X 0x19
#define ___XX_X_ 0x1A
#define ___XX_XX 0x1B
#define ___XXX__ 0x1C
#define ___XXX_X 0x1D
#define ___XXXX_ 0x1E
#define ___XXXXX 0x1F
#define __X_____ 0x20
#define __X____X 0x21
#define __X___X_ 0x22
#define __X___XX 0x23
#define __X__X__ 0x24
#define __X__X_X 0x25
#define __X__XX_ 0x26
#define __X__XXX 0x27
#define __X_X___ 0x28
#define __X_X__X 0x29
#define __X_X_X_ 0x2A
#define __X_X_XX 0x2B
#define __X_XX__ 0x2C
#define __X_XX_X 0x2D
#define __X_XXX_ 0x2E
#define __X_XXXX 0x2F
#define __XX____ 0x30
#define __XX___X 0x31
#define __XX__X_ 0x32
#define __XX__XX 0x33
#define __XX_X__ 0x34
#define __XX_X_X 0x35
#define __XX_XX_ 0x36
#define __XX_XXX 0x37
#define __XXX___ 0x38
#define __XXX__X 0x39
#define __XXX_X_ 0x3A
#define __XXX_XX 0x3B
#define __XXXX__ 0x3C
#define __XXXX_X 0x3D
#define __XXXXX_ 0x3E
#define __XXXXXX 0x3F
#define _X______ 0x40
#define _X_____X 0x41
#define _X____X_ 0x42
#define _X____XX 0x43
#define _X___X__ 0x44
#define _X___X_X 0x45
#define _X___XX_ 0x46
#define _X___XXX 0x47
#define _X__X___ 0x48
#define _X__X__X 0x49
#define _X__X_X_ 0x4A
#define _X__X_XX 0x4B
#define _X__XX__ 0x4C
#define _X__XX_X 0x4D
#define _X__XXX_ 0x4E
#define _X__XXXX 0x4F
#define _X_X____ 0x50
#define _X_X___X 0x51
#define _X_X__X_ 0x52
#define _X_X__XX 0x53
#define _X_X_X__ 0x54
#define _X_X_X_X 0x55
#define _X_X_XX_ 0x56
#define _X_X_XXX 0x57
#define _X_XX___ 0x58
#define _X_XX__X 0x59
#define _X_XX_X_ 0x5A
#define _X_XX_XX 0x5B
#define _X_XXX__ 0x5C
#define _X_XXX_X 0x5D
#define _X_XXXX_ 0x5E
#define _X_XXXXX 0x5F
#define _XX_____ 0x60
#define _XX____X 0x61
#define _XX___X_ 0x62
#define _XX___XX 0x63
#define _XX__X__ 0x64
#define _XX__X_X 0x65
#define _XX__XX_ 0x66
#define _XX__XXX 0x67
#define _XX_X___ 0x68
#define _XX_X__X 0x69
#define _XX_X_X_ 0x6A
#define _XX_X_XX 0x6B
#define _XX_XX__ 0x6C
#define _XX_XX_X 0x6D
#define _XX_XXX_ 0x6E
#define _XX_XXXX 0x6F
#define _XXX____ 0x70
#define _XXX___X 0x71
#define _XXX__X_ 0x72
#define _XXX__XX 0x73
#define _XXX_X__ 0x74
#define _XXX_X_X 0x75
#define _XXX_XX_ 0x76
#define _XXX_XXX 0x77
#define _XXXX___ 0x78
#define _XXXX__X 0x79
#define _XXXX_X_ 0x7A
#define _XXXX_XX 0x7B
#define _XXXXX__ 0x7C
#define _XXXXX_X 0x7D
#define _XXXXXX_ 0x7E
#define _XXXXXXX 0x7F
#define X_______ 0x80
#define X______X 0x81
#define X_____X_ 0x82
#define X_____XX 0x83
#define X____X__ 0x84
#define X____X_X 0x85
#define X____XX_ 0x86
#define X____XXX 0x87
#define X___X___ 0x88
#define X___X__X 0x89
#define X___X_X_ 0x8A
#define X___X_XX 0x8B
#define X___XX__ 0x8C
#define X___XX_X 0x8D
#define X___XXX_ 0x8E
#define X___XXXX 0x8F
#define X__X____ 0x90
#define X__X___X 0x91
#define X__X__X_ 0x92
#define X__X__XX 0x93
#define X__X_X__ 0x94
#define X__X_X_X 0x95
#define X__X_XX_ 0x96
#define X__X_XXX 0x97
#define X__XX___ 0x98
#define X__XX__X 0x99
#define X__XX_X_ 0x9A
#define X__XX_XX 0x9B
#define X__XXX__ 0x9C
#define X__XXX_X 0x9D
#define X__XXXX_ 0x9E
#define X__XXXXX 0x9F
#define X_X_____ 0xA0
#define X_X____X 0xA1
#define X_X___X_ 0xA2
#define X_X___XX 0xA3
#define X_X__X__ 0xA4
#define X_X__X_X 0xA5
#define X_X__XX_ 0xA6
#define X_X__XXX 0xA7
#define X_X_X___ 0xA8
#define X_X_X__X 0xA9
#define X_X_X_X_ 0xAA
#define X_X_X_XX 0xAB
#define X_X_XX__ 0xAC
#define X_X_XX_X 0xAD
#define X_X_XXX_ 0xAE
#define X_X_XXXX 0xAF
#define X_XX____ 0xB0
#define X_XX___X 0xB1
#define X_XX__X_ 0xB2
#define X_XX__XX 0xB3
#define X_XX_X__ 0xB4
#define X_XX_X_X 0xB5
#define X_XX_XX_ 0xB6
#define X_XX_XXX 0xB7
#define X_XXX___ 0xB8
#define X_XXX__X 0xB9
#define X_XXX_X_ 0xBA
#define X_XXX_XX 0xBB
#define X_XXXX__ 0xBC
#define X_XXXX_X 0xBD
#define X_XXXXX_ 0xBE
#define X_XXXXXX 0xBF
#define XX______ 0xC0
#define XX_____X 0xC1
#define XX____X_ 0xC2
#define XX____XX 0xC3
#define XX___X__ 0xC4
#define XX___X_X 0xC5
#define XX___XX_ 0xC6
#define XX___XXX 0xC7
#define XX__X___ 0xC8
#define XX__X__X 0xC9
#define XX__X_X_ 0xCA
#define XX__X_XX 0xCB
#define XX__XX__ 0xCC
#define XX__XX_X 0xCD
#define XX__XXX_ 0xCE
#define XX__XXXX 0xCF
#define XX_X____ 0xD0
#define XX_X___X 0xD1
#define XX_X__X_ 0xD2
#define XX_X__XX 0xD3
#define XX_X_X__ 0xD4
#define XX_X_X_X 0xD5
#define XX_X_XX_ 0xD6
#define XX_X_XXX 0xD7
#define XX_XX___ 0xD8
#define XX_XX__X 0xD9
#define XX_XX_X_ 0xDA
#define XX_XX_XX 0xDB
#define XX_XXX__ 0xDC
#define XX_XXX_X 0xDD
#define XX_XXXX_ 0xDE
#define XX_XXXXX 0xDF
#define XXX_____ 0xE0
#define XXX____X 0xE1
#define XXX___X_ 0xE2
#define XXX___XX 0xE3
#define XXX__X__ 0xE4
#define XXX__X_X 0xE5
#define XXX__XX_ 0xE6
#define XXX__XXX 0xE7
#define XXX_X___ 0xE8
#define XXX_X__X 0xE9
#define XXX_X_X_ 0xEA
#define XXX_X_XX 0xEB
#define XXX_XX__ 0xEC
#define XXX_XX_X 0xED
#define XXX_XXX_ 0xEE
#define XXX_XXXX 0xEF
#define XXXX____ 0xF0
#define XXXX___X 0xF1
#define XXXX__X_ 0xF2
#define XXXX__XX 0xF3
#define XXXX_X__ 0xF4
#define XXXX_X_X 0xF5
#define XXXX_XX_ 0xF6
#define XXXX_XXX 0xF7
#define XXXXX___ 0xF8
#define XXXXX__X 0xF9
#define XXXXX_X_ 0xFA
#define XXXXX_XX 0xFB
#define XXXXXX__ 0xFC
#define XXXXXX_X 0xFD
#define XXXXXXX_ 0xFE
#define XXXXXXXX 0xFF

#endif

Ostatní fonty a všechny zdrojové soubory jsou v archivu: ssd1363-static-v0.2.tar.gz

Zdroje a odkazy