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();
382 tt_face = face->face;
384 // Store font metrics.
385 FT_Set_Pixel_Sizes(tt_face, size, 0);
386 font_metrics = tt_face->size->metrics;
388 // Allocate our glyphs.
390 Glyphs.reallocate(tt_face->num_glyphs);
391 Glyphs.set_used(tt_face->num_glyphs);
392 for (FT_Long i = 0; i < tt_face->num_glyphs; ++i)
394 Glyphs[i].isLoaded = false;
395 Glyphs[i].glyph_page = 0;
396 Glyphs[i].source_rect = core::recti();
397 Glyphs[i].offset = core::vector2di();
398 Glyphs[i].advance = FT_Vector();
399 Glyphs[i].surface = 0;
400 Glyphs[i].parent = this;
403 // Cache the first 127 ascii characters.
404 u32 old_size = batch_load_size;
405 batch_load_size = 127;
406 getGlyphIndexByChar((uchar32_t)0);
407 batch_load_size = old_size;
412 CGUITTFont::~CGUITTFont()
414 // Delete the glyphs and glyph pages.
416 CGUITTAssistDelete::Delete(Glyphs);
419 // We aren't using this face anymore.
420 core::map<io::path, SGUITTFace*>::Node* n = c_faces.find(filename);
423 SGUITTFace* f = n->getValue();
425 // Drop our face. If this was the last face, the destructor will clean up.
427 c_faces.remove(filename);
429 // If there are no more faces referenced by FreeType, clean up.
430 if (c_faces.size() == 0)
432 FT_Done_FreeType(c_library);
433 c_libraryLoaded = false;
437 // Drop our driver now.
441 // Destroy sguitt_face after clearing c_faces
445 void CGUITTFont::reset_images()
447 // Delete the glyphs.
448 for (u32 i = 0; i != Glyphs.size(); ++i)
451 // Unload the glyph pages from video memory.
452 for (u32 i = 0; i != Glyph_Pages.size(); ++i)
453 delete Glyph_Pages[i];
456 // Always update the internal FreeType loading flags after resetting.
460 void CGUITTFont::update_glyph_pages() const
462 for (u32 i = 0; i != Glyph_Pages.size(); ++i)
464 if (Glyph_Pages[i]->dirty)
465 Glyph_Pages[i]->updateTexture();
469 CGUITTGlyphPage* CGUITTFont::getLastGlyphPage() const
471 CGUITTGlyphPage* page = 0;
472 if (Glyph_Pages.empty())
476 page = Glyph_Pages[getLastGlyphPageIndex()];
477 if (page->available_slots == 0)
483 CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8& pixel_mode)
485 CGUITTGlyphPage* page = 0;
488 io::path name("TTFontGlyphPage_");
489 name += tt_face->family_name;
491 name += tt_face->style_name;
495 name += Glyph_Pages.size(); // The newly created page will be at the end of the collection.
497 // Create the new page.
498 page = new CGUITTGlyphPage(Driver, name);
500 // Determine our maximum texture size.
501 // If we keep getting 0, set it to 1024x1024, as that number is pretty safe.
502 core::dimension2du max_texture_size = max_page_texture_size;
503 if (max_texture_size.Width == 0 || max_texture_size.Height == 0)
504 max_texture_size = Driver->getMaxTextureSize();
505 if (max_texture_size.Width == 0 || max_texture_size.Height == 0)
506 max_texture_size = core::dimension2du(1024, 1024);
508 // We want to try to put at least 144 glyphs on a single texture.
509 core::dimension2du page_texture_size;
510 if (size <= 21) page_texture_size = core::dimension2du(256, 256);
511 else if (size <= 42) page_texture_size = core::dimension2du(512, 512);
512 else if (size <= 84) page_texture_size = core::dimension2du(1024, 1024);
513 else if (size <= 168) page_texture_size = core::dimension2du(2048, 2048);
514 else page_texture_size = core::dimension2du(4096, 4096);
516 if (page_texture_size.Width > max_texture_size.Width || page_texture_size.Height > max_texture_size.Height)
517 page_texture_size = max_texture_size;
519 if (!page->createPageTexture(pixel_mode, page_texture_size)) {
520 // TODO: add error message?
527 // Determine the number of glyph slots on the page and add it to the list of pages.
528 page->available_slots = (page_texture_size.Width / size) * (page_texture_size.Height / size);
529 Glyph_Pages.push_back(page);
534 void CGUITTFont::setTransparency(const bool flag)
536 use_transparency = flag;
540 void CGUITTFont::setMonochrome(const bool flag)
542 use_monochrome = flag;
546 void CGUITTFont::setFontHinting(const bool enable, const bool enable_auto_hinting)
548 use_hinting = enable;
549 use_auto_hinting = enable_auto_hinting;
553 void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position, video::SColor color, bool hcenter, bool vcenter, const core::rect<s32>* clip)
555 draw(EnrichedString(std::wstring(text.c_str()), color), position, color, hcenter, vcenter, clip);
558 void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& position, video::SColor color, bool hcenter, bool vcenter, const core::rect<s32>* clip)
560 std::vector<video::SColor> colors = text.getColors();
565 // Clear the glyph pages of their render information.
566 for (u32 i = 0; i < Glyph_Pages.size(); ++i)
568 Glyph_Pages[i]->render_positions.clear();
569 Glyph_Pages[i]->render_source_rects.clear();
572 // Set up some variables.
573 core::dimension2d<s32> textDimension;
574 core::position2d<s32> offset = position.UpperLeftCorner;
576 // Determine offset positions.
577 if (hcenter || vcenter)
579 textDimension = getDimension(text.c_str());
582 offset.X = ((position.getWidth() - textDimension.Width) >> 1) + offset.X;
585 offset.Y = ((position.getHeight() - textDimension.Height) >> 1) + offset.Y;
588 // Convert to a unicode string.
589 core::ustring utext = text.getString();
591 // Set up our render map.
592 core::map<u32, CGUITTGlyphPage*> Render_Map;
594 // Start parsing characters.
596 uchar32_t previousChar = 0;
597 core::ustring::const_iterator iter(utext);
598 std::vector<video::SColor> applied_colors;
599 while (!iter.atEnd())
601 uchar32_t currentChar = *iter;
602 n = getGlyphIndexByChar(currentChar);
603 bool visible = (Invisible.findFirst(currentChar) == -1);
604 bool lineBreak=false;
605 if (currentChar == L'\r') // Mac or Windows breaks
608 if (*(iter + 1) == (uchar32_t)'\n') // Windows line breaks.
609 currentChar = *(++iter);
611 else if (currentChar == (uchar32_t)'\n') // Unix breaks
619 offset.Y += font_metrics.height / 64;
620 offset.X = position.UpperLeftCorner.X;
623 offset.X += (position.getWidth() - textDimension.Width) >> 1;
628 if (n > 0 && visible)
630 // Calculate the glyph offset.
631 s32 offx = Glyphs[n-1].offset.X;
632 s32 offy = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y;
635 core::vector2di k = getKerning(currentChar, previousChar);
639 // Determine rendering information.
640 SGUITTGlyph& glyph = Glyphs[n-1];
641 CGUITTGlyphPage* const page = Glyph_Pages[glyph.glyph_page];
642 page->render_positions.push_back(core::position2di(offset.X + offx, offset.Y + offy));
643 page->render_source_rects.push_back(glyph.source_rect);
644 Render_Map.set(glyph.glyph_page, page);
645 u32 current_color = iter.getPos();
646 if (current_color < colors.size())
647 applied_colors.push_back(colors[current_color]);
649 offset.X += getWidthFromCharacter(currentChar);
651 previousChar = currentChar;
656 update_glyph_pages();
657 core::map<u32, CGUITTGlyphPage*>::Iterator j = Render_Map.getIterator();
660 core::map<u32, CGUITTGlyphPage*>::Node* n = j.getNode();
662 if (n == 0) continue;
664 CGUITTGlyphPage* page = n->getValue();
667 for (size_t i = 0; i < page->render_positions.size(); ++i)
668 page->render_positions[i] += core::vector2di(shadow_offset, shadow_offset);
669 Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, video::SColor(shadow_alpha,0,0,0), true);
670 for (size_t i = 0; i < page->render_positions.size(); ++i)
671 page->render_positions[i] -= core::vector2di(shadow_offset, shadow_offset);
673 for (size_t i = 0; i < page->render_positions.size(); ++i) {
674 irr::video::SColor col;
675 if (!applied_colors.empty()) {
676 col = applied_colors[i < applied_colors.size() ? i : 0];
678 col = irr::video::SColor(255, 255, 255, 255);
680 if (!use_transparency)
681 col.color |= 0xff000000;
682 Driver->draw2DImage(page->texture, page->render_positions[i], page->render_source_rects[i], clip, col, true);
687 core::dimension2d<u32> CGUITTFont::getCharDimension(const wchar_t ch) const
689 return core::dimension2d<u32>(getWidthFromCharacter(ch), getHeightFromCharacter(ch));
692 core::dimension2d<u32> CGUITTFont::getDimension(const wchar_t* text) const
694 return getDimension(core::ustring(text));
697 core::dimension2d<u32> CGUITTFont::getDimension(const core::ustring& text) const
699 // Get the maximum font height. Unfortunately, we have to do this hack as
700 // Irrlicht will draw things wrong. In FreeType, the font size is the
701 // maximum size for a single glyph, but that glyph may hang "under" the
702 // draw line, increasing the total font height to beyond the set size.
703 // Irrlicht does not understand this concept when drawing fonts. Also, I
704 // add +1 to give it a 1 pixel blank border. This makes things like
705 // tooltips look nicer.
706 s32 test1 = getHeightFromCharacter((uchar32_t)'g') + 1;
707 s32 test2 = getHeightFromCharacter((uchar32_t)'j') + 1;
708 s32 test3 = getHeightFromCharacter((uchar32_t)'_') + 1;
709 s32 max_font_height = core::max_(test1, core::max_(test2, test3));
711 core::dimension2d<u32> text_dimension(0, max_font_height);
712 core::dimension2d<u32> line(0, max_font_height);
714 uchar32_t previousChar = 0;
715 core::ustring::const_iterator iter = text.begin();
716 for (; !iter.atEnd(); ++iter)
719 bool lineBreak = false;
720 if (p == '\r') // Mac or Windows line breaks.
723 if (*(iter + 1) == '\n')
729 else if (p == '\n') // Unix line breaks.
735 core::vector2di k = getKerning(p, previousChar);
739 // Check for linebreak.
743 text_dimension.Height += line.Height;
744 if (text_dimension.Width < line.Width)
745 text_dimension.Width = line.Width;
747 line.Height = max_font_height;
750 line.Width += getWidthFromCharacter(p);
752 if (text_dimension.Width < line.Width)
753 text_dimension.Width = line.Width;
755 return text_dimension;
758 inline u32 CGUITTFont::getWidthFromCharacter(wchar_t c) const
760 return getWidthFromCharacter((uchar32_t)c);
763 inline u32 CGUITTFont::getWidthFromCharacter(uchar32_t c) const
765 // Set the size of the face.
766 // This is because we cache faces and the face may have been set to a different size.
767 //FT_Set_Pixel_Sizes(tt_face, 0, size);
769 u32 n = getGlyphIndexByChar(c);
772 int w = Glyphs[n-1].advance.x / 64;
776 return (font_metrics.ascender / 64);
777 else return (font_metrics.ascender / 64) / 2;
780 inline u32 CGUITTFont::getHeightFromCharacter(wchar_t c) const
782 return getHeightFromCharacter((uchar32_t)c);
785 inline u32 CGUITTFont::getHeightFromCharacter(uchar32_t c) const
787 // Set the size of the face.
788 // This is because we cache faces and the face may have been set to a different size.
789 //FT_Set_Pixel_Sizes(tt_face, 0, size);
791 u32 n = getGlyphIndexByChar(c);
794 // Grab the true height of the character, taking into account underhanging glyphs.
795 s32 height = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y + Glyphs[n-1].source_rect.getHeight();
799 return (font_metrics.ascender / 64);
800 else return (font_metrics.ascender / 64) / 2;
803 u32 CGUITTFont::getGlyphIndexByChar(wchar_t c) const
805 return getGlyphIndexByChar((uchar32_t)c);
808 u32 CGUITTFont::getGlyphIndexByChar(uchar32_t c) const
811 u32 glyph = FT_Get_Char_Index(tt_face, c);
813 // Check for a valid glyph. If it is invalid, attempt to use the replacement character.
815 glyph = FT_Get_Char_Index(tt_face, core::unicode::UTF_REPLACEMENT_CHARACTER);
817 // If our glyph is already loaded, don't bother doing any batch loading code.
818 if (glyph != 0 && Glyphs[glyph - 1].isLoaded)
821 // Determine our batch loading positions.
822 u32 half_size = (batch_load_size / 2);
824 if (c > half_size) start_pos = c - half_size;
825 u32 end_pos = start_pos + batch_load_size;
827 // Load all our characters.
830 // Get the character we are going to load.
831 u32 char_index = FT_Get_Char_Index(tt_face, start_pos);
833 // If the glyph hasn't been loaded yet, do it now.
836 SGUITTGlyph& glyph = Glyphs[char_index - 1];
839 glyph.preload(char_index, tt_face, Driver, size, load_flags);
840 Glyph_Pages[glyph.glyph_page]->pushGlyphToBePaged(&glyph);
844 while (++start_pos < end_pos);
846 // Return our original character.
850 s32 CGUITTFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x) const
852 return getCharacterFromPos(core::ustring(text), pixel_x);
855 s32 CGUITTFont::getCharacterFromPos(const core::ustring& text, s32 pixel_x) const
861 uchar32_t previousChar = 0;
862 core::ustring::const_iterator iter = text.begin();
863 while (!iter.atEnd())
866 x += getWidthFromCharacter(c);
869 core::vector2di k = getKerning(c, previousChar);
883 void CGUITTFont::setKerningWidth(s32 kerning)
885 GlobalKerningWidth = kerning;
888 void CGUITTFont::setKerningHeight(s32 kerning)
890 GlobalKerningHeight = kerning;
893 s32 CGUITTFont::getKerningWidth(const wchar_t* thisLetter, const wchar_t* previousLetter) const
896 return GlobalKerningWidth;
897 if (thisLetter == 0 || previousLetter == 0)
900 return getKerningWidth((uchar32_t)*thisLetter, (uchar32_t)*previousLetter);
903 s32 CGUITTFont::getKerningWidth(const uchar32_t thisLetter, const uchar32_t previousLetter) const
905 // Return only the kerning width.
906 return getKerning(thisLetter, previousLetter).X;
909 s32 CGUITTFont::getKerningHeight() const
911 // FreeType 2 currently doesn't return any height kerning information.
912 return GlobalKerningHeight;
915 core::vector2di CGUITTFont::getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const
917 return getKerning((uchar32_t)thisLetter, (uchar32_t)previousLetter);
920 core::vector2di CGUITTFont::getKerning(const uchar32_t thisLetter, const uchar32_t previousLetter) const
922 if (tt_face == 0 || thisLetter == 0 || previousLetter == 0)
923 return core::vector2di();
925 // Set the size of the face.
926 // This is because we cache faces and the face may have been set to a different size.
927 FT_Set_Pixel_Sizes(tt_face, 0, size);
929 core::vector2di ret(GlobalKerningWidth, GlobalKerningHeight);
931 // If we don't have kerning, no point in continuing.
932 if (!FT_HAS_KERNING(tt_face))
935 // Get the kerning information.
937 FT_Get_Kerning(tt_face, getGlyphIndexByChar(previousLetter), getGlyphIndexByChar(thisLetter), FT_KERNING_DEFAULT, &v);
939 // If we have a scalable font, the return value will be in font points.
940 if (FT_IS_SCALABLE(tt_face))
942 // Font points, so divide by 64.
955 void CGUITTFont::setInvisibleCharacters(const wchar_t *s)
961 void CGUITTFont::setInvisibleCharacters(const core::ustring& s)
966 video::IImage* CGUITTFont::createTextureFromChar(const uchar32_t& ch)
968 u32 n = getGlyphIndexByChar(ch);
969 const SGUITTGlyph& glyph = Glyphs[n-1];
970 CGUITTGlyphPage* page = Glyph_Pages[glyph.glyph_page];
973 page->updateTexture();
975 video::ITexture* tex = page->texture;
977 // Acquire a read-only lock of the corresponding page texture.
978 #if IRRLICHT_VERSION_MAJOR==1 && IRRLICHT_VERSION_MINOR>=8
979 void* ptr = tex->lock(video::ETLM_READ_ONLY);
981 void* ptr = tex->lock(true);
984 video::ECOLOR_FORMAT format = tex->getColorFormat();
985 core::dimension2du tex_size = tex->getOriginalSize();
986 video::IImage* pageholder = Driver->createImageFromData(format, tex_size, ptr, true, false);
988 // Copy the image data out of the page texture.
989 core::dimension2du glyph_size(glyph.source_rect.getSize());
990 video::IImage* image = Driver->createImage(format, glyph_size);
991 pageholder->copyTo(image, core::position2di(0, 0), glyph.source_rect);
997 video::ITexture* CGUITTFont::getPageTextureByIndex(const u32& page_index) const
999 if (page_index < Glyph_Pages.size())
1000 return Glyph_Pages[page_index]->texture;
1005 void CGUITTFont::createSharedPlane()
1010 | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
1011 |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
1015 using namespace core;
1016 using namespace video;
1017 using namespace scene;
1018 S3DVertex vertices[4];
1019 u16 indices[6] = {0,2,3,3,1,0};
1020 vertices[0] = S3DVertex(vector3df(0,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,1));
1021 vertices[1] = S3DVertex(vector3df(1,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,1));
1022 vertices[2] = S3DVertex(vector3df(0, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,0));
1023 vertices[3] = S3DVertex(vector3df(1, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,0));
1025 SMeshBuffer* buf = new SMeshBuffer();
1026 buf->append(vertices, 4, indices, 6);
1028 shared_plane_.addMeshBuffer( buf );
1030 shared_plane_ptr_ = &shared_plane_;
1031 buf->drop(); //the addMeshBuffer method will grab it, so we can drop this ptr.
1034 core::dimension2d<u32> CGUITTFont::getDimensionUntilEndOfLine(const wchar_t* p) const
1037 for (const wchar_t* temp = p; temp && *temp != '\0' && *temp != L'\r' && *temp != L'\n'; ++temp )
1040 return getDimension(s.c_str());
1043 core::array<scene::ISceneNode*> CGUITTFont::addTextSceneNode(const wchar_t* text, scene::ISceneManager* smgr, scene::ISceneNode* parent, const video::SColor& color, bool center)
1045 using namespace core;
1046 using namespace video;
1047 using namespace scene;
1049 array<scene::ISceneNode*> container;
1051 if (!Driver || !smgr) return container;
1053 parent = smgr->addEmptySceneNode(smgr->getRootSceneNode(), -1);
1054 // if you don't specify parent, then we add a empty node attached to the root node
1055 // this is generally undesirable.
1057 if (!shared_plane_ptr_) //this points to a static mesh that contains the plane
1058 createSharedPlane(); //if it's not initialized, we create one.
1060 dimension2d<s32> text_size(getDimension(text)); //convert from unsigned to signed.
1061 vector3df start_point(0, 0, 0), offset;
1064 Because we are considering adding texts into 3D world, all Y axis vectors are inverted.
1067 // There's currently no "vertical center" concept when you apply text scene node to the 3D world.
1070 offset.X = start_point.X = -text_size.Width / 2.f;
1071 offset.Y = start_point.Y = +text_size.Height/ 2.f;
1072 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text).Width) >> 1;
1075 // the default font material
1077 mat.setFlag(video::EMF_LIGHTING, true);
1078 mat.setFlag(video::EMF_ZWRITE_ENABLE, false);
1079 mat.setFlag(video::EMF_NORMALIZE_NORMALS, true);
1080 mat.ColorMaterial = video::ECM_NONE;
1081 mat.MaterialType = use_transparency ? video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_SOLID;
1082 mat.MaterialTypeParam = 0.01f;
1083 mat.DiffuseColor = color;
1085 wchar_t current_char = 0, previous_char = 0;
1088 array<u32> glyph_indices;
1092 current_char = *text;
1093 bool line_break=false;
1094 if (current_char == L'\r') // Mac or Windows breaks
1097 if (*(text + 1) == L'\n') // Windows line breaks.
1098 current_char = *(++text);
1100 else if (current_char == L'\n') // Unix breaks
1108 offset.Y -= tt_face->size->metrics.ascender / 64;
1109 offset.X = start_point.X;
1111 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text+1).Width) >> 1;
1116 n = getGlyphIndexByChar(current_char);
1119 glyph_indices.push_back( n );
1121 // Store glyph size and offset informations.
1122 SGUITTGlyph const& glyph = Glyphs[n-1];
1123 u32 texw = glyph.source_rect.getWidth();
1124 u32 texh = glyph.source_rect.getHeight();
1125 s32 offx = glyph.offset.X;
1126 s32 offy = (font_metrics.ascender / 64) - glyph.offset.Y;
1129 vector2di k = getKerning(current_char, previous_char);
1133 vector3df current_pos(offset.X + offx, offset.Y - offy, 0);
1134 dimension2d<u32> letter_size = dimension2d<u32>(texw, texh);
1136 // Now we copy planes corresponding to the letter size.
1137 IMeshManipulator* mani = smgr->getMeshManipulator();
1138 IMesh* meshcopy = mani->createMeshCopy(shared_plane_ptr_);
1139 #if IRRLICHT_VERSION_MAJOR==1 && IRRLICHT_VERSION_MINOR>=8
1140 mani->scale(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1));
1142 mani->scaleMesh(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1));
1145 ISceneNode* current_node = smgr->addMeshSceneNode(meshcopy, parent, -1, current_pos);
1148 current_node->getMaterial(0) = mat;
1149 current_node->setAutomaticCulling(EAC_OFF);
1150 current_node->setIsDebugObject(true); //so the picking won't have any effect on individual letter
1151 //current_node->setDebugDataVisible(EDS_BBOX); //de-comment this when debugging
1153 container.push_back(current_node);
1155 offset.X += getWidthFromCharacter(current_char);
1156 previous_char = current_char;
1161 update_glyph_pages();
1162 //only after we update the textures can we use the glyph page textures.
1164 for (u32 i = 0; i < glyph_indices.size(); ++i)
1166 u32 n = glyph_indices[i];
1167 SGUITTGlyph const& glyph = Glyphs[n-1];
1168 ITexture* current_tex = Glyph_Pages[glyph.glyph_page]->texture;
1169 f32 page_texture_size = (f32)current_tex->getSize().Width;
1170 //Now we calculate the UV position according to the texture size and the source rect.
1174 // | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
1175 // |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
1178 f32 u1 = glyph.source_rect.UpperLeftCorner.X / page_texture_size;
1179 f32 u2 = u1 + (glyph.source_rect.getWidth() / page_texture_size);
1180 f32 v1 = glyph.source_rect.UpperLeftCorner.Y / page_texture_size;
1181 f32 v2 = v1 + (glyph.source_rect.getHeight() / page_texture_size);
1183 //we can be quite sure that this is IMeshSceneNode, because we just added them in the above loop.
1184 IMeshSceneNode* node = static_cast<IMeshSceneNode*>(container[i]);
1186 S3DVertex* pv = static_cast<S3DVertex*>(node->getMesh()->getMeshBuffer(0)->getVertices());
1187 //pv[0].TCoords.Y = pv[1].TCoords.Y = (letter_size.Height - 1) / static_cast<f32>(letter_size.Height);
1188 //pv[1].TCoords.X = pv[3].TCoords.X = (letter_size.Width - 1) / static_cast<f32>(letter_size.Width);
1189 pv[0].TCoords = vector2df(u1, v2);
1190 pv[1].TCoords = vector2df(u2, v2);
1191 pv[2].TCoords = vector2df(u1, v1);
1192 pv[3].TCoords = vector2df(u2, v1);
1194 container[i]->getMaterial(0).setTexture(0, current_tex);
1200 } // end namespace gui
1201 } // end namespace irr