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