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, const u32 shadow)
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;
224 CGUITTFont* CGUITTFont::createTTFont(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
226 if (!c_libraryLoaded)
228 if (FT_Init_FreeType(&c_library))
230 c_libraryLoaded = true;
233 CGUITTFont* font = new CGUITTFont(device->getGUIEnvironment());
234 font->Device = device;
235 bool ret = font->load(filename, size, antialias, transparency);
245 CGUITTFont* CGUITTFont::create(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
247 return CGUITTFont::createTTFont(env, filename, size, antialias, transparency);
250 CGUITTFont* CGUITTFont::create(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
252 return CGUITTFont::createTTFont(device, filename, size, antialias, transparency);
255 //////////////////////
258 CGUITTFont::CGUITTFont(IGUIEnvironment *env)
259 : use_monochrome(false), use_transparency(true), use_hinting(true), use_auto_hinting(true),
260 batch_load_size(1), Device(0), Environment(env), Driver(0), GlobalKerningWidth(0), GlobalKerningHeight(0)
263 setDebugName("CGUITTFont");
268 // don't grab environment, to avoid circular references
269 Driver = Environment->getVideoDriver();
275 setInvisibleCharacters(L" ");
277 // Glyphs aren't reference counted, so don't try to delete them when we free the array.
278 Glyphs.set_free_when_destroyed(false);
281 bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antialias, const bool transparency)
283 // Some sanity checks.
284 if (Environment == 0 || Driver == 0) return false;
285 if (size == 0) return false;
286 if (filename.size() == 0) return false;
288 io::IFileSystem* filesystem = Environment->getFileSystem();
289 irr::ILogger* logger = (Device != 0 ? Device->getLogger() : 0);
291 this->filename = filename;
293 // Update the font loading flags when the font is first loaded.
294 this->use_monochrome = !antialias;
295 this->use_transparency = transparency;
300 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);
303 SGUITTFace* face = 0;
304 core::map<io::path, SGUITTFace*>::Node* node = c_faces.find(filename);
307 face = new SGUITTFace();
308 c_faces.set(filename, face);
312 // Read in the file data.
313 io::IReadFile* file = filesystem->createAndOpenFile(filename);
316 if (logger) logger->log(L"CGUITTFont", L"Failed to open the file.", irr::ELL_INFORMATION);
318 c_faces.remove(filename);
323 face->face_buffer = new FT_Byte[file->getSize()];
324 file->read(face->face_buffer, file->getSize());
325 face->face_buffer_size = file->getSize();
329 if (FT_New_Memory_Face(c_library, face->face_buffer, face->face_buffer_size, 0, &face->face))
331 if (logger) logger->log(L"CGUITTFont", L"FT_New_Memory_Face failed.", irr::ELL_INFORMATION);
333 c_faces.remove(filename);
341 core::ustring converter(filename);
342 if (FT_New_Face(c_library, reinterpret_cast<const char*>(converter.toUTF8_s().c_str()), 0, &face->face))
344 if (logger) logger->log(L"CGUITTFont", L"FT_New_Face failed.", irr::ELL_INFORMATION);
346 c_faces.remove(filename);
355 // Using another instance of this face.
356 face = node->getValue();
361 tt_face = face->face;
363 // Store font metrics.
364 FT_Set_Pixel_Sizes(tt_face, size, 0);
365 font_metrics = tt_face->size->metrics;
367 // Allocate our glyphs.
369 Glyphs.reallocate(tt_face->num_glyphs);
370 Glyphs.set_used(tt_face->num_glyphs);
371 for (FT_Long i = 0; i < tt_face->num_glyphs; ++i)
373 Glyphs[i].isLoaded = false;
374 Glyphs[i].glyph_page = 0;
375 Glyphs[i].source_rect = core::recti();
376 Glyphs[i].offset = core::vector2di();
377 Glyphs[i].advance = FT_Vector();
378 Glyphs[i].surface = 0;
379 Glyphs[i].parent = this;
382 // Cache the first 127 ascii characters.
383 u32 old_size = batch_load_size;
384 batch_load_size = 127;
385 getGlyphIndexByChar((uchar32_t)0);
386 batch_load_size = old_size;
391 CGUITTFont::~CGUITTFont()
393 // Delete the glyphs and glyph pages.
395 CGUITTAssistDelete::Delete(Glyphs);
398 // We aren't using this face anymore.
399 core::map<io::path, SGUITTFace*>::Node* n = c_faces.find(filename);
402 SGUITTFace* f = n->getValue();
404 // Drop our face. If this was the last face, the destructor will clean up.
406 c_faces.remove(filename);
408 // If there are no more faces referenced by FreeType, clean up.
409 if (c_faces.size() == 0)
411 FT_Done_FreeType(c_library);
412 c_libraryLoaded = false;
416 // Drop our driver now.
421 void CGUITTFont::reset_images()
423 // Delete the glyphs.
424 for (u32 i = 0; i != Glyphs.size(); ++i)
427 // Unload the glyph pages from video memory.
428 for (u32 i = 0; i != Glyph_Pages.size(); ++i)
429 delete Glyph_Pages[i];
432 // Always update the internal FreeType loading flags after resetting.
436 void CGUITTFont::update_glyph_pages() const
438 for (u32 i = 0; i != Glyph_Pages.size(); ++i)
440 if (Glyph_Pages[i]->dirty)
441 Glyph_Pages[i]->updateTexture();
445 CGUITTGlyphPage* CGUITTFont::getLastGlyphPage() const
447 CGUITTGlyphPage* page = 0;
448 if (Glyph_Pages.empty())
452 page = Glyph_Pages[getLastGlyphPageIndex()];
453 if (page->available_slots == 0)
459 CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8& pixel_mode)
461 CGUITTGlyphPage* page = 0;
464 io::path name("TTFontGlyphPage_");
465 name += tt_face->family_name;
467 name += tt_face->style_name;
471 name += Glyph_Pages.size(); // The newly created page will be at the end of the collection.
473 // Create the new page.
474 page = new CGUITTGlyphPage(Driver, name);
476 // Determine our maximum texture size.
477 // If we keep getting 0, set it to 1024x1024, as that number is pretty safe.
478 core::dimension2du max_texture_size = max_page_texture_size;
479 if (max_texture_size.Width == 0 || max_texture_size.Height == 0)
480 max_texture_size = Driver->getMaxTextureSize();
481 if (max_texture_size.Width == 0 || max_texture_size.Height == 0)
482 max_texture_size = core::dimension2du(1024, 1024);
484 // We want to try to put at least 144 glyphs on a single texture.
485 core::dimension2du page_texture_size;
486 if (size <= 21) page_texture_size = core::dimension2du(256, 256);
487 else if (size <= 42) page_texture_size = core::dimension2du(512, 512);
488 else if (size <= 84) page_texture_size = core::dimension2du(1024, 1024);
489 else if (size <= 168) page_texture_size = core::dimension2du(2048, 2048);
490 else page_texture_size = core::dimension2du(4096, 4096);
492 if (page_texture_size.Width > max_texture_size.Width || page_texture_size.Height > max_texture_size.Height)
493 page_texture_size = max_texture_size;
495 if (!page->createPageTexture(pixel_mode, page_texture_size))
496 // TODO: add error message?
501 // Determine the number of glyph slots on the page and add it to the list of pages.
502 page->available_slots = (page_texture_size.Width / size) * (page_texture_size.Height / size);
503 Glyph_Pages.push_back(page);
508 void CGUITTFont::setTransparency(const bool flag)
510 use_transparency = flag;
514 void CGUITTFont::setMonochrome(const bool flag)
516 use_monochrome = flag;
520 void CGUITTFont::setFontHinting(const bool enable, const bool enable_auto_hinting)
522 use_hinting = enable;
523 use_auto_hinting = enable_auto_hinting;
527 void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position, video::SColor color, bool hcenter, bool vcenter, const core::rect<s32>* clip)
532 // Clear the glyph pages of their render information.
533 for (u32 i = 0; i < Glyph_Pages.size(); ++i)
535 Glyph_Pages[i]->render_positions.clear();
536 Glyph_Pages[i]->render_source_rects.clear();
539 // Set up some variables.
540 core::dimension2d<s32> textDimension;
541 core::position2d<s32> offset = position.UpperLeftCorner;
543 // Determine offset positions.
544 if (hcenter || vcenter)
546 textDimension = getDimension(text.c_str());
549 offset.X = ((position.getWidth() - textDimension.Width) >> 1) + offset.X;
552 offset.Y = ((position.getHeight() - textDimension.Height) >> 1) + offset.Y;
555 // Convert to a unicode string.
556 core::ustring utext(text);
558 // Set up our render map.
559 core::map<u32, CGUITTGlyphPage*> Render_Map;
561 // Start parsing characters.
563 uchar32_t previousChar = 0;
564 core::ustring::const_iterator iter(utext);
565 while (!iter.atEnd())
567 uchar32_t currentChar = *iter;
568 n = getGlyphIndexByChar(currentChar);
569 bool visible = (Invisible.findFirst(currentChar) == -1);
570 if (n > 0 && visible)
572 bool lineBreak=false;
573 if (currentChar == L'\r') // Mac or Windows breaks
576 if (*(iter + 1) == (uchar32_t)'\n') // Windows line breaks.
577 currentChar = *(++iter);
579 else if (currentChar == (uchar32_t)'\n') // Unix breaks
587 offset.Y += font_metrics.ascender / 64;
588 offset.X = position.UpperLeftCorner.X;
591 offset.X += (position.getWidth() - textDimension.Width) >> 1;
596 // Calculate the glyph offset.
597 s32 offx = Glyphs[n-1].offset.X;
598 s32 offy = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y;
601 core::vector2di k = getKerning(currentChar, previousChar);
605 // Determine rendering information.
606 SGUITTGlyph& glyph = Glyphs[n-1];
607 CGUITTGlyphPage* const page = Glyph_Pages[glyph.glyph_page];
608 page->render_positions.push_back(core::position2di(offset.X + offx, offset.Y + offy));
609 page->render_source_rects.push_back(glyph.source_rect);
610 Render_Map.set(glyph.glyph_page, page);
612 offset.X += getWidthFromCharacter(currentChar);
614 previousChar = currentChar;
619 update_glyph_pages();
620 core::map<u32, CGUITTGlyphPage*>::Iterator j = Render_Map.getIterator();
623 core::map<u32, CGUITTGlyphPage*>::Node* n = j.getNode();
625 if (n == 0) continue;
627 CGUITTGlyphPage* page = n->getValue();
629 if (!use_transparency) color.color |= 0xff000000;
632 for (size_t i = 0; i < page->render_positions.size(); ++i)
633 page->render_positions[i] += core::vector2di(shadow_offset, shadow_offset);
634 Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, video::SColor(255, 0, 0, 0), true);
635 for (size_t i = 0; i < page->render_positions.size(); ++i)
636 page->render_positions[i] -= core::vector2di(shadow_offset, shadow_offset);
638 Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, color, true);
642 core::dimension2d<u32> CGUITTFont::getCharDimension(const wchar_t ch) const
644 return core::dimension2d<u32>(getWidthFromCharacter(ch), getHeightFromCharacter(ch));
647 core::dimension2d<u32> CGUITTFont::getDimension(const wchar_t* text) const
649 return getDimension(core::ustring(text));
652 core::dimension2d<u32> CGUITTFont::getDimension(const core::ustring& text) const
654 // Get the maximum font height. Unfortunately, we have to do this hack as
655 // Irrlicht will draw things wrong. In FreeType, the font size is the
656 // maximum size for a single glyph, but that glyph may hang "under" the
657 // draw line, increasing the total font height to beyond the set size.
658 // Irrlicht does not understand this concept when drawing fonts. Also, I
659 // add +1 to give it a 1 pixel blank border. This makes things like
660 // tooltips look nicer.
661 s32 test1 = getHeightFromCharacter((uchar32_t)'g') + 1;
662 s32 test2 = getHeightFromCharacter((uchar32_t)'j') + 1;
663 s32 test3 = getHeightFromCharacter((uchar32_t)'_') + 1;
664 s32 max_font_height = core::max_(test1, core::max_(test2, test3));
666 core::dimension2d<u32> text_dimension(0, max_font_height);
667 core::dimension2d<u32> line(0, max_font_height);
669 uchar32_t previousChar = 0;
670 core::ustring::const_iterator iter = text.begin();
671 for (; !iter.atEnd(); ++iter)
674 bool lineBreak = false;
675 if (p == '\r') // Mac or Windows line breaks.
678 if (*(iter + 1) == '\n')
684 else if (p == '\n') // Unix line breaks.
690 core::vector2di k = getKerning(p, previousChar);
694 // Check for linebreak.
698 text_dimension.Height += line.Height;
699 if (text_dimension.Width < line.Width)
700 text_dimension.Width = line.Width;
702 line.Height = max_font_height;
705 line.Width += getWidthFromCharacter(p);
707 if (text_dimension.Width < line.Width)
708 text_dimension.Width = line.Width;
710 return text_dimension;
713 inline u32 CGUITTFont::getWidthFromCharacter(wchar_t c) const
715 return getWidthFromCharacter((uchar32_t)c);
718 inline u32 CGUITTFont::getWidthFromCharacter(uchar32_t c) const
720 // Set the size of the face.
721 // This is because we cache faces and the face may have been set to a different size.
722 //FT_Set_Pixel_Sizes(tt_face, 0, size);
724 u32 n = getGlyphIndexByChar(c);
727 int w = Glyphs[n-1].advance.x / 64;
731 return (font_metrics.ascender / 64);
732 else return (font_metrics.ascender / 64) / 2;
735 inline u32 CGUITTFont::getHeightFromCharacter(wchar_t c) const
737 return getHeightFromCharacter((uchar32_t)c);
740 inline u32 CGUITTFont::getHeightFromCharacter(uchar32_t c) const
742 // Set the size of the face.
743 // This is because we cache faces and the face may have been set to a different size.
744 //FT_Set_Pixel_Sizes(tt_face, 0, size);
746 u32 n = getGlyphIndexByChar(c);
749 // Grab the true height of the character, taking into account underhanging glyphs.
750 s32 height = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y + Glyphs[n-1].source_rect.getHeight();
754 return (font_metrics.ascender / 64);
755 else return (font_metrics.ascender / 64) / 2;
758 u32 CGUITTFont::getGlyphIndexByChar(wchar_t c) const
760 return getGlyphIndexByChar((uchar32_t)c);
763 u32 CGUITTFont::getGlyphIndexByChar(uchar32_t c) const
766 u32 glyph = FT_Get_Char_Index(tt_face, c);
768 // Check for a valid glyph. If it is invalid, attempt to use the replacement character.
770 glyph = FT_Get_Char_Index(tt_face, core::unicode::UTF_REPLACEMENT_CHARACTER);
772 // If our glyph is already loaded, don't bother doing any batch loading code.
773 if (glyph != 0 && Glyphs[glyph - 1].isLoaded)
776 // Determine our batch loading positions.
777 u32 half_size = (batch_load_size / 2);
779 if (c > half_size) start_pos = c - half_size;
780 u32 end_pos = start_pos + batch_load_size;
782 // Load all our characters.
785 // Get the character we are going to load.
786 u32 char_index = FT_Get_Char_Index(tt_face, start_pos);
788 // If the glyph hasn't been loaded yet, do it now.
791 SGUITTGlyph& glyph = Glyphs[char_index - 1];
794 glyph.preload(char_index, tt_face, Driver, size, load_flags);
795 Glyph_Pages[glyph.glyph_page]->pushGlyphToBePaged(&glyph);
799 while (++start_pos < end_pos);
801 // Return our original character.
805 s32 CGUITTFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x) const
807 return getCharacterFromPos(core::ustring(text), pixel_x);
810 s32 CGUITTFont::getCharacterFromPos(const core::ustring& text, s32 pixel_x) const
816 uchar32_t previousChar = 0;
817 core::ustring::const_iterator iter = text.begin();
818 while (!iter.atEnd())
821 x += getWidthFromCharacter(c);
824 core::vector2di k = getKerning(c, previousChar);
838 void CGUITTFont::setKerningWidth(s32 kerning)
840 GlobalKerningWidth = kerning;
843 void CGUITTFont::setKerningHeight(s32 kerning)
845 GlobalKerningHeight = kerning;
848 s32 CGUITTFont::getKerningWidth(const wchar_t* thisLetter, const wchar_t* previousLetter) const
851 return GlobalKerningWidth;
852 if (thisLetter == 0 || previousLetter == 0)
855 return getKerningWidth((uchar32_t)*thisLetter, (uchar32_t)*previousLetter);
858 s32 CGUITTFont::getKerningWidth(const uchar32_t thisLetter, const uchar32_t previousLetter) const
860 // Return only the kerning width.
861 return getKerning(thisLetter, previousLetter).X;
864 s32 CGUITTFont::getKerningHeight() const
866 // FreeType 2 currently doesn't return any height kerning information.
867 return GlobalKerningHeight;
870 core::vector2di CGUITTFont::getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const
872 return getKerning((uchar32_t)thisLetter, (uchar32_t)previousLetter);
875 core::vector2di CGUITTFont::getKerning(const uchar32_t thisLetter, const uchar32_t previousLetter) const
877 if (tt_face == 0 || thisLetter == 0 || previousLetter == 0)
878 return core::vector2di();
880 // Set the size of the face.
881 // This is because we cache faces and the face may have been set to a different size.
882 FT_Set_Pixel_Sizes(tt_face, 0, size);
884 core::vector2di ret(GlobalKerningWidth, GlobalKerningHeight);
886 // If we don't have kerning, no point in continuing.
887 if (!FT_HAS_KERNING(tt_face))
890 // Get the kerning information.
892 FT_Get_Kerning(tt_face, getGlyphIndexByChar(previousLetter), getGlyphIndexByChar(thisLetter), FT_KERNING_DEFAULT, &v);
894 // If we have a scalable font, the return value will be in font points.
895 if (FT_IS_SCALABLE(tt_face))
897 // Font points, so divide by 64.
910 void CGUITTFont::setInvisibleCharacters(const wchar_t *s)
916 void CGUITTFont::setInvisibleCharacters(const core::ustring& s)
921 video::IImage* CGUITTFont::createTextureFromChar(const uchar32_t& ch)
923 u32 n = getGlyphIndexByChar(ch);
924 const SGUITTGlyph& glyph = Glyphs[n-1];
925 CGUITTGlyphPage* page = Glyph_Pages[glyph.glyph_page];
928 page->updateTexture();
930 video::ITexture* tex = page->texture;
932 // Acquire a read-only lock of the corresponding page texture.
933 #if IRRLICHT_VERSION_MAJOR==1 && IRRLICHT_VERSION_MINOR>=8
934 void* ptr = tex->lock(video::ETLM_READ_ONLY);
936 void* ptr = tex->lock(true);
939 video::ECOLOR_FORMAT format = tex->getColorFormat();
940 core::dimension2du tex_size = tex->getOriginalSize();
941 video::IImage* pageholder = Driver->createImageFromData(format, tex_size, ptr, true, false);
943 // Copy the image data out of the page texture.
944 core::dimension2du glyph_size(glyph.source_rect.getSize());
945 video::IImage* image = Driver->createImage(format, glyph_size);
946 pageholder->copyTo(image, core::position2di(0, 0), glyph.source_rect);
952 video::ITexture* CGUITTFont::getPageTextureByIndex(const u32& page_index) const
954 if (page_index < Glyph_Pages.size())
955 return Glyph_Pages[page_index]->texture;
960 void CGUITTFont::createSharedPlane()
965 | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
966 |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
970 using namespace core;
971 using namespace video;
972 using namespace scene;
973 S3DVertex vertices[4];
974 u16 indices[6] = {0,2,3,3,1,0};
975 vertices[0] = S3DVertex(vector3df(0,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,1));
976 vertices[1] = S3DVertex(vector3df(1,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,1));
977 vertices[2] = S3DVertex(vector3df(0, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,0));
978 vertices[3] = S3DVertex(vector3df(1, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,0));
980 SMeshBuffer* buf = new SMeshBuffer();
981 buf->append(vertices, 4, indices, 6);
983 shared_plane_.addMeshBuffer( buf );
985 shared_plane_ptr_ = &shared_plane_;
986 buf->drop(); //the addMeshBuffer method will grab it, so we can drop this ptr.
989 core::dimension2d<u32> CGUITTFont::getDimensionUntilEndOfLine(const wchar_t* p) const
992 for (const wchar_t* temp = p; temp && *temp != '\0' && *temp != L'\r' && *temp != L'\n'; ++temp )
995 return getDimension(s.c_str());
998 core::array<scene::ISceneNode*> CGUITTFont::addTextSceneNode(const wchar_t* text, scene::ISceneManager* smgr, scene::ISceneNode* parent, const video::SColor& color, bool center)
1000 using namespace core;
1001 using namespace video;
1002 using namespace scene;
1004 array<scene::ISceneNode*> container;
1006 if (!Driver || !smgr) return container;
1008 parent = smgr->addEmptySceneNode(smgr->getRootSceneNode(), -1);
1009 // if you don't specify parent, then we add a empty node attached to the root node
1010 // this is generally undesirable.
1012 if (!shared_plane_ptr_) //this points to a static mesh that contains the plane
1013 createSharedPlane(); //if it's not initialized, we create one.
1015 dimension2d<s32> text_size(getDimension(text)); //convert from unsigned to signed.
1016 vector3df start_point(0, 0, 0), offset;
1019 Because we are considering adding texts into 3D world, all Y axis vectors are inverted.
1022 // There's currently no "vertical center" concept when you apply text scene node to the 3D world.
1025 offset.X = start_point.X = -text_size.Width / 2.f;
1026 offset.Y = start_point.Y = +text_size.Height/ 2.f;
1027 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text).Width) >> 1;
1030 // the default font material
1032 mat.setFlag(video::EMF_LIGHTING, true);
1033 mat.setFlag(video::EMF_ZWRITE_ENABLE, false);
1034 mat.setFlag(video::EMF_NORMALIZE_NORMALS, true);
1035 mat.ColorMaterial = video::ECM_NONE;
1036 mat.MaterialType = use_transparency ? video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_SOLID;
1037 mat.MaterialTypeParam = 0.01f;
1038 mat.DiffuseColor = color;
1040 wchar_t current_char = 0, previous_char = 0;
1043 array<u32> glyph_indices;
1047 current_char = *text;
1048 bool line_break=false;
1049 if (current_char == L'\r') // Mac or Windows breaks
1052 if (*(text + 1) == L'\n') // Windows line breaks.
1053 current_char = *(++text);
1055 else if (current_char == L'\n') // Unix breaks
1063 offset.Y -= tt_face->size->metrics.ascender / 64;
1064 offset.X = start_point.X;
1066 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text+1).Width) >> 1;
1071 n = getGlyphIndexByChar(current_char);
1074 glyph_indices.push_back( n );
1076 // Store glyph size and offset informations.
1077 SGUITTGlyph const& glyph = Glyphs[n-1];
1078 u32 texw = glyph.source_rect.getWidth();
1079 u32 texh = glyph.source_rect.getHeight();
1080 s32 offx = glyph.offset.X;
1081 s32 offy = (font_metrics.ascender / 64) - glyph.offset.Y;
1084 vector2di k = getKerning(current_char, previous_char);
1088 vector3df current_pos(offset.X + offx, offset.Y - offy, 0);
1089 dimension2d<u32> letter_size = dimension2d<u32>(texw, texh);
1091 // Now we copy planes corresponding to the letter size.
1092 IMeshManipulator* mani = smgr->getMeshManipulator();
1093 IMesh* meshcopy = mani->createMeshCopy(shared_plane_ptr_);
1094 #if IRRLICHT_VERSION_MAJOR==1 && IRRLICHT_VERSION_MINOR>=8
1095 mani->scale(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1));
1097 mani->scaleMesh(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1));
1100 ISceneNode* current_node = smgr->addMeshSceneNode(meshcopy, parent, -1, current_pos);
1103 current_node->getMaterial(0) = mat;
1104 current_node->setAutomaticCulling(EAC_OFF);
1105 current_node->setIsDebugObject(true); //so the picking won't have any effect on individual letter
1106 //current_node->setDebugDataVisible(EDS_BBOX); //de-comment this when debugging
1108 container.push_back(current_node);
1110 offset.X += getWidthFromCharacter(current_char);
1111 previous_char = current_char;
1116 update_glyph_pages();
1117 //only after we update the textures can we use the glyph page textures.
1119 for (u32 i = 0; i < glyph_indices.size(); ++i)
1121 u32 n = glyph_indices[i];
1122 SGUITTGlyph const& glyph = Glyphs[n-1];
1123 ITexture* current_tex = Glyph_Pages[glyph.glyph_page]->texture;
1124 f32 page_texture_size = (f32)current_tex->getSize().Width;
1125 //Now we calculate the UV position according to the texture size and the source rect.
1129 // | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
1130 // |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
1133 f32 u1 = glyph.source_rect.UpperLeftCorner.X / page_texture_size;
1134 f32 u2 = u1 + (glyph.source_rect.getWidth() / page_texture_size);
1135 f32 v1 = glyph.source_rect.UpperLeftCorner.Y / page_texture_size;
1136 f32 v2 = v1 + (glyph.source_rect.getHeight() / page_texture_size);
1138 //we can be quite sure that this is IMeshSceneNode, because we just added them in the above loop.
1139 IMeshSceneNode* node = static_cast<IMeshSceneNode*>(container[i]);
1141 S3DVertex* pv = static_cast<S3DVertex*>(node->getMesh()->getMeshBuffer(0)->getVertices());
1142 //pv[0].TCoords.Y = pv[1].TCoords.Y = (letter_size.Height - 1) / static_cast<f32>(letter_size.Height);
1143 //pv[1].TCoords.X = pv[3].TCoords.X = (letter_size.Width - 1) / static_cast<f32>(letter_size.Width);
1144 pv[0].TCoords = vector2df(u1, v2);
1145 pv[1].TCoords = vector2df(u2, v2);
1146 pv[2].TCoords = vector2df(u1, v1);
1147 pv[3].TCoords = vector2df(u2, v1);
1149 container[i]->getMaterial(0).setTexture(0, current_tex);
1155 } // end namespace gui
1156 } // end namespace irr