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->lock();
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;
126 case FT_PIXEL_MODE_GRAY:
128 // Create our blank image.
129 texture_size = d.getOptimalSize(!driver->queryFeature(video::EVDF_TEXTURE_NPOT), !driver->queryFeature(video::EVDF_TEXTURE_NSQUARE), true, 0);
130 image = driver->createImage(video::ECF_A8R8G8B8, texture_size);
131 image->fill(video::SColor(0, 255, 255, 255));
133 // Load the grayscale data in.
134 const float gray_count = static_cast<float>(bits.num_grays);
135 const u32 image_pitch = image->getPitch() / sizeof(u32);
136 u32* image_data = (u32*)image->lock();
137 u8* glyph_data = bits.buffer;
138 for (s32 y = 0; y < (s32)bits.rows; ++y)
140 u8* row = glyph_data;
141 for (s32 x = 0; x < (s32)bits.width; ++x)
143 image_data[y * image_pitch + x] |= static_cast<u32>(255.0f * (static_cast<float>(*row++) / gray_count)) << 24;
144 //data[y * image_pitch + x] |= ((u32)(*bitsdata++) << 24);
146 glyph_data += bits.pitch;
152 // TODO: error message?
158 void SGUITTGlyph::preload(u32 char_index, FT_Face face, video::IVideoDriver* driver, u32 font_size, const FT_Int32 loadFlags)
160 if (isLoaded) return;
162 // Set the size of the glyph.
163 FT_Set_Pixel_Sizes(face, 0, font_size);
165 // Attempt to load the glyph.
166 if (FT_Load_Glyph(face, char_index, loadFlags) != FT_Err_Ok)
167 // TODO: error message?
170 FT_GlyphSlot glyph = face->glyph;
171 FT_Bitmap bits = glyph->bitmap;
173 // Setup the glyph information here:
174 advance = glyph->advance;
175 offset = core::vector2di(glyph->bitmap_left, glyph->bitmap_top);
177 // Try to get the last page with available slots.
178 CGUITTGlyphPage* page = parent->getLastGlyphPage();
180 // If we need to make a new page, do that now.
183 page = parent->createGlyphPage(bits.pixel_mode);
185 // TODO: add error message?
189 glyph_page = parent->getLastGlyphPageIndex();
190 u32 texture_side_length = page->texture->getOriginalSize().Width;
191 core::vector2di page_position(
192 (page->used_slots % (texture_side_length / font_size)) * font_size,
193 (page->used_slots / (texture_side_length / font_size)) * font_size
195 source_rect.UpperLeftCorner = page_position;
196 source_rect.LowerRightCorner = core::vector2di(page_position.X + bits.width, page_position.Y + bits.rows);
200 --page->available_slots;
202 // We grab the glyph bitmap here so the data won't be removed when the next glyph is loaded.
203 surface = createGlyphImage(bits, driver);
205 // Set our glyph as loaded.
209 void SGUITTGlyph::unload()
219 //////////////////////
221 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)
223 if (!c_libraryLoaded)
225 if (FT_Init_FreeType(&c_library))
227 c_libraryLoaded = true;
230 CGUITTFont* font = new CGUITTFont(env);
231 bool ret = font->load(filename, size, antialias, transparency);
238 font->shadow_offset = shadow;
239 font->shadow_alpha = shadow_alpha;
244 CGUITTFont* CGUITTFont::createTTFont(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
246 if (!c_libraryLoaded)
248 if (FT_Init_FreeType(&c_library))
250 c_libraryLoaded = true;
253 CGUITTFont* font = new CGUITTFont(device->getGUIEnvironment());
254 font->Device = device;
255 bool ret = font->load(filename, size, antialias, transparency);
265 CGUITTFont* CGUITTFont::create(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
267 return CGUITTFont::createTTFont(env, filename, size, antialias, transparency);
270 CGUITTFont* CGUITTFont::create(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
272 return CGUITTFont::createTTFont(device, filename, size, antialias, transparency);
275 //////////////////////
278 CGUITTFont::CGUITTFont(IGUIEnvironment *env)
279 : use_monochrome(false), use_transparency(true), use_hinting(true), use_auto_hinting(true),
280 batch_load_size(1), Device(0), Environment(env), Driver(0), GlobalKerningWidth(0), GlobalKerningHeight(0)
283 setDebugName("CGUITTFont");
288 // don't grab environment, to avoid circular references
289 Driver = Environment->getVideoDriver();
295 setInvisibleCharacters(L" ");
297 // Glyphs aren't reference counted, so don't try to delete them when we free the array.
298 Glyphs.set_free_when_destroyed(false);
301 bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antialias, const bool transparency)
303 // Some sanity checks.
304 if (Environment == 0 || Driver == 0) return false;
305 if (size == 0) return false;
306 if (filename.size() == 0) return false;
308 io::IFileSystem* filesystem = Environment->getFileSystem();
309 irr::ILogger* logger = (Device != 0 ? Device->getLogger() : 0);
311 this->filename = filename;
313 // Update the font loading flags when the font is first loaded.
314 this->use_monochrome = !antialias;
315 this->use_transparency = transparency;
320 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);
323 SGUITTFace* face = 0;
324 core::map<io::path, SGUITTFace*>::Node* node = c_faces.find(filename);
327 face = new SGUITTFace();
328 c_faces.set(filename, face);
332 // Read in the file data.
333 io::IReadFile* file = filesystem->createAndOpenFile(filename);
336 if (logger) logger->log(L"CGUITTFont", L"Failed to open the file.", irr::ELL_INFORMATION);
338 c_faces.remove(filename);
343 face->face_buffer = new FT_Byte[file->getSize()];
344 file->read(face->face_buffer, file->getSize());
345 face->face_buffer_size = file->getSize();
349 if (FT_New_Memory_Face(c_library, face->face_buffer, face->face_buffer_size, 0, &face->face))
351 if (logger) logger->log(L"CGUITTFont", L"FT_New_Memory_Face failed.", irr::ELL_INFORMATION);
353 c_faces.remove(filename);
361 core::ustring converter(filename);
362 if (FT_New_Face(c_library, reinterpret_cast<const char*>(converter.toUTF8_s().c_str()), 0, &face->face))
364 if (logger) logger->log(L"CGUITTFont", L"FT_New_Face failed.", irr::ELL_INFORMATION);
366 c_faces.remove(filename);
375 // Using another instance of this face.
376 face = node->getValue();
381 tt_face = face->face;
383 // Store font metrics.
384 FT_Set_Pixel_Sizes(tt_face, size, 0);
385 font_metrics = tt_face->size->metrics;
387 // Allocate our glyphs.
389 Glyphs.reallocate(tt_face->num_glyphs);
390 Glyphs.set_used(tt_face->num_glyphs);
391 for (FT_Long i = 0; i < tt_face->num_glyphs; ++i)
393 Glyphs[i].isLoaded = false;
394 Glyphs[i].glyph_page = 0;
395 Glyphs[i].source_rect = core::recti();
396 Glyphs[i].offset = core::vector2di();
397 Glyphs[i].advance = FT_Vector();
398 Glyphs[i].surface = 0;
399 Glyphs[i].parent = this;
402 // Cache the first 127 ascii characters.
403 u32 old_size = batch_load_size;
404 batch_load_size = 127;
405 getGlyphIndexByChar((uchar32_t)0);
406 batch_load_size = old_size;
411 CGUITTFont::~CGUITTFont()
413 // Delete the glyphs and glyph pages.
415 CGUITTAssistDelete::Delete(Glyphs);
418 // We aren't using this face anymore.
419 core::map<io::path, SGUITTFace*>::Node* n = c_faces.find(filename);
422 SGUITTFace* f = n->getValue();
424 // Drop our face. If this was the last face, the destructor will clean up.
426 c_faces.remove(filename);
428 // If there are no more faces referenced by FreeType, clean up.
429 if (c_faces.size() == 0)
431 FT_Done_FreeType(c_library);
432 c_libraryLoaded = false;
436 // Drop our driver now.
441 void CGUITTFont::reset_images()
443 // Delete the glyphs.
444 for (u32 i = 0; i != Glyphs.size(); ++i)
447 // Unload the glyph pages from video memory.
448 for (u32 i = 0; i != Glyph_Pages.size(); ++i)
449 delete Glyph_Pages[i];
452 // Always update the internal FreeType loading flags after resetting.
456 void CGUITTFont::update_glyph_pages() const
458 for (u32 i = 0; i != Glyph_Pages.size(); ++i)
460 if (Glyph_Pages[i]->dirty)
461 Glyph_Pages[i]->updateTexture();
465 CGUITTGlyphPage* CGUITTFont::getLastGlyphPage() const
467 CGUITTGlyphPage* page = 0;
468 if (Glyph_Pages.empty())
472 page = Glyph_Pages[getLastGlyphPageIndex()];
473 if (page->available_slots == 0)
479 CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8& pixel_mode)
481 CGUITTGlyphPage* page = 0;
484 io::path name("TTFontGlyphPage_");
485 name += tt_face->family_name;
487 name += tt_face->style_name;
491 name += Glyph_Pages.size(); // The newly created page will be at the end of the collection.
493 // Create the new page.
494 page = new CGUITTGlyphPage(Driver, name);
496 // Determine our maximum texture size.
497 // If we keep getting 0, set it to 1024x1024, as that number is pretty safe.
498 core::dimension2du max_texture_size = max_page_texture_size;
499 if (max_texture_size.Width == 0 || max_texture_size.Height == 0)
500 max_texture_size = Driver->getMaxTextureSize();
501 if (max_texture_size.Width == 0 || max_texture_size.Height == 0)
502 max_texture_size = core::dimension2du(1024, 1024);
504 // We want to try to put at least 144 glyphs on a single texture.
505 core::dimension2du page_texture_size;
506 if (size <= 21) page_texture_size = core::dimension2du(256, 256);
507 else if (size <= 42) page_texture_size = core::dimension2du(512, 512);
508 else if (size <= 84) page_texture_size = core::dimension2du(1024, 1024);
509 else if (size <= 168) page_texture_size = core::dimension2du(2048, 2048);
510 else page_texture_size = core::dimension2du(4096, 4096);
512 if (page_texture_size.Width > max_texture_size.Width || page_texture_size.Height > max_texture_size.Height)
513 page_texture_size = max_texture_size;
515 if (!page->createPageTexture(pixel_mode, page_texture_size))
516 // 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