Шрифты с GLCD Font Creator на коленке

Известная тема графических дисплеев — необходимость носить шрифты с собой.

Задача:
— IAR, STM32;
— есть дисплей 128х64 OLED(монохром);
— нужен один хороший шрифт с Кириллицей;
— нужна приемлемая читаемость и размер;
— нужна хорошая плотность записи на экране;
— нужно вводить строки прямо в коде программы, не задумываясь над кодировками.

Итак, начнём решать задачи.
1) Дабы вводить строки прямо в программе определимся с кодировками. Решил проверить кодировки, а именно, понять как они проходят внутрь контроллера через компилятор.
Пишем небольшой код:
char A = 'A', z = 'z', CyrA = 'А', CyrB = 'Б', Cyrya = 'я';

Запускаем код на контроллере, останавливаемся и смотрим получившиеся значения HEX в отладчике:
Лат. A = 0x41
Лат. z = 0x7A
Кир. А = 0xC0
Кир. Б = 0xC1
Кир. я = 0xFF

Ищём в этих ващих интернетах g: 0xС0 for cyrillic A. Подсказывает, что это может быть кодировка Windows-1251. Смотрим кодировку исходников — Windows-1251, надеюсь это она. Проверяем.

Открываем «Таблицу символов» (у меня Win7). Открываем какой-нибудь шрифт с Кириллицей. Внизу окна ставим галочку «дополнительные параметры», выбираем «Набор символов» — «Windows: Кириллица» (что соответствует кодировке Windows-1251). Теперь выбираем символы, для которых мы получили коды в отладчике, и смотрим их коды внизу окна. Совпадает! (Знаю, что я наркоман, можно было и в инете посмотреть таблицы, но вдруг такой способ кому-то когда-то сгодится.) Значит в память контроллера попадают строки в кодировке Windows-1251.

2) Теперь нужно как-то создать шрифты и запихать их в память контроллера.
Мне попалась под руку программа GLCD Font Creator.
Программа позволяет
— создавать свои растровые шрифты с нуля;
— растеризовать существующие в системе Windows шрифты и модифицировать полученный результат;
— создавать программное представление для хранения шрифтов в виде массивов.
По хорошему, эти ребята ориентируются на различные, в т.ч. знакосинтезирующие индикаторы, у них своя библиотека. Но я хочу их попользовать в своих грязных делишках, не используя их библиотеку, и вообще облегчить всё в пределах разумной экономии места в памяти и моего времени.
Открываем программу, выполняем File -> New Font -> Import An Existing System Font.

Выбор пал на шрифт Verdana минимального размера, понравился он мне.

Выбираем диапазон значений символов, которые хотим использовать (32-255). Нажимаем кнопочку возле «Remove», чтобы он самостоятельно не удалял пустые строки и столбцы, это мне не нужно.

Получили шрифт размером 12x13.

Если просмотреть все символы, видно, что отсутствует Кириллица и верхняя строка не используется в печатаемых символах, а 12 пикселей по высоте мне нравится больше, ибо 64/12 = 5 строк на дисплее. Удалим верхнюю строку, получим шрифт размером 12x12.

Потом нажимаем полшебную клавишу «Export ......» и получаем код на C:

Выделяем и копируем оттуда только то, что нужно, то есть печатаемые символы. Вот кусок:
nst unsigned short Verdana12x12[] = {
        0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Code for char  
        0x03, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Code for char !
        0x04, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Code for char "
        0x08, 0x00, 0x00, 0x80, 0x00, 0x90, 0x03, 0xF0, 0x00, 0x9C, 0x03, 0xF0, 0x00, 0x9C, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Code for char #
        0x06, 0x00, 0x00, 0x30, 0x02, 0x48, 0x02, 0xFE, 0x0F, 0x48, 0x02, 0x88, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Code for char $
        ......
        0x08, 0x00, 0x00, 0xC0, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00, 0x80, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Code for char ~

Ок, кусок с печатаемыми латинскими символами мы отхомячили. Теперь нужно найти, где же символы Кириллицы.
Как видим, в данном случае выбора кодировки нам не предоставили. Где же Кириллица?
Есть подозрение, что используется Юникодный набор для растеризации. Идём в таблицу символов, находим там Юникодную Кириллицу, точнее её коды.

0x0410 для буквы «А» и 0x044F для буквы «я», все буквы подряд. Создаём новый шрифт, с символами с 1040 (0x0410) по 1103 (0x044F).

Бинго! Получили нужные символы.

Преобразуем в код, получаем ещё один массив только для Кириллицы.

Теперь обратимся к обработке данных шрифтов на контроллере.

Для кодировки одного символа используется следующая строка:
0x03, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Code for char !

25 байт. Непорядок. Размер шрифта нам известен — 12x12, скорее всего 12 по щирине или высоте выровнено до 2х байт, а по высоте или ширине ровно 12, то есть должно быть 2байта X 12 = 24. Первые значения для всех символов ненулевые, однако первая строка или первый столбец часто пустые, значит это что? Видимо ширина символа. Проверяем догадку: 3 для !, 4 для ", 8 для #. Похоже это ширина символа от левого края, смотрим на битмапы, так оно и есть. Отлично! Шрифт не моноширный, мы можем писать так, что в ширину влезет как можно больше символов, и выглядеть это должно отлично.

Теперь про обработку всего этого хозяйства процедурно.
Латинские и специальные печатные символы (скобки, знаки препинания) начинаются от значения 0x20 (32 «для пробела») до 0x7E (126 «тильда»), можно конечно захватить ещё 0x7F («неразнывный пробел»), но оно нам надо? Мне нет — не буду включать сюда.
Таким образом таблица для латинских и специальных символов будет содержать коды от 0x20 до 0x7E.
const unsigned short const unsigned short Verdana12x12[] = {
        0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Code for char  
        0x03, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Code for char !
        0x04, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Code for char "
        0x08, 0x00, 0x00, 0x80, 0x00, 0x90, 0x03, 0xF0, 0x00, 0x9C, 0x03, 0xF0, 0x00, 0x9C, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Code for char #
        .............
        0x08, 0x00, 0x00, 0xC0, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00, 0x80, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Code for char ~
        };

Далее в нашей Windows-1251 до самой Кириллицы идут непечатаемые символы, а затем сама Кириллица от 0xC0 до 0xFF.
Под неё сделаем вторую таблицу. Снова отрезаем верхний ряд пикселей, у «И краткой» немного шапочку съели, не умрёт, и так нормально…
const unsigned short Verdana12x12rus[] = {
        0x07, 0x00, 0x00, 0x80, 0x03, 0xF0, 0x00, 0x8C, 0x00, 0x8C, 0x00, 0xF0, 0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Code for char 
        0x07, 0x00, 0x00, 0xFC, 0x03, 0x24, 0x02, 0x24, 0x02, 0x24, 0x02, 0x24, 0x02, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Code for char 
        .......
        0x06, 0x00, 0x00, 0x60, 0x02, 0x90, 0x01, 0x90, 0x00, 0x90, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00   // Code for char O
        };

В массиве комментарии смешные, конечно не соответствующие действительным символам.

Теперь сама процедура.
Символы закодированы таким образом:
— в первом байте лежит ширина символа, как мы ранее выяснили;
— в следующих байтах лежат вертикальные столбцы пикселей, младшими битами к верху. Высота дополнена до полного количества байт. То есть для символа высотой больше 8 пикселей (и меньше 17ти) для каждого вертикального столбца будет задействовано два байта;
— столбцы лежат друг за другом в порядке слева направо.
Подправили «unsigned short» на «uint8_t» для шрифтов, ну и пишем процедуру вывода строки… Можно было использовать нуль-терминированную строку, но я почему-то не стал.
extern const uint8_t Verdana12x12eng[];
extern const uint8_t Verdana12x12rus[];
const uint8_t* font12x12eng = &Verdana12x12eng[0];
const uint8_t* font12x12rus = &Verdana12x12rus[0];
void PutStringRus(uint8_t ix, uint8_t iy, char* iString, uint8_t iLength)
{
    ///Входные параметры ix, iy -- координаты верхнего левого угла, от которого начинать печатать
    ///iString -- указатель на содержание строки
    ///iLength -- длина строки с учётом "терминального ноля"
    iLength--;
    uint8_t xpos = ix;
    for(int sym = 0; sym<iLength; ++sym)
    {
        uint8_t sm = iString[sym];
        uint8_t snum = (sm<0xC0 ? sm-0x20 : sm-0xC0);///Так себе проверка
        const uint8_t* symbol = &(sm<0xC0?font12x12eng:font12x12rus)[(1+12*2)*(snum)];
        for(uint8_t x = 0; x<symbol[0]; ++x)
        {
            for(uint8_t y = 0; y<12; ++y)
            {
                if(symbol[1+y/8+x*2] & (0x01<<(y%8)))
                    PutPixel(xpos+x, iy+y);
                else
                    ErasePixel(xpos+x, iy+y);
            }
        }
        xpos += symbol[0];
    }
}

Процедуры PutPixel(a, b); и ErasePixel(a, b); для своих дисплеев прописывайте сами, ибо у всех по-разному. У меня так:
#define PutPixel(x, y) ( screen_field[(127-(x))*8+(63-(y))/8]|=(0x01<<((63-(y))%8)) )

#define ErasePixel(x, y) ( screen_field[(127-(x))*8+(63-(y))/8]&=(~(0x01<<((63-(y))%8))) )

В коде не хватает проверки на корректность ввода, т.е. при вводе чего-то непечатаемого, оно пройдёт с ошибкой, то есть неправильно, ибо захапает какую-то левую память и всё нафиг может рухнуть. Доработать код можете и сами, но можно и меня попросить :)
Ну вот и всё, выводим строки!
  • +7
  • 29 января 2016, 14:39
  • toxin65

Комментарии (23)

RSS свернуть / развернуть
Значит в память контроллера попадают строки в кодировке Windows-1251.
А не проще ли было просто посмотреть кодировку, которая используется Вашим редактором кода? :) Ведь если редактор отображает русские буквы, то значит используется кодировка с русским шрифтом. А компилятору уже всё равно, что отправлять в код, он просто скопирует коды из вашего исходника. :)
0
  • avatar
  • kvm
  • 29 января 2016, 18:26
Согласен, проще. Но нет гарантий, что компилятор не сыграет с вами злую шутку. Лучше убедиться.
0
Замечательно. Только 1251 — прошлый век. Она конечно проще и понятней, но UTF-8 спасет мир. KOI8-R и 866 уже отмерли, а 1251 живучая, собака.
-1
Прежде чем использовать utf8 нужно учесть, что работа с такими строками не проста, потребуется больше памяти как для хранения самих строк, так и для программы. При этом и скорость обработки таких строк снизится. Такое решение для МК будет неоправданно затратным. Если кроме кириллицы и латиницы никаких шрифтов в приложении не планируется, заморачиваться с utf не имеет смысла.
+2
Исходный код, я считаю, должен быть в UTF8, тогда любой другой разработчик, не зависимо от локализации, увидит нормальный текст, пусть на не понятном языке, но не кракозябры. Я как линуксойд не могу серьезно воспринимать 1251. У микроконтроллеров сейчас достаточно памяти и производительности. Когда мне нужна была 1251, я сделал конвертер UTF-8 в 1251. Keil MDK-ARM из коробки не умеет отображать 1251.

Заморачиваться с UTF-8 стоит, потому что рано или поздно придется освоить.
0
Сделал свои функции для UTF-8. Исходники программ в UTF-8. Для микроконтроллера, да.
Старые проблемы исчезли сами собой. Считаю оправданным.
0
TheDotFactory вот мировая программа www.pavius.net/the-dot-factory-an-lcd-font-and-image-generator/
+1
SG Bitmap Font Editor — ещё одна хорошая программа :) Давненько, когда я тоже столкнулся с подобными задачами, она мне больше понравилась чем эта GLCD Font Creator.
0
ХОСПАДЕ! качните монохромные шрифты .psf и всё!
лежат в
/usr/share/consolefonts/
качать отдельно
packages.debian.org/jessie/utils/console-data
и
packages.debian.org/jessie/console-setup-linux

вот исходник с устройством .psf:
code.metager.de/source/xref/busybox/console-tools/loadfont.c

Кстати, если STM32, то можно использовать g++-4.8, который умеет с++-11, в котором есть constexpr и пользовательские литералы, которыми можно сделать преобразование строк во время компиляции и не париться, как там это всё проходит через компилятор.
-1
Скорость и объём того, что предлагаете вы весьма спорны, имхо.
использовать g++-4.8, который умеет с++-11
Ох уж эти любители микроскопом гвозди забивать. С++ для голого контроллера неподходящий инструмент.
Я когда начинал писать на C после C++ моё нутро тоже сопротивлялось. Но на сегодняшний день — прошло.
0
хыхы. у меня нутро сопротивлялось, когда я начинал писать под контроллеры на С++. Но на сегодняшний день — прошло.
0
объём шрифтов спорный или объём чего?

если вы про constexpr функции преобразования кодировок, то они работают во время компиляции. в прошивке — сгенеренные ими массивы индексов в файле шрифта, например, или строки в выбранной вами кодировке.
0
Думаю объём данных будет больше. А под скоростью я имел в виду объём работы программиста, который точно больше.
0
ну, если считать относительно кода, в котором для вывода графики используются PutPixel(a, b); и ErasePixel(a, b); — то да, работы программиста будет точно больше.
0
Очень часто пользуюсь шрифтами которые шли с досовскими драйверами для текстовых режимов, они как раз под разные размеры знакомест есть, и представляют собой просто сплошной массив бит, без каких либо заголовков.
Но вот однажды понадобился шрифт с двумя битами на пиксель, например когда белый символ в черном обрамлении и с прозрачным фоном. Это нужно чтобы накладывать текст на произвольный фон и он на нём хорошо читался. Вот инструментов для такого найти не удалось.
0
  • avatar
  • mChel
  • 03 февраля 2016, 19:10
это делается ооочень просто. берётся ЛЮБОЙ монохромный шрифт, глиф рендерится в 8-битный буфер. В буфере делается блюр, потом по нему делается монохромная маска: где пиксель !=0 — там 0, иначе 1. При отрисовке на экран маской стирается фон, потом накладывается исходный глиф.
0
Это если вычислительной мощности в избытке, для мелких МК лучше подготовить всё заранее в наиболее удобном формате. Да и у меня изображение накладывалось аппаратно. В МК два буфера, по 1 биту на пиксель в каждом, первый буфер это чёрные пиксели, второй буфер это белые пиксели, определённая комбинация этих двух битов давала прозрачное состояние. Эти два буфера посредством DMA выплёвывались одновременно через два SPI, которые были настроены как однонаправленные слейвы и на клок которых подавалась пиксельная синхронизация извне.
0
Подобные фокусы при высоком разрешении хороши, при низком лучше вручную попиксельно шрифт рисовать (рисовать — в смысле глифы рисовать, а не на экран выводить).
0
одного не пойму — нафига это всё на мк городить.

и потом — 8битный буфер под один глиф, только при загрузке шрифта, да и это можно сделать на компе.
0
Я ведь специально уточнил, что говорю про рисование шрифта, а не отрисовку.
0
То же пришел к выводу, что самое простое вытащить шрифты из русификатора для DOS. И программку для этого дела по быстрому накидал, сохраняет в массив для С или Pascal.
0
Ох, если бы всё было так просто, как кажется…
+1
ну всмысле, не стирается — где в маске 1 — там ставится цвет обрамления, где 0 — просвечивает фон. Потом поверх рисуется глиф.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.