]> git.lizzy.rs Git - minetest.git/blob - src/cguittfont/CGUITTFont.cpp
Fix selectionbox not honoring anaglyph mode 3d distortion
[minetest.git] / src / cguittfont / CGUITTFont.cpp
1 /*
2    CGUITTFont FreeType class for Irrlicht
3    Copyright (c) 2009-2010 John Norman
4
5    This software is provided 'as-is', without any express or implied
6    warranty. In no event will the authors be held liable for any
7    damages arising from the use of this software.
8
9    Permission is granted to anyone to use this software for any
10    purpose, including commercial applications, and to alter it and
11    redistribute it freely, subject to the following restrictions:
12
13    1. The origin of this software must not be misrepresented; you
14       must not claim that you wrote the original software. If you use
15       this software in a product, an acknowledgment in the product
16       documentation would be appreciated but is not required.
17
18    2. Altered source versions must be plainly marked as such, and
19       must not be misrepresented as being the original software.
20
21    3. This notice may not be removed or altered from any source
22       distribution.
23
24    The original version of this class can be located at:
25    http://irrlicht.suckerfreegames.com/
26
27    John Norman
28    john@suckerfreegames.com
29 */
30
31 #include <irrlicht.h>
32 #include "CGUITTFont.h"
33
34 namespace irr
35 {
36 namespace gui
37 {
38
39 // Manages the FT_Face cache.
40 struct SGUITTFace : public virtual irr::IReferenceCounted
41 {
42         SGUITTFace() : face_buffer(0), face_buffer_size(0)
43         {
44                 memset((void*)&face, 0, sizeof(FT_Face));
45         }
46
47         ~SGUITTFace()
48         {
49                 FT_Done_Face(face);
50                 delete[] face_buffer;
51         }
52
53         FT_Face face;
54         FT_Byte* face_buffer;
55         FT_Long face_buffer_size;
56 };
57
58 // Static variables.
59 FT_Library CGUITTFont::c_library;
60 core::map<io::path, SGUITTFace*> CGUITTFont::c_faces;
61 bool CGUITTFont::c_libraryLoaded = false;
62 scene::IMesh* CGUITTFont::shared_plane_ptr_ = 0;
63 scene::SMesh CGUITTFont::shared_plane_;
64
65 //
66
67 video::IImage* SGUITTGlyph::createGlyphImage(const FT_Bitmap& bits, video::IVideoDriver* driver) const
68 {
69         // Determine what our texture size should be.
70         // Add 1 because textures are inclusive-exclusive.
71         core::dimension2du d(bits.width + 1, bits.rows + 1);
72         core::dimension2du texture_size;
73         //core::dimension2du texture_size(bits.width + 1, bits.rows + 1);
74
75         // Create and load our image now.
76         video::IImage* image = 0;
77         switch (bits.pixel_mode)
78         {
79                 case FT_PIXEL_MODE_MONO:
80                 {
81                         // Create a blank image and fill it with transparent pixels.
82                         texture_size = d.getOptimalSize(true, true);
83                         image = driver->createImage(video::ECF_A1R5G5B5, texture_size);
84                         image->fill(video::SColor(0, 255, 255, 255));
85
86                         // Load the monochrome data in.
87                         const u32 image_pitch = image->getPitch() / sizeof(u16);
88                         u16* image_data = (u16*)image->lock();
89                         u8* glyph_data = bits.buffer;
90                         for (s32 y = 0; y < bits.rows; ++y)
91                         {
92                                 u16* row = image_data;
93                                 for (s32 x = 0; x < bits.width; ++x)
94                                 {
95                                         // Monochrome bitmaps store 8 pixels per byte.  The left-most pixel is the bit 0x80.
96                                         // So, we go through the data each bit at a time.
97                                         if ((glyph_data[y * bits.pitch + (x / 8)] & (0x80 >> (x % 8))) != 0)
98                                                 *row = 0xFFFF;
99                                         ++row;
100                                 }
101                                 image_data += image_pitch;
102                         }
103                         image->unlock();
104                         break;
105                 }
106
107                 case FT_PIXEL_MODE_GRAY:
108                 {
109                         // Create our blank image.
110                         texture_size = d.getOptimalSize(!driver->queryFeature(video::EVDF_TEXTURE_NPOT), !driver->queryFeature(video::EVDF_TEXTURE_NSQUARE), true, 0);
111                         image = driver->createImage(video::ECF_A8R8G8B8, texture_size);
112                         image->fill(video::SColor(0, 255, 255, 255));
113
114                         // Load the grayscale data in.
115                         const float gray_count = static_cast<float>(bits.num_grays);
116                         const u32 image_pitch = image->getPitch() / sizeof(u32);
117                         u32* image_data = (u32*)image->lock();
118                         u8* glyph_data = bits.buffer;
119                         for (s32 y = 0; y < bits.rows; ++y)
120                         {
121                                 u8* row = glyph_data;
122                                 for (s32 x = 0; x < bits.width; ++x)
123                                 {
124                                         image_data[y * image_pitch + x] |= static_cast<u32>(255.0f * (static_cast<float>(*row++) / gray_count)) << 24;
125                                         //data[y * image_pitch + x] |= ((u32)(*bitsdata++) << 24);
126                                 }
127                                 glyph_data += bits.pitch;
128                         }
129                         image->unlock();
130                         break;
131                 }
132                 default:
133                         // TODO: error message?
134                         return 0;
135         }
136         return image;
137 }
138
139 void SGUITTGlyph::preload(u32 char_index, FT_Face face, video::IVideoDriver* driver, u32 font_size, const FT_Int32 loadFlags)
140 {
141         if (isLoaded) return;
142
143         // Set the size of the glyph.
144         FT_Set_Pixel_Sizes(face, 0, font_size);
145
146         // Attempt to load the glyph.
147         if (FT_Load_Glyph(face, char_index, loadFlags) != FT_Err_Ok)
148                 // TODO: error message?
149                 return;
150
151         FT_GlyphSlot glyph = face->glyph;
152         FT_Bitmap bits = glyph->bitmap;
153
154         // Setup the glyph information here:
155         advance = glyph->advance;
156         offset = core::vector2di(glyph->bitmap_left, glyph->bitmap_top);
157
158         // Try to get the last page with available slots.
159         CGUITTGlyphPage* page = parent->getLastGlyphPage();
160
161         // If we need to make a new page, do that now.
162         if (!page)
163         {
164                 page = parent->createGlyphPage(bits.pixel_mode);
165                 if (!page)
166                         // TODO: add error message?
167                         return;
168         }
169
170         glyph_page = parent->getLastGlyphPageIndex();
171         u32 texture_side_length = page->texture->getOriginalSize().Width;
172         core::vector2di page_position(
173                 (page->used_slots % (texture_side_length / font_size)) * font_size,
174                 (page->used_slots / (texture_side_length / font_size)) * font_size
175                 );
176         source_rect.UpperLeftCorner = page_position;
177         source_rect.LowerRightCorner = core::vector2di(page_position.X + bits.width, page_position.Y + bits.rows);
178
179         page->dirty = true;
180         ++page->used_slots;
181         --page->available_slots;
182
183         // We grab the glyph bitmap here so the data won't be removed when the next glyph is loaded.
184         surface = createGlyphImage(bits, driver);
185
186         // Set our glyph as loaded.
187         isLoaded = true;
188 }
189
190 void SGUITTGlyph::unload()
191 {
192         if (surface)
193         {
194                 surface->drop();
195                 surface = 0;
196         }
197         isLoaded = false;
198 }
199
200 //////////////////////
201
202 CGUITTFont* CGUITTFont::createTTFont(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
203 {
204         if (!c_libraryLoaded)
205         {
206                 if (FT_Init_FreeType(&c_library))
207                         return 0;
208                 c_libraryLoaded = true;
209         }
210
211         CGUITTFont* font = new CGUITTFont(env);
212         bool ret = font->load(filename, size, antialias, transparency);
213         if (!ret)
214         {
215                 font->drop();
216                 return 0;
217         }
218
219         return font;
220 }
221
222 CGUITTFont* CGUITTFont::createTTFont(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
223 {
224         if (!c_libraryLoaded)
225         {
226                 if (FT_Init_FreeType(&c_library))
227                         return 0;
228                 c_libraryLoaded = true;
229         }
230
231         CGUITTFont* font = new CGUITTFont(device->getGUIEnvironment());
232         font->Device = device;
233         bool ret = font->load(filename, size, antialias, transparency);
234         if (!ret)
235         {
236                 font->drop();
237                 return 0;
238         }
239
240         return font;
241 }
242
243 CGUITTFont* CGUITTFont::create(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
244 {
245         return CGUITTFont::createTTFont(env, filename, size, antialias, transparency);
246 }
247
248 CGUITTFont* CGUITTFont::create(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
249 {
250         return CGUITTFont::createTTFont(device, filename, size, antialias, transparency);
251 }
252
253 //////////////////////
254
255 //! Constructor.
256 CGUITTFont::CGUITTFont(IGUIEnvironment *env)
257 : use_monochrome(false), use_transparency(true), use_hinting(true), use_auto_hinting(true),
258 batch_load_size(1), Device(0), Environment(env), Driver(0), GlobalKerningWidth(0), GlobalKerningHeight(0)
259 {
260         #ifdef _DEBUG
261         setDebugName("CGUITTFont");
262         #endif
263
264         if (Environment)
265         {
266                 // don't grab environment, to avoid circular references
267                 Driver = Environment->getVideoDriver();
268         }
269
270         if (Driver)
271                 Driver->grab();
272
273         setInvisibleCharacters(L" ");
274
275         // Glyphs aren't reference counted, so don't try to delete them when we free the array.
276         Glyphs.set_free_when_destroyed(false);
277 }
278
279 bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antialias, const bool transparency)
280 {
281         // Some sanity checks.
282         if (Environment == 0 || Driver == 0) return false;
283         if (size == 0) return false;
284         if (filename.size() == 0) return false;
285
286         io::IFileSystem* filesystem = Environment->getFileSystem();
287         irr::ILogger* logger = (Device != 0 ? Device->getLogger() : 0);
288         this->size = size;
289         this->filename = filename;
290
291         // Update the font loading flags when the font is first loaded.
292         this->use_monochrome = !antialias;
293         this->use_transparency = transparency;
294         update_load_flags();
295
296         // Log.
297         if (logger)
298                 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);
299
300         // Grab the face.
301         SGUITTFace* face = 0;
302         core::map<io::path, SGUITTFace*>::Node* node = c_faces.find(filename);
303         if (node == 0)
304         {
305                 face = new SGUITTFace();
306                 c_faces.set(filename, face);
307
308                 if (filesystem)
309                 {
310                         // Read in the file data.
311                         io::IReadFile* file = filesystem->createAndOpenFile(filename);
312                         if (file == 0)
313                         {
314                                 if (logger) logger->log(L"CGUITTFont", L"Failed to open the file.", irr::ELL_INFORMATION);
315
316                                 c_faces.remove(filename);
317                                 delete face;
318                                 face = 0;
319                                 return false;
320                         }
321                         face->face_buffer = new FT_Byte[file->getSize()];
322                         file->read(face->face_buffer, file->getSize());
323                         face->face_buffer_size = file->getSize();
324                         file->drop();
325
326                         // Create the face.
327                         if (FT_New_Memory_Face(c_library, face->face_buffer, face->face_buffer_size, 0, &face->face))
328                         {
329                                 if (logger) logger->log(L"CGUITTFont", L"FT_New_Memory_Face failed.", irr::ELL_INFORMATION);
330
331                                 c_faces.remove(filename);
332                                 delete face;
333                                 face = 0;
334                                 return false;
335                         }
336                 }
337                 else
338                 {
339                         core::ustring converter(filename);
340                         if (FT_New_Face(c_library, reinterpret_cast<const char*>(converter.toUTF8_s().c_str()), 0, &face->face))
341                         {
342                                 if (logger) logger->log(L"CGUITTFont", L"FT_New_Face failed.", irr::ELL_INFORMATION);
343
344                                 c_faces.remove(filename);
345                                 delete face;
346                                 face = 0;
347                                 return false;
348                         }
349                 }
350         }
351         else
352         {
353                 // Using another instance of this face.
354                 face = node->getValue();
355                 face->grab();
356         }
357
358         // Store our face.
359         tt_face = face->face;
360
361         // Store font metrics.
362         FT_Set_Pixel_Sizes(tt_face, size, 0);
363         font_metrics = tt_face->size->metrics;
364
365         // Allocate our glyphs.
366         Glyphs.clear();
367         Glyphs.reallocate(tt_face->num_glyphs);
368         Glyphs.set_used(tt_face->num_glyphs);
369         for (FT_Long i = 0; i < tt_face->num_glyphs; ++i)
370         {
371                 Glyphs[i].isLoaded = false;
372                 Glyphs[i].glyph_page = 0;
373                 Glyphs[i].source_rect = core::recti();
374                 Glyphs[i].offset = core::vector2di();
375                 Glyphs[i].advance = FT_Vector();
376                 Glyphs[i].surface = 0;
377                 Glyphs[i].parent = this;
378         }
379
380         // Cache the first 127 ascii characters.
381         u32 old_size = batch_load_size;
382         batch_load_size = 127;
383         getGlyphIndexByChar((uchar32_t)0);
384         batch_load_size = old_size;
385
386         return true;
387 }
388
389 CGUITTFont::~CGUITTFont()
390 {
391         // Delete the glyphs and glyph pages.
392         reset_images();
393         CGUITTAssistDelete::Delete(Glyphs);
394         //Glyphs.clear();
395
396         // We aren't using this face anymore.
397         core::map<io::path, SGUITTFace*>::Node* n = c_faces.find(filename);
398         if (n)
399         {
400                 SGUITTFace* f = n->getValue();
401
402                 // Drop our face.  If this was the last face, the destructor will clean up.
403                 if (f->drop())
404                         c_faces.remove(filename);
405
406                 // If there are no more faces referenced by FreeType, clean up.
407                 if (c_faces.size() == 0)
408                 {
409                         FT_Done_FreeType(c_library);
410                         c_libraryLoaded = false;
411                 }
412         }
413
414         // Drop our driver now.
415         if (Driver)
416                 Driver->drop();
417 }
418
419 void CGUITTFont::reset_images()
420 {
421         // Delete the glyphs.
422         for (u32 i = 0; i != Glyphs.size(); ++i)
423                 Glyphs[i].unload();
424
425         // Unload the glyph pages from video memory.
426         for (u32 i = 0; i != Glyph_Pages.size(); ++i)
427                 delete Glyph_Pages[i];
428         Glyph_Pages.clear();
429
430         // Always update the internal FreeType loading flags after resetting.
431         update_load_flags();
432 }
433
434 void CGUITTFont::update_glyph_pages() const
435 {
436         for (u32 i = 0; i != Glyph_Pages.size(); ++i)
437         {
438                 if (Glyph_Pages[i]->dirty)
439                         Glyph_Pages[i]->updateTexture();
440         }
441 }
442
443 CGUITTGlyphPage* CGUITTFont::getLastGlyphPage() const
444 {
445         CGUITTGlyphPage* page = 0;
446         if (Glyph_Pages.empty())
447                 return 0;
448         else
449         {
450                 page = Glyph_Pages[getLastGlyphPageIndex()];
451                 if (page->available_slots == 0)
452                         page = 0;
453         }
454         return page;
455 }
456
457 CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8& pixel_mode)
458 {
459         CGUITTGlyphPage* page = 0;
460         
461         // Name of our page.
462         io::path name("TTFontGlyphPage_");
463         name += tt_face->family_name;
464         name += ".";
465         name += tt_face->style_name;
466         name += ".";
467         name += size;
468         name += "_";
469         name += Glyph_Pages.size(); // The newly created page will be at the end of the collection.
470
471         // Create the new page.
472         page = new CGUITTGlyphPage(Driver, name);
473
474         // Determine our maximum texture size.
475         // If we keep getting 0, set it to 1024x1024, as that number is pretty safe.
476         core::dimension2du max_texture_size = max_page_texture_size;
477         if (max_texture_size.Width == 0 || max_texture_size.Height == 0)
478                 max_texture_size = Driver->getMaxTextureSize();
479         if (max_texture_size.Width == 0 || max_texture_size.Height == 0)
480                 max_texture_size = core::dimension2du(1024, 1024);
481
482         // We want to try to put at least 144 glyphs on a single texture.
483         core::dimension2du page_texture_size;
484         if (size <= 21) page_texture_size = core::dimension2du(256, 256);
485         else if (size <= 42) page_texture_size = core::dimension2du(512, 512);
486         else if (size <= 84) page_texture_size = core::dimension2du(1024, 1024);
487         else if (size <= 168) page_texture_size = core::dimension2du(2048, 2048);
488         else page_texture_size = core::dimension2du(4096, 4096);
489
490         if (page_texture_size.Width > max_texture_size.Width || page_texture_size.Height > max_texture_size.Height)
491                 page_texture_size = max_texture_size;
492
493         if (!page->createPageTexture(pixel_mode, page_texture_size))
494                 // TODO: add error message?
495                 return 0;
496
497         if (page)
498         {
499                 // Determine the number of glyph slots on the page and add it to the list of pages.
500                 page->available_slots = (page_texture_size.Width / size) * (page_texture_size.Height / size);
501                 Glyph_Pages.push_back(page);
502         }
503         return page;
504 }
505
506 void CGUITTFont::setTransparency(const bool flag)
507 {
508         use_transparency = flag;
509         reset_images();
510 }
511
512 void CGUITTFont::setMonochrome(const bool flag)
513 {
514         use_monochrome = flag;
515         reset_images();
516 }
517
518 void CGUITTFont::setFontHinting(const bool enable, const bool enable_auto_hinting)
519 {
520         use_hinting = enable;
521         use_auto_hinting = enable_auto_hinting;
522         reset_images();
523 }
524
525 void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position, video::SColor color, bool hcenter, bool vcenter, const core::rect<s32>* clip)
526 {
527         if (!Driver)
528                 return;
529
530         // Clear the glyph pages of their render information.
531         for (u32 i = 0; i < Glyph_Pages.size(); ++i)
532         {
533                 Glyph_Pages[i]->render_positions.clear();
534                 Glyph_Pages[i]->render_source_rects.clear();
535         }
536
537         // Set up some variables.
538         core::dimension2d<s32> textDimension;
539         core::position2d<s32> offset = position.UpperLeftCorner;
540
541         // Determine offset positions.
542         if (hcenter || vcenter)
543         {
544                 textDimension = getDimension(text.c_str());
545
546                 if (hcenter)
547                         offset.X = ((position.getWidth() - textDimension.Width) >> 1) + offset.X;
548
549                 if (vcenter)
550                         offset.Y = ((position.getHeight() - textDimension.Height) >> 1) + offset.Y;
551         }
552
553         // Convert to a unicode string.
554         core::ustring utext(text);
555
556         // Set up our render map.
557         core::map<u32, CGUITTGlyphPage*> Render_Map;
558
559         // Start parsing characters.
560         u32 n;
561         uchar32_t previousChar = 0;
562         core::ustring::const_iterator iter(utext);
563         while (!iter.atEnd())
564         {
565                 uchar32_t currentChar = *iter;
566                 n = getGlyphIndexByChar(currentChar);
567                 bool visible = (Invisible.findFirst(currentChar) == -1);
568                 if (n > 0 && visible)
569                 {
570                         bool lineBreak=false;
571                         if (currentChar == L'\r') // Mac or Windows breaks
572                         {
573                                 lineBreak = true;
574                                 if (*(iter + 1) == (uchar32_t)'\n')     // Windows line breaks.
575                                         currentChar = *(++iter);
576                         }
577                         else if (currentChar == (uchar32_t)'\n') // Unix breaks
578                         {
579                                 lineBreak = true;
580                         }
581
582                         if (lineBreak)
583                         {
584                                 previousChar = 0;
585                                 offset.Y += font_metrics.ascender / 64;
586                                 offset.X = position.UpperLeftCorner.X;
587
588                                 if (hcenter)
589                                         offset.X += (position.getWidth() - textDimension.Width) >> 1;
590                                 ++iter;
591                                 continue;
592                         }
593
594                         // Calculate the glyph offset.
595                         s32 offx = Glyphs[n-1].offset.X;
596                         s32 offy = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y;
597
598                         // Apply kerning.
599                         core::vector2di k = getKerning(currentChar, previousChar);
600                         offset.X += k.X;
601                         offset.Y += k.Y;
602
603                         // Determine rendering information.
604                         SGUITTGlyph& glyph = Glyphs[n-1];
605                         CGUITTGlyphPage* const page = Glyph_Pages[glyph.glyph_page];
606                         page->render_positions.push_back(core::position2di(offset.X + offx, offset.Y + offy));
607                         page->render_source_rects.push_back(glyph.source_rect);
608                         Render_Map.set(glyph.glyph_page, page);
609                 }
610                 offset.X += getWidthFromCharacter(currentChar);
611
612                 previousChar = currentChar;
613                 ++iter;
614         }
615
616         // Draw now.
617         update_glyph_pages();
618         core::map<u32, CGUITTGlyphPage*>::Iterator j = Render_Map.getIterator();
619         while (!j.atEnd())
620         {
621                 core::map<u32, CGUITTGlyphPage*>::Node* n = j.getNode();
622                 j++;
623                 if (n == 0) continue;
624
625                 CGUITTGlyphPage* page = n->getValue();
626
627                 if (!use_transparency) color.color |= 0xff000000;
628                 Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, color, true);
629         }
630 }
631
632 core::dimension2d<u32> CGUITTFont::getCharDimension(const wchar_t ch) const
633 {
634         return core::dimension2d<u32>(getWidthFromCharacter(ch), getHeightFromCharacter(ch));
635 }
636
637 core::dimension2d<u32> CGUITTFont::getDimension(const wchar_t* text) const
638 {
639         return getDimension(core::ustring(text));
640 }
641
642 core::dimension2d<u32> CGUITTFont::getDimension(const core::ustring& text) const
643 {
644         // Get the maximum font height.  Unfortunately, we have to do this hack as
645         // Irrlicht will draw things wrong.  In FreeType, the font size is the
646         // maximum size for a single glyph, but that glyph may hang "under" the
647         // draw line, increasing the total font height to beyond the set size.
648         // Irrlicht does not understand this concept when drawing fonts.  Also, I
649         // add +1 to give it a 1 pixel blank border.  This makes things like
650         // tooltips look nicer.
651         s32 test1 = getHeightFromCharacter((uchar32_t)'g') + 1;
652         s32 test2 = getHeightFromCharacter((uchar32_t)'j') + 1;
653         s32 test3 = getHeightFromCharacter((uchar32_t)'_') + 1;
654         s32 max_font_height = core::max_(test1, core::max_(test2, test3));
655
656         core::dimension2d<u32> text_dimension(0, max_font_height);
657         core::dimension2d<u32> line(0, max_font_height);
658
659         uchar32_t previousChar = 0;
660         core::ustring::const_iterator iter = text.begin();
661         for (; !iter.atEnd(); ++iter)
662         {
663                 uchar32_t p = *iter;
664                 bool lineBreak = false;
665                 if (p == '\r')  // Mac or Windows line breaks.
666                 {
667                         lineBreak = true;
668                         if (*(iter + 1) == '\n')
669                         {
670                                 ++iter;
671                                 p = *iter;
672                         }
673                 }
674                 else if (p == '\n')     // Unix line breaks.
675                 {
676                         lineBreak = true;
677                 }
678
679                 // Kerning.
680                 core::vector2di k = getKerning(p, previousChar);
681                 line.Width += k.X;
682                 previousChar = p;
683
684                 // Check for linebreak.
685                 if (lineBreak)
686                 {
687                         previousChar = 0;
688                         text_dimension.Height += line.Height;
689                         if (text_dimension.Width < line.Width)
690                                 text_dimension.Width = line.Width;
691                         line.Width = 0;
692                         line.Height = max_font_height;
693                         continue;
694                 }
695                 line.Width += getWidthFromCharacter(p);
696         }
697         if (text_dimension.Width < line.Width)
698                 text_dimension.Width = line.Width;
699
700         return text_dimension;
701 }
702
703 inline u32 CGUITTFont::getWidthFromCharacter(wchar_t c) const
704 {
705         return getWidthFromCharacter((uchar32_t)c);
706 }
707
708 inline u32 CGUITTFont::getWidthFromCharacter(uchar32_t c) const
709 {
710         // Set the size of the face.
711         // This is because we cache faces and the face may have been set to a different size.
712         //FT_Set_Pixel_Sizes(tt_face, 0, size);
713
714         u32 n = getGlyphIndexByChar(c);
715         if (n > 0)
716         {
717                 int w = Glyphs[n-1].advance.x / 64;
718                 return w;
719         }
720         if (c >= 0x2000)
721                 return (font_metrics.ascender / 64);
722         else return (font_metrics.ascender / 64) / 2;
723 }
724
725 inline u32 CGUITTFont::getHeightFromCharacter(wchar_t c) const
726 {
727         return getHeightFromCharacter((uchar32_t)c);
728 }
729
730 inline u32 CGUITTFont::getHeightFromCharacter(uchar32_t c) const
731 {
732         // Set the size of the face.
733         // This is because we cache faces and the face may have been set to a different size.
734         //FT_Set_Pixel_Sizes(tt_face, 0, size);
735
736         u32 n = getGlyphIndexByChar(c);
737         if (n > 0)
738         {
739                 // Grab the true height of the character, taking into account underhanging glyphs.
740                 s32 height = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y + Glyphs[n-1].source_rect.getHeight();
741                 return height;
742         }
743         if (c >= 0x2000)
744                 return (font_metrics.ascender / 64);
745         else return (font_metrics.ascender / 64) / 2;
746 }
747
748 u32 CGUITTFont::getGlyphIndexByChar(wchar_t c) const
749 {
750         return getGlyphIndexByChar((uchar32_t)c);
751 }
752
753 u32 CGUITTFont::getGlyphIndexByChar(uchar32_t c) const
754 {
755         // Get the glyph.
756         u32 glyph = FT_Get_Char_Index(tt_face, c);
757
758         // Check for a valid glyph.  If it is invalid, attempt to use the replacement character.
759         if (glyph == 0)
760                 glyph = FT_Get_Char_Index(tt_face, core::unicode::UTF_REPLACEMENT_CHARACTER);
761
762         // If our glyph is already loaded, don't bother doing any batch loading code.
763         if (glyph != 0 && Glyphs[glyph - 1].isLoaded)
764                 return glyph;
765
766         // Determine our batch loading positions.
767         u32 half_size = (batch_load_size / 2);
768         u32 start_pos = 0;
769         if (c > half_size) start_pos = c - half_size;
770         u32 end_pos = start_pos + batch_load_size;
771
772         // Load all our characters.
773         do
774         {
775                 // Get the character we are going to load.
776                 u32 char_index = FT_Get_Char_Index(tt_face, start_pos);
777
778                 // If the glyph hasn't been loaded yet, do it now.
779                 if (char_index)
780                 {
781                         SGUITTGlyph& glyph = Glyphs[char_index - 1];
782                         if (!glyph.isLoaded)
783                         {
784                                 glyph.preload(char_index, tt_face, Driver, size, load_flags);
785                                 Glyph_Pages[glyph.glyph_page]->pushGlyphToBePaged(&glyph);
786                         }
787                 }
788         }
789         while (++start_pos < end_pos);
790
791         // Return our original character.
792         return glyph;
793 }
794
795 s32 CGUITTFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x) const
796 {
797         return getCharacterFromPos(core::ustring(text), pixel_x);
798 }
799
800 s32 CGUITTFont::getCharacterFromPos(const core::ustring& text, s32 pixel_x) const
801 {
802         s32 x = 0;
803         //s32 idx = 0;
804
805         u32 character = 0;
806         uchar32_t previousChar = 0;
807         core::ustring::const_iterator iter = text.begin();
808         while (!iter.atEnd())
809         {
810                 uchar32_t c = *iter;
811                 x += getWidthFromCharacter(c);
812
813                 // Kerning.
814                 core::vector2di k = getKerning(c, previousChar);
815                 x += k.X;
816
817                 if (x >= pixel_x)
818                         return character;
819
820                 previousChar = c;
821                 ++iter;
822                 ++character;
823         }
824
825         return -1;
826 }
827
828 void CGUITTFont::setKerningWidth(s32 kerning)
829 {
830         GlobalKerningWidth = kerning;
831 }
832
833 void CGUITTFont::setKerningHeight(s32 kerning)
834 {
835         GlobalKerningHeight = kerning;
836 }
837
838 s32 CGUITTFont::getKerningWidth(const wchar_t* thisLetter, const wchar_t* previousLetter) const
839 {
840         if (tt_face == 0)
841                 return GlobalKerningWidth;
842         if (thisLetter == 0 || previousLetter == 0)
843                 return 0;
844
845         return getKerningWidth((uchar32_t)*thisLetter, (uchar32_t)*previousLetter);
846 }
847
848 s32 CGUITTFont::getKerningWidth(const uchar32_t thisLetter, const uchar32_t previousLetter) const
849 {
850         // Return only the kerning width.
851         return getKerning(thisLetter, previousLetter).X;
852 }
853
854 s32 CGUITTFont::getKerningHeight() const
855 {
856         // FreeType 2 currently doesn't return any height kerning information.
857         return GlobalKerningHeight;
858 }
859
860 core::vector2di CGUITTFont::getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const
861 {
862         return getKerning((uchar32_t)thisLetter, (uchar32_t)previousLetter);
863 }
864
865 core::vector2di CGUITTFont::getKerning(const uchar32_t thisLetter, const uchar32_t previousLetter) const
866 {
867         if (tt_face == 0 || thisLetter == 0 || previousLetter == 0)
868                 return core::vector2di();
869
870         // Set the size of the face.
871         // This is because we cache faces and the face may have been set to a different size.
872         FT_Set_Pixel_Sizes(tt_face, 0, size);
873
874         core::vector2di ret(GlobalKerningWidth, GlobalKerningHeight);
875
876         // If we don't have kerning, no point in continuing.
877         if (!FT_HAS_KERNING(tt_face))
878                 return ret;
879
880         // Get the kerning information.
881         FT_Vector v;
882         FT_Get_Kerning(tt_face, getGlyphIndexByChar(previousLetter), getGlyphIndexByChar(thisLetter), FT_KERNING_DEFAULT, &v);
883
884         // If we have a scalable font, the return value will be in font points.
885         if (FT_IS_SCALABLE(tt_face))
886         {
887                 // Font points, so divide by 64.
888                 ret.X += (v.x / 64);
889                 ret.Y += (v.y / 64);
890         }
891         else
892         {
893                 // Pixel units.
894                 ret.X += v.x;
895                 ret.Y += v.y;
896         }
897         return ret;
898 }
899
900 void CGUITTFont::setInvisibleCharacters(const wchar_t *s)
901 {
902         core::ustring us(s);
903         Invisible = us;
904 }
905
906 void CGUITTFont::setInvisibleCharacters(const core::ustring& s)
907 {
908         Invisible = s;
909 }
910
911 video::IImage* CGUITTFont::createTextureFromChar(const uchar32_t& ch)
912 {
913         u32 n = getGlyphIndexByChar(ch);
914         const SGUITTGlyph& glyph = Glyphs[n-1];
915         CGUITTGlyphPage* page = Glyph_Pages[glyph.glyph_page];
916
917         if (page->dirty)
918                 page->updateTexture();
919
920         video::ITexture* tex = page->texture;
921
922         // Acquire a read-only lock of the corresponding page texture.
923         #if IRRLICHT_VERSION_MAJOR==1 && IRRLICHT_VERSION_MINOR>=8
924         void* ptr = tex->lock(video::ETLM_READ_ONLY);
925         #else
926         void* ptr = tex->lock(true);
927         #endif
928
929         video::ECOLOR_FORMAT format = tex->getColorFormat();
930         core::dimension2du tex_size = tex->getOriginalSize();
931         video::IImage* pageholder = Driver->createImageFromData(format, tex_size, ptr, true, false);
932
933         // Copy the image data out of the page texture.
934         core::dimension2du glyph_size(glyph.source_rect.getSize());
935         video::IImage* image = Driver->createImage(format, glyph_size);
936         pageholder->copyTo(image, core::position2di(0, 0), glyph.source_rect);
937
938         tex->unlock();
939         return image;
940 }
941
942 video::ITexture* CGUITTFont::getPageTextureByIndex(const u32& page_index) const
943 {
944         if (page_index < Glyph_Pages.size())
945                 return Glyph_Pages[page_index]->texture;
946         else
947                 return 0;
948 }
949
950 void CGUITTFont::createSharedPlane()
951 {
952         /*
953                 2___3
954                 |  /|
955                 | / |   <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
956                 |/  |   <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
957                 0---1
958         */
959
960         using namespace core;
961         using namespace video;
962         using namespace scene;
963         S3DVertex vertices[4];
964         u16 indices[6] = {0,2,3,3,1,0};
965         vertices[0] = S3DVertex(vector3df(0,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,1));
966         vertices[1] = S3DVertex(vector3df(1,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,1));
967         vertices[2] = S3DVertex(vector3df(0, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,0));
968         vertices[3] = S3DVertex(vector3df(1, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,0));
969
970         SMeshBuffer* buf = new SMeshBuffer();
971         buf->append(vertices, 4, indices, 6);
972
973         shared_plane_.addMeshBuffer( buf );
974
975         shared_plane_ptr_ = &shared_plane_;
976         buf->drop(); //the addMeshBuffer method will grab it, so we can drop this ptr.
977 }
978
979 core::dimension2d<u32> CGUITTFont::getDimensionUntilEndOfLine(const wchar_t* p) const
980 {
981         core::stringw s;
982         for (const wchar_t* temp = p; temp && *temp != '\0' && *temp != L'\r' && *temp != L'\n'; ++temp )
983                 s.append(*temp);
984
985         return getDimension(s.c_str());
986 }
987
988 core::array<scene::ISceneNode*> CGUITTFont::addTextSceneNode(const wchar_t* text, scene::ISceneManager* smgr, scene::ISceneNode* parent, const video::SColor& color, bool center)
989 {
990         using namespace core;
991         using namespace video;
992         using namespace scene;
993
994         array<scene::ISceneNode*> container;
995
996         if (!Driver || !smgr) return container;
997         if (!parent)
998                 parent = smgr->addEmptySceneNode(smgr->getRootSceneNode(), -1);
999         // if you don't specify parent, then we add a empty node attached to the root node
1000         // this is generally undesirable.
1001
1002         if (!shared_plane_ptr_) //this points to a static mesh that contains the plane
1003                 createSharedPlane(); //if it's not initialized, we create one.
1004
1005         dimension2d<s32> text_size(getDimension(text)); //convert from unsigned to signed.
1006         vector3df start_point(0, 0, 0), offset;
1007
1008         /** NOTICE:
1009                 Because we are considering adding texts into 3D world, all Y axis vectors are inverted.
1010         **/
1011
1012         // There's currently no "vertical center" concept when you apply text scene node to the 3D world.
1013         if (center)
1014         {
1015                 offset.X = start_point.X = -text_size.Width / 2.f;
1016                 offset.Y = start_point.Y = +text_size.Height/ 2.f;
1017                 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text).Width) >> 1;
1018         }
1019
1020         // the default font material
1021         SMaterial mat;
1022         mat.setFlag(video::EMF_LIGHTING, true);
1023         mat.setFlag(video::EMF_ZWRITE_ENABLE, false);
1024         mat.setFlag(video::EMF_NORMALIZE_NORMALS, true);
1025         mat.ColorMaterial = video::ECM_NONE;
1026         mat.MaterialType = use_transparency ? video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_SOLID;
1027         mat.MaterialTypeParam = 0.01f;
1028         mat.DiffuseColor = color;
1029
1030         wchar_t current_char = 0, previous_char = 0;
1031         u32 n = 0;
1032
1033         array<u32> glyph_indices;
1034
1035         while (*text)
1036         {
1037                 current_char = *text;
1038                 bool line_break=false;
1039                 if (current_char == L'\r') // Mac or Windows breaks
1040                 {
1041                         line_break = true;
1042                         if (*(text + 1) == L'\n') // Windows line breaks.
1043                                 current_char = *(++text);
1044                 }
1045                 else if (current_char == L'\n') // Unix breaks
1046                 {
1047                         line_break = true;
1048                 }
1049
1050                 if (line_break)
1051                 {
1052                         previous_char = 0;
1053                         offset.Y -= tt_face->size->metrics.ascender / 64;
1054                         offset.X = start_point.X;
1055                         if (center)
1056                                 offset.X += (text_size.Width - getDimensionUntilEndOfLine(text+1).Width) >> 1;
1057                         ++text;
1058                 }
1059                 else
1060                 {
1061                         n = getGlyphIndexByChar(current_char);
1062                         if (n > 0)
1063                         {
1064                                 glyph_indices.push_back( n );
1065
1066                                 // Store glyph size and offset informations.
1067                                 SGUITTGlyph const& glyph = Glyphs[n-1];
1068                                 u32 texw = glyph.source_rect.getWidth();
1069                                 u32 texh = glyph.source_rect.getHeight();
1070                                 s32 offx = glyph.offset.X;
1071                                 s32 offy = (font_metrics.ascender / 64) - glyph.offset.Y;
1072
1073                                 // Apply kerning.
1074                                 vector2di k = getKerning(current_char, previous_char);
1075                                 offset.X += k.X;
1076                                 offset.Y += k.Y;
1077
1078                                 vector3df current_pos(offset.X + offx, offset.Y - offy, 0);
1079                                 dimension2d<u32> letter_size = dimension2d<u32>(texw, texh);
1080
1081                                 // Now we copy planes corresponding to the letter size.
1082                                 IMeshManipulator* mani = smgr->getMeshManipulator();
1083                                 IMesh* meshcopy = mani->createMeshCopy(shared_plane_ptr_);
1084                                 #if IRRLICHT_VERSION_MAJOR==1 && IRRLICHT_VERSION_MINOR>=8
1085                                 mani->scale(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1));
1086                                 #else
1087                                 mani->scaleMesh(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1));
1088                                 #endif
1089
1090                                 ISceneNode* current_node = smgr->addMeshSceneNode(meshcopy, parent, -1, current_pos);
1091                                 meshcopy->drop();
1092
1093                                 current_node->getMaterial(0) = mat;
1094                                 current_node->setAutomaticCulling(EAC_OFF);
1095                                 current_node->setIsDebugObject(true);  //so the picking won't have any effect on individual letter
1096                                 //current_node->setDebugDataVisible(EDS_BBOX); //de-comment this when debugging
1097
1098                                 container.push_back(current_node);
1099                         }
1100                         offset.X += getWidthFromCharacter(current_char);
1101                         previous_char = current_char;
1102                         ++text;
1103                 }
1104         }
1105
1106         update_glyph_pages();
1107         //only after we update the textures can we use the glyph page textures.
1108
1109         for (u32 i = 0; i < glyph_indices.size(); ++i)
1110         {
1111                 u32 n = glyph_indices[i];
1112                 SGUITTGlyph const& glyph = Glyphs[n-1];
1113                 ITexture* current_tex = Glyph_Pages[glyph.glyph_page]->texture;
1114                 f32 page_texture_size = (f32)current_tex->getSize().Width;
1115                 //Now we calculate the UV position according to the texture size and the source rect.
1116                 //
1117                 //  2___3
1118                 //  |  /|
1119                 //  | / |       <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1)
1120                 //  |/  |       <-- the texture coords of point 2 is (0,0, point 0 is (0, 1)
1121                 //  0---1
1122                 //
1123                 f32 u1 = glyph.source_rect.UpperLeftCorner.X / page_texture_size;
1124                 f32 u2 = u1 + (glyph.source_rect.getWidth() / page_texture_size);
1125                 f32 v1 = glyph.source_rect.UpperLeftCorner.Y / page_texture_size;
1126                 f32 v2 = v1 + (glyph.source_rect.getHeight() / page_texture_size);
1127
1128                 //we can be quite sure that this is IMeshSceneNode, because we just added them in the above loop.
1129                 IMeshSceneNode* node = static_cast<IMeshSceneNode*>(container[i]);
1130
1131                 S3DVertex* pv = static_cast<S3DVertex*>(node->getMesh()->getMeshBuffer(0)->getVertices());
1132                 //pv[0].TCoords.Y = pv[1].TCoords.Y = (letter_size.Height - 1) / static_cast<f32>(letter_size.Height);
1133                 //pv[1].TCoords.X = pv[3].TCoords.X = (letter_size.Width - 1)  / static_cast<f32>(letter_size.Width);
1134                 pv[0].TCoords = vector2df(u1, v2);
1135                 pv[1].TCoords = vector2df(u2, v2);
1136                 pv[2].TCoords = vector2df(u1, v1);
1137                 pv[3].TCoords = vector2df(u2, v1);
1138
1139                 container[i]->getMaterial(0).setTexture(0, current_tex);
1140         }
1141
1142         return container;
1143 }
1144
1145 } // end namespace gui
1146 } // end namespace irr