2 CGUITTFont FreeType class for Irrlicht
3 Copyright (c) 2009-2010 John Norman
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any
7 damages arising from the use of this software.
9 Permission is granted to anyone to use this software for any
10 purpose, including commercial applications, and to alter it and
11 redistribute it freely, subject to the following restrictions:
13 1. The origin of this software must not be misrepresented; you
14 must not claim that you wrote the original software. If you use
15 this software in a product, an acknowledgment in the product
16 documentation would be appreciated but is not required.
18 2. Altered source versions must be plainly marked as such, and
19 must not be misrepresented as being the original software.
21 3. This notice may not be removed or altered from any source
24 The original version of this class can be located at:
25 http://irrlicht.suckerfreegames.com/
28 john@suckerfreegames.com
33 #include "CGUITTFont.h"
40 // Manages the FT_Face cache.
41 struct SGUITTFace : public virtual irr::IReferenceCounted
43 SGUITTFace() : face_buffer(0), face_buffer_size(0)
45 memset((void*)&face, 0, sizeof(FT_Face));
56 FT_Long face_buffer_size;
60 FT_Library CGUITTFont::c_library;
61 core::map<io::path, SGUITTFace*> CGUITTFont::c_faces;
62 bool CGUITTFont::c_libraryLoaded = false;
63 scene::IMesh* CGUITTFont::shared_plane_ptr_ = 0;
64 scene::SMesh CGUITTFont::shared_plane_;
68 /** Checks that no dimension of the FT_BitMap object is negative. If either is
69 * negative, abort execution.
71 inline void checkFontBitmapSize(const FT_Bitmap &bits)
73 if ((s32)bits.rows < 0 || (s32)bits.width < 0) {
74 std::cout << "Insane font glyph size. File: "
75 << __FILE__ << " Line " << __LINE__
81 video::IImage* SGUITTGlyph::createGlyphImage(const FT_Bitmap& bits, video::IVideoDriver* driver) const
83 // Make sure our casts to s32 in the loops below will not cause problems
84 checkFontBitmapSize(bits);
86 // Determine what our texture size should be.
87 // Add 1 because textures are inclusive-exclusive.
88 core::dimension2du d(bits.width + 1, bits.rows + 1);
89 core::dimension2du texture_size;
90 //core::dimension2du texture_size(bits.width + 1, bits.rows + 1);
92 // Create and load our image now.
93 video::IImage* image = 0;
94 switch (bits.pixel_mode)
96 case FT_PIXEL_MODE_MONO:
98 // Create a blank image and fill it with transparent pixels.
99 texture_size = d.getOptimalSize(true, true);
100 image = driver->createImage(video::ECF_A1R5G5B5, texture_size);
101 image->fill(video::SColor(0, 255, 255, 255));
103 // Load the monochrome data in.
104 const u32 image_pitch = image->getPitch() / sizeof(u16);
105 u16* image_data = (u16*)image->lock();
106 u8* glyph_data = bits.buffer;
108 for (s32 y = 0; y < (s32)bits.rows; ++y)
110 u16* row = image_data;
111 for (s32 x = 0; x < (s32)bits.width; ++x)
113 // Monochrome bitmaps store 8 pixels per byte. The left-most pixel is the bit 0x80.
114 // So, we go through the data each bit at a time.
115 if ((glyph_data[y * bits.pitch + (x / 8)] & (0x80 >> (x % 8))) != 0)
119 image_data += image_pitch;
125 case FT_PIXEL_MODE_GRAY:
127 // Create our blank image.
128 texture_size = d.getOptimalSize(!driver->queryFeature(video::EVDF_TEXTURE_NPOT), !driver->queryFeature(video::EVDF_TEXTURE_NSQUARE), true, 0);
129 image = driver->createImage(video::ECF_A8R8G8B8, texture_size);
130 image->fill(video::SColor(0, 255, 255, 255));
132 // Load the grayscale data in.
133 const float gray_count = static_cast<float>(bits.num_grays);
134 const u32 image_pitch = image->getPitch() / sizeof(u32);
135 u32* image_data = (u32*)image->lock();
136 u8* glyph_data = bits.buffer;
137 for (s32 y = 0; y < (s32)bits.rows; ++y)
139 u8* row = glyph_data;
140 for (s32 x = 0; x < (s32)bits.width; ++x)
142 image_data[y * image_pitch + x] |= static_cast<u32>(255.0f * (static_cast<float>(*row++) / gray_count)) << 24;
143 //data[y * image_pitch + x] |= ((u32)(*bitsdata++) << 24);
145 glyph_data += bits.pitch;
151 // TODO: error message?
157 void SGUITTGlyph::preload(u32 char_index, FT_Face face, video::IVideoDriver* driver, u32 font_size, const FT_Int32 loadFlags)
159 if (isLoaded) return;
161 // Set the size of the glyph.
162 FT_Set_Pixel_Sizes(face, 0, font_size);
164 // Attempt to load the glyph.
165 if (FT_Load_Glyph(face, char_index, loadFlags) != FT_Err_Ok)
166 // TODO: error message?
169 FT_GlyphSlot glyph = face->glyph;
170 FT_Bitmap bits = glyph->bitmap;
172 // Setup the glyph information here:
173 advance = glyph->advance;
174 offset = core::vector2di(glyph->bitmap_left, glyph->bitmap_top);
176 // Try to get the last page with available slots.
177 CGUITTGlyphPage* page = parent->getLastGlyphPage();
179 // If we need to make a new page, do that now.
182 page = parent->createGlyphPage(bits.pixel_mode);
184 // TODO: add error message?
188 glyph_page = parent->getLastGlyphPageIndex();
189 u32 texture_side_length = page->texture->getOriginalSize().Width;
190 core::vector2di page_position(
191 (page->used_slots % (texture_side_length / font_size)) * font_size,
192 (page->used_slots / (texture_side_length / font_size)) * font_size
194 source_rect.UpperLeftCorner = page_position;
195 source_rect.LowerRightCorner = core::vector2di(page_position.X + bits.width, page_position.Y + bits.rows);
199 --page->available_slots;
201 // We grab the glyph bitmap here so the data won't be removed when the next glyph is loaded.
202 surface = createGlyphImage(bits, driver);
204 // Set our glyph as loaded.
208 void SGUITTGlyph::unload()
218 //////////////////////
220 CGUITTFont* CGUITTFont::createTTFont(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency, const u32 shadow, const u32 shadow_alpha)
222 if (!c_libraryLoaded)
224 if (FT_Init_FreeType(&c_library))
226 c_libraryLoaded = true;
229 CGUITTFont* font = new CGUITTFont(env);
230 bool ret = font->load(filename, size, antialias, transparency);
237 font->shadow_offset = shadow;
238 font->shadow_alpha = shadow_alpha;
243 CGUITTFont* CGUITTFont::createTTFont(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
245 if (!c_libraryLoaded)
247 if (FT_Init_FreeType(&c_library))
249 c_libraryLoaded = true;
252 CGUITTFont* font = new CGUITTFont(device->getGUIEnvironment());
253 font->Device = device;
254 bool ret = font->load(filename, size, antialias, transparency);
264 CGUITTFont* CGUITTFont::create(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
266 return CGUITTFont::createTTFont(env, filename, size, antialias, transparency);
269 CGUITTFont* CGUITTFont::create(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
271 return CGUITTFont::createTTFont(device, filename, size, antialias, transparency);
274 //////////////////////
277 CGUITTFont::CGUITTFont(IGUIEnvironment *env)
278 : use_monochrome(false), use_transparency(true), use_hinting(true), use_auto_hinting(true),
279 batch_load_size(1), Device(0), Environment(env), Driver(0), GlobalKerningWidth(0), GlobalKerningHeight(0)
282 setDebugName("CGUITTFont");
287 // don't grab environment, to avoid circular references
288 Driver = Environment->getVideoDriver();
294 setInvisibleCharacters(L" ");
296 // Glyphs aren't reference counted, so don't try to delete them when we free the array.
297 Glyphs.set_free_when_destroyed(false);
300 bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antialias, const bool transparency)
302 // Some sanity checks.
303 if (Environment == 0 || Driver == 0) return false;
304 if (size == 0) return false;
305 if (filename.size() == 0) return false;
307 io::IFileSystem* filesystem = Environment->getFileSystem();
308 irr::ILogger* logger = (Device != 0 ? Device->getLogger() : 0);
310 this->filename = filename;
312 // Update the font loading flags when the font is first loaded.
313 this->use_monochrome = !antialias;
314 this->use_transparency = transparency;
319 logger->log(L"CGUITTFont", core::stringw(core::stringw(L"Creating new font: ") + core::ustring(filename).toWCHAR_s() + L" " + core::stringc(size) + L"pt " + (antialias ? L"+antialias " : L"-antialias ") + (transparency ? L"+transparency" : L"-transparency")).c_str(), irr::ELL_INFORMATION);
322 SGUITTFace* face = 0;
323 core::map<io::path, SGUITTFace*>::Node* node = c_faces.find(filename);
326 face = new SGUITTFace();
327 c_faces.set(filename, face);
331 // Read in the file data.
332 io::IReadFile* file = filesystem->createAndOpenFile(filename);
335 if (logger) logger->log(L"CGUITTFont", L"Failed to open the file.", irr::ELL_INFORMATION);
337 c_faces.remove(filename);
342 face->face_buffer = new FT_Byte[file->getSize()];
343 file->read(face->face_buffer, file->getSize());
344 face->face_buffer_size = file->getSize();
348 if (FT_New_Memory_Face(c_library, face->face_buffer, face->face_buffer_size, 0, &face->face))
350 if (logger) logger->log(L"CGUITTFont", L"FT_New_Memory_Face failed.", irr::ELL_INFORMATION);
352 c_faces.remove(filename);
360 core::ustring converter(filename);
361 if (FT_New_Face(c_library, reinterpret_cast<const char*>(converter.toUTF8_s().c_str()), 0, &face->face))
363 if (logger) logger->log(L"CGUITTFont", L"FT_New_Face failed.", irr::ELL_INFORMATION);
365 c_faces.remove(filename);
374 // Using another instance of this face.
375 face = node->getValue();
380 tt_face = face->face;
382 // Store font metrics.
383 FT_Set_Pixel_Sizes(tt_face, size, 0);
384 font_metrics = tt_face->size->metrics;
386 // Allocate our glyphs.
388 Glyphs.reallocate(tt_face->num_glyphs);
389 Glyphs.set_used(tt_face->num_glyphs);
390 for (FT_Long i = 0; i < tt_face->num_glyphs; ++i)
392 Glyphs[i].isLoaded = false;
393 Glyphs[i].glyph_page = 0;
394 Glyphs[i].source_rect = core::recti();
395 Glyphs[i].offset = core::vector2di();
396 Glyphs[i].advance = FT_Vector();
397 Glyphs[i].surface = 0;
398 Glyphs[i].parent = this;
401 // Cache the first 127 ascii characters.
402 u32 old_size = batch_load_size;
403 batch_load_size = 127;
404 getGlyphIndexByChar((uchar32_t)0);
405 batch_load_size = old_size;
410 CGUITTFont::~CGUITTFont()
412 // Delete the glyphs and glyph pages.
414 CGUITTAssistDelete::Delete(Glyphs);
417 // We aren't using this face anymore.
418 core::map<io::path, SGUITTFace*>::Node* n = c_faces.find(filename);
421 SGUITTFace* f = n->getValue();
423 // Drop our face. If this was the last face, the destructor will clean up.
425 c_faces.remove(filename);
427 // If there are no more faces referenced by FreeType, clean up.
428 if (c_faces.size() == 0)
430 FT_Done_FreeType(c_library);
431 c_libraryLoaded = false;
435 // Drop our driver now.
440 void CGUITTFont::reset_images()
442 // Delete the glyphs.
443 for (u32 i = 0; i != Glyphs.size(); ++i)
446 // Unload the glyph pages from video memory.
447 for (u32 i = 0; i != Glyph_Pages.size(); ++i)
448 delete Glyph_Pages[i];
451 // Always update the internal FreeType loading flags after resetting.
455 void CGUITTFont::update_glyph_pages() const
457 for (u32 i = 0; i != Glyph_Pages.size(); ++i)
459 if (Glyph_Pages[i]->dirty)
460 Glyph_Pages[i]->updateTexture();
464 CGUITTGlyphPage* CGUITTFont::getLastGlyphPage() const
466 CGUITTGlyphPage* page = 0;
467 if (Glyph_Pages.empty())
471 page = Glyph_Pages[getLastGlyphPageIndex()];
472 if (page->available_slots == 0)
478 CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8& pixel_mode)
480 CGUITTGlyphPage* page = 0;
483 io::path name("TTFontGlyphPage_");
484 name += tt_face->family_name;
486 name += tt_face->style_name;
490 name += Glyph_Pages.size(); // The newly created page will be at the end of the collection.
492 // Create the new page.
493 page = new CGUITTGlyphPage(Driver, name);
495 // Determine our maximum texture size.
496 // If we keep getting 0, set it to 1024x1024, as that number is pretty safe.
497 core::dimension2du max_texture_size = max_page_texture_size;
498 if (max_texture_size.Width == 0 || max_texture_size.Height == 0)
499 max_texture_size = Driver->getMaxTextureSize();
500 if (max_texture_size.Width == 0 || max_texture_size.Height == 0)
501 max_texture_size = core::dimension2du(1024, 1024);
503 // We want to try to put at least 144 glyphs on a single texture.
504 core::dimension2du page_texture_size;
505 if (size <= 21) page_texture_size = core::dimension2du(256, 256);
506 else if (size <= 42) page_texture_size = core::dimension2du(512, 512);
507 else if (size <= 84) page_texture_size = core::dimension2du(1024, 1024);
508 else if (size <= 168) page_texture_size = core::dimension2du(2048, 2048);
509 else page_texture_size = core::dimension2du(4096, 4096);
511 if (page_texture_size.Width > max_texture_size.Width || page_texture_size.Height > max_texture_size.Height)
512 page_texture_size = max_texture_size;
514 if (!page->createPageTexture(pixel_mode, page_texture_size))
515 // TODO: add error message?
520 // Determine the number of glyph slots on the page and add it to the list of pages.
521 page->available_slots = (page_texture_size.Width / size) * (page_texture_size.Height / size);
522 Glyph_Pages.push_back(page);
527 void CGUITTFont::setTransparency(const bool flag)
529 use_transparency = flag;
533 void CGUITTFont::setMonochrome(const bool flag)
535 use_monochrome = flag;
539 void CGUITTFont::setFontHinting(const bool enable, const bool enable_auto_hinting)
541 use_hinting = enable;
542 use_auto_hinting = enable_auto_hinting;
546 void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position, video::SColor color, bool hcenter, bool vcenter, const core::rect<s32>* clip)
551 // Clear the glyph pages of their render information.
552 for (u32 i = 0; i < Glyph_Pages.size(); ++i)
554 Glyph_Pages[i]->render_positions.clear();
555 Glyph_Pages[i]->render_source_rects.clear();
558 // Set up some variables.
559 core::dimension2d<s32> textDimension;
560 core::position2d<s32> offset = position.UpperLeftCorner;
562 // Determine offset positions.
563 if (hcenter || vcenter)
565 textDimension = getDimension(text.c_str());
568 offset.X = ((position.getWidth() - textDimension.Width) >> 1) + offset.X;
571 offset.Y = ((position.getHeight() - textDimension.Height) >> 1) + offset.Y;
574 // Convert to a unicode string.
575 core::ustring utext(text);
577 // Set up our render map.
578 core::map<u32, CGUITTGlyphPage*> Render_Map;
580 // Start parsing characters.
582 uchar32_t previousChar = 0;
583 core::ustring::const_iterator iter(utext);
584 while (!iter.atEnd())
586 uchar32_t currentChar = *iter;
587 n = getGlyphIndexByChar(currentChar);
588 bool visible = (Invisible.findFirst(currentChar) == -1);
589 bool lineBreak=false;
590 if (currentChar == L'\r') // Mac or Windows breaks
593 if (*(iter + 1) == (uchar32_t)'\n') // Windows line breaks.
594 currentChar = *(++iter);
596 else if (currentChar == (uchar32_t)'\n') // Unix breaks
604 offset.Y += font_metrics.height / 64;
605 offset.X = position.UpperLeftCorner.X;
608 offset.X += (position.getWidth() - textDimension.Width) >> 1;
613 if (n > 0 && visible)
615 // Calculate the glyph offset.
616 s32 offx = Glyphs[n-1].offset.X;
617 s32 offy = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y;
620 core::vector2di k = getKerning(currentChar, previousChar);
624 // Determine rendering information.
625 SGUITTGlyph& glyph = Glyphs[n-1];
626 CGUITTGlyphPage* const page = Glyph_Pages[glyph.glyph_page];
627 page->render_positions.push_back(core::position2di(offset.X + offx, offset.Y + offy));
628 page->render_source_rects.push_back(glyph.source_rect);
629 Render_Map.set(glyph.glyph_page, page);
631 offset.X += getWidthFromCharacter(currentChar);
633 previousChar = currentChar;
638 update_glyph_pages();
639 core::map<u32, CGUITTGlyphPage*>::Iterator j = Render_Map.getIterator();
642 core::map<u32, CGUITTGlyphPage*>::Node* n = j.getNode();
644 if (n == 0) continue;
646 CGUITTGlyphPage* page = n->getValue();
648 if (!use_transparency) color.color |= 0xff000000;
651 for (size_t i = 0; i < page->render_positions.size(); ++i)
652 page->render_positions[i] += core::vector2di(shadow_offset, shadow_offset);
653 Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, video::SColor(shadow_alpha,0,0,0), true);
654 for (size_t i = 0; i < page->render_positions.size(); ++i)
655 page->render_positions[i] -= core::vector2di(shadow_offset, shadow_offset);
657 Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, color, true);
661 core::dimension2d<u32> CGUITTFont::getCharDimension(const wchar_t ch) const
663 return core::dimension2d<u32>(getWidthFromCharacter(ch), getHeightFromCharacter(ch));
666 core::dimension2d<u32> CGUITTFont::getDimension(const wchar_t* text) const
668 return getDimension(core::ustring(text));
671 core::dimension2d<u32> CGUITTFont::getDimension(const core::ustring& text) const
673 // Get the maximum font height. Unfortunately, we have to do this hack as
674 // Irrlicht will draw things wrong. In FreeType, the font size is the
675 // maximum size for a single glyph, but that glyph may hang "under" the
676 // draw line, increasing the total font height to beyond the set size.
677 // Irrlicht does not understand this concept when drawing fonts. Also, I
678 // add +1 to give it a 1 pixel blank border. This makes things like
679 // tooltips look nicer.
680 s32 test1 = getHeightFromCharacter((uchar32_t)'g') + 1;
681 s32 test2 = getHeightFromCharacter((uchar32_t)'j') + 1;
682 s32 test3 = getHeightFromCharacter((uchar32_t)'_') + 1;
683 s32 max_font_height = core::max_(test1, core::max_(test2, test3));
685 core::dimension2d<u32> text_dimension(0, max_font_height);
686 core::dimension2d<u32> line(0, max_font_height);
688 uchar32_t previousChar = 0;
689 core::ustring::const_iterator iter = text.begin();
690 for (; !iter.atEnd(); ++iter)
693 bool lineBreak = false;
694 if (p == '\r') // Mac or Windows line breaks.
697 if (*(iter + 1) == '\n')
703 else if (p == '\n') // Unix line breaks.
709 core::vector2di k = getKerning(p, previousChar);
713 // Check for linebreak.
717 text_dimension.Height += line.Height;
718 if (text_dimension.Width < line.Width)
719 text_dimension.Width = line.Width;
721 line.Height = max_font_height;
724 line.Width += getWidthFromCharacter(p);
726 if (text_dimension.Width < line.Width)
727 text_dimension.Width = line.Width;
729 return text_dimension;
732 inline u32 CGUITTFont::getWidthFromCharacter(wchar_t c) const
734 return getWidthFromCharacter((uchar32_t)c);
737 inline u32 CGUITTFont::getWidthFromCharacter(uchar32_t c) const
739 // Set the size of the face.
740 // This is because we cache faces and the face may have been set to a different size.
741 //FT_Set_Pixel_Sizes(tt_face, 0, size);
743 u32 n = getGlyphIndexByChar(c);
746 int w = Glyphs[n-1].advance.x / 64;
750 return (font_metrics.ascender / 64);
751 else return (font_metrics.ascender / 64) / 2;
754 inline u32 CGUITTFont::getHeightFromCharacter(wchar_t c) const
756 return getHeightFromCharacter((uchar32_t)c);
759 inline u32 CGUITTFont::getHeightFromCharacter(uchar32_t c) const
761 // Set the size of the face.
762 // This is because we cache faces and the face may have been set to a different size.
763 //FT_Set_Pixel_Sizes(tt_face, 0, size);
765 u32 n = getGlyphIndexByChar(c);
768 // Grab the true height of the character, taking into account underhanging glyphs.
769 s32 height = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y + Glyphs[n-1].source_rect.getHeight();
773 return (font_metrics.ascender / 64);
774 else return (font_metrics.ascender / 64) / 2;
777 u32 CGUITTFont::getGlyphIndexByChar(wchar_t c) const
779 return getGlyphIndexByChar((uchar32_t)c);
782 u32 CGUITTFont::getGlyphIndexByChar(uchar32_t c) const
785 u32 glyph = FT_Get_Char_Index(tt_face, c);
787 // Check for a valid glyph. If it is invalid, attempt to use the replacement character.
789 glyph = FT_Get_Char_Index(tt_face, core::unicode::UTF_REPLACEMENT_CHARACTER);
791 // If our glyph is already loaded, don't bother doing any batch loading code.
792 if (glyph != 0 && Glyphs[glyph - 1].isLoaded)
795 // Determine our batch loading positions.
796 u32 half_size = (batch_load_size / 2);
798 if (c > half_size) start_pos = c - half_size;
799 u32 end_pos = start_pos + batch_load_size;
801 // Load all our characters.
804 // Get the character we are going to load.
805 u32 char_index = FT_Get_Char_Index(tt_face, start_pos);
807 // If the glyph hasn't been loaded yet, do it now.
810 SGUITTGlyph& glyph = Glyphs[char_index - 1];
813 glyph.preload(char_index, tt_face, Driver, size, load_flags);
814 Glyph_Pages[glyph.glyph_page]->pushGlyphToBePaged(&glyph);
818 while (++start_pos < end_pos);
820 // Return our original character.
824 s32 CGUITTFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x) const
826 return getCharacterFromPos(core::ustring(text), pixel_x);
829 s32 CGUITTFont::getCharacterFromPos(const core::ustring& text, s32 pixel_x) const
835 uchar32_t previousChar = 0;
836 core::ustring::const_iterator iter = text.begin();
837 while (!iter.atEnd())
840 x += getWidthFromCharacter(c);
843 core::vector2di k = getKerning(c, previousChar);
857 void CGUITTFont::setKerningWidth(s32 kerning)
859 GlobalKerningWidth = kerning;
862 void CGUITTFont::setKerningHeight(s32 kerning)
864 GlobalKerningHeight = kerning;
867 s32 CGUITTFont::getKerningWidth(const wchar_t* thisLetter, const wchar_t* previousLetter) const
870 return GlobalKerningWidth;
871 if (thisLetter == 0 || previousLetter == 0)
874 return getKerningWidth((uchar32_t)*thisLetter, (uchar32_t)*previousLetter);
877 s32 CGUITTFont::getKerningWidth(const uchar32_t thisLetter, const uchar32_t previousLetter) const
879 // Return only the kerning width.
880 return getKerning(thisLetter, previousLetter).X;
883 s32 CGUITTFont::getKerningHeight() const
885 // FreeType 2 currently doesn't return any height kerning information.
886 return GlobalKerningHeight;
889 core::vector2di CGUITTFont::getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const
891 return getKerning((uchar32_t)thisLetter, (uchar32_t)previousLetter);
894 core::vector2di CGUITTFont::getKerning(const uchar32_t thisLetter, const uchar32_t previousLetter) const
896 if (tt_face == 0 || thisLetter == 0 || previousLetter == 0)
897 return core::vector2di();
899 // Set the size of the face.
900 // This is because we cache faces and the face may have been set to a different size.
901 FT_Set_Pixel_Sizes(tt_face, 0, size);
903 core::vector2di ret(GlobalKerningWidth, GlobalKerningHeight);
905 // If we don't have kerning, no point in continuing.
906 if (!FT_HAS_KERNING(tt_face))
909 // Get the kerning information.
911 FT_Get_Kerning(tt_face, getGlyphIndexByChar(previousLetter), getGlyphIndexByChar(thisLetter), FT_KERNING_DEFAULT, &v);
913 // If we have a scalable font, the return value will be in font points.
914 if (FT_IS_SCALABLE(tt_face))
916 // Font points, so divide by 64.
929 void CGUITTFont::setInvisibleCharacters(const wchar_t *s)
935 void CGUITTFont::setInvisibleCharacters(const core::ustring& s)
940 video::IImage* CGUITTFont::createTextureFromChar(const uchar32_t& ch)
942 u32 n = getGlyphIndexByChar(ch);
943 const SGUITTGlyph& glyph = Glyphs[n-1];
944 CGUITTGlyphPage* page = Glyph_Pages[glyph.glyph_page];
947 page->updateTexture();
949 video::ITexture* tex = page->texture;
951 // Acquire a read-only lock of the corresponding page texture.
952 #if IRRLICHT_VERSION_MAJOR==1 && IRRLICHT_VERSION_MINOR>=8
953 void* ptr = tex->lock(video::ETLM_READ_ONLY);
955 void* ptr = tex->lock(true);
958 video::ECOLOR_FORMAT format = tex->getColorFormat();
959 core::dimension2du tex_size = tex->getOriginalSize();
960 video::IImage* pageholder = Driver->createImageFromData(format, tex_size, ptr, true, false);
962 // Copy the image data out of the page texture.
963 core::dimension2du glyph_size(glyph.source_rect.getSize());
964 video::IImage* image = Driver->createImage(format, glyph_size);
965 pageholder->copyTo(image, core::position2di(0, 0), glyph.source_rect);
971 video::ITexture* CGUITTFont::getPageTextureByIndex(const u32& page_index) const
973 if (page_index < Glyph_Pages.size())
974 return Glyph_Pages[page_index]->texture;
979 void CGUITTFont::createSharedPlane()
984 | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
985 |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
989 using namespace core;
990 using namespace video;
991 using namespace scene;
992 S3DVertex vertices[4];
993 u16 indices[6] = {0,2,3,3,1,0};
994 vertices[0] = S3DVertex(vector3df(0,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,1));
995 vertices[1] = S3DVertex(vector3df(1,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,1));
996 vertices[2] = S3DVertex(vector3df(0, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,0));
997 vertices[3] = S3DVertex(vector3df(1, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,0));
999 SMeshBuffer* buf = new SMeshBuffer();
1000 buf->append(vertices, 4, indices, 6);
1002 shared_plane_.addMeshBuffer( buf );
1004 shared_plane_ptr_ = &shared_plane_;
1005 buf->drop(); //the addMeshBuffer method will grab it, so we can drop this ptr.
1008 core::dimension2d<u32> CGUITTFont::getDimensionUntilEndOfLine(const wchar_t* p) const
1011 for (const wchar_t* temp = p; temp && *temp != '\0' && *temp != L'\r' && *temp != L'\n'; ++temp )
1014 return getDimension(s.c_str());
1017 core::array<scene::ISceneNode*> CGUITTFont::addTextSceneNode(const wchar_t* text, scene::ISceneManager* smgr, scene::ISceneNode* parent, const video::SColor& color, bool center)
1019 using namespace core;
1020 using namespace video;
1021 using namespace scene;
1023 array<scene::ISceneNode*> container;
1025 if (!Driver || !smgr) return container;
1027 parent = smgr->addEmptySceneNode(smgr->getRootSceneNode(), -1);
1028 // if you don't specify parent, then we add a empty node attached to the root node
1029 // this is generally undesirable.
1031 if (!shared_plane_ptr_) //this points to a static mesh that contains the plane
1032 createSharedPlane(); //if it's not initialized, we create one.
1034 dimension2d<s32> text_size(getDimension(text)); //convert from unsigned to signed.
1035 vector3df start_point(0, 0, 0), offset;
1038 Because we are considering adding texts into 3D world, all Y axis vectors are inverted.
1041 // There's currently no "vertical center" concept when you apply text scene node to the 3D world.
1044 offset.X = start_point.X = -text_size.Width / 2.f;
1045 offset.Y = start_point.Y = +text_size.Height/ 2.f;
1046 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text).Width) >> 1;
1049 // the default font material
1051 mat.setFlag(video::EMF_LIGHTING, true);
1052 mat.setFlag(video::EMF_ZWRITE_ENABLE, false);
1053 mat.setFlag(video::EMF_NORMALIZE_NORMALS, true);
1054 mat.ColorMaterial = video::ECM_NONE;
1055 mat.MaterialType = use_transparency ? video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_SOLID;
1056 mat.MaterialTypeParam = 0.01f;
1057 mat.DiffuseColor = color;
1059 wchar_t current_char = 0, previous_char = 0;
1062 array<u32> glyph_indices;
1066 current_char = *text;
1067 bool line_break=false;
1068 if (current_char == L'\r') // Mac or Windows breaks
1071 if (*(text + 1) == L'\n') // Windows line breaks.
1072 current_char = *(++text);
1074 else if (current_char == L'\n') // Unix breaks
1082 offset.Y -= tt_face->size->metrics.ascender / 64;
1083 offset.X = start_point.X;
1085 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text+1).Width) >> 1;
1090 n = getGlyphIndexByChar(current_char);
1093 glyph_indices.push_back( n );
1095 // Store glyph size and offset informations.
1096 SGUITTGlyph const& glyph = Glyphs[n-1];
1097 u32 texw = glyph.source_rect.getWidth();
1098 u32 texh = glyph.source_rect.getHeight();
1099 s32 offx = glyph.offset.X;
1100 s32 offy = (font_metrics.ascender / 64) - glyph.offset.Y;
1103 vector2di k = getKerning(current_char, previous_char);
1107 vector3df current_pos(offset.X + offx, offset.Y - offy, 0);
1108 dimension2d<u32> letter_size = dimension2d<u32>(texw, texh);
1110 // Now we copy planes corresponding to the letter size.
1111 IMeshManipulator* mani = smgr->getMeshManipulator();
1112 IMesh* meshcopy = mani->createMeshCopy(shared_plane_ptr_);
1113 #if IRRLICHT_VERSION_MAJOR==1 && IRRLICHT_VERSION_MINOR>=8
1114 mani->scale(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1));
1116 mani->scaleMesh(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1));
1119 ISceneNode* current_node = smgr->addMeshSceneNode(meshcopy, parent, -1, current_pos);
1122 current_node->getMaterial(0) = mat;
1123 current_node->setAutomaticCulling(EAC_OFF);
1124 current_node->setIsDebugObject(true); //so the picking won't have any effect on individual letter
1125 //current_node->setDebugDataVisible(EDS_BBOX); //de-comment this when debugging
1127 container.push_back(current_node);
1129 offset.X += getWidthFromCharacter(current_char);
1130 previous_char = current_char;
1135 update_glyph_pages();
1136 //only after we update the textures can we use the glyph page textures.
1138 for (u32 i = 0; i < glyph_indices.size(); ++i)
1140 u32 n = glyph_indices[i];
1141 SGUITTGlyph const& glyph = Glyphs[n-1];
1142 ITexture* current_tex = Glyph_Pages[glyph.glyph_page]->texture;
1143 f32 page_texture_size = (f32)current_tex->getSize().Width;
1144 //Now we calculate the UV position according to the texture size and the source rect.
1148 // | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
1149 // |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
1152 f32 u1 = glyph.source_rect.UpperLeftCorner.X / page_texture_size;
1153 f32 u2 = u1 + (glyph.source_rect.getWidth() / page_texture_size);
1154 f32 v1 = glyph.source_rect.UpperLeftCorner.Y / page_texture_size;
1155 f32 v2 = v1 + (glyph.source_rect.getHeight() / page_texture_size);
1157 //we can be quite sure that this is IMeshSceneNode, because we just added them in the above loop.
1158 IMeshSceneNode* node = static_cast<IMeshSceneNode*>(container[i]);
1160 S3DVertex* pv = static_cast<S3DVertex*>(node->getMesh()->getMeshBuffer(0)->getVertices());
1161 //pv[0].TCoords.Y = pv[1].TCoords.Y = (letter_size.Height - 1) / static_cast<f32>(letter_size.Height);
1162 //pv[1].TCoords.X = pv[3].TCoords.X = (letter_size.Width - 1) / static_cast<f32>(letter_size.Width);
1163 pv[0].TCoords = vector2df(u1, v2);
1164 pv[1].TCoords = vector2df(u2, v2);
1165 pv[2].TCoords = vector2df(u1, v1);
1166 pv[3].TCoords = vector2df(u2, v1);
1168 container[i]->getMaterial(0).setTexture(0, current_tex);
1174 } // end namespace gui
1175 } // end namespace irr