Replaced more lisp::Lisp/lisp::Writer with Reader/Writer
[supertux.git] / src / video / font.cpp
1 //  SuperTux
2 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
3 //                     Ingo Ruhnke <grumbel@gmx.de>
4 //
5 //  This program is free software: you can redistribute it and/or modify
6 //  it under the terms of the GNU General Public License as published by
7 //  the Free Software Foundation, either version 3 of the License, or
8 //  (at your option) any later version.
9 //
10 //  This program is distributed in the hope that it will be useful,
11 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //  GNU General Public License for more details.
14 //
15 //  You should have received a copy of the GNU General Public License
16 //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 #include <config.h>
19
20 #include <stdlib.h>
21 #include <string.h>
22 #include <stdexcept>
23 #include <SDL_image.h>
24 #include <physfs.h>
25
26 #include "physfs/physfs_sdl.hpp"
27
28 #include "util/file_system.hpp"
29
30 #include "lisp/list_iterator.hpp"
31 #include "lisp/parser.hpp"
32 #include "supertux/screen.hpp"
33 #include "util/log.hpp"
34 #include "video/drawing_context.hpp"
35 #include "video/font.hpp"
36 #include "video/renderer.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 std::string encode_utf8(uint32_t code);
42
43 struct UTF8Iterator
44 {
45   const std::string&     text;
46   std::string::size_type pos;
47   uint32_t chr;
48
49   UTF8Iterator(const std::string& text_) :
50     text(text_),
51     pos(0),
52     chr()
53   {
54     try {
55       chr = decode_utf8(text, pos);
56     } catch (std::exception) {
57       log_debug << "Malformed utf-8 sequence beginning with " << *((uint32_t*)(text.c_str() + pos)) << " found " << std::endl;
58       chr = 0;
59     }
60   }
61
62   bool done() const
63   {
64     return pos > text.size();
65   }
66
67   UTF8Iterator& operator++() {
68     try {
69       chr = decode_utf8(text, pos);
70     } catch (std::exception) {
71       log_debug << "Malformed utf-8 sequence beginning with " << *((uint32_t*)(text.c_str() + pos)) << " found " << std::endl;
72       chr = 0;
73       ++pos;
74     }
75
76     return *this;
77   }
78
79   uint32_t operator*() const {
80     return chr;
81   }
82 };
83
84 bool vline_empty(SDL_Surface* surface, int x, int start_y, int end_y, Uint8 threshold)
85 {
86   Uint8* pixels = (Uint8*)surface->pixels;
87
88   for(int y = start_y; y < end_y; ++y)
89   {
90     const Uint8& p = pixels[surface->pitch*y + x*surface->format->BytesPerPixel + 3];
91     if (p > threshold)
92     {
93       return false;
94     }
95   }
96   return true;
97 }
98 } // namespace
99
100 Font::Font(GlyphWidth glyph_width_,
101            const std::string& filename,
102            int shadowsize_) :
103   glyph_width(glyph_width_),
104   glyph_surfaces(),
105   shadow_surfaces(),
106   char_height(),
107   shadowsize(shadowsize_),
108   glyphs(65536)
109 {
110   for(unsigned int i=0; i<65536;i++) glyphs[i].surface_idx = -1;
111
112   const std::string fontdir = FileSystem::dirname(filename);
113   const std::string fontname = FileSystem::basename(filename);
114
115   // scan for prefix-filename in addons search path
116   char **rc = PHYSFS_enumerateFiles(fontdir.c_str());
117   for (char **i = rc; *i != NULL; i++) {
118     std::string filename(*i);
119     if( filename.rfind(fontname) != std::string::npos ) {
120       loadFontFile(fontdir + filename);
121     }
122   }
123   PHYSFS_freeList(rc);
124 }
125
126 void 
127 Font::loadFontFile(const std::string &filename)
128 {
129   lisp::Parser parser;
130   log_debug << "Loading font: " << filename << std::endl;
131   const lisp::Lisp* root = parser.parse(filename);
132   const lisp::Lisp* config_l = root->get_lisp("supertux-font");
133
134   if(!config_l) {
135     std::ostringstream msg;
136     msg << "Font file:" << filename << ": is not a supertux-font file";
137     throw std::runtime_error(msg.str());
138   }
139
140   int def_char_width=0;
141
142   if( !config_l->get("glyph-width",def_char_width) ) {
143     log_warning << "Font:"<< filename << ": misses default glyph-width" << std::endl;
144   }
145   
146   if( !config_l->get("glyph-height",char_height) ) {
147     std::ostringstream msg;
148     msg << "Font:" << filename << ": misses glyph-height";
149     throw std::runtime_error(msg.str());
150   }
151
152   lisp::ListIterator iter(config_l);
153   while(iter.next()) {
154     const std::string& token = iter.item();
155     if( token == "surface" ) {
156       const lisp::Lisp * glyphs_val = iter.lisp();
157       int local_char_width;
158       bool monospaced;
159       GlyphWidth local_glyph_width;
160       std::string glyph_image;
161       std::string shadow_image;
162       std::vector<std::string> chars;
163       if( ! glyphs_val->get("glyph-width", local_char_width) ) {
164         local_char_width = def_char_width;
165       }
166       if( ! glyphs_val->get("monospace", monospaced ) ) {
167         local_glyph_width = glyph_width;
168       }
169       else {
170         if( monospaced ) local_glyph_width = FIXED;
171         else local_glyph_width = VARIABLE;
172       }
173       if( ! glyphs_val->get("glyphs", glyph_image) ) {
174         std::ostringstream msg;
175         msg << "Font:" << filename << ": missing glyphs image";
176         throw std::runtime_error(msg.str());
177       }
178       if( ! glyphs_val->get("shadows", shadow_image) ) {
179         std::ostringstream msg;
180         msg << "Font:" << filename << ": missing shadows image";
181         throw std::runtime_error(msg.str());
182       }
183       if( ! glyphs_val->get("chars", chars) || chars.size() == 0) {
184         std::ostringstream msg;
185         msg << "Font:" << filename << ": missing chars definition";
186         throw std::runtime_error(msg.str());
187       }
188
189       if( local_char_width==0 ) {
190         std::ostringstream msg;
191         msg << "Font:" << filename << ": misses glyph-width for some surface";
192         throw std::runtime_error(msg.str());
193       }
194
195       loadFontSurface(glyph_image, shadow_image, chars,
196                       local_glyph_width, local_char_width);
197     }
198   }
199 }
200
201 void 
202 Font::loadFontSurface(
203   const std::string &glyphimage,
204   const std::string &shadowimage,
205   const std::vector<std::string> &chars,
206   GlyphWidth glyph_width,
207   int char_width
208   )
209 {
210   Surface glyph_surface("images/engine/fonts/" + glyphimage);
211   Surface shadow_surface("images/engine/fonts/" + shadowimage);
212
213   int surface_idx = glyph_surfaces.size();
214   glyph_surfaces.push_back(glyph_surface);
215   shadow_surfaces.push_back(shadow_surface);
216
217   int row=0, col=0;
218   int wrap = glyph_surface.get_width() / char_width;
219  
220   SDL_Surface *surface = NULL;
221   
222   if( glyph_width == VARIABLE ) {
223     //this does not work:
224     // surface = ((SDL::Texture *)glyph_surface.get_texture())->get_texture();
225     surface = IMG_Load_RW(get_physfs_SDLRWops("images/engine/fonts/"+glyphimage), 1);
226     if(surface == NULL) {
227       std::ostringstream msg;
228       msg << "Couldn't load image '" << glyphimage << "' :" << SDL_GetError();
229       throw std::runtime_error(msg.str());
230     }
231     SDL_LockSurface(surface);
232   }
233
234   for( unsigned int i = 0; i < chars.size(); i++) {
235     for(UTF8Iterator chr(chars[i]); !chr.done(); ++chr) {
236       int y = row * char_height;
237       int x = col * char_width;
238       if( ++col == wrap ) { col=0; row++; }
239       if( *chr == 0x0020 && glyphs[0x20].surface_idx != -1) continue;
240         
241       Glyph glyph;
242       glyph.surface_idx   = surface_idx;
243       
244       if( glyph_width == FIXED ) {
245         glyph.rect    = Rect(x, y, x + char_width, y + char_height);
246         glyph.offset  = Vector(0, 0);
247         glyph.advance = char_width;
248       }
249       else {
250         int left = x;
251         while (left < x + char_width && vline_empty(surface, left, y, y + char_height, 64))
252           left += 1;
253         int right = x + char_width - 1;
254         while (right > left && vline_empty(surface, right, y, y + char_height, 64))
255           right -= 1;
256           
257         if (left <= right)
258           glyph.rect = Rect(left,  y, right+1, y + char_height);
259         else // glyph is completely transparent
260           glyph.rect = Rect(x,  y, x + char_width, y + char_height);
261         
262         glyph.offset  = Vector(0, 0);
263         glyph.advance = glyph.rect.get_width() + 1; // FIXME: might be useful to make spacing configurable
264       }
265
266       glyphs[*chr] = glyph;
267     }
268     if( col>0 && col <= wrap ) { 
269       col = 0;
270       row++;
271     }
272   }
273   
274   if( surface != NULL ) {
275     SDL_UnlockSurface(surface);
276     SDL_FreeSurface(surface);
277   }
278 }
279
280 Font::~Font()
281 {
282 }
283
284 float
285 Font::get_text_width(const std::string& text) const
286 {
287   float curr_width = 0;
288   float last_width = 0;
289
290   for(UTF8Iterator it(text); !it.done(); ++it)
291   {
292     if (*it == '\n')
293     {
294       last_width = std::max(last_width, curr_width);
295       curr_width = 0;
296     }
297     else
298     {
299       if( glyphs.at(*it).surface_idx != -1 )
300         curr_width += glyphs[*it].advance;
301       else 
302         curr_width += glyphs[0x20].advance;
303     }
304   }
305
306   return std::max(curr_width, last_width);
307 }
308
309 float
310 Font::get_text_height(const std::string& text) const
311 {
312   std::string::size_type text_height = char_height;
313
314   for(std::string::const_iterator it = text.begin(); it != text.end(); ++it)
315   { // since UTF8 multibyte characters are decoded with values
316     // outside the ASCII range there is no risk of overlapping and
317     // thus we don't need to decode the utf-8 string
318     if (*it == '\n')
319       text_height += char_height + 2;
320   }
321
322   return text_height;
323 }
324
325 float
326 Font::get_height() const
327 {
328   return char_height;
329 }
330
331 std::string
332 Font::wrap_to_chars(const std::string& s, int line_length, std::string* overflow)
333 {
334   // if text is already smaller, return full text
335   if ((int)s.length() <= line_length) {
336     if (overflow) *overflow = "";
337     return s;
338   }
339
340   // if we can find a whitespace character to break at, return text up to this character
341   int i = line_length;
342   while ((i > 0) && (s[i] != ' ')) i--;
343   if (i > 0) {
344     if (overflow) *overflow = s.substr(i+1);
345     return s.substr(0, i);
346   }
347
348   // FIXME: wrap at line_length, taking care of multibyte characters
349   if (overflow) *overflow = "";
350   return s;
351 }
352
353 std::string
354 Font::wrap_to_width(const std::string& s_, float width, std::string* overflow)
355 {
356   std::string s = s_;
357
358   // if text is already smaller, return full text
359   if (get_text_width(s) <= width) {
360     if (overflow) *overflow = "";
361     return s;
362   }
363
364   // if we can find a whitespace character to break at, return text up to this character
365   for (int i = s.length()-1; i >= 0; i--) {
366     std::string s2 = s.substr(0,i);
367     if (s[i] != ' ') continue;
368     if (get_text_width(s2) <= width) {
369       if (overflow) *overflow = s.substr(i+1);
370       return s.substr(0, i);
371     }
372   }
373   
374   // FIXME: hard-wrap at width, taking care of multibyte characters
375   if (overflow) *overflow = "";
376   return s;
377 }
378
379 void
380 Font::draw(Renderer *renderer, const std::string& text, const Vector& pos_,
381            FontAlignment alignment, DrawingEffect drawing_effect, Color color,
382            float alpha) const
383 {
384   float x = pos_.x;
385   float y = pos_.y;
386
387   std::string::size_type last = 0;
388   for(std::string::size_type i = 0;; ++i)
389   {
390     if (text[i] == '\n' || i == text.size())
391     {
392       std::string temp = text.substr(last, i - last);
393
394       // calculate X positions based on the alignment type
395       Vector pos = Vector(x, y);
396
397       if(alignment == ALIGN_CENTER)
398         pos.x -= get_text_width(temp) / 2;
399       else if(alignment == ALIGN_RIGHT)
400         pos.x -= get_text_width(temp);
401
402       // Cast font position to integer to get a clean drawing result and
403       // no blurring as we would get with subpixel positions
404       pos.x = static_cast<int>(pos.x);
405
406       draw_text(renderer, temp, pos, drawing_effect, color, alpha);
407
408       if (i == text.size())
409         break;
410
411       y += char_height + 2;
412       last = i + 1;
413     }
414   }
415 }
416
417 void
418 Font::draw_text(Renderer *renderer, const std::string& text, const Vector& pos,
419                 DrawingEffect drawing_effect, Color color, float alpha) const
420 {
421   if(shadowsize > 0)
422     draw_chars(renderer, false, text, 
423                pos + Vector(shadowsize, shadowsize), drawing_effect, Color(1,1,1), alpha);
424
425   draw_chars(renderer, true, text, pos, drawing_effect, color, alpha);
426 }
427
428 void
429 Font::draw_chars(Renderer *renderer, bool notshadow, const std::string& text,
430                  const Vector& pos, DrawingEffect drawing_effect, Color color,
431                  float alpha) const
432 {
433   Vector p = pos;
434
435   for(UTF8Iterator it(text); !it.done(); ++it)
436   {
437     if(*it == '\n')
438     {
439       p.x = pos.x;
440       p.y += char_height + 2;
441     }
442     else if(*it == ' ')
443     {
444       p.x += glyphs[0x20].advance;
445     }
446     else
447     {
448       Glyph glyph;
449       if( glyphs.at(*it).surface_idx != -1 )
450         glyph = glyphs[*it];
451       else 
452         glyph = glyphs[0x20];
453
454       DrawingRequest request;
455
456       request.pos = p + glyph.offset;
457       request.drawing_effect = drawing_effect;
458       request.color = color;
459       request.alpha = alpha;
460
461       SurfacePartRequest surfacepartrequest;
462       surfacepartrequest.size = glyph.rect.p2 - glyph.rect.p1;
463       surfacepartrequest.source = glyph.rect.p1;
464       surfacepartrequest.surface = notshadow ? &(glyph_surfaces[glyph.surface_idx]) : &(shadow_surfaces[glyph.surface_idx]);
465
466       request.request_data = &surfacepartrequest;
467       renderer->draw_surface_part(request);
468
469       p.x += glyph.advance;
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
535
536 /* EOF */