658 lines
15 KiB
C++
658 lines
15 KiB
C++
// Copyright (C) 2002-2009 Nikolaus Gebhardt
|
|
// This file is part of the "Irrlicht Engine".
|
|
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
|
|
|
#include "CGUIFont.h"
|
|
#ifdef _IRR_COMPILE_WITH_GUI_
|
|
|
|
#include "os.h"
|
|
#include "IGUIEnvironment.h"
|
|
#include "IXMLReader.h"
|
|
#include "IReadFile.h"
|
|
#include "IVideoDriver.h"
|
|
#include "IGUISpriteBank.h"
|
|
#include "CImage.h"
|
|
|
|
namespace irr
|
|
{
|
|
namespace gui
|
|
{
|
|
|
|
//! constructor
|
|
CGUIFont::CGUIFont(IGUIEnvironment *env, const core::string<c16>& filename)
|
|
: Driver(0), SpriteBank(0), Environment(env), WrongCharacter(0),
|
|
MaxHeight(0), GlobalKerningWidth(0), GlobalKerningHeight(0)
|
|
{
|
|
#ifdef _DEBUG
|
|
setDebugName("CGUIFont");
|
|
#endif
|
|
|
|
if (Environment)
|
|
{
|
|
// don't grab environment, to avoid circular references
|
|
Driver = Environment->getVideoDriver();
|
|
|
|
SpriteBank = Environment->addEmptySpriteBank(filename);
|
|
if (SpriteBank)
|
|
SpriteBank->grab();
|
|
}
|
|
|
|
if (Driver)
|
|
Driver->grab();
|
|
|
|
setInvisibleCharacters ( L" " );
|
|
}
|
|
|
|
|
|
//! destructor
|
|
CGUIFont::~CGUIFont()
|
|
{
|
|
if (Driver)
|
|
Driver->drop();
|
|
|
|
if (SpriteBank)
|
|
SpriteBank->drop();
|
|
}
|
|
|
|
|
|
//! loads a font file from xml
|
|
bool CGUIFont::load(io::IXMLReader* xml)
|
|
{
|
|
if (!SpriteBank)
|
|
return false;
|
|
|
|
while (xml->read())
|
|
{
|
|
if (io::EXN_ELEMENT == xml->getNodeType())
|
|
{
|
|
if (core::stringw(L"Texture") == xml->getNodeName())
|
|
{
|
|
// add a texture
|
|
core::stringc fn = xml->getAttributeValue(L"filename");
|
|
u32 i = (u32)xml->getAttributeValueAsInt(L"index");
|
|
core::stringw alpha = xml->getAttributeValue(L"hasAlpha");
|
|
|
|
while (i+1 > SpriteBank->getTextureCount())
|
|
SpriteBank->addTexture(0);
|
|
|
|
// disable mipmaps+filtering
|
|
bool mipmap = Driver->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS);
|
|
Driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);
|
|
|
|
// load texture
|
|
SpriteBank->setTexture(i, Driver->getTexture(fn));
|
|
|
|
// set previous mip-map+filter state
|
|
Driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, mipmap);
|
|
|
|
// couldn't load texture, abort.
|
|
if (!SpriteBank->getTexture(i))
|
|
{
|
|
os::Printer::log("Unable to load all textures in the font, aborting", ELL_ERROR);
|
|
_IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// colorkey texture rather than alpha channel?
|
|
if (alpha == core::stringw("false"))
|
|
Driver->makeColorKeyTexture(SpriteBank->getTexture(i), core::position2di(0,0));
|
|
}
|
|
}
|
|
else if (core::stringw(L"c") == xml->getNodeName())
|
|
{
|
|
// adding a character to this font
|
|
SFontArea a;
|
|
SGUISpriteFrame f;
|
|
SGUISprite s;
|
|
core::rect<s32> rectangle;
|
|
|
|
a.underhang = xml->getAttributeValueAsInt(L"u");
|
|
a.overhang = xml->getAttributeValueAsInt(L"o");
|
|
a.spriteno = SpriteBank->getSprites().size();
|
|
s32 texno = xml->getAttributeValueAsInt(L"i");
|
|
|
|
// parse rectangle
|
|
core::stringc rectstr = xml->getAttributeValue(L"r");
|
|
wchar_t ch = xml->getAttributeValue(L"c")[0];
|
|
|
|
const c8 *c = rectstr.c_str();
|
|
s32 val;
|
|
val = 0;
|
|
while (*c >= '0' && *c <= '9')
|
|
{
|
|
val *= 10;
|
|
val += *c - '0';
|
|
c++;
|
|
}
|
|
rectangle.UpperLeftCorner.X = val;
|
|
while (*c == L' ' || *c == L',') c++;
|
|
|
|
val = 0;
|
|
while (*c >= '0' && *c <= '9')
|
|
{
|
|
val *= 10;
|
|
val += *c - '0';
|
|
c++;
|
|
}
|
|
rectangle.UpperLeftCorner.Y = val;
|
|
while (*c == L' ' || *c == L',') c++;
|
|
|
|
val = 0;
|
|
while (*c >= '0' && *c <= '9')
|
|
{
|
|
val *= 10;
|
|
val += *c - '0';
|
|
c++;
|
|
}
|
|
rectangle.LowerRightCorner.X = val;
|
|
while (*c == L' ' || *c == L',') c++;
|
|
|
|
val = 0;
|
|
while (*c >= '0' && *c <= '9')
|
|
{
|
|
val *= 10;
|
|
val += *c - '0';
|
|
c++;
|
|
}
|
|
rectangle.LowerRightCorner.Y = val;
|
|
|
|
CharacterMap.insert(ch,Areas.size());
|
|
|
|
// make frame
|
|
f.rectNumber = SpriteBank->getPositions().size();
|
|
f.textureNumber = texno;
|
|
|
|
// add frame to sprite
|
|
s.Frames.push_back(f);
|
|
s.frameTime = 0;
|
|
|
|
// add rectangle to sprite bank
|
|
SpriteBank->getPositions().push_back(rectangle);
|
|
a.width = rectangle.getWidth();
|
|
|
|
// add sprite to sprite bank
|
|
SpriteBank->getSprites().push_back(s);
|
|
|
|
// add character to font
|
|
Areas.push_back(a);
|
|
}
|
|
}
|
|
}
|
|
|
|
// set bad character
|
|
WrongCharacter = getAreaFromCharacter(L' ');
|
|
|
|
setMaxHeight();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void CGUIFont::setMaxHeight()
|
|
{
|
|
MaxHeight = 0;
|
|
s32 t;
|
|
|
|
core::array< core::rect<s32> >& p = SpriteBank->getPositions();
|
|
|
|
for (u32 i=0; i<p.size(); ++i)
|
|
{
|
|
t = p[i].getHeight();
|
|
if (t>MaxHeight)
|
|
MaxHeight = t;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//! loads a font file, native file needed, for texture parsing
|
|
bool CGUIFont::load(io::IReadFile* file)
|
|
{
|
|
if (!Driver)
|
|
return false;
|
|
|
|
return loadTexture(Driver->createImageFromFile(file),
|
|
file->getFileName());
|
|
}
|
|
|
|
|
|
//! loads a font file, native file needed, for texture parsing
|
|
bool CGUIFont::load(const core::string<c16>& filename)
|
|
{
|
|
if (!Driver)
|
|
return false;
|
|
return loadTexture(Driver->createImageFromFile( filename ),
|
|
filename);
|
|
}
|
|
|
|
|
|
//! load & prepare font from ITexture
|
|
bool CGUIFont::loadTexture(video::IImage* image, const core::string<c16>& name)
|
|
{
|
|
if (!image)
|
|
return false;
|
|
|
|
s32 lowerRightPositions = 0;
|
|
|
|
video::IImage* tmpImage=image;
|
|
bool deleteTmpImage=false;
|
|
switch(image->getColorFormat())
|
|
{
|
|
case video::ECF_R5G6B5:
|
|
tmpImage = new video::CImage(video::ECF_A1R5G5B5,image);
|
|
deleteTmpImage=true;
|
|
case video::ECF_A1R5G5B5:
|
|
readPositions16bit(tmpImage, lowerRightPositions);
|
|
break;
|
|
case video::ECF_R8G8B8:
|
|
tmpImage = new video::CImage(video::ECF_A8R8G8B8,image);
|
|
deleteTmpImage=true;
|
|
case video::ECF_A8R8G8B8:
|
|
readPositions32bit (tmpImage, lowerRightPositions);
|
|
break;
|
|
}
|
|
|
|
WrongCharacter = getAreaFromCharacter(L' ');
|
|
|
|
// output warnings
|
|
if (!lowerRightPositions || !SpriteBank->getSprites().size())
|
|
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);
|
|
else
|
|
if (lowerRightPositions != (s32)SpriteBank->getPositions().size())
|
|
os::Printer::log("The amount of upper corner pixels and the lower corner pixels is not equal, font file may be corrupted.", ELL_ERROR);
|
|
|
|
bool ret = ( !SpriteBank->getSprites().empty() && lowerRightPositions );
|
|
|
|
|
|
if ( ret )
|
|
{
|
|
bool flag[2];
|
|
flag[0] = Driver->getTextureCreationFlag ( video::ETCF_ALLOW_NON_POWER_2 );
|
|
flag[1] = Driver->getTextureCreationFlag ( video::ETCF_CREATE_MIP_MAPS );
|
|
|
|
Driver->setTextureCreationFlag(video::ETCF_ALLOW_NON_POWER_2, true);
|
|
Driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false );
|
|
|
|
SpriteBank->addTexture(Driver->addTexture(name, tmpImage));
|
|
|
|
Driver->setTextureCreationFlag(video::ETCF_ALLOW_NON_POWER_2, flag[0] );
|
|
Driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, flag[1] );
|
|
}
|
|
if (deleteTmpImage)
|
|
tmpImage->drop();
|
|
image->drop();
|
|
|
|
setMaxHeight();
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
void CGUIFont::readPositions32bit(video::IImage* image, s32& lowerRightPositions)
|
|
{
|
|
const core::dimension2d<u32>& size = image->getDimension();
|
|
|
|
s32* p = (s32*)image->lock();
|
|
if (!p)
|
|
{
|
|
os::Printer::log("Could not lock texture while preparing texture for a font.", ELL_ERROR);
|
|
return;
|
|
}
|
|
|
|
// fix half alpha of top left pixel in some font textures
|
|
p[0] |= 0xFF000000;
|
|
|
|
s32 colorTopLeft = p[0];
|
|
s32 colorLowerRight = *(p+1);
|
|
s32 colorBackGround = *(p+2);
|
|
s32 colorBackGroundTransparent = 0; // 0x00FFFFFF & colorBackGround;
|
|
|
|
*(p+1) = colorBackGround;
|
|
|
|
// start parsing
|
|
|
|
core::position2d<s32> pos(0,0);
|
|
for (pos.Y=0; pos.Y<(s32)size.Height; ++pos.Y)
|
|
{
|
|
for (pos.X=0; pos.X<(s32)size.Width; ++pos.X)
|
|
{
|
|
if ( *p == colorTopLeft)
|
|
{
|
|
*p = colorBackGroundTransparent;
|
|
SpriteBank->getPositions().push_back(core::rect<s32>(pos, pos));
|
|
}
|
|
else
|
|
if (*p == colorLowerRight)
|
|
{
|
|
if (SpriteBank->getPositions().size()<=(u32)lowerRightPositions)
|
|
{
|
|
image->unlock();
|
|
lowerRightPositions = 0;
|
|
return;
|
|
}
|
|
|
|
*p = colorBackGroundTransparent;
|
|
SpriteBank->getPositions()[lowerRightPositions].LowerRightCorner = pos;
|
|
// add frame to sprite bank
|
|
SGUISpriteFrame f;
|
|
f.rectNumber = lowerRightPositions;
|
|
f.textureNumber = 0;
|
|
SGUISprite s;
|
|
s.Frames.push_back(f);
|
|
s.frameTime = 0;
|
|
SpriteBank->getSprites().push_back(s);
|
|
// add character to font
|
|
SFontArea a;
|
|
a.overhang = 0;
|
|
a.underhang = 0;
|
|
a.spriteno = lowerRightPositions;
|
|
a.width = SpriteBank->getPositions()[lowerRightPositions].getWidth();
|
|
Areas.push_back(a);
|
|
// map letter to character
|
|
wchar_t ch = (wchar_t)(lowerRightPositions + 32);
|
|
CharacterMap.set(ch, lowerRightPositions);
|
|
|
|
++lowerRightPositions;
|
|
}
|
|
else
|
|
if (*p == colorBackGround)
|
|
{
|
|
*p = colorBackGroundTransparent;
|
|
}
|
|
++p;
|
|
}
|
|
}
|
|
|
|
// Positions parsed.
|
|
|
|
image->unlock();
|
|
}
|
|
|
|
|
|
void CGUIFont::readPositions16bit(video::IImage* image, s32& lowerRightPositions)
|
|
{
|
|
core::dimension2d<u32> size = image->getDimension();
|
|
|
|
s16* p = (s16*)image->lock();
|
|
if (!p)
|
|
{
|
|
os::Printer::log("Could not lock texture while preparing texture for a font.", ELL_ERROR);
|
|
return;
|
|
}
|
|
|
|
// fix half alpha of top left pixel in some font textures
|
|
p[0] |= 0x8000;
|
|
|
|
s16 colorTopLeft = p[0];
|
|
s16 colorLowerRight = *(p+1);
|
|
s16 colorBackGround = *(p+2);
|
|
s16 colorBackGroundTransparent = 0; // 0x7FFF & colorBackGround;
|
|
|
|
*(p+1) = colorBackGround;
|
|
|
|
// start parsing
|
|
|
|
core::position2d<s32> pos(0,0);
|
|
for (pos.Y=0; pos.Y<(s32)size.Height; ++pos.Y)
|
|
{
|
|
for (pos.X=0; pos.X<(s32)size.Width; ++pos.X)
|
|
{
|
|
if (*p == colorTopLeft)
|
|
{
|
|
*p = colorBackGroundTransparent;
|
|
SpriteBank->getPositions().push_back(core::rect<s32>(pos, pos));
|
|
}
|
|
else
|
|
if (*p == colorLowerRight)
|
|
{
|
|
// too many lower right points
|
|
if (SpriteBank->getPositions().size()<=(u32)lowerRightPositions)
|
|
{
|
|
image->unlock();
|
|
lowerRightPositions = 0;
|
|
return;
|
|
}
|
|
|
|
*p = colorBackGroundTransparent;
|
|
SpriteBank->getPositions()[lowerRightPositions].LowerRightCorner = pos;
|
|
// add frame to sprite bank
|
|
SGUISpriteFrame f;
|
|
f.rectNumber = lowerRightPositions;
|
|
f.textureNumber = 0;
|
|
SGUISprite s;
|
|
s.Frames.push_back(f);
|
|
s.frameTime = 0;
|
|
SpriteBank->getSprites().push_back(s);
|
|
// add character to font
|
|
SFontArea a;
|
|
a.overhang = 0;
|
|
a.underhang = 0;
|
|
a.spriteno = lowerRightPositions;
|
|
a.width = SpriteBank->getPositions()[lowerRightPositions].getWidth();
|
|
Areas.push_back(a);
|
|
// map letter to character
|
|
wchar_t ch = (wchar_t)(lowerRightPositions + 32);
|
|
CharacterMap.set(ch, lowerRightPositions);
|
|
|
|
++lowerRightPositions;
|
|
}
|
|
else
|
|
if (*p == colorBackGround)
|
|
*p = colorBackGroundTransparent;
|
|
++p;
|
|
}
|
|
}
|
|
|
|
// Positions parsed.
|
|
|
|
image->unlock();
|
|
}
|
|
|
|
|
|
|
|
|
|
//! set an Pixel Offset on Drawing ( scale position on width )
|
|
void CGUIFont::setKerningWidth(s32 kerning)
|
|
{
|
|
GlobalKerningWidth = kerning;
|
|
}
|
|
|
|
|
|
//! set an Pixel Offset on Drawing ( scale position on width )
|
|
s32 CGUIFont::getKerningWidth(const wchar_t* thisLetter, const wchar_t* previousLetter) const
|
|
{
|
|
s32 ret = GlobalKerningWidth;
|
|
|
|
if (thisLetter)
|
|
{
|
|
ret += Areas[getAreaFromCharacter(*thisLetter)].overhang;
|
|
|
|
if (previousLetter)
|
|
{
|
|
ret += Areas[getAreaFromCharacter(*previousLetter)].underhang;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
//! set an Pixel Offset on Drawing ( scale position on height )
|
|
void CGUIFont::setKerningHeight(s32 kerning)
|
|
{
|
|
GlobalKerningHeight = kerning;
|
|
}
|
|
|
|
|
|
//! set an Pixel Offset on Drawing ( scale position on height )
|
|
s32 CGUIFont::getKerningHeight () const
|
|
{
|
|
return GlobalKerningHeight;
|
|
}
|
|
|
|
|
|
//! returns the sprite number from a given character
|
|
u32 CGUIFont::getSpriteNoFromChar(const wchar_t *c) const
|
|
{
|
|
return Areas[getAreaFromCharacter(*c)].spriteno;
|
|
}
|
|
|
|
|
|
s32 CGUIFont::getAreaFromCharacter(const wchar_t c) const
|
|
{
|
|
core::map<wchar_t, s32>::Node* n = CharacterMap.find(c);
|
|
if (n)
|
|
return n->getValue();
|
|
else
|
|
return WrongCharacter;
|
|
}
|
|
|
|
void CGUIFont::setInvisibleCharacters( const wchar_t *s )
|
|
{
|
|
Invisible = s;
|
|
}
|
|
|
|
|
|
//! returns the dimension of text
|
|
core::dimension2d<u32> CGUIFont::getDimension(const wchar_t* text) const
|
|
{
|
|
core::dimension2d<u32> dim(0, 0);
|
|
core::dimension2d<u32> thisLine(0, MaxHeight);
|
|
|
|
for (const wchar_t* p = text; *p; ++p)
|
|
{
|
|
bool lineBreak=false;
|
|
if (*p == L'\r') // Mac or Windows breaks
|
|
{
|
|
lineBreak = true;
|
|
if (p[1] == L'\n') // Windows breaks
|
|
++p;
|
|
}
|
|
else if (*p == L'\n') // Unix breaks
|
|
{
|
|
lineBreak = true;
|
|
}
|
|
if (lineBreak)
|
|
{
|
|
dim.Height += thisLine.Height;
|
|
if (dim.Width < thisLine.Width)
|
|
dim.Width = thisLine.Width;
|
|
thisLine.Width = 0;
|
|
continue;
|
|
}
|
|
|
|
const SFontArea &area = Areas[getAreaFromCharacter(*p)];
|
|
|
|
thisLine.Width += area.underhang;
|
|
thisLine.Width += area.width + area.overhang + GlobalKerningWidth;
|
|
}
|
|
|
|
dim.Height += thisLine.Height;
|
|
if (dim.Width < thisLine.Width)
|
|
dim.Width = thisLine.Width;
|
|
|
|
return dim;
|
|
}
|
|
|
|
//! draws some text and clips it to the specified rectangle if wanted
|
|
void CGUIFont::draw(const wchar_t* text, const core::rect<s32>& position,
|
|
video::SColor color,
|
|
bool hcenter, bool vcenter, const core::rect<s32>* clip
|
|
)
|
|
{
|
|
if (!Driver)
|
|
return;
|
|
|
|
core::dimension2d<s32> textDimension;
|
|
core::position2d<s32> offset = position.UpperLeftCorner;
|
|
|
|
if (hcenter || vcenter || clip)
|
|
textDimension = getDimension(text);
|
|
|
|
if (hcenter)
|
|
offset.X += (position.getWidth() - textDimension.Width) >> 1;
|
|
|
|
if (vcenter)
|
|
offset.Y += (position.getHeight() - textDimension.Height) >> 1;
|
|
|
|
if (clip)
|
|
{
|
|
core::rect<s32> clippedRect(offset, textDimension);
|
|
clippedRect.clipAgainst(*clip);
|
|
if (!clippedRect.isValid())
|
|
return;
|
|
}
|
|
|
|
wchar_t c;
|
|
while (( c = *text++))
|
|
{
|
|
bool lineBreak=false;
|
|
if ( c == L'\r') // Mac or Windows breaks
|
|
{
|
|
lineBreak = true;
|
|
if ( *text == L'\n') // Windows breaks
|
|
++text;
|
|
}
|
|
else if ( c == L'\n') // Unix breaks
|
|
{
|
|
lineBreak = true;
|
|
}
|
|
|
|
if (lineBreak)
|
|
{
|
|
offset.Y += MaxHeight;
|
|
offset.X = position.UpperLeftCorner.X;
|
|
|
|
if ( hcenter )
|
|
{
|
|
core::dimension2d<u32> lineDim = getDimension(text);
|
|
offset.X += (position.getWidth() - lineDim.Width) >> 1;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
SFontArea& area = Areas[getAreaFromCharacter(c)];
|
|
|
|
offset.X += area.underhang;
|
|
if ( Invisible.findFirst ( c ) < 0 )
|
|
SpriteBank->draw2DSprite(area.spriteno, offset, clip, color);
|
|
offset.X += area.width + area.overhang + GlobalKerningWidth;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
//! Calculates the index of the character in the text which is on a specific position.
|
|
s32 CGUIFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x) const
|
|
{
|
|
s32 x = 0;
|
|
s32 idx = 0;
|
|
|
|
while (text[idx])
|
|
{
|
|
const SFontArea& a = Areas[getAreaFromCharacter(text[idx])];
|
|
|
|
x += a.width + a.overhang + a.underhang;
|
|
|
|
if (x >= pixel_x)
|
|
return idx;
|
|
|
|
++idx;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
IGUISpriteBank* CGUIFont::getSpriteBank() const
|
|
{
|
|
return SpriteBank;
|
|
}
|
|
|
|
} // end namespace gui
|
|
} // end namespace irr
|
|
|
|
#endif // _IRR_COMPILE_WITH_GUI_
|
|
|