Каждый раз, когда я вывожу текст на TFT-дисплей через TFT_eSPI, приходится смотреть на одни и те же встроенные шрифты: жирные, зубчатые и будто прямиком из 1980-х. И это особенно обидно, потому что библиотека уже много лет умеет отрисовывать красивые сглаженные шрифты (smooth fonts). Проблема никогда не была в самой отрисовке — она была в инструментах. Официальный способ создать гладкий шрифт предполагает установку Processing IDE, редактирование Java-скетча и повторный запуск для каждого шрифта и каждого размера. В итоге в большинстве проектов с этим просто не связываются.
Поэтому я сделал инструмент, который решает эту проблему.
fonts.atomic14.com — бесплатный браузерный инструмент, который преобразует любой из более чем 1900 шрифтов Google Fonts или ваш собственный TTF-файл в шрифты, готовые для использования с TFT_eSPI, M5Stack/M5GFX или LVGL. Там же можно увидеть попиксельно точный предпросмотр того, как шрифт будет выглядеть на вашем дисплее. Всё работает прямо в браузере: файлы шрифтов никуда не загружаются.

Дальше разберём, что на самом деле находится внутри этих файлов шрифтов и что стоит знать перед их использованием.
Что на самом деле представляет собой «гладкий шрифт»
Гладкие шрифты в TFT_eSPI — это файлы VLW, формат которых достался библиотеке от проекта Processing. VLW устроен до неприличия просто: небольшой заголовок с количеством глифов, размером, верхним и нижним выносами, таблица метрик каждого глифа, а затем необработанные 8-битные альфа-маски — по одному байту на пиксель. Никаких векторных контуров, хинтинга или кернинга. Шрифт заранее растеризуется на компьютере, а микроконтроллеру остаётся лишь копировать готовые пиксели на экран.
«Сглаживание» обеспечивает именно альфа-канал. В каждом пикселе хранится степень покрытия глифом — от 0 до 255. Во время отрисовки библиотека смешивает цвет текста с цветом фона в цветовом пространстве RGB565:
pixel = (fg × alpha + bg × (255 − alpha)) / 256
Именно поэтому для сглаженных шрифтов в tft.setTextColor(TFT_WHITE, TFT_BLACK) нужно указывать оба цвета: прозрачности здесь нет, есть только смешивание с заранее известным фоном. Если вывести текст поверх фотографии, по краям букв появятся цветные ореолы. Это не ошибка шрифта, а особенность формата.
PROGMEM или LittleFS?
Загрузить VLW на микроконтроллер можно двумя способами, и инструмент поддерживает оба:
Заголовочный файл .h, в котором шрифт хранится как массив байтов в PROGMEM. Это самый простой вариант: подключаете файл через #include, вызываете tft.loadFont(MyFont28); — и готово. Шрифт хранится во флеш-памяти вместе с кодом.
Файл .vlw в LittleFS или на SD-карте, который загружается вызовом tft.loadFont("MyFont28", LittleFS);. Этот вариант удобнее, если у вас несколько шрифтов или вы хотите менять их без перекомпиляции, но придётся повозиться с загрузкой файловой системы.
В любом случае не забудьте добавить #define SMOOTH_FONT в User_Setup.h. Его отсутствие — самая частая причина ошибок компиляции в духе «почему loadFont не существует».
Как уменьшить размер файлов
На один глиф приходится 28 байт метаданных плюс ширина × высота байт растрового изображения. При размере 28 пикселей полный набор Basic Latin занимает примерно 25–30 Кбайт, а шрифт только с цифрами для вывода показаний датчика может весить меньше 2 Кбайт. Каждый исключённый символ — это сэкономленная флеш-память. Инструмент в реальном времени показывает итоговый размер при изменении набора символов, а ненужные символы можно убирать из таблицы по одному. Если вам нужны только знак °C и десять цифр, оставьте только знак °C и десять цифр.
Особенности, которые лучше увидеть в предпросмотре
Есть два нюанса, которые удивляют почти всех. Я специально воспроизвёл их в предпросмотре: для отрисовки используется порт собственного кода TFT_eSPI, а не шрифтовый движок браузера.
Текст переносится у правого края. Если глиф не помещается, при отрисовке сглаженного шрифта библиотека переходит на следующую строку и начинает с x=0. По границе текст не обрезается.
Для пробелов используется не реальная ширина пробела из шрифта. Вместо неё библиотека вычисляет значение по формуле (ascent + descent) × 2/7 пикселя. Поэтому межсловный интервал на устройстве никогда полностью не совпадёт с тем, как тот же шрифт выглядит на компьютере. Зато предпросмотр совпадает с устройством — а это здесь главное.
Если какой-то символ на дисплее отображается пустым, почти всегда причина в том, что он не вошёл в выбранное подмножество. Классический пример — знак градуса: его нет в Basic Latin, поэтому показания температуры незаметно превращаются в 23.5C. Достаточно добавить его в поле дополнительных символов. Предпросмотр покажет пропуск ещё до того, как вы увидите его на дисплее.
Попробуйте без реального железа
Любой созданный шрифт можно проверить на эмулируемых ESP32 и дисплее ILI9341 в Wokwi. Одним кликом инструмент копирует готовый тестовый скетч со встроенным шрифтом и открывает заранее настроенный проект. Вставляете код, нажимаете Play — и видите, как шрифт отрисовывается настоящей библиотекой TFT_eSPI на эмулируемом дисплее.

В архив также входит готовый к прошивке тестовый проект для Arduino, если вы предпочитаете проверить шрифт на реальном устройстве.
И LVGL тоже
Тот же конвейер формирования подмножества шрифта и растеризации умеет экспортировать шрифты для LVGL: массивы на C, совместимые с результатом lv_font_conv, с глубиной 1, 2, 4 или 8 бит на пиксель. Если ваш проект использует LVGL или SquareLine Studio, скачивайте этот файл.
Что происходит под капотом
Для тех, кому интересны подробности: генератор VLW побайтово совместим с PFont.save из Processing — это проверено по эталонным файлам. Результат работы средства отрисовки сверяется с TFT_eSPI, запущенной в эмуляторе, а генератор LVGL откалиброван по реальному выводу lv_font_conv.
Всё работает на стороне клиента: opentype.js разбирает файл шрифта, Web Worker растеризует глифы в альфа-маски, а модули на TypeScript формируют выходные байты.
А если вы ещё выбираете дисплей для своего проекта, в базе плат с дисплеями для ESP32 собраны все популярные модели в одной таблице с фильтрами. Буду рад узнать, что у вас получилось.
Работа со шрифтами и дисплеями быстро выводит проект за пределы одной графической библиотеки: приходится разбираться с периферией ESP32, интерфейсами обмена и особенностями оцифровки сигналов. Продолжить погружение можно на бесплатных уроках (все стартуют в 20:00):
27 июля: «Разработка Embedded-устройств для IoT». Записаться
12 августа: «Тайный язык общения чипов. Подключаем всё к ESP32». Записаться
24 августа: «АЦП в ESP32. Оцифровать сигнал, а не „погоду на Марсе“: нюансы, особенности, повышение точности». Записаться
Пройдите вступительное тестирование по курсу «Embedded-разработчик», чтобы оценить свои знания и навыки.
n0isy
Я настрадался с преобразованием шрифтов в формат термо принтера и дело не в коде конвертера. Оказывается все либы по разному рендерят шрифт. К примеру у виндового GDI+ свои особенности. Но задача была чуть сложнее вашей, так как монохром.