2 CGUITTFont FreeType class for Irrlicht
3 Copyright (c) 2009-2010 John Norman
4 Copyright (c) 2016 Nathanaƫl Courant
6 This software is provided 'as-is', without any express or implied
7 warranty. In no event will the authors be held liable for any
8 damages arising from the use of this software.
10 Permission is granted to anyone to use this software for any
11 purpose, including commercial applications, and to alter it and
12 redistribute it freely, subject to the following restrictions:
14 1. The origin of this software must not be misrepresented; you
15 must not claim that you wrote the original software. If you use
16 this software in a product, an acknowledgment in the product
17 documentation would be appreciated but is not required.
19 2. Altered source versions must be plainly marked as such, and
20 must not be misrepresented as being the original software.
22 3. This notice may not be removed or altered from any source
25 The original version of this class can be located at:
26 http://irrlicht.suckerfreegames.com/
29 john@suckerfreegames.com
34 #include "CGUITTFont.h"
41 // Manages the FT_Face cache.
42 struct SGUITTFace : public virtual irr::IReferenceCounted
44 SGUITTFace() : face_buffer(0), face_buffer_size(0)
46 memset((void*)&face, 0, sizeof(FT_Face));
57 FT_Long face_buffer_size;
61 FT_Library CGUITTFont::c_library;
62 core::map<io::path, SGUITTFace*> CGUITTFont::c_faces;
63 bool CGUITTFont::c_libraryLoaded = false;
64 scene::IMesh* CGUITTFont::shared_plane_ptr_ = 0;
65 scene::SMesh CGUITTFont::shared_plane_;
69 /** Checks that no dimension of the FT_BitMap object is negative. If either is
70 * negative, abort execution.
72 inline void checkFontBitmapSize(const FT_Bitmap &bits)
74 if ((s32)bits.rows < 0 || (s32)bits.width < 0) {
75 std::cout << "Insane font glyph size. File: "
76 << __FILE__ << " Line " << __LINE__
82 video::IImage* SGUITTGlyph::createGlyphImage(const FT_Bitmap& bits, video::IVideoDriver* driver) const
84 // Make sure our casts to s32 in the loops below will not cause problems
85 checkFontBitmapSize(bits);
87 // Determine what our texture size should be.
88 // Add 1 because textures are inclusive-exclusive.
89 core::dimension2du d(bits.width + 1, bits.rows + 1);
90 core::dimension2du texture_size;
91 //core::dimension2du texture_size(bits.width + 1, bits.rows + 1);
93 // Create and load our image now.
94 video::IImage* image = 0;
95 switch (bits.pixel_mode)
97 case FT_PIXEL_MODE_MONO:
99 // Create a blank image and fill it with transparent pixels.
100 texture_size = d.getOptimalSize(true, true);
101 image = driver->createImage(video::ECF_A1R5G5B5, texture_size);
102 image->fill(video::SColor(0, 255, 255, 255));
104 // Load the monochrome data in.
105 const u32 image_pitch = image->getPitch() / sizeof(u16);
106 u16* image_data = (u16*)image->getData();
107 u8* glyph_data = bits.buffer;
109 for (s32 y = 0; y < (s32)bits.rows; ++y)
111 u16* row = image_data;
112 for (s32 x = 0; x < (s32)bits.width; ++x)
114 // Monochrome bitmaps store 8 pixels per byte. The left-most pixel is the bit 0x80.
115 // So, we go through the data each bit at a time.
116 if ((glyph_data[y * bits.pitch + (x / 8)] & (0x80 >> (x % 8))) != 0)
120 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->getData();
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;
150 // TODO: error message?
156 void SGUITTGlyph::preload(u32 char_index, FT_Face face, video::IVideoDriver* driver, u32 font_size, const FT_Int32 loadFlags)
158 if (isLoaded) return;
160 // Set the size of the glyph.
161 FT_Set_Pixel_Sizes(face, 0, font_size);
163 // Attempt to load the glyph.
164 if (FT_Load_Glyph(face, char_index, loadFlags) != FT_Err_Ok)
165 // TODO: error message?
168 FT_GlyphSlot glyph = face->glyph;
169 FT_Bitmap bits = glyph->bitmap;
171 // Setup the glyph information here:
172 advance = glyph->advance;
173 offset = core::vector2di(glyph->bitmap_left, glyph->bitmap_top);
175 // Try to get the last page with available slots.
176 CGUITTGlyphPage* page = parent->getLastGlyphPage();
178 // If we need to make a new page, do that now.
181 page = parent->createGlyphPage(bits.pixel_mode);
183 // TODO: add error message?
187 glyph_page = parent->getLastGlyphPageIndex();
188 u32 texture_side_length = page->texture->getOriginalSize().Width;
189 core::vector2di page_position(
190 (page->used_slots % (texture_side_length / font_size)) * font_size,
191 (page->used_slots / (texture_side_length / font_size)) * font_size
193 source_rect.UpperLeftCorner = page_position;
194 source_rect.LowerRightCorner = core::vector2di(page_position.X + bits.width, page_position.Y + bits.rows);
198 --page->available_slots;
200 // We grab the glyph bitmap here so the data won't be removed when the next glyph is loaded.
201 surface = createGlyphImage(bits, driver);
203 // Set our glyph as loaded.
207 void SGUITTGlyph::unload()
217 //////////////////////
219 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)
221 if (!c_libraryLoaded)
223 if (FT_Init_FreeType(&c_library))
225 c_libraryLoaded = true;
228 CGUITTFont* font = new CGUITTFont(env);
229 bool ret = font->load(filename, size, antialias, transparency);
236 font->shadow_offset = shadow;
237 font->shadow_alpha = shadow_alpha;
242 CGUITTFont* CGUITTFont::createTTFont(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
244 if (!c_libraryLoaded)
246 if (FT_Init_FreeType(&c_library))
248 c_libraryLoaded = true;
251 CGUITTFont* font = new CGUITTFont(device->getGUIEnvironment());
252 font->Device = device;
253 bool ret = font->load(filename, size, antialias, transparency);
263 CGUITTFont* CGUITTFont::create(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
265 return CGUITTFont::createTTFont(env, filename, size, antialias, transparency);
268 CGUITTFont* CGUITTFont::create(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
270 return CGUITTFont::createTTFont(device, filename, size, antialias, transparency);
273 //////////////////////
276 CGUITTFont::CGUITTFont(IGUIEnvironment *env)
277 : use_monochrome(false), use_transparency(true), use_hinting(true), use_auto_hinting(true),
278 batch_load_size(1), Device(0), Environment(env), Driver(0), GlobalKerningWidth(0), GlobalKerningHeight(0)
281 setDebugName("CGUITTFont");
286 // don't grab environment, to avoid circular references
287 Driver = Environment->getVideoDriver();
293 setInvisibleCharacters(L" ");
295 // Glyphs aren't reference counted, so don't try to delete them when we free the array.
296 Glyphs.set_free_when_destroyed(false);
299 bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antialias, const bool transparency)
301 // Some sanity checks.
302 if (Environment == 0 || Driver == 0) return false;
303 if (size == 0) return false;
304 if (filename.size() == 0) return false;
306 io::IFileSystem* filesystem = Environment->getFileSystem();
307 irr::ILogger* logger = (Device != 0 ? Device->getLogger() : 0);
309 this->filename = filename;
311 // Update the font loading flags when the font is first loaded.
312 this->use_monochrome = !antialias;
313 this->use_transparency = transparency;
318 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);
321 SGUITTFace* face = 0;
322 core::map<io::path, SGUITTFace*>::Node* node = c_faces.find(filename);
325 face = new SGUITTFace();
326 c_faces.set(filename, face);
330 // Read in the file data.
331 io::IReadFile* file = filesystem->createAndOpenFile(filename);
334 if (logger) logger->log(L"CGUITTFont", L"Failed to open the file.", irr::ELL_INFORMATION);
336 c_faces.remove(filename);
341 face->face_buffer = new FT_Byte[file->getSize()];
342 file->read(face->face_buffer, file->getSize());
343 face->face_buffer_size = file->getSize();
347 if (FT_New_Memory_Face(c_library, face->face_buffer, face->face_buffer_size, 0, &face->face))
349 if (logger) logger->log(L"CGUITTFont", L"FT_New_Memory_Face failed.", irr::ELL_INFORMATION);
351 c_faces.remove(filename);
359 core::ustring converter(filename);
360 if (FT_New_Face(c_library, reinterpret_cast<const char*>(converter.toUTF8_s().c_str()), 0, &face->face))
362 if (logger) logger->log(L"CGUITTFont", L"FT_New_Face failed.", irr::ELL_INFORMATION);
364 c_faces.remove(filename);
373 // Using another instance of this face.
374 face = node->getValue();
379 tt_face = face->face;
381 // Store font metrics.
382 FT_Set_Pixel_Sizes(tt_face, size, 0);
383 font_metrics = tt_face->size->metrics;
385 // Allocate our glyphs.
387 Glyphs.reallocate(tt_face->num_glyphs);
388 Glyphs.set_used(tt_face->num_glyphs);
389 for (FT_Long i = 0; i < tt_face->num_glyphs; ++i)
391 Glyphs[i].isLoaded = false;
392 Glyphs[i].glyph_page = 0;
393 Glyphs[i].source_rect = core::recti();
394 Glyphs[i].offset = core::vector2di();
395 Glyphs[i].advance = FT_Vector();
396 Glyphs[i].surface = 0;
397 Glyphs[i].parent = this;
400 // Cache the first 127 ascii characters.
401 u32 old_size = batch_load_size;
402 batch_load_size = 127;
403 getGlyphIndexByChar((uchar32_t)0);
404 batch_load_size = old_size;
409 CGUITTFont::~CGUITTFont()
411 // Delete the glyphs and glyph pages.
413 CGUITTAssistDelete::Delete(Glyphs);
416 // We aren't using this face anymore.
417 core::map<io::path, SGUITTFace*>::Node* n = c_faces.find(filename);
420 SGUITTFace* f = n->getValue();
422 // Drop our face. If this was the last face, the destructor will clean up.
424 c_faces.remove(filename);
426 // If there are no more faces referenced by FreeType, clean up.
427 if (c_faces.size() == 0)
429 FT_Done_FreeType(c_library);
430 c_libraryLoaded = false;
434 // Drop our driver now.
439 void CGUITTFont::reset_images()
441 // Delete the glyphs.
442 for (u32 i = 0; i != Glyphs.size(); ++i)
445 // Unload the glyph pages from video memory.
446 for (u32 i = 0; i != Glyph_Pages.size(); ++i)
447 delete Glyph_Pages[i];
450 // Always update the internal FreeType loading flags after resetting.
454 void CGUITTFont::update_glyph_pages() const
456 for (u32 i = 0; i != Glyph_Pages.size(); ++i)
458 if (Glyph_Pages[i]->dirty)
459 Glyph_Pages[i]->updateTexture();
463 CGUITTGlyphPage* CGUITTFont::getLastGlyphPage() const
465 CGUITTGlyphPage* page = 0;
466 if (Glyph_Pages.empty())
470 page = Glyph_Pages[getLastGlyphPageIndex()];
471 if (page->available_slots == 0)
477 CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8& pixel_mode)
479 CGUITTGlyphPage* page = 0;
482 io::path name("TTFontGlyphPage_");
483 name += tt_face->family_name;
485 name += tt_face->style_name;
489 name += Glyph_Pages.size(); // The newly created page will be at the end of the collection.
491 // Create the new page.
492 page = new CGUITTGlyphPage(Driver, name);
494 // Determine our maximum texture size.
495 // If we keep getting 0, set it to 1024x1024, as that number is pretty safe.
496 core::dimension2du max_texture_size = max_page_texture_size;
497 if (max_texture_size.Width == 0 || max_texture_size.Height == 0)
498 max_texture_size = Driver->getMaxTextureSize();
499 if (max_texture_size.Width == 0 || max_texture_size.Height == 0)
500 max_texture_size = core::dimension2du(1024, 1024);
502 // We want to try to put at least 144 glyphs on a single texture.
503 core::dimension2du page_texture_size;
504 if (size <= 21) page_texture_size = core::dimension2du(256, 256);
505 else if (size <= 42) page_texture_size = core::dimension2du(512, 512);
506 else if (size <= 84) page_texture_size = core::dimension2du(1024, 1024);
507 else if (size <= 168) page_texture_size = core::dimension2du(2048, 2048);
508 else page_texture_size = core::dimension2du(4096, 4096);
510 if (page_texture_size.Width > max_texture_size.Width || page_texture_size.Height > max_texture_size.Height)
511 page_texture_size = max_texture_size;
513 if (!page->createPageTexture(pixel_mode, page_texture_size)) {
514 // TODO: add error message?
521 // Determine the number of glyph slots on the page and add it to the list of pages.
522 page->available_slots = (page_texture_size.Width / size) * (page_texture_size.Height / size);
523 Glyph_Pages.push_back(page);
528 void CGUITTFont::setTransparency(const bool flag)
530 use_transparency = flag;
534 void CGUITTFont::setMonochrome(const bool flag)
536 use_monochrome = flag;
540 void CGUITTFont::setFontHinting(const bool enable, const bool enable_auto_hinting)
542 use_hinting = enable;
543 use_auto_hinting = enable_auto_hinting;
547 void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position, video::SColor color, bool hcenter, bool vcenter, const core::rect<s32>* clip)
549 draw(EnrichedString(std::wstring(text.c_str()), color), position, color, hcenter, vcenter, clip);
552 void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& position, video::SColor color, bool hcenter, bool vcenter, const core::rect<s32>* clip)
554 std::vector<video::SColor> colors = text.getColors();
559 // Clear the glyph pages of their render information.
560 for (u32 i = 0; i < Glyph_Pages.size(); ++i)
562 Glyph_Pages[i]->render_positions.clear();
563 Glyph_Pages[i]->render_source_rects.clear();
566 // Set up some variables.
567 core::dimension2d<s32> textDimension;
568 core::position2d<s32> offset = position.UpperLeftCorner;
570 // Determine offset positions.
571 if (hcenter || vcenter)
573 textDimension = getDimension(text.c_str());
576 offset.X = ((position.getWidth() - textDimension.Width) >> 1) + offset.X;
579 offset.Y = ((position.getHeight() - textDimension.Height) >> 1) + offset.Y;
582 // Convert to a unicode string.
583 core::ustring utext = text.getString();
585 // Set up our render map.
586 core::map<u32, CGUITTGlyphPage*> Render_Map;
588 // Start parsing characters.
590 uchar32_t previousChar = 0;
591 core::ustring::const_iterator iter(utext);
592 std::vector<video::SColor> applied_colors;
593 while (!iter.atEnd())
595 uchar32_t currentChar = *iter;
596 n = getGlyphIndexByChar(currentChar);
597 bool visible = (Invisible.findFirst(currentChar) == -1);
598 bool lineBreak=false;
599 if (currentChar == L'\r') // Mac or Windows breaks
602 if (*(iter + 1) == (uchar32_t)'\n') // Windows line breaks.
603 currentChar = *(++iter);
605 else if (currentChar == (uchar32_t)'\n') // Unix breaks
613 offset.Y += font_metrics.height / 64;
614 offset.X = position.UpperLeftCorner.X;
617 offset.X += (position.getWidth() - textDimension.Width) >> 1;
622 if (n > 0 && visible)
624 // Calculate the glyph offset.
625 s32 offx = Glyphs[n-1].offset.X;
626 s32 offy = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y;
629 core::vector2di k = getKerning(currentChar, previousChar);
633 // Determine rendering information.
634 SGUITTGlyph& glyph = Glyphs[n-1];
635 CGUITTGlyphPage* const page = Glyph_Pages[glyph.glyph_page];
636 page->render_positions.push_back(core::position2di(offset.X + offx, offset.Y + offy));
637 page->render_source_rects.push_back(glyph.source_rect);
638 Render_Map.set(glyph.glyph_page, page);
639 u32 current_color = iter.getPos();
640 if (current_color < colors.size())
641 applied_colors.push_back(colors[current_color]);
643 offset.X += getWidthFromCharacter(currentChar);
645 previousChar = currentChar;
650 update_glyph_pages();
651 core::map<u32, CGUITTGlyphPage*>::Iterator j = Render_Map.getIterator();
654 core::map<u32, CGUITTGlyphPage*>::Node* n = j.getNode();
656 if (n == 0) continue;
658 CGUITTGlyphPage* page = n->getValue();
661 for (size_t i = 0; i < page->render_positions.size(); ++i)
662 page->render_positions[i] += core::vector2di(shadow_offset, shadow_offset);
663 Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, video::SColor(shadow_alpha,0,0,0), true);
664 for (size_t i = 0; i < page->render_positions.size(); ++i)
665 page->render_positions[i] -= core::vector2di(shadow_offset, shadow_offset);
667 for (size_t i = 0; i < page->render_positions.size(); ++i) {
668 irr::video::SColor col;
669 if (!applied_colors.empty()) {
670 col = applied_colors[i < applied_colors.size() ? i : 0];
672 col = irr::video::SColor(255, 255, 255, 255);
674 if (!use_transparency)
675 col.color |= 0xff000000;
676 Driver->draw2DImage(page->texture, page->render_positions[i], page->render_source_rects[i], clip, col, true);
681 core::dimension2d<u32> CGUITTFont::getCharDimension(const wchar_t ch) const
683 return core::dimension2d<u32>(getWidthFromCharacter(ch), getHeightFromCharacter(ch));
686 core::dimension2d<u32> CGUITTFont::getDimension(const wchar_t* text) const
688 return getDimension(core::ustring(text));
691 core::dimension2d<u32> CGUITTFont::getDimension(const core::ustring& text) const
693 // Get the maximum font height. Unfortunately, we have to do this hack as
694 // Irrlicht will draw things wrong. In FreeType, the font size is the
695 // maximum size for a single glyph, but that glyph may hang "under" the
696 // draw line, increasing the total font height to beyond the set size.
697 // Irrlicht does not understand this concept when drawing fonts. Also, I
698 // add +1 to give it a 1 pixel blank border. This makes things like
699 // tooltips look nicer.
700 s32 test1 = getHeightFromCharacter((uchar32_t)'g') + 1;
701 s32 test2 = getHeightFromCharacter((uchar32_t)'j') + 1;
702 s32 test3 = getHeightFromCharacter((uchar32_t)'_') + 1;
703 s32 max_font_height = core::max_(test1, core::max_(test2, test3));
705 core::dimension2d<u32> text_dimension(0, max_font_height);
706 core::dimension2d<u32> line(0, max_font_height);
708 uchar32_t previousChar = 0;
709 core::ustring::const_iterator iter = text.begin();
710 for (; !iter.atEnd(); ++iter)
713 bool lineBreak = false;
714 if (p == '\r') // Mac or Windows line breaks.
717 if (*(iter + 1) == '\n')
723 else if (p == '\n') // Unix line breaks.
729 core::vector2di k = getKerning(p, previousChar);
733 // Check for linebreak.
737 text_dimension.Height += line.Height;
738 if (text_dimension.Width < line.Width)
739 text_dimension.Width = line.Width;
741 line.Height = max_font_height;
744 line.Width += getWidthFromCharacter(p);
746 if (text_dimension.Width < line.Width)
747 text_dimension.Width = line.Width;
749 return text_dimension;
752 inline u32 CGUITTFont::getWidthFromCharacter(wchar_t c) const
754 return getWidthFromCharacter((uchar32_t)c);
757 inline u32 CGUITTFont::getWidthFromCharacter(uchar32_t c) const
759 // Set the size of the face.
760 // This is because we cache faces and the face may have been set to a different size.
761 //FT_Set_Pixel_Sizes(tt_face, 0, size);
763 u32 n = getGlyphIndexByChar(c);
766 int w = Glyphs[n-1].advance.x / 64;
770 return (font_metrics.ascender / 64);
771 else return (font_metrics.ascender / 64) / 2;
774 inline u32 CGUITTFont::getHeightFromCharacter(wchar_t c) const
776 return getHeightFromCharacter((uchar32_t)c);
779 inline u32 CGUITTFont::getHeightFromCharacter(uchar32_t c) const
781 // Set the size of the face.
782 // This is because we cache faces and the face may have been set to a different size.
783 //FT_Set_Pixel_Sizes(tt_face, 0, size);
785 u32 n = getGlyphIndexByChar(c);
788 // Grab the true height of the character, taking into account underhanging glyphs.
789 s32 height = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y + Glyphs[n-1].source_rect.getHeight();
793 return (font_metrics.ascender / 64);
794 else return (font_metrics.ascender / 64) / 2;
797 u32 CGUITTFont::getGlyphIndexByChar(wchar_t c) const
799 return getGlyphIndexByChar((uchar32_t)c);
802 u32 CGUITTFont::getGlyphIndexByChar(uchar32_t c) const
805 u32 glyph = FT_Get_Char_Index(tt_face, c);
807 // Check for a valid glyph. If it is invalid, attempt to use the replacement character.
809 glyph = FT_Get_Char_Index(tt_face, core::unicode::UTF_REPLACEMENT_CHARACTER);
811 // If our glyph is already loaded, don't bother doing any batch loading code.
812 if (glyph != 0 && Glyphs[glyph - 1].isLoaded)
815 // Determine our batch loading positions.
816 u32 half_size = (batch_load_size / 2);
818 if (c > half_size) start_pos = c - half_size;
819 u32 end_pos = start_pos + batch_load_size;
821 // Load all our characters.
824 // Get the character we are going to load.
825 u32 char_index = FT_Get_Char_Index(tt_face, start_pos);
827 // If the glyph hasn't been loaded yet, do it now.
830 SGUITTGlyph& glyph = Glyphs[char_index - 1];
833 glyph.preload(char_index, tt_face, Driver, size, load_flags);
834 Glyph_Pages[glyph.glyph_page]->pushGlyphToBePaged(&glyph);
838 while (++start_pos < end_pos);
840 // Return our original character.
844 s32 CGUITTFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x) const
846 return getCharacterFromPos(core::ustring(text), pixel_x);
849 s32 CGUITTFont::getCharacterFromPos(const core::ustring& text, s32 pixel_x) const
855 uchar32_t previousChar = 0;
856 core::ustring::const_iterator iter = text.begin();
857 while (!iter.atEnd())
860 x += getWidthFromCharacter(c);
863 core::vector2di k = getKerning(c, previousChar);
877 void CGUITTFont::setKerningWidth(s32 kerning)
879 GlobalKerningWidth = kerning;
882 void CGUITTFont::setKerningHeight(s32 kerning)
884 GlobalKerningHeight = kerning;
887 s32 CGUITTFont::getKerningWidth(const wchar_t* thisLetter, const wchar_t* previousLetter) const
890 return GlobalKerningWidth;
891 if (thisLetter == 0 || previousLetter == 0)
894 return getKerningWidth((uchar32_t)*thisLetter, (uchar32_t)*previousLetter);
897 s32 CGUITTFont::getKerningWidth(const uchar32_t thisLetter, const uchar32_t previousLetter) const
899 // Return only the kerning width.
900 return getKerning(thisLetter, previousLetter).X;
903 s32 CGUITTFont::getKerningHeight() const
905 // FreeType 2 currently doesn't return any height kerning information.
906 return GlobalKerningHeight;
909 core::vector2di CGUITTFont::getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const
911 return getKerning((uchar32_t)thisLetter, (uchar32_t)previousLetter);
914 core::vector2di CGUITTFont::getKerning(const uchar32_t thisLetter, const uchar32_t previousLetter) const
916 if (tt_face == 0 || thisLetter == 0 || previousLetter == 0)
917 return core::vector2di();
919 // Set the size of the face.
920 // This is because we cache faces and the face may have been set to a different size.
921 FT_Set_Pixel_Sizes(tt_face, 0, size);
923 core::vector2di ret(GlobalKerningWidth, GlobalKerningHeight);
925 // If we don't have kerning, no point in continuing.
926 if (!FT_HAS_KERNING(tt_face))
929 // Get the kerning information.
931 FT_Get_Kerning(tt_face, getGlyphIndexByChar(previousLetter), getGlyphIndexByChar(thisLetter), FT_KERNING_DEFAULT, &v);
933 // If we have a scalable font, the return value will be in font points.
934 if (FT_IS_SCALABLE(tt_face))
936 // Font points, so divide by 64.
949 void CGUITTFont::setInvisibleCharacters(const wchar_t *s)
955 void CGUITTFont::setInvisibleCharacters(const core::ustring& s)
960 video::IImage* CGUITTFont::createTextureFromChar(const uchar32_t& ch)
962 u32 n = getGlyphIndexByChar(ch);
963 const SGUITTGlyph& glyph = Glyphs[n-1];
964 CGUITTGlyphPage* page = Glyph_Pages[glyph.glyph_page];
967 page->updateTexture();
969 video::ITexture* tex = page->texture;
971 // Acquire a read-only lock of the corresponding page texture.
972 #if IRRLICHT_VERSION_MAJOR==1 && IRRLICHT_VERSION_MINOR>=8
973 void* ptr = tex->lock(video::ETLM_READ_ONLY);
975 void* ptr = tex->lock(true);
978 video::ECOLOR_FORMAT format = tex->getColorFormat();
979 core::dimension2du tex_size = tex->getOriginalSize();
980 video::IImage* pageholder = Driver->createImageFromData(format, tex_size, ptr, true, false);
982 // Copy the image data out of the page texture.
983 core::dimension2du glyph_size(glyph.source_rect.getSize());
984 video::IImage* image = Driver->createImage(format, glyph_size);
985 pageholder->copyTo(image, core::position2di(0, 0), glyph.source_rect);
991 video::ITexture* CGUITTFont::getPageTextureByIndex(const u32& page_index) const
993 if (page_index < Glyph_Pages.size())
994 return Glyph_Pages[page_index]->texture;
999 void CGUITTFont::createSharedPlane()
1004 | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
1005 |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
1009 using namespace core;
1010 using namespace video;
1011 using namespace scene;
1012 S3DVertex vertices[4];
1013 u16 indices[6] = {0,2,3,3,1,0};
1014 vertices[0] = S3DVertex(vector3df(0,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,1));
1015 vertices[1] = S3DVertex(vector3df(1,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,1));
1016 vertices[2] = S3DVertex(vector3df(0, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,0));
1017 vertices[3] = S3DVertex(vector3df(1, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,0));
1019 SMeshBuffer* buf = new SMeshBuffer();
1020 buf->append(vertices, 4, indices, 6);
1022 shared_plane_.addMeshBuffer( buf );
1024 shared_plane_ptr_ = &shared_plane_;
1025 buf->drop(); //the addMeshBuffer method will grab it, so we can drop this ptr.
1028 core::dimension2d<u32> CGUITTFont::getDimensionUntilEndOfLine(const wchar_t* p) const
1031 for (const wchar_t* temp = p; temp && *temp != '\0' && *temp != L'\r' && *temp != L'\n'; ++temp )
1034 return getDimension(s.c_str());
1037 core::array<scene::ISceneNode*> CGUITTFont::addTextSceneNode(const wchar_t* text, scene::ISceneManager* smgr, scene::ISceneNode* parent, const video::SColor& color, bool center)
1039 using namespace core;
1040 using namespace video;
1041 using namespace scene;
1043 array<scene::ISceneNode*> container;
1045 if (!Driver || !smgr) return container;
1047 parent = smgr->addEmptySceneNode(smgr->getRootSceneNode(), -1);
1048 // if you don't specify parent, then we add a empty node attached to the root node
1049 // this is generally undesirable.
1051 if (!shared_plane_ptr_) //this points to a static mesh that contains the plane
1052 createSharedPlane(); //if it's not initialized, we create one.
1054 dimension2d<s32> text_size(getDimension(text)); //convert from unsigned to signed.
1055 vector3df start_point(0, 0, 0), offset;
1058 Because we are considering adding texts into 3D world, all Y axis vectors are inverted.
1061 // There's currently no "vertical center" concept when you apply text scene node to the 3D world.
1064 offset.X = start_point.X = -text_size.Width / 2.f;
1065 offset.Y = start_point.Y = +text_size.Height/ 2.f;
1066 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text).Width) >> 1;
1069 // the default font material
1071 mat.setFlag(video::EMF_LIGHTING, true);
1072 mat.setFlag(video::EMF_ZWRITE_ENABLE, false);
1073 mat.setFlag(video::EMF_NORMALIZE_NORMALS, true);
1074 mat.ColorMaterial = video::ECM_NONE;
1075 mat.MaterialType = use_transparency ? video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_SOLID;
1076 mat.MaterialTypeParam = 0.01f;
1077 mat.DiffuseColor = color;
1079 wchar_t current_char = 0, previous_char = 0;
1082 array<u32> glyph_indices;
1086 current_char = *text;
1087 bool line_break=false;
1088 if (current_char == L'\r') // Mac or Windows breaks
1091 if (*(text + 1) == L'\n') // Windows line breaks.
1092 current_char = *(++text);
1094 else if (current_char == L'\n') // Unix breaks
1102 offset.Y -= tt_face->size->metrics.ascender / 64;
1103 offset.X = start_point.X;
1105 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text+1).Width) >> 1;
1110 n = getGlyphIndexByChar(current_char);
1113 glyph_indices.push_back( n );
1115 // Store glyph size and offset informations.
1116 SGUITTGlyph const& glyph = Glyphs[n-1];
1117 u32 texw = glyph.source_rect.getWidth();
1118 u32 texh = glyph.source_rect.getHeight();
1119 s32 offx = glyph.offset.X;
1120 s32 offy = (font_metrics.ascender / 64) - glyph.offset.Y;
1123 vector2di k = getKerning(current_char, previous_char);
1127 vector3df current_pos(offset.X + offx, offset.Y - offy, 0);
1128 dimension2d<u32> letter_size = dimension2d<u32>(texw, texh);
1130 // Now we copy planes corresponding to the letter size.
1131 IMeshManipulator* mani = smgr->getMeshManipulator();
1132 IMesh* meshcopy = mani->createMeshCopy(shared_plane_ptr_);
1133 #if IRRLICHT_VERSION_MAJOR==1 && IRRLICHT_VERSION_MINOR>=8
1134 mani->scale(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1));
1136 mani->scaleMesh(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1));
1139 ISceneNode* current_node = smgr->addMeshSceneNode(meshcopy, parent, -1, current_pos);
1142 current_node->getMaterial(0) = mat;
1143 current_node->setAutomaticCulling(EAC_OFF);
1144 current_node->setIsDebugObject(true); //so the picking won't have any effect on individual letter
1145 //current_node->setDebugDataVisible(EDS_BBOX); //de-comment this when debugging
1147 container.push_back(current_node);
1149 offset.X += getWidthFromCharacter(current_char);
1150 previous_char = current_char;
1155 update_glyph_pages();
1156 //only after we update the textures can we use the glyph page textures.
1158 for (u32 i = 0; i < glyph_indices.size(); ++i)
1160 u32 n = glyph_indices[i];
1161 SGUITTGlyph const& glyph = Glyphs[n-1];
1162 ITexture* current_tex = Glyph_Pages[glyph.glyph_page]->texture;
1163 f32 page_texture_size = (f32)current_tex->getSize().Width;
1164 //Now we calculate the UV position according to the texture size and the source rect.
1168 // | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
1169 // |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
1172 f32 u1 = glyph.source_rect.UpperLeftCorner.X / page_texture_size;
1173 f32 u2 = u1 + (glyph.source_rect.getWidth() / page_texture_size);
1174 f32 v1 = glyph.source_rect.UpperLeftCorner.Y / page_texture_size;
1175 f32 v2 = v1 + (glyph.source_rect.getHeight() / page_texture_size);
1177 //we can be quite sure that this is IMeshSceneNode, because we just added them in the above loop.
1178 IMeshSceneNode* node = static_cast<IMeshSceneNode*>(container[i]);
1180 S3DVertex* pv = static_cast<S3DVertex*>(node->getMesh()->getMeshBuffer(0)->getVertices());
1181 //pv[0].TCoords.Y = pv[1].TCoords.Y = (letter_size.Height - 1) / static_cast<f32>(letter_size.Height);
1182 //pv[1].TCoords.X = pv[3].TCoords.X = (letter_size.Width - 1) / static_cast<f32>(letter_size.Width);
1183 pv[0].TCoords = vector2df(u1, v2);
1184 pv[1].TCoords = vector2df(u2, v2);
1185 pv[2].TCoords = vector2df(u1, v1);
1186 pv[3].TCoords = vector2df(u2, v1);
1188 container[i]->getMaterial(0).setTexture(0, current_tex);
1194 } // end namespace gui
1195 } // end namespace irr