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 (u32 y = 0; y < bits.rows; ++y)
92 u16* row = image_data;
93 for (u32 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 (u32 y = 0; y < bits.rows; ++y)
121 u8* row = glyph_data;
122 for (u32 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, const u32 shadow, const u32 shadow_alpha)
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);
219 font->shadow_offset = shadow;
220 font->shadow_alpha = shadow_alpha;
225 CGUITTFont* CGUITTFont::createTTFont(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
227 if (!c_libraryLoaded)
229 if (FT_Init_FreeType(&c_library))
231 c_libraryLoaded = true;
234 CGUITTFont* font = new CGUITTFont(device->getGUIEnvironment());
235 font->Device = device;
236 bool ret = font->load(filename, size, antialias, transparency);
246 CGUITTFont* CGUITTFont::create(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
248 return CGUITTFont::createTTFont(env, filename, size, antialias, transparency);
251 CGUITTFont* CGUITTFont::create(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
253 return CGUITTFont::createTTFont(device, filename, size, antialias, transparency);
256 //////////////////////
259 CGUITTFont::CGUITTFont(IGUIEnvironment *env)
260 : use_monochrome(false), use_transparency(true), use_hinting(true), use_auto_hinting(true),
261 batch_load_size(1), Device(0), Environment(env), Driver(0), GlobalKerningWidth(0), GlobalKerningHeight(0)
264 setDebugName("CGUITTFont");
269 // don't grab environment, to avoid circular references
270 Driver = Environment->getVideoDriver();
276 setInvisibleCharacters(L" ");
278 // Glyphs aren't reference counted, so don't try to delete them when we free the array.
279 Glyphs.set_free_when_destroyed(false);
282 bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antialias, const bool transparency)
284 // Some sanity checks.
285 if (Environment == 0 || Driver == 0) return false;
286 if (size == 0) return false;
287 if (filename.size() == 0) return false;
289 io::IFileSystem* filesystem = Environment->getFileSystem();
290 irr::ILogger* logger = (Device != 0 ? Device->getLogger() : 0);
292 this->filename = filename;
294 // Update the font loading flags when the font is first loaded.
295 this->use_monochrome = !antialias;
296 this->use_transparency = transparency;
301 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);
304 SGUITTFace* face = 0;
305 core::map<io::path, SGUITTFace*>::Node* node = c_faces.find(filename);
308 face = new SGUITTFace();
309 c_faces.set(filename, face);
313 // Read in the file data.
314 io::IReadFile* file = filesystem->createAndOpenFile(filename);
317 if (logger) logger->log(L"CGUITTFont", L"Failed to open the file.", irr::ELL_INFORMATION);
319 c_faces.remove(filename);
324 face->face_buffer = new FT_Byte[file->getSize()];
325 file->read(face->face_buffer, file->getSize());
326 face->face_buffer_size = file->getSize();
330 if (FT_New_Memory_Face(c_library, face->face_buffer, face->face_buffer_size, 0, &face->face))
332 if (logger) logger->log(L"CGUITTFont", L"FT_New_Memory_Face failed.", irr::ELL_INFORMATION);
334 c_faces.remove(filename);
342 core::ustring converter(filename);
343 if (FT_New_Face(c_library, reinterpret_cast<const char*>(converter.toUTF8_s().c_str()), 0, &face->face))
345 if (logger) logger->log(L"CGUITTFont", L"FT_New_Face failed.", irr::ELL_INFORMATION);
347 c_faces.remove(filename);
356 // Using another instance of this face.
357 face = node->getValue();
362 tt_face = face->face;
364 // Store font metrics.
365 FT_Set_Pixel_Sizes(tt_face, size, 0);
366 font_metrics = tt_face->size->metrics;
368 // Allocate our glyphs.
370 Glyphs.reallocate(tt_face->num_glyphs);
371 Glyphs.set_used(tt_face->num_glyphs);
372 for (FT_Long i = 0; i < tt_face->num_glyphs; ++i)
374 Glyphs[i].isLoaded = false;
375 Glyphs[i].glyph_page = 0;
376 Glyphs[i].source_rect = core::recti();
377 Glyphs[i].offset = core::vector2di();
378 Glyphs[i].advance = FT_Vector();
379 Glyphs[i].surface = 0;
380 Glyphs[i].parent = this;
383 // Cache the first 127 ascii characters.
384 u32 old_size = batch_load_size;
385 batch_load_size = 127;
386 getGlyphIndexByChar((uchar32_t)0);
387 batch_load_size = old_size;
392 CGUITTFont::~CGUITTFont()
394 // Delete the glyphs and glyph pages.
396 CGUITTAssistDelete::Delete(Glyphs);
399 // We aren't using this face anymore.
400 core::map<io::path, SGUITTFace*>::Node* n = c_faces.find(filename);
403 SGUITTFace* f = n->getValue();
405 // Drop our face. If this was the last face, the destructor will clean up.
407 c_faces.remove(filename);
409 // If there are no more faces referenced by FreeType, clean up.
410 if (c_faces.size() == 0)
412 FT_Done_FreeType(c_library);
413 c_libraryLoaded = false;
417 // Drop our driver now.
422 void CGUITTFont::reset_images()
424 // Delete the glyphs.
425 for (u32 i = 0; i != Glyphs.size(); ++i)
428 // Unload the glyph pages from video memory.
429 for (u32 i = 0; i != Glyph_Pages.size(); ++i)
430 delete Glyph_Pages[i];
433 // Always update the internal FreeType loading flags after resetting.
437 void CGUITTFont::update_glyph_pages() const
439 for (u32 i = 0; i != Glyph_Pages.size(); ++i)
441 if (Glyph_Pages[i]->dirty)
442 Glyph_Pages[i]->updateTexture();
446 CGUITTGlyphPage* CGUITTFont::getLastGlyphPage() const
448 CGUITTGlyphPage* page = 0;
449 if (Glyph_Pages.empty())
453 page = Glyph_Pages[getLastGlyphPageIndex()];
454 if (page->available_slots == 0)
460 CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8& pixel_mode)
462 CGUITTGlyphPage* page = 0;
465 io::path name("TTFontGlyphPage_");
466 name += tt_face->family_name;
468 name += tt_face->style_name;
472 name += Glyph_Pages.size(); // The newly created page will be at the end of the collection.
474 // Create the new page.
475 page = new CGUITTGlyphPage(Driver, name);
477 // Determine our maximum texture size.
478 // If we keep getting 0, set it to 1024x1024, as that number is pretty safe.
479 core::dimension2du max_texture_size = max_page_texture_size;
480 if (max_texture_size.Width == 0 || max_texture_size.Height == 0)
481 max_texture_size = Driver->getMaxTextureSize();
482 if (max_texture_size.Width == 0 || max_texture_size.Height == 0)
483 max_texture_size = core::dimension2du(1024, 1024);
485 // We want to try to put at least 144 glyphs on a single texture.
486 core::dimension2du page_texture_size;
487 if (size <= 21) page_texture_size = core::dimension2du(256, 256);
488 else if (size <= 42) page_texture_size = core::dimension2du(512, 512);
489 else if (size <= 84) page_texture_size = core::dimension2du(1024, 1024);
490 else if (size <= 168) page_texture_size = core::dimension2du(2048, 2048);
491 else page_texture_size = core::dimension2du(4096, 4096);
493 if (page_texture_size.Width > max_texture_size.Width || page_texture_size.Height > max_texture_size.Height)
494 page_texture_size = max_texture_size;
496 if (!page->createPageTexture(pixel_mode, page_texture_size))
497 // TODO: add error message?
502 // Determine the number of glyph slots on the page and add it to the list of pages.
503 page->available_slots = (page_texture_size.Width / size) * (page_texture_size.Height / size);
504 Glyph_Pages.push_back(page);
509 void CGUITTFont::setTransparency(const bool flag)
511 use_transparency = flag;
515 void CGUITTFont::setMonochrome(const bool flag)
517 use_monochrome = flag;
521 void CGUITTFont::setFontHinting(const bool enable, const bool enable_auto_hinting)
523 use_hinting = enable;
524 use_auto_hinting = enable_auto_hinting;
528 void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position, video::SColor color, bool hcenter, bool vcenter, const core::rect<s32>* clip)
533 // Clear the glyph pages of their render information.
534 for (u32 i = 0; i < Glyph_Pages.size(); ++i)
536 Glyph_Pages[i]->render_positions.clear();
537 Glyph_Pages[i]->render_source_rects.clear();
540 // Set up some variables.
541 core::dimension2d<s32> textDimension;
542 core::position2d<s32> offset = position.UpperLeftCorner;
544 // Determine offset positions.
545 if (hcenter || vcenter)
547 textDimension = getDimension(text.c_str());
550 offset.X = ((position.getWidth() - textDimension.Width) >> 1) + offset.X;
553 offset.Y = ((position.getHeight() - textDimension.Height) >> 1) + offset.Y;
556 // Convert to a unicode string.
557 core::ustring utext(text);
559 // Set up our render map.
560 core::map<u32, CGUITTGlyphPage*> Render_Map;
562 // Start parsing characters.
564 uchar32_t previousChar = 0;
565 core::ustring::const_iterator iter(utext);
566 while (!iter.atEnd())
568 uchar32_t currentChar = *iter;
569 n = getGlyphIndexByChar(currentChar);
570 bool visible = (Invisible.findFirst(currentChar) == -1);
571 bool lineBreak=false;
572 if (currentChar == L'\r') // Mac or Windows breaks
575 if (*(iter + 1) == (uchar32_t)'\n') // Windows line breaks.
576 currentChar = *(++iter);
578 else if (currentChar == (uchar32_t)'\n') // Unix breaks
586 offset.Y += font_metrics.height / 64;
587 offset.X = position.UpperLeftCorner.X;
590 offset.X += (position.getWidth() - textDimension.Width) >> 1;
595 if (n > 0 && visible)
597 // Calculate the glyph offset.
598 s32 offx = Glyphs[n-1].offset.X;
599 s32 offy = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y;
602 core::vector2di k = getKerning(currentChar, previousChar);
606 // Determine rendering information.
607 SGUITTGlyph& glyph = Glyphs[n-1];
608 CGUITTGlyphPage* const page = Glyph_Pages[glyph.glyph_page];
609 page->render_positions.push_back(core::position2di(offset.X + offx, offset.Y + offy));
610 page->render_source_rects.push_back(glyph.source_rect);
611 Render_Map.set(glyph.glyph_page, page);
613 offset.X += getWidthFromCharacter(currentChar);
615 previousChar = currentChar;
620 update_glyph_pages();
621 core::map<u32, CGUITTGlyphPage*>::Iterator j = Render_Map.getIterator();
624 core::map<u32, CGUITTGlyphPage*>::Node* n = j.getNode();
626 if (n == 0) continue;
628 CGUITTGlyphPage* page = n->getValue();
630 if (!use_transparency) color.color |= 0xff000000;
633 for (size_t i = 0; i < page->render_positions.size(); ++i)
634 page->render_positions[i] += core::vector2di(shadow_offset, shadow_offset);
635 Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, video::SColor(shadow_alpha,0,0,0), true);
636 for (size_t i = 0; i < page->render_positions.size(); ++i)
637 page->render_positions[i] -= core::vector2di(shadow_offset, shadow_offset);
639 Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, color, true);
643 core::dimension2d<u32> CGUITTFont::getCharDimension(const wchar_t ch) const
645 return core::dimension2d<u32>(getWidthFromCharacter(ch), getHeightFromCharacter(ch));
648 core::dimension2d<u32> CGUITTFont::getDimension(const wchar_t* text) const
650 return getDimension(core::ustring(text));
653 core::dimension2d<u32> CGUITTFont::getDimension(const core::ustring& text) const
655 // Get the maximum font height. Unfortunately, we have to do this hack as
656 // Irrlicht will draw things wrong. In FreeType, the font size is the
657 // maximum size for a single glyph, but that glyph may hang "under" the
658 // draw line, increasing the total font height to beyond the set size.
659 // Irrlicht does not understand this concept when drawing fonts. Also, I
660 // add +1 to give it a 1 pixel blank border. This makes things like
661 // tooltips look nicer.
662 s32 test1 = getHeightFromCharacter((uchar32_t)'g') + 1;
663 s32 test2 = getHeightFromCharacter((uchar32_t)'j') + 1;
664 s32 test3 = getHeightFromCharacter((uchar32_t)'_') + 1;
665 s32 max_font_height = core::max_(test1, core::max_(test2, test3));
667 core::dimension2d<u32> text_dimension(0, max_font_height);
668 core::dimension2d<u32> line(0, max_font_height);
670 uchar32_t previousChar = 0;
671 core::ustring::const_iterator iter = text.begin();
672 for (; !iter.atEnd(); ++iter)
675 bool lineBreak = false;
676 if (p == '\r') // Mac or Windows line breaks.
679 if (*(iter + 1) == '\n')
685 else if (p == '\n') // Unix line breaks.
691 core::vector2di k = getKerning(p, previousChar);
695 // Check for linebreak.
699 text_dimension.Height += line.Height;
700 if (text_dimension.Width < line.Width)
701 text_dimension.Width = line.Width;
703 line.Height = max_font_height;
706 line.Width += getWidthFromCharacter(p);
708 if (text_dimension.Width < line.Width)
709 text_dimension.Width = line.Width;
711 return text_dimension;
714 inline u32 CGUITTFont::getWidthFromCharacter(wchar_t c) const
716 return getWidthFromCharacter((uchar32_t)c);
719 inline u32 CGUITTFont::getWidthFromCharacter(uchar32_t c) const
721 // Set the size of the face.
722 // This is because we cache faces and the face may have been set to a different size.
723 //FT_Set_Pixel_Sizes(tt_face, 0, size);
725 u32 n = getGlyphIndexByChar(c);
728 int w = Glyphs[n-1].advance.x / 64;
732 return (font_metrics.ascender / 64);
733 else return (font_metrics.ascender / 64) / 2;
736 inline u32 CGUITTFont::getHeightFromCharacter(wchar_t c) const
738 return getHeightFromCharacter((uchar32_t)c);
741 inline u32 CGUITTFont::getHeightFromCharacter(uchar32_t c) const
743 // Set the size of the face.
744 // This is because we cache faces and the face may have been set to a different size.
745 //FT_Set_Pixel_Sizes(tt_face, 0, size);
747 u32 n = getGlyphIndexByChar(c);
750 // Grab the true height of the character, taking into account underhanging glyphs.
751 s32 height = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y + Glyphs[n-1].source_rect.getHeight();
755 return (font_metrics.ascender / 64);
756 else return (font_metrics.ascender / 64) / 2;
759 u32 CGUITTFont::getGlyphIndexByChar(wchar_t c) const
761 return getGlyphIndexByChar((uchar32_t)c);
764 u32 CGUITTFont::getGlyphIndexByChar(uchar32_t c) const
767 u32 glyph = FT_Get_Char_Index(tt_face, c);
769 // Check for a valid glyph. If it is invalid, attempt to use the replacement character.
771 glyph = FT_Get_Char_Index(tt_face, core::unicode::UTF_REPLACEMENT_CHARACTER);
773 // If our glyph is already loaded, don't bother doing any batch loading code.
774 if (glyph != 0 && Glyphs[glyph - 1].isLoaded)
777 // Determine our batch loading positions.
778 u32 half_size = (batch_load_size / 2);
780 if (c > half_size) start_pos = c - half_size;
781 u32 end_pos = start_pos + batch_load_size;
783 // Load all our characters.
786 // Get the character we are going to load.
787 u32 char_index = FT_Get_Char_Index(tt_face, start_pos);
789 // If the glyph hasn't been loaded yet, do it now.
792 SGUITTGlyph& glyph = Glyphs[char_index - 1];
795 glyph.preload(char_index, tt_face, Driver, size, load_flags);
796 Glyph_Pages[glyph.glyph_page]->pushGlyphToBePaged(&glyph);
800 while (++start_pos < end_pos);
802 // Return our original character.
806 s32 CGUITTFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x) const
808 return getCharacterFromPos(core::ustring(text), pixel_x);
811 s32 CGUITTFont::getCharacterFromPos(const core::ustring& text, s32 pixel_x) const
817 uchar32_t previousChar = 0;
818 core::ustring::const_iterator iter = text.begin();
819 while (!iter.atEnd())
822 x += getWidthFromCharacter(c);
825 core::vector2di k = getKerning(c, previousChar);
839 void CGUITTFont::setKerningWidth(s32 kerning)
841 GlobalKerningWidth = kerning;
844 void CGUITTFont::setKerningHeight(s32 kerning)
846 GlobalKerningHeight = kerning;
849 s32 CGUITTFont::getKerningWidth(const wchar_t* thisLetter, const wchar_t* previousLetter) const
852 return GlobalKerningWidth;
853 if (thisLetter == 0 || previousLetter == 0)
856 return getKerningWidth((uchar32_t)*thisLetter, (uchar32_t)*previousLetter);
859 s32 CGUITTFont::getKerningWidth(const uchar32_t thisLetter, const uchar32_t previousLetter) const
861 // Return only the kerning width.
862 return getKerning(thisLetter, previousLetter).X;
865 s32 CGUITTFont::getKerningHeight() const
867 // FreeType 2 currently doesn't return any height kerning information.
868 return GlobalKerningHeight;
871 core::vector2di CGUITTFont::getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const
873 return getKerning((uchar32_t)thisLetter, (uchar32_t)previousLetter);
876 core::vector2di CGUITTFont::getKerning(const uchar32_t thisLetter, const uchar32_t previousLetter) const
878 if (tt_face == 0 || thisLetter == 0 || previousLetter == 0)
879 return core::vector2di();
881 // Set the size of the face.
882 // This is because we cache faces and the face may have been set to a different size.
883 FT_Set_Pixel_Sizes(tt_face, 0, size);
885 core::vector2di ret(GlobalKerningWidth, GlobalKerningHeight);
887 // If we don't have kerning, no point in continuing.
888 if (!FT_HAS_KERNING(tt_face))
891 // Get the kerning information.
893 FT_Get_Kerning(tt_face, getGlyphIndexByChar(previousLetter), getGlyphIndexByChar(thisLetter), FT_KERNING_DEFAULT, &v);
895 // If we have a scalable font, the return value will be in font points.
896 if (FT_IS_SCALABLE(tt_face))
898 // Font points, so divide by 64.
911 void CGUITTFont::setInvisibleCharacters(const wchar_t *s)
917 void CGUITTFont::setInvisibleCharacters(const core::ustring& s)
922 video::IImage* CGUITTFont::createTextureFromChar(const uchar32_t& ch)
924 u32 n = getGlyphIndexByChar(ch);
925 const SGUITTGlyph& glyph = Glyphs[n-1];
926 CGUITTGlyphPage* page = Glyph_Pages[glyph.glyph_page];
929 page->updateTexture();
931 video::ITexture* tex = page->texture;
933 // Acquire a read-only lock of the corresponding page texture.
934 #if IRRLICHT_VERSION_MAJOR==1 && IRRLICHT_VERSION_MINOR>=8
935 void* ptr = tex->lock(video::ETLM_READ_ONLY);
937 void* ptr = tex->lock(true);
940 video::ECOLOR_FORMAT format = tex->getColorFormat();
941 core::dimension2du tex_size = tex->getOriginalSize();
942 video::IImage* pageholder = Driver->createImageFromData(format, tex_size, ptr, true, false);
944 // Copy the image data out of the page texture.
945 core::dimension2du glyph_size(glyph.source_rect.getSize());
946 video::IImage* image = Driver->createImage(format, glyph_size);
947 pageholder->copyTo(image, core::position2di(0, 0), glyph.source_rect);
953 video::ITexture* CGUITTFont::getPageTextureByIndex(const u32& page_index) const
955 if (page_index < Glyph_Pages.size())
956 return Glyph_Pages[page_index]->texture;
961 void CGUITTFont::createSharedPlane()
966 | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
967 |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
971 using namespace core;
972 using namespace video;
973 using namespace scene;
974 S3DVertex vertices[4];
975 u16 indices[6] = {0,2,3,3,1,0};
976 vertices[0] = S3DVertex(vector3df(0,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,1));
977 vertices[1] = S3DVertex(vector3df(1,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,1));
978 vertices[2] = S3DVertex(vector3df(0, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,0));
979 vertices[3] = S3DVertex(vector3df(1, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,0));
981 SMeshBuffer* buf = new SMeshBuffer();
982 buf->append(vertices, 4, indices, 6);
984 shared_plane_.addMeshBuffer( buf );
986 shared_plane_ptr_ = &shared_plane_;
987 buf->drop(); //the addMeshBuffer method will grab it, so we can drop this ptr.
990 core::dimension2d<u32> CGUITTFont::getDimensionUntilEndOfLine(const wchar_t* p) const
993 for (const wchar_t* temp = p; temp && *temp != '\0' && *temp != L'\r' && *temp != L'\n'; ++temp )
996 return getDimension(s.c_str());
999 core::array<scene::ISceneNode*> CGUITTFont::addTextSceneNode(const wchar_t* text, scene::ISceneManager* smgr, scene::ISceneNode* parent, const video::SColor& color, bool center)
1001 using namespace core;
1002 using namespace video;
1003 using namespace scene;
1005 array<scene::ISceneNode*> container;
1007 if (!Driver || !smgr) return container;
1009 parent = smgr->addEmptySceneNode(smgr->getRootSceneNode(), -1);
1010 // if you don't specify parent, then we add a empty node attached to the root node
1011 // this is generally undesirable.
1013 if (!shared_plane_ptr_) //this points to a static mesh that contains the plane
1014 createSharedPlane(); //if it's not initialized, we create one.
1016 dimension2d<s32> text_size(getDimension(text)); //convert from unsigned to signed.
1017 vector3df start_point(0, 0, 0), offset;
1020 Because we are considering adding texts into 3D world, all Y axis vectors are inverted.
1023 // There's currently no "vertical center" concept when you apply text scene node to the 3D world.
1026 offset.X = start_point.X = -text_size.Width / 2.f;
1027 offset.Y = start_point.Y = +text_size.Height/ 2.f;
1028 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text).Width) >> 1;
1031 // the default font material
1033 mat.setFlag(video::EMF_LIGHTING, true);
1034 mat.setFlag(video::EMF_ZWRITE_ENABLE, false);
1035 mat.setFlag(video::EMF_NORMALIZE_NORMALS, true);
1036 mat.ColorMaterial = video::ECM_NONE;
1037 mat.MaterialType = use_transparency ? video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_SOLID;
1038 mat.MaterialTypeParam = 0.01f;
1039 mat.DiffuseColor = color;
1041 wchar_t current_char = 0, previous_char = 0;
1044 array<u32> glyph_indices;
1048 current_char = *text;
1049 bool line_break=false;
1050 if (current_char == L'\r') // Mac or Windows breaks
1053 if (*(text + 1) == L'\n') // Windows line breaks.
1054 current_char = *(++text);
1056 else if (current_char == L'\n') // Unix breaks
1064 offset.Y -= tt_face->size->metrics.ascender / 64;
1065 offset.X = start_point.X;
1067 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text+1).Width) >> 1;
1072 n = getGlyphIndexByChar(current_char);
1075 glyph_indices.push_back( n );
1077 // Store glyph size and offset informations.
1078 SGUITTGlyph const& glyph = Glyphs[n-1];
1079 u32 texw = glyph.source_rect.getWidth();
1080 u32 texh = glyph.source_rect.getHeight();
1081 s32 offx = glyph.offset.X;
1082 s32 offy = (font_metrics.ascender / 64) - glyph.offset.Y;
1085 vector2di k = getKerning(current_char, previous_char);
1089 vector3df current_pos(offset.X + offx, offset.Y - offy, 0);
1090 dimension2d<u32> letter_size = dimension2d<u32>(texw, texh);
1092 // Now we copy planes corresponding to the letter size.
1093 IMeshManipulator* mani = smgr->getMeshManipulator();
1094 IMesh* meshcopy = mani->createMeshCopy(shared_plane_ptr_);
1095 #if IRRLICHT_VERSION_MAJOR==1 && IRRLICHT_VERSION_MINOR>=8
1096 mani->scale(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1));
1098 mani->scaleMesh(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1));
1101 ISceneNode* current_node = smgr->addMeshSceneNode(meshcopy, parent, -1, current_pos);
1104 current_node->getMaterial(0) = mat;
1105 current_node->setAutomaticCulling(EAC_OFF);
1106 current_node->setIsDebugObject(true); //so the picking won't have any effect on individual letter
1107 //current_node->setDebugDataVisible(EDS_BBOX); //de-comment this when debugging
1109 container.push_back(current_node);
1111 offset.X += getWidthFromCharacter(current_char);
1112 previous_char = current_char;
1117 update_glyph_pages();
1118 //only after we update the textures can we use the glyph page textures.
1120 for (u32 i = 0; i < glyph_indices.size(); ++i)
1122 u32 n = glyph_indices[i];
1123 SGUITTGlyph const& glyph = Glyphs[n-1];
1124 ITexture* current_tex = Glyph_Pages[glyph.glyph_page]->texture;
1125 f32 page_texture_size = (f32)current_tex->getSize().Width;
1126 //Now we calculate the UV position according to the texture size and the source rect.
1130 // | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
1131 // |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
1134 f32 u1 = glyph.source_rect.UpperLeftCorner.X / page_texture_size;
1135 f32 u2 = u1 + (glyph.source_rect.getWidth() / page_texture_size);
1136 f32 v1 = glyph.source_rect.UpperLeftCorner.Y / page_texture_size;
1137 f32 v2 = v1 + (glyph.source_rect.getHeight() / page_texture_size);
1139 //we can be quite sure that this is IMeshSceneNode, because we just added them in the above loop.
1140 IMeshSceneNode* node = static_cast<IMeshSceneNode*>(container[i]);
1142 S3DVertex* pv = static_cast<S3DVertex*>(node->getMesh()->getMeshBuffer(0)->getVertices());
1143 //pv[0].TCoords.Y = pv[1].TCoords.Y = (letter_size.Height - 1) / static_cast<f32>(letter_size.Height);
1144 //pv[1].TCoords.X = pv[3].TCoords.X = (letter_size.Width - 1) / static_cast<f32>(letter_size.Width);
1145 pv[0].TCoords = vector2df(u1, v2);
1146 pv[1].TCoords = vector2df(u2, v2);
1147 pv[2].TCoords = vector2df(u1, v1);
1148 pv[3].TCoords = vector2df(u2, v1);
1150 container[i]->getMaterial(0).setTexture(0, current_tex);
1156 } // end namespace gui
1157 } // end namespace irr