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
5 #include "CGUIFont.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
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
25 setDebugName("CGUIFont");
\r
30 // don't grab environment, to avoid circular references
\r
31 Driver = Environment->getVideoDriver();
\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
43 setInvisibleCharacters ( L" " );
\r
48 CGUIFont::~CGUIFont()
\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
65 //! loads a font file from xml
\r
66 bool CGUIFont::load(io::IXMLReader* xml, const io::path& directory)
\r
71 SpriteBank->clear();
\r
75 if (io::EXN_ELEMENT == xml->getNodeType())
\r
77 if (core::stringw(L"Texture") == xml->getNodeName())
\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
84 while (i+1 > SpriteBank->getTextureCount())
\r
85 SpriteBank->addTexture(0);
\r
88 pushTextureCreationFlags(flags);
\r
91 io::path textureFullName = core::mergeFilename(directory, fn);
\r
92 SpriteBank->setTexture(i, Driver->getTexture(textureFullName));
\r
94 popTextureCreationFlags(flags);
\r
96 // couldn't load texture, abort.
\r
97 if (!SpriteBank->getTexture(i))
\r
99 os::Printer::log("Unable to load all textures in the font, aborting", ELL_ERROR);
\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
109 else if (core::stringw(L"c") == xml->getNodeName())
\r
111 // adding a character to this font
\r
115 core::rect<s32> rectangle;
\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
123 core::stringc rectstr = xml->getAttributeValue(L"r");
\r
124 wchar_t ch = xml->getAttributeValue(L"c")[0];
\r
126 const c8 *c = rectstr.c_str();
\r
129 while (*c >= '0' && *c <= '9')
\r
135 rectangle.UpperLeftCorner.X = val;
\r
136 while (*c == L' ' || *c == L',') c++;
\r
139 while (*c >= '0' && *c <= '9')
\r
145 rectangle.UpperLeftCorner.Y = val;
\r
146 while (*c == L' ' || *c == L',') c++;
\r
149 while (*c >= '0' && *c <= '9')
\r
155 rectangle.LowerRightCorner.X = val;
\r
156 while (*c == L' ' || *c == L',') c++;
\r
159 while (*c >= '0' && *c <= '9')
\r
165 rectangle.LowerRightCorner.Y = val;
\r
167 CharacterMap.emplace(ch, Areas.size());
\r
170 f.rectNumber = SpriteBank->getPositions().size();
\r
171 f.textureNumber = texno;
\r
173 // add frame to sprite
\r
174 s.Frames.push_back(f);
\r
177 // add rectangle to sprite bank
\r
178 SpriteBank->getPositions().push_back(rectangle);
\r
179 a.width = rectangle.getWidth();
\r
181 // add sprite to sprite bank
\r
182 SpriteBank->getSprites().push_back(s);
\r
184 // add character to font
\r
185 Areas.push_back(a);
\r
190 // set bad character
\r
191 WrongCharacter = getAreaFromCharacter(L' ');
\r
200 void CGUIFont::setMaxHeight()
\r
207 core::array< core::rect<s32> >& p = SpriteBank->getPositions();
\r
209 for (u32 i=0; i<p.size(); ++i)
\r
211 const s32 t = p[i].getHeight();
\r
217 void CGUIFont::pushTextureCreationFlags(bool(&flags)[3])
\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
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
228 void CGUIFont::popTextureCreationFlags(const bool(&flags)[3])
\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
235 //! loads a font file, native file needed, for texture parsing
\r
236 bool CGUIFont::load(io::IReadFile* file)
\r
241 return loadTexture(Driver->createImageFromFile(file),
\r
242 file->getFileName());
\r
246 //! loads a font file, native file needed, for texture parsing
\r
247 bool CGUIFont::load(const io::path& filename)
\r
252 return loadTexture(Driver->createImageFromFile( filename ),
\r
257 //! load & prepare font from ITexture
\r
258 bool CGUIFont::loadTexture(video::IImage* image, const io::path& name)
\r
260 if (!image || !SpriteBank)
\r
263 s32 lowerRightPositions = 0;
\r
265 video::IImage* tmpImage=image;
\r
266 bool deleteTmpImage=false;
\r
267 switch(image->getColorFormat())
\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
274 case video::ECF_A1R5G5B5:
\r
275 case video::ECF_A8R8G8B8:
\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
283 os::Printer::log("Unknown texture format provided for CGUIFont::loadTexture", ELL_ERROR);
\r
286 readPositions(tmpImage, lowerRightPositions);
\r
288 WrongCharacter = getAreaFromCharacter(L' ');
\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
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
297 bool ret = ( !SpriteBank->getSprites().empty() && lowerRightPositions );
\r
302 pushTextureCreationFlags(flags);
\r
304 SpriteBank->addTexture(Driver->addTexture(name, tmpImage));
\r
306 popTextureCreationFlags(flags);
\r
308 if (deleteTmpImage)
\r
318 void CGUIFont::readPositions(video::IImage* image, s32& lowerRightPositions)
\r
323 const core::dimension2d<u32> size = image->getDimension();
\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
332 image->setPixel(1,0,colorBackGround);
\r
336 core::position2d<s32> pos(0,0);
\r
337 for (pos.Y=0; pos.Y<(s32)size.Height; ++pos.Y)
\r
339 for (pos.X=0; pos.X<(s32)size.Width; ++pos.X)
\r
341 const video::SColor c = image->getPixel(pos.X, pos.Y);
\r
342 if (c == colorTopLeft)
\r
344 image->setPixel(pos.X, pos.Y, colorBackGroundTransparent);
\r
345 SpriteBank->getPositions().push_back(core::rect<s32>(pos, pos));
\r
348 if (c == colorLowerRight)
\r
350 // too many lower right points
\r
351 if (SpriteBank->getPositions().size()<=(u32)lowerRightPositions)
\r
353 lowerRightPositions = 0;
\r
357 image->setPixel(pos.X, pos.Y, colorBackGroundTransparent);
\r
358 SpriteBank->getPositions()[lowerRightPositions].LowerRightCorner = pos;
\r
359 // add frame to sprite bank
\r
361 f.rectNumber = lowerRightPositions;
\r
362 f.textureNumber = 0;
\r
364 s.Frames.push_back(f);
\r
366 SpriteBank->getSprites().push_back(s);
\r
367 // add character to font
\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
378 ++lowerRightPositions;
\r
381 if (c == colorBackGround)
\r
382 image->setPixel(pos.X, pos.Y, colorBackGroundTransparent);
\r
388 //! set an Pixel Offset on Drawing ( scale position on width )
\r
389 void CGUIFont::setKerningWidth(s32 kerning)
\r
391 GlobalKerningWidth = kerning;
\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
398 s32 ret = GlobalKerningWidth;
\r
402 ret += Areas[getAreaFromCharacter(*thisLetter)].overhang;
\r
404 if (previousLetter)
\r
406 ret += Areas[getAreaFromCharacter(*previousLetter)].underhang;
\r
414 //! set an Pixel Offset on Drawing ( scale position on height )
\r
415 void CGUIFont::setKerningHeight(s32 kerning)
\r
417 GlobalKerningHeight = kerning;
\r
421 //! set an Pixel Offset on Drawing ( scale position on height )
\r
422 s32 CGUIFont::getKerningHeight () const
\r
424 return GlobalKerningHeight;
\r
428 //! returns the sprite number from a given character
\r
429 u32 CGUIFont::getSpriteNoFromChar(const wchar_t *c) const
\r
431 return Areas[getAreaFromCharacter(*c)].spriteno;
\r
435 s32 CGUIFont::getAreaFromCharacter(const wchar_t c) const
\r
437 auto n = CharacterMap.find(c);
\r
438 if (n != CharacterMap.end())
\r
441 return WrongCharacter;
\r
444 void CGUIFont::setInvisibleCharacters( const wchar_t *s )
\r
450 //! returns the dimension of text
\r
451 core::dimension2d<u32> CGUIFont::getDimension(const wchar_t* text) const
\r
453 core::dimension2d<u32> dim(0, 0);
\r
454 core::dimension2d<u32> thisLine(0, MaxHeight);
\r
456 for (const wchar_t* p = text; *p; ++p)
\r
458 bool lineBreak=false;
\r
459 if (*p == L'\r') // Mac or Windows breaks
\r
462 if (p[1] == L'\n') // Windows breaks
\r
465 else if (*p == L'\n') // Unix breaks
\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
478 const SFontArea &area = Areas[getAreaFromCharacter(*p)];
\r
480 thisLine.Width += area.underhang;
\r
481 thisLine.Width += area.width + area.overhang + GlobalKerningWidth;
\r
484 dim.Height += thisLine.Height;
\r
485 if (dim.Width < thisLine.Width)
\r
486 dim.Width = thisLine.Width;
\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
497 if (!Driver || !SpriteBank)
\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
503 if (hcenter || vcenter || clip)
\r
504 textDimension = getDimension(text.c_str());
\r
507 offset.X += (position.getWidth() - textDimension.Width) >> 1;
\r
510 offset.Y += (position.getHeight() - textDimension.Height) >> 1;
\r
514 core::rect<s32> clippedRect(offset, textDimension);
\r
515 clippedRect.clipAgainst(*clip);
\r
516 if (!clippedRect.isValid())
\r
520 core::array<u32> indices(text.size());
\r
521 core::array<core::position2di> offsets(text.size());
\r
523 for(u32 i = 0;i < text.size();i++)
\r
525 wchar_t c = text[i];
\r
527 bool lineBreak=false;
\r
528 if ( c == L'\r') // Mac or Windows breaks
\r
531 if ( text[i + 1] == L'\n') // Windows breaks
\r
534 else if ( c == L'\n') // Unix breaks
\r
541 offset.Y += MaxHeight;
\r
542 offset.X = position.UpperLeftCorner.X;
\r
546 offset.X += (position.getWidth() - textDimension.Width) >> 1;
\r
551 SFontArea& area = Areas[getAreaFromCharacter(c)];
\r
553 offset.X += area.underhang;
\r
554 if ( Invisible.findFirst ( c ) < 0 )
\r
556 indices.push_back(area.spriteno);
\r
557 offsets.push_back(offset);
\r
560 offset.X += area.width + area.overhang + GlobalKerningWidth;
\r
563 SpriteBank->draw2DSpriteBatch(indices, offsets, clip, color);
\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
575 const SFontArea& a = Areas[getAreaFromCharacter(text[idx])];
\r
577 x += a.width + a.overhang + a.underhang + GlobalKerningWidth;
\r
589 IGUISpriteBank* CGUIFont::getSpriteBank() const
\r
594 } // end namespace gui
\r
595 } // end namespace irr
\r