Настало время как я разобрался с freetype2. Теперь я хочу сделать так, чтобы мой код стал доступен нуждающимся. Потому как обдумывать, как работать с библиотекой, не всегда есть время. Я хочу показать код работы именно с freetype и немного с opengl. Немного о коде. Я не могу создавать сложный код. У меня все получается как-то по-простому. Я видел несколько фрагментов кода, работающего с freetype2, и никак не мог понять, как он на самом деле работает. Уж очень сложный код создавали авторы. Я надеюсь, что мой простой код вам понравиться. После прочтения этой статьи можно будет создать многострочный текст и отобразить одной текстурой на экран.
Итак, начнем.
Первое, что я хотел бы здесь написать, так это шейдер, который я выписал из книги. Он накладывает двухмерную текстуру на несколько треугольников.
За создание шейдеров у меня отвечает отдельный класс. Я пишу ему какой шейдер скомпилировать и он мне возвращает программу. Также он добавляет в std::map контейнер программу по названию, чтобы я мог в другом участке кода получить программу именно этого шейдера.
Далее я создал класс шрифта. Объект этого класса будет инициализировать текст, указывать позицию на экране и рисовать текстуру.
Ну вот, класс готов. Теперь приступим к реализации. Я делаю игру на android с sdl2 и тестирую на пк. Поэтому я знаю один единственный способ отобразить данные на экран, используя gles2 и opengl.
Итак начнем.
Из кода можно вызвать эту функцию вот так.

Итак, начнем.
Первое, что я хотел бы здесь написать, так это шейдер, который я выписал из книги. Он накладывает двухмерную текстуру на несколько треугольников.
За создание шейдеров у меня отвечает отдельный класс. Я пишу ему какой шейдер скомпилировать и он мне возвращает программу. Также он добавляет в std::map контейнер программу по названию, чтобы я мог в другом участке кода получить программу именно этого шейдера.
GLuint ShaderManager::createProgram ( const char *param ) { if ( !strncmp ( param, "sprite\0",7 ) ) { const char *vshader = "#version 300 es\n" "layout(location = 0) in vec2 position;\n" "layout(location = 1) in vec2 texCoord;\n" "uniform mat4 transform;\n" "out vec2 v_texCoord;\n" "void main ( )\n" "{\n" " gl_Position = transform * vec4 ( position, 0.0, 1.0 );\n" " v_texCoord = texCoord;\n" "}"; const char *fshader = "#version 300 es\n" "precision mediump float;\n" "in vec2 v_texCoord;\n" "layout(location = 0) out vec4 outColor;\n" "uniform sampler2D s_texture;\n" "void main ( )\n" "{\n" " outColor = texture ( s_texture, v_texCoord );\n" "}"; /* создать программу */ GLuint program = loadProgram ( vshader, fshader ); /* добавить программу в контейнер */ global.programs["sprite"] = program; return program;
Далее я создал класс шрифта. Объект этого класса будет инициализировать текст, указывать позицию на экране и рисовать текстуру.
#ifndef H_FONT_H #define H_FONT_H #include <stdint.h> #include <ft2build.h> #include <string> #include <vector> #include <SDL2/SDL_opengl.h> #include <SDL2/SDL_opengles2.h> #include "gl_mat.hpp" #include "global.hpp" #include <wchar.h> #include FT_FREETYPE_H #include FT_GLYPH_H class Font { public: Font ( ) { } /* инициализировать библиотеку freetype и загрузить ttf файл. */ Font ( const char *ttf_file ); /* задать позицию на экране */ void setPos ( int x, int y ); /* здесь происходит создание текстуры. Вот параметры *\1 сам текст в широких символах. *\2 размер шрифта. *\3 расстояние между шрифтами по горизонтали в пикселях. *\4 расстояние между шрифтами по вертикали в пикселях. *\5 размер пробела в пикселях. *\6 компонент цвет красный. *\7 компонент цвет зеленый. *\8 компонент цвет синий. * ну это значит что можно задать любой цвет тексту */ void init ( wchar_t *text, int fontSize, int align, int valign, int space, uint8_t r, uint8_t g, uint8_t b ); /* задать размер текстуры */ void setSize ( int w, int h ); /* рисовать текстуру */ void draw ( ); private: FT_Face face = 0; /* здесь текстурные координаты */ float *texture; /* здесь координаты вершин */ float *vertices; /* это размер текстуры : ширина */ int width; /* это размер текстуры : высота */ int height; /* это для шейдера надо */ int sampler; /* id текстуры */ GLuint textureid; /* координата x */ int x; /* координата y */ int y; /* это замена функции glOrtho */ float ortho[4][4]; /* это для перемещения на экране */ float translate[4][4]; /* здесь результат матрицы */ float result[4][4]; /* шейдерная программа */ unsigned int program; FT_Library ft_library; FT_Face ttf; }; #endif
Ну вот, класс готов. Теперь приступим к реализации. Я делаю игру на android с sdl2 и тестирую на пк. Поэтому я знаю один единственный способ отобразить данные на экран, используя gles2 и opengl.
Итак начнем.
#include "font.hpp" Font::Font ( const char *ttf_file ) { /* Начнем с создании объекта и подготовки данных * так как я делаю игру для android, я не использую cpp библиотеку glm. * Я создал альтернативу, сишную библиотеку и пока там только три или четыре функции * ну здесь идет очистка массивов в ноль */ glm::clearMatrix4x4 ( &ortho[0] ); glm::clearMatrix4x4 ( &translate[0] ); glm::clearMatrix4x4 ( &result[0] ); /* получаю из глобальной структуры шейдерную программу */ program = global.programs["sprite"]; /* также в глобальной структуре хранятся размеры экрана, их я тоже использую */ int width = global.width; int height = global.height; /* вот и пригодились размеры экрана, здесь я заполняю матрицу правильными значениями * для 2d рисунков */ glm::ortho ( &ortho[0], 0.0f, width, 0.0f, height, 0.0f, 1.0f ); /* устанавливаю позицию в ноль */ setPos ( 0, 0 ); /* инициализация библиотеки freetype2. */ FT_Init_FreeType( &ft_library ); /* здесь загружается файл шрифта */ #ifdef __ANDROID__ FT_NewFace ( ft_library, ttf_file, 0, &face ); #else char *path = (char *) new char[255]; sprintf ( path, "assets/%s", ttf_file ); FT_New_Face ( ft_library, path, 0, &face ); delete[] path; #endif } /* а вот здесь самое интересное */ void Font::init ( wchar_t *es, int fontSize, int align, int vert, int space, uint8_t r, uint8_t g, uint8_t b ) { /* задать размер пикселя в высоту */ FT_Set_Pixel_Sizes ( face, 0, fontSize ); FT_Glyph glyph; int w = 0; unsigned int h = 0; unsigned int maxh = 0; unsigned int toprow = 0; /* эта функция возвращает сколько символов в широкой строке, если например в строке * будут три буквы iаф, то функция вернет три символа. */ int len = wcslen ( es ); /* первое что я придумал это посчитать какую текстуру вообще надо создать, но для этого * мне пришлось создать каждый символ и узнать его ширину. Так я вижу полную картину. Знаю * какой массив создать */ for ( int i = 0; i < len; i++ ) { /* итак получаем символ */ wchar_t charcode = es[i]; /* далее идут стандартные операции для создания bitmap символа */ FT_Load_Char ( face, charcode, FT_LOAD_RENDER ); FT_UInt glyph_index = FT_Get_Char_Index ( face, charcode ) FT_Load_Glyph ( face, glyph_index, FT_LOAD_DEFAULT ); FT_Render_Glyph ( face->glyph, FT_RENDER_MODE_NORMAL ); FT_Get_Glyph ( face->glyph, &glyph ); FT_Glyph_To_Bitmap ( &glyph, FT_RENDER_MODE_NORMAL, 0, 1 ); FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph) glyph; FT_Bitmap bitmap = bitmap_glyph->bitmap; /* теперь надо узнать ширину символа */ w += bitmap.width; /* узнать разницу высоты шрифта и отступа от верха. */ int resize = bitmap.rows > bitmap_glyph->top ? bitmap.rows - bitmap_glyph->top : bitmap_glyph->top - bitmap.rows; /* теперь высота значиться как высота символа плюс отступ */ if ( h < bitmap.rows + resize ) h = bitmap.rows + resize; /* здесь надо знать самую большую высоту символа */ if ( toprow < bitmap.rows ) toprow = bitmap.rows; if ( maxh < bitmap.rows + bitmap_glyph->top ) maxh = bitmap.rows + bitmap_glyph->top; /* если символ равен пробелу, то увеличить w на столько пикселей, сколько задали при * инициализации */ if ( charcode == ' ' ) w += space; /* если встретился символ 'новая строка' * то увеличить высоту включив туда вертикальный отступ и максимальную высоту */ if ( charcode == '\n' ) { h += vert + maxh; FT_Done_Glyph ( glyph ); continue; } /* это расстояние между шрифтом, если align равен одному пикселю, то увеличиться на один */ w += align; FT_Done_Glyph ( glyph ); } /* теперь можно создать подготовительный двухмерный массив, * он включает размер всего текста в пикселях */ if ( h <= 0 ) h = maxh; uint8_t im[h][w]; /* заполню нулями массив */ memset ( &im[0][0], 0, w * h * sizeof ( uint8_t ) ); int ih = 0; int iw = 0; int posy = 0; int topy = 0; int maxwidth = 0; for ( int i = 0; i < len; i++ ) { wchar_t charcode = es[i]; FT_Load_Char ( face, charcode, FT_LOAD_RENDER ); FT_UInt glyph_index = FT_Get_Char_Index ( face, charcode ); FT_Load_Glyph ( face, glyph_index, FT_LOAD_DEFAULT ); FT_Render_Glyph ( face->glyph, FT_RENDER_MODE_NORMAL ); FT_Get_Glyph ( face->glyph, &glyph ); FT_Glyph_To_Bitmap ( &glyph, FT_RENDER_MODE_NORMAL, 0, 1 ); FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph) glyph; FT_Bitmap bitmap = bitmap_glyph->bitmap; /* получить отступ символа от верха */ posy = bitmap_glyph->top; /* это математика наверное, немогу объяснить как я тут высчитал */ posy = bitmap.rows - posy; topy = toprow - bitmap.rows; /* если новая строка, то ih - это высота от верха, то есть сверху это ноль, * ниже увеличивается */ if ( charcode == '\n' ) { ih += maxh; iw = 0; FT_Done_Glyph ( glyph ); continue; } for ( unsigned int y = 0, i = 0; y < bitmap.rows; y++ ) { for ( unsigned int x = 0; x < bitmap.width; x++, i++ ) { if ( ( ih + posy + y + topy ) > h ) { if ( posy < 0 ) posy = abs ( posy ); } /* здесь заполняется в нужное место один компонент цвета * пока массив из одного компонента gray, потом его перенесем в альфа канал */ im [ ih + posy + y + topy ] [ iw + x ] = bitmap.buffer[i]; } } /* увеличиваем ширину */ iw += bitmap.width; /* увеличиваем расстояние между символами */ iw += align; if ( maxwidth < iw ) maxwidth = iw; if ( charcode == ' ' ) { iw += space; } FT_Done_Glyph ( glyph ); } iw = maxwidth; width = iw; height = h; unsigned int size = width * height; /* а вот это уже будущая текстура */ uint8_t *image_data = new uint8_t [ size * 4 ]; /* заполняет белым цветом всю текстуру */ memset ( image_data, 255, size * 4 * sizeof ( uint8_t ) ); for ( unsigned int i = 0, y = 0; i < size; y++ ) { for ( int x = 0; x < width; x++, i++ ) { /* сюда помещаем из нашего массива значение в альфа канал */ image_data[ 4 * i + 3] = im [ y ][ x ]; /* сюда цвет текста */ image_data[ 4 * i + 0] = r; image_data[ 4 * i + 1] = g; image_data[ 4 * i + 2] = b; } } /* стандартные действия для заполнения текстуры */ glGenTextures ( 1, &textureid ); glBindTexture ( GL_TEXTURE_2D, textureid ); glTexImage2D ( GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_data ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); /* теперь нужно задать размер текстуры */ setSize ( width, height ); /* и удалить текстуру, она уже загружена в буфер и image_data больше не требуется. */ delete[] image_data; } void Font::setSize ( int w, int h ) { /* это я высчитал, где должны быть размеры ширины и высоты, чтобы отобразить треугольники правильно */ if ( vertices ) delete[] vertices; if ( texture ) delete[] texture; vertices = new float [ 12 ]; vertices[0] = 0; vertices[1] = 0; vertices[2] = 0; vertices[3] = h; vertices[4] = w; vertices[5] = 0; vertices[6] = w; vertices[7] = 0; vertices[8] = w; vertices[9] = h; vertices[10] = 0; vertices[11] = h; /* для текстуры надо задавать полный размер в единицу, так она будет полностью наложена на * треугольники */ texture = new float [ 12 ]; texture[0] = 0; texture[1] = 1; texture[2] = 0; texture[3] = 0; texture[4] = 1; texture[5] = 1; texture[6] = 1; texture[7] = 1; texture[8] = 1; texture[9] = 0; texture[10] = 0; texture[11] = 0; } void Font::setPos ( int x, int y ) { /* ну здесь задается позиция, где отобразить текст */ this->x = x; this->y = y; glm::translate ( &translate[0], x, y, 0 ); glm::sumMatrix ( &result[0], &translate[0], &ortho[0] ); } void Font::draw ( ) { /* стандартные действия для использования шейдера */ glUseProgram ( program ); sampler = glGetUniformLocation ( program, "s_texture" ); glActiveTexture ( GL_TEXTURE0 ); glBindTexture ( GL_TEXTURE_2D, textureid ); glUniform1i ( sampler, 0 ); GLint projection_location = glGetUniformLocation ( program, "transform" ); glUniformMatrix4fv ( projection_location, 1, GL_FALSE, &result[0][0] ); glEnableVertexAttribArray ( 0 ); glEnableVertexAttribArray ( 1 ); /* сюда заноситься координаты вершин */ glVertexAttribPointer ( 0, 2, GL_FLOAT, GL_FALSE, 0, vertices ); /* сюда заноситься координаты текстуры */ glVertexAttribPointer ( 1, 2, GL_FLOAT, GL_FALSE, 0, texture ); /* и рисуем текстуру */ glDrawArrays ( GL_TRIANGLES, 0, 12 ); glDisableVertexAttribArray ( 0 ); glDisableVertexAttribArray ( 1 ); }
Из кода можно вызвать эту функцию вот так.
Font *font = new Font ("anonymous.ttf"); wchar_t * text = L"привет habr. Я тут статью написал. Она о freetype и opengl.\n" "С помощью freetype можно выводить текст.\n" "А с помощью моего кода, можно вывести несколько строк в одной текстуре"; font->init ( text, 21, 1, 4, 4, 0, 0, 0 ); font->setPos ( 100, 100 );

