diff --git a/B3View.pro b/B3View.pro index bce12dd..2d92882 100644 --- a/B3View.pro +++ b/B3View.pro @@ -7,13 +7,15 @@ SOURCES += main.cpp \ UserInterface.cpp \ Debug.cpp \ View.cpp \ - Utils.cpp + Utils.cpp \ + extlib/CGUITTFont.cpp HEADERS += Engine.h \ EventHandler.h \ UserInterface.h \ Debug.h \ View.h \ - Utils.h + Utils.h \ + extlib/CGUITTFont.h CONFIG += warn_off # Irrlicht @@ -24,3 +26,7 @@ LIBS += -L$$IRRLICHTBASE/source/Irrlicht \ -lX11 \ -lGL \ -lXxf86vm + +# Freetype +INCLUDEPATH += /usr/include/freetype2 +LIBS += -lfreetype diff --git a/Engine.cpp b/Engine.cpp index e185768..e0afddc 100644 --- a/Engine.cpp +++ b/Engine.cpp @@ -137,7 +137,11 @@ Engine::Engine() m_EventHandler->addEventReceiver( ERT_3DVIEW, m_View ); // Load font for displaying Axis names - m_AxisFont = m_Device->getGUIEnvironment()->getFont( "arial.xml" ); + m_AxisFontFace = new CGUITTFace(); + m_AxisFontFace->load( "arial.ttf" ); + m_AxisFont = new CGUITTFont( m_UserInterface->getGUIEnvironment() ); + m_AxisFont->attach( m_AxisFontFace, 14 ); + m_AxisFont->AntiAlias = false; // Set Engine enabled m_RunEngine = true; @@ -153,6 +157,8 @@ Engine::~Engine() { m_Device->drop(); delete m_WindowSize; + delete m_AxisFont; + delete m_AxisFontFace; } void Engine::loadMesh( const wstring &fileName ) diff --git a/Engine.h b/Engine.h index 2003f48..ff2b399 100644 --- a/Engine.h +++ b/Engine.h @@ -14,6 +14,8 @@ class View; #include "UserInterface.h" #include "View.h" +#include "extlib/CGUITTFont.h" + using std::cout; using std::endl; using std::wstring; @@ -42,7 +44,9 @@ private: IVideoDriver *m_Driver; ISceneManager *m_Scene; IAnimatedMeshSceneNode *m_LoadedMesh; - IGUIFont *m_AxisFont; + CGUITTFont *m_AxisFont; + CGUITTFace *m_AxisFontFace; + dimension2d *m_WindowSize; bool m_RunEngine; diff --git a/Makefile b/Makefile index 291d3b6..f3b1382 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ ############################################################################# # Makefile for building: build/B3View -# Generated by qmake (2.01a) (Qt 4.7.0) on: Thu Apr 22 14:20:08 2010 +# Generated by qmake (2.01a) (Qt 4.7.0) on: Fri Apr 23 09:51:28 2010 # Project: B3View.pro # Template: app # Command: /opt/qt47/bin/qmake -spec /opt/qt47/mkspecs/linux-g++ CONFIG+=debug -o Makefile B3View.pro @@ -13,10 +13,10 @@ CXX = g++ DEFINES = CFLAGS = -pipe -g -w $(DEFINES) CXXFLAGS = -pipe -g -w $(DEFINES) -INCPATH = -I/opt/qt47/mkspecs/linux-g++ -I. -I../irrlicht/trunk/include +INCPATH = -I/opt/qt47/mkspecs/linux-g++ -I. -I../irrlicht/trunk/include -I/usr/include/freetype2 LINK = g++ LFLAGS = -Wl,-rpath,/opt/qt47/lib -LIBS = $(SUBLIBS) -L/home/er/tmp/irrlicht/trunk/source/Irrlicht -lIrrlicht -lX11 -lGL -lXxf86vm +LIBS = $(SUBLIBS) -L/home/er/tmp/irrlicht/trunk/source/Irrlicht -lIrrlicht -lX11 -lGL -lXxf86vm -lfreetype AR = ar cqs RANLIB = QMAKE = /opt/qt47/bin/qmake @@ -49,14 +49,16 @@ SOURCES = main.cpp \ UserInterface.cpp \ Debug.cpp \ View.cpp \ - Utils.cpp + Utils.cpp \ + extlib/CGUITTFont.cpp OBJECTS = tmp/main.o \ tmp/Engine.o \ tmp/EventHandler.o \ tmp/UserInterface.o \ tmp/Debug.o \ tmp/View.o \ - tmp/Utils.o + tmp/Utils.o \ + tmp/CGUITTFont.o DIST = /opt/qt47/mkspecs/common/g++.conf \ /opt/qt47/mkspecs/common/unix.conf \ /opt/qt47/mkspecs/common/linux.conf \ @@ -221,6 +223,9 @@ tmp/Utils.o: Utils.cpp Utils.h \ Debug.h $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/Utils.o Utils.cpp +tmp/CGUITTFont.o: extlib/CGUITTFont.cpp extlib/CGUITTFont.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/CGUITTFont.o extlib/CGUITTFont.cpp + ####### Install install: FORCE diff --git a/UserInterface.cpp b/UserInterface.cpp index 80b5f9f..5f00962 100644 --- a/UserInterface.cpp +++ b/UserInterface.cpp @@ -12,8 +12,16 @@ void UserInterface::setupUserInterface() fileMenu->addItem( L"Load", UIC_FILE_LOAD ); fileMenu->addItem( L"Quit", UIC_FILE_QUIT ); + // Playback Control Window + IGUIWindow *playbackWindow = m_Gui->addWindow( rect( vector2d( 20, 60 ), dimension2d( 160, 300 )), false, L"Playback", 0, UIE_PLAYBACKWINDOW ); + playbackWindow->getCloseButton()->setVisible( false ); + // Set Font for UI Elements - m_GuiFont = m_Gui->getFont( "arial.xml" ); + m_GuiFontFace = new CGUITTFace(); + m_GuiFontFace->load( "arial.ttf" ); + m_GuiFont = new CGUITTFont( m_Gui ); + m_GuiFont->attach( m_GuiFontFace, 14 ); + m_Gui->getSkin()->setFont( m_GuiFont ); } @@ -50,6 +58,7 @@ UserInterface::UserInterface( Engine *engine ) UserInterface::~UserInterface() { delete m_GuiFont; + delete m_GuiFontFace; } IGUIEnvironment * UserInterface::getGUIEnvironment() const diff --git a/UserInterface.h b/UserInterface.h index 17f05dc..0b4fc42 100644 --- a/UserInterface.h +++ b/UserInterface.h @@ -11,6 +11,8 @@ class Engine; #include "Debug.h" #include "Engine.h" +#include "extlib/CGUITTFont.h" + using namespace irr; using namespace irr::core; using namespace irr::gui; @@ -20,7 +22,7 @@ using std::wstring; enum UserInterfaceElements { - UIE_MAINWINDOW = 1000, + UIE_PLAYBACKWINDOW = 1000, UIE_LOADBUTTON = 1001, UIE_LOADFILEDIALOG = 1002, UIE_FILEMENU = 1003, @@ -37,7 +39,8 @@ class UserInterface : public IEventReceiver private: Engine *m_Engine; IGUIEnvironment *m_Gui; - IGUIFont *m_GuiFont; + CGUITTFont *m_GuiFont; + CGUITTFace *m_GuiFontFace; void setupUserInterface(); void displayLoadFileDialog(); diff --git a/build/arial.bmp b/build/arial.bmp deleted file mode 100644 index 1c7f463..0000000 Binary files a/build/arial.bmp and /dev/null differ diff --git a/build/arial.ttf b/build/arial.ttf new file mode 100644 index 0000000..ff0815c Binary files /dev/null and b/build/arial.ttf differ diff --git a/build/arial.xml b/build/arial.xml deleted file mode 100644 index 29a9220..0000000 Binary files a/build/arial.xml and /dev/null differ diff --git a/build/nskinbl.jpg b/build/nskinbl.jpg new file mode 100644 index 0000000..b701adb Binary files /dev/null and b/build/nskinbl.jpg differ diff --git a/build/nskinrd.jpg b/build/nskinrd.jpg new file mode 100644 index 0000000..458dbfc Binary files /dev/null and b/build/nskinrd.jpg differ diff --git a/extlib/CGUITTFont.cpp b/extlib/CGUITTFont.cpp new file mode 100644 index 0000000..70c7fba --- /dev/null +++ b/extlib/CGUITTFont.cpp @@ -0,0 +1,628 @@ +// Kudus to Nalin for this wonderful piece of code! +// See: http://irrlicht.sourceforge.net/phpBB2/viewtopic.php?t=3995 +#include +using namespace irr; +#include "CGUITTFont.h" + +namespace irr +{ +namespace gui +{ + +FT_Library library; +bool CGUITTFace::libraryLoaded = false; +s32 facesCount = 0; + +////////////////////// + +CGUITTFace::CGUITTFace() +: faceLoaded(false), font_buffer(0), font_size(0) +{ +} + +CGUITTFace::~CGUITTFace() +{ + if (faceLoaded) + { + FT_Done_Face(face); + if (font_buffer) + { + delete[] font_buffer; + font_buffer = 0; + } + if (--facesCount == 0) + { + FT_Done_FreeType(library); + CGUITTFace::libraryLoaded = false; + } + } +} + +bool CGUITTFace::load(const io::path& filename, io::IFileSystem* filesystem) +{ + if (!libraryLoaded) + { + if (FT_Init_FreeType(&library)) + return false; + CGUITTFace::libraryLoaded = true; + } + + if (filesystem) + { + // Read in the file data. + io::IReadFile* file = filesystem->createAndOpenFile(filename); + font_buffer = new FT_Byte[file->getSize()]; + file->read(font_buffer, file->getSize()); + font_size = file->getSize(); + file->drop(); + + // Create the face. + if (FT_New_Memory_Face(library, font_buffer, font_size, 0, &face)) + { + delete[] font_buffer; + font_buffer = 0; + return false; + } + } + else + { + if (FT_New_Face(library, core::stringc(filename).c_str(), 0, &face)) + return false; + } + + ++facesCount; + faceLoaded = true; + return true; +} + +////////////////////// + +CGUITTGlyph::CGUITTGlyph() +: cached(false), face(0), image(0), texture(0), texture_mono(0), +size(0), size_is_pixels(false), hasDefault(false), hasMonochrome(false) +{ +} + +CGUITTGlyph::~CGUITTGlyph() +{ + if (image) delete[] image; + if (texture) Driver->removeTexture(texture); + if (texture_mono) Driver->removeTexture(texture_mono); +} + +void CGUITTGlyph::cache(u32 idx, bool fontHinting, bool autoHinting) +{ + // Set the size of the glyph. + if (size_is_pixels) + FT_Set_Pixel_Sizes(*face, 0, size); + else + FT_Set_Char_Size(*face, size * 64, size * 64, 0, 0); + + FT_Int32 loadFlags = FT_LOAD_DEFAULT; + if (!fontHinting) loadFlags |= FT_LOAD_NO_HINTING; + if (!autoHinting) loadFlags |= FT_LOAD_NO_AUTOHINT; + if (!FT_Load_Glyph(*face, idx, loadFlags)) + { + hasDefault = true; + FT_GlyphSlot glyph = (*face)->glyph; + FT_Bitmap bits; + FT_Error err = 0; + if (glyph->format != FT_GLYPH_FORMAT_BITMAP) + err = FT_Render_Glyph(glyph, FT_RENDER_MODE_NORMAL); + + if (!err) + { + // Generate the image. + bits = glyph->bitmap; + u8 *pt = bits.buffer; + image = new u8[bits.width * bits.rows]; + memcpy(image, pt, bits.width * bits.rows); + + // Bitmap information. + bitmap.top = glyph->bitmap_top; + bitmap.left = glyph->bitmap_left; + bitmap.width = bits.width; + bitmap.height = bits.rows; + + // Initialize the texture information. + texture_size.set(1, 1); + + // Determine what our texture size should be. + // Keep multiplying by 2 until we get it. + while (texture_size.Width <= bitmap.width) + texture_size.Width <<= 1; + while (texture_size.Height <= bitmap.height) + texture_size.Height <<= 1; + + // Square our texture. + if (texture_size.Height < texture_size.Width) + texture_size.Height = texture_size.Width; + else texture_size.Width = texture_size.Height; + + // Create our texture data. + u32 *texture_data = new u32[texture_size.Width * texture_size.Height]; + memset(texture_data, 0, texture_size.Width * texture_size.Height * sizeof(u32)); + u32 *texp = texture_data; + bool cflag = (Driver->getDriverType() == video::EDT_DIRECT3D8); + for (int i = 0; i < bits.rows; i++) + { + u32 *rowp = texp; + for (int j = 0; j < bits.width; j++) + { + if (*pt) + { + if (cflag) + { + *rowp = *pt; + *rowp *= 0x01010101; + } + else + { + *rowp = *pt << 24; + *rowp |= 0x00ffffff; + } + } + else *rowp = 0; + pt++; + rowp++; + } + texp += texture_size.Width; + } + + // Name our texture. + c8 name[128]; + sprintf(name, "TTFontGlyph%d", idx); + + // Create our texture. + video::IImage *img = Driver->createImageFromData(video::ECF_A8R8G8B8, core::dimension2d(texture_size.Width, texture_size.Height), texture_data); + bool flg16 = Driver->getTextureCreationFlag(video::ETCF_ALWAYS_16_BIT); + bool flg32 = Driver->getTextureCreationFlag(video::ETCF_ALWAYS_32_BIT); + bool flgmip = Driver->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS); + Driver->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT, false); + Driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true); + Driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false); + if (texture) Driver->removeTexture(texture); + texture = Driver->addTexture(name, img); + img->drop(); + Driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, flgmip); + Driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, flg32); + Driver->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT, flg16); + delete[] texture_data; + + // Set our glyph as "cached". + cached = true; + } + } + + loadFlags = FT_LOAD_RENDER | FT_LOAD_MONOCHROME; + if (!fontHinting) loadFlags |= FT_LOAD_NO_HINTING; + if (!autoHinting) loadFlags |= FT_LOAD_NO_AUTOHINT; + if (fontHinting || autoHinting) loadFlags |= FT_LOAD_TARGET_MONO; + if (!FT_Load_Glyph(*face, idx, loadFlags)) + { + hasMonochrome = true; + FT_GlyphSlot glyph = (*face)->glyph; + FT_Bitmap bits = glyph->bitmap; + u8 *pt = bits.buffer; + + // Bitmap information. + bitmap_mono.top = glyph->bitmap_top; + bitmap_mono.left = glyph->bitmap_left; + bitmap_mono.width = bits.width; + bitmap_mono.height = bits.rows; + + // Initialize the texture information. + texture_mono_size.set(1, 1); + + // Determine what our texture size should be. + // Keep multiplying by 2 until we get it. + while (texture_mono_size.Width <= bitmap_mono.width) + texture_mono_size.Width <<= 1; + while (texture_mono_size.Height <= bitmap_mono.height) + texture_mono_size.Height <<= 1; + + // Square our texture. + if (texture_mono_size.Height < texture_mono_size.Width) + texture_mono_size.Height = texture_mono_size.Width; + else texture_mono_size.Width = texture_mono_size.Height; + + // Create our texture data. + u16 *texture_mono_data = new u16[texture_mono_size.Width * texture_mono_size.Height]; + memset(texture_mono_data, 0, texture_mono_size.Width * texture_mono_size.Height * sizeof(u16)); + u16 *texpm = texture_mono_data; + for (int y = 0; y < bits.rows; y++) + { + u16 *rowp = texpm; + for (int x = 0; x < bits.width; x++) + { + if (pt[y * bits.pitch + (x / 8)] & (0x80 >> (x % 8))) + *rowp = 0xffff; + rowp++; + } + texpm += texture_mono_size.Width; + } + + // Name our texture. + c8 name[128]; + sprintf(name, "TTFontGlyph%d_mono", idx); + + // Create our texture. + video::IImage *img = Driver->createImageFromData(video::ECF_A1R5G5B5, core::dimension2d(texture_mono_size.Width, texture_mono_size.Height), texture_mono_data); + bool flg16 = Driver->getTextureCreationFlag(video::ETCF_ALWAYS_16_BIT); + bool flg32 = Driver->getTextureCreationFlag(video::ETCF_ALWAYS_32_BIT); + bool flgmip = Driver->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS); + Driver->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT,true); + Driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT,false); + Driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS,false); + if (texture_mono) Driver->removeTexture(texture_mono); + texture_mono = Driver->addTexture(name, img); + img->drop(); + Driver->makeColorKeyTexture(texture_mono, video::SColor(0,0,0,0)); + Driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS,flgmip); + Driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT,flg32); + Driver->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT,flg16); + delete[] texture_mono_data; + } +} + +////////////////////// + +//! constructor +CGUITTFont::CGUITTFont(IGUIEnvironment *env) +: AntiAlias(true), Transparency(true), FontHinting(true), AutoHinting(true), +Environment(env), Driver(0), tt_face(0), GlobalKerningWidth(0), GlobalKerningHeight(0) +{ + #ifdef _DEBUG + setDebugName("CGUITTFont"); + #endif + + if (Environment) + { + // don't grab environment, to avoid circular references + Driver = Environment->getVideoDriver(); + } + + if (Driver) + Driver->grab(); + + setInvisibleCharacters ( L" " ); + + // Glyphs isn't reference counted, so don't try to delete when we free the array. + Glyphs.set_free_when_destroyed(false); +} + +//! destructor +CGUITTFont::~CGUITTFont() +{ + Glyphs.clear(); + if (Driver) + Driver->drop(); +} + +bool CGUITTFont::attach(CGUITTFace *face, u32 size, bool size_is_pixels) +{ + if (!Driver) + return false; + + tt_face = face; + + Glyphs.reallocate(tt_face->face->num_glyphs); + Glyphs.set_used(tt_face->face->num_glyphs); + for (int i = 0; i < tt_face->face->num_glyphs; i++) + { + Glyphs[i].Driver = Driver; + Glyphs[i].size = size; + Glyphs[i].size_is_pixels = size_is_pixels; + Glyphs[i].face = &(tt_face->face); + Glyphs[i].cached = false; + } + return true; +} + +void CGUITTFont::draw(const core::stringw& text, const core::rect& position, video::SColor color, bool hcenter, bool vcenter, const core::rect* clip) +{ + if (!Driver) + return; + + core::dimension2d textDimension; + core::position2d offset = position.UpperLeftCorner; + video::SColor colors[4]; + for (int i = 0; i < 4; i++) + colors[i] = color; + + if (hcenter || vcenter) + { + textDimension = getDimension(text.c_str()); + + if (hcenter) + offset.X = ((position.getWidth() - textDimension.Width) >> 1) + offset.X; + + if (vcenter) + offset.Y = ((position.getHeight() - textDimension.Height) >> 1) + offset.Y; + } + + u32 n; + + wchar_t previousChar = 0; + const wchar_t* ptext = text.c_str(); + while (*ptext) + { + wchar_t currentChar = *ptext; + n = getGlyphByChar(currentChar); + bool visible = (Invisible.findFirst(currentChar) == -1); + if (n > 0 && visible) + { + bool lineBreak=false; + if (currentChar == L'\r') // Mac or Windows breaks + { + lineBreak = true; + if (*(ptext + 1) == L'\n') // Windows breaks + currentChar = *(++ptext); + } + else if (currentChar == L'\n') // Unix breaks + { + lineBreak = true; + } + + if (lineBreak) + { + offset.Y += Glyphs[0].size; + offset.X = position.UpperLeftCorner.X; + + if (hcenter) + { + core::dimension2d lineDim = getDimension(text.c_str()); + offset.X += (position.getWidth() - lineDim.Width) >> 1; + } + ++ptext; + continue; + } + + // Only use the 256-color font if we are anti-aliasing, or if there was no monochrome font. + if (AntiAlias || !Glyphs[n-1].hasMonochrome) + { + u32 texw = Glyphs[n-1].texture_size.Width; + u32 texh = Glyphs[n-1].texture_size.Height; + u32 bmpw = Glyphs[n-1].bitmap.width; + u32 bmph = Glyphs[n-1].bitmap.height; + s32 offx = Glyphs[n-1].bitmap.left; + s32 offy = Glyphs[n-1].size - Glyphs[n-1].bitmap.top; + + // Apply kerning. + core::vector2di k = getKerning(currentChar, previousChar); + offset.X += k.X; + offset.Y += k.Y; + + if (Driver->getDriverType() != video::EDT_SOFTWARE) + { + if (!Transparency) color.color |= 0xff000000; + Driver->draw2DImage(Glyphs[n-1].texture, core::position2d(offset.X + offx, offset.Y + offy), core::rect(0, 0, texw-1, texh-1), clip, color, true); + } + else + { + // Software driver doesn't do transparency correctly, so if we are using the driver, + // we must manually apply the transparency. + u32 a = color.getAlpha(); + u32 r = color.getRed(); + u32 g = color.getGreen(); + u32 b = color.getBlue(); + u8 *pt = Glyphs[n-1].image; + if (!Transparency) a = 255; + for (u32 y = 0; y < bmph; y++) + { + for (u32 x = 0; x < bmpw; x++) + { + if (!clip || clip->isPointInside(core::position2d(offset.X + x + offx, offset.Y + y + offy))) + { + if (*pt) + { + Driver->draw2DRectangle(video::SColor((a * (*pt)) / 255, r, g, b), core::rect(offset.X + x + offx, offset.Y + y + offy, offset.X + x + offx + 1, offset.Y + y + offy + 1)); + } + pt++; + } + } + } + } + } + else if (Glyphs[n-1].hasMonochrome) + { + u32 texw = Glyphs[n-1].texture_mono_size.Width; + u32 texh = Glyphs[n-1].texture_mono_size.Height; + s32 offx = Glyphs[n-1].bitmap_mono.left; + s32 offy = Glyphs[n-1].size - Glyphs[n-1].bitmap_mono.top; + + // Apply kerning. + core::vector2di k = getKerning(currentChar, previousChar); + offset.X += k.X; + offset.Y += k.Y; + + if (!Transparency) color.color |= 0xff000000; + Driver->draw2DImage(Glyphs[n-1].texture_mono, core::position2d(offset.X + offx, offset.Y + offy), core::rect(0, 0, texw - 1, texh - 1), clip, color, true); + } + offset.X += getWidthFromCharacter(currentChar); + } + else offset.X += getWidthFromCharacter(currentChar); + + previousChar = currentChar; + ++ptext; + } +} + +core::dimension2d CGUITTFont::getDimension(const wchar_t* text) const +{ + // Get the maximum font height. Unfortunately, we have to do this hack as + // Irrlicht will draw things wrong. In FreeType, the font size is the + // maximum size for a single glyph, but that glyph may hang "under" the + // draw line, increasing the total font height to beyond the set size. + // Irrlicht does not understand this concept when drawing fonts. Also, I + // add +1 to give it a 1 pixel blank border. This makes things like + // tooltips look nicer. + s32 test1 = getHeightFromCharacter(L'g') + 1; + s32 test2 = getHeightFromCharacter(L'j') + 1; + s32 test3 = getHeightFromCharacter(L'_') + 1; + s32 max_font_height = core::max_(test1, core::max_(test2, test3)); + + core::dimension2d dim(0, max_font_height); + core::dimension2d thisLine(0, max_font_height); + + wchar_t previousChar = 0; + for (const wchar_t* p = text; *p; ++p) + { + bool lineBreak=false; + if (*p == L'\r') // Mac or Windows breaks + { + lineBreak = true; + if (p[1] == L'\n') // Windows breaks + ++p; + } + else if (*p == L'\n') // Unix breaks + { + lineBreak = true; + } + + // Kerning. + core::vector2di k = getKerning(*p, previousChar); + thisLine.Width += k.X; + previousChar = *p; + + // Check for linebreak. + if (lineBreak) + { + previousChar = 0; + dim.Height += thisLine.Height; + if (dim.Width < thisLine.Width) + dim.Width = thisLine.Width; + thisLine.Width = 0; + thisLine.Height = max_font_height; + continue; + } + thisLine.Width += getWidthFromCharacter(*p); + } + if (dim.Width < thisLine.Width) + dim.Width = thisLine.Width; + + return dim; +} + +inline u32 CGUITTFont::getWidthFromCharacter(wchar_t c) const +{ + u32 n = getGlyphByChar(c); + if (n > 0) + { + int w = Glyphs[n - 1].bitmap.width; + s32 left = Glyphs[n - 1].bitmap.left; + if (w + left > 0) return w + left; + } + if (c >= 0x2000) + return Glyphs[0].size; + else return Glyphs[0].size / 2; +} + +inline u32 CGUITTFont::getHeightFromCharacter(wchar_t c) const +{ + u32 n = getGlyphByChar(c); + if (n > 0) + { + // Grab the true height of the character, taking into account underhanging glyphs. + s32 height = Glyphs[n-1].size - Glyphs[n-1].bitmap.top + Glyphs[n-1].bitmap.height; + return height; + } + if (c >= 0x2000) + return Glyphs[0].size; + else return Glyphs[0].size / 2; +} + +u32 CGUITTFont::getGlyphByChar(wchar_t c) const +{ + u32 idx = FT_Get_Char_Index(tt_face->face, c); + + // If the glyph hasn't been loaded yet, do it now. + if (idx && !Glyphs[idx - 1].cached) + Glyphs[idx - 1].cache(idx, FontHinting, AutoHinting); + + return idx; +} + +s32 CGUITTFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x) const +{ + s32 x = 0; + s32 idx = 0; + + while (text[idx]) + { + x += getWidthFromCharacter(text[idx]); + + if (x >= pixel_x) + return idx; + + ++idx; + } + + return -1; +} + +void CGUITTFont::setKerningWidth(s32 kerning) +{ + GlobalKerningWidth = kerning; +} + +void CGUITTFont::setKerningHeight(s32 kerning) +{ + GlobalKerningHeight = kerning; +} + +s32 CGUITTFont::getKerningWidth(const wchar_t* thisLetter, const wchar_t* previousLetter) const +{ + if (tt_face == 0) + return GlobalKerningWidth; + if (thisLetter == 0 || previousLetter == 0) + return 0; + + // Return only the kerning width. + return getKerning(*thisLetter, *previousLetter).X; +} + +s32 CGUITTFont::getKerningHeight() const +{ + // FreeType 2 currently doesn't return any height kerning information. + return GlobalKerningHeight; +} + +core::vector2di CGUITTFont::getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const +{ + if (tt_face == 0 || thisLetter == 0 || previousLetter == 0) + return core::vector2di(); + + core::vector2di ret(GlobalKerningWidth, GlobalKerningHeight); + + // Grab the face's flags to determine if the font is scalable or not. + FT_Long flags = tt_face->face->face_flags; + bool scalable = ((flags & FT_FACE_FLAG_SCALABLE) != 0); + + // Get the kerning information. + FT_Vector v; + FT_Get_Kerning(tt_face->face, getGlyphByChar(previousLetter), getGlyphByChar(thisLetter), FT_KERNING_DEFAULT, &v); + + // If we have a scalable font, the return value will be in font points. + if (scalable) + { + // Font points, so divide by 64. + ret.X += (v.x / 64); + ret.Y += (v.y / 64); + } + else + { + // Pixel units. + ret.X += v.x; + ret.Y += v.y; + } + return ret; +} + +void CGUITTFont::setInvisibleCharacters( const wchar_t *s ) +{ + Invisible = s; +} + +} // end namespace gui +} // end namespace irr diff --git a/extlib/CGUITTFont.h b/extlib/CGUITTFont.h new file mode 100644 index 0000000..95e6d0b --- /dev/null +++ b/extlib/CGUITTFont.h @@ -0,0 +1,169 @@ +#ifndef __C_GUI_TTFONT_H_INCLUDED__ +#define __C_GUI_TTFONT_H_INCLUDED__ + +#include +#include +#include FT_FREETYPE_H + +namespace irr +{ +namespace gui +{ + //! Represents a font face. + class CGUITTFace : public virtual IReferenceCounted + { + public: + CGUITTFace(); + virtual ~CGUITTFace(); + + //! Loads a font face. + //! \param filename Path to the font face. + //! \param filesystem The Irrlicht filesystem to use. If 0, fonts will be loaded into memory by FreeType instead of by Irrlicht. + //! \return Returns true if the font face loaded, false if it failed to load. + bool load(const io::path& filename, io::IFileSystem* filesystem = 0); + + //! The font face. + FT_Face face; + + private: + //! Flag to load the library. + static bool libraryLoaded; + bool faceLoaded; + FT_Byte* font_buffer; + FT_Long font_size; + }; + + //! Represents a glyph's bitmap info. + struct CGUITTBitmapInfo + { + u32 top; + u32 left; + u32 width; + u32 height; + }; + + //! Class representing a single glyph. + class CGUITTGlyph + { + public: + CGUITTGlyph(); + virtual ~CGUITTGlyph(); + + //! Loads the glyph. + void cache(u32 idx, bool fontHinting, bool autoHinting); + + //! Informs if the glyph was loaded. + bool cached; + + //! Video driver. + video::IVideoDriver* Driver; + + //! The face. + FT_Face *face; + + //! The image data. + //! Used for rendering with the software engine. + u8* image; + + // Texture/image data. + video::ITexture *texture; + video::ITexture *texture_mono; + + // Size of the glyph. + u32 size; + + // The size of the glyph is expressed in pixels. + bool size_is_pixels; + + // Info. + bool hasDefault; + bool hasMonochrome; + + // Bitmap information. + CGUITTBitmapInfo bitmap; + CGUITTBitmapInfo bitmap_mono; + + // Texture information. + core::dimension2du texture_size; + core::dimension2du texture_mono_size; + }; + + //! Class representing a font. + class CGUITTFont : public IGUIFont + { + public: + //! Constructor + CGUITTFont(IGUIEnvironment *env); + + //! Destructor + virtual ~CGUITTFont(); + + //! Binds a font face to the class. + //! \param face The font face to attach. + //! \param size The size you want to load the font at. + //! \param size_is_pixels If true, size is represented as pixels instead of points. + bool attach(CGUITTFace *face, u32 size, bool size_is_pixels = false); + + //! Draws some text and clips it to the specified rectangle if wanted. + virtual void draw(const core::stringw& text, const core::rect& position, + video::SColor color, bool hcenter=false, bool vcenter=false, + const core::rect* clip=0); + + //! Returns the dimension of a text string. + virtual core::dimension2d getDimension(const wchar_t* text) const; + + //! Calculates the index of the character in the text which is on a specific position. + virtual s32 getCharacterFromPos(const wchar_t* text, s32 pixel_x) const; + + //! Sets global kerning width for the font. + virtual void setKerningWidth(s32 kerning); + + //! Sets global kerning height for the font. + virtual void setKerningHeight(s32 kerning); + + //! Gets kerning values (distance between letters) for the font. If no parameters are provided, + virtual s32 getKerningWidth(const wchar_t* thisLetter=0, const wchar_t* previousLetter=0) const; + + //! Returns the distance between letters + virtual s32 getKerningHeight() const; + + //! Define which characters should not be drawn by the font. + virtual void setInvisibleCharacters(const wchar_t *s); + + //! Determines if the font will be loaded with antialiasing. + //! Defaults true. + bool AntiAlias; + + //! Determines if the font will be drawn with transparency. + //! Defaults true. + bool Transparency; + + //! Turns font hinting on or off. If a font looks odd, try toggling this option. + //! This setting controls whether or not FreeType uses a font's built-in hinting. + //! Defaults true. + bool FontHinting; + + //! Turns FreeType auto-hinting on or off. If a font looks odd, try toggling this option. + //! This setting controls whether or not FreeType uses its built-in auto hinting. + //! Defaults to true. + bool AutoHinting; + + private: + u32 getWidthFromCharacter(wchar_t c) const; + u32 getHeightFromCharacter(wchar_t c) const; + u32 getGlyphByChar(wchar_t c) const; + core::vector2di getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const; + + gui::IGUIEnvironment* Environment; + video::IVideoDriver* Driver; + mutable core::array< CGUITTGlyph > Glyphs; + CGUITTFace *tt_face; + s32 GlobalKerningWidth; + s32 GlobalKerningHeight; + core::stringw Invisible; + }; + +} // end namespace gui +} // end namespace irr + +#endif