Поиск

Простенький подход к шрифтам для микроконтроллеров


Если в шрифте определять все 256 символов, он будет занимать приличное количество флеша, который можно использовать в более благих целях. Поэтому я решил подойти со стороны индексирования лишь тех символов, которые реально в шрифте определены (а в тех же МК зачастую определяется лишь очень скудный набор — все символы таблицы КОИ8-Р обычно не нужны).
Мне как-то mbr подсказал программку «lcd image converter» (в ответ на мое описание веб-морды велосипеда для генерирования шрифтов). Пока вожусь со светодиодной панелью, мне понадобилось сделать набор из нескольких шрифтов для цифр разного размера, вот я и вспомнил про эту штуку. Она достаточно хорошо преобразует в растр разные шрифты, а дальше остается лишь набить вручную все в файле с исходниками.
Саму идею (как и основной шрифт величиной 12 пикселей) я стащил где-то на просторах интернета (Nadyrshin Ruslan, youtube). Но там подход был «в лоб»: надо 256 символов — значит, столько места и займем (хоть половина пустая). Меняем подход.
Итак, нужно нам не так уж и много. Сначала заведем заголовочный файл fonts.h, где определим перечень имеющихся шрифтов, базовые функции и структуру, характеризующую шрифт:

// type for font choosing
typedef enum{ FONT_T_MIN = -1, // no fonts <= this FONT14, // 16x16, font height near 14px FONTN16, // numbers 16x8, font height 16px FONTN10, // numbers 10x8, font height 10px FONT_T_MAX // no fonts >= this
} font_t; int choose_font(font_t newfont);
const uint8_t *font_char(uint8_t Char); typedef struct{ const uint8_t *font; // font inself const uint8_t *enctable; // font encoding table uint8_t height; // full font matrix height uint8_t bytes; // amount of bytes in font matrix uint8_t baseline; // baseline position (coordinate from bottom line)
} afont; extern const afont *curfont;


Глобальная постоянная curfont является указателем на текущий используемый шрифт (задается он командой choose_font, далее описываю содержимое файла fonts.c):

int choose_font(font_t newfont){ if(newfont >= FONT_T_MAX || newfont <= FONT_T_MIN) return 1; curfont = &FONTS[newfont]; return 0;
}


Если шрифт не найден, возвращается единица. Если все ОК — нуль.
Функция font_char позволяет получить доступ к нужному символу:

const uint8_t *font_char(uint8_t Char){ uint8_t idx = curfont->enctable[Char]; if(!idx) return NULL; // no this character in font return &(curfont->font[idx*(curfont->bytes+1)]);
}


Если символ не найден, возвращается NULL. Если найден — указатель на него (первым байтом идет ширина символа в пикселях, далее — данные самого символа).
Еще в файл fonts.c нужно поместить массив — все существующие шрифты:

static const afont FONTS[] = { [FONT14] = {font14_table, font14_encoding, FONT14HEIGHT, FONT14BYTES, FONT14BASELINE}, [FONTN16] = {fontNumb16_table, fontNumb16_encoding, FONTNUMB16HEIGHT, FONTNUMB16BYTES, FONTNUMB16BASELINE}, [FONTN10] = {fontNumb10_table, fontNumb10_encoding, FONTNUMB10HEIGHT, FONTNUMB10BYTES, FONTNUMB10BASELINE},
};
const afont *curfont = &FONTS[FONT14];


(ну и сам указатель на текущий шрифт).
Но в самом начале этого файла надо перечислить дефайны для удобства рисования. Я не буду все эти 256 строк включать, а только приведу скрипт, генерирующий их:

#!/bin/bash function bits(){ Ans="" for x in $(seq 7 -1 0); do B=$((1<<$x)) if [ $(($1&$B)) -ne 0 ]; then Ans="${Ans}X" else Ans="${Ans}_" fi done echo $Ans
} for x in $(seq 0 255); do printf "#define $(bits $x)\t0x%02x\n" $x
done


Сразу после этого включаем все файлы со шрифтами:

#include "font14.h"
#include "fontNumb16.h"
#include "fontNumb10.h"


Ну а потом уже пишем то, что я выше привел.

Содержимое каждого файла со шрифтами несложно нарисовать. Во-первых, это нужные дефайны:

#define FONTNUMB10BYTES 10
#define FONTNUMB10HEIGHT 10
#define FONTNUMB10BASELINE 0


Во-вторых, таблица для перекодировки (своя для каждого шрифта):

const uint8_t fontNumb10_encoding[256] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0..31 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, // 47 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0, 0, 0, // 63 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 79 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 95 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 111 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 127 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 143 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 159 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 175 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 191 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 207 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 223 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 239 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 255
};


Здесь определены маленькие цифры (10 пикселей высотой), поэтому почти вся таблица — нули. Зато очень приличная экономия памяти получается.
Сам этот шрифт — в константе fontNumb10_table[]. Первым ее элементом обязательно должен идти символ-пустышка с нулевой шириной, а дальше — определяемые символы. Скажем, для этого шрифта начало данного массива имеет вид

const uint8_t fontNumb10_table[] = { // 0x00 - empty 0, ________, ________, ________, ________, ________, ________, ________, ________, ________, ________, // 0x20 - ' ' 4, ________, ________, ________, ________, ________, ________, ________, ________, ________, ________, // 0x30 - '0' 7, __XX____, _X__X___, X____X__, X____X__, X____X__, X____X__, X____X__, X____X__, _X__X___, __XX____,


Руслан Надыршин подсказал превосходнейший способ визуализации данных! Мы просто определяем 256 макросов для каждого числа, вместо нуля симпол подчеркивания, вместо единицы — X. И можно прямо в исходниках рисовать символы.
Другой шрифт с цифрами имеет высоту 16 пикселей — на всю высоту экрана. Поэтому там символы выглядят как-то так:

 // 0x32 - '2' 7, __XX____, _XXXX___, XX_XXX__, X___XX__, ____XX__, ____XX__, ____XX__, ____XX__, ___XX___, ___X____, __XX____, __X_____, _XX_____, XX______, XX___X__, XXXXXX__,


А в оригинальном шрифте буквы занимают по высоте почти все 16 пикселей (у некоторых вниз выползает хвостик, у некоторых — вверх). И выглядят они уже вот так:

 // 0x40 , 16, _____XXX,XXX_____, ___XXXXX,XXXXX___, __XXX___,___XXX__, _XXX__XX,X_XXXX__, _XX_XXXX,XXXX_XX_, XXX_XX__,_XXX_XX_, XX_XX___,_XX__XX_, XX_XX___,_XX__XX_, XX_XX___,_XX__XX_, XX_XX___,XXX_XX__, XX_XXXXX,XXXXX___, _XX_XXXX,_XXX____, _XXX____,_____XX_, __XXX___,___XXX__, ___XXXXX,XXXXX___, _____XXX,XXX_____


Здесь, правда, приходится разделять байты запятыми, что немножко ломает наглядность. Но определять 65536 макросов что-то не хочется: боюсь, что сборка в этом случае затянется…
Вот на таких широченных шрифтах 16х16 пикселей экономия флеша при индексации получается вполне приличной: ведь один символ занимает 33 байта, а таблица с индексами — 256. Если в шрифте не определено 8 символов, индексация уже экономит немного места. А если там определено лишь полтора десятка, так совсем прилично!
eddy_em.livejournal.com

Добавить комментарий