u8glib: шрифты и работа со строками текста Печать
Добавил(а) microsin   

В этой статье (перевод документации [1]) обсуждаются различные вопросы, касающиеся работы с текстом с помощью графической библиотеки u8glib.

[Что такое интервалы Ascent, Descent]

Для того, чтобы задавать и определять расстояния между строками текста, существуют такие понятия, как базовая линия, Ascent, Descent и Line Spacing Factor.

Базовая линия. Координата y, относительно которой рисуется шрифт. Условно базовую линию можно представить как строку в линованной тетради, на которой Вы пишете текст. Ниже на рисунке пунктиром показана базовая линия для текста (ABg), нарисованного шрифтом u8g_font_gdr25.

u8g.setFont(u8g_font_gdr25);
u8g.drawStr(5, 40, "(ABg)");

u8glib linespacing ex text

Ascent. Дословно это переводится как "подъем", "возвышение". В библиотеке u8glib это максимальная высота текста в пикселах выше базовой линии. На рисунке красным цветом показано значение параметра Ascent, для нашего примера оно равно 31. Значение Ascent для текущего шрифта можно получить вызовом функции getFontAscent().

u8glib accent text

Descent. Дословно переводится как "спуск". Некоторые символы, такие как g, y, могут быть нарисованы частично ниже базовой линии. В библиотеке u8glib термин Descent это максимальная высота текста в пикселах ниже базовой линии. На рисунке зеленым цветом показано значение параметра Descent, для нашего примера оно равно -9. Значение Descent для текущего шрифта можно получить вызовом функции getFontDescent().

u8glib descent text

Line Spacing Factor. Ascent, descent и расстояние между строками вычисляются специальным методом расчета высоты шрифта. Ниже приведены все эти 3 метода, где вычисление "extended text" применяется по умолчанию. The three methods are:

void u8glib::setFontRefHeightText(void)
void u8glib::setFontRefHeightExtendedText(void)
void u8glib::setFontRefHeightAll(void)

Все эти три метода будут рассмотрены в следующей секции статьи.

Расстояние между строками (функция getFontLineSpacing) вычисляется из значений ascent и descent:

line_spacing = ((ascent - descent)*factor) / 64

В этой формуле мы видим параметр factor, который и есть Line Spacing Factor. Этот параметр позволяет растягивать расстояние между строками. По умолчанию factor равен 64. Значение 128 для factor позволяет удваивать расстояние между строками.

factor 64 77 128
интервал между строками одинарный 1.2x двойной

Line Spacing Factor можно установить вызовом функции setFontLineSpacingFactor.

[Чему равна высота шрифта?]

Высота шрифта очевидно зависит от самого шрифта. Авторы библиотеки создали wiki-страничку [2], на которой дан обзор существующих шрифтов на базе их размеров. Было решено, что за основу размера будет взят размер буквы A.

Однако для обычных задач графической библиотеки высота заглавной буквы A часто бесполезна, потому что есть и другие символы, размер которых больше. Возможно, что было бы лучше использовать размер символа круглой скобки ( или другой большой символ в шрифте.

В настоящий момент u8glib предоставляет 3 основные высоты шрифта. Пользователь может выбрать ту высоту шрифта, которая ему подходит больше всего. Выбор устанавливается вызовами функций, перечисленных ниже.

setFontRefHeightText(). В этом способе выбора высоты в качестве базовой взята высота заглавной буквы A. Ascent шрифта равен высоте буквы A или цифры 1. Значение descent равен количеству пикселей, которое занимает хвостик маленькой g, опускающийся ниже базовой линии шрифта.

u8glib height text

setFontRefHeightExtendedText(). В качестве базовой выбрана высота символа (. Ascent равен количеству точек символа ( выше базовой линии. Значение descent равно спуску символа g или ( ниже базовой линии. Этот способ выбора высоты задан по умолчанию.

u8glib height ex text

setFontRefHeightAll(). Этот вариант выберет самый высокий ascent и самый нижний descent среди символов шрифта.

u8glib height all

[Как выровнять строку и выбрать её положение?]

Имеется четыре разные точки отсчета позиции для текста:

Слева вверху: setFontPosTop()
Центр: setFontPosCenter()
От базовой линии: setFontPosBaseline()
Снизу: setFontPosBottom()

В следующем примере кода в качестве опорной позиции положения шрифта будет выбран верхний (+1) левый угол текста.

u8g.setFont(u8g_font_gdr25);
u8g.setFontPosTop();
u8g.drawStr(5, 20, "(ABC)");

u8glib top height ex text

[Как узнать размер текста по ширине?]

Функция getStrWidth(const char *s) вычислит и возвратит ширину строки в точках по горизонтали. Возвращенное значение соответствует ширине в точках, которая должна быть зарезервирована для отображения строки. Она включает в себя некоторое пространство слева и справа от выводимого текста.

u8glib str width

[Как нарисовать текста в рамке?]

Для этого нужно определить размеры строки по вертикали и горизонтали:

h = u8g.getFontAscent()-u8g.getFontDescent();
w = u8g.getStrWidth("(AgI)");

Обратите внимание, что только ширина w зависит от содержимого строки. Высота h определяется текущими установками для расчета высоты (о чем говорилось ранее):

void u8glib::setFontRefHeightText(void)
void u8glib::setFontRefHeightExtendedText(void)
void u8glib::setFontRefHeightAll(void)

Ниже приведен код, который рисует текст вместе с рамкой вокруг текста:

u8g.setFontRefHeightExtendedText();
u8g.setFontPosTop();
...
h = u8g.getFontAscent()-u8g.getFontDescent(u8g);
w = u8g.getStrWidth("(AgI)");
u8g.drawStr(3, 10, "(AgI)");
u8g.drawFrame(3-1, 10, w+2, h+2);

u8glib str bbx top2

Графика всех шрифтов библиотеки сосредоточена в модуле u8g_font_data.cpp. В начале каждого массива шрифта приведен небольшой текстовый блок комментария, описыващий основные параметры шрифта. Кроме того, в названии шрифта кратко закодированы его параметры - цифры означают средний размер по x и y, буква B в названии шрифта означает Bold, O наклонный шрифт, r уменьшенный набор символов (reduced, 32..128), n означает, что в шрифте есть только цифры и некоторое небольшое число дополнительных символов (numeric, 42 - 57), без суффикса в шрифте имеется полный набор символов (32..255).

Массив шрифта имеет следующую неявную структуру:

[U8G_FONT_DATA_STRUCT_SIZE][растровая графика шрифта: символ, символ, символ]

U8G_FONT_DATA_STRUCT_SIZE это специальный блок данных из 17 байт. В этих 17 байтах закодирована информация, по которой шрифт рисуется процедурами обработки текста. Ниже приведено назначение каждого байта в этой структуре (цифрой помечено смещение каждого параметра в структуре).

0  формат шрифта. Формат шрифта может быть 0, 1 или 2. От этого зависит размер структуры определения картинки (Glyph) символа шрифта. Я просмотрел все шрифты в модуле u8g_font_data.cpp, и оказалось что примерно половина шрифтов имеет тип 0 и примерно половина тип 1, других типов нет.

1  FONTBUNDINGBOX width. Число без знака (unsigned). Определяет ширину прямоугольгой области, в которую может быть вписан символ шрифта.

2  FONTBOUNDINGBOX height. Число без знака (unsigned). Определяет высоту прямоугольгой области, в которую может быть вписан символ шрифта.

3  FONTBOUNDINGBOX x-offset. Число со знаком (signed).

4  FONTBOUNDINGBOX y-offset. Число со знаком (signed).

Пока не разобрался, что значат эти 2 параметра x-offset и y-offset. Судя по названию, возможно это некое смещение шрифта по горизонтали и вертикали.

5  высота заглавной A. Число без знака (unsigned). Определяет общий размер шрифта.

6  начало 'A'. В какой позиции знакогенератора по порядковому номеру находится английская буква A.

8  начало 'a'. В какой позиции знакогенератора по порядковому номеру находится английская буква a.

10 начало кодировки. Самый первый код символа, графика которого присутствует в этом шрифте (включительно).

11 конец кодировки. Самый последний код символа, графика которого присутствует в этом шрифте (включительно).

12 descent 'g'. Отрицательное значение: определяет занижение хвостика буквы g ниже базовой линии.

13 max ascent для шрифта. Максимальное возвышение шрифта.

14 min decent для шрифта. Отрицательное значение: максимальное занижение шрифта ниже базовой линии.

15 font xascent

16 font xdecent. Отрицательное значение: ниже базовой линии. Что такое xascent и xdecent, пока не разобрался.

Для доступа к параметрам структуры U8G_FONT_DATA_STRUCT_SIZE сделаны специальные функции-хеперы, которые по индексу извлекают из заголовка шрифта нужные параметры. Вот пример процедуры, которая использует параметры 10 и 11 для отрисовки всех символов шрифта (для извлечения параметров 10 и 11 используются хелперы u8g_GetFontLineSpacing и u8g_font_GetFontStartEncoding соответственно).

static void drawFont (const u8g_fntpgm_uint8_t* font)
{
   u8g_uint_t x, y, symwidth;
   uint8_t startcode, endcode, symcode;
   
   memset(Screen4bit, 0, sizeof(Screen4bit));
   u8g_SetFont(&u8gval, font);
   x = 0;
   y = u8g_GetFontLineSpacing(&u8gval);
   
   startcode = u8g_font_GetFontStartEncoding(font);
   endcode = u8g_font_GetFontEndEncoding(font);
   symcode = startcode;
   while(true)
   {
      symwidth = u8g_draw_glyph(&u8gval, x, y, symcode);
      if (x+symwidth >= (WIDTH-1))
      {
         y += u8g_GetFontLineSpacing(&u8gval);
         x = 0;
      }
      else
         x += symwidth;
      if (symcode==endcode)
         break;
      else
         symcode++;
   }
}

[Структура формата шрифта]

В настоящий момент в библиотеке u8glib реально имеется только 2 формата шрифтов 0 и 1. Формат 0 занимает в памяти 6 байт, а формат 1 только 3 байта. Достигается это тем, что в формате 1 данные, описывающие картинку шрифта, запакованы в старшей и младшей тетрадах байта. Если для обоих типов байт 0 формата равен 255, то это означает пустую картинку символа.

Информация о рисуемом символе кэшируется в структуре u8g_t (см. поля glyph_dx, glyph_x, glyph_y, glyph_width, glyph_height).

Что означает в названиях функций сокращение BBX? Например u8g_GetFontBBXWidth и т. п. BBX означает Bounded BoX, т. е. прямоугольная область экрана, в которую можно вписать некий объект.

[Вывод русского текста]

Шрифты: в оригинальной библиотеке u8glib почти все шрифты в кодировке ISO10646-1, и только один из них ISO8859-1. Ни один из этих шрифтов не позволяет не выводить русские буквы в кодировке ANSI (кодировка ANSI наиболее удобна, потому что текст программного кода, где есть русские буквы в тексте сообщений, почти во всех популярных IDE используют кодировку ANSI, т. е. Windows 1251).

Можно найти выход из положения, если найти шрифты для u8glib в кодировке ISO8859-5, где есть русские буквы. Для вывода русского текста в кодировке ANSI можно использовать шрифты ISO8859-5, если немного подправить код функции вывода символа u8g_draw_glyph:

//Эти шрифты раньше были ISO10646-1, поменял их на ISO8859-5 с русскими буквами:
static const u8g_fntpgm_uint8_t *ISO8859fonts[] =
{
   u8g_font_10x20,   //крупные буквы
   u8g_font_4x6,     //очень мелкие буквы
   u8g_font_5x8,     //мелкие буквы
   u8g_font_6x10,    //минимально разборчивый шрифт
   u8g_font_6x12,    //разборчивый шрифт
   u8g_font_8x13B,   //толстые буквы, шрифт крупнее
   u8g_font_8x13,    //шрифт крупнее
   u8g_font_8x13O,   //шрифт крупнее, наклонный
   u8g_font_9x15B,   //крупный шрифт, толстый
   u8g_font_9x15,    //крупный шрифт
   u8g_font_9x18B,   //самый крупный шрифт, толстый
   u8g_font_9x18,    //самый крупный шрифт
   NULL
};
 
bool isFontRussian (const u8g_fntpgm_uint8_t *font)
{
   uint8_t fontidx = 0;
   
   while(true)
   {
      if (NULL == ISO8859fonts[fontidx])
         break;
      else if (font == ISO8859fonts[fontidx])
         return true;
      fontidx++;
   }
   return false;
}
 
int8_t u8g_draw_glyph(u8g_t *u8g, u8g_uint_t x, u8g_uint_t y, uint8_t encoding)
{
  const u8g_pgm_uint8_t *data;
  uint8_t w, h;
  uint8_t i, j;
  u8g_uint_t ix, iy;
 
  if (isFontRussian(u8g->font) && (encoding >= (uint8_t)'А'))
  {
     encoding -= 16; //коррекция кода шрифта
  }
  
  {
    u8g_glyph_t g = u8g_GetGlyph(u8g, encoding);
    if ( g == NULL  )
      return 0;
    data = u8g_font_GetGlyphDataStart(u8g->font, g);
  }
  
  w = u8g->glyph_width;
  h = u8g->glyph_height;
  
  x += u8g->glyph_x;
  y -= u8g->glyph_y;
  y--;
 
  w += 7;
  w /= 8;
  
  iy = y;
  iy -= h;
  iy++;
  for( j = 0; j < h; j++ )
  {
    ix = x;
    for( i = 0; i < w; i++ )
    {
      u8g_Draw8Pixel(u8g, ix, iy, 0, u8g_pgm_read(data));
      data++;
      ix+=8;
    }
    iy++;
  }
  return u8g->glyph_dx;
}

Русскоязычные шрифты, исправленный код библиотеки u8glib и пример использования можно скачать в архиве [3], см. проект u8glib-test-fonts (проект VisualDSP для процессора Blackfin, но шрифты и код функций модуля u8g_font можно использовать и в проектах для других микроконтроллеров).

[Ссылки]

1. u8glib Font and String Handling site:code.google.com.
2. Fonts, sorted by capital A height site:code.google.com.
3150924u8glib-blackfin.zip.
4. Обзор шрифтов библиотеки u8glib.