The second example is all I could code learning two FreeType2 tutorials. The result is not the best, though.
And code has at least three drawbacks:
1) hard-coded amount of characters per font size;
2) since the amount is hard-coded, memory is allocated for all 72 font sizes and (in my case) 1299 characters each. That only contains ints and short ints though, so memory usage is not really high;
3) the more longer strings you display, the lower your FPS is; this is the most awful thing, because looping through all the characters in a string and drawing its corresponding textures is really not the fastest way... but i don't know any other way... except for joining these character textures into single one string texture and then displaying that texture without looping. That would greatly increase FPS, but it doesn't suit for multi-line strings...
Makefile for Linux:
Code: Select all
FLAGS = `freetype-config --cflags` -I/usr/local/include/irrlicht
LIBS = `freetype-config --libs` -lIrrlicht
test: .objs/ttf.o .objs/main.o
g++ -o test .objs/ttf.o .objs/main.o $(LIBS)
.objs/ttf.o: ttf.*
g++ -o .objs/ttf.o -c ttf.cpp $(FLAGS)
.objs/main.o: main.cpp
g++ -o .objs/main.o -c main.cpp $(FLAGS)
clean:
rm -f test .objs/*.o
char_texs.h:
Code: Select all
#ifndef OSRPG_GAME_CHAR_TEXS_H
#define OSRPG_GAME_CHAR_TEXS_H
#include <irrlicht.h>
typedef struct {
irr::video::ITexture *tex;
irr::u16 char_index;
} CharTexs;
#endif // OSRPG_GAME_CHAR_TEXS_H
font_sizes.h:
Code: Select all
#ifndef OSRPG_GAME_FONT_SIZES_H
#define OSRPG_GAME_FONT_SIZES_H
#include "char_texs.h"
#include <vector>
typedef struct {
std::vector<CharTexs> char_texs;
irr::u8 font_size; // 1 - 72
} FontSizes;
#endif // OSRPG_GAME_FONT_SIZES_H
string_texs.h:
Code: Select all
#ifndef OSRPG_GAME_STRING_TEXS_H
#define OSRPG_GAME_STRING_TEXS_H
#include <irrlicht.h>
typedef struct {
irr::video::ITexture *tex;
irr::u16 x,
y,
w,
h;
} StringTexs;
#endif // OSRPG_GAME_STRING_TEXS_H
cooked_strings.h:
Code: Select all
#ifndef OSRPG_GAME_COOKED_STRINGS_H
#define OSRPG_GAME_COOKED_STRINGS_H
#include "string_texs.h"
#include <vector>
typedef struct {
std::vector<StringTexs> string_texs;
// x, y are not coords of drawing position...
irr::u16 x,
y,
size;
irr::video::SColor cl;
} CookedStrings;
#endif // OSRPG_GAME_COOKED_STRINGS_H
ttf.h:
Code: Select all
#ifndef OSRPG_GAME_TTF_H
#define OSRPG_GAME_TTF_H
#include <ft2build.h>
#include FT_FREETYPE_H
#include <freetype/ftglyph.h>
#include <string>
#include <vector>
#include "font_sizes.h"
#include "cooked_strings.h"
class TTF {
public:
TTF (irr::video::IVideoDriver *vd, const char *fn, irr::u16 xd,
irr::u16 yd);
~TTF ();
irr::u32 CacheString (irr::u8 size, irr::s16 x, irr::s16 y, std::wstring s,
irr::video::SColor cl = irr::video::SColor (255, 0, 0, 0));
void Clear ();
void DisplayCache (irr::u32 string_index, irr::s16 x, irr::s16 y);
void RemoveCache (irr::u32 string_index);
void SetColor (irr::u32 string_index, irr::video::SColor cl);
private:
irr::u16 NextP2 (irr::u16 a);
void SetSize (irr::u8 size);
irr::video::IVideoDriver *drv;
FT_Library lib;
FT_Face face;
FT_Bool use_kerning;
std::vector<FT_Vector> pss;
std::vector<FT_Glyph> ghs;
std::vector<FontSizes> fss;
std::vector<CookedStrings> css;
irr::u16 xdpi,
ydpi;
};
#endif // OSRPG_GAME_TTF_H
ttf.cpp:
Code: Select all
#include "ttf.h"
#include <iostream>
using namespace irr;
using namespace video;
using namespace std;
TTF::TTF (IVideoDriver *vd, const char *fn, u16 xd, u16 yd) {
xdpi = xd;
ydpi = yd;
drv = vd;
if (FT_Init_FreeType (&lib))
cout << "Can't init lib\n";
s8 error = FT_New_Face (lib, fn, 0, &face);
if (error)
cout << "Unsupported format\n";
else
if (error)
cout << "Can't init face\n";
use_kerning = FT_HAS_KERNING (face);
SetSize (16);
for (u16 i = 0; i < 72; i++) {
FontSizes fs;
// 1299 characters maximum, this includes all cyrillic symbols I may need :P
// I know this is no good to hard-code number of possible characters
// It'd be better to create vector with [index, character]
// May be I'll do it later...
for (u16 j = 0; j < 1300; j++) {
CharTexs ct;
ct.tex = 0;
ct.char_index = j;
fs.char_texs.push_back (ct);
}
fs.font_size = i;
fss.push_back (fs);
}
}
TTF::~TTF () {
Clear ();
FT_Done_Face (face);
FT_Done_FreeType (lib);
}
u32 TTF::CacheString (u8 size, s16 x, s16 y, wstring s, SColor cl) {
SetSize (size);
CookedStrings cooked;
pss.clear ();
ghs.clear ();
u16 len = s.length ();
u16 glyph_index = 0,
previous = 0,
pen_x = 0,
pen_y = 0;
for (u16 i = 0; i < len; i++) {
u16 idx = (u16)s[i];
glyph_index = FT_Get_Char_Index (face, idx);
if (use_kerning && previous && glyph_index) {
FT_Vector delta;
FT_Get_Kerning (face, previous, glyph_index,
FT_KERNING_DEFAULT, &delta);
pen_x += delta.x >> 6;
}
FT_Vector pos;
pos.x = pen_x;
pos.y = pen_y;
pss.push_back (pos);
if (FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT))
cout << "Can't load glyph\n";
FT_Glyph gh;
if (FT_Get_Glyph (face->glyph, &gh))
cout << "Can't get glyph\n";
ghs.push_back (gh);
pen_x += face->glyph->advance.x >> 6;
previous = glyph_index;
}
for (u16 i = 0; i < len; i++) {
FT_Glyph image;
FT_Vector pen;
image = ghs[i];
pen.x = x + pss[i].x * 64;
pen.y = y + pss[i].y;
if (!FT_Glyph_To_Bitmap (&image, FT_RENDER_MODE_NORMAL, &pen, 0)) {
FT_BitmapGlyph bit = (FT_BitmapGlyph)image;
FT_Bitmap &bmp = bit->bitmap;
u16 w = NextP2 (bmp.width);
u16 h = NextP2 (bmp.rows);
u16 idx = (u16)s[i];
//cout << "char_texs index: " << idx << endl;
if (fss[size - 1].char_texs[idx].tex == 0) {
u32 *data = new u32[w * h];
for (u16 j = 0; j < h; j++)
for (u16 i = 0; i < w; i++)
data[i + j * w] =
(i >= bmp.width || j >= bmp.rows) ? 0 :
(bmp.buffer[i + j * bmp.width] << 24) | 0x00ffffff;
IImage *img = drv->createImageFromData (ECF_A8R8G8B8,
core::dimension2d<s32> (w, h), data);
ITexture *tex = drv->addTexture ("data", img);
fss[size - 1].char_texs[idx].tex = tex;
img->drop ();
delete [] data;
}
StringTexs st;
st.tex = fss[size - 1].char_texs[idx].tex;
st.x = bit->left;
st.y = y - bit->top;
core::dimension2d<s32> dim = st.tex->getSize ();
st.w = dim.Width;
st.h = dim.Height;
cooked.string_texs.push_back (st);
FT_Done_Glyph (image);
}
else {
cout << "ERROR. Can't proccess: " << s[i] << endl;
}
}
cooked.x = x;
cooked.y = y;
cooked.cl = cl;
cooked.size = cooked.string_texs.size ();
css.push_back (cooked);
return css.size () - 1;
}
void TTF::Clear () {
for (u32 i = 0; i < css.size (); i++)
css.erase (css.begin () + i);
}
void TTF::DisplayCache (u32 string_index, s16 x, s16 y) {
CookedStrings *cooked = &css[string_index];
for (u16 i = 0; i < cooked->size; i++)
drv->draw2DImage (cooked->string_texs[i].tex,
core::position2d<s32> (
x + cooked->string_texs[i].x /*- cooked.x*/,
y + cooked->string_texs[i].y - cooked->y),
core::rect<s32> (0, 0, cooked->string_texs[i].w,
cooked->string_texs[i].h), 0, cooked->cl, true);
}
void TTF::RemoveCache (irr::u32 string_index) {
css.erase (css.begin () + string_index);
}
void TTF::SetColor (u32 string_index, SColor cl) {
CookedStrings *cooked = &css[string_index];
cooked->cl = cl;
}
u16 TTF::NextP2 (u16 a) {
u16 b = 1;
while (b < a)
b <<= 1;
return b;
}
void TTF::SetSize (u8 size) {
if (size > 72 && size < 1) {
cout << "Incorrect font size\n";
return;
}
if (FT_Set_Char_Size (face, 0, size * 64, xdpi, ydpi))
cout << "Can't set font size\n";
}
main.cpp:
Code: Select all
#include "ttf.h"
using namespace irr;
using namespace scene;
using namespace video;
#include <iostream>
using namespace std;
//#define SINGLE_CYCLE
int main () {
IrrlichtDevice *dev = createDevice (EDT_OPENGL, core::dimension2d<s32> (800, 600),
32, false, false, false, 0);
IVideoDriver *drv = dev->getVideoDriver ();
ISceneManager *smgr = dev->getSceneManager ();
//dev->setWindowCaption (L"Irrlicht 1.2 & FreeType2 2.2");
ICameraSceneNode *cam = smgr->addCameraSceneNode ();
cam->setInputReceiverEnabled (false);
TTF *ttf = new TTF (drv, "arial.ttf", 120, 120);
//string s = "John is hiding far from me AVI Micro$oft $uX OSRPG Rulez!";
wstring s = L"Лестер, будешь заниматься проектом OSRPG? I can see through you ;)";
//wstring s = L"Real short string";
// The X, Y here mean where char glyph will be created and taken at
// I.e., if it will be created outside ViewPort, char texture will be empty
// This is the second drawback of this TTF code
u32 john10 = ttf->CacheString (10, 10, 50, s);
u32 john14 = ttf->CacheString (14, 0, 50, s);
u32 john16 = ttf->CacheString (16, 0, 50, s);
u32 some10 = ttf->CacheString (10, 0, 50, L"KoЯn - Somebody, someone :D");
u32 irrft2 = ttf->CacheString (18, 0, 50, L"#Irrlicht & #FreeType2 Example$",
SColor (255, 50, 100, 0));
// Just to show the reuse of a handle
ttf->RemoveCache (irrft2);
irrft2 = ttf->CacheString (24, 0, 50, L"Irrlicht & FreeType2 eXample",
SColor (255, 84, 0, 0));
// A bunch of strings
wstring sarr [] = {
L"A, B, C, D, E, F, G.",
L"John is hiding far from me.",
L"Looking here, looking there",
L"I can't see him anywhere.",
};
u32 en_verses[4];
for (u8 i = 0; i < 4; i++)
en_verses[i] = ttf->CacheString (14, 50, 50, sarr[i],
SColor (255, 20, 50, 20));
int last_fps = -1;
#ifdef SINGLE_CYCLE
dev->run ();
#else
while (dev->run ()) {
#endif
drv->beginScene (true, true, SColor (255, 255, 255, 255));
smgr->drawAll ();
ttf->DisplayCache (john16, 10, 30);
ttf->DisplayCache (john14, 10, 70);
ttf->DisplayCache (john10, 10, 110);
ttf->DisplayCache (some10, 10, 150);
ttf->DisplayCache (irrft2, 10, 190);
for (u8 i = 0; i < 4; i++) ttf->DisplayCache (en_verses[i], 14, 220 + i * 20);
drv->endScene ();
int fps = drv->getFPS ();
if (last_fps != fps) {
core::stringw s = L"Irrlicht 1.2 & FreeType2 2.2 FPS: ";
s += fps;
dev->setWindowCaption (s.c_str ());
last_fps = fps;
}
#ifdef SINGLE_CYCLE
#else
}
#endif
dev->drop ();
// All string handles will be removed when you delete TTF instance
// Memory will be freed as well, of course. It should be so at least
delete ttf;
return 0;
}
Result should look like this