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