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, hcenter, vcenter, clip);
553 void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& position, bool hcenter, bool vcenter, const core::rect<s32>* clip)
555 const 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();
565 Glyph_Pages[i]->render_colors.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 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 if (iter.getPos() < colors.size())
640 page->render_colors.push_back(colors[iter.getPos()]);
642 page->render_colors.push_back(video::SColor(255,255,255,255));
643 Render_Map.set(glyph.glyph_page, page);
647 offset.X += getWidthFromCharacter(currentChar);
649 else if (fallback != 0)
651 // Let the fallback font draw it, this isn't super efficient but hopefully that doesn't matter
652 wchar_t l1[] = { (wchar_t) currentChar, 0 }, l2 = (wchar_t) previousChar;
657 offset.X += fallback->getKerningWidth(l1, &l2);
658 offset.Y += fallback->getKerningHeight();
660 u32 current_color = iter.getPos();
661 fallback->draw(core::stringw(l1),
662 core::rect<s32>({offset.X-1, offset.Y-1}, position.LowerRightCorner), // ???
663 current_color < colors.size() ? colors[current_color] : video::SColor(255, 255, 255, 255),
667 offset.X += fallback->getDimension(l1).Width;
670 previousChar = currentChar;
675 update_glyph_pages();
676 core::map<u32, CGUITTGlyphPage*>::Iterator j = Render_Map.getIterator();
679 core::map<u32, CGUITTGlyphPage*>::Node* n = j.getNode();
681 if (n == 0) continue;
683 CGUITTGlyphPage* page = n->getValue();
686 for (size_t i = 0; i < page->render_positions.size(); ++i)
687 page->render_positions[i] += core::vector2di(shadow_offset, shadow_offset);
688 Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, video::SColor(shadow_alpha,0,0,0), true);
689 for (size_t i = 0; i < page->render_positions.size(); ++i)
690 page->render_positions[i] -= core::vector2di(shadow_offset, shadow_offset);
692 // render runs of matching color in batch
694 video::SColor colprev;
695 for (size_t i = 0; i < page->render_positions.size(); ++i) {
697 colprev = page->render_colors[i];
700 while (i < page->render_positions.size() && page->render_colors[i] == colprev);
701 core::array<core::vector2di> tmp_positions;
702 core::array<core::recti> tmp_source_rects;
703 tmp_positions.set_pointer(&page->render_positions[ibegin], i - ibegin, false, false); // no copy
704 tmp_source_rects.set_pointer(&page->render_source_rects[ibegin], i - ibegin, false, false);
707 if (!use_transparency)
708 colprev.color |= 0xff000000;
709 Driver->draw2DImageBatch(page->texture, tmp_positions, tmp_source_rects, clip, colprev, true);
714 core::dimension2d<u32> CGUITTFont::getCharDimension(const wchar_t ch) const
716 return core::dimension2d<u32>(getWidthFromCharacter(ch), getHeightFromCharacter(ch));
719 core::dimension2d<u32> CGUITTFont::getDimension(const wchar_t* text) const
721 return getDimension(core::ustring(text));
724 core::dimension2d<u32> CGUITTFont::getDimension(const core::ustring& text) const
726 // Get the maximum font height. Unfortunately, we have to do this hack as
727 // Irrlicht will draw things wrong. In FreeType, the font size is the
728 // maximum size for a single glyph, but that glyph may hang "under" the
729 // draw line, increasing the total font height to beyond the set size.
730 // Irrlicht does not understand this concept when drawing fonts. Also, I
731 // add +1 to give it a 1 pixel blank border. This makes things like
732 // tooltips look nicer.
733 s32 test1 = getHeightFromCharacter((uchar32_t)'g') + 1;
734 s32 test2 = getHeightFromCharacter((uchar32_t)'j') + 1;
735 s32 test3 = getHeightFromCharacter((uchar32_t)'_') + 1;
736 s32 max_font_height = core::max_(test1, core::max_(test2, test3));
738 core::dimension2d<u32> text_dimension(0, max_font_height);
739 core::dimension2d<u32> line(0, max_font_height);
741 uchar32_t previousChar = 0;
742 core::ustring::const_iterator iter = text.begin();
743 for (; !iter.atEnd(); ++iter)
746 bool lineBreak = false;
747 if (p == '\r') // Mac or Windows line breaks.
750 if (*(iter + 1) == '\n')
756 else if (p == '\n') // Unix line breaks.
762 core::vector2di k = getKerning(p, previousChar);
766 // Check for linebreak.
770 text_dimension.Height += line.Height;
771 if (text_dimension.Width < line.Width)
772 text_dimension.Width = line.Width;
774 line.Height = max_font_height;
777 line.Width += getWidthFromCharacter(p);
779 if (text_dimension.Width < line.Width)
780 text_dimension.Width = line.Width;
782 return text_dimension;
785 inline u32 CGUITTFont::getWidthFromCharacter(wchar_t c) const
787 return getWidthFromCharacter((uchar32_t)c);
790 inline u32 CGUITTFont::getWidthFromCharacter(uchar32_t c) const
792 // Set the size of the face.
793 // This is because we cache faces and the face may have been set to a different size.
794 //FT_Set_Pixel_Sizes(tt_face, 0, size);
796 u32 n = getGlyphIndexByChar(c);
799 int w = Glyphs[n-1].advance.x / 64;
804 wchar_t s[] = { (wchar_t) c, 0 };
805 return fallback->getDimension(s).Width;
809 return (font_metrics.ascender / 64);
810 else return (font_metrics.ascender / 64) / 2;
813 inline u32 CGUITTFont::getHeightFromCharacter(wchar_t c) const
815 return getHeightFromCharacter((uchar32_t)c);
818 inline u32 CGUITTFont::getHeightFromCharacter(uchar32_t c) const
820 // Set the size of the face.
821 // This is because we cache faces and the face may have been set to a different size.
822 //FT_Set_Pixel_Sizes(tt_face, 0, size);
824 u32 n = getGlyphIndexByChar(c);
827 // Grab the true height of the character, taking into account underhanging glyphs.
828 s32 height = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y + Glyphs[n-1].source_rect.getHeight();
833 wchar_t s[] = { (wchar_t) c, 0 };
834 return fallback->getDimension(s).Height;
838 return (font_metrics.ascender / 64);
839 else return (font_metrics.ascender / 64) / 2;
842 u32 CGUITTFont::getGlyphIndexByChar(wchar_t c) const
844 return getGlyphIndexByChar((uchar32_t)c);
847 u32 CGUITTFont::getGlyphIndexByChar(uchar32_t c) const
850 u32 glyph = FT_Get_Char_Index(tt_face, c);
852 // Check for a valid glyph.
856 // If our glyph is already loaded, don't bother doing any batch loading code.
857 if (glyph != 0 && Glyphs[glyph - 1].isLoaded)
860 // Determine our batch loading positions.
861 u32 half_size = (batch_load_size / 2);
863 if (c > half_size) start_pos = c - half_size;
864 u32 end_pos = start_pos + batch_load_size;
866 // Load all our characters.
869 // Get the character we are going to load.
870 u32 char_index = FT_Get_Char_Index(tt_face, start_pos);
872 // If the glyph hasn't been loaded yet, do it now.
875 SGUITTGlyph& glyph = Glyphs[char_index - 1];
878 glyph.preload(char_index, tt_face, Driver, size, load_flags);
879 Glyph_Pages[glyph.glyph_page]->pushGlyphToBePaged(&glyph);
883 while (++start_pos < end_pos);
885 // Return our original character.
889 s32 CGUITTFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x) const
891 return getCharacterFromPos(core::ustring(text), pixel_x);
894 s32 CGUITTFont::getCharacterFromPos(const core::ustring& text, s32 pixel_x) const
900 uchar32_t previousChar = 0;
901 core::ustring::const_iterator iter = text.begin();
902 while (!iter.atEnd())
905 x += getWidthFromCharacter(c);
908 core::vector2di k = getKerning(c, previousChar);
922 void CGUITTFont::setKerningWidth(s32 kerning)
924 GlobalKerningWidth = kerning;
927 void CGUITTFont::setKerningHeight(s32 kerning)
929 GlobalKerningHeight = kerning;
932 s32 CGUITTFont::getKerningWidth(const wchar_t* thisLetter, const wchar_t* previousLetter) const
935 return GlobalKerningWidth;
936 if (thisLetter == 0 || previousLetter == 0)
939 return getKerningWidth((uchar32_t)*thisLetter, (uchar32_t)*previousLetter);
942 s32 CGUITTFont::getKerningWidth(const uchar32_t thisLetter, const uchar32_t previousLetter) const
944 // Return only the kerning width.
945 return getKerning(thisLetter, previousLetter).X;
948 s32 CGUITTFont::getKerningHeight() const
950 // FreeType 2 currently doesn't return any height kerning information.
951 return GlobalKerningHeight;
954 core::vector2di CGUITTFont::getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const
956 return getKerning((uchar32_t)thisLetter, (uchar32_t)previousLetter);
959 core::vector2di CGUITTFont::getKerning(const uchar32_t thisLetter, const uchar32_t previousLetter) const
961 if (tt_face == 0 || thisLetter == 0 || previousLetter == 0)
962 return core::vector2di();
964 // Set the size of the face.
965 // This is because we cache faces and the face may have been set to a different size.
966 FT_Set_Pixel_Sizes(tt_face, 0, size);
968 core::vector2di ret(GlobalKerningWidth, GlobalKerningHeight);
970 u32 n = getGlyphIndexByChar(thisLetter);
972 // If we don't have this glyph, ask fallback font
976 wchar_t l1 = (wchar_t) thisLetter, l2 = (wchar_t) previousLetter;
977 ret.X = fallback->getKerningWidth(&l1, &l2);
978 ret.Y = fallback->getKerningHeight();
983 // If we don't have kerning, no point in continuing.
984 if (!FT_HAS_KERNING(tt_face))
987 // Get the kerning information.
989 FT_Get_Kerning(tt_face, getGlyphIndexByChar(previousLetter), n, FT_KERNING_DEFAULT, &v);
991 // If we have a scalable font, the return value will be in font points.
992 if (FT_IS_SCALABLE(tt_face))
994 // Font points, so divide by 64.
1007 void CGUITTFont::setInvisibleCharacters(const wchar_t *s)
1009 core::ustring us(s);
1013 void CGUITTFont::setInvisibleCharacters(const core::ustring& s)
1018 video::IImage* CGUITTFont::createTextureFromChar(const uchar32_t& ch)
1020 u32 n = getGlyphIndexByChar(ch);
1022 n = getGlyphIndexByChar((uchar32_t) core::unicode::UTF_REPLACEMENT_CHARACTER);
1024 const SGUITTGlyph& glyph = Glyphs[n-1];
1025 CGUITTGlyphPage* page = Glyph_Pages[glyph.glyph_page];
1028 page->updateTexture();
1030 video::ITexture* tex = page->texture;
1032 // Acquire a read-only lock of the corresponding page texture.
1033 void* ptr = tex->lock(video::ETLM_READ_ONLY);
1035 video::ECOLOR_FORMAT format = tex->getColorFormat();
1036 core::dimension2du tex_size = tex->getOriginalSize();
1037 video::IImage* pageholder = Driver->createImageFromData(format, tex_size, ptr, true, false);
1039 // Copy the image data out of the page texture.
1040 core::dimension2du glyph_size(glyph.source_rect.getSize());
1041 video::IImage* image = Driver->createImage(format, glyph_size);
1042 pageholder->copyTo(image, core::position2di(0, 0), glyph.source_rect);
1048 video::ITexture* CGUITTFont::getPageTextureByIndex(const u32& page_index) const
1050 if (page_index < Glyph_Pages.size())
1051 return Glyph_Pages[page_index]->texture;
1056 void CGUITTFont::createSharedPlane()
1061 | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
1062 |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
1066 using namespace core;
1067 using namespace video;
1068 using namespace scene;
1069 S3DVertex vertices[4];
1070 u16 indices[6] = {0,2,3,3,1,0};
1071 vertices[0] = S3DVertex(vector3df(0,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,1));
1072 vertices[1] = S3DVertex(vector3df(1,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,1));
1073 vertices[2] = S3DVertex(vector3df(0, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,0));
1074 vertices[3] = S3DVertex(vector3df(1, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,0));
1076 SMeshBuffer* buf = new SMeshBuffer();
1077 buf->append(vertices, 4, indices, 6);
1079 shared_plane_.addMeshBuffer( buf );
1081 shared_plane_ptr_ = &shared_plane_;
1082 buf->drop(); //the addMeshBuffer method will grab it, so we can drop this ptr.
1085 core::dimension2d<u32> CGUITTFont::getDimensionUntilEndOfLine(const wchar_t* p) const
1088 for (const wchar_t* temp = p; temp && *temp != '\0' && *temp != L'\r' && *temp != L'\n'; ++temp )
1091 return getDimension(s.c_str());
1094 core::array<scene::ISceneNode*> CGUITTFont::addTextSceneNode(const wchar_t* text, scene::ISceneManager* smgr, scene::ISceneNode* parent, const video::SColor& color, bool center)
1096 using namespace core;
1097 using namespace video;
1098 using namespace scene;
1100 array<scene::ISceneNode*> container;
1102 if (!Driver || !smgr) return container;
1104 parent = smgr->addEmptySceneNode(smgr->getRootSceneNode(), -1);
1105 // if you don't specify parent, then we add a empty node attached to the root node
1106 // this is generally undesirable.
1108 if (!shared_plane_ptr_) //this points to a static mesh that contains the plane
1109 createSharedPlane(); //if it's not initialized, we create one.
1111 dimension2d<s32> text_size(getDimension(text)); //convert from unsigned to signed.
1112 vector3df start_point(0, 0, 0), offset;
1115 Because we are considering adding texts into 3D world, all Y axis vectors are inverted.
1118 // There's currently no "vertical center" concept when you apply text scene node to the 3D world.
1121 offset.X = start_point.X = -text_size.Width / 2.f;
1122 offset.Y = start_point.Y = +text_size.Height/ 2.f;
1123 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text).Width) >> 1;
1126 // the default font material
1128 mat.setFlag(video::EMF_LIGHTING, true);
1129 mat.setFlag(video::EMF_ZWRITE_ENABLE, false);
1130 mat.setFlag(video::EMF_NORMALIZE_NORMALS, true);
1131 mat.ColorMaterial = video::ECM_NONE;
1132 mat.MaterialType = use_transparency ? video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_SOLID;
1133 mat.MaterialTypeParam = 0.01f;
1134 mat.DiffuseColor = color;
1136 wchar_t current_char = 0, previous_char = 0;
1139 array<u32> glyph_indices;
1143 current_char = *text;
1144 bool line_break=false;
1145 if (current_char == L'\r') // Mac or Windows breaks
1148 if (*(text + 1) == L'\n') // Windows line breaks.
1149 current_char = *(++text);
1151 else if (current_char == L'\n') // Unix breaks
1159 offset.Y -= tt_face->size->metrics.ascender / 64;
1160 offset.X = start_point.X;
1162 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text+1).Width) >> 1;
1167 n = getGlyphIndexByChar(current_char);
1170 glyph_indices.push_back( n );
1172 // Store glyph size and offset informations.
1173 SGUITTGlyph const& glyph = Glyphs[n-1];
1174 u32 texw = glyph.source_rect.getWidth();
1175 u32 texh = glyph.source_rect.getHeight();
1176 s32 offx = glyph.offset.X;
1177 s32 offy = (font_metrics.ascender / 64) - glyph.offset.Y;
1180 vector2di k = getKerning(current_char, previous_char);
1184 vector3df current_pos(offset.X + offx, offset.Y - offy, 0);
1185 dimension2d<u32> letter_size = dimension2d<u32>(texw, texh);
1187 // Now we copy planes corresponding to the letter size.
1188 IMeshManipulator* mani = smgr->getMeshManipulator();
1189 IMesh* meshcopy = mani->createMeshCopy(shared_plane_ptr_);
1190 mani->scale(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1));
1192 ISceneNode* current_node = smgr->addMeshSceneNode(meshcopy, parent, -1, current_pos);
1195 current_node->getMaterial(0) = mat;
1196 current_node->setAutomaticCulling(EAC_OFF);
1197 current_node->setIsDebugObject(true); //so the picking won't have any effect on individual letter
1198 //current_node->setDebugDataVisible(EDS_BBOX); //de-comment this when debugging
1200 container.push_back(current_node);
1202 offset.X += getWidthFromCharacter(current_char);
1203 // Note that fallback font handling is missing here (Minetest never uses this)
1205 previous_char = current_char;
1210 update_glyph_pages();
1211 //only after we update the textures can we use the glyph page textures.
1213 for (u32 i = 0; i < glyph_indices.size(); ++i)
1215 u32 n = glyph_indices[i];
1216 SGUITTGlyph const& glyph = Glyphs[n-1];
1217 ITexture* current_tex = Glyph_Pages[glyph.glyph_page]->texture;
1218 f32 page_texture_size = (f32)current_tex->getSize().Width;
1219 //Now we calculate the UV position according to the texture size and the source rect.
1223 // | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
1224 // |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
1227 f32 u1 = glyph.source_rect.UpperLeftCorner.X / page_texture_size;
1228 f32 u2 = u1 + (glyph.source_rect.getWidth() / page_texture_size);
1229 f32 v1 = glyph.source_rect.UpperLeftCorner.Y / page_texture_size;
1230 f32 v2 = v1 + (glyph.source_rect.getHeight() / page_texture_size);
1232 //we can be quite sure that this is IMeshSceneNode, because we just added them in the above loop.
1233 IMeshSceneNode* node = static_cast<IMeshSceneNode*>(container[i]);
1235 S3DVertex* pv = static_cast<S3DVertex*>(node->getMesh()->getMeshBuffer(0)->getVertices());
1236 //pv[0].TCoords.Y = pv[1].TCoords.Y = (letter_size.Height - 1) / static_cast<f32>(letter_size.Height);
1237 //pv[1].TCoords.X = pv[3].TCoords.X = (letter_size.Width - 1) / static_cast<f32>(letter_size.Width);
1238 pv[0].TCoords = vector2df(u1, v2);
1239 pv[1].TCoords = vector2df(u2, v2);
1240 pv[2].TCoords = vector2df(u1, v1);
1241 pv[3].TCoords = vector2df(u2, v1);
1243 container[i]->getMaterial(0).setTexture(0, current_tex);
1249 } // end namespace gui
1250 } // end namespace irr