1 /////////////////////////////////////////
2 //
3 //   OpenLieroX
4 //
5 //   Auxiliary Software class library
6 //
7 //   based on the work of JasonB
8 //   enhanced by Dark Charlie and Albert Zeyer
9 //
10 //   code under LGPL
11 //
12 /////////////////////////////////////////
13 
14 
15 // Font class
16 // Created 15/7/01
17 // Jason Boettcher
18 
19 
20 #include "LieroX.h"
21 
22 #include "GfxPrimitives.h"
23 #include "PixelFunctors.h"
24 #include "Unicode.h"
25 #include "MathLib.h"
26 
27 
28 //
29 // IMPORTANT: all surfaces in CFont are alpha blended which means they can (and mostly do) have another format than screen.
30 // Because of this we cannot use almost any of the color handling functions from GfxPrimitives
31 // Format of the surfaces is guaranted to be 32bit ARGB
32 //
33 
34 
35 // For font drawing use FontGenerator tool in /tools/fontgenerator
36 
37 ///////////////////
38 // Load a font
Load(const std::string & fontname,bool _colour)39 int CFont::Load(const std::string& fontname, bool _colour) {
40 
41 	// Load the font
42 	LOAD_IMAGE_WITHALPHA(bmpFont, fontname);
43 
44 	// Set the color key for this alpha surface
45 	SetColorKey(bmpFont.get(), 255, 0, 255);
46 
47 	Colorize = _colour;
48 
49 	bmpWhite = gfxCreateSurfaceAlpha(bmpFont.get()->w, bmpFont.get()->h);
50 	bmpGreen = gfxCreateSurfaceAlpha(bmpFont.get()->w, bmpFont.get()->h);
51 
52 	// Calculate the width of each character and number of characters
53 	Parse();
54 
55 	// Pre-calculate some colours
56 	f_white = tLX->clNormalLabel;
57 	f_green = tLX->clChatText;
58 
59 	// Precache some common font colors (but only if this font should be colorized)
60 	if (Colorize) {
61 		PreCalculate(bmpWhite, f_white);
62 		PreCalculate(bmpGreen, f_green);
63 	}
64 
65 	return true;
66 }
67 
68 
69 ///////////////////
70 // Shutdown the font
Shutdown()71 void CFont::Shutdown() {
72 }
73 
74 
75 //////////////////
76 // Helper function for CalculateWidth
77 // Checks, whether a vertical line is free
78 // NOTE: bmpFont must be locked before calling this
IsColumnFree(int x)79 bool CFont::IsColumnFree(int x) {
80 	// it's only completelly see through
81 	for (int y = 0; y < bmpFont.get()->h; y++) {
82 		if ((GetPixel(bmpFont.get(), x, y) & ALPHASURFACE_AMASK) != 0)
83 			return false;
84 	}
85 
86 	return true;
87 }
88 
89 ///////////////////
90 // Calculate character widths, number of characters and offsets
Parse()91 void CFont::Parse() {
92 	int x;
93 	UnicodeChar CurChar = FIRST_CHARACTER;
94 	int cur_w;
95 
96 	// Lock the surface
97 	LOCK_OR_QUIT(bmpFont);
98 
99 	Uint32 blue = SDL_MapRGB(bmpFont.get()->format, 0, 0, 255);
100 
101 	// a blue pixel always indicates a new char exept for the first
102 
103 	uint char_start = 0;
104 	for (x = 0; x < bmpFont.get()->w; x++) {
105 		// x is always one pixel behind a blue line or x==0
106 
107 		// Ignore any free pixel columns before the character
108 		char_start = x;
109 		while (x < bmpFont.get()->w && IsColumnFree(x))
110 			++x;
111 		if(x >= bmpFont.get()->w) break;
112 
113 		// If we stopped at next blue line/end, this must be a kind of space
114 		if (GetPixel(bmpFont.get(), x, 0) == blue || x == bmpFont.get()->w)  {
115 			cur_w = x - char_start;
116 
117 		// Non-blank character
118 		} else {
119 			char_start = x;
120 			++x;
121 
122 			// Read until a blue pixel or end of the image
123 			cur_w = 1;
124 			while (GetPixel(bmpFont.get(), x, 0) != blue && x < bmpFont.get()->w) {
125 				++x;
126 				++cur_w;
127 			}
128 
129 			// Ignore any free pixel columns *after* the character
130 			uint tmp_x = x - 1; // last line before blue line or end
131 			while (IsColumnFree(tmp_x) && cur_w) {
132 				--cur_w;
133 				--tmp_x;
134 			}
135 
136 			if(cur_w == 0)
137 				warnings << "CFont cur_w == 0" << endl;
138 		}
139 
140 		// Add the character
141 		FontWidth.push_back(cur_w);
142 		CharacterOffset.push_back(char_start);
143 		NumCharacters++;
144 		CurChar++;
145 	}
146 
147 
148 
149 	// Unlock the surface
150 	UnlockSurface(bmpFont);
151 }
152 
153 ///////////////////
154 // Precalculate a font's colour
PreCalculate(const SmartPointer<SDL_Surface> & bmpSurf,Color colour)155 void CFont::PreCalculate(const SmartPointer<SDL_Surface> & bmpSurf, Color colour) {
156 	Uint32 pixel;
157 	int x, y;
158 
159 	FillSurface(bmpSurf.get(), SDL_MapRGBA(bmpSurf.get()->format, 255, 0, 255, 0));
160 
161 	// Lock the surfaces
162 	LOCK_OR_QUIT(bmpSurf);
163 	LOCK_OR_QUIT(bmpFont);
164 
165 	Uint8 R, G, B, A;
166 	const Uint8 sr = colour.r, sg = colour.g, sb = colour.b;
167 
168 	// Outline font: replace white pixels with appropriate color, put black pixels
169 	if (OutlineFont) {
170 		for (y = 0; y < bmpSurf.get()->h; y++) {
171 			for (x = 0; x < bmpSurf.get()->w; x++) {
172 				pixel = GetPixel(bmpFont.get(), x, y);
173 				GetColour4(pixel, bmpFont.get()->format, &R, &G, &B, &A);
174 
175 				if (R == 255 && G == 255 && B == 255)    // White
176 					PutPixel(bmpSurf.get(), x, y,
177 					         SDL_MapRGBA(bmpSurf.get()->format, sr, sg, sb, A));
178 				else if (!R && !G && !B)   // Black
179 					PutPixel(bmpSurf.get(), x, y,
180 					         SDL_MapRGBA(bmpSurf.get()->format, 0, 0, 0, A));
181 			}
182 		}
183 	// Not outline: replace black pixels with appropriate color
184 	} else {
185 		for (y = 0; y < bmpSurf.get()->h; y++) {
186 			for (x = 0; x < bmpSurf.get()->w; x++) {
187 				pixel = GetPixel(bmpFont.get(), x, y);
188 				GetColour4(pixel, bmpFont.get()->format, &R, &G, &B, &A);
189 
190 				if (!R && !G && !B)   // Black
191 					PutPixel(bmpSurf.get(), x, y,
192 					         SDL_MapRGBA(bmpSurf.get()->format, sr, sg, sb, A));
193 			}
194 		}
195 	}
196 
197 
198 	// Unlock the surfaces
199 	UnlockSurface(bmpSurf);
200 	UnlockSurface(bmpFont);
201 }
202 
203 
204 ////////////////////
205 // Get height of multiline text
GetHeight(const std::string & buf)206 int CFont::GetHeight(const std::string& buf) {
207 	int numlines = 1;
208 	for (std::string::const_iterator i = buf.begin(); i != buf.end(); i++)
209 		if (*i == '\n') numlines++;
210 	return numlines * (bmpFont.get()->h + VSpacing);
211 }
212 
213 ///////////////////
214 // Draws a font at X, Y, but visible only in specified rect
215 // HINT: not thread-safe
DrawInRect(SDL_Surface * dst,int x,int y,int rectX,int rectY,int rectW,int rectH,Color col,const std::string & txt)216 void CFont::DrawInRect(SDL_Surface * dst, int x, int y, int rectX, int rectY, int rectW, int rectH, Color col, const std::string &txt)  {
217 	// Set the special clipping rectangle and then draw the font
218 	SDL_Rect newrect;
219 	newrect.x = rectX;
220 	newrect.y = rectY;
221 	newrect.w = rectW;
222 	newrect.h = rectH;
223 	ScopedSurfaceClip clip(dst, newrect);
224 
225 	// Blit the font
226 	DrawAdv(dst, x, y, 9999, col, txt);
227 }
228 
229 ///////////////////
230 // Draw a font (advanced)
DrawAdv(SDL_Surface * dst,int x,int y,int max_w,Color col,const std::string & txt)231 void CFont::DrawAdv(SDL_Surface * dst, int x, int y, int max_w, Color col, const std::string& txt) {
232 
233 	if (txt.size() == 0)
234 		return;
235 
236 	if(dst == NULL) {
237 		errors << "CFont::DrawAdv(" << txt << "): dst == NULL" << endl;
238 		return;
239 	}
240 
241 	if(dst->format == NULL) {
242 		errors << "CFont::DrawAdv(" << txt << "): dst->format == NULL" << endl;
243 		return;
244 	}
245 
246 	// Set the newrect width and use this newrect temporarily to draw the font
247 	// We use this rect because of precached fonts which use SDL_Blit for drawing (and it takes care of cliprect)
248 	SDL_Rect newrect = dst->clip_rect;
249 	newrect.w = MIN(newrect.w, (Uint16)max_w);
250 	newrect.x = MAX(newrect.x, (Sint16)x);
251 	newrect.y = MAX(newrect.y, (Sint16)y);
252 	if (!ClipRefRectWith(newrect.x, newrect.y, newrect.w, newrect.h, (SDLRect&)dst->clip_rect))
253 		return;
254 
255 	ScopedSurfaceClip clip(dst, newrect);
256 
257 	// Look in the precached fonts if there's some for this color
258 	SmartPointer<SDL_Surface> bmpCached = NULL;
259 	if (Colorize) {
260 		// HINT: if we leave this disabled, the drawing will always be done manually
261 		// this is a bit (not not much) slower but prevents from the usual errors with CFont (wrong color, invisible, so on)
262 		// TODO: should we completly remove the caches? how much speed improvements do they give?
263 		// under MacOSX, it doesn't seem to give any performance improvement at all, at least no change in FPS
264 		// I have activated it again now as it seem to work at the moment (perhabps because of my change in gfxCreateSurfaceAlpha)
265 		if (col == f_white)
266 			bmpCached = bmpWhite;
267 		else if (col == tLX->clBlack)
268 			bmpCached = bmpFont;
269 		else if (col == f_green)
270 			bmpCached = bmpGreen;
271 	}
272 	// Not colourize, bmpFont itself should be blitted without any changes, so it's precached
273 	else {
274 		bmpCached = bmpFont;
275 	}
276 
277 
278 	// Lock the surfaces
279 	// If we use cached font, we do not access the pixels
280 	if (!bmpCached.get())  {
281 		LOCK_OR_QUIT(dst);
282 		LOCK_OR_QUIT(bmpFont);
283 	}
284 
285 	// Get the color values
286 	// TODO: this function should accept a device-independent color, change it asap!
287 	Color font_cl(col);
288 
289 	// Position at destination surface
290 	int char_y = y;
291 	int char_h = bmpFont.get()->h;
292 
293 	// Get the putpixel & getpixel functors
294 	PixelPutAlpha& putter = getPixelAlphaPutFunc(dst);
295 	PixelGet& getter = getPixelGetFunc(bmpFont.get());
296 
297 	// Vertical clipping
298 	OneSideClip(char_y, char_h, newrect.y, newrect.h);
299 
300 	// Get the correct drawing function
301 	typedef void(CFont::*GlyphBlitter)(SDL_Surface *dst, const SDL_Rect& r, int sx, int sy, Color col, int glyph_index, PixelPutAlpha& putter, PixelGet& getter);
302 	GlyphBlitter func = &CFont::DrawGlyphNormal_Internal;
303 	if (OutlineFont)
304 		func = &CFont::DrawGlyphOutline_Internal;
305 
306 	for (std::string::const_iterator p = txt.begin(); p != txt.end();) {
307 
308 		// Line break
309 		if (*p == '\n') {
310 			y += bmpFont.get()->h + VSpacing;
311 			char_y = y;
312 			x = newrect.x;
313 			p++;
314 
315 			// If any further text wouldn't be drawn, just stop
316 			if (char_y >= (newrect.y + newrect.h))
317 				break;
318 			else  {
319 				OneSideClip(char_y, char_h, newrect.y, newrect.h);
320 				continue;
321 			}
322 		}
323 
324 		// Translate and ignore unknown
325 		int l = TranslateCharacter(p, txt.end()); // HINT: increases the iterator
326 		if (l == -1)
327 			continue;
328 
329 
330 		// Check if the current line will be drawn, if not, just proceed
331 		if (char_h == 0)
332 			continue;
333 
334 		// Horizontal clipping
335 		int char_x = x;
336 		int char_w = FontWidth[l];
337 		if (!OneSideClip(char_x, char_w, newrect.x, newrect.w))  {
338 			x += FontWidth[l] + Spacing;
339 			continue;
340 		}
341 
342 		// Precached fonts
343 		if (bmpCached.get()) {
344 			DrawImageAdv(dst, bmpCached, CharacterOffset[l], 0, x, y, FontWidth[l], bmpFont.get()->h);
345 			x += FontWidth[l] + Spacing;
346 			continue;
347 		}
348 
349 		// Draw the glyph
350 		(this->*(func))(dst, MakeRect(char_x, char_y, char_w, char_h), char_x - x, char_y - y, font_cl, l, putter, getter);
351 
352 		x += FontWidth[l] + Spacing;
353 	}
354 
355 
356 	// Unlock the surfaces
357 	if (!bmpCached.get())  {
358 		UnlockSurface(dst);
359 		UnlockSurface(bmpFont);
360 	}
361 }
362 
363 /////////////////////////
364 // Draws an outlined character
DrawGlyphOutline_Internal(SDL_Surface * dst,const SDL_Rect & r,int sx,int sy,Color col,int glyph_index,PixelPutAlpha & putter,PixelGet & getter)365 void CFont::DrawGlyphOutline_Internal(SDL_Surface *dst, const SDL_Rect& r, int sx, int sy, Color col, int glyph_index, PixelPutAlpha& putter, PixelGet& getter)
366 {
367 	const short bpp = bmpFont->format->BytesPerPixel;
368 	Uint8 *src = (Uint8 *) bmpFont.get()->pixels + bmpFont.get()->pitch * sy +
369 					(CharacterOffset[glyph_index] + sx) * bpp;
370 
371 	// Draw the glyph
372 	for (int j = 0; j < r.h; ++j) {
373 		Uint8 *px = src;
374 		for (int i = 0; i < r.w; ++i, px += bpp) {
375 			Uint8 R, G, B, A;
376 			GetColour4(getter.get(px), bmpFont->format, &R, &G, &B, &A);
377 
378 			// Put black pixels and colorize white ones
379 			if (R == 255 && G == 255 && B == 255)  {    // White
380 				Color current_cl = col;
381 				current_cl.a = (current_cl.a * A) / 255; // Add the alpha from the font
382 				putter.put(GetPixelAddr(dst, r.x + i, r.y + j), dst->format, current_cl);
383 			} else if (!R && !G && !B)    // Black
384 				putter.put(GetPixelAddr(dst, r.x + i, r.y + j), dst->format, Color(0, 0, 0, (A * col.a) / 255));
385 		}
386 		src += bmpFont.get()->pitch;
387 	}
388 }
389 
390 //////////////////////////
391 // Draws a character
DrawGlyphNormal_Internal(SDL_Surface * dst,const SDL_Rect & r,int sx,int sy,Color col,int glyph_index,PixelPutAlpha & putter,PixelGet & getter)392 void CFont::DrawGlyphNormal_Internal(SDL_Surface *dst, const SDL_Rect& r, int sx, int sy, Color col, int glyph_index, PixelPutAlpha& putter, PixelGet& getter)
393 {
394 	if(glyph_index < 0 || (size_t)glyph_index >= CharacterOffset.size()) {
395 		errors << "CFont::DrawGlyphNormal_Internal: glyph_index " << glyph_index << " is invalid" << endl;
396 		return;
397 	}
398 
399 	const short bpp = bmpFont->format->BytesPerPixel;
400 	Uint8 *src = (Uint8 *) bmpFont.get()->pixels + bmpFont.get()->pitch * sy +
401 					(CharacterOffset[glyph_index] + sx) * bpp;
402 
403 	// Draw the glyph
404 	for (int j = 0; j < r.h; ++j) {
405 		Uint8 *px = src;
406 		for (int i = 0; i < r.w; ++i, px += bpp) {
407 			Uint8 R, G, B, A;
408 			GetColour4(getter.get(px), bmpFont.get()->format, &R, &G, &B, &A);
409 
410 			// Put only black pixels
411 			if (!R && !G && !B)  {
412 				Color current_cl = col;
413 				current_cl.a = (current_cl.a * A) / 255; // Add the alpha from the font
414 				putter.put(GetPixelAddr(dst, r.x + i, r.y + j), dst->format, current_cl);
415 			}
416 		}
417 		src += bmpFont.get()->pitch;
418 	}
419 }
420 
421 
422 /////////////////////
423 // Draws one character to the dest surface
DrawGlyph(SDL_Surface * dst,int x,int y,Color col,UnicodeChar c)424 void CFont::DrawGlyph(SDL_Surface *dst, int x, int y, Color col, UnicodeChar c)
425 {
426 	// Don't draw spaces
427 	if (c == ' ' || c == '\r' || c == '\n' || c == '\t')
428 		return;
429 
430 	int index = TranslateCharacter(c);
431 	if (index == -1) // Unknown character
432 		return;
433 
434 	// Clipping
435 	SDL_Rect r = { (SDLRect::Type) x, (SDLRect::Type) y, (SDLRect::TypeS) FontWidth[index], (SDLRect::TypeS) bmpFont->h };
436 	if (!ClipRefRectWith((SDLRect &)r, (SDLRect &)dst->clip_rect))
437 		return;
438 
439 	// Get the putpixel & getpixel functors
440 	PixelPutAlpha& putter = getPixelAlphaPutFunc(dst);
441 	PixelGet& getter = getPixelGetFunc(bmpFont.get());
442 
443 	// Draw the glyph
444 	if (OutlineFont)
445 		DrawGlyphOutline_Internal(dst, r, x - r.x, y - r.y, col, index, putter, getter);
446 	else
447 		DrawGlyphNormal_Internal(dst, r, x - r.x, y - r.y, col, index, putter, getter);
448 }
449 
450 
451 ///////////////////
452 // Calculate the width of a string of text
GetWidth(const std::string & buf)453 int CFont::GetWidth(const std::string& buf) {
454 	int length = 0, maxlength = 0;
455 	short l;
456 
457 	// Calculate the length of the text
458 	for (std::string::const_iterator p = buf.begin(); p != buf.end();) {
459 		if( *p == '\n' ) {
460 			length = 0;
461 			p++;
462 			continue;
463 		}
464 		l = TranslateCharacter(p, buf.end());
465 		if (l != -1)
466 			length += FontWidth[l] + Spacing;
467 		if( length > maxlength )
468 			maxlength = length;
469 	}
470 
471 	return maxlength;
472 }
473 
474 /////////////////
475 // Get width of a single character
GetCharacterWidth(UnicodeChar c)476 int CFont::GetCharacterWidth(UnicodeChar c)
477 {
478 	int l = TranslateCharacter(c);
479 	if (l != -1)
480 		return FontWidth[l];
481 	else
482 		return 0;
483 }
484 
485 ///////////////////
486 // Draws the text in centre alignment
DrawCentre(SDL_Surface * dst,int x,int y,Color col,const std::string & txt)487 void CFont::DrawCentre(SDL_Surface * dst, int x, int y, Color col, const std::string& txt) {
488 	Draw(dst, x - GetWidth(txt) / 2, y, col, txt);
489 }
490 
491 ///////////////////
492 // Draw's the text in centre alignment
DrawCentreAdv(SDL_Surface * dst,int x,int y,int min_x,int max_w,Color col,const std::string & txt)493 void CFont::DrawCentreAdv(SDL_Surface * dst, int x, int y, int min_x, int max_w, Color col, const std::string& txt) {
494 	DrawAdv(dst, MAX(min_x, x - GetWidth(txt) / 2), y, max_w, col, txt);
495 }
496