Использование библиотеки FreeType для растеризации символов

Всем поклонникам OpenSource приложений известна библиотека растеризации шрифтов FreeType. Ее используют практически все графические приложения под GNU/Linux. Попробуем и мы освоить этот несложный навык.

Сперва про используемые типы:

  • FT_Library — представляет собой структуру для иницизации библиотеки
  • FT_Face — шрифт, который загружается при помощи FT_Library
  • FT_Glyph — глиф шрифта, который создается при помощи FT_Face

Таким образом, для отображения символа нам надо создать объект типа FT_Library, затем загрузить шрифт в объект типа FT_Face, после этого рисовать символы при помощи FT_Glyph.

Символ из шрифта можно извлечь двумя способами:

  1. Получить информацию для его рисования кривыми
  2. Получить готовое изображение

Первый способ нам неинтересен, будем использовать сразу второй. Поехали (проверка ошибок убрана для упрощения кода):

#include <ft2build.h>
#include FT_FREETYPE_H
 
int main()
{
    // Библиотека FreeType
    FT_Library library = 0;
 
    // Инициализация библиотеки
    FT_Init_FreeType(&library);
 
    // Шрифт
    FT_Face face = 0;
 
    // Загрузка шрифта
    FT_New_Face(library, "tahoma.ttf", 0, &face);
 
    // Установка размера пикселя
    FT_Set_Pixel_Sizes(face, 24, 12);
 
    // Код символа (юникод)
    const FT_ULong charCode = L'W';
 
    // Загрузка глифа из шрифта с его отрисовкой
    FT_Load_Char(face, charCode, FT_LOAD_RENDER);
 
    // Получение готового к использованию глифа
    FT_GlyphSlot glyph = face->glyph;
 
    // Получение размеров глифа
    const int width = glyph->bitmap.width;
    const int height = glyph->bitmap.rows;
    const int pitch = glyph->bitmap.pitch;
 
    // Вывод символа в консоли
    for (int y = 0; y < height; ++y)
    {
        for (int x = 0; x < width; ++x)
        {
            // Получение прозрачности точки (x, y)
            const int a = glyph->bitmap.buffer[y * pitch + x];
 
            if (a > 127)
            {
                printf("*");
            }
            else
            {
                printf(" ");
            }
        }
        printf("\n");
    }
 
    // Удаление шрифта
    FT_Done_Face(face);
    face = 0;
 
    // Удаление библиотеки
    FT_Done_FreeType(library);
    library = 0;
}

Консоль:

**        ***      ***
 **      ****      **
 **      ****     ***
  **    **  **    **
  **    *   **   ***
   **  **    **  **
   **  *      * ***
    ****      ****
    ***        ***

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

  • положение горизонтальной базовой линии (например, «W» — внизу, «y» — примерно середина высоты)
  • межстрочный интервал (если вы хотите вывести многострочный текст)
  • кернинг (расстояние между различными буквами)

Подробнее об этом в следующей части.

13 комментариев
  1. написал(а) Алех (14 января 2018, 10:54)

    Вместо ‘W’ хочу букву ‘Я’!

    1. написал(а) eJ (26 февраля 2018, 11:10)

      Используйте юникод для этих целей. Извините за столь долгий ответ.

  2. написал(а) Артем (19 октября 2018, 02:30)

    Здравствуйте!
    Подскажите пожалуйста, как извлечь символ из шрифта первым способом.

  3. написал(а) Пользователь (15 февраля 2019, 23:55)

    Код компилируется, но готовая программа при запуске останавливается с ошибкой. Ошибка где-то в FT_Load_Char(face, charCode, FT_LOAD_RENDER);

    1. написал(а) eJ (18 февраля 2019, 12:54)

      Шрифт «tahoma.ttf» лежит рядом с исполняемым файлом?

  4. написал(а) Петр (16 апреля 2019, 15:55)

    Спасибо автору за статью.
    У меня возникла проблема: библиотеку freetype я собираю из исходников, используя makefile которые присутствуют в freetype.
    Но как мне использовать ее в моем проекте? При компиляции MinGW выдает ошибку:

    undefined reference to 'FT_Init_FreeType'
    undefined reference to 'FT_New_Face'
    undefined reference to 'FT_Set_Pixel_Sizes'
    undefined reference to 'FT_Load_Char'
    undefined reference to 'FT_Done_Face'
    undefined reference to 'FT_Done_FreeType'
    1. написал(а) eJ (16 апреля 2019, 16:10)

      После компиляции библиотеки у вас, скорее всего, получается файл libfreetype2.a (статическая линковка) или libfreetype2.so (динамическая). Вам необходимо при компиляции своего проекта указать этот файл для линковки. Смотрите флаги компилятора «-l» и/или «-L».

      1. написал(а) Петр (17 апреля 2019, 12:39)

        Да, после компиляции получается статическая библиотека freetype.a
        Вот мой Makefile. Разве из lib не должны подтянуться библиотеки?

        CC      := g++
        C_FLAGS := -std=c++17 -Wall -Wextra
         
        BIN     := bin
        SRC     := src
        INCLUDE := include
        LIB     := lib
         
        LIBRARIES   :=
         
        ifeq ($(OS),Windows_NT)
        EXECUTABLE  := main.exe
        else
        EXECUTABLE  := main
        endif
         
        all: $(BIN)/$(EXECUTABLE)
         
        clean:
            $(RM) $(BIN)/$(EXECUTABLE)
         
        run: all
            ./$(BIN)/$(EXECUTABLE)
         
        $(BIN)/$(EXECUTABLE): $(SRC)/*
            $(CC) $(C_FLAGS) -I$(INCLUDE) -L$(LIB) $^ -o $@ $(LIBRARIES)
        1. написал(а) eJ (17 апреля 2019, 13:01)

          Они бы с радостью подтянулись, но необходимо указать какие, компилятор не умеет угадывать )

          Попробуйте прописать libfreetype2.a в переменную LIBRARIES.

          1. написал(а) Петр (19 апреля 2019, 11:54)

            Спасибо за помощь.
            Все собралось, когда в переменную LIBRARIES я добавил lib/freetype.a

  5. написал(а) Петр (19 апреля 2019, 15:29)

    Уважаемый автор, имеется еще вопрос к вам.
    В данной статье вы получаете глиф растром (с заливкой) — FT_Load_Char(face, charCode, FT_LOAD_RENDER);
    Скажите, как можно получить координаты контрольных точек глифа, вписанного в область заданной высоты, чтобы ширина данной области изменялась автоматически, пропорционально заданной высоте?

    1. написал(а) eJ (23 апреля 2019, 17:13)

      Никогда такого не требовалось, так что ничем помочь не могу, увы…

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

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

Блог Евгения Жирнова