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