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