Заголовок на моём дашборде при setTextSize(3) выглядел бедно: буквы из клеточек, ступеньки по диагоналям, никакого намёка на гарнитуру. На форуме я нашёл скетч с ровным жирным заголовком и скопировал оттуда одну строку — setFont(&FreeSansBold12pt7b). Компилятор ответил, что FreeSansBold12pt7b не объявлен, и я пошёл в менеджер библиотек искать шрифт как библиотеку — естественно, безуспешно. Час я уверенно чинил несуществующую проблему: подозревал версию IDE, переустанавливал ядро, пока не открыл исходники Adafruit GFX. Папка Fonts стояла у меня на диске с самого первого дня, только я в неё не заглядывал.
Разгадка заняла минуту: Free-шрифты уже лежат внутри библиотеки, в папке Fonts, и подключаются обычным include заголовка. Ставить ничего не нужно — файл с шрифтом входит в состав Adafruit GFX, которую я ставил ещё в уроке про менеджер библиотек. Я просто не знал, что она у меня уже есть.
Разберём семью по полкам: как читать имена вида FreeSansBold12pt7b, сколько каждый шрифт весит во флеше, где у курсора базовая линия и как смешать Free-заголовок с базовой сеткой данных из урока про текст и setCursor. Всё это поверх модели буфера, разобранной в уроке про clearDisplay и display() — кадр и здесь собирается в ОЗУ и уходит одним display().
Семья FreeFonts: лица, стили, кегли
Free-семейство пришло в библиотеку из проекта GNU FreeFont. Лиц три: Serif с засечками, в духе Times New Roman; Sans без засечек, похожая на Helvetica; Mono — моноширинная, как Courier. У каждого лица стили: прямой, Bold, Oblique или Italic и комбинированный BoldOblique. Кеглей четыре: 9, 12, 18 и 24 пункта. Итого три лица на четыре стиля на четыре кегля — 48 заголовочных файлов в папке Fonts, от FreeMono9pt7b.h до FreeSerifBoldItalic24pt7b.h. Кегль здесь — размер гарнитуры в типографских пунктах, а не множитель setTextSize: девятый сопоставим с size(1) базового шрифта, а двадцать четвёртый на 128×64 занимает почти весь экран.
Имя читается по частям и само себе документация: FreeSansBold12pt7b — лицо Sans, стиль Bold, кегль 12 пунктов, а 7b означает 7-битный набор: символы с кодами от 32 до 126, от пробела до тильды. Латиница, цифры, знаки препинания — есть; кириллицы нет, как и в базовом шрифте. Свой шрифт, в том числе с кириллицей, делают утилитой fontconvert из TTF-файла — отдельная история, а на LCD обходной путь с кириллицей мы уже видели в уроке про знакогенератор.
Все 48 шрифтов — битмапы: таблицы байтов, где каждый бит — пиксель глифа, отрисованного заранее в своём кегле. Компилятор кладёт эти таблицы во флеш через PROGMEM, оперативную память они не трогают. Я посчитал по заголовкам библиотеки версии 1.x: FreeMono9pt7b — 844 байта битмапов плюс таблица глифов, итого около полутора килобайт; FreeSans9pt7b — около 1,8 КБ; FreeSansBold24pt7b — 8143 байта битмапов, почти десять килобайт с таблицей. У Uno 32 КБ флеша, из них скетчу доступно порядка тридцати: два-три шрифта по 9–12 пунктов проходят незаметно, а пара 24-х откусит треть. Однажды я уложил в скетч с Ethernet-ширдом три крупных шрифта: заливка влезла, но запас на рост кода кончился, и следующий датчик добавлять стало некуда.
Отсюда моё правило: во флеше держу только то, что реально вижу на экране прибора. Рабочий набор — FreeSansBold12pt7b для заголовка и FreeMono9pt7b для столбцов цифр; всё остальное рисует базовый шрифт 6×8, у которого флеш-цена ровно ноль.
| Шрифт | Характер | Флеш (по заголовкам GFX 1.x) | Где ставлю |
|---|---|---|---|
| базовый 6×8 | моноширинный, клетка 6×8, сетка 21×8 | 0 байт — встроен | данные, журнал, подписи |
| FreeMono9pt7b | моноширинный, гарнитура Courier | около 1,5 КБ | столбцы цифр, таймеры |
| FreeSans9pt7b | пропорциональный, без засечек | около 1,8 КБ | значения, строки статуса |
| FreeSansBold24pt7b | жирный, крупный | почти 10 КБ | одно число во весь экран |
Подключение двухшаговое. Сначала заголовок: include Fonts/FreeMono9pt7b.h после include Adafruit_GFX.h. Потом выбор: setFont(&FreeMono9pt7b) — и все print рисуют этим шрифтом. Возврат к базовому — setFont() без аргументов или setFont(NULL). setTextSize и setTextColor продолжают работать, но Free-кегли и так крупные, размер обычно оставляют единицей. Цвет печатает setTextColor: одиночный SSD1306_WHITE включает пиксели букв, а фоновый второй аргумент с Free-шрифтами не работает — об этом ниже. Отдельная приятность: глифы моноширинного FreeMono выровнены по одной оси, поэтому столбец цифр не пляшет при смене разрядов — то, чего пропорциональным лицам не дано.
Главная ловушка — курсор. У базового шрифта setCursor ставит левый верхний угол клетки; у Free-шрифтов та же координата y означает базовую линию — нижний край букв без хвостов вроде p и y. Мой заголовок из первого абзаца исчез не случайно: setCursor(0, 0) кладёт базовую линию на верхний край экрана, и глифы рисуются выше стекла. Рабочие значения y: не меньше 13 для 9-го кегля, 17 для 12-го и 30 для 24-го. При переключении шрифта библиотека сама сдвигает курсор на шесть пикселей, чтобы строка продолжилась с той же базовой линии — смешать два шрифта в одной строке можно без ручной арифметики.
Вторая особенность — пропорциональная ширина: клетка 6×8 и сетка 21×8 больше не работают, у каждой буквы своя ширина. Ширину строки меряют getTextBounds — он возвращает прямоугольник, который накроет текст; центрирование считается как x = (128 - w) / 2. Тот же прямоугольник служит и для стирания: фоновый цвет в setTextColor с Free-шрифтами игнорируется, старую надпись гасят fillRect по измеренным границам — ластик из урока про буфер.
#include <Wire.h>#include <Adafruit_GFX.h>#include <Adafruit_SSD1306.h>#include <Fonts/FreeSansBold12pt7b.h> Adafruit_SSD1306 display(128, 64, &Wire, -1); void setup() { display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.clearDisplay(); // Free-шрифт: курсор стоит на БАЗОВОЙ линии, y не меньше высоты кегля display.setFont(&FreeSansBold12pt7b); display.setTextSize(1); display.setCursor(4, 22); // 12pt: глифы поднимаются на ~17 px выше линии display.print("METEO"); // назад к базовому шрифту: сетка 21x8 снова работает display.setFont(); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 40); display.print("T: 21.4 C"); display.setCursor(0, 50); display.print("H: 58 %"); display.setCursor(0, 56); display.print("DHT22 aktualno 1 s"); display.display();} void loop() {}Смешивание шрифтов в одном кадре
Кадр с двумя шрифтами собирается тем же конвейером: clearDisplay, зоны по порядку, один display(). Между зонами — setFont: сначала Free-заголовок с курсором на базовой линии, потом setFont() и базовый шрифт для сетки данных. Слои в буфере накладываются в порядке вызовов, поэтому заголовок можно рисовать и после сетки — он просто ляжет поверх. Проверка на бумаге перед кодом не отменяется: заголовок в FreeSansBold12pt7b из пяти букв — это под 60 пикселей, на статус в шапке остаётся меньше половины строки, и знать это надо до заливки кадра, а не после.
Считать ширину Free-строки по шесть пикселей на символ нельзя: слово METEO в FreeSansBold12pt7b занимает около 60 пикселей, а не 30, как дала бы базовая клетка; точную цифру даёт getTextBounds. Взамен вы получаете настоящую гарнитуру: кегль отрисован из векторного оригинала в этот размер, формы букв ровные, без ступенек, которыми setTextSize(3) размножает пиксели базового шрифта. Разница на экране заметнее любой таблицы. Ступеньки всё же возможны: если растить Free-кегль через setTextSize(2), библиотека размножит пиксели уже настоящего глифа — так делают только когда кегля в библиотеке не нашлось.
Мнение с границей: два Free-шрифта на прибор — потолок. Третий и четвёртый вспоминают себя во флеше ровно тогда, когда скетч обрастёт датчиками и логами, и место кончается на самом неожиданном этапе. Если заливка вдруг перестала вставляться на плату — первым делом посчитайте килобайты шрифтов, а не обновляйте загрузчик.
Симулятор setFont не рисует: он показывает саму арифметику — ширины букв заданы числами, как у пропорциональной гарнитуры, и кадр центрируется формулой (128 - w) / 2. На плате ту же ширину вернёт getTextBounds.
- FreeFonts — 48 файлов: лица Serif, Sans, Mono × стили Bold, Oblique × кегли 9, 12, 18, 24 pt; имя файла читается по частям
- 7b — это ASCII с кодами 32–126: кириллицы нет ни в Free-шрифтах, ни в базовом; свой шрифт делают fontconvert
- Шрифт лежит во флеше, не в ОЗУ: 9pt — около 1,5–2 КБ, 24pt — до 10 КБ; у Uno скетчу доступно около 30 КБ
- Курсор у Free-шрифта — базовая линия: y меньше высоты кегля (13/17/30) прячет текст над экраном
- setFont() возвращает базовый шрифт; ширину Free-строки меряют getTextBounds, стирание — fillRect по границам
- Фоновый цвет setTextColor с Free-шрифтами игнорируется — следствие пропорциональных ширин
- 1. Где находятся данные Free-шрифта после компиляции?
- 2. Что означает 7b в имени FreeSans9pt7b?
- 3. Что означает координата y в setCursor при работе с Free-шрифтом?
- 4. Как вернуться к базовому шрифту 6×8 после Free-шрифта?
- 5. Зачем Free-строке getTextBounds перед перерисовкой числа?
На приборе теперь три слоя текста: Free-заголовок, который читается с двух метров, столбцы цифр FreeMono, где разряды ходят ровно, и базовая сетка для журнала. Прибор от этого выигрывает: заголовок различим с двух метров, данные не теряются в украшениях, и все слои по-прежнему едут в одном кадре из 1024 байт. Дальше по разделу — полный набор линий и фигур, которые лягут в тот же кадр рядом с любым шрифтом, и анимация движущегося объекта без мерцания на этом же бюджете кадра.
Домашнее задание: включите FreeSansBold12pt7b, поставьте заголовок на y = 22, замерьте getTextBounds ширину слова METEO и отцентрируйте его формулой x = (128 - w) / 2. Потом верните setFont() и убедитесь, что сетка 21×8 не поехала. Если заголовок пропал — вы снова поставили базовую линию на верхний край, и это самый дешёвый способ запомнить ловушку навсегда.
Частые вопросы по теме урока
- Где находятся данные Free-шрифта после компиляции?
- Таблицы глифов лежат во флеше через PROGMEM, как любая константа. Оперативную память они не расходуют — там только буфер кадра. Во флеше (PROGMEM), ОЗУ не тратят
- Что означает 7b в имени FreeSans9pt7b?
- 7b — семь бит кодировки: ASCII от пробела до тильды. Международных символов в штатных Free-шрифтах нет. 7-битный набор символов: коды 32–126, кириллицы нет
- Что означает координата y в setCursor при работе с Free-шрифтом?
- У Free-шрифтов курсор указывает на базовую линию. setCursor(0, 0) уводит глифы выше экрана — ставьте y не меньше высоты кегля. Левый верхний угол клетки, как у базового шрифта
- Как вернуться к базовому шрифту 6×8 после Free-шрифта?
- setFont() без аргументов возвращает встроенный шрифт. Размер и очистка на выбор шрифта не влияют. setFont() без аргументов или setFont(NULL)
- Зачем Free-строке getTextBounds перед перерисовкой числа?
- Пропорциональные буквы перекрываются при стирании фоном, поэтому старую надпись гасят fillRect по прямоугольнику из getTextBounds. Фоновый цвет с Free-шрифтами игнорируется — границы нужны для fillRect-ластика