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?
523 // Determine the number of glyph slots on the page and add it to the list of pages.
524 page->available_slots = (page_texture_size.Width / size) * (page_texture_size.Height / size);
525 Glyph_Pages.push_back(page);
530 void CGUITTFont::setTransparency(const bool flag)
532 use_transparency = flag;
536 void CGUITTFont::setMonochrome(const bool flag)
538 use_monochrome = flag;
542 void CGUITTFont::setFontHinting(const bool enable, const bool enable_auto_hinting)
544 use_hinting = enable;
545 use_auto_hinting = enable_auto_hinting;
549 void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position, video::SColor color, bool hcenter, bool vcenter, const core::rect<s32>* clip)
551 draw(EnrichedString(std::wstring(text.c_str()), color), position, color, hcenter, vcenter, clip);
554 void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& position, video::SColor color, bool hcenter, bool vcenter, const core::rect<s32>* clip)
556 std::vector<video::SColor> colors = text.getColors();
561 // Clear the glyph pages of their render information.
562 for (u32 i = 0; i < Glyph_Pages.size(); ++i)
564 Glyph_Pages[i]->render_positions.clear();
565 Glyph_Pages[i]->render_source_rects.clear();
568 // Set up some variables.
569 core::dimension2d<s32> textDimension;
570 core::position2d<s32> offset = position.UpperLeftCorner;
572 // Determine offset positions.
573 if (hcenter || vcenter)
575 textDimension = getDimension(text.c_str());
578 offset.X = ((position.getWidth() - textDimension.Width) >> 1) + offset.X;
581 offset.Y = ((position.getHeight() - textDimension.Height) >> 1) + offset.Y;
584 // Convert to a unicode string.
585 core::ustring utext = text.getString();
587 // Set up our render map.
588 core::map<u32, CGUITTGlyphPage*> Render_Map;
590 // Start parsing characters.
592 uchar32_t previousChar = 0;
593 core::ustring::const_iterator iter(utext);
594 std::vector<video::SColor> applied_colors;
595 while (!iter.atEnd())
597 uchar32_t currentChar = *iter;
598 n = getGlyphIndexByChar(currentChar);
599 bool visible = (Invisible.findFirst(currentChar) == -1);
600 bool lineBreak=false;
601 if (currentChar == L'\r') // Mac or Windows breaks
604 if (*(iter + 1) == (uchar32_t)'\n') // Windows line breaks.
605 currentChar = *(++iter);
607 else if (currentChar == (uchar32_t)'\n') // Unix breaks
615 offset.Y += font_metrics.height / 64;
616 offset.X = position.UpperLeftCorner.X;
619 offset.X += (position.getWidth() - textDimension.Width) >> 1;
624 if (n > 0 && visible)
626 // Calculate the glyph offset.
627 s32 offx = Glyphs[n-1].offset.X;
628 s32 offy = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y;
631 core::vector2di k = getKerning(currentChar, previousChar);
635 // Determine rendering information.
636 SGUITTGlyph& glyph = Glyphs[n-1];
637 CGUITTGlyphPage* const page = Glyph_Pages[glyph.glyph_page];
638 page->render_positions.push_back(core::position2di(offset.X + offx, offset.Y + offy));
639 page->render_source_rects.push_back(glyph.source_rect);
640 Render_Map.set(glyph.glyph_page, page);
641 u32 current_color = iter.getPos();
642 if (current_color < colors.size())
643 applied_colors.push_back(colors[current_color]);
645 offset.X += getWidthFromCharacter(currentChar);
647 previousChar = currentChar;
652 update_glyph_pages();
653 core::map<u32, CGUITTGlyphPage*>::Iterator j = Render_Map.getIterator();
656 core::map<u32, CGUITTGlyphPage*>::Node* n = j.getNode();
658 if (n == 0) continue;
660 CGUITTGlyphPage* page = n->getValue();
663 for (size_t i = 0; i < page->render_positions.size(); ++i)
664 page->render_positions[i] += core::vector2di(shadow_offset, shadow_offset);
665 Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, video::SColor(shadow_alpha,0,0,0), true);
666 for (size_t i = 0; i < page->render_positions.size(); ++i)
667 page->render_positions[i] -= core::vector2di(shadow_offset, shadow_offset);
669 for (size_t i = 0; i < page->render_positions.size(); ++i) {
670 irr::video::SColor col;
671 if (!applied_colors.empty()) {
672 col = applied_colors[i < applied_colors.size() ? i : 0];
674 col = irr::video::SColor(255, 255, 255, 255);
676 if (!use_transparency)
677 col.color |= 0xff000000;
678 Driver->draw2DImage(page->texture, page->render_positions[i], page->render_source_rects[i], clip, col, true);
683 core::dimension2d<u32> CGUITTFont::getCharDimension(const wchar_t ch) const
685 return core::dimension2d<u32>(getWidthFromCharacter(ch), getHeightFromCharacter(ch));
688 core::dimension2d<u32> CGUITTFont::getDimension(const wchar_t* text) const
690 return getDimension(core::ustring(text));
693 core::dimension2d<u32> CGUITTFont::getDimension(const core::ustring& text) const
695 // Get the maximum font height. Unfortunately, we have to do this hack as
696 // Irrlicht will draw things wrong. In FreeType, the font size is the
697 // maximum size for a single glyph, but that glyph may hang "under" the
698 // draw line, increasing the total font height to beyond the set size.
699 // Irrlicht does not understand this concept when drawing fonts. Also, I
700 // add +1 to give it a 1 pixel blank border. This makes things like
701 // tooltips look nicer.
702 s32 test1 = getHeightFromCharacter((uchar32_t)'g') + 1;
703 s32 test2 = getHeightFromCharacter((uchar32_t)'j') + 1;
704 s32 test3 = getHeightFromCharacter((uchar32_t)'_') + 1;
705 s32 max_font_height = core::max_(test1, core::max_(test2, test3));
707 core::dimension2d<u32> text_dimension(0, max_font_height);
708 core::dimension2d<u32> line(0, max_font_height);
710 uchar32_t previousChar = 0;
711 core::ustring::const_iterator iter = text.begin();
712 for (; !iter.atEnd(); ++iter)
715 bool lineBreak = false;
716 if (p == '\r') // Mac or Windows line breaks.
719 if (*(iter + 1) == '\n')
725 else if (p == '\n') // Unix line breaks.
731 core::vector2di k = getKerning(p, previousChar);
735 // Check for linebreak.
739 text_dimension.Height += line.Height;
740 if (text_dimension.Width < line.Width)
741 text_dimension.Width = line.Width;
743 line.Height = max_font_height;
746 line.Width += getWidthFromCharacter(p);
748 if (text_dimension.Width < line.Width)
749 text_dimension.Width = line.Width;
751 return text_dimension;
754 inline u32 CGUITTFont::getWidthFromCharacter(wchar_t c) const
756 return getWidthFromCharacter((uchar32_t)c);
759 inline u32 CGUITTFont::getWidthFromCharacter(uchar32_t c) const
761 // Set the size of the face.
762 // This is because we cache faces and the face may have been set to a different size.
763 //FT_Set_Pixel_Sizes(tt_face, 0, size);
765 u32 n = getGlyphIndexByChar(c);
768 int w = Glyphs[n-1].advance.x / 64;
772 return (font_metrics.ascender / 64);
773 else return (font_metrics.ascender / 64) / 2;
776 inline u32 CGUITTFont::getHeightFromCharacter(wchar_t c) const
778 return getHeightFromCharacter((uchar32_t)c);
781 inline u32 CGUITTFont::getHeightFromCharacter(uchar32_t c) const
783 // Set the size of the face.
784 // This is because we cache faces and the face may have been set to a different size.
785 //FT_Set_Pixel_Sizes(tt_face, 0, size);
787 u32 n = getGlyphIndexByChar(c);
790 // Grab the true height of the character, taking into account underhanging glyphs.
791 s32 height = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y + Glyphs[n-1].source_rect.getHeight();
795 return (font_metrics.ascender / 64);
796 else return (font_metrics.ascender / 64) / 2;
799 u32 CGUITTFont::getGlyphIndexByChar(wchar_t c) const
801 return getGlyphIndexByChar((uchar32_t)c);
804 u32 CGUITTFont::getGlyphIndexByChar(uchar32_t c) const
807 u32 glyph = FT_Get_Char_Index(tt_face, c);
809 // Check for a valid glyph. If it is invalid, attempt to use the replacement character.
811 glyph = FT_Get_Char_Index(tt_face, core::unicode::UTF_REPLACEMENT_CHARACTER);
813 // If our glyph is already loaded, don't bother doing any batch loading code.
814 if (glyph != 0 && Glyphs[glyph - 1].isLoaded)
817 // Determine our batch loading positions.
818 u32 half_size = (batch_load_size / 2);
820 if (c > half_size) start_pos = c - half_size;
821 u32 end_pos = start_pos + batch_load_size;
823 // Load all our characters.
826 // Get the character we are going to load.
827 u32 char_index = FT_Get_Char_Index(tt_face, start_pos);
829 // If the glyph hasn't been loaded yet, do it now.
832 SGUITTGlyph& glyph = Glyphs[char_index - 1];
835 glyph.preload(char_index, tt_face, Driver, size, load_flags);
836 Glyph_Pages[glyph.glyph_page]->pushGlyphToBePaged(&glyph);
840 while (++start_pos < end_pos);
842 // Return our original character.
846 s32 CGUITTFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x) const
848 return getCharacterFromPos(core::ustring(text), pixel_x);
851 s32 CGUITTFont::getCharacterFromPos(const core::ustring& text, s32 pixel_x) const
857 uchar32_t previousChar = 0;
858 core::ustring::const_iterator iter = text.begin();
859 while (!iter.atEnd())
862 x += getWidthFromCharacter(c);
865 core::vector2di k = getKerning(c, previousChar);
879 void CGUITTFont::setKerningWidth(s32 kerning)
881 GlobalKerningWidth = kerning;
884 void CGUITTFont::setKerningHeight(s32 kerning)
886 GlobalKerningHeight = kerning;
889 s32 CGUITTFont::getKerningWidth(const wchar_t* thisLetter, const wchar_t* previousLetter) const
892 return GlobalKerningWidth;
893 if (thisLetter == 0 || previousLetter == 0)
896 return getKerningWidth((uchar32_t)*thisLetter, (uchar32_t)*previousLetter);
899 s32 CGUITTFont::getKerningWidth(const uchar32_t thisLetter, const uchar32_t previousLetter) const
901 // Return only the kerning width.
902 return getKerning(thisLetter, previousLetter).X;
905 s32 CGUITTFont::getKerningHeight() const
907 // FreeType 2 currently doesn't return any height kerning information.
908 return GlobalKerningHeight;
911 core::vector2di CGUITTFont::getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const
913 return getKerning((uchar32_t)thisLetter, (uchar32_t)previousLetter);
916 core::vector2di CGUITTFont::getKerning(const uchar32_t thisLetter, const uchar32_t previousLetter) const
918 if (tt_face == 0 || thisLetter == 0 || previousLetter == 0)
919 return core::vector2di();
921 // Set the size of the face.
922 // This is because we cache faces and the face may have been set to a different size.
923 FT_Set_Pixel_Sizes(tt_face, 0, size);
925 core::vector2di ret(GlobalKerningWidth, GlobalKerningHeight);
927 // If we don't have kerning, no point in continuing.
928 if (!FT_HAS_KERNING(tt_face))
931 // Get the kerning information.
933 FT_Get_Kerning(tt_face, getGlyphIndexByChar(previousLetter), getGlyphIndexByChar(thisLetter), FT_KERNING_DEFAULT, &v);
935 // If we have a scalable font, the return value will be in font points.
936 if (FT_IS_SCALABLE(tt_face))
938 // Font points, so divide by 64.
951 void CGUITTFont::setInvisibleCharacters(const wchar_t *s)
957 void CGUITTFont::setInvisibleCharacters(const core::ustring& s)
962 video::IImage* CGUITTFont::createTextureFromChar(const uchar32_t& ch)
964 u32 n = getGlyphIndexByChar(ch);
965 const SGUITTGlyph& glyph = Glyphs[n-1];
966 CGUITTGlyphPage* page = Glyph_Pages[glyph.glyph_page];
969 page->updateTexture();
971 video::ITexture* tex = page->texture;
973 // Acquire a read-only lock of the corresponding page texture.
974 #if IRRLICHT_VERSION_MAJOR==1 && IRRLICHT_VERSION_MINOR>=8
975 void* ptr = tex->lock(video::ETLM_READ_ONLY);
977 void* ptr = tex->lock(true);
980 video::ECOLOR_FORMAT format = tex->getColorFormat();
981 core::dimension2du tex_size = tex->getOriginalSize();
982 video::IImage* pageholder = Driver->createImageFromData(format, tex_size, ptr, true, false);
984 // Copy the image data out of the page texture.
985 core::dimension2du glyph_size(glyph.source_rect.getSize());
986 video::IImage* image = Driver->createImage(format, glyph_size);
987 pageholder->copyTo(image, core::position2di(0, 0), glyph.source_rect);
993 video::ITexture* CGUITTFont::getPageTextureByIndex(const u32& page_index) const
995 if (page_index < Glyph_Pages.size())
996 return Glyph_Pages[page_index]->texture;
1001 void CGUITTFont::createSharedPlane()
1006 | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
1007 |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
1011 using namespace core;
1012 using namespace video;
1013 using namespace scene;
1014 S3DVertex vertices[4];
1015 u16 indices[6] = {0,2,3,3,1,0};
1016 vertices[0] = S3DVertex(vector3df(0,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,1));
1017 vertices[1] = S3DVertex(vector3df(1,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,1));
1018 vertices[2] = S3DVertex(vector3df(0, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,0));
1019 vertices[3] = S3DVertex(vector3df(1, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,0));
1021 SMeshBuffer* buf = new SMeshBuffer();
1022 buf->append(vertices, 4, indices, 6);
1024 shared_plane_.addMeshBuffer( buf );
1026 shared_plane_ptr_ = &shared_plane_;
1027 buf->drop(); //the addMeshBuffer method will grab it, so we can drop this ptr.
1030 core::dimension2d<u32> CGUITTFont::getDimensionUntilEndOfLine(const wchar_t* p) const
1033 for (const wchar_t* temp = p; temp && *temp != '\0' && *temp != L'\r' && *temp != L'\n'; ++temp )
1036 return getDimension(s.c_str());
1039 core::array<scene::ISceneNode*> CGUITTFont::addTextSceneNode(const wchar_t* text, scene::ISceneManager* smgr, scene::ISceneNode* parent, const video::SColor& color, bool center)
1041 using namespace core;
1042 using namespace video;
1043 using namespace scene;
1045 array<scene::ISceneNode*> container;
1047 if (!Driver || !smgr) return container;
1049 parent = smgr->addEmptySceneNode(smgr->getRootSceneNode(), -1);
1050 // if you don't specify parent, then we add a empty node attached to the root node
1051 // this is generally undesirable.
1053 if (!shared_plane_ptr_) //this points to a static mesh that contains the plane
1054 createSharedPlane(); //if it's not initialized, we create one.
1056 dimension2d<s32> text_size(getDimension(text)); //convert from unsigned to signed.
1057 vector3df start_point(0, 0, 0), offset;
1060 Because we are considering adding texts into 3D world, all Y axis vectors are inverted.
1063 // There's currently no "vertical center" concept when you apply text scene node to the 3D world.
1066 offset.X = start_point.X = -text_size.Width / 2.f;
1067 offset.Y = start_point.Y = +text_size.Height/ 2.f;
1068 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text).Width) >> 1;
1071 // the default font material
1073 mat.setFlag(video::EMF_LIGHTING, true);
1074 mat.setFlag(video::EMF_ZWRITE_ENABLE, false);
1075 mat.setFlag(video::EMF_NORMALIZE_NORMALS, true);
1076 mat.ColorMaterial = video::ECM_NONE;
1077 mat.MaterialType = use_transparency ? video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_SOLID;
1078 mat.MaterialTypeParam = 0.01f;
1079 mat.DiffuseColor = color;
1081 wchar_t current_char = 0, previous_char = 0;
1084 array<u32> glyph_indices;
1088 current_char = *text;
1089 bool line_break=false;
1090 if (current_char == L'\r') // Mac or Windows breaks
1093 if (*(text + 1) == L'\n') // Windows line breaks.
1094 current_char = *(++text);
1096 else if (current_char == L'\n') // Unix breaks
1104 offset.Y -= tt_face->size->metrics.ascender / 64;
1105 offset.X = start_point.X;
1107 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text+1).Width) >> 1;
1112 n = getGlyphIndexByChar(current_char);
1115 glyph_indices.push_back( n );
1117 // Store glyph size and offset informations.
1118 SGUITTGlyph const& glyph = Glyphs[n-1];
1119 u32 texw = glyph.source_rect.getWidth();
1120 u32 texh = glyph.source_rect.getHeight();
1121 s32 offx = glyph.offset.X;
1122 s32 offy = (font_metrics.ascender / 64) - glyph.offset.Y;
1125 vector2di k = getKerning(current_char, previous_char);
1129 vector3df current_pos(offset.X + offx, offset.Y - offy, 0);
1130 dimension2d<u32> letter_size = dimension2d<u32>(texw, texh);
1132 // Now we copy planes corresponding to the letter size.
1133 IMeshManipulator* mani = smgr->getMeshManipulator();
1134 IMesh* meshcopy = mani->createMeshCopy(shared_plane_ptr_);
1135 #if IRRLICHT_VERSION_MAJOR==1 && IRRLICHT_VERSION_MINOR>=8
1136 mani->scale(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1));
1138 mani->scaleMesh(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1));
1141 ISceneNode* current_node = smgr->addMeshSceneNode(meshcopy, parent, -1, current_pos);
1144 current_node->getMaterial(0) = mat;
1145 current_node->setAutomaticCulling(EAC_OFF);
1146 current_node->setIsDebugObject(true); //so the picking won't have any effect on individual letter
1147 //current_node->setDebugDataVisible(EDS_BBOX); //de-comment this when debugging
1149 container.push_back(current_node);
1151 offset.X += getWidthFromCharacter(current_char);
1152 previous_char = current_char;
1157 update_glyph_pages();
1158 //only after we update the textures can we use the glyph page textures.
1160 for (u32 i = 0; i < glyph_indices.size(); ++i)
1162 u32 n = glyph_indices[i];
1163 SGUITTGlyph const& glyph = Glyphs[n-1];
1164 ITexture* current_tex = Glyph_Pages[glyph.glyph_page]->texture;
1165 f32 page_texture_size = (f32)current_tex->getSize().Width;
1166 //Now we calculate the UV position according to the texture size and the source rect.
1170 // | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
1171 // |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
1174 f32 u1 = glyph.source_rect.UpperLeftCorner.X / page_texture_size;
1175 f32 u2 = u1 + (glyph.source_rect.getWidth() / page_texture_size);
1176 f32 v1 = glyph.source_rect.UpperLeftCorner.Y / page_texture_size;
1177 f32 v2 = v1 + (glyph.source_rect.getHeight() / page_texture_size);
1179 //we can be quite sure that this is IMeshSceneNode, because we just added them in the above loop.
1180 IMeshSceneNode* node = static_cast<IMeshSceneNode*>(container[i]);
1182 S3DVertex* pv = static_cast<S3DVertex*>(node->getMesh()->getMeshBuffer(0)->getVertices());
1183 //pv[0].TCoords.Y = pv[1].TCoords.Y = (letter_size.Height - 1) / static_cast<f32>(letter_size.Height);
1184 //pv[1].TCoords.X = pv[3].TCoords.X = (letter_size.Width - 1) / static_cast<f32>(letter_size.Width);
1185 pv[0].TCoords = vector2df(u1, v2);
1186 pv[1].TCoords = vector2df(u2, v2);
1187 pv[2].TCoords = vector2df(u1, v1);
1188 pv[3].TCoords = vector2df(u2, v1);
1190 container[i]->getMaterial(0).setTexture(0, current_tex);
1196 } // end namespace gui
1197 } // end namespace irr