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" ");
297 bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antialias, const bool transparency)
299 // Some sanity checks.
300 if (Environment == 0 || Driver == 0) return false;
301 if (size == 0) return false;
302 if (filename.size() == 0) return false;
304 io::IFileSystem* filesystem = Environment->getFileSystem();
305 irr::ILogger* logger = (Device != 0 ? Device->getLogger() : 0);
307 this->filename = filename;
309 // Update the font loading flags when the font is first loaded.
310 this->use_monochrome = !antialias;
311 this->use_transparency = transparency;
316 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);
319 SGUITTFace* face = 0;
320 auto node = c_faces.find(filename);
321 if (node == c_faces.end())
323 face = new SGUITTFace();
324 c_faces.emplace(filename, face);
328 // Read in the file data.
329 io::IReadFile* file = filesystem->createAndOpenFile(filename);
332 if (logger) logger->log(L"CGUITTFont", L"Failed to open the file.", irr::ELL_INFORMATION);
334 c_faces.erase(filename);
339 face->face_buffer = new FT_Byte[file->getSize()];
340 file->read(face->face_buffer, file->getSize());
341 face->face_buffer_size = file->getSize();
345 if (FT_New_Memory_Face(c_library, face->face_buffer, face->face_buffer_size, 0, &face->face))
347 if (logger) logger->log(L"CGUITTFont", L"FT_New_Memory_Face failed.", irr::ELL_INFORMATION);
349 c_faces.erase(filename);
357 core::ustring converter(filename);
358 if (FT_New_Face(c_library, reinterpret_cast<const char*>(converter.toUTF8_s().c_str()), 0, &face->face))
360 if (logger) logger->log(L"CGUITTFont", L"FT_New_Face failed.", irr::ELL_INFORMATION);
362 c_faces.erase(filename);
371 // Using another instance of this face.
377 tt_face = face->face;
379 // Store font metrics.
380 FT_Set_Pixel_Sizes(tt_face, size, 0);
381 font_metrics = tt_face->size->metrics;
383 // Allocate our glyphs.
385 Glyphs.reallocate(tt_face->num_glyphs);
386 Glyphs.set_used(tt_face->num_glyphs);
387 for (FT_Long i = 0; i < tt_face->num_glyphs; ++i)
389 Glyphs[i].isLoaded = false;
390 Glyphs[i].glyph_page = 0;
391 Glyphs[i].source_rect = core::recti();
392 Glyphs[i].offset = core::vector2di();
393 Glyphs[i].advance = FT_Vector();
394 Glyphs[i].surface = 0;
395 Glyphs[i].parent = this;
398 // Cache the first 127 ascii characters.
399 u32 old_size = batch_load_size;
400 batch_load_size = 127;
401 getGlyphIndexByChar((uchar32_t)0);
402 batch_load_size = old_size;
407 CGUITTFont::~CGUITTFont()
409 // Delete the glyphs and glyph pages.
413 // We aren't using this face anymore.
414 auto n = c_faces.find(filename);
415 if (n != c_faces.end())
417 SGUITTFace* f = n->second;
419 // Drop our face. If this was the last face, the destructor will clean up.
421 c_faces.erase(filename);
423 // If there are no more faces referenced by FreeType, clean up.
426 FT_Done_FreeType(c_library);
427 c_libraryLoaded = false;
431 // Drop our driver now.
436 void CGUITTFont::reset_images()
438 // Delete the glyphs.
439 for (u32 i = 0; i != Glyphs.size(); ++i)
442 // Unload the glyph pages from video memory.
443 for (u32 i = 0; i != Glyph_Pages.size(); ++i)
444 delete Glyph_Pages[i];
447 // Always update the internal FreeType loading flags after resetting.
451 void CGUITTFont::update_glyph_pages() const
453 for (u32 i = 0; i != Glyph_Pages.size(); ++i)
455 if (Glyph_Pages[i]->dirty)
456 Glyph_Pages[i]->updateTexture();
460 CGUITTGlyphPage* CGUITTFont::getLastGlyphPage() const
462 CGUITTGlyphPage* page = 0;
463 if (Glyph_Pages.empty())
467 page = Glyph_Pages[getLastGlyphPageIndex()];
468 if (page->available_slots == 0)
474 CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8& pixel_mode)
476 CGUITTGlyphPage* page = 0;
479 io::path name("TTFontGlyphPage_");
480 name += tt_face->family_name;
482 name += tt_face->style_name;
486 name += Glyph_Pages.size(); // The newly created page will be at the end of the collection.
488 // Create the new page.
489 page = new CGUITTGlyphPage(Driver, name);
491 // Determine our maximum texture size.
492 // If we keep getting 0, set it to 1024x1024, as that number is pretty safe.
493 core::dimension2du max_texture_size = max_page_texture_size;
494 if (max_texture_size.Width == 0 || max_texture_size.Height == 0)
495 max_texture_size = Driver->getMaxTextureSize();
496 if (max_texture_size.Width == 0 || max_texture_size.Height == 0)
497 max_texture_size = core::dimension2du(1024, 1024);
499 // We want to try to put at least 144 glyphs on a single texture.
500 core::dimension2du page_texture_size;
501 if (size <= 21) page_texture_size = core::dimension2du(256, 256);
502 else if (size <= 42) page_texture_size = core::dimension2du(512, 512);
503 else if (size <= 84) page_texture_size = core::dimension2du(1024, 1024);
504 else if (size <= 168) page_texture_size = core::dimension2du(2048, 2048);
505 else page_texture_size = core::dimension2du(4096, 4096);
507 if (page_texture_size.Width > max_texture_size.Width || page_texture_size.Height > max_texture_size.Height)
508 page_texture_size = max_texture_size;
510 if (!page->createPageTexture(pixel_mode, page_texture_size)) {
511 // TODO: add error message?
518 // Determine the number of glyph slots on the page and add it to the list of pages.
519 page->available_slots = (page_texture_size.Width / size) * (page_texture_size.Height / size);
520 Glyph_Pages.push_back(page);
525 void CGUITTFont::setTransparency(const bool flag)
527 use_transparency = flag;
531 void CGUITTFont::setMonochrome(const bool flag)
533 use_monochrome = flag;
537 void CGUITTFont::setFontHinting(const bool enable, const bool enable_auto_hinting)
539 use_hinting = enable;
540 use_auto_hinting = enable_auto_hinting;
544 void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position, video::SColor color, bool hcenter, bool vcenter, const core::rect<s32>* clip)
546 draw(EnrichedString(std::wstring(text.c_str()), color), position, hcenter, vcenter, clip);
549 void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& position, bool hcenter, bool vcenter, const core::rect<s32>* clip)
551 const std::vector<video::SColor> &colors = text.getColors();
556 // Clear the glyph pages of their render information.
557 for (u32 i = 0; i < Glyph_Pages.size(); ++i)
559 Glyph_Pages[i]->render_positions.clear();
560 Glyph_Pages[i]->render_source_rects.clear();
561 Glyph_Pages[i]->render_colors.clear();
564 // Set up some variables.
565 core::dimension2d<s32> textDimension;
566 core::position2d<s32> offset = position.UpperLeftCorner;
568 // Determine offset positions.
569 if (hcenter || vcenter)
571 textDimension = getDimension(text.c_str());
574 offset.X = ((position.getWidth() - textDimension.Width) >> 1) + offset.X;
577 offset.Y = ((position.getHeight() - textDimension.Height) >> 1) + offset.Y;
580 // Convert to a unicode string.
581 core::ustring utext = text.getString();
583 // Set up our render map.
584 std::map<u32, CGUITTGlyphPage*> Render_Map;
586 // Start parsing characters.
588 uchar32_t previousChar = 0;
589 core::ustring::const_iterator iter(utext);
590 while (!iter.atEnd())
592 uchar32_t currentChar = *iter;
593 n = getGlyphIndexByChar(currentChar);
594 bool visible = (Invisible.findFirst(currentChar) == -1);
595 bool lineBreak=false;
596 if (currentChar == L'\r') // Mac or Windows breaks
599 if (*(iter + 1) == (uchar32_t)'\n') // Windows line breaks.
600 currentChar = *(++iter);
602 else if (currentChar == (uchar32_t)'\n') // Unix breaks
610 offset.Y += font_metrics.height / 64;
611 offset.X = position.UpperLeftCorner.X;
614 offset.X += (position.getWidth() - textDimension.Width) >> 1;
619 if (n > 0 && visible)
621 // Calculate the glyph offset.
622 s32 offx = Glyphs[n-1].offset.X;
623 s32 offy = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y;
626 core::vector2di k = getKerning(currentChar, previousChar);
630 // Determine rendering information.
631 SGUITTGlyph& glyph = Glyphs[n-1];
632 CGUITTGlyphPage* const page = Glyph_Pages[glyph.glyph_page];
633 page->render_positions.push_back(core::position2di(offset.X + offx, offset.Y + offy));
634 page->render_source_rects.push_back(glyph.source_rect);
635 if (iter.getPos() < colors.size())
636 page->render_colors.push_back(colors[iter.getPos()]);
638 page->render_colors.push_back(video::SColor(255,255,255,255));
639 Render_Map[glyph.glyph_page] = page;
643 offset.X += getWidthFromCharacter(currentChar);
645 else if (fallback != 0)
647 // Let the fallback font draw it, this isn't super efficient but hopefully that doesn't matter
648 wchar_t l1[] = { (wchar_t) currentChar, 0 }, l2 = (wchar_t) previousChar;
653 offset.X += fallback->getKerningWidth(l1, &l2);
654 offset.Y += fallback->getKerningHeight();
656 u32 current_color = iter.getPos();
657 fallback->draw(core::stringw(l1),
658 core::rect<s32>({offset.X-1, offset.Y-1}, position.LowerRightCorner), // ???
659 current_color < colors.size() ? colors[current_color] : video::SColor(255, 255, 255, 255),
663 offset.X += fallback->getDimension(l1).Width;
666 previousChar = currentChar;
671 update_glyph_pages();
672 auto it = Render_Map.begin();
673 auto ie = Render_Map.end();
674 core::array<core::vector2di> tmp_positions;
675 core::array<core::recti> tmp_source_rects;
678 CGUITTGlyphPage* page = it->second;
682 for (size_t i = 0; i < page->render_positions.size(); ++i)
683 page->render_positions[i] += core::vector2di(shadow_offset, shadow_offset);
684 Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, video::SColor(shadow_alpha,0,0,0), true);
685 for (size_t i = 0; i < page->render_positions.size(); ++i)
686 page->render_positions[i] -= core::vector2di(shadow_offset, shadow_offset);
688 // render runs of matching color in batch
690 video::SColor colprev;
691 for (size_t i = 0; i < page->render_positions.size(); ++i) {
693 colprev = page->render_colors[i];
696 while (i < page->render_positions.size() && page->render_colors[i] == colprev);
697 tmp_positions.set_data(&page->render_positions[ibegin], i - ibegin);
698 tmp_source_rects.set_data(&page->render_source_rects[ibegin], i - ibegin);
701 if (!use_transparency)
702 colprev.color |= 0xff000000;
703 Driver->draw2DImageBatch(page->texture, tmp_positions, tmp_source_rects, clip, colprev, true);
708 core::dimension2d<u32> CGUITTFont::getCharDimension(const wchar_t ch) const
710 return core::dimension2d<u32>(getWidthFromCharacter(ch), getHeightFromCharacter(ch));
713 core::dimension2d<u32> CGUITTFont::getDimension(const wchar_t* text) const
715 return getDimension(core::ustring(text));
718 core::dimension2d<u32> CGUITTFont::getDimension(const core::ustring& text) const
720 // Get the maximum font height. Unfortunately, we have to do this hack as
721 // Irrlicht will draw things wrong. In FreeType, the font size is the
722 // maximum size for a single glyph, but that glyph may hang "under" the
723 // draw line, increasing the total font height to beyond the set size.
724 // Irrlicht does not understand this concept when drawing fonts. Also, I
725 // add +1 to give it a 1 pixel blank border. This makes things like
726 // tooltips look nicer.
727 s32 test1 = getHeightFromCharacter((uchar32_t)'g') + 1;
728 s32 test2 = getHeightFromCharacter((uchar32_t)'j') + 1;
729 s32 test3 = getHeightFromCharacter((uchar32_t)'_') + 1;
730 s32 max_font_height = core::max_(test1, core::max_(test2, test3));
732 core::dimension2d<u32> text_dimension(0, max_font_height);
733 core::dimension2d<u32> line(0, max_font_height);
735 uchar32_t previousChar = 0;
736 core::ustring::const_iterator iter = text.begin();
737 for (; !iter.atEnd(); ++iter)
740 bool lineBreak = false;
741 if (p == '\r') // Mac or Windows line breaks.
744 if (*(iter + 1) == '\n')
750 else if (p == '\n') // Unix line breaks.
756 core::vector2di k = getKerning(p, previousChar);
760 // Check for linebreak.
764 text_dimension.Height += line.Height;
765 if (text_dimension.Width < line.Width)
766 text_dimension.Width = line.Width;
768 line.Height = max_font_height;
771 line.Width += getWidthFromCharacter(p);
773 if (text_dimension.Width < line.Width)
774 text_dimension.Width = line.Width;
776 return text_dimension;
779 inline u32 CGUITTFont::getWidthFromCharacter(wchar_t c) const
781 return getWidthFromCharacter((uchar32_t)c);
784 inline u32 CGUITTFont::getWidthFromCharacter(uchar32_t c) const
786 // Set the size of the face.
787 // This is because we cache faces and the face may have been set to a different size.
788 //FT_Set_Pixel_Sizes(tt_face, 0, size);
790 u32 n = getGlyphIndexByChar(c);
793 int w = Glyphs[n-1].advance.x / 64;
798 wchar_t s[] = { (wchar_t) c, 0 };
799 return fallback->getDimension(s).Width;
803 return (font_metrics.ascender / 64);
804 else return (font_metrics.ascender / 64) / 2;
807 inline u32 CGUITTFont::getHeightFromCharacter(wchar_t c) const
809 return getHeightFromCharacter((uchar32_t)c);
812 inline u32 CGUITTFont::getHeightFromCharacter(uchar32_t c) const
814 // Set the size of the face.
815 // This is because we cache faces and the face may have been set to a different size.
816 //FT_Set_Pixel_Sizes(tt_face, 0, size);
818 u32 n = getGlyphIndexByChar(c);
821 // Grab the true height of the character, taking into account underhanging glyphs.
822 s32 height = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y + Glyphs[n-1].source_rect.getHeight();
827 wchar_t s[] = { (wchar_t) c, 0 };
828 return fallback->getDimension(s).Height;
832 return (font_metrics.ascender / 64);
833 else return (font_metrics.ascender / 64) / 2;
836 u32 CGUITTFont::getGlyphIndexByChar(wchar_t c) const
838 return getGlyphIndexByChar((uchar32_t)c);
841 u32 CGUITTFont::getGlyphIndexByChar(uchar32_t c) const
844 u32 glyph = FT_Get_Char_Index(tt_face, c);
846 // Check for a valid glyph.
850 // If our glyph is already loaded, don't bother doing any batch loading code.
851 if (glyph != 0 && Glyphs[glyph - 1].isLoaded)
854 // Determine our batch loading positions.
855 u32 half_size = (batch_load_size / 2);
857 if (c > half_size) start_pos = c - half_size;
858 u32 end_pos = start_pos + batch_load_size;
860 // Load all our characters.
863 // Get the character we are going to load.
864 u32 char_index = FT_Get_Char_Index(tt_face, start_pos);
866 // If the glyph hasn't been loaded yet, do it now.
869 SGUITTGlyph& glyph = Glyphs[char_index - 1];
872 glyph.preload(char_index, tt_face, Driver, size, load_flags);
873 Glyph_Pages[glyph.glyph_page]->pushGlyphToBePaged(&glyph);
877 while (++start_pos < end_pos);
879 // Return our original character.
883 s32 CGUITTFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x) const
885 return getCharacterFromPos(core::ustring(text), pixel_x);
888 s32 CGUITTFont::getCharacterFromPos(const core::ustring& text, s32 pixel_x) const
894 uchar32_t previousChar = 0;
895 core::ustring::const_iterator iter = text.begin();
896 while (!iter.atEnd())
899 x += getWidthFromCharacter(c);
902 core::vector2di k = getKerning(c, previousChar);
916 void CGUITTFont::setKerningWidth(s32 kerning)
918 GlobalKerningWidth = kerning;
921 void CGUITTFont::setKerningHeight(s32 kerning)
923 GlobalKerningHeight = kerning;
926 s32 CGUITTFont::getKerningWidth(const wchar_t* thisLetter, const wchar_t* previousLetter) const
929 return GlobalKerningWidth;
930 if (thisLetter == 0 || previousLetter == 0)
933 return getKerningWidth((uchar32_t)*thisLetter, (uchar32_t)*previousLetter);
936 s32 CGUITTFont::getKerningWidth(const uchar32_t thisLetter, const uchar32_t previousLetter) const
938 // Return only the kerning width.
939 return getKerning(thisLetter, previousLetter).X;
942 s32 CGUITTFont::getKerningHeight() const
944 // FreeType 2 currently doesn't return any height kerning information.
945 return GlobalKerningHeight;
948 core::vector2di CGUITTFont::getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const
950 return getKerning((uchar32_t)thisLetter, (uchar32_t)previousLetter);
953 core::vector2di CGUITTFont::getKerning(const uchar32_t thisLetter, const uchar32_t previousLetter) const
955 if (tt_face == 0 || thisLetter == 0 || previousLetter == 0)
956 return core::vector2di();
958 // Set the size of the face.
959 // This is because we cache faces and the face may have been set to a different size.
960 FT_Set_Pixel_Sizes(tt_face, 0, size);
962 core::vector2di ret(GlobalKerningWidth, GlobalKerningHeight);
964 u32 n = getGlyphIndexByChar(thisLetter);
966 // If we don't have this glyph, ask fallback font
970 wchar_t l1 = (wchar_t) thisLetter, l2 = (wchar_t) previousLetter;
971 ret.X = fallback->getKerningWidth(&l1, &l2);
972 ret.Y = fallback->getKerningHeight();
977 // If we don't have kerning, no point in continuing.
978 if (!FT_HAS_KERNING(tt_face))
981 // Get the kerning information.
983 FT_Get_Kerning(tt_face, getGlyphIndexByChar(previousLetter), n, FT_KERNING_DEFAULT, &v);
985 // If we have a scalable font, the return value will be in font points.
986 if (FT_IS_SCALABLE(tt_face))
988 // Font points, so divide by 64.
1001 void CGUITTFont::setInvisibleCharacters(const wchar_t *s)
1003 core::ustring us(s);
1007 void CGUITTFont::setInvisibleCharacters(const core::ustring& s)
1012 video::IImage* CGUITTFont::createTextureFromChar(const uchar32_t& ch)
1014 u32 n = getGlyphIndexByChar(ch);
1016 n = getGlyphIndexByChar((uchar32_t) core::unicode::UTF_REPLACEMENT_CHARACTER);
1018 const SGUITTGlyph& glyph = Glyphs[n-1];
1019 CGUITTGlyphPage* page = Glyph_Pages[glyph.glyph_page];
1022 page->updateTexture();
1024 video::ITexture* tex = page->texture;
1026 // Acquire a read-only lock of the corresponding page texture.
1027 void* ptr = tex->lock(video::ETLM_READ_ONLY);
1029 video::ECOLOR_FORMAT format = tex->getColorFormat();
1030 core::dimension2du tex_size = tex->getOriginalSize();
1031 video::IImage* pageholder = Driver->createImageFromData(format, tex_size, ptr, true, false);
1033 // Copy the image data out of the page texture.
1034 core::dimension2du glyph_size(glyph.source_rect.getSize());
1035 video::IImage* image = Driver->createImage(format, glyph_size);
1036 pageholder->copyTo(image, core::position2di(0, 0), glyph.source_rect);
1042 video::ITexture* CGUITTFont::getPageTextureByIndex(const u32& page_index) const
1044 if (page_index < Glyph_Pages.size())
1045 return Glyph_Pages[page_index]->texture;
1050 void CGUITTFont::createSharedPlane()
1055 | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
1056 |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
1060 using namespace core;
1061 using namespace video;
1062 using namespace scene;
1063 S3DVertex vertices[4];
1064 u16 indices[6] = {0,2,3,3,1,0};
1065 vertices[0] = S3DVertex(vector3df(0,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,1));
1066 vertices[1] = S3DVertex(vector3df(1,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,1));
1067 vertices[2] = S3DVertex(vector3df(0, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,0));
1068 vertices[3] = S3DVertex(vector3df(1, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,0));
1070 SMeshBuffer* buf = new SMeshBuffer();
1071 buf->append(vertices, 4, indices, 6);
1073 shared_plane_.addMeshBuffer( buf );
1075 shared_plane_ptr_ = &shared_plane_;
1076 buf->drop(); //the addMeshBuffer method will grab it, so we can drop this ptr.
1079 core::dimension2d<u32> CGUITTFont::getDimensionUntilEndOfLine(const wchar_t* p) const
1082 for (const wchar_t* temp = p; temp && *temp != '\0' && *temp != L'\r' && *temp != L'\n'; ++temp )
1085 return getDimension(s.c_str());
1088 core::array<scene::ISceneNode*> CGUITTFont::addTextSceneNode(const wchar_t* text, scene::ISceneManager* smgr, scene::ISceneNode* parent, const video::SColor& color, bool center)
1090 using namespace core;
1091 using namespace video;
1092 using namespace scene;
1094 array<scene::ISceneNode*> container;
1096 if (!Driver || !smgr) return container;
1098 parent = smgr->addEmptySceneNode(smgr->getRootSceneNode(), -1);
1099 // if you don't specify parent, then we add a empty node attached to the root node
1100 // this is generally undesirable.
1102 if (!shared_plane_ptr_) //this points to a static mesh that contains the plane
1103 createSharedPlane(); //if it's not initialized, we create one.
1105 dimension2d<s32> text_size(getDimension(text)); //convert from unsigned to signed.
1106 vector3df start_point(0, 0, 0), offset;
1109 Because we are considering adding texts into 3D world, all Y axis vectors are inverted.
1112 // There's currently no "vertical center" concept when you apply text scene node to the 3D world.
1115 offset.X = start_point.X = -text_size.Width / 2.f;
1116 offset.Y = start_point.Y = +text_size.Height/ 2.f;
1117 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text).Width) >> 1;
1120 // the default font material
1122 mat.setFlag(video::EMF_LIGHTING, true);
1123 mat.setFlag(video::EMF_ZWRITE_ENABLE, false);
1124 mat.setFlag(video::EMF_NORMALIZE_NORMALS, true);
1125 mat.ColorMaterial = video::ECM_NONE;
1126 mat.MaterialType = use_transparency ? video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_SOLID;
1127 mat.MaterialTypeParam = 0.01f;
1128 mat.DiffuseColor = color;
1130 wchar_t current_char = 0, previous_char = 0;
1133 array<u32> glyph_indices;
1137 current_char = *text;
1138 bool line_break=false;
1139 if (current_char == L'\r') // Mac or Windows breaks
1142 if (*(text + 1) == L'\n') // Windows line breaks.
1143 current_char = *(++text);
1145 else if (current_char == L'\n') // Unix breaks
1153 offset.Y -= tt_face->size->metrics.ascender / 64;
1154 offset.X = start_point.X;
1156 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text+1).Width) >> 1;
1161 n = getGlyphIndexByChar(current_char);
1164 glyph_indices.push_back( n );
1166 // Store glyph size and offset informations.
1167 SGUITTGlyph const& glyph = Glyphs[n-1];
1168 u32 texw = glyph.source_rect.getWidth();
1169 u32 texh = glyph.source_rect.getHeight();
1170 s32 offx = glyph.offset.X;
1171 s32 offy = (font_metrics.ascender / 64) - glyph.offset.Y;
1174 vector2di k = getKerning(current_char, previous_char);
1178 vector3df current_pos(offset.X + offx, offset.Y - offy, 0);
1179 dimension2d<u32> letter_size = dimension2d<u32>(texw, texh);
1181 // Now we copy planes corresponding to the letter size.
1182 IMeshManipulator* mani = smgr->getMeshManipulator();
1183 IMesh* meshcopy = mani->createMeshCopy(shared_plane_ptr_);
1184 mani->scale(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1));
1186 ISceneNode* current_node = smgr->addMeshSceneNode(meshcopy, parent, -1, current_pos);
1189 current_node->getMaterial(0) = mat;
1190 current_node->setAutomaticCulling(EAC_OFF);
1191 current_node->setIsDebugObject(true); //so the picking won't have any effect on individual letter
1192 //current_node->setDebugDataVisible(EDS_BBOX); //de-comment this when debugging
1194 container.push_back(current_node);
1196 offset.X += getWidthFromCharacter(current_char);
1197 // Note that fallback font handling is missing here (Minetest never uses this)
1199 previous_char = current_char;
1204 update_glyph_pages();
1205 //only after we update the textures can we use the glyph page textures.
1207 for (u32 i = 0; i < glyph_indices.size(); ++i)
1209 u32 n = glyph_indices[i];
1210 SGUITTGlyph const& glyph = Glyphs[n-1];
1211 ITexture* current_tex = Glyph_Pages[glyph.glyph_page]->texture;
1212 f32 page_texture_size = (f32)current_tex->getSize().Width;
1213 //Now we calculate the UV position according to the texture size and the source rect.
1217 // | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
1218 // |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
1221 f32 u1 = glyph.source_rect.UpperLeftCorner.X / page_texture_size;
1222 f32 u2 = u1 + (glyph.source_rect.getWidth() / page_texture_size);
1223 f32 v1 = glyph.source_rect.UpperLeftCorner.Y / page_texture_size;
1224 f32 v2 = v1 + (glyph.source_rect.getHeight() / page_texture_size);
1226 //we can be quite sure that this is IMeshSceneNode, because we just added them in the above loop.
1227 IMeshSceneNode* node = static_cast<IMeshSceneNode*>(container[i]);
1229 S3DVertex* pv = static_cast<S3DVertex*>(node->getMesh()->getMeshBuffer(0)->getVertices());
1230 //pv[0].TCoords.Y = pv[1].TCoords.Y = (letter_size.Height - 1) / static_cast<f32>(letter_size.Height);
1231 //pv[1].TCoords.X = pv[3].TCoords.X = (letter_size.Width - 1) / static_cast<f32>(letter_size.Width);
1232 pv[0].TCoords = vector2df(u1, v2);
1233 pv[1].TCoords = vector2df(u2, v2);
1234 pv[2].TCoords = vector2df(u1, v1);
1235 pv[3].TCoords = vector2df(u2, v1);
1237 container[i]->getMaterial(0).setTexture(0, current_tex);
1243 } // end namespace gui
1244 } // end namespace irr