Konstrukce generátoru náhody pro výběr maturitních otázek pro studenty.
Displej SH1106 na sběrnici SPI.
Čekání na náhodu

Vygenerovaná otázka

Zapojení displeje
| pin na displeji | pin na Picu |
|---|---|
GND (bílo oranžová) |
GND |
VCC (oranžová) |
3V3 |
SCK (černá) |
SPI0 SCK (GP18) |
SDA (červená) |
SPI0 TX (GP19) MOSI |
RES (zelená) |
GP20 (reset displeje) |
DC (bílá) |
GP16 |
CS (žlutá) |
GP17 (chip select) |
Zdrojové kódy
CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)
set(PICO_BOARD pico_w)
project(nahoda C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_executable(nahoda
main.c
sh1106_spi.c
utf8.c
font_spleen_8x16.c
font_spleen_16x32.c
)
target_link_libraries(nahoda pico_stdlib hardware_spi)
# create map/bin/hex file etc.
pico_enable_stdio_usb(nahoda 1)
pico_enable_stdio_uart(nahoda 0)
pico_add_extra_outputs(nahoda)
main.c
/* Generator nahody s SH1106 OLED displejem (spi sběrnice)
* (c) Jirka Chráska 2026; jirka@lixis.cz
*/
#include <stdlib.h>
#include <string.h>
#include <pico/stdlib.h>
#include "sh1106_spi.h"
#include "font.h"
sh1106_t disp;
extern bitmapFONT font_spleen_6x12;
extern bitmapFONT font_spleen_8x16;
extern bitmapFONT font_spleen_16x32;
#define WHITE 1
#define BLACK 0
void screen( const char *str1, const char *str2 )
{
SH1106_clear(&disp);
SH1106_drawString(&disp, 0, 0, &font_spleen_8x16, str1, WHITE);
SH1106_drawString(&disp, 0, 32, &font_spleen_8x16, str2, WHITE);
SH1106_draw(&disp);
}
#define BAUD 1000000
#define MOSI 19
#define CLK 18
#define DC 16
#define CS 17
#define RST 20
#define SIRKA 128
#define VYSKA 64
#define RANDOM_MIN 1
#define RANDOM_MAX 25
#define TL_PIN 15
int main() {
char buf1[128];
int x;
uint64_t t;
memset(buf1,'\0',128);
stdio_init_all();
gpio_init(TL_PIN);
gpio_set_dir(TL_PIN, GPIO_OUT);
gpio_pull_down(TL_PIN);
// inicializace SPI displeje
//
SH1106_init(&disp, spi0, BAUD, MOSI, CLK, DC, CS, RST, SIRKA, VYSKA);
screen("Výběr maturitní otázky","Stiskněte tlačítko");
sleep_ms(1500);
while( !gpio_get(TL_PIN) ) {
sleep_ms(10);
}
t = time_us_64();
SH1106_clear(&disp);
srand((unsigned int) t);
// x bude náhodné v rozsahu RANDOM_MIN až RANDOM_MAX
x = rand() % (RANDOM_MAX + 1 - RANDOM_MIN) + RANDOM_MIN;
screen("Máte otázku:", "Můžete se jít připravit.");
sprintf(buf1,"%d",x);
SH1106_drawString(&disp,96,0,&font_spleen_16x32, buf1, WHITE);
SH1106_draw(&disp);
return 0;
}
sh1106_spi.c
/* sh1106_spi.c
* Display driver for SH1106 spi bus
* (c) Jirka Chráska 2026; <jirka@lixis.cz>
*/
#include "sh1106_spi.h"
#include "utf8.h"
#include "font.h"
#include <math.h>
#include <stdlib.h>
/*
Příkazy pro SH1102 z datasheetu (nejsou použité)
#define SH1106_DISPLAYOFF 0xAE
#define SH1106_DISPLAYCLKDIVIDE 0xD5
#define SH1106_OSCILLATORFREQ 0x80
#define SH1106_MULTIPLEXRATIO_1 0xA8
#define SH1106_MULTIPLEXRATIO_2 0x3F
#define SH1106_DISPLAYOFFSET_1 0xD3
#define SH1106_DISPLAYOFFSET_2 0x00
#define SH1106_DISPLAYSTARTLINE 0x40
#define SH1106_CHARGEPUMP_1 0xAD
#define SH1106_CHARGEPUMP_2 0x8B
#define SH1106_SEGMENTREMAP 0xA1
#define SH1106_COMOUTSCANDIR 0xC8
#define SH1106_COMPINHWCONFIG_1 0xDA
#define SH1106_COMPINHWCONFIG_2 0x12
#define SH1106_CONTRASTCTRL_1 0x81
#define SH1106_CONTRASTCTRL_2 0xFF
#define SH1106_PRECHARGEPERIOD_1 0xD9
#define SH1106_PRECHARGEPERIOD_2 0x1F
#define SH1106_VCOMHDESELLVL_1 0xDB
#define SH1106_VCOMHDESELLVL_2 0x40
#define SH1106_VPP 0x33
#define SH1106_NORMALINVDISPL 0xA6
#define SH1106_DISPLAYON 0xAF
#define SH1106_PAGE0 0xB0
#define SH1106_PAGE_OFFSET(x) (SH1106_PAGE0 + x)
---
#define SET_DISP 0xAE
#define SET_SCAN_DIR 0xC0
#define SET_SEG_REMAP 0xA0
#define LOW_COL_ADDR 0x00
#define HIGH_COL_ADDR 0x10
#define SET_PAGE_ADDR 0xB0
---
*/
static uint8_t pageBuffer[8][128+4];
inline static void swap(int32_t *a, int32_t *b)
{
int32_t *t=a;
*a=*b;
*b=*t;
};
// inicializace SPI a displeje v jednom
uint SH1106_init(sh1106_t *sh1106, spi_inst_t *spi, uint32_t baud, uint8_t mosi, uint8_t clk, uint8_t dc_pin, uint8_t cs_pin, uint8_t rst_pin, uint8_t width, uint8_t height)
{
uint res;
sh1106->width = width;
sh1106->height = height;
sh1106->pages = height / 8;
sh1106->spi = spi;
sh1106->baud = baud;
sh1106->mosi = mosi;
sh1106->clk = clk;
sh1106->dc_pin = dc_pin;
sh1106->cs_pin = cs_pin;
sh1106->rst_pin = rst_pin;
sh1106->buffer = (uint8_t **) pageBuffer;
// inicializace reset pinu
gpio_init(sh1106->rst_pin);
gpio_set_dir(sh1106->rst_pin, GPIO_OUT);
// inicializace CS pinu
gpio_init(sh1106->cs_pin);
gpio_set_dir(sh1106->cs_pin, GPIO_OUT);
gpio_put(sh1106->cs_pin, 1); // CS->1, nelze komunikovat s displejem
// inicializace SPI
gpio_set_function(sh1106->mosi, GPIO_FUNC_SPI);
gpio_set_function(sh1106->clk, GPIO_FUNC_SPI);
res = spi_init(sh1106->spi, sh1106->baud);
// inicializace DC pinu
gpio_init(sh1106->dc_pin);
gpio_set_dir(sh1106->dc_pin, GPIO_OUT);
gpio_put(sh1106->dc_pin,0);
// reset displeje
gpio_put(sh1106->rst_pin,0);
sleep_ms(10);
gpio_put(sh1106->rst_pin,1);
sleep_ms(100);
for(uint8_t i = 0; i < 8; i++) { // vymazání displeje (černá)
for(uint8_t j = 0; j < 128+4; j++) {
pageBuffer[i][j] = 0x00;
}
}
SH1106_Write_CMD(sh1106, SET_SEG_REMAP | 0x01); // prohodit vodorovně
SH1106_Write_CMD(sh1106, SET_SCAN_DIR | 0x08); // prohodit svisle
SH1106_Write_CMD(sh1106, SET_DISP | 0x01);
sleep_ms(100);
return res;
}
// poslání příkazu do displeje
void SH1106_Write_CMD(sh1106_t *sh1106, uint8_t command)
{
uint8_t buffer[2];
buffer[0] = 0x80;
buffer[1] = command;
gpio_put(sh1106->dc_pin, 0); // DC->0, bude se posílat příkaz
gpio_put(sh1106->cs_pin, 0); // počátek komunikace s displejem
spi_write_blocking(sh1106->spi, buffer, 2);
gpio_put(sh1106->cs_pin, 1); // vypnutí komunikace s displejem
}
// poslání dat do displeje
void SH1106_Write_Data(sh1106_t *sh1106, uint8_t* data)
{
size_t bufsize = sh1106->width+1;
uint8_t broadCastBuffer[bufsize];
broadCastBuffer[0] = 0x40;
for (int i = 0; i < sh1106->width; i++) {
broadCastBuffer[i+1] = data[i];
}
gpio_put(sh1106->dc_pin, 1); // DC->1, budou se posílat data
gpio_put(sh1106->cs_pin, 0); // počátek komunikace
spi_write_blocking(sh1106->spi, broadCastBuffer, bufsize);
gpio_put(sh1106->cs_pin, 1); // konec komunikace
}
// vykreslení celého pageBufferu na displeji
void SH1106_draw(sh1106_t *sh1106)
{
for(uint8_t page = 0; page < sh1106->pages; page++){
SH1106_Write_CMD(sh1106, SET_PAGE_ADDR | page);
SH1106_Write_CMD(sh1106, LOW_COL_ADDR | 0x01);
SH1106_Write_CMD(sh1106, HIGH_COL_ADDR | 0x00);
SH1106_Write_Data(sh1106, pageBuffer[page]);
}
}
// kreslení bodu do pageBufferu
void SH1106_drawPixel(sh1106_t *sh1106, uint8_t x, uint8_t y, uint8_t color)
{
if(x > sh1106->width || y > sh1106->height){
return;
}
if(color == 0){
pageBuffer[y/8][x] &= ~(1 << (y % 8));
}else{
pageBuffer[y/8][x] |= (1 << (y % 8));
}
}
// kreslení čáry -- dole je lepší funkce
// void SH1106_drawLine(sh1106_t *sh1106, int32_t x1, int32_t y1, int32_t x2, int32_t y2)
// {
// if(x1 > x2) {
// swap(&x1, &x2);
// swap(&y1, &y2);
// }
// if(x1 == x2) {
// if(y1 > y2) {
// swap(&y1, &y2);
// }
// for( int32_t i=y1; i<=y2; ++i) {
// SH1106_drawPixel(sh1106, x1, i, 1);
// }
// return;
// }
//
// float m = (float) (y2-y1) / (float) (x2-x1);
// for(int32_t i=x1; i<=x2; ++i) {
// float y = m * (float) (i-x1) + (float) y1;
// SH1106_drawPixel( sh1106, i, (uint32_t) y, 1);
// }
// }
// kreslení čáry (Bresenham)
void SH1106_drawLine(sh1106_t *sh1106, int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint8_t color)
{
int dx = abs(x1-x0), sx = x0<x1 ? 1 : -1;
int dy = -abs(y1-y0), sy = y0<y1 ? 1 : -1;
int err = dx+dy, e2; /* error value e_xy */
for(;;){ /* loop */
SH1106_drawPixel(sh1106, 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í vodorovné linky do pageBufferu
void SH1106_drawHLine(sh1106_t *sh1106, uint8_t x, uint8_t y, uint8_t w, uint8_t color)
{
if(x > sh1106->width || y > sh1106->height){
return;
}
if((x + w) > sh1106->width){
w = sh1106->width - x;
}
for(uint8_t i = 0; i < w; i++){
SH1106_drawPixel(sh1106, x + i, y, color);
}
};
// kreslení svislé linky do pageBufferu
void SH1106_drawVLine(sh1106_t *sh1106, uint8_t x, uint8_t y, uint8_t w, uint8_t color)
{
if(x > sh1106->width || y > sh1106->height){
return;
}
if((x + w) > sh1106->height){
w = sh1106->height - x;
}
for(uint8_t i = 0; i < w; i++){
SH1106_drawPixel(sh1106, x, y+i, color);
}
};
// kreslení kružnice
void SH1106_drawCircle(sh1106_t *sh1106, int xm, int ym, int r, uint8_t color)
{
int x = -r, y = 0, err = 2-2*r; /* II. Quadrant */
do {
SH1106_drawPixel(sh1106, xm-x, ym+y, color); /* I. Quadrant */
SH1106_drawPixel(sh1106, xm-y, ym-x, color); /* II. Quadrant */
SH1106_drawPixel(sh1106, xm+x, ym-y, color); /* III. Quadrant */
SH1106_drawPixel(sh1106, 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);
}
// vymazání pageBufferu
void SH1106_clear(sh1106_t *sh1106)
{
for(uint8_t i = 0; i < 8; i++){ //dark screen
for(uint8_t j = 0; j < 128; j++){
pageBuffer[i][j] = 0x00;
}
}
};
// kreslení obdélníku: x,y jsou souřadnice pravého horního rohu, width je výška a height je šířka obdélníku
void SH1106_drawRectangle(sh1106_t *sh1106, uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint8_t color)
{
for(uint8_t i=0; i<width; ++i)
for(uint8_t j=0; j<height; ++j)
SH1106_drawPixel(sh1106, x+i, y+j, color);
};
/*
kreslení jednoho znaku
function: SH1106_drawChar
parameters:
p : ukazatel na strukturu sh1106_t
x : souřadnice x
y : souřadnice y
Font : ukazatel na strukturu bitmapFONT
Index : index znaku ve fontu
vrací šířku znaku v pixelech
*/
uint16_t SH1106_drawChar(sh1106_t * p, uint32_t x, uint32_t y, const bitmapFONT* Font, uint32_t Index, uint8_t color)
{
uint8_t row, column, fcolor;
if( x > p->width || y > p->height ) {
return 0;
}
uint8_t col = Font->Width/8; // sloupce v bytech
uint8_t fcol = col;
uint8_t zb = Font->Width%8;
if( zb != 0) {
col ++;
}
uint8_t chwidth = Font->Widths[Index]; // šířka znaku
uint16_t chsize = Font->Height * col;
uint32_t bindex = Index*chsize;
for (row = 0; row < Font->Height; row++ ) {
// lezeme po bytech (po osmičkách)
uint8_t chw = chwidth;
for (column = 0; column < col; column++, chw-- ) {
for( int j = 0; j<8; j++ ) {
if( chw == 0 ) break;
fcolor = ((uint8_t) Font->Bitmap[bindex+row*col+column]) & (0x80 >> j);
if( fcolor ) {
SH1106_drawPixel(p, x + column*8 + j, y + row, color);
} else {
SH1106_drawPixel(p, x + column*8 + j, y + row, !color);
}
}
}
}
return chwidth;
}
/**
* kreslení UTF-8 řetězce
parameters:
p : ukazatel na strukturu sh1106_t
x : souřadnice x
y : souřadnice y
Font : ukazatel na strukturu bitmapFONT
pString : řetězec
returns:
šířka v pixelech zabíraná řetězcem
*/
uint16_t SH1106_drawString(sh1106_t *p, uint32_t x, uint32_t y, bitmapFONT * Font, const char * pString, uint8_t color )
{
uint32_t Xpoint = x;
uint32_t Ypoint = y;
uint16_t index;
uint16_t str_width = 0;
uint16_t char_width = 0;
if (x > p->width || y > p->height) {
return 0;
}
utf8_string ustr = make_utf8_string(pString);
utf8_char_iter iter = make_utf8_char_iter(ustr);
utf8_char c;
uint32_t codepoint;
while ((c = next_utf8_char(&iter)).byte_len > 0 ) {
codepoint = unicode_code_point(c);
index = 0;
for(int i=0; i < Font->Chars; i++) {
if(Font->Index[i] == codepoint ) {
index = i;
break;
}
}
if((Xpoint + Font->Width ) > p->width ) {
Xpoint = x;
Ypoint += Font->Height;
}
if ((Ypoint + Font->Height ) > p->height ) {
Xpoint = x;
Ypoint = y;
}
char_width = SH1106_drawChar(p, Xpoint, Ypoint, Font, index, color);
Xpoint += char_width;
str_width += char_width;
}
return str_width;
}
sh1106_spi.h
/* sh1106_spi.h
* (c) Jirka Chráska 2026, <jirka@lixis.cz>
*/
#ifndef SH1106_SH1106_SPI_H
#define SH1106_SH1106_SPI_H
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/malloc.h"
#include <malloc.h>
#include "hardware/spi.h"
#include "font.h"
#define SET_DISP 0xAE
#define SET_SCAN_DIR 0xC0
#define SET_SEG_REMAP 0xA0
#define LOW_COL_ADDR 0x00
#define HIGH_COL_ADDR 0x10
#define SET_PAGE_ADDR 0xB0
typedef struct sh1106 {
uint8_t width; // šířka displeje (128)
uint8_t height; // výška displeje (64)
uint8_t pages; // počet stránek
uint8_t **buffer; // displej bufer
spi_inst_t *spi; // spi instance (spi0 nebo spi1)
uint32_t baud; // rychlost spi sběrnice
uint8_t mosi; // MOSI pin na Picu
uint8_t clk; // hodinový pin na Picu
uint8_t dc_pin; // DC pin na Picu
uint8_t cs_pin; // CS pin na Picu
uint8_t rst_pin; // reset pin
} sh1106_t;
// poslání dat na displej
void SH1106_Write_Data(sh1106_t *sh1106, uint8_t* data);
// poslání příkazu na displej
void SH1106_Write_CMD(sh1106_t *sh1106, uint8_t command);
// inicializace SPI a displeje
// spi je použitá SPI sběrnice (spi0 nebo spi1)
// baud je rychlost přenosu dat ( obvykle 1000000
// mosi je Pico pin SPIx TX
// clk je Pico pin SPIx CSn
// dc_pin je GPIO pin pro signalizaci přenosu dat
// cs_pin je GPIO pin pro výběr čipu (chip select)
// rts_pin je GPIO pin pro reset displeje
// width je šířka displeje (obvykle 128)
// height je výška displeje (obvykle 64)
uint SH1106_init(sh1106_t *sh1106, spi_inst_t *spi, uint32_t baud, uint8_t mosi, uint8_t clk, uint8_t dc_pin, uint8_t cs_pin, uint8_t rst_pin, uint8_t width, uint8_t height);
// aktualizace displeje - přenos dat z pageBufferu na displej
void SH1106_draw(sh1106_t *sh1106);
// vymazání pageBufferu (pro vymazání displeje je potřeba potom zavolat funkci SH1106_draw)
void SH1106_clear(sh1106_t *sh1106);
// kreslení bodu
// x,y jsou souřadnice bodu
// color je barva bodu (0 - černá, 1 - bílá)
void SH1106_drawPixel(sh1106_t *sh1106, uint8_t x, uint8_t y, uint8_t color);
// kreslení úsečky z bodu [x0,y0] do bodu [x1,y1]
// barva (color): 0 - černá, 1 - bílá
void SH1106_drawLine(sh1106_t *sh1106, int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint8_t color);
// kreslení vodorovné úsečky
// x,y jsou souřadnice krajního bodu,
// w je délka úsečky
// color je barva úsečky (0 - černá, 1 - bílá )
void SH1106_drawHLine(sh1106_t *sh1106, uint8_t x, uint8_t y, uint8_t w, uint8_t color);
// kreslení svislé úsečky
// x,y jsou souřadnice krajního bodu,
// w je délka úsečky
// color je barva úsečky (0 - černá, 1 - bílá )
void SH1106_drawVLine(sh1106_t *sh1106, uint8_t x, uint8_t y, uint8_t w, uint8_t color);// kreslení kružnice
// kreslení kružnice:
// xm a ym jsou souřadnice středu kružnice,
// r je poloměr kružnice,
// color je barva kružnice (0 - černá, 1-bílá)
void SH1106_drawCircle(sh1106_t *sh1106, int xm, int ym, int r, uint8_t color);
// kreslení nevyplněného obdélníku
// x,y jsou souřadnice levého horního bodu obdélníku
// width a height je šířka a výška displeje v bodech (max 0 až 128)
// color je barva obdélníku (0 - černá, 1-bílá)
void SH1106_drawRectangle(sh1106_t *sh1106, uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint8_t color);
// kreslení UTF8 řetězce
// x, y jsou rouřadnice levého horního rohu,
// *Font je použitý bitmapový font
// *pstring je C řetězec s UTF8 znaky
// color je barva (0 - černé písmo na bílém pozadí, 1 - bílé písmo na černém pozací)
// Funkce vrací šířku v bodech displeje, kterou zabírá řetězec
uint16_t SH1106_drawString(sh1106_t *p, uint32_t x, uint32_t y, bitmapFONT * Font, const char * pString, uint8_t color);
// kreslení UTF8 znaku
uint16_t SH1106_drawChar(sh1106_t * p, uint32_t x, uint32_t y, const bitmapFONT* Font, uint32_t Index, uint8_t color);
#endif
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
Celý projekt ke stažení ../generator_otazek.tar.gz