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
32 #include "CGUITTFont.h"
39 // Manages the FT_Face cache.
40 struct SGUITTFace : public virtual irr::IReferenceCounted
42 SGUITTFace() : face_buffer(0), face_buffer_size(0)
44 memset((void*)&face, 0, sizeof(FT_Face));
55 FT_Long face_buffer_size;
59 FT_Library CGUITTFont::c_library;
60 core::map<io::path, SGUITTFace*> CGUITTFont::c_faces;
61 bool CGUITTFont::c_libraryLoaded = false;
62 scene::IMesh* CGUITTFont::shared_plane_ptr_ = 0;
63 scene::SMesh CGUITTFont::shared_plane_;
67 video::IImage* SGUITTGlyph::createGlyphImage(const FT_Bitmap& bits, video::IVideoDriver* driver) const
69 // Determine what our texture size should be.
70 // Add 1 because textures are inclusive-exclusive.
71 core::dimension2du d(bits.width + 1, bits.rows + 1);
72 core::dimension2du texture_size;
73 //core::dimension2du texture_size(bits.width + 1, bits.rows + 1);
75 // Create and load our image now.
76 video::IImage* image = 0;
77 switch (bits.pixel_mode)
79 case FT_PIXEL_MODE_MONO:
81 // Create a blank image and fill it with transparent pixels.
82 texture_size = d.getOptimalSize(true, true);
83 image = driver->createImage(video::ECF_A1R5G5B5, texture_size);
84 image->fill(video::SColor(0, 255, 255, 255));
86 // Load the monochrome data in.
87 const u32 image_pitch = image->getPitch() / sizeof(u16);
88 u16* image_data = (u16*)image->lock();
89 u8* glyph_data = bits.buffer;
90 for (s32 y = 0; y < bits.rows; ++y)
92 u16* row = image_data;
93 for (s32 x = 0; x < bits.width; ++x)
95 // Monochrome bitmaps store 8 pixels per byte. The left-most pixel is the bit 0x80.
96 // So, we go through the data each bit at a time.
97 if ((glyph_data[y * bits.pitch + (x / 8)] & (0x80 >> (x % 8))) != 0)
101 image_data += image_pitch;
107 case FT_PIXEL_MODE_GRAY:
109 // Create our blank image.
110 texture_size = d.getOptimalSize(!driver->queryFeature(video::EVDF_TEXTURE_NPOT), !driver->queryFeature(video::EVDF_TEXTURE_NSQUARE), true, 0);
111 image = driver->createImage(video::ECF_A8R8G8B8, texture_size);
112 image->fill(video::SColor(0, 255, 255, 255));
114 // Load the grayscale data in.
115 const float gray_count = static_cast<float>(bits.num_grays);
116 const u32 image_pitch = image->getPitch() / sizeof(u32);
117 u32* image_data = (u32*)image->lock();
118 u8* glyph_data = bits.buffer;
119 for (s32 y = 0; y < bits.rows; ++y)
121 u8* row = glyph_data;
122 for (s32 x = 0; x < bits.width; ++x)
124 image_data[y * image_pitch + x] |= static_cast<u32>(255.0f * (static_cast<float>(*row++) / gray_count)) << 24;
125 //data[y * image_pitch + x] |= ((u32)(*bitsdata++) << 24);
127 glyph_data += bits.pitch;
133 // TODO: error message?
139 void SGUITTGlyph::preload(u32 char_index, FT_Face face, video::IVideoDriver* driver, u32 font_size, const FT_Int32 loadFlags)
141 if (isLoaded) return;
143 // Set the size of the glyph.
144 FT_Set_Pixel_Sizes(face, 0, font_size);
146 // Attempt to load the glyph.
147 if (FT_Load_Glyph(face, char_index, loadFlags) != FT_Err_Ok)
148 // TODO: error message?
151 FT_GlyphSlot glyph = face->glyph;
152 FT_Bitmap bits = glyph->bitmap;
154 // Setup the glyph information here:
155 advance = glyph->advance;
156 offset = core::vector2di(glyph->bitmap_left, glyph->bitmap_top);
158 // Try to get the last page with available slots.
159 CGUITTGlyphPage* page = parent->getLastGlyphPage();
161 // If we need to make a new page, do that now.
164 page = parent->createGlyphPage(bits.pixel_mode);
166 // TODO: add error message?
170 glyph_page = parent->getLastGlyphPageIndex();
171 u32 texture_side_length = page->texture->getOriginalSize().Width;
172 core::vector2di page_position(
173 (page->used_slots % (texture_side_length / font_size)) * font_size,
174 (page->used_slots / (texture_side_length / font_size)) * font_size
176 source_rect.UpperLeftCorner = page_position;
177 source_rect.LowerRightCorner = core::vector2di(page_position.X + bits.width, page_position.Y + bits.rows);
181 --page->available_slots;
183 // We grab the glyph bitmap here so the data won't be removed when the next glyph is loaded.
184 surface = createGlyphImage(bits, driver);
186 // Set our glyph as loaded.
190 void SGUITTGlyph::unload()
200 //////////////////////
202 CGUITTFont* CGUITTFont::createTTFont(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
204 if (!c_libraryLoaded)
206 if (FT_Init_FreeType(&c_library))
208 c_libraryLoaded = true;
211 CGUITTFont* font = new CGUITTFont(env);
212 bool ret = font->load(filename, size, antialias, transparency);
222 CGUITTFont* CGUITTFont::createTTFont(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
224 if (!c_libraryLoaded)
226 if (FT_Init_FreeType(&c_library))
228 c_libraryLoaded = true;
231 CGUITTFont* font = new CGUITTFont(device->getGUIEnvironment());
232 font->Device = device;
233 bool ret = font->load(filename, size, antialias, transparency);
243 CGUITTFont* CGUITTFont::create(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
245 return CGUITTFont::createTTFont(env, filename, size, antialias, transparency);
248 CGUITTFont* CGUITTFont::create(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
250 return CGUITTFont::createTTFont(device, filename, size, antialias, transparency);
253 //////////////////////
256 CGUITTFont::CGUITTFont(IGUIEnvironment *env)
257 : use_monochrome(false), use_transparency(true), use_hinting(true), use_auto_hinting(true),
258 batch_load_size(1), Device(0), Environment(env), Driver(0), GlobalKerningWidth(0), GlobalKerningHeight(0)
261 setDebugName("CGUITTFont");
266 // don't grab environment, to avoid circular references
267 Driver = Environment->getVideoDriver();
273 setInvisibleCharacters(L" ");
275 // Glyphs aren't reference counted, so don't try to delete them when we free the array.
276 Glyphs.set_free_when_destroyed(false);
279 bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antialias, const bool transparency)
281 // Some sanity checks.
282 if (Environment == 0 || Driver == 0) return false;
283 if (size == 0) return false;
284 if (filename.size() == 0) return false;
286 io::IFileSystem* filesystem = Environment->getFileSystem();
287 irr::ILogger* logger = (Device != 0 ? Device->getLogger() : 0);
289 this->filename = filename;
291 // Update the font loading flags when the font is first loaded.
292 this->use_monochrome = !antialias;
293 this->use_transparency = transparency;
298 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);
301 SGUITTFace* face = 0;
302 core::map<io::path, SGUITTFace*>::Node* node = c_faces.find(filename);
305 face = new SGUITTFace();
306 c_faces.set(filename, face);
310 // Read in the file data.
311 io::IReadFile* file = filesystem->createAndOpenFile(filename);
314 if (logger) logger->log(L"CGUITTFont", L"Failed to open the file.", irr::ELL_INFORMATION);
316 c_faces.remove(filename);
321 face->face_buffer = new FT_Byte[file->getSize()];
322 file->read(face->face_buffer, file->getSize());
323 face->face_buffer_size = file->getSize();
327 if (FT_New_Memory_Face(c_library, face->face_buffer, face->face_buffer_size, 0, &face->face))
329 if (logger) logger->log(L"CGUITTFont", L"FT_New_Memory_Face failed.", irr::ELL_INFORMATION);
331 c_faces.remove(filename);
339 core::ustring converter(filename);
340 if (FT_New_Face(c_library, reinterpret_cast<const char*>(converter.toUTF8_s().c_str()), 0, &face->face))
342 if (logger) logger->log(L"CGUITTFont", L"FT_New_Face failed.", irr::ELL_INFORMATION);
344 c_faces.remove(filename);
353 // Using another instance of this face.
354 face = node->getValue();
359 tt_face = face->face;
361 // Store font metrics.
362 FT_Set_Pixel_Sizes(tt_face, size, 0);
363 font_metrics = tt_face->size->metrics;
365 // Allocate our glyphs.
367 Glyphs.reallocate(tt_face->num_glyphs);
368 Glyphs.set_used(tt_face->num_glyphs);
369 for (FT_Long i = 0; i < tt_face->num_glyphs; ++i)
371 Glyphs[i].isLoaded = false;
372 Glyphs[i].glyph_page = 0;
373 Glyphs[i].source_rect = core::recti();
374 Glyphs[i].offset = core::vector2di();
375 Glyphs[i].advance = FT_Vector();
376 Glyphs[i].surface = 0;
377 Glyphs[i].parent = this;
380 // Cache the first 127 ascii characters.
381 u32 old_size = batch_load_size;
382 batch_load_size = 127;
383 getGlyphIndexByChar((uchar32_t)0);
384 batch_load_size = old_size;
389 CGUITTFont::~CGUITTFont()
391 // Delete the glyphs and glyph pages.
393 CGUITTAssistDelete::Delete(Glyphs);
396 // We aren't using this face anymore.
397 core::map<io::path, SGUITTFace*>::Node* n = c_faces.find(filename);
400 SGUITTFace* f = n->getValue();
402 // Drop our face. If this was the last face, the destructor will clean up.
404 c_faces.remove(filename);
406 // If there are no more faces referenced by FreeType, clean up.
407 if (c_faces.size() == 0)
409 FT_Done_FreeType(c_library);
410 c_libraryLoaded = false;
414 // Drop our driver now.
419 void CGUITTFont::reset_images()
421 // Delete the glyphs.
422 for (u32 i = 0; i != Glyphs.size(); ++i)
425 // Unload the glyph pages from video memory.
426 for (u32 i = 0; i != Glyph_Pages.size(); ++i)
427 delete Glyph_Pages[i];
430 // Always update the internal FreeType loading flags after resetting.
434 void CGUITTFont::update_glyph_pages() const
436 for (u32 i = 0; i != Glyph_Pages.size(); ++i)
438 if (Glyph_Pages[i]->dirty)
439 Glyph_Pages[i]->updateTexture();
443 CGUITTGlyphPage* CGUITTFont::getLastGlyphPage() const
445 CGUITTGlyphPage* page = 0;
446 if (Glyph_Pages.empty())
450 page = Glyph_Pages[getLastGlyphPageIndex()];
451 if (page->available_slots == 0)
457 CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8& pixel_mode)
459 CGUITTGlyphPage* page = 0;
462 io::path name("TTFontGlyphPage_");
463 name += tt_face->family_name;
465 name += tt_face->style_name;
469 name += Glyph_Pages.size(); // The newly created page will be at the end of the collection.
471 // Create the new page.
472 page = new CGUITTGlyphPage(Driver, name);
474 // Determine our maximum texture size.
475 // If we keep getting 0, set it to 1024x1024, as that number is pretty safe.
476 core::dimension2du max_texture_size = max_page_texture_size;
477 if (max_texture_size.Width == 0 || max_texture_size.Height == 0)
478 max_texture_size = Driver->getMaxTextureSize();
479 if (max_texture_size.Width == 0 || max_texture_size.Height == 0)
480 max_texture_size = core::dimension2du(1024, 1024);
482 // We want to try to put at least 144 glyphs on a single texture.
483 core::dimension2du page_texture_size;
484 if (size <= 21) page_texture_size = core::dimension2du(256, 256);
485 else if (size <= 42) page_texture_size = core::dimension2du(512, 512);
486 else if (size <= 84) page_texture_size = core::dimension2du(1024, 1024);
487 else if (size <= 168) page_texture_size = core::dimension2du(2048, 2048);
488 else page_texture_size = core::dimension2du(4096, 4096);
490 if (page_texture_size.Width > max_texture_size.Width || page_texture_size.Height > max_texture_size.Height)
491 page_texture_size = max_texture_size;
493 if (!page->createPageTexture(pixel_mode, page_texture_size))
494 // TODO: add error message?
499 // Determine the number of glyph slots on the page and add it to the list of pages.
500 page->available_slots = (page_texture_size.Width / size) * (page_texture_size.Height / size);
501 Glyph_Pages.push_back(page);
506 void CGUITTFont::setTransparency(const bool flag)
508 use_transparency = flag;
512 void CGUITTFont::setMonochrome(const bool flag)
514 use_monochrome = flag;
518 void CGUITTFont::setFontHinting(const bool enable, const bool enable_auto_hinting)
520 use_hinting = enable;
521 use_auto_hinting = enable_auto_hinting;
525 void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position, video::SColor color, bool hcenter, bool vcenter, const core::rect<s32>* clip)
530 // Clear the glyph pages of their render information.
531 for (u32 i = 0; i < Glyph_Pages.size(); ++i)
533 Glyph_Pages[i]->render_positions.clear();
534 Glyph_Pages[i]->render_source_rects.clear();
537 // Set up some variables.
538 core::dimension2d<s32> textDimension;
539 core::position2d<s32> offset = position.UpperLeftCorner;
541 // Determine offset positions.
542 if (hcenter || vcenter)
544 textDimension = getDimension(text.c_str());
547 offset.X = ((position.getWidth() - textDimension.Width) >> 1) + offset.X;
550 offset.Y = ((position.getHeight() - textDimension.Height) >> 1) + offset.Y;
553 // Convert to a unicode string.
554 core::ustring utext(text);
556 // Set up our render map.
557 core::map<u32, CGUITTGlyphPage*> Render_Map;
559 // Start parsing characters.
561 uchar32_t previousChar = 0;
562 core::ustring::const_iterator iter(utext);
563 while (!iter.atEnd())
565 uchar32_t currentChar = *iter;
566 n = getGlyphIndexByChar(currentChar);
567 bool visible = (Invisible.findFirst(currentChar) == -1);
568 if (n > 0 && visible)
570 bool lineBreak=false;
571 if (currentChar == L'\r') // Mac or Windows breaks
574 if (*(iter + 1) == (uchar32_t)'\n') // Windows line breaks.
575 currentChar = *(++iter);
577 else if (currentChar == (uchar32_t)'\n') // Unix breaks
585 offset.Y += font_metrics.ascender / 64;
586 offset.X = position.UpperLeftCorner.X;
589 offset.X += (position.getWidth() - textDimension.Width) >> 1;
594 // Calculate the glyph offset.
595 s32 offx = Glyphs[n-1].offset.X;
596 s32 offy = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y;
599 core::vector2di k = getKerning(currentChar, previousChar);
603 // Determine rendering information.
604 SGUITTGlyph& glyph = Glyphs[n-1];
605 CGUITTGlyphPage* const page = Glyph_Pages[glyph.glyph_page];
606 page->render_positions.push_back(core::position2di(offset.X + offx, offset.Y + offy));
607 page->render_source_rects.push_back(glyph.source_rect);
608 Render_Map.set(glyph.glyph_page, page);
610 offset.X += getWidthFromCharacter(currentChar);
612 previousChar = currentChar;
617 update_glyph_pages();
618 core::map<u32, CGUITTGlyphPage*>::Iterator j = Render_Map.getIterator();
621 core::map<u32, CGUITTGlyphPage*>::Node* n = j.getNode();
623 if (n == 0) continue;
625 CGUITTGlyphPage* page = n->getValue();
627 if (!use_transparency) color.color |= 0xff000000;
628 Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, color, true);
632 core::dimension2d<u32> CGUITTFont::getCharDimension(const wchar_t ch) const
634 return core::dimension2d<u32>(getWidthFromCharacter(ch), getHeightFromCharacter(ch));
637 core::dimension2d<u32> CGUITTFont::getDimension(const wchar_t* text) const
639 return getDimension(core::ustring(text));
642 core::dimension2d<u32> CGUITTFont::getDimension(const core::ustring& text) const
644 // Get the maximum font height. Unfortunately, we have to do this hack as
645 // Irrlicht will draw things wrong. In FreeType, the font size is the
646 // maximum size for a single glyph, but that glyph may hang "under" the
647 // draw line, increasing the total font height to beyond the set size.
648 // Irrlicht does not understand this concept when drawing fonts. Also, I
649 // add +1 to give it a 1 pixel blank border. This makes things like
650 // tooltips look nicer.
651 s32 test1 = getHeightFromCharacter((uchar32_t)'g') + 1;
652 s32 test2 = getHeightFromCharacter((uchar32_t)'j') + 1;
653 s32 test3 = getHeightFromCharacter((uchar32_t)'_') + 1;
654 s32 max_font_height = core::max_(test1, core::max_(test2, test3));
656 core::dimension2d<u32> text_dimension(0, max_font_height);
657 core::dimension2d<u32> line(0, max_font_height);
659 uchar32_t previousChar = 0;
660 core::ustring::const_iterator iter = text.begin();
661 for (; !iter.atEnd(); ++iter)
664 bool lineBreak = false;
665 if (p == '\r') // Mac or Windows line breaks.
668 if (*(iter + 1) == '\n')
674 else if (p == '\n') // Unix line breaks.
680 core::vector2di k = getKerning(p, previousChar);
684 // Check for linebreak.
688 text_dimension.Height += line.Height;
689 if (text_dimension.Width < line.Width)
690 text_dimension.Width = line.Width;
692 line.Height = max_font_height;
695 line.Width += getWidthFromCharacter(p);
697 if (text_dimension.Width < line.Width)
698 text_dimension.Width = line.Width;
700 return text_dimension;
703 inline u32 CGUITTFont::getWidthFromCharacter(wchar_t c) const
705 return getWidthFromCharacter((uchar32_t)c);
708 inline u32 CGUITTFont::getWidthFromCharacter(uchar32_t c) const
710 // Set the size of the face.
711 // This is because we cache faces and the face may have been set to a different size.
712 //FT_Set_Pixel_Sizes(tt_face, 0, size);
714 u32 n = getGlyphIndexByChar(c);
717 int w = Glyphs[n-1].advance.x / 64;
721 return (font_metrics.ascender / 64);
722 else return (font_metrics.ascender / 64) / 2;
725 inline u32 CGUITTFont::getHeightFromCharacter(wchar_t c) const
727 return getHeightFromCharacter((uchar32_t)c);
730 inline u32 CGUITTFont::getHeightFromCharacter(uchar32_t c) const
732 // Set the size of the face.
733 // This is because we cache faces and the face may have been set to a different size.
734 //FT_Set_Pixel_Sizes(tt_face, 0, size);
736 u32 n = getGlyphIndexByChar(c);
739 // Grab the true height of the character, taking into account underhanging glyphs.
740 s32 height = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y + Glyphs[n-1].source_rect.getHeight();
744 return (font_metrics.ascender / 64);
745 else return (font_metrics.ascender / 64) / 2;
748 u32 CGUITTFont::getGlyphIndexByChar(wchar_t c) const
750 return getGlyphIndexByChar((uchar32_t)c);
753 u32 CGUITTFont::getGlyphIndexByChar(uchar32_t c) const
756 u32 glyph = FT_Get_Char_Index(tt_face, c);
758 // Check for a valid glyph. If it is invalid, attempt to use the replacement character.
760 glyph = FT_Get_Char_Index(tt_face, core::unicode::UTF_REPLACEMENT_CHARACTER);
762 // If our glyph is already loaded, don't bother doing any batch loading code.
763 if (glyph != 0 && Glyphs[glyph - 1].isLoaded)
766 // Determine our batch loading positions.
767 u32 half_size = (batch_load_size / 2);
769 if (c > half_size) start_pos = c - half_size;
770 u32 end_pos = start_pos + batch_load_size;
772 // Load all our characters.
775 // Get the character we are going to load.
776 u32 char_index = FT_Get_Char_Index(tt_face, start_pos);
778 // If the glyph hasn't been loaded yet, do it now.
781 SGUITTGlyph& glyph = Glyphs[char_index - 1];
784 glyph.preload(char_index, tt_face, Driver, size, load_flags);
785 Glyph_Pages[glyph.glyph_page]->pushGlyphToBePaged(&glyph);
789 while (++start_pos < end_pos);
791 // Return our original character.
795 s32 CGUITTFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x) const
797 return getCharacterFromPos(core::ustring(text), pixel_x);
800 s32 CGUITTFont::getCharacterFromPos(const core::ustring& text, s32 pixel_x) const
806 uchar32_t previousChar = 0;
807 core::ustring::const_iterator iter = text.begin();
808 while (!iter.atEnd())
811 x += getWidthFromCharacter(c);
814 core::vector2di k = getKerning(c, previousChar);
828 void CGUITTFont::setKerningWidth(s32 kerning)
830 GlobalKerningWidth = kerning;
833 void CGUITTFont::setKerningHeight(s32 kerning)
835 GlobalKerningHeight = kerning;
838 s32 CGUITTFont::getKerningWidth(const wchar_t* thisLetter, const wchar_t* previousLetter) const
841 return GlobalKerningWidth;
842 if (thisLetter == 0 || previousLetter == 0)
845 return getKerningWidth((uchar32_t)*thisLetter, (uchar32_t)*previousLetter);
848 s32 CGUITTFont::getKerningWidth(const uchar32_t thisLetter, const uchar32_t previousLetter) const
850 // Return only the kerning width.
851 return getKerning(thisLetter, previousLetter).X;
854 s32 CGUITTFont::getKerningHeight() const
856 // FreeType 2 currently doesn't return any height kerning information.
857 return GlobalKerningHeight;
860 core::vector2di CGUITTFont::getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const
862 return getKerning((uchar32_t)thisLetter, (uchar32_t)previousLetter);
865 core::vector2di CGUITTFont::getKerning(const uchar32_t thisLetter, const uchar32_t previousLetter) const
867 if (tt_face == 0 || thisLetter == 0 || previousLetter == 0)
868 return core::vector2di();
870 // Set the size of the face.
871 // This is because we cache faces and the face may have been set to a different size.
872 FT_Set_Pixel_Sizes(tt_face, 0, size);
874 core::vector2di ret(GlobalKerningWidth, GlobalKerningHeight);
876 // If we don't have kerning, no point in continuing.
877 if (!FT_HAS_KERNING(tt_face))
880 // Get the kerning information.
882 FT_Get_Kerning(tt_face, getGlyphIndexByChar(previousLetter), getGlyphIndexByChar(thisLetter), FT_KERNING_DEFAULT, &v);
884 // If we have a scalable font, the return value will be in font points.
885 if (FT_IS_SCALABLE(tt_face))
887 // Font points, so divide by 64.
900 void CGUITTFont::setInvisibleCharacters(const wchar_t *s)
906 void CGUITTFont::setInvisibleCharacters(const core::ustring& s)
911 video::IImage* CGUITTFont::createTextureFromChar(const uchar32_t& ch)
913 u32 n = getGlyphIndexByChar(ch);
914 const SGUITTGlyph& glyph = Glyphs[n-1];
915 CGUITTGlyphPage* page = Glyph_Pages[glyph.glyph_page];
918 page->updateTexture();
920 video::ITexture* tex = page->texture;
922 // Acquire a read-only lock of the corresponding page texture.
923 #if IRRLICHT_VERSION_MAJOR==1 && IRRLICHT_VERSION_MINOR>=8
924 void* ptr = tex->lock(video::ETLM_READ_ONLY);
926 void* ptr = tex->lock(true);
929 video::ECOLOR_FORMAT format = tex->getColorFormat();
930 core::dimension2du tex_size = tex->getOriginalSize();
931 video::IImage* pageholder = Driver->createImageFromData(format, tex_size, ptr, true, false);
933 // Copy the image data out of the page texture.
934 core::dimension2du glyph_size(glyph.source_rect.getSize());
935 video::IImage* image = Driver->createImage(format, glyph_size);
936 pageholder->copyTo(image, core::position2di(0, 0), glyph.source_rect);
942 video::ITexture* CGUITTFont::getPageTextureByIndex(const u32& page_index) const
944 if (page_index < Glyph_Pages.size())
945 return Glyph_Pages[page_index]->texture;
950 void CGUITTFont::createSharedPlane()
955 | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
956 |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
960 using namespace core;
961 using namespace video;
962 using namespace scene;
963 S3DVertex vertices[4];
964 u16 indices[6] = {0,2,3,3,1,0};
965 vertices[0] = S3DVertex(vector3df(0,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,1));
966 vertices[1] = S3DVertex(vector3df(1,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,1));
967 vertices[2] = S3DVertex(vector3df(0, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,0));
968 vertices[3] = S3DVertex(vector3df(1, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,0));
970 SMeshBuffer* buf = new SMeshBuffer();
971 buf->append(vertices, 4, indices, 6);
973 shared_plane_.addMeshBuffer( buf );
975 shared_plane_ptr_ = &shared_plane_;
976 buf->drop(); //the addMeshBuffer method will grab it, so we can drop this ptr.
979 core::dimension2d<u32> CGUITTFont::getDimensionUntilEndOfLine(const wchar_t* p) const
982 for (const wchar_t* temp = p; temp && *temp != '\0' && *temp != L'\r' && *temp != L'\n'; ++temp )
985 return getDimension(s.c_str());
988 core::array<scene::ISceneNode*> CGUITTFont::addTextSceneNode(const wchar_t* text, scene::ISceneManager* smgr, scene::ISceneNode* parent, const video::SColor& color, bool center)
990 using namespace core;
991 using namespace video;
992 using namespace scene;
994 array<scene::ISceneNode*> container;
996 if (!Driver || !smgr) return container;
998 parent = smgr->addEmptySceneNode(smgr->getRootSceneNode(), -1);
999 // if you don't specify parent, then we add a empty node attached to the root node
1000 // this is generally undesirable.
1002 if (!shared_plane_ptr_) //this points to a static mesh that contains the plane
1003 createSharedPlane(); //if it's not initialized, we create one.
1005 dimension2d<s32> text_size(getDimension(text)); //convert from unsigned to signed.
1006 vector3df start_point(0, 0, 0), offset;
1009 Because we are considering adding texts into 3D world, all Y axis vectors are inverted.
1012 // There's currently no "vertical center" concept when you apply text scene node to the 3D world.
1015 offset.X = start_point.X = -text_size.Width / 2.f;
1016 offset.Y = start_point.Y = +text_size.Height/ 2.f;
1017 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text).Width) >> 1;
1020 // the default font material
1022 mat.setFlag(video::EMF_LIGHTING, true);
1023 mat.setFlag(video::EMF_ZWRITE_ENABLE, false);
1024 mat.setFlag(video::EMF_NORMALIZE_NORMALS, true);
1025 mat.ColorMaterial = video::ECM_NONE;
1026 mat.MaterialType = use_transparency ? video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_SOLID;
1027 mat.MaterialTypeParam = 0.01f;
1028 mat.DiffuseColor = color;
1030 wchar_t current_char = 0, previous_char = 0;
1033 array<u32> glyph_indices;
1037 current_char = *text;
1038 bool line_break=false;
1039 if (current_char == L'\r') // Mac or Windows breaks
1042 if (*(text + 1) == L'\n') // Windows line breaks.
1043 current_char = *(++text);
1045 else if (current_char == L'\n') // Unix breaks
1053 offset.Y -= tt_face->size->metrics.ascender / 64;
1054 offset.X = start_point.X;
1056 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text+1).Width) >> 1;
1061 n = getGlyphIndexByChar(current_char);
1064 glyph_indices.push_back( n );
1066 // Store glyph size and offset informations.
1067 SGUITTGlyph const& glyph = Glyphs[n-1];
1068 u32 texw = glyph.source_rect.getWidth();
1069 u32 texh = glyph.source_rect.getHeight();
1070 s32 offx = glyph.offset.X;
1071 s32 offy = (font_metrics.ascender / 64) - glyph.offset.Y;
1074 vector2di k = getKerning(current_char, previous_char);
1078 vector3df current_pos(offset.X + offx, offset.Y - offy, 0);
1079 dimension2d<u32> letter_size = dimension2d<u32>(texw, texh);
1081 // Now we copy planes corresponding to the letter size.
1082 IMeshManipulator* mani = smgr->getMeshManipulator();
1083 IMesh* meshcopy = mani->createMeshCopy(shared_plane_ptr_);
1084 #if IRRLICHT_VERSION_MAJOR==1 && IRRLICHT_VERSION_MINOR>=8
1085 mani->scale(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1));
1087 mani->scaleMesh(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1));
1090 ISceneNode* current_node = smgr->addMeshSceneNode(meshcopy, parent, -1, current_pos);
1093 current_node->getMaterial(0) = mat;
1094 current_node->setAutomaticCulling(EAC_OFF);
1095 current_node->setIsDebugObject(true); //so the picking won't have any effect on individual letter
1096 //current_node->setDebugDataVisible(EDS_BBOX); //de-comment this when debugging
1098 container.push_back(current_node);
1100 offset.X += getWidthFromCharacter(current_char);
1101 previous_char = current_char;
1106 update_glyph_pages();
1107 //only after we update the textures can we use the glyph page textures.
1109 for (u32 i = 0; i < glyph_indices.size(); ++i)
1111 u32 n = glyph_indices[i];
1112 SGUITTGlyph const& glyph = Glyphs[n-1];
1113 ITexture* current_tex = Glyph_Pages[glyph.glyph_page]->texture;
1114 f32 page_texture_size = (f32)current_tex->getSize().Width;
1115 //Now we calculate the UV position according to the texture size and the source rect.
1119 // | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
1120 // |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
1123 f32 u1 = glyph.source_rect.UpperLeftCorner.X / page_texture_size;
1124 f32 u2 = u1 + (glyph.source_rect.getWidth() / page_texture_size);
1125 f32 v1 = glyph.source_rect.UpperLeftCorner.Y / page_texture_size;
1126 f32 v2 = v1 + (glyph.source_rect.getHeight() / page_texture_size);
1128 //we can be quite sure that this is IMeshSceneNode, because we just added them in the above loop.
1129 IMeshSceneNode* node = static_cast<IMeshSceneNode*>(container[i]);
1131 S3DVertex* pv = static_cast<S3DVertex*>(node->getMesh()->getMeshBuffer(0)->getVertices());
1132 //pv[0].TCoords.Y = pv[1].TCoords.Y = (letter_size.Height - 1) / static_cast<f32>(letter_size.Height);
1133 //pv[1].TCoords.X = pv[3].TCoords.X = (letter_size.Width - 1) / static_cast<f32>(letter_size.Width);
1134 pv[0].TCoords = vector2df(u1, v2);
1135 pv[1].TCoords = vector2df(u2, v2);
1136 pv[2].TCoords = vector2df(u1, v1);
1137 pv[3].TCoords = vector2df(u2, v1);
1139 container[i]->getMaterial(0).setTexture(0, current_tex);
1145 } // end namespace gui
1146 } // end namespace irr