Хабрахабр

[Перевод] Современный рендеринг текста в Linux: часть 1

Добро пожаловать в первую часть «Современного рендеринга текста в Linux». В каждой статье из этой серии мы разработаем самодостаточную программу на C для визуализации символа или последовательности символов. Каждая из этих программ будет реализовывать функцию, которую я считаю необходимой для современного рендеринга текста.

В первой части настроим FreeType и напишем простой рендерер символов в консоли.

А вот и код.
Вот что мы будем писать.

  • Моя операционная система: Ubuntu 18.04.2 LTS (bionic)
  • Компилятор C: clang version 6.0.0-1ubuntu2

Установка FreeType

На Ubuntu нужно установить FreeType и libpng.

$ sudo apt install libfreetype6 libfreetype6-dev
$ sudo apt install libpng16-16 libpng-dev

  • У меня FreeType версии 2.8.1-2ubuntu2, хотя на момент написания статьи последняя версия FreeType-2.10.1, она тоже подходит.
  • libpng версии (1.6.34-1ubuntu0.18.04.2)

Создаём файл C (main.c в моём случае)

#include <stdio.h> int main() { printf("Hello, world\n"); return 0;
}

$ clang -Wall -Werror -o main main.c
$ ./main
Hello, world

Подключаем библиотеки FreeType

Для поиска пути include (т. е. каталогов, которые компилятор проходит при поиске файлов в #include) для FreeType запускаем:

$ pkg-config --cflags freetype2
-I/usr/include/freetype2 -I/usr/include/libpng16

Строка -I/usr/include/freetype2 -I/usr/include/libpng16 содержит флаги компиляции, необходимые для подключения FreeType в программу C.

#include <stdio.h> #include <freetype2/ft2build.h>
#include FT_FREETYPE_H int main() { printf("Hello, world\n"); return 0;
}

$ clang -I/usr/include/freetype2 \ -I/usr/include/libpng16 \ -Wall -Werror \ -o main \ main.c
$ ./main
Hello, world

Печатаем версию FreeType

Внутри main() инициализируем FreeType с помощью FT_Init_FreeType(&ft) и проверяем наличие ошибок (функции FreeType возвращают 0 при успешном выполнении).

(С этого момента все функции, которые я буду использовать, взяты из справки по FreeType API).

FT_Library ft;
FT_Error err = FT_Init_FreeType(&ft);
if (err != 0) { printf("Failed to initialize FreeType\n"); exit(EXIT_FAILURE);
}

Затем с помощью FT_Library_Version получаем номер версии.

FT_Int major, minor, patch;
FT_Library_Version(ft, &major, &minor, &patch);
printf("FreeType's version is %d.%d.%d\n", major, minor, patch);

Если скомпилировать с помощью последней команды, то выскочит ошибка компоновщика:

/tmp/main-d41304.o: In function `main':
main.c:(.text+0x14): undefined reference to `FT_Init_FreeType'
main.c:(.text+0x54): undefined reference to `FT_Library_Version'
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Для исправление добавляем -lfreetype.

$ clang -I/usr/include/freetype2 \ -I/usr/include/libpng16 \ -Wall -Werror \ -o main \ -lfreetype \ main.c
$ ./main
FreeType's version is 2.8.1

Загрузка шрифта

Первый шаг для рендеринга символа — загрузка файла шрифта. Я использую ubuntu mono.

документацию FreeType. Чтобы понять точную разницу между конструкцией font face, семейством шрифтов (font family) и отдельными шрифтами, см.

Он создан, чтобы позволить создателям шрифтов вставлять несколько face в один размер шрифта. Третий аргумент называется face index. Поскольку у каждого шрифта есть по крайней мере один face, то значение 0 будет работать всегда, выбирая первый вариант.

FT_Face face;
err = FT_New_Face(ft, "./UbuntuMono.ttf", 0, &face);
if (err != 0) { printf("Failed to load face\n"); exit(EXIT_FAILURE);
}

Установка пиксельного размера для face

С помощью этой инструкции мы сообщаем FreeType желаемую ширину и высоту для отображаемых символов.

Это можно использовать для отображения символа, например, с шириной 10px и высотой 16px. Если для ширины передать нуль, FreeType интерпретирует это как «такая же, как другие», в данном случае 32px.

Эта операция может потерпеть неудачу на шрифте фиксированного размера, как в случае эмодзи.

err = FT_Set_Pixel_Sizes(face, 0, 32);
if (err != 0) { printf("Failed to set pixel size\n"); exit(EXIT_FAILURE);
}

Получение индекса для символа

Прежде всего, вернёмся к документации FreeType и установим соглашение об именах. Символ — это не то же самое, что глиф. Символ — это то, что указано в char, а глиф — это образ, который каким-то образом связан с этим символом. Это отношение довольно сложное, потому что char может соответствовать нескольким глифам: т. е. акцентам. А глиф может соответствовать многим символам: т. е. лигатурам, где -> представляется как одно изображение.

Как вы можете понять, это предусматривает сопоставление символов и глифов только один к одному. Для получения индекса глифа, соответствующего символу, мы используем FT_Get_Char_Index. В будущей статье из этой серии мы решим проблему с помощью библиотеки HarfBuzz.

FT_UInt glyph_index = FT_Get_Char_Index(face, 'a');

Загрузка глифа из face

Получив glyph_index, мы можем загрузить соответствующий глиф из нашего face.

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

FT_Int32 load_flags = FT_LOAD_DEFAULT;
err = FT_Load_Glyph(face, glyph_index, load_flags);
if (err != 0) { printf("Failed to load glyph\n"); exit(EXIT_FAILURE);
}

Отображение глифа в его контейнере (glyph slot)

Теперь мы можем, наконец, отобразить наш глиф в его контейнере (слоте), указанном в face->glyph.

Флаги рендеринга мы тоже обсудим в будущем, потому что они позволяют использовать LCD- (или cубпиксельный) рендеринг и сглаживание оттенков серого (grayscale antialiasing).

FT_Int32 render_flags = FT_RENDER_MODE_NORMAL;
err = FT_Render_Glyph(face->glyph, render_flags);
if (err != 0) { printf("Failed to render the glyph\n"); exit(EXIT_FAILURE);
}

Вывод символа в консоль

Растровое изображение отрисованного глифа можно получить из face->glyph->bitmap.buffer, где оно представлено в виде массива беззнаковых значений char, поэтому его значения находятся в диапазоне от 0 до 255.

Чтобы получить доступ к i-ой строки j-го столбца, рассчитываем column * row_width + row, как в bitmap.buffer[i * face->glyph->bitmap.pitch + j]. Буфер возвращается в виде одномерного массива, но представляет собой 2D-изображение.

Вы можете видеть, что при доступе к массиву мы использовали bitmap.width в цикле и bitmap.pitch, потому что длина каждой строки пикселей равна bitmap.width, но «ширина» буфера составляет bitmap.pitch.

В следующем коде перебираются все строки и столбцы, а в зависимости от яркости пикселя рисуются разные символы.

for (size_t i = 0; i < face->glyph->bitmap.rows; i++) else if (pixel_brightness > 84) { printf("."); } else { printf(" "); } } printf("\n");
}

Вывод консоли.

$ clang -I/usr/include/freetype2 \ -I/usr/include/libpng16 \ -Wall -Werror \ -o main \ -lfreetype \ main.c && ./main
FreeType's version is 2.8.1 .*****. .********. .********* . ***. *** *** .******** *********** .**. *** *** *** *** *** ***. *** .*********** *********** .*******..

→ Полный код можно посмотреть здесь
Мы создали базовый рендерер символов в консоли. Этот пример может (и будет) расширен для рендеринга символов в текстуру OpenGL для поддержки эмодзи, субпиксельной рендеринга, лигатур и многого другого. В следующей части поговорим о субпиксельном сглаживании LCD по сравнению с оттенками серого, их плюсах и минусах.

До скорой встречи.

Теги
Показать больше

Похожие статьи

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть