Refactored video/ subsystem to make adding other methods of rendering (in particular...
[supertux.git] / src / video / font.cpp
1 //  $Id$
2 //
3 //  SuperTux
4 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
5 //                     Ingo Ruhnke <grumbel@gmx.de>
6 //
7 //  This program is free software; you can redistribute it and/or
8 //  modify it under the terms of the GNU General Public License
9 //  as published by the Free Software Foundation; either version 2
10 //  of the License, or (at your option) any later version.
11 //
12 //  This program is distributed in the hope that it will be useful,
13 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
14 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 //  GNU General Public License for more details.
16 //
17 //  You should have received a copy of the GNU General Public License
18 //  along with this program; if not, write to the Free Software
19 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20
21 #include <config.h>
22
23 #include <cstdlib>
24 #include <cstring>
25 #include <stdexcept>
26
27 #include <SDL_image.h>
28 #include "physfs/physfs_sdl.hpp"
29
30 #include "lisp/parser.hpp"
31 #include "lisp/lisp.hpp"
32 #include "screen.hpp"
33 #include "font.hpp"
34 #include "renderer.hpp"
35 #include "drawing_context.hpp"
36 #include "log.hpp"
37
38 namespace {
39 bool     has_multibyte_mark(unsigned char c);
40 uint32_t decode_utf8(const std::string& text, size_t& p);
41
42 struct UTF8Iterator
43 {
44   const std::string&     text;
45   std::string::size_type pos;
46   uint32_t chr;
47
48   UTF8Iterator(const std::string& text_)
49     : text(text_),
50       pos(0)
51   {
52     chr = decode_utf8(text, pos);
53   }
54
55   bool done() const
56   {
57     return pos > text.size();
58   }
59
60   UTF8Iterator& operator++() {
61     try {
62       chr = decode_utf8(text, pos);
63     } catch (std::runtime_error) {
64       log_debug << "Malformed utf-8 sequence beginning with " << *((uint32_t*)(text.c_str() + pos)) << " found " << std::endl;
65       chr = 0;
66       ++pos;
67     }
68
69     return *this;
70   }
71
72   uint32_t operator*() const {
73     return chr;
74   }
75 };
76
77 bool vline_empty(SDL_Surface* surface, int x, int start_y, int end_y, Uint8 threshold)
78 {
79   Uint8* pixels = (Uint8*)surface->pixels;
80
81   for(int y = start_y; y < end_y; ++y)
82     {
83       const Uint8& p = pixels[surface->pitch*y + x*surface->format->BytesPerPixel + 3];
84       if (p > threshold)
85         {
86           return false;
87         }
88     }
89   return true;
90 }
91 } // namespace
92
93 Font::Font(GlyphWidth glyph_width_,
94            const std::string& filename,
95            const std::string& shadowfile,
96            int char_width, int char_height_,
97            int shadowsize_)
98   : glyph_width(glyph_width_),
99     glyph_surface(0), shadow_glyph_surface(0),
100     char_height(char_height_),
101     shadowsize(shadowsize_)
102 {
103   glyph_surface = new Surface(filename);
104   shadow_glyph_surface  = new Surface(shadowfile);
105
106   first_char = 32;
107   char_count = ((int) glyph_surface->get_height() / char_height) * 16;
108
109   if (glyph_width == FIXED)
110     {
111       for(uint32_t i = 0; i < char_count; ++i)
112         {
113           float x = (i % 16) * char_width;
114           float y = (i / 16) * char_height;
115
116           Glyph glyph;
117           glyph.advance = char_width;
118           glyph.offset  = Vector(0, 0);
119           glyph.rect    = Rect(x, y, x + char_width, y + char_height);
120
121           glyphs.push_back(glyph);
122           shadow_glyphs.push_back(glyph);
123         }
124     }
125   else // glyph_width == VARIABLE
126     {
127       // Load the surface into RAM and scan the pixel data for characters
128       SDL_Surface* surface = IMG_Load_RW(get_physfs_SDLRWops(filename), 1);
129       if(surface == NULL) {
130         std::ostringstream msg;
131         msg << "Couldn't load image '" << filename << "' :" << SDL_GetError();
132         throw std::runtime_error(msg.str());
133       }
134
135       SDL_LockSurface(surface);
136
137       for(uint32_t i = 0; i < char_count; ++i)
138         {
139           int x = (i % 16) * char_width;
140           int y = (i / 16) * char_height;
141
142           int left = x;
143           while (left < x + char_width &&
144                  vline_empty(surface, left, y, y + char_height, 64))
145             left += 1;
146
147           int right = x + char_width - 1;
148           while (right > left &&
149                  vline_empty(surface, right, y, y + char_height, 64))
150             right -= 1;
151
152           Glyph glyph;
153           glyph.offset = Vector(0, 0);
154
155           if (left <= right)
156             glyph.rect = Rect(left,  y, right+1, y + char_height);
157           else // glyph is completly transparent
158             glyph.rect = Rect(x,  y, x + char_width, y + char_height);
159
160           glyph.advance = glyph.rect.get_width() + 1; // FIXME: might be usefull to make spacing configurable
161
162           glyphs.push_back(glyph);
163           shadow_glyphs.push_back(glyph);
164         }
165
166       SDL_UnlockSurface(surface);
167
168       SDL_FreeSurface(surface);
169     }
170 }
171
172 Font::~Font()
173 {
174   delete glyph_surface;
175   delete shadow_glyph_surface;
176 }
177
178 float
179 Font::get_text_width(const std::string& text) const
180 {
181   float curr_width = 0;
182   float last_width = 0;
183
184   for(UTF8Iterator it(text); !it.done(); ++it)
185     {
186       if (*it == '\n')
187         {
188           last_width = std::max(last_width, curr_width);
189           curr_width = 0;
190         }
191       else
192         {
193           int idx = chr2glyph(*it);
194           curr_width += glyphs[idx].advance;
195         }
196     }
197
198   return std::max(curr_width, last_width);
199 }
200
201 float
202 Font::get_text_height(const std::string& text) const
203 {
204   std::string::size_type text_height = char_height;
205
206   for(std::string::const_iterator it = text.begin(); it != text.end(); ++it)
207     { // since UTF8 multibyte characters are decoded with values
208       // outside the ASCII range there is no risk of overlapping and
209       // thus we don't need to decode the utf-8 string
210       if (*it == '\n')
211         text_height += char_height + 2;
212     }
213
214   return text_height;
215 }
216
217 float
218 Font::get_height() const
219 {
220   return char_height;
221 }
222
223 std::string
224 Font::wrap_to_chars(const std::string& s, int line_length, std::string* overflow)
225 {
226   // if text is already smaller, return full text
227   if ((int)s.length() <= line_length) {
228     if (overflow) *overflow = "";
229     return s;
230   }
231
232   // if we can find a whitespace character to break at, return text up to this character
233   int i = line_length;
234   while ((i > 0) && (s[i] != ' ')) i--;
235   if (i > 0) {
236     if (overflow) *overflow = s.substr(i+1);
237     return s.substr(0, i);
238   }
239
240   // FIXME: wrap at line_length, taking care of multibyte characters
241   if (overflow) *overflow = "";
242   return s;
243 }
244
245 std::string
246 Font::wrap_to_width(const std::string& s, float width, std::string* overflow)
247 {
248   // if text is already smaller, return full text
249   if (get_text_width(s) <= width) {
250     if (overflow) *overflow = "";
251     return s;
252   }
253
254   // if we can find a whitespace character to break at, return text up to this character
255   for (int i = s.length()-1; i >= 0; i--) {
256     std::string s2 = s.substr(0,i);
257     if (s[i] != ' ') continue;
258     if (get_text_width(s2) <= width) {
259       if (overflow) *overflow = s.substr(i+1);
260       return s.substr(0, i);
261     }
262   }
263   
264   // FIXME: hard-wrap at width, taking care of multibyte characters
265   if (overflow) *overflow = "";
266   return s;
267 }
268
269 void
270 Font::draw(Renderer *renderer, const std::string& text, const Vector& pos_,
271            FontAlignment alignment, DrawingEffect drawing_effect,
272            float alpha) const
273 {
274   float x = pos_.x;
275   float y = pos_.y;
276
277   std::string::size_type last = 0;
278   for(std::string::size_type i = 0;; ++i)
279     {
280       if (text[i] == '\n' || i == text.size())
281         {
282           std::string temp = text.substr(last, i - last);
283
284           // calculate X positions based on the alignment type
285           Vector pos = Vector(x, y);
286
287           if(alignment == ALIGN_CENTER)
288             pos.x -= get_text_width(temp) / 2;
289           else if(alignment == ALIGN_RIGHT)
290             pos.x -= get_text_width(temp);
291
292           // Cast font position to integer to get a clean drawing result and
293           // no bluring as we would get with subpixel positions
294           pos.x = static_cast<int>(pos.x);
295
296           draw_text(renderer, temp, pos, drawing_effect, alpha);
297
298           if (i == text.size())
299             break;
300
301           y += char_height + 2;
302           last = i + 1;
303         }
304     }
305 }
306
307 void
308 Font::draw_text(Renderer *renderer, const std::string& text, const Vector& pos,
309                 DrawingEffect drawing_effect, float alpha) const
310 {
311   if(shadowsize > 0)
312     {
313       // FIXME: shadow_glyph_surface and glyph_surface do currently
314       // share the same glyph array, this is incorrect and should be
315       // fixed, it is however hardly noticable
316       draw_chars(renderer, shadow_glyph_surface, text,
317                  pos + Vector(shadowsize, shadowsize), drawing_effect, alpha);
318     }
319
320   draw_chars(renderer, glyph_surface, text, pos, drawing_effect, alpha);
321 }
322
323 int
324 Font::chr2glyph(uint32_t chr) const
325 {
326   int glyph_index = chr - first_char;
327
328   // we don't have the control chars 0x80-0xa0 in the font
329   if (chr >= 0x80) { // non-ascii character
330     glyph_index -= 32;
331     if(chr <= 0xa0) {
332       log_debug << "Unsupported utf-8 character '" << chr << "' found" << std::endl;
333       glyph_index = 0;
334     }
335   }
336
337   if(glyph_index < 0 || glyph_index >= (int) char_count) {
338     log_debug << "Unsupported utf-8 character found" << std::endl;
339     glyph_index = 0;
340   }
341
342   return glyph_index;
343 }
344
345 void
346 Font::draw_chars(Renderer *renderer, Surface* pchars, const std::string& text,
347                  const Vector& pos, DrawingEffect drawing_effect,
348                  float alpha) const
349 {
350   Vector p = pos;
351
352   for(UTF8Iterator it(text); !it.done(); ++it)
353     {
354       int font_index = chr2glyph(*it);
355
356       if(*it == '\n')
357         {
358           p.x = pos.x;
359           p.y += char_height + 2;
360         }
361       else if(*it == ' ')
362         {
363           p.x += glyphs[font_index].advance;
364         }
365       else
366         {
367           const Glyph& glyph = glyphs[font_index];
368           DrawingRequest request;
369
370           request.pos = p + glyph.offset;
371           request.drawing_effect = drawing_effect;
372           request.alpha = alpha;
373
374           SurfacePartRequest surfacepartrequest;
375           surfacepartrequest.size = glyph.rect.p2 - glyph.rect.p1;
376           surfacepartrequest.source = glyph.rect.p1;
377           surfacepartrequest.surface = pchars;
378
379           request.request_data = &surfacepartrequest;
380           renderer->draw_surface_part(request);
381
382           p.x += glyphs[font_index].advance;
383         }
384     }
385 }
386
387
388 namespace {
389
390 /**
391  * returns true if this byte matches a bitmask of 10xx.xxxx, i.e. it is the 2nd, 3rd or 4th byte of a multibyte utf8 string
392  */
393 bool has_multibyte_mark(unsigned char c) {
394   return ((c & 0300) == 0200);
395 }
396
397 /**
398  * gets unicode character at byte position @a p of UTF-8 encoded @a
399  * text, then advances @a p to the next character.
400  *
401  * @throws std::runtime_error if decoding fails.
402  * See unicode standard section 3.10 table 3-5 and 3-6 for details.
403  */
404 uint32_t decode_utf8(const std::string& text, size_t& p)
405 {
406   uint32_t c1 = (unsigned char) text[p+0];
407
408   if (has_multibyte_mark(c1)) std::runtime_error("Malformed utf-8 sequence");
409
410   if ((c1 & 0200) == 0000) {
411     // 0xxx.xxxx: 1 byte sequence
412     p+=1;
413     return c1;
414   }
415   else if ((c1 & 0340) == 0300) {
416     // 110x.xxxx: 2 byte sequence
417     if(p+1 >= text.size()) throw std::range_error("Malformed utf-8 sequence");
418     uint32_t c2 = (unsigned char) text[p+1];
419     if (!has_multibyte_mark(c2)) throw std::runtime_error("Malformed utf-8 sequence");
420     p+=2;
421     return (c1 & 0037) << 6 | (c2 & 0077);
422   }
423   else if ((c1 & 0360) == 0340) {
424     // 1110.xxxx: 3 byte sequence
425     if(p+2 >= text.size()) throw std::range_error("Malformed utf-8 sequence");
426     uint32_t c2 = (unsigned char) text[p+1];
427     uint32_t c3 = (unsigned char) text[p+2];
428     if (!has_multibyte_mark(c2)) throw std::runtime_error("Malformed utf-8 sequence");
429     if (!has_multibyte_mark(c3)) throw std::runtime_error("Malformed utf-8 sequence");
430     p+=3;
431     return (c1 & 0017) << 12 | (c2 & 0077) << 6 | (c3 & 0077);
432   }
433   else if ((c1 & 0370) == 0360) {
434     // 1111.0xxx: 4 byte sequence
435     if(p+3 >= text.size()) throw std::range_error("Malformed utf-8 sequence");
436     uint32_t c2 = (unsigned char) text[p+1];
437     uint32_t c3 = (unsigned char) text[p+2];
438     uint32_t c4 = (unsigned char) text[p+4];
439     if (!has_multibyte_mark(c2)) throw std::runtime_error("Malformed utf-8 sequence");
440     if (!has_multibyte_mark(c3)) throw std::runtime_error("Malformed utf-8 sequence");
441     if (!has_multibyte_mark(c4)) throw std::runtime_error("Malformed utf-8 sequence");
442     p+=4;
443     return (c1 & 0007) << 18 | (c2 & 0077) << 12 | (c3 & 0077) << 6 | (c4 & 0077);
444   }
445   throw std::runtime_error("Malformed utf-8 sequence");
446 }
447
448 } // namespace