2 CGUITTFont FreeType class for Irrlicht
3 Copyright (c) 2009-2010 John Norman
4 Copyright (c) 2016 Nathanaƫl Courant
6 This software is provided 'as-is', without any express or implied
7 warranty. In no event will the authors be held liable for any
8 damages arising from the use of this software.
10 Permission is granted to anyone to use this software for any
11 purpose, including commercial applications, and to alter it and
12 redistribute it freely, subject to the following restrictions:
14 1. The origin of this software must not be misrepresented; you
15 must not claim that you wrote the original software. If you use
16 this software in a product, an acknowledgment in the product
17 documentation would be appreciated but is not required.
19 2. Altered source versions must be plainly marked as such, and
20 must not be misrepresented as being the original software.
22 3. This notice may not be removed or altered from any source
25 The original version of this class can be located at:
26 http://irrlicht.suckerfreegames.com/
29 john@suckerfreegames.com
34 #include "CGUITTFont.h"
41 // Manages the FT_Face cache.
42 struct SGUITTFace : public virtual irr::IReferenceCounted
44 SGUITTFace() : face_buffer(0), face_buffer_size(0)
46 memset((void*)&face, 0, sizeof(FT_Face));
57 FT_Long face_buffer_size;
61 FT_Library CGUITTFont::c_library;
62 core::map<io::path, SGUITTFace*> CGUITTFont::c_faces;
63 bool CGUITTFont::c_libraryLoaded = false;
64 scene::IMesh* CGUITTFont::shared_plane_ptr_ = 0;
65 scene::SMesh CGUITTFont::shared_plane_;
69 /** Checks that no dimension of the FT_BitMap object is negative. If either is
70 * negative, abort execution.
72 inline void checkFontBitmapSize(const FT_Bitmap &bits)
74 if ((s32)bits.rows < 0 || (s32)bits.width < 0) {
75 std::cout << "Insane font glyph size. File: "
76 << __FILE__ << " Line " << __LINE__
82 video::IImage* SGUITTGlyph::createGlyphImage(const FT_Bitmap& bits, video::IVideoDriver* driver) const
84 // Make sure our casts to s32 in the loops below will not cause problems
85 checkFontBitmapSize(bits);
87 // Determine what our texture size should be.
88 // Add 1 because textures are inclusive-exclusive.
89 core::dimension2du d(bits.width + 1, bits.rows + 1);
90 core::dimension2du texture_size;
91 //core::dimension2du texture_size(bits.width + 1, bits.rows + 1);
93 // Create and load our image now.
94 video::IImage* image = 0;
95 switch (bits.pixel_mode)
97 case FT_PIXEL_MODE_MONO:
99 // Create a blank image and fill it with transparent pixels.
100 texture_size = d.getOptimalSize(true, true);
101 image = driver->createImage(video::ECF_A1R5G5B5, texture_size);
102 image->fill(video::SColor(0, 255, 255, 255));
104 // Load the monochrome data in.
105 const u32 image_pitch = image->getPitch() / sizeof(u16);
106 u16* image_data = (u16*)image->getData();
107 u8* glyph_data = bits.buffer;
109 for (s32 y = 0; y < (s32)bits.rows; ++y)
111 u16* row = image_data;
112 for (s32 x = 0; x < (s32)bits.width; ++x)
114 // Monochrome bitmaps store 8 pixels per byte. The left-most pixel is the bit 0x80.
115 // So, we go through the data each bit at a time.
116 if ((glyph_data[y * bits.pitch + (x / 8)] & (0x80 >> (x % 8))) != 0)
120 image_data += image_pitch;
125 case FT_PIXEL_MODE_GRAY:
127 // Create our blank image.
128 texture_size = d.getOptimalSize(!driver->queryFeature(video::EVDF_TEXTURE_NPOT), !driver->queryFeature(video::EVDF_TEXTURE_NSQUARE), true, 0);
129 image = driver->createImage(video::ECF_A8R8G8B8, texture_size);
130 image->fill(video::SColor(0, 255, 255, 255));
132 // Load the grayscale data in.
133 const float gray_count = static_cast<float>(bits.num_grays);
134 const u32 image_pitch = image->getPitch() / sizeof(u32);
135 u32* image_data = (u32*)image->getData();
136 u8* glyph_data = bits.buffer;
137 for (s32 y = 0; y < (s32)bits.rows; ++y)
139 u8* row = glyph_data;
140 for (s32 x = 0; x < (s32)bits.width; ++x)
142 image_data[y * image_pitch + x] |= static_cast<u32>(255.0f * (static_cast<float>(*row++) / gray_count)) << 24;
143 //data[y * image_pitch + x] |= ((u32)(*bitsdata++) << 24);
145 glyph_data += bits.pitch;
150 // TODO: error message?
156 void SGUITTGlyph::preload(u32 char_index, FT_Face face, video::IVideoDriver* driver, u32 font_size, const FT_Int32 loadFlags)
158 if (isLoaded) return;
160 // Set the size of the glyph.
161 FT_Set_Pixel_Sizes(face, 0, font_size);
163 // Attempt to load the glyph.
164 if (FT_Load_Glyph(face, char_index, loadFlags) != FT_Err_Ok)
165 // TODO: error message?
168 FT_GlyphSlot glyph = face->glyph;
169 FT_Bitmap bits = glyph->bitmap;
171 // Setup the glyph information here:
172 advance = glyph->advance;
173 offset = core::vector2di(glyph->bitmap_left, glyph->bitmap_top);
175 // Try to get the last page with available slots.
176 CGUITTGlyphPage* page = parent->getLastGlyphPage();
178 // If we need to make a new page, do that now.
181 page = parent->createGlyphPage(bits.pixel_mode);
183 // TODO: add error message?
187 glyph_page = parent->getLastGlyphPageIndex();
188 u32 texture_side_length = page->texture->getOriginalSize().Width;
189 core::vector2di page_position(
190 (page->used_slots % (texture_side_length / font_size)) * font_size,
191 (page->used_slots / (texture_side_length / font_size)) * font_size
193 source_rect.UpperLeftCorner = page_position;
194 source_rect.LowerRightCorner = core::vector2di(page_position.X + bits.width, page_position.Y + bits.rows);
198 --page->available_slots;
200 // We grab the glyph bitmap here so the data won't be removed when the next glyph is loaded.
201 surface = createGlyphImage(bits, driver);
203 // Set our glyph as loaded.
207 void SGUITTGlyph::unload()
217 //////////////////////
219 CGUITTFont* CGUITTFont::createTTFont(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency, const u32 shadow, const u32 shadow_alpha)
221 if (!c_libraryLoaded)
223 if (FT_Init_FreeType(&c_library))
225 c_libraryLoaded = true;
228 CGUITTFont* font = new CGUITTFont(env);
229 bool ret = font->load(filename, size, antialias, transparency);
236 font->shadow_offset = shadow;
237 font->shadow_alpha = shadow_alpha;
242 CGUITTFont* CGUITTFont::createTTFont(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
244 if (!c_libraryLoaded)
246 if (FT_Init_FreeType(&c_library))
248 c_libraryLoaded = true;
251 CGUITTFont* font = new CGUITTFont(device->getGUIEnvironment());
252 font->Device = device;
253 bool ret = font->load(filename, size, antialias, transparency);
263 CGUITTFont* CGUITTFont::create(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
265 return CGUITTFont::createTTFont(env, filename, size, antialias, transparency);
268 CGUITTFont* CGUITTFont::create(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
270 return CGUITTFont::createTTFont(device, filename, size, antialias, transparency);
273 //////////////////////
276 CGUITTFont::CGUITTFont(IGUIEnvironment *env)
277 : use_monochrome(false), use_transparency(true), use_hinting(true), use_auto_hinting(true),
278 batch_load_size(1), Device(0), Environment(env), Driver(0), GlobalKerningWidth(0), GlobalKerningHeight(0),
279 shadow_offset(0), shadow_alpha(0), fallback(0)
282 setDebugName("CGUITTFont");
287 // don't grab environment, to avoid circular references
288 Driver = Environment->getVideoDriver();
294 setInvisibleCharacters(L" ");
296 // Glyphs aren't reference counted, so don't try to delete them when we free the array.
297 Glyphs.set_free_when_destroyed(false);
300 bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antialias, const bool transparency)
302 // Some sanity checks.
303 if (Environment == 0 || Driver == 0) return false;
304 if (size == 0) return false;
305 if (filename.size() == 0) return false;
307 io::IFileSystem* filesystem = Environment->getFileSystem();
308 irr::ILogger* logger = (Device != 0 ? Device->getLogger() : 0);
310 this->filename = filename;
312 // Update the font loading flags when the font is first loaded.
313 this->use_monochrome = !antialias;
314 this->use_transparency = transparency;
319 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);
322 SGUITTFace* face = 0;
323 core::map<io::path, SGUITTFace*>::Node* node = c_faces.find(filename);
326 face = new SGUITTFace();
327 c_faces.set(filename, face);
331 // Read in the file data.
332 io::IReadFile* file = filesystem->createAndOpenFile(filename);
335 if (logger) logger->log(L"CGUITTFont", L"Failed to open the file.", irr::ELL_INFORMATION);
337 c_faces.remove(filename);
342 face->face_buffer = new FT_Byte[file->getSize()];
343 file->read(face->face_buffer, file->getSize());
344 face->face_buffer_size = file->getSize();
348 if (FT_New_Memory_Face(c_library, face->face_buffer, face->face_buffer_size, 0, &face->face))
350 if (logger) logger->log(L"CGUITTFont", L"FT_New_Memory_Face failed.", irr::ELL_INFORMATION);
352 c_faces.remove(filename);
360 core::ustring converter(filename);
361 if (FT_New_Face(c_library, reinterpret_cast<const char*>(converter.toUTF8_s().c_str()), 0, &face->face))
363 if (logger) logger->log(L"CGUITTFont", L"FT_New_Face failed.", irr::ELL_INFORMATION);
365 c_faces.remove(filename);
374 // Using another instance of this face.
375 face = node->getValue();
380 tt_face = face->face;
382 // Store font metrics.
383 FT_Set_Pixel_Sizes(tt_face, size, 0);
384 font_metrics = tt_face->size->metrics;
386 // Allocate our glyphs.
388 Glyphs.reallocate(tt_face->num_glyphs);
389 Glyphs.set_used(tt_face->num_glyphs);
390 for (FT_Long i = 0; i < tt_face->num_glyphs; ++i)
392 Glyphs[i].isLoaded = false;
393 Glyphs[i].glyph_page = 0;
394 Glyphs[i].source_rect = core::recti();
395 Glyphs[i].offset = core::vector2di();
396 Glyphs[i].advance = FT_Vector();
397 Glyphs[i].surface = 0;
398 Glyphs[i].parent = this;
401 // Cache the first 127 ascii characters.
402 u32 old_size = batch_load_size;
403 batch_load_size = 127;
404 getGlyphIndexByChar((uchar32_t)0);
405 batch_load_size = old_size;
410 CGUITTFont::~CGUITTFont()
412 // Delete the glyphs and glyph pages.
414 CGUITTAssistDelete::Delete(Glyphs);
417 // We aren't using this face anymore.
418 core::map<io::path, SGUITTFace*>::Node* n = c_faces.find(filename);
421 SGUITTFace* f = n->getValue();
423 // Drop our face. If this was the last face, the destructor will clean up.
425 c_faces.remove(filename);
427 // If there are no more faces referenced by FreeType, clean up.
428 if (c_faces.size() == 0)
430 FT_Done_FreeType(c_library);
431 c_libraryLoaded = false;
435 // Drop our driver now.
440 void CGUITTFont::reset_images()
442 // Delete the glyphs.
443 for (u32 i = 0; i != Glyphs.size(); ++i)
446 // Unload the glyph pages from video memory.
447 for (u32 i = 0; i != Glyph_Pages.size(); ++i)
448 delete Glyph_Pages[i];
451 // Always update the internal FreeType loading flags after resetting.
455 void CGUITTFont::update_glyph_pages() const
457 for (u32 i = 0; i != Glyph_Pages.size(); ++i)
459 if (Glyph_Pages[i]->dirty)
460 Glyph_Pages[i]->updateTexture();
464 CGUITTGlyphPage* CGUITTFont::getLastGlyphPage() const
466 CGUITTGlyphPage* page = 0;
467 if (Glyph_Pages.empty())
471 page = Glyph_Pages[getLastGlyphPageIndex()];
472 if (page->available_slots == 0)
478 CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8& pixel_mode)
480 CGUITTGlyphPage* page = 0;
483 io::path name("TTFontGlyphPage_");
484 name += tt_face->family_name;
486 name += tt_face->style_name;
490 name += Glyph_Pages.size(); // The newly created page will be at the end of the collection.
492 // Create the new page.
493 page = new CGUITTGlyphPage(Driver, name);
495 // Determine our maximum texture size.
496 // If we keep getting 0, set it to 1024x1024, as that number is pretty safe.
497 core::dimension2du max_texture_size = max_page_texture_size;
498 if (max_texture_size.Width == 0 || max_texture_size.Height == 0)
499 max_texture_size = Driver->getMaxTextureSize();
500 if (max_texture_size.Width == 0 || max_texture_size.Height == 0)
501 max_texture_size = core::dimension2du(1024, 1024);
503 // We want to try to put at least 144 glyphs on a single texture.
504 core::dimension2du page_texture_size;
505 if (size <= 21) page_texture_size = core::dimension2du(256, 256);
506 else if (size <= 42) page_texture_size = core::dimension2du(512, 512);
507 else if (size <= 84) page_texture_size = core::dimension2du(1024, 1024);
508 else if (size <= 168) page_texture_size = core::dimension2du(2048, 2048);
509 else page_texture_size = core::dimension2du(4096, 4096);
511 if (page_texture_size.Width > max_texture_size.Width || page_texture_size.Height > max_texture_size.Height)
512 page_texture_size = max_texture_size;
514 if (!page->createPageTexture(pixel_mode, page_texture_size)) {
515 // TODO: add error message?
522 // Determine the number of glyph slots on the page and add it to the list of pages.
523 page->available_slots = (page_texture_size.Width / size) * (page_texture_size.Height / size);
524 Glyph_Pages.push_back(page);
529 void CGUITTFont::setTransparency(const bool flag)
531 use_transparency = flag;
535 void CGUITTFont::setMonochrome(const bool flag)
537 use_monochrome = flag;
541 void CGUITTFont::setFontHinting(const bool enable, const bool enable_auto_hinting)
543 use_hinting = enable;
544 use_auto_hinting = enable_auto_hinting;
548 void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position, video::SColor color, bool hcenter, bool vcenter, const core::rect<s32>* clip)
550 draw(EnrichedString(std::wstring(text.c_str()), color), position, color, hcenter, vcenter, clip);
553 void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& position, video::SColor color, bool hcenter, bool vcenter, const core::rect<s32>* clip)
555 std::vector<video::SColor> colors = text.getColors();
560 // Clear the glyph pages of their render information.
561 for (u32 i = 0; i < Glyph_Pages.size(); ++i)
563 Glyph_Pages[i]->render_positions.clear();
564 Glyph_Pages[i]->render_source_rects.clear();
567 // Set up some variables.
568 core::dimension2d<s32> textDimension;
569 core::position2d<s32> offset = position.UpperLeftCorner;
571 // Determine offset positions.
572 if (hcenter || vcenter)
574 textDimension = getDimension(text.c_str());
577 offset.X = ((position.getWidth() - textDimension.Width) >> 1) + offset.X;
580 offset.Y = ((position.getHeight() - textDimension.Height) >> 1) + offset.Y;
583 // Convert to a unicode string.
584 core::ustring utext = text.getString();
586 // Set up our render map.
587 core::map<u32, CGUITTGlyphPage*> Render_Map;
589 // Start parsing characters.
591 uchar32_t previousChar = 0;
592 core::ustring::const_iterator iter(utext);
593 std::vector<video::SColor> applied_colors;
594 while (!iter.atEnd())
596 uchar32_t currentChar = *iter;
597 n = getGlyphIndexByChar(currentChar);
598 bool visible = (Invisible.findFirst(currentChar) == -1);
599 bool lineBreak=false;
600 if (currentChar == L'\r') // Mac or Windows breaks
603 if (*(iter + 1) == (uchar32_t)'\n') // Windows line breaks.
604 currentChar = *(++iter);
606 else if (currentChar == (uchar32_t)'\n') // Unix breaks
614 offset.Y += font_metrics.height / 64;
615 offset.X = position.UpperLeftCorner.X;
618 offset.X += (position.getWidth() - textDimension.Width) >> 1;
623 if (n > 0 && visible)
625 // Calculate the glyph offset.
626 s32 offx = Glyphs[n-1].offset.X;
627 s32 offy = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y;
630 core::vector2di k = getKerning(currentChar, previousChar);
634 // Determine rendering information.
635 SGUITTGlyph& glyph = Glyphs[n-1];
636 CGUITTGlyphPage* const page = Glyph_Pages[glyph.glyph_page];
637 page->render_positions.push_back(core::position2di(offset.X + offx, offset.Y + offy));
638 page->render_source_rects.push_back(glyph.source_rect);
639 Render_Map.set(glyph.glyph_page, page);
640 u32 current_color = iter.getPos();
641 if (current_color < colors.size())
642 applied_colors.push_back(colors[current_color]);
646 offset.X += getWidthFromCharacter(currentChar);
648 else if (fallback != 0)
650 // Let the fallback font draw it, this isn't super efficient but hopefully that doesn't matter
651 wchar_t l1[] = { (wchar_t) currentChar, 0 }, l2 = (wchar_t) previousChar;
656 offset.X += fallback->getKerningWidth(l1, &l2);
657 offset.Y += fallback->getKerningHeight();
659 u32 current_color = iter.getPos();
660 fallback->draw(core::stringw(l1),
661 core::rect<s32>({offset.X-1, offset.Y-1}, position.LowerRightCorner), // ???
662 current_color < colors.size() ? colors[current_color] : video::SColor(255, 255, 255, 255),
666 offset.X += fallback->getDimension(l1).Width;
669 previousChar = currentChar;
674 update_glyph_pages();
675 core::map<u32, CGUITTGlyphPage*>::Iterator j = Render_Map.getIterator();
678 core::map<u32, CGUITTGlyphPage*>::Node* n = j.getNode();
680 if (n == 0) continue;
682 CGUITTGlyphPage* page = n->getValue();
685 for (size_t i = 0; i < page->render_positions.size(); ++i)
686 page->render_positions[i] += core::vector2di(shadow_offset, shadow_offset);
687 Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, video::SColor(shadow_alpha,0,0,0), true);
688 for (size_t i = 0; i < page->render_positions.size(); ++i)
689 page->render_positions[i] -= core::vector2di(shadow_offset, shadow_offset);
691 for (size_t i = 0; i < page->render_positions.size(); ++i) {
692 irr::video::SColor col;
693 if (!applied_colors.empty()) {
694 col = applied_colors[i < applied_colors.size() ? i : 0];
696 col = irr::video::SColor(255, 255, 255, 255);
698 if (!use_transparency)
699 col.color |= 0xff000000;
700 Driver->draw2DImage(page->texture, page->render_positions[i], page->render_source_rects[i], clip, col, true);
705 core::dimension2d<u32> CGUITTFont::getCharDimension(const wchar_t ch) const
707 return core::dimension2d<u32>(getWidthFromCharacter(ch), getHeightFromCharacter(ch));
710 core::dimension2d<u32> CGUITTFont::getDimension(const wchar_t* text) const
712 return getDimension(core::ustring(text));
715 core::dimension2d<u32> CGUITTFont::getDimension(const core::ustring& text) const
717 // Get the maximum font height. Unfortunately, we have to do this hack as
718 // Irrlicht will draw things wrong. In FreeType, the font size is the
719 // maximum size for a single glyph, but that glyph may hang "under" the
720 // draw line, increasing the total font height to beyond the set size.
721 // Irrlicht does not understand this concept when drawing fonts. Also, I
722 // add +1 to give it a 1 pixel blank border. This makes things like
723 // tooltips look nicer.
724 s32 test1 = getHeightFromCharacter((uchar32_t)'g') + 1;
725 s32 test2 = getHeightFromCharacter((uchar32_t)'j') + 1;
726 s32 test3 = getHeightFromCharacter((uchar32_t)'_') + 1;
727 s32 max_font_height = core::max_(test1, core::max_(test2, test3));
729 core::dimension2d<u32> text_dimension(0, max_font_height);
730 core::dimension2d<u32> line(0, max_font_height);
732 uchar32_t previousChar = 0;
733 core::ustring::const_iterator iter = text.begin();
734 for (; !iter.atEnd(); ++iter)
737 bool lineBreak = false;
738 if (p == '\r') // Mac or Windows line breaks.
741 if (*(iter + 1) == '\n')
747 else if (p == '\n') // Unix line breaks.
753 core::vector2di k = getKerning(p, previousChar);
757 // Check for linebreak.
761 text_dimension.Height += line.Height;
762 if (text_dimension.Width < line.Width)
763 text_dimension.Width = line.Width;
765 line.Height = max_font_height;
768 line.Width += getWidthFromCharacter(p);
770 if (text_dimension.Width < line.Width)
771 text_dimension.Width = line.Width;
773 return text_dimension;
776 inline u32 CGUITTFont::getWidthFromCharacter(wchar_t c) const
778 return getWidthFromCharacter((uchar32_t)c);
781 inline u32 CGUITTFont::getWidthFromCharacter(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 int w = Glyphs[n-1].advance.x / 64;
795 wchar_t s[] = { (wchar_t) c, 0 };
796 return fallback->getDimension(s).Width;
800 return (font_metrics.ascender / 64);
801 else return (font_metrics.ascender / 64) / 2;
804 inline u32 CGUITTFont::getHeightFromCharacter(wchar_t c) const
806 return getHeightFromCharacter((uchar32_t)c);
809 inline u32 CGUITTFont::getHeightFromCharacter(uchar32_t c) const
811 // Set the size of the face.
812 // This is because we cache faces and the face may have been set to a different size.
813 //FT_Set_Pixel_Sizes(tt_face, 0, size);
815 u32 n = getGlyphIndexByChar(c);
818 // Grab the true height of the character, taking into account underhanging glyphs.
819 s32 height = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y + Glyphs[n-1].source_rect.getHeight();
824 wchar_t s[] = { (wchar_t) c, 0 };
825 return fallback->getDimension(s).Height;
829 return (font_metrics.ascender / 64);
830 else return (font_metrics.ascender / 64) / 2;
833 u32 CGUITTFont::getGlyphIndexByChar(wchar_t c) const
835 return getGlyphIndexByChar((uchar32_t)c);
838 u32 CGUITTFont::getGlyphIndexByChar(uchar32_t c) const
841 u32 glyph = FT_Get_Char_Index(tt_face, c);
843 // Check for a valid glyph.
847 // If our glyph is already loaded, don't bother doing any batch loading code.
848 if (glyph != 0 && Glyphs[glyph - 1].isLoaded)
851 // Determine our batch loading positions.
852 u32 half_size = (batch_load_size / 2);
854 if (c > half_size) start_pos = c - half_size;
855 u32 end_pos = start_pos + batch_load_size;
857 // Load all our characters.
860 // Get the character we are going to load.
861 u32 char_index = FT_Get_Char_Index(tt_face, start_pos);
863 // If the glyph hasn't been loaded yet, do it now.
866 SGUITTGlyph& glyph = Glyphs[char_index - 1];
869 glyph.preload(char_index, tt_face, Driver, size, load_flags);
870 Glyph_Pages[glyph.glyph_page]->pushGlyphToBePaged(&glyph);
874 while (++start_pos < end_pos);
876 // Return our original character.
880 s32 CGUITTFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x) const
882 return getCharacterFromPos(core::ustring(text), pixel_x);
885 s32 CGUITTFont::getCharacterFromPos(const core::ustring& text, s32 pixel_x) const
891 uchar32_t previousChar = 0;
892 core::ustring::const_iterator iter = text.begin();
893 while (!iter.atEnd())
896 x += getWidthFromCharacter(c);
899 core::vector2di k = getKerning(c, previousChar);
913 void CGUITTFont::setKerningWidth(s32 kerning)
915 GlobalKerningWidth = kerning;
918 void CGUITTFont::setKerningHeight(s32 kerning)
920 GlobalKerningHeight = kerning;
923 s32 CGUITTFont::getKerningWidth(const wchar_t* thisLetter, const wchar_t* previousLetter) const
926 return GlobalKerningWidth;
927 if (thisLetter == 0 || previousLetter == 0)
930 return getKerningWidth((uchar32_t)*thisLetter, (uchar32_t)*previousLetter);
933 s32 CGUITTFont::getKerningWidth(const uchar32_t thisLetter, const uchar32_t previousLetter) const
935 // Return only the kerning width.
936 return getKerning(thisLetter, previousLetter).X;
939 s32 CGUITTFont::getKerningHeight() const
941 // FreeType 2 currently doesn't return any height kerning information.
942 return GlobalKerningHeight;
945 core::vector2di CGUITTFont::getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const
947 return getKerning((uchar32_t)thisLetter, (uchar32_t)previousLetter);
950 core::vector2di CGUITTFont::getKerning(const uchar32_t thisLetter, const uchar32_t previousLetter) const
952 if (tt_face == 0 || thisLetter == 0 || previousLetter == 0)
953 return core::vector2di();
955 // Set the size of the face.
956 // This is because we cache faces and the face may have been set to a different size.
957 FT_Set_Pixel_Sizes(tt_face, 0, size);
959 core::vector2di ret(GlobalKerningWidth, GlobalKerningHeight);
961 u32 n = getGlyphIndexByChar(thisLetter);
963 // If we don't have this glyph, ask fallback font
967 wchar_t l1 = (wchar_t) thisLetter, l2 = (wchar_t) previousLetter;
968 ret.X = fallback->getKerningWidth(&l1, &l2);
969 ret.Y = fallback->getKerningHeight();
974 // If we don't have kerning, no point in continuing.
975 if (!FT_HAS_KERNING(tt_face))
978 // Get the kerning information.
980 FT_Get_Kerning(tt_face, getGlyphIndexByChar(previousLetter), n, FT_KERNING_DEFAULT, &v);
982 // If we have a scalable font, the return value will be in font points.
983 if (FT_IS_SCALABLE(tt_face))
985 // Font points, so divide by 64.
998 void CGUITTFont::setInvisibleCharacters(const wchar_t *s)
1000 core::ustring us(s);
1004 void CGUITTFont::setInvisibleCharacters(const core::ustring& s)
1009 video::IImage* CGUITTFont::createTextureFromChar(const uchar32_t& ch)
1011 u32 n = getGlyphIndexByChar(ch);
1013 n = getGlyphIndexByChar((uchar32_t) core::unicode::UTF_REPLACEMENT_CHARACTER);
1015 const SGUITTGlyph& glyph = Glyphs[n-1];
1016 CGUITTGlyphPage* page = Glyph_Pages[glyph.glyph_page];
1019 page->updateTexture();
1021 video::ITexture* tex = page->texture;
1023 // Acquire a read-only lock of the corresponding page texture.
1024 void* ptr = tex->lock(video::ETLM_READ_ONLY);
1026 video::ECOLOR_FORMAT format = tex->getColorFormat();
1027 core::dimension2du tex_size = tex->getOriginalSize();
1028 video::IImage* pageholder = Driver->createImageFromData(format, tex_size, ptr, true, false);
1030 // Copy the image data out of the page texture.
1031 core::dimension2du glyph_size(glyph.source_rect.getSize());
1032 video::IImage* image = Driver->createImage(format, glyph_size);
1033 pageholder->copyTo(image, core::position2di(0, 0), glyph.source_rect);
1039 video::ITexture* CGUITTFont::getPageTextureByIndex(const u32& page_index) const
1041 if (page_index < Glyph_Pages.size())
1042 return Glyph_Pages[page_index]->texture;
1047 void CGUITTFont::createSharedPlane()
1052 | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
1053 |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
1057 using namespace core;
1058 using namespace video;
1059 using namespace scene;
1060 S3DVertex vertices[4];
1061 u16 indices[6] = {0,2,3,3,1,0};
1062 vertices[0] = S3DVertex(vector3df(0,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,1));
1063 vertices[1] = S3DVertex(vector3df(1,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,1));
1064 vertices[2] = S3DVertex(vector3df(0, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,0));
1065 vertices[3] = S3DVertex(vector3df(1, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,0));
1067 SMeshBuffer* buf = new SMeshBuffer();
1068 buf->append(vertices, 4, indices, 6);
1070 shared_plane_.addMeshBuffer( buf );
1072 shared_plane_ptr_ = &shared_plane_;
1073 buf->drop(); //the addMeshBuffer method will grab it, so we can drop this ptr.
1076 core::dimension2d<u32> CGUITTFont::getDimensionUntilEndOfLine(const wchar_t* p) const
1079 for (const wchar_t* temp = p; temp && *temp != '\0' && *temp != L'\r' && *temp != L'\n'; ++temp )
1082 return getDimension(s.c_str());
1085 core::array<scene::ISceneNode*> CGUITTFont::addTextSceneNode(const wchar_t* text, scene::ISceneManager* smgr, scene::ISceneNode* parent, const video::SColor& color, bool center)
1087 using namespace core;
1088 using namespace video;
1089 using namespace scene;
1091 array<scene::ISceneNode*> container;
1093 if (!Driver || !smgr) return container;
1095 parent = smgr->addEmptySceneNode(smgr->getRootSceneNode(), -1);
1096 // if you don't specify parent, then we add a empty node attached to the root node
1097 // this is generally undesirable.
1099 if (!shared_plane_ptr_) //this points to a static mesh that contains the plane
1100 createSharedPlane(); //if it's not initialized, we create one.
1102 dimension2d<s32> text_size(getDimension(text)); //convert from unsigned to signed.
1103 vector3df start_point(0, 0, 0), offset;
1106 Because we are considering adding texts into 3D world, all Y axis vectors are inverted.
1109 // There's currently no "vertical center" concept when you apply text scene node to the 3D world.
1112 offset.X = start_point.X = -text_size.Width / 2.f;
1113 offset.Y = start_point.Y = +text_size.Height/ 2.f;
1114 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text).Width) >> 1;
1117 // the default font material
1119 mat.setFlag(video::EMF_LIGHTING, true);
1120 mat.setFlag(video::EMF_ZWRITE_ENABLE, false);
1121 mat.setFlag(video::EMF_NORMALIZE_NORMALS, true);
1122 mat.ColorMaterial = video::ECM_NONE;
1123 mat.MaterialType = use_transparency ? video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_SOLID;
1124 mat.MaterialTypeParam = 0.01f;
1125 mat.DiffuseColor = color;
1127 wchar_t current_char = 0, previous_char = 0;
1130 array<u32> glyph_indices;
1134 current_char = *text;
1135 bool line_break=false;
1136 if (current_char == L'\r') // Mac or Windows breaks
1139 if (*(text + 1) == L'\n') // Windows line breaks.
1140 current_char = *(++text);
1142 else if (current_char == L'\n') // Unix breaks
1150 offset.Y -= tt_face->size->metrics.ascender / 64;
1151 offset.X = start_point.X;
1153 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text+1).Width) >> 1;
1158 n = getGlyphIndexByChar(current_char);
1161 glyph_indices.push_back( n );
1163 // Store glyph size and offset informations.
1164 SGUITTGlyph const& glyph = Glyphs[n-1];
1165 u32 texw = glyph.source_rect.getWidth();
1166 u32 texh = glyph.source_rect.getHeight();
1167 s32 offx = glyph.offset.X;
1168 s32 offy = (font_metrics.ascender / 64) - glyph.offset.Y;
1171 vector2di k = getKerning(current_char, previous_char);
1175 vector3df current_pos(offset.X + offx, offset.Y - offy, 0);
1176 dimension2d<u32> letter_size = dimension2d<u32>(texw, texh);
1178 // Now we copy planes corresponding to the letter size.
1179 IMeshManipulator* mani = smgr->getMeshManipulator();
1180 IMesh* meshcopy = mani->createMeshCopy(shared_plane_ptr_);
1181 mani->scale(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1));
1183 ISceneNode* current_node = smgr->addMeshSceneNode(meshcopy, parent, -1, current_pos);
1186 current_node->getMaterial(0) = mat;
1187 current_node->setAutomaticCulling(EAC_OFF);
1188 current_node->setIsDebugObject(true); //so the picking won't have any effect on individual letter
1189 //current_node->setDebugDataVisible(EDS_BBOX); //de-comment this when debugging
1191 container.push_back(current_node);
1193 offset.X += getWidthFromCharacter(current_char);
1194 // Note that fallback font handling is missing here (Minetest never uses this)
1196 previous_char = current_char;
1201 update_glyph_pages();
1202 //only after we update the textures can we use the glyph page textures.
1204 for (u32 i = 0; i < glyph_indices.size(); ++i)
1206 u32 n = glyph_indices[i];
1207 SGUITTGlyph const& glyph = Glyphs[n-1];
1208 ITexture* current_tex = Glyph_Pages[glyph.glyph_page]->texture;
1209 f32 page_texture_size = (f32)current_tex->getSize().Width;
1210 //Now we calculate the UV position according to the texture size and the source rect.
1214 // | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
1215 // |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
1218 f32 u1 = glyph.source_rect.UpperLeftCorner.X / page_texture_size;
1219 f32 u2 = u1 + (glyph.source_rect.getWidth() / page_texture_size);
1220 f32 v1 = glyph.source_rect.UpperLeftCorner.Y / page_texture_size;
1221 f32 v2 = v1 + (glyph.source_rect.getHeight() / page_texture_size);
1223 //we can be quite sure that this is IMeshSceneNode, because we just added them in the above loop.
1224 IMeshSceneNode* node = static_cast<IMeshSceneNode*>(container[i]);
1226 S3DVertex* pv = static_cast<S3DVertex*>(node->getMesh()->getMeshBuffer(0)->getVertices());
1227 //pv[0].TCoords.Y = pv[1].TCoords.Y = (letter_size.Height - 1) / static_cast<f32>(letter_size.Height);
1228 //pv[1].TCoords.X = pv[3].TCoords.X = (letter_size.Width - 1) / static_cast<f32>(letter_size.Width);
1229 pv[0].TCoords = vector2df(u1, v2);
1230 pv[1].TCoords = vector2df(u2, v2);
1231 pv[2].TCoords = vector2df(u1, v1);
1232 pv[3].TCoords = vector2df(u2, v1);
1234 container[i]->getMaterial(0).setTexture(0, current_tex);
1240 } // end namespace gui
1241 } // end namespace irr