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 std::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 auto node = c_faces.find(filename);
324 if (node == c_faces.end())
326 face = new SGUITTFace();
327 c_faces.emplace(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.erase(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.erase(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.erase(filename);
374 // Using another instance of this face.
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 auto n = c_faces.find(filename);
419 if (n != c_faces.end())
421 SGUITTFace* f = n->second;
423 // Drop our face. If this was the last face, the destructor will clean up.
425 c_faces.erase(filename);
427 // If there are no more faces referenced by FreeType, clean up.
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 std::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[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 auto it = Render_Map.begin();
677 auto ie = Render_Map.end();
680 CGUITTGlyphPage* page = it->second;
684 for (size_t i = 0; i < page->render_positions.size(); ++i)
685 page->render_positions[i] += core::vector2di(shadow_offset, shadow_offset);
686 Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, video::SColor(shadow_alpha,0,0,0), true);
687 for (size_t i = 0; i < page->render_positions.size(); ++i)
688 page->render_positions[i] -= core::vector2di(shadow_offset, shadow_offset);
690 // render runs of matching color in batch
692 video::SColor colprev;
693 for (size_t i = 0; i < page->render_positions.size(); ++i) {
695 colprev = page->render_colors[i];
698 while (i < page->render_positions.size() && page->render_colors[i] == colprev);
699 core::array<core::vector2di> tmp_positions;
700 core::array<core::recti> tmp_source_rects;
701 tmp_positions.set_pointer(&page->render_positions[ibegin], i - ibegin, false, false); // no copy
702 tmp_source_rects.set_pointer(&page->render_source_rects[ibegin], i - ibegin, false, false);
705 if (!use_transparency)
706 colprev.color |= 0xff000000;
707 Driver->draw2DImageBatch(page->texture, tmp_positions, tmp_source_rects, clip, colprev, true);
712 core::dimension2d<u32> CGUITTFont::getCharDimension(const wchar_t ch) const
714 return core::dimension2d<u32>(getWidthFromCharacter(ch), getHeightFromCharacter(ch));
717 core::dimension2d<u32> CGUITTFont::getDimension(const wchar_t* text) const
719 return getDimension(core::ustring(text));
722 core::dimension2d<u32> CGUITTFont::getDimension(const core::ustring& text) const
724 // Get the maximum font height. Unfortunately, we have to do this hack as
725 // Irrlicht will draw things wrong. In FreeType, the font size is the
726 // maximum size for a single glyph, but that glyph may hang "under" the
727 // draw line, increasing the total font height to beyond the set size.
728 // Irrlicht does not understand this concept when drawing fonts. Also, I
729 // add +1 to give it a 1 pixel blank border. This makes things like
730 // tooltips look nicer.
731 s32 test1 = getHeightFromCharacter((uchar32_t)'g') + 1;
732 s32 test2 = getHeightFromCharacter((uchar32_t)'j') + 1;
733 s32 test3 = getHeightFromCharacter((uchar32_t)'_') + 1;
734 s32 max_font_height = core::max_(test1, core::max_(test2, test3));
736 core::dimension2d<u32> text_dimension(0, max_font_height);
737 core::dimension2d<u32> line(0, max_font_height);
739 uchar32_t previousChar = 0;
740 core::ustring::const_iterator iter = text.begin();
741 for (; !iter.atEnd(); ++iter)
744 bool lineBreak = false;
745 if (p == '\r') // Mac or Windows line breaks.
748 if (*(iter + 1) == '\n')
754 else if (p == '\n') // Unix line breaks.
760 core::vector2di k = getKerning(p, previousChar);
764 // Check for linebreak.
768 text_dimension.Height += line.Height;
769 if (text_dimension.Width < line.Width)
770 text_dimension.Width = line.Width;
772 line.Height = max_font_height;
775 line.Width += getWidthFromCharacter(p);
777 if (text_dimension.Width < line.Width)
778 text_dimension.Width = line.Width;
780 return text_dimension;
783 inline u32 CGUITTFont::getWidthFromCharacter(wchar_t c) const
785 return getWidthFromCharacter((uchar32_t)c);
788 inline u32 CGUITTFont::getWidthFromCharacter(uchar32_t c) const
790 // Set the size of the face.
791 // This is because we cache faces and the face may have been set to a different size.
792 //FT_Set_Pixel_Sizes(tt_face, 0, size);
794 u32 n = getGlyphIndexByChar(c);
797 int w = Glyphs[n-1].advance.x / 64;
802 wchar_t s[] = { (wchar_t) c, 0 };
803 return fallback->getDimension(s).Width;
807 return (font_metrics.ascender / 64);
808 else return (font_metrics.ascender / 64) / 2;
811 inline u32 CGUITTFont::getHeightFromCharacter(wchar_t c) const
813 return getHeightFromCharacter((uchar32_t)c);
816 inline u32 CGUITTFont::getHeightFromCharacter(uchar32_t c) const
818 // Set the size of the face.
819 // This is because we cache faces and the face may have been set to a different size.
820 //FT_Set_Pixel_Sizes(tt_face, 0, size);
822 u32 n = getGlyphIndexByChar(c);
825 // Grab the true height of the character, taking into account underhanging glyphs.
826 s32 height = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y + Glyphs[n-1].source_rect.getHeight();
831 wchar_t s[] = { (wchar_t) c, 0 };
832 return fallback->getDimension(s).Height;
836 return (font_metrics.ascender / 64);
837 else return (font_metrics.ascender / 64) / 2;
840 u32 CGUITTFont::getGlyphIndexByChar(wchar_t c) const
842 return getGlyphIndexByChar((uchar32_t)c);
845 u32 CGUITTFont::getGlyphIndexByChar(uchar32_t c) const
848 u32 glyph = FT_Get_Char_Index(tt_face, c);
850 // Check for a valid glyph.
854 // If our glyph is already loaded, don't bother doing any batch loading code.
855 if (glyph != 0 && Glyphs[glyph - 1].isLoaded)
858 // Determine our batch loading positions.
859 u32 half_size = (batch_load_size / 2);
861 if (c > half_size) start_pos = c - half_size;
862 u32 end_pos = start_pos + batch_load_size;
864 // Load all our characters.
867 // Get the character we are going to load.
868 u32 char_index = FT_Get_Char_Index(tt_face, start_pos);
870 // If the glyph hasn't been loaded yet, do it now.
873 SGUITTGlyph& glyph = Glyphs[char_index - 1];
876 glyph.preload(char_index, tt_face, Driver, size, load_flags);
877 Glyph_Pages[glyph.glyph_page]->pushGlyphToBePaged(&glyph);
881 while (++start_pos < end_pos);
883 // Return our original character.
887 s32 CGUITTFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x) const
889 return getCharacterFromPos(core::ustring(text), pixel_x);
892 s32 CGUITTFont::getCharacterFromPos(const core::ustring& text, s32 pixel_x) const
898 uchar32_t previousChar = 0;
899 core::ustring::const_iterator iter = text.begin();
900 while (!iter.atEnd())
903 x += getWidthFromCharacter(c);
906 core::vector2di k = getKerning(c, previousChar);
920 void CGUITTFont::setKerningWidth(s32 kerning)
922 GlobalKerningWidth = kerning;
925 void CGUITTFont::setKerningHeight(s32 kerning)
927 GlobalKerningHeight = kerning;
930 s32 CGUITTFont::getKerningWidth(const wchar_t* thisLetter, const wchar_t* previousLetter) const
933 return GlobalKerningWidth;
934 if (thisLetter == 0 || previousLetter == 0)
937 return getKerningWidth((uchar32_t)*thisLetter, (uchar32_t)*previousLetter);
940 s32 CGUITTFont::getKerningWidth(const uchar32_t thisLetter, const uchar32_t previousLetter) const
942 // Return only the kerning width.
943 return getKerning(thisLetter, previousLetter).X;
946 s32 CGUITTFont::getKerningHeight() const
948 // FreeType 2 currently doesn't return any height kerning information.
949 return GlobalKerningHeight;
952 core::vector2di CGUITTFont::getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const
954 return getKerning((uchar32_t)thisLetter, (uchar32_t)previousLetter);
957 core::vector2di CGUITTFont::getKerning(const uchar32_t thisLetter, const uchar32_t previousLetter) const
959 if (tt_face == 0 || thisLetter == 0 || previousLetter == 0)
960 return core::vector2di();
962 // Set the size of the face.
963 // This is because we cache faces and the face may have been set to a different size.
964 FT_Set_Pixel_Sizes(tt_face, 0, size);
966 core::vector2di ret(GlobalKerningWidth, GlobalKerningHeight);
968 u32 n = getGlyphIndexByChar(thisLetter);
970 // If we don't have this glyph, ask fallback font
974 wchar_t l1 = (wchar_t) thisLetter, l2 = (wchar_t) previousLetter;
975 ret.X = fallback->getKerningWidth(&l1, &l2);
976 ret.Y = fallback->getKerningHeight();
981 // If we don't have kerning, no point in continuing.
982 if (!FT_HAS_KERNING(tt_face))
985 // Get the kerning information.
987 FT_Get_Kerning(tt_face, getGlyphIndexByChar(previousLetter), n, FT_KERNING_DEFAULT, &v);
989 // If we have a scalable font, the return value will be in font points.
990 if (FT_IS_SCALABLE(tt_face))
992 // Font points, so divide by 64.
1005 void CGUITTFont::setInvisibleCharacters(const wchar_t *s)
1007 core::ustring us(s);
1011 void CGUITTFont::setInvisibleCharacters(const core::ustring& s)
1016 video::IImage* CGUITTFont::createTextureFromChar(const uchar32_t& ch)
1018 u32 n = getGlyphIndexByChar(ch);
1020 n = getGlyphIndexByChar((uchar32_t) core::unicode::UTF_REPLACEMENT_CHARACTER);
1022 const SGUITTGlyph& glyph = Glyphs[n-1];
1023 CGUITTGlyphPage* page = Glyph_Pages[glyph.glyph_page];
1026 page->updateTexture();
1028 video::ITexture* tex = page->texture;
1030 // Acquire a read-only lock of the corresponding page texture.
1031 void* ptr = tex->lock(video::ETLM_READ_ONLY);
1033 video::ECOLOR_FORMAT format = tex->getColorFormat();
1034 core::dimension2du tex_size = tex->getOriginalSize();
1035 video::IImage* pageholder = Driver->createImageFromData(format, tex_size, ptr, true, false);
1037 // Copy the image data out of the page texture.
1038 core::dimension2du glyph_size(glyph.source_rect.getSize());
1039 video::IImage* image = Driver->createImage(format, glyph_size);
1040 pageholder->copyTo(image, core::position2di(0, 0), glyph.source_rect);
1046 video::ITexture* CGUITTFont::getPageTextureByIndex(const u32& page_index) const
1048 if (page_index < Glyph_Pages.size())
1049 return Glyph_Pages[page_index]->texture;
1054 void CGUITTFont::createSharedPlane()
1059 | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
1060 |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
1064 using namespace core;
1065 using namespace video;
1066 using namespace scene;
1067 S3DVertex vertices[4];
1068 u16 indices[6] = {0,2,3,3,1,0};
1069 vertices[0] = S3DVertex(vector3df(0,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,1));
1070 vertices[1] = S3DVertex(vector3df(1,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,1));
1071 vertices[2] = S3DVertex(vector3df(0, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,0));
1072 vertices[3] = S3DVertex(vector3df(1, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,0));
1074 SMeshBuffer* buf = new SMeshBuffer();
1075 buf->append(vertices, 4, indices, 6);
1077 shared_plane_.addMeshBuffer( buf );
1079 shared_plane_ptr_ = &shared_plane_;
1080 buf->drop(); //the addMeshBuffer method will grab it, so we can drop this ptr.
1083 core::dimension2d<u32> CGUITTFont::getDimensionUntilEndOfLine(const wchar_t* p) const
1086 for (const wchar_t* temp = p; temp && *temp != '\0' && *temp != L'\r' && *temp != L'\n'; ++temp )
1089 return getDimension(s.c_str());
1092 core::array<scene::ISceneNode*> CGUITTFont::addTextSceneNode(const wchar_t* text, scene::ISceneManager* smgr, scene::ISceneNode* parent, const video::SColor& color, bool center)
1094 using namespace core;
1095 using namespace video;
1096 using namespace scene;
1098 array<scene::ISceneNode*> container;
1100 if (!Driver || !smgr) return container;
1102 parent = smgr->addEmptySceneNode(smgr->getRootSceneNode(), -1);
1103 // if you don't specify parent, then we add a empty node attached to the root node
1104 // this is generally undesirable.
1106 if (!shared_plane_ptr_) //this points to a static mesh that contains the plane
1107 createSharedPlane(); //if it's not initialized, we create one.
1109 dimension2d<s32> text_size(getDimension(text)); //convert from unsigned to signed.
1110 vector3df start_point(0, 0, 0), offset;
1113 Because we are considering adding texts into 3D world, all Y axis vectors are inverted.
1116 // There's currently no "vertical center" concept when you apply text scene node to the 3D world.
1119 offset.X = start_point.X = -text_size.Width / 2.f;
1120 offset.Y = start_point.Y = +text_size.Height/ 2.f;
1121 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text).Width) >> 1;
1124 // the default font material
1126 mat.setFlag(video::EMF_LIGHTING, true);
1127 mat.setFlag(video::EMF_ZWRITE_ENABLE, false);
1128 mat.setFlag(video::EMF_NORMALIZE_NORMALS, true);
1129 mat.ColorMaterial = video::ECM_NONE;
1130 mat.MaterialType = use_transparency ? video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_SOLID;
1131 mat.MaterialTypeParam = 0.01f;
1132 mat.DiffuseColor = color;
1134 wchar_t current_char = 0, previous_char = 0;
1137 array<u32> glyph_indices;
1141 current_char = *text;
1142 bool line_break=false;
1143 if (current_char == L'\r') // Mac or Windows breaks
1146 if (*(text + 1) == L'\n') // Windows line breaks.
1147 current_char = *(++text);
1149 else if (current_char == L'\n') // Unix breaks
1157 offset.Y -= tt_face->size->metrics.ascender / 64;
1158 offset.X = start_point.X;
1160 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text+1).Width) >> 1;
1165 n = getGlyphIndexByChar(current_char);
1168 glyph_indices.push_back( n );
1170 // Store glyph size and offset informations.
1171 SGUITTGlyph const& glyph = Glyphs[n-1];
1172 u32 texw = glyph.source_rect.getWidth();
1173 u32 texh = glyph.source_rect.getHeight();
1174 s32 offx = glyph.offset.X;
1175 s32 offy = (font_metrics.ascender / 64) - glyph.offset.Y;
1178 vector2di k = getKerning(current_char, previous_char);
1182 vector3df current_pos(offset.X + offx, offset.Y - offy, 0);
1183 dimension2d<u32> letter_size = dimension2d<u32>(texw, texh);
1185 // Now we copy planes corresponding to the letter size.
1186 IMeshManipulator* mani = smgr->getMeshManipulator();
1187 IMesh* meshcopy = mani->createMeshCopy(shared_plane_ptr_);
1188 mani->scale(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1));
1190 ISceneNode* current_node = smgr->addMeshSceneNode(meshcopy, parent, -1, current_pos);
1193 current_node->getMaterial(0) = mat;
1194 current_node->setAutomaticCulling(EAC_OFF);
1195 current_node->setIsDebugObject(true); //so the picking won't have any effect on individual letter
1196 //current_node->setDebugDataVisible(EDS_BBOX); //de-comment this when debugging
1198 container.push_back(current_node);
1200 offset.X += getWidthFromCharacter(current_char);
1201 // Note that fallback font handling is missing here (Minetest never uses this)
1203 previous_char = current_char;
1208 update_glyph_pages();
1209 //only after we update the textures can we use the glyph page textures.
1211 for (u32 i = 0; i < glyph_indices.size(); ++i)
1213 u32 n = glyph_indices[i];
1214 SGUITTGlyph const& glyph = Glyphs[n-1];
1215 ITexture* current_tex = Glyph_Pages[glyph.glyph_page]->texture;
1216 f32 page_texture_size = (f32)current_tex->getSize().Width;
1217 //Now we calculate the UV position according to the texture size and the source rect.
1221 // | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
1222 // |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
1225 f32 u1 = glyph.source_rect.UpperLeftCorner.X / page_texture_size;
1226 f32 u2 = u1 + (glyph.source_rect.getWidth() / page_texture_size);
1227 f32 v1 = glyph.source_rect.UpperLeftCorner.Y / page_texture_size;
1228 f32 v2 = v1 + (glyph.source_rect.getHeight() / page_texture_size);
1230 //we can be quite sure that this is IMeshSceneNode, because we just added them in the above loop.
1231 IMeshSceneNode* node = static_cast<IMeshSceneNode*>(container[i]);
1233 S3DVertex* pv = static_cast<S3DVertex*>(node->getMesh()->getMeshBuffer(0)->getVertices());
1234 //pv[0].TCoords.Y = pv[1].TCoords.Y = (letter_size.Height - 1) / static_cast<f32>(letter_size.Height);
1235 //pv[1].TCoords.X = pv[3].TCoords.X = (letter_size.Width - 1) / static_cast<f32>(letter_size.Width);
1236 pv[0].TCoords = vector2df(u1, v2);
1237 pv[1].TCoords = vector2df(u2, v2);
1238 pv[2].TCoords = vector2df(u1, v1);
1239 pv[3].TCoords = vector2df(u2, v1);
1241 container[i]->getMaterial(0).setTexture(0, current_tex);
1247 } // end namespace gui
1248 } // end namespace irr