]> git.lizzy.rs Git - irrlicht.git/blob - source/Irrlicht/CGUIFont.cpp
Drop IrrCompileConfig (#163)
[irrlicht.git] / source / Irrlicht / CGUIFont.cpp
1 // Copyright (C) 2002-2012 Nikolaus Gebhardt\r
2 // This file is part of the "Irrlicht Engine".\r
3 // For conditions of distribution and use, see copyright notice in irrlicht.h\r
4 \r
5 #include "CGUIFont.h"\r
6 \r
7 #include "os.h"\r
8 #include "coreutil.h"\r
9 #include "IGUIEnvironment.h"\r
10 #include "IReadFile.h"\r
11 #include "IVideoDriver.h"\r
12 #include "IGUISpriteBank.h"\r
13 \r
14 namespace irr\r
15 {\r
16 namespace gui\r
17 {\r
18 \r
19 //! constructor\r
20 CGUIFont::CGUIFont(IGUIEnvironment *env, const io::path& filename)\r
21 : Driver(0), SpriteBank(0), Environment(env), WrongCharacter(0),\r
22         MaxHeight(0), GlobalKerningWidth(0), GlobalKerningHeight(0)\r
23 {\r
24         #ifdef _DEBUG\r
25         setDebugName("CGUIFont");\r
26         #endif\r
27 \r
28         if (Environment)\r
29         {\r
30                 // don't grab environment, to avoid circular references\r
31                 Driver = Environment->getVideoDriver();\r
32 \r
33                 SpriteBank = Environment->getSpriteBank(filename);\r
34                 if (!SpriteBank)        // could be default-font which has no file\r
35                         SpriteBank = Environment->addEmptySpriteBank(filename);\r
36                 if (SpriteBank)\r
37                         SpriteBank->grab();\r
38         }\r
39 \r
40         if (Driver)\r
41                 Driver->grab();\r
42 \r
43         setInvisibleCharacters ( L" " );\r
44 }\r
45 \r
46 \r
47 //! destructor\r
48 CGUIFont::~CGUIFont()\r
49 {\r
50         if (Driver)\r
51                 Driver->drop();\r
52 \r
53         if (SpriteBank)\r
54         {\r
55                 SpriteBank->drop();\r
56                 // TODO: spritebank still exists in gui-environment and should be removed here when it's\r
57                 // reference-count is 1. Just can't do that from here at the moment.\r
58                 // But spritebank would not be able to drop textures anyway because those are in texture-cache\r
59                 // where they can't be removed unless materials start reference-couting 'em.\r
60         }\r
61 }\r
62 \r
63 \r
64 #if 0\r
65 //! loads a font file from xml\r
66 bool CGUIFont::load(io::IXMLReader* xml, const io::path& directory)\r
67 {\r
68         if (!SpriteBank)\r
69                 return false;\r
70 \r
71         SpriteBank->clear();\r
72 \r
73         while (xml->read())\r
74         {\r
75                 if (io::EXN_ELEMENT == xml->getNodeType())\r
76                 {\r
77                         if (core::stringw(L"Texture") == xml->getNodeName())\r
78                         {\r
79                                 // add a texture\r
80                                 core::stringc fn = xml->getAttributeValue(L"filename");\r
81                                 u32 i = (u32)xml->getAttributeValueAsInt(L"index");\r
82                                 core::stringw alpha = xml->getAttributeValue(L"hasAlpha");\r
83 \r
84                                 while (i+1 > SpriteBank->getTextureCount())\r
85                                         SpriteBank->addTexture(0);\r
86 \r
87                                 bool flags[3];\r
88                                 pushTextureCreationFlags(flags);\r
89 \r
90                                 // load texture\r
91                                 io::path textureFullName = core::mergeFilename(directory, fn);\r
92                                 SpriteBank->setTexture(i, Driver->getTexture(textureFullName));\r
93 \r
94                                 popTextureCreationFlags(flags);\r
95 \r
96                                 // couldn't load texture, abort.\r
97                                 if (!SpriteBank->getTexture(i))\r
98                                 {\r
99                                         os::Printer::log("Unable to load all textures in the font, aborting", ELL_ERROR);\r
100                                         return false;\r
101                                 }\r
102                                 else\r
103                                 {\r
104                                         // colorkey texture rather than alpha channel?\r
105                                         if (alpha == core::stringw("false"))\r
106                                                 Driver->makeColorKeyTexture(SpriteBank->getTexture(i), core::position2di(0,0));\r
107                                 }\r
108                         }\r
109                         else if (core::stringw(L"c") == xml->getNodeName())\r
110                         {\r
111                                 // adding a character to this font\r
112                                 SFontArea a;\r
113                                 SGUISpriteFrame f;\r
114                                 SGUISprite s;\r
115                                 core::rect<s32> rectangle;\r
116 \r
117                                 a.underhang             = xml->getAttributeValueAsInt(L"u");\r
118                                 a.overhang              = xml->getAttributeValueAsInt(L"o");\r
119                                 a.spriteno              = SpriteBank->getSprites().size();\r
120                                 s32 texno               = xml->getAttributeValueAsInt(L"i");\r
121 \r
122                                 // parse rectangle\r
123                                 core::stringc rectstr   = xml->getAttributeValue(L"r");\r
124                                 wchar_t ch              = xml->getAttributeValue(L"c")[0];\r
125 \r
126                                 const c8 *c = rectstr.c_str();\r
127                                 s32 val;\r
128                                 val = 0;\r
129                                 while (*c >= '0' && *c <= '9')\r
130                                 {\r
131                                         val *= 10;\r
132                                         val += *c - '0';\r
133                                         c++;\r
134                                 }\r
135                                 rectangle.UpperLeftCorner.X = val;\r
136                                 while (*c == L' ' || *c == L',') c++;\r
137 \r
138                                 val = 0;\r
139                                 while (*c >= '0' && *c <= '9')\r
140                                 {\r
141                                         val *= 10;\r
142                                         val += *c - '0';\r
143                                         c++;\r
144                                 }\r
145                                 rectangle.UpperLeftCorner.Y = val;\r
146                                 while (*c == L' ' || *c == L',') c++;\r
147 \r
148                                 val = 0;\r
149                                 while (*c >= '0' && *c <= '9')\r
150                                 {\r
151                                         val *= 10;\r
152                                         val += *c - '0';\r
153                                         c++;\r
154                                 }\r
155                                 rectangle.LowerRightCorner.X = val;\r
156                                 while (*c == L' ' || *c == L',') c++;\r
157 \r
158                                 val = 0;\r
159                                 while (*c >= '0' && *c <= '9')\r
160                                 {\r
161                                         val *= 10;\r
162                                         val += *c - '0';\r
163                                         c++;\r
164                                 }\r
165                                 rectangle.LowerRightCorner.Y = val;\r
166 \r
167                                 CharacterMap.emplace(ch, Areas.size());\r
168 \r
169                                 // make frame\r
170                                 f.rectNumber = SpriteBank->getPositions().size();\r
171                                 f.textureNumber = texno;\r
172 \r
173                                 // add frame to sprite\r
174                                 s.Frames.push_back(f);\r
175                                 s.frameTime = 0;\r
176 \r
177                                 // add rectangle to sprite bank\r
178                                 SpriteBank->getPositions().push_back(rectangle);\r
179                                 a.width = rectangle.getWidth();\r
180 \r
181                                 // add sprite to sprite bank\r
182                                 SpriteBank->getSprites().push_back(s);\r
183 \r
184                                 // add character to font\r
185                                 Areas.push_back(a);\r
186                         }\r
187                 }\r
188         }\r
189 \r
190         // set bad character\r
191         WrongCharacter = getAreaFromCharacter(L' ');\r
192 \r
193         setMaxHeight();\r
194 \r
195         return true;\r
196 }\r
197 #endif\r
198 \r
199 \r
200 void CGUIFont::setMaxHeight()\r
201 {\r
202         if ( !SpriteBank )\r
203                 return;\r
204 \r
205         MaxHeight = 0;\r
206 \r
207         core::array< core::rect<s32> >& p = SpriteBank->getPositions();\r
208 \r
209         for (u32 i=0; i<p.size(); ++i)\r
210         {\r
211                 const s32 t = p[i].getHeight();\r
212                 if (t>MaxHeight)\r
213                         MaxHeight = t;\r
214         }\r
215 }\r
216 \r
217 void CGUIFont::pushTextureCreationFlags(bool(&flags)[3])\r
218 {\r
219         flags[0] = Driver->getTextureCreationFlag(video::ETCF_ALLOW_NON_POWER_2);\r
220         flags[1] = Driver->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS);\r
221         flags[2] = Driver->getTextureCreationFlag(video::ETCF_ALLOW_MEMORY_COPY);\r
222 \r
223         Driver->setTextureCreationFlag(video::ETCF_ALLOW_NON_POWER_2, true);\r
224         Driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);\r
225         Driver->setTextureCreationFlag(video::ETCF_ALLOW_MEMORY_COPY, true);\r
226 }\r
227 \r
228 void CGUIFont::popTextureCreationFlags(const bool(&flags)[3])\r
229 {\r
230         Driver->setTextureCreationFlag(video::ETCF_ALLOW_NON_POWER_2, flags[0]);\r
231         Driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, flags[1]);\r
232         Driver->setTextureCreationFlag(video::ETCF_ALLOW_MEMORY_COPY, flags[2]);\r
233 }\r
234 \r
235 //! loads a font file, native file needed, for texture parsing\r
236 bool CGUIFont::load(io::IReadFile* file)\r
237 {\r
238         if (!Driver)\r
239                 return false;\r
240 \r
241         return loadTexture(Driver->createImageFromFile(file),\r
242                                 file->getFileName());\r
243 }\r
244 \r
245 \r
246 //! loads a font file, native file needed, for texture parsing\r
247 bool CGUIFont::load(const io::path& filename)\r
248 {\r
249         if (!Driver)\r
250                 return false;\r
251 \r
252         return loadTexture(Driver->createImageFromFile( filename ),\r
253                                 filename);\r
254 }\r
255 \r
256 \r
257 //! load & prepare font from ITexture\r
258 bool CGUIFont::loadTexture(video::IImage* image, const io::path& name)\r
259 {\r
260         if (!image || !SpriteBank)\r
261                 return false;\r
262 \r
263         s32 lowerRightPositions = 0;\r
264 \r
265         video::IImage* tmpImage=image;\r
266         bool deleteTmpImage=false;\r
267         switch(image->getColorFormat())\r
268         {\r
269         case video::ECF_R5G6B5:\r
270                 tmpImage =  Driver->createImage(video::ECF_A1R5G5B5,image->getDimension());\r
271                 image->copyTo(tmpImage);\r
272                 deleteTmpImage=true;\r
273                 break;\r
274         case video::ECF_A1R5G5B5:\r
275         case video::ECF_A8R8G8B8:\r
276                 break;\r
277         case video::ECF_R8G8B8:\r
278                 tmpImage = Driver->createImage(video::ECF_A8R8G8B8,image->getDimension());\r
279                 image->copyTo(tmpImage);\r
280                 deleteTmpImage=true;\r
281                 break;\r
282         default:\r
283                 os::Printer::log("Unknown texture format provided for CGUIFont::loadTexture", ELL_ERROR);\r
284                 return false;\r
285         }\r
286         readPositions(tmpImage, lowerRightPositions);\r
287 \r
288         WrongCharacter = getAreaFromCharacter(L' ');\r
289 \r
290         // output warnings\r
291         if (!lowerRightPositions || !SpriteBank->getSprites().size())\r
292                 os::Printer::log("Either no upper or lower corner pixels in the font file. If this font was made using the new font tool, please load the XML file instead. If not, the font may be corrupted.", ELL_ERROR);\r
293         else\r
294         if (lowerRightPositions != (s32)SpriteBank->getPositions().size())\r
295                 os::Printer::log("The amount of upper corner pixels and the lower corner pixels is not equal, font file may be corrupted.", ELL_ERROR);\r
296 \r
297         bool ret = ( !SpriteBank->getSprites().empty() && lowerRightPositions );\r
298 \r
299         if ( ret )\r
300         {\r
301                 bool flags[3];\r
302                 pushTextureCreationFlags(flags);\r
303 \r
304                 SpriteBank->addTexture(Driver->addTexture(name, tmpImage));\r
305 \r
306                 popTextureCreationFlags(flags);\r
307         }\r
308         if (deleteTmpImage)\r
309                 tmpImage->drop();\r
310         image->drop();\r
311 \r
312         setMaxHeight();\r
313 \r
314         return ret;\r
315 }\r
316 \r
317 \r
318 void CGUIFont::readPositions(video::IImage* image, s32& lowerRightPositions)\r
319 {\r
320         if (!SpriteBank )\r
321                 return;\r
322 \r
323         const core::dimension2d<u32> size = image->getDimension();\r
324 \r
325         video::SColor colorTopLeft = image->getPixel(0,0);\r
326         colorTopLeft.setAlpha(255);\r
327         image->setPixel(0,0,colorTopLeft);\r
328         video::SColor colorLowerRight = image->getPixel(1,0);\r
329         video::SColor colorBackGround = image->getPixel(2,0);\r
330         video::SColor colorBackGroundTransparent = 0;\r
331 \r
332         image->setPixel(1,0,colorBackGround);\r
333 \r
334         // start parsing\r
335 \r
336         core::position2d<s32> pos(0,0);\r
337         for (pos.Y=0; pos.Y<(s32)size.Height; ++pos.Y)\r
338         {\r
339                 for (pos.X=0; pos.X<(s32)size.Width; ++pos.X)\r
340                 {\r
341                         const video::SColor c = image->getPixel(pos.X, pos.Y);\r
342                         if (c == colorTopLeft)\r
343                         {\r
344                                 image->setPixel(pos.X, pos.Y, colorBackGroundTransparent);\r
345                                 SpriteBank->getPositions().push_back(core::rect<s32>(pos, pos));\r
346                         }\r
347                         else\r
348                         if (c == colorLowerRight)\r
349                         {\r
350                                 // too many lower right points\r
351                                 if (SpriteBank->getPositions().size()<=(u32)lowerRightPositions)\r
352                                 {\r
353                                         lowerRightPositions = 0;\r
354                                         return;\r
355                                 }\r
356 \r
357                                 image->setPixel(pos.X, pos.Y, colorBackGroundTransparent);\r
358                                 SpriteBank->getPositions()[lowerRightPositions].LowerRightCorner = pos;\r
359                                 // add frame to sprite bank\r
360                                 SGUISpriteFrame f;\r
361                                 f.rectNumber = lowerRightPositions;\r
362                                 f.textureNumber = 0;\r
363                                 SGUISprite s;\r
364                                 s.Frames.push_back(f);\r
365                                 s.frameTime = 0;\r
366                                 SpriteBank->getSprites().push_back(s);\r
367                                 // add character to font\r
368                                 SFontArea a;\r
369                                 a.overhang = 0;\r
370                                 a.underhang = 0;\r
371                                 a.spriteno = lowerRightPositions;\r
372                                 a.width = SpriteBank->getPositions()[lowerRightPositions].getWidth();\r
373                                 Areas.push_back(a);\r
374                                 // map letter to character\r
375                                 wchar_t ch = (wchar_t)(lowerRightPositions + 32);\r
376                                 CharacterMap[ch] = lowerRightPositions;\r
377 \r
378                                 ++lowerRightPositions;\r
379                         }\r
380                         else\r
381                         if (c == colorBackGround)\r
382                                 image->setPixel(pos.X, pos.Y, colorBackGroundTransparent);\r
383                 }\r
384         }\r
385 }\r
386 \r
387 \r
388 //! set an Pixel Offset on Drawing ( scale position on width )\r
389 void CGUIFont::setKerningWidth(s32 kerning)\r
390 {\r
391         GlobalKerningWidth = kerning;\r
392 }\r
393 \r
394 \r
395 //! set an Pixel Offset on Drawing ( scale position on width )\r
396 s32 CGUIFont::getKerningWidth(const wchar_t* thisLetter, const wchar_t* previousLetter) const\r
397 {\r
398         s32 ret = GlobalKerningWidth;\r
399 \r
400         if (thisLetter)\r
401         {\r
402                 ret += Areas[getAreaFromCharacter(*thisLetter)].overhang;\r
403 \r
404                 if (previousLetter)\r
405                 {\r
406                         ret += Areas[getAreaFromCharacter(*previousLetter)].underhang;\r
407                 }\r
408         }\r
409 \r
410         return ret;\r
411 }\r
412 \r
413 \r
414 //! set an Pixel Offset on Drawing ( scale position on height )\r
415 void CGUIFont::setKerningHeight(s32 kerning)\r
416 {\r
417         GlobalKerningHeight = kerning;\r
418 }\r
419 \r
420 \r
421 //! set an Pixel Offset on Drawing ( scale position on height )\r
422 s32 CGUIFont::getKerningHeight () const\r
423 {\r
424         return GlobalKerningHeight;\r
425 }\r
426 \r
427 \r
428 //! returns the sprite number from a given character\r
429 u32 CGUIFont::getSpriteNoFromChar(const wchar_t *c) const\r
430 {\r
431         return Areas[getAreaFromCharacter(*c)].spriteno;\r
432 }\r
433 \r
434 \r
435 s32 CGUIFont::getAreaFromCharacter(const wchar_t c) const\r
436 {\r
437         auto n = CharacterMap.find(c);\r
438         if (n != CharacterMap.end())\r
439                 return n->second;\r
440         else\r
441                 return WrongCharacter;\r
442 }\r
443 \r
444 void CGUIFont::setInvisibleCharacters( const wchar_t *s )\r
445 {\r
446         Invisible = s;\r
447 }\r
448 \r
449 \r
450 //! returns the dimension of text\r
451 core::dimension2d<u32> CGUIFont::getDimension(const wchar_t* text) const\r
452 {\r
453         core::dimension2d<u32> dim(0, 0);\r
454         core::dimension2d<u32> thisLine(0, MaxHeight);\r
455 \r
456         for (const wchar_t* p = text; *p; ++p)\r
457         {\r
458                 bool lineBreak=false;\r
459                 if (*p == L'\r') // Mac or Windows breaks\r
460                 {\r
461                         lineBreak = true;\r
462                         if (p[1] == L'\n') // Windows breaks\r
463                                 ++p;\r
464                 }\r
465                 else if (*p == L'\n') // Unix breaks\r
466                 {\r
467                         lineBreak = true;\r
468                 }\r
469                 if (lineBreak)\r
470                 {\r
471                         dim.Height += thisLine.Height;\r
472                         if (dim.Width < thisLine.Width)\r
473                                 dim.Width = thisLine.Width;\r
474                         thisLine.Width = 0;\r
475                         continue;\r
476                 }\r
477 \r
478                 const SFontArea &area = Areas[getAreaFromCharacter(*p)];\r
479 \r
480                 thisLine.Width += area.underhang;\r
481                 thisLine.Width += area.width + area.overhang + GlobalKerningWidth;\r
482         }\r
483 \r
484         dim.Height += thisLine.Height;\r
485         if (dim.Width < thisLine.Width)\r
486                 dim.Width = thisLine.Width;\r
487 \r
488         return dim;\r
489 }\r
490 \r
491 //! draws some text and clips it to the specified rectangle if wanted\r
492 void CGUIFont::draw(const core::stringw& text, const core::rect<s32>& position,\r
493                                         video::SColor color,\r
494                                         bool hcenter, bool vcenter, const core::rect<s32>* clip\r
495                                 )\r
496 {\r
497         if (!Driver || !SpriteBank)\r
498                 return;\r
499 \r
500         core::dimension2d<s32> textDimension;   // NOTE: don't make this u32 or the >> later on can fail when the dimension width is < position width\r
501         core::position2d<s32> offset = position.UpperLeftCorner;\r
502 \r
503         if (hcenter || vcenter || clip)\r
504                 textDimension = getDimension(text.c_str());\r
505 \r
506         if (hcenter)\r
507                 offset.X += (position.getWidth() - textDimension.Width) >> 1;\r
508 \r
509         if (vcenter)\r
510                 offset.Y += (position.getHeight() - textDimension.Height) >> 1;\r
511 \r
512         if (clip)\r
513         {\r
514                 core::rect<s32> clippedRect(offset, textDimension);\r
515                 clippedRect.clipAgainst(*clip);\r
516                 if (!clippedRect.isValid())\r
517                         return;\r
518         }\r
519 \r
520         core::array<u32> indices(text.size());\r
521         core::array<core::position2di> offsets(text.size());\r
522 \r
523         for(u32 i = 0;i < text.size();i++)\r
524         {\r
525                 wchar_t c = text[i];\r
526 \r
527                 bool lineBreak=false;\r
528                 if ( c == L'\r') // Mac or Windows breaks\r
529                 {\r
530                         lineBreak = true;\r
531                         if ( text[i + 1] == L'\n') // Windows breaks\r
532                                 c = text[++i];\r
533                 }\r
534                 else if ( c == L'\n') // Unix breaks\r
535                 {\r
536                         lineBreak = true;\r
537                 }\r
538 \r
539                 if (lineBreak)\r
540                 {\r
541                         offset.Y += MaxHeight;\r
542                         offset.X = position.UpperLeftCorner.X;\r
543 \r
544                         if ( hcenter )\r
545                         {\r
546                                 offset.X += (position.getWidth() - textDimension.Width) >> 1;\r
547                         }\r
548                         continue;\r
549                 }\r
550 \r
551                 SFontArea& area = Areas[getAreaFromCharacter(c)];\r
552 \r
553                 offset.X += area.underhang;\r
554                 if ( Invisible.findFirst ( c ) < 0 )\r
555                 {\r
556                         indices.push_back(area.spriteno);\r
557                         offsets.push_back(offset);\r
558                 }\r
559 \r
560                 offset.X += area.width + area.overhang + GlobalKerningWidth;\r
561         }\r
562 \r
563         SpriteBank->draw2DSpriteBatch(indices, offsets, clip, color);\r
564 }\r
565 \r
566 \r
567 //! Calculates the index of the character in the text which is on a specific position.\r
568 s32 CGUIFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x) const\r
569 {\r
570         s32 x = 0;\r
571         s32 idx = 0;\r
572 \r
573         while (text[idx])\r
574         {\r
575                 const SFontArea& a = Areas[getAreaFromCharacter(text[idx])];\r
576 \r
577                 x += a.width + a.overhang + a.underhang + GlobalKerningWidth;\r
578 \r
579                 if (x >= pixel_x)\r
580                         return idx;\r
581 \r
582                 ++idx;\r
583         }\r
584 \r
585         return -1;\r
586 }\r
587 \r
588 \r
589 IGUISpriteBank* CGUIFont::getSpriteBank() const\r
590 {\r
591         return SpriteBank;\r
592 }\r
593 \r
594 } // end namespace gui\r
595 } // end namespace irr\r