e0a8e1977dd59eee6c2be55641e27687232a019b
[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 #include <physfs.h>
30 #include "file_system.hpp"
31
32 #include "lisp/lisp.hpp"
33 #include "lisp/parser.hpp"
34 #include "lisp/list_iterator.hpp"
35 #include "screen.hpp"
36 #include "font.hpp"
37 #include "renderer.hpp"
38 #include "drawing_context.hpp"
39 #include "log.hpp"
40
41 namespace {
42 bool     has_multibyte_mark(unsigned char c);
43 uint32_t decode_utf8(const std::string& text, size_t& p);
44 std::string encode_utf8(uint32_t code);
45
46 struct UTF8Iterator
47 {
48   const std::string&     text;
49   std::string::size_type pos;
50   uint32_t chr;
51
52   UTF8Iterator(const std::string& text_)
53     : text(text_),
54       pos(0)
55   {
56     try {
57       chr = decode_utf8(text, pos);
58     } catch (std::exception) {
59       log_debug << "Malformed utf-8 sequence beginning with " << *((uint32_t*)(text.c_str() + pos)) << " found " << std::endl;
60       chr = 0;
61     }
62   }
63
64   bool done() const
65   {
66     return pos > text.size();
67   }
68
69   UTF8Iterator& operator++() {
70     try {
71       chr = decode_utf8(text, pos);
72     } catch (std::exception) {
73       log_debug << "Malformed utf-8 sequence beginning with " << *((uint32_t*)(text.c_str() + pos)) << " found " << std::endl;
74       chr = 0;
75       ++pos;
76     }
77
78     return *this;
79   }
80
81   uint32_t operator*() const {
82     return chr;
83   }
84 };
85
86 bool vline_empty(SDL_Surface* surface, int x, int start_y, int end_y, Uint8 threshold)
87 {
88   Uint8* pixels = (Uint8*)surface->pixels;
89
90   for(int y = start_y; y < end_y; ++y)
91     {
92       const Uint8& p = pixels[surface->pitch*y + x*surface->format->BytesPerPixel + 3];
93       if (p > threshold)
94         {
95           return false;
96         }
97     }
98   return true;
99 }
100 } // namespace
101
102 Font::Font(GlyphWidth glyph_width_,
103            const std::string& filename,
104            int shadowsize_)
105 :   glyph_width(glyph_width_),
106     shadowsize(shadowsize_),
107     glyphs(65536)
108 {
109   for(unsigned int i=0; i<65536;i++) glyphs[i].surface_idx = -1;
110
111   const std::string fontdir = FileSystem::dirname(filename);
112   const std::string fontname = FileSystem::basename(filename);
113
114   // scan for prefix-filename in addons search path
115   char **rc = PHYSFS_enumerateFiles(fontdir.c_str());
116   for (char **i = rc; *i != NULL; i++) {
117     std::string filename(*i);
118     if( filename.rfind(fontname) != std::string::npos ) {
119       loadFontFile(fontdir + filename);
120       }
121     }
122   PHYSFS_freeList(rc);
123 }
124
125 void 
126 Font::loadFontFile(const std::string &filename)
127 {
128   lisp::Parser parser;
129   log_debug << "Loading font: " << filename << std::endl;
130   const lisp::Lisp* root = parser.parse(filename);
131   const lisp::Lisp* config_l = root->get_lisp("supertux-font");
132
133   if(!config_l) {
134     std::ostringstream msg;
135     msg << "Font file:" << filename << ": is not a supertux-font file";
136     throw std::runtime_error(msg.str());
137     }
138
139   int def_char_width=0;
140
141   if( !config_l->get("glyph-width",def_char_width) ) {
142     log_warning << "Font:"<< filename << ": misses default glyph-width" << std::endl;
143     }
144   
145   if( !config_l->get("glyph-height",char_height) ) {
146     std::ostringstream msg;
147     msg << "Font:" << filename << ": misses glyph-height";
148     throw std::runtime_error(msg.str());
149     }
150
151   lisp::ListIterator iter(config_l);
152   while(iter.next()) {
153     const std::string& token = iter.item();
154     if( token == "surface" ) {
155       const lisp::Lisp * glyphs_val = iter.lisp();
156       int local_char_width;
157       bool monospaced;
158       GlyphWidth local_glyph_width;
159       std::string glyph_image;
160       std::string shadow_image;
161       std::vector<std::string> chars;
162       if( ! glyphs_val->get("glyph-width", local_char_width) ) {
163         local_char_width = def_char_width;
164         }
165       if( ! glyphs_val->get("monospace", monospaced ) ) {
166         local_glyph_width = glyph_width;
167         }
168       else {
169         if( monospaced ) local_glyph_width = FIXED;
170         else local_glyph_width = VARIABLE;
171         }
172       if( ! glyphs_val->get("glyphs", glyph_image) ) {
173         std::ostringstream msg;
174         msg << "Font:" << filename << ": missing glyphs image";
175         throw std::runtime_error(msg.str());
176         }
177       if( ! glyphs_val->get("shadows", shadow_image) ) {
178         std::ostringstream msg;
179         msg << "Font:" << filename << ": missing shadows image";
180         throw std::runtime_error(msg.str());
181         }
182       if( ! glyphs_val->get("chars", chars) || chars.size() == 0) {
183         std::ostringstream msg;
184         msg << "Font:" << filename << ": missing chars definition";
185         throw std::runtime_error(msg.str());
186         }
187
188       if( local_char_width==0 ) {
189         std::ostringstream msg;
190         msg << "Font:" << filename << ": misses glyph-width for some surface";
191         throw std::runtime_error(msg.str());
192         }
193
194       loadFontSurface(glyph_image, shadow_image, chars,
195                  local_glyph_width, local_char_width);
196       }
197     }
198 }
199
200 void 
201 Font::loadFontSurface(
202   const std::string &glyphimage,
203   const std::string &shadowimage,
204   const std::vector<std::string> &chars,
205   GlyphWidth glyph_width,
206   int char_width
207   )
208 {
209   Surface glyph_surface = Surface("images/engine/fonts/" + glyphimage);
210   Surface shadow_surface = Surface("images/engine/fonts/" + shadowimage);
211
212   int surface_idx = glyph_surfaces.size();
213   glyph_surfaces.push_back(glyph_surface);
214   shadow_surfaces.push_back(shadow_surface);
215
216   int row=0, col=0;
217   int wrap = glyph_surface.get_width() / char_width;
218  
219   SDL_Surface *surface = NULL;
220   
221   if( glyph_width == VARIABLE ) {
222     //this does not work:
223     // surface = ((SDL::Texture *)glyph_surface.get_texture())->get_texture();
224     surface = IMG_Load_RW(get_physfs_SDLRWops("images/engine/fonts/"+glyphimage), 1);
225     if(surface == NULL) {
226       std::ostringstream msg;
227       msg << "Couldn't load image '" << glyphimage << "' :" << SDL_GetError();
228       throw std::runtime_error(msg.str());
229       }
230     SDL_LockSurface(surface);
231     }
232
233   for( unsigned int i = 0; i < chars.size(); i++) {
234     for(UTF8Iterator chr(chars[i]); !chr.done(); ++chr) {
235       int y = row * char_height;
236       int x = col * char_width;
237       if( ++col == wrap ) { col=0; row++; }
238       if( *chr == 0x0020 && glyphs[0x20].surface_idx != -1) continue;
239         
240       Glyph glyph;
241       glyph.surface_idx   = surface_idx;
242       
243       if( glyph_width == FIXED ) {
244         glyph.rect    = Rect(x, y, x + char_width, y + char_height);
245         glyph.offset  = Vector(0, 0);
246         glyph.advance = char_width;
247         }
248       else {
249         int left = x;
250         while (left < x + char_width && vline_empty(surface, left, y, y + char_height, 64))
251           left += 1;
252         int right = x + char_width - 1;
253         while (right > left && vline_empty(surface, right, y, y + char_height, 64))
254           right -= 1;
255           
256         if (left <= right)
257           glyph.rect = Rect(left,  y, right+1, y + char_height);
258         else // glyph is completely transparent
259           glyph.rect = Rect(x,  y, x + char_width, y + char_height);
260         
261         glyph.offset  = Vector(0, 0);
262         glyph.advance = glyph.rect.get_width() + 1; // FIXME: might be useful to make spacing configurable
263         }
264
265       glyphs[*chr] = glyph;
266       }
267     if( col>0 && col <= wrap ) { 
268       col = 0;
269       row++;
270       }
271     }
272   
273   if( surface != NULL ) {
274     SDL_UnlockSurface(surface);
275     SDL_FreeSurface(surface);
276     }
277 }
278
279 Font::~Font()
280 {
281 }
282
283 float
284 Font::get_text_width(const std::string& text) const
285 {
286   float curr_width = 0;
287   float last_width = 0;
288
289   for(UTF8Iterator it(text); !it.done(); ++it)
290     {
291       if (*it == '\n')
292         {
293           last_width = std::max(last_width, curr_width);
294           curr_width = 0;
295         }
296       else
297         {
298           if( glyphs.at(*it).surface_idx != -1 )
299             curr_width += glyphs[*it].advance;
300           else 
301             curr_width += glyphs[0x20].advance;
302         }
303     }
304
305   return std::max(curr_width, last_width);
306 }
307
308 float
309 Font::get_text_height(const std::string& text) const
310 {
311   std::string::size_type text_height = char_height;
312
313   for(std::string::const_iterator it = text.begin(); it != text.end(); ++it)
314     { // since UTF8 multibyte characters are decoded with values
315       // outside the ASCII range there is no risk of overlapping and
316       // thus we don't need to decode the utf-8 string
317       if (*it == '\n')
318         text_height += char_height + 2;
319     }
320
321   return text_height;
322 }
323
324 float
325 Font::get_height() const
326 {
327   return char_height;
328 }
329
330 std::string
331 Font::wrap_to_chars(const std::string& s, int line_length, std::string* overflow)
332 {
333   // if text is already smaller, return full text
334   if ((int)s.length() <= line_length) {
335     if (overflow) *overflow = "";
336     return s;
337   }
338
339   // if we can find a whitespace character to break at, return text up to this character
340   int i = line_length;
341   while ((i > 0) && (s[i] != ' ')) i--;
342   if (i > 0) {
343     if (overflow) *overflow = s.substr(i+1);
344     return s.substr(0, i);
345   }
346
347   // FIXME: wrap at line_length, taking care of multibyte characters
348   if (overflow) *overflow = "";
349   return s;
350 }
351
352 std::string
353 Font::wrap_to_width(const std::string& s_, float width, std::string* overflow)
354 {
355   std::string s = s_;
356
357   // if text is already smaller, return full text
358   if (get_text_width(s) <= width) {
359     if (overflow) *overflow = "";
360     return s;
361   }
362
363   // if we can find a whitespace character to break at, return text up to this character
364   for (int i = s.length()-1; i >= 0; i--) {
365     std::string s2 = s.substr(0,i);
366     if (s[i] != ' ') continue;
367     if (get_text_width(s2) <= width) {
368       if (overflow) *overflow = s.substr(i+1);
369       return s.substr(0, i);
370     }
371   }
372   
373   // FIXME: hard-wrap at width, taking care of multibyte characters
374   if (overflow) *overflow = "";
375   return s;
376 }
377
378 void
379 Font::draw(Renderer *renderer, const std::string& text, const Vector& pos_,
380            FontAlignment alignment, DrawingEffect drawing_effect, Color color,
381            float alpha) const
382 {
383   float x = pos_.x;
384   float y = pos_.y;
385
386   std::string::size_type last = 0;
387   for(std::string::size_type i = 0;; ++i)
388     {
389       if (text[i] == '\n' || i == text.size())
390         {
391           std::string temp = text.substr(last, i - last);
392
393           // calculate X positions based on the alignment type
394           Vector pos = Vector(x, y);
395
396           if(alignment == ALIGN_CENTER)
397             pos.x -= get_text_width(temp) / 2;
398           else if(alignment == ALIGN_RIGHT)
399             pos.x -= get_text_width(temp);
400
401           // Cast font position to integer to get a clean drawing result and
402           // no blurring as we would get with subpixel positions
403           pos.x = static_cast<int>(pos.x);
404
405           draw_text(renderer, temp, pos, drawing_effect, color, alpha);
406
407           if (i == text.size())
408             break;
409
410           y += char_height + 2;
411           last = i + 1;
412         }
413     }
414 }
415
416 void
417 Font::draw_text(Renderer *renderer, const std::string& text, const Vector& pos,
418                 DrawingEffect drawing_effect, Color color, float alpha) const
419 {
420   if(shadowsize > 0)
421       draw_chars(renderer, false, text, 
422                  pos + Vector(shadowsize, shadowsize), drawing_effect, Color(1,1,1), alpha);
423
424   draw_chars(renderer, true, text, pos, drawing_effect, color, alpha);
425 }
426
427 void
428 Font::draw_chars(Renderer *renderer, bool notshadow, const std::string& text,
429                  const Vector& pos, DrawingEffect drawing_effect, Color color,
430                  float alpha) const
431 {
432   Vector p = pos;
433
434   for(UTF8Iterator it(text); !it.done(); ++it)
435     {
436       if(*it == '\n')
437         {
438           p.x = pos.x;
439           p.y += char_height + 2;
440         }
441       else if(*it == ' ')
442         {
443           p.x += glyphs[0x20].advance;
444         }
445       else
446         {
447           Glyph glyph;
448           if( glyphs.at(*it).surface_idx != -1 )
449             glyph = glyphs[*it];
450           else 
451             glyph = glyphs[0x20];
452
453           DrawingRequest request;
454
455           request.pos = p + glyph.offset;
456           request.drawing_effect = drawing_effect;
457           request.color = color;
458           request.alpha = alpha;
459
460           SurfacePartRequest surfacepartrequest;
461           surfacepartrequest.size = glyph.rect.p2 - glyph.rect.p1;
462           surfacepartrequest.source = glyph.rect.p1;
463           surfacepartrequest.surface = notshadow ? &(glyph_surfaces[glyph.surface_idx]) : &(shadow_surfaces[glyph.surface_idx]);
464
465           request.request_data = &surfacepartrequest;
466           renderer->draw_surface_part(request);
467
468           p.x += glyph.advance;
469         }
470     }
471 }
472
473
474 namespace {
475
476 /**
477  * 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
478  */
479 bool has_multibyte_mark(unsigned char c) {
480   return ((c & 0300) == 0200);
481 }
482
483 /**
484  * gets unicode character at byte position @a p of UTF-8 encoded @a
485  * text, then advances @a p to the next character.
486  *
487  * @throws std::runtime_error if decoding fails.
488  * See unicode standard section 3.10 table 3-5 and 3-6 for details.
489  */
490 uint32_t decode_utf8(const std::string& text, size_t& p)
491 {
492   uint32_t c1 = (unsigned char) text[p+0];
493
494   if (has_multibyte_mark(c1)) std::runtime_error("Malformed utf-8 sequence");
495
496   if ((c1 & 0200) == 0000) {
497     // 0xxx.xxxx: 1 byte sequence
498     p+=1;
499     return c1;
500   }
501   else if ((c1 & 0340) == 0300) {
502     // 110x.xxxx: 2 byte sequence
503     if(p+1 >= text.size()) throw std::range_error("Malformed utf-8 sequence");
504     uint32_t c2 = (unsigned char) text[p+1];
505     if (!has_multibyte_mark(c2)) throw std::runtime_error("Malformed utf-8 sequence");
506     p+=2;
507     return (c1 & 0037) << 6 | (c2 & 0077);
508   }
509   else if ((c1 & 0360) == 0340) {
510     // 1110.xxxx: 3 byte sequence
511     if(p+2 >= text.size()) throw std::range_error("Malformed utf-8 sequence");
512     uint32_t c2 = (unsigned char) text[p+1];
513     uint32_t c3 = (unsigned char) text[p+2];
514     if (!has_multibyte_mark(c2)) throw std::runtime_error("Malformed utf-8 sequence");
515     if (!has_multibyte_mark(c3)) throw std::runtime_error("Malformed utf-8 sequence");
516     p+=3;
517     return (c1 & 0017) << 12 | (c2 & 0077) << 6 | (c3 & 0077);
518   }
519   else if ((c1 & 0370) == 0360) {
520     // 1111.0xxx: 4 byte sequence
521     if(p+3 >= text.size()) throw std::range_error("Malformed utf-8 sequence");
522     uint32_t c2 = (unsigned char) text[p+1];
523     uint32_t c3 = (unsigned char) text[p+2];
524     uint32_t c4 = (unsigned char) text[p+4];
525     if (!has_multibyte_mark(c2)) throw std::runtime_error("Malformed utf-8 sequence");
526     if (!has_multibyte_mark(c3)) throw std::runtime_error("Malformed utf-8 sequence");
527     if (!has_multibyte_mark(c4)) throw std::runtime_error("Malformed utf-8 sequence");
528     p+=4;
529     return (c1 & 0007) << 18 | (c2 & 0077) << 12 | (c3 & 0077) << 6 | (c4 & 0077);
530   }
531   throw std::runtime_error("Malformed utf-8 sequence");
532 }
533
534 } // namespace