move over rewritten lispreader from tuxkart (with additional fixes), generalized...
[supertux.git] / lib / video / font.cpp
1 //  $Id$
2 // 
3 //  SuperTux
4 //  Copyright (C) 2004 Tobias Glaesser <tobi.web@gmx.de>
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 // 
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19 //  02111-1307, USA.
20
21 #include <config.h>
22
23 #include <cstdlib>
24 #include <cstring>
25 #include <stdexcept>
26
27 #include "app/globals.h"
28 #include "lisp/parser.h"
29 #include "lisp/lisp.h"
30 #include "screen.h"
31 #include "font.h"
32 #include "drawing_context.h"
33
34 using namespace SuperTux;
35
36 Font::Font(const std::string& file, FontType ntype, int nw, int nh,
37         int nshadowsize)
38     : chars(0), shadow_chars(0), type(ntype), w(nw), h(nh),
39       shadowsize(nshadowsize)
40 {
41   chars = new Surface(file, true);
42  
43   switch(type) {
44     case TEXT:
45       first_char = 32;
46       break;
47     case NUM:
48       first_char = 48;
49       break;
50   }
51   last_char = first_char + (chars->h / h) * 16;
52   if(last_char > 127) // we have left out some control chars at 128-159
53     last_char += 32;
54    
55   // Load shadow font.
56   if(shadowsize > 0) {
57     SDL_Surface* conv = SDL_DisplayFormatAlpha(chars->impl->get_sdl_surface());
58     int pixels = conv->w * conv->h;
59     SDL_LockSurface(conv);
60     for(int i = 0; i < pixels; ++i) {
61       Uint32 *p = (Uint32 *)conv->pixels + i;
62       *p = *p & conv->format->Amask;
63     }
64     SDL_UnlockSurface(conv);
65     SDL_SetAlpha(conv, SDL_SRCALPHA, 128);
66     shadow_chars = new Surface(conv, true);
67     SDL_FreeSurface(conv);
68   }
69 }
70
71 Font::~Font()
72 {
73   delete chars;
74   delete shadow_chars;
75 }
76
77 float
78 Font::get_text_width(const std::string& text) const
79 {
80   /** Let's calculate the size of the biggest paragraph */
81   std::string::size_type l, hl, ol;
82   hl = 0; l = 0;
83   while(true)
84     {
85     ol = l;
86     l = text.find("\n", l+1);
87     if(l == std::string::npos)
88       break;
89     if(hl < l-ol)
90       hl = l-ol;
91     }
92   if(hl == 0)
93     hl = text.size();
94
95   return hl * w;
96 }
97
98 float
99 Font::get_text_height(const std::string& text) const
100 {
101   /** Let's calculate height of the text */
102   std::string::size_type l, hh;
103   hh = h; l = 0;
104   while(true)
105     {
106     l = text.find("\n", l+1);
107     if(l == std::string::npos)
108       break;
109     hh += h + 2;
110     }
111
112   return hh;
113 }
114
115 float
116 Font::get_height() const
117 {
118   return h;
119 }
120
121 void
122 Font::draw(const std::string& text, const Vector& pos_, int allignment, Uint32 drawing_effect, int alpha)
123 {
124   /* Cut lines changes into seperate strings, needed to support center/right text
125      allignments with break lines.
126      Feel free to replace this hack with a more elegant solution
127   */
128   char temp[1024];
129   std::string::size_type l, i, y;
130   bool done = false;
131   i = y = 0;
132
133   while(!done)
134     {
135     l = text.find("\n", i);
136     if(l == std::string::npos)
137       {
138       l = text.size();
139       done = true;
140       }
141
142     temp[text.copy(temp, l - i, i)] = '\0';
143
144     // calculate X positions based on the allignment type
145     Vector pos = Vector(pos_);
146     if(allignment == CENTER_ALLIGN)
147       pos.x -= get_text_width(temp) / 2;
148     else if(allignment == RIGHT_ALLIGN)
149       pos.x -= get_text_width(temp);
150
151     draw_text(temp, pos + Vector(0,y), drawing_effect, alpha);
152
153     i = l+1;
154     y += h + 2;
155     }
156 }
157
158 void
159 Font::draw_text(const std::string& text, const Vector& pos, Uint32 drawing_effect, int alpha)
160 {
161   if(shadowsize > 0)
162     draw_chars(shadow_chars, text, pos + Vector(shadowsize, shadowsize),
163                drawing_effect, alpha);
164
165   draw_chars(chars, text, pos, drawing_effect, alpha);
166 }
167
168 void
169 Font::draw_chars(Surface* pchars, const std::string& text, const Vector& pos,
170                  Uint32 drawing_effect, int alpha)
171 {
172   SurfaceImpl* impl = pchars->impl;
173
174   Vector p = pos;
175   for(size_t i = 0; i < text.size(); ++i)
176   {
177     int c = (unsigned char) text[i];
178     if(c > 127) // correct for the 32 controlchars at 128-159
179       c -= 32;
180     // a non-printable character?
181     if(c == '\n') {
182       p.x = pos.x;
183       p.y += h + 2;
184       continue;
185     }
186     if(c == ' ' || c < first_char || c > last_char) {
187       p.x += w;
188       continue;
189     }
190     
191     int index = c - first_char;
192     int source_x = (index % 16) * w;
193     int source_y = (index / 16) * h;
194
195     impl->draw_part(source_x, source_y, p.x, p.y, w, h, alpha, drawing_effect);
196     p.x += w;
197   }
198 }
199
200 /* --- SCROLL TEXT FUNCTION --- */
201
202 #define MAX_VEL     10
203 #define SPEED_INC   0.01
204 #define SCROLL      60
205 #define ITEMS_SPACE 4
206
207 void SuperTux::display_text_file(const std::string& file, float scroll_speed,
208     Font* heading_font, Font* normal_font, Font* small_font,
209     Font* reference_font)
210 {
211   std::string text;
212   std::string background_file;
213   std::vector<std::string> names;
214
215   std::string filename = datadir + "/" + file;
216   lisp::Parser parser;
217   try {
218     std::auto_ptr<lisp::Lisp> root (parser.parse(filename));
219
220     const lisp::Lisp* text_lisp = root->get_lisp("supertux-text");
221     if(!text_lisp)
222       throw std::runtime_error("File isn't a supertux-text file");
223     
224     if(!text_lisp->get("text", text))
225       throw std::runtime_error("file doesn't contain a text field");
226   } catch(std::exception& e) {
227     std::cerr << "Couldn't load file '" << filename << "': " << e.what() <<
228       "\n";
229     return;
230   }
231
232   // Split text string lines into a vector
233   names.clear();
234   std::string::size_type i, l;
235   i = 0;
236   while(true)
237     {
238     l = text.find("\n", i);
239
240     if(l == std::string::npos)
241       {
242       char temp[1024];
243       temp[text.copy(temp, text.size() - i, i)] = '\0';
244       names.push_back(temp);
245       break;
246       }
247
248     char temp[1024];
249     temp[text.copy(temp, l-i, i)] = '\0';
250     names.push_back(temp);
251
252     i = l+1;
253     }
254
255   // load background image
256   Surface* background = new Surface(datadir + "/images/background/" + background_file, false);
257
258   int done = 0;
259   float scroll = 0;
260   float speed = scroll_speed / 50;
261
262   DrawingContext context;
263   SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
264
265   Uint32 lastticks = SDL_GetTicks();
266   while(!done)
267     {
268       /* in case of input, exit */
269       SDL_Event event;
270       while(SDL_PollEvent(&event))
271         switch(event.type)
272           {
273           case SDL_KEYDOWN:
274             switch(event.key.keysym.sym)
275               {
276               case SDLK_UP:
277                 speed -= SPEED_INC;
278                 break;
279               case SDLK_DOWN:
280                 speed += SPEED_INC;
281                 break;
282               case SDLK_SPACE:
283               case SDLK_RETURN:
284                 if(speed >= 0)
285                   scroll += SCROLL;
286                 break;
287               case SDLK_ESCAPE:
288                 done = 1;
289                 break;
290               default:
291                 break;
292               }
293             break;
294           case SDL_QUIT:
295             done = 1;
296             break;
297           default:
298             break;
299           }
300
301       if(speed > MAX_VEL)
302         speed = MAX_VEL;
303       else if(speed < -MAX_VEL)
304         speed = -MAX_VEL;
305
306       /* draw the credits */
307       context.draw_surface(background, Vector(0,0), 0);
308
309       float y = 0;
310       for(size_t i = 0; i < names.size(); i++) {
311         if(names[i].size() == 0) {
312           y += normal_font->get_height() + ITEMS_SPACE;
313           continue;
314         }
315
316         Font* font = 0;
317         switch(names[i][0])
318         {
319           case ' ': font = small_font; break;
320           case '\t': font = normal_font; break;
321           case '-': font = heading_font; break;
322           case '*': font = reference_font; break;
323           default: font = reference_font; break;
324         }
325
326         context.draw_text(font,
327             names[i].substr(1, names[i].size()-1),
328             Vector(screen->w/2, screen->h + y - scroll), CENTER_ALLIGN, LAYER_FOREGROUND1);
329         y += font->get_height() + ITEMS_SPACE;
330       }
331
332       context.do_drawing();
333
334       if(screen->h+y-scroll < 0 && 20+screen->h+y-scroll < 0)
335         done = 1;
336
337       Uint32 ticks = SDL_GetTicks();
338       scroll += speed * (ticks - lastticks);
339       lastticks = ticks;
340       if(scroll < 0)
341         scroll = 0;
342
343       SDL_Delay(10);
344     }
345
346   SDL_EnableKeyRepeat(0, 0);    // disables key repeating
347   delete background;
348 }
349