#303: Typo fixes from mathnerd314
[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 completely 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 useful 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   std::string s = s_;
249
250   // if text is already smaller, return full text
251   if (get_text_width(s) <= width) {
252     if (overflow) *overflow = "";
253     return s;
254   }
255
256   // if we can find a whitespace character to break at, return text up to this character
257   for (int i = s.length()-1; i >= 0; i--) {
258     std::string s2 = s.substr(0,i);
259     if (s[i] != ' ') continue;
260     if (get_text_width(s2) <= width) {
261       if (overflow) *overflow = s.substr(i+1);
262       return s.substr(0, i);
263     }
264   }
265   
266   // FIXME: hard-wrap at width, taking care of multibyte characters
267   if (overflow) *overflow = "";
268   return s;
269 }
270
271 void
272 Font::draw(Renderer *renderer, const std::string& text, const Vector& pos_,
273            FontAlignment alignment, DrawingEffect drawing_effect,
274            float alpha) const
275 {
276   float x = pos_.x;
277   float y = pos_.y;
278
279   std::string::size_type last = 0;
280   for(std::string::size_type i = 0;; ++i)
281     {
282       if (text[i] == '\n' || i == text.size())
283         {
284           std::string temp = text.substr(last, i - last);
285
286           // calculate X positions based on the alignment type
287           Vector pos = Vector(x, y);
288
289           if(alignment == ALIGN_CENTER)
290             pos.x -= get_text_width(temp) / 2;
291           else if(alignment == ALIGN_RIGHT)
292             pos.x -= get_text_width(temp);
293
294           // Cast font position to integer to get a clean drawing result and
295           // no blurring as we would get with subpixel positions
296           pos.x = static_cast<int>(pos.x);
297
298           draw_text(renderer, temp, pos, drawing_effect, alpha);
299
300           if (i == text.size())
301             break;
302
303           y += char_height + 2;
304           last = i + 1;
305         }
306     }
307 }
308
309 void
310 Font::draw_text(Renderer *renderer, const std::string& text, const Vector& pos,
311                 DrawingEffect drawing_effect, float alpha) const
312 {
313   if(shadowsize > 0)
314     {
315       // FIXME: shadow_glyph_surface and glyph_surface do currently
316       // share the same glyph array, this is incorrect and should be
317       // fixed, it is however hardly noticeable
318       draw_chars(renderer, shadow_glyph_surface, text,
319                  pos + Vector(shadowsize, shadowsize), drawing_effect, alpha);
320     }
321
322   draw_chars(renderer, glyph_surface, text, pos, drawing_effect, alpha);
323 }
324
325 int
326 Font::chr2glyph(uint32_t chr) const
327 {
328   int glyph_index = chr - first_char;
329
330   // we don't have the control chars 0x80-0xa0 in the font
331   if (chr >= 0x80) { // non-ascii character
332     glyph_index -= 32;
333     if(chr <= 0xa0) {
334       log_debug << "Unsupported utf-8 character '" << chr << "' found" << std::endl;
335       glyph_index = 0;
336     }
337   }
338
339   if(glyph_index < 0 || glyph_index >= (int) char_count) {
340     log_debug << "Unsupported utf-8 character found" << std::endl;
341     glyph_index = 0;
342   }
343
344   return glyph_index;
345 }
346
347 void
348 Font::draw_chars(Renderer *renderer, Surface* pchars, const std::string& text,
349                  const Vector& pos, DrawingEffect drawing_effect,
350                  float alpha) const
351 {
352   Vector p = pos;
353
354   for(UTF8Iterator it(text); !it.done(); ++it)
355     {
356       int font_index = chr2glyph(*it);
357
358       if(*it == '\n')
359         {
360           p.x = pos.x;
361           p.y += char_height + 2;
362         }
363       else if(*it == ' ')
364         {
365           p.x += glyphs[font_index].advance;
366         }
367       else
368         {
369           const Glyph& glyph = glyphs[font_index];
370           DrawingRequest request;
371
372           request.pos = p + glyph.offset;
373           request.drawing_effect = drawing_effect;
374           request.alpha = alpha;
375
376           SurfacePartRequest surfacepartrequest;
377           surfacepartrequest.size = glyph.rect.p2 - glyph.rect.p1;
378           surfacepartrequest.source = glyph.rect.p1;
379           surfacepartrequest.surface = pchars;
380
381           request.request_data = &surfacepartrequest;
382           renderer->draw_surface_part(request);
383
384           p.x += glyphs[font_index].advance;
385         }
386     }
387 }
388
389
390 namespace {
391
392 /**
393  * 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
394  */
395 bool has_multibyte_mark(unsigned char c) {
396   return ((c & 0300) == 0200);
397 }
398
399 /**
400  * gets unicode character at byte position @a p of UTF-8 encoded @a
401  * text, then advances @a p to the next character.
402  *
403  * @throws std::runtime_error if decoding fails.
404  * See unicode standard section 3.10 table 3-5 and 3-6 for details.
405  */
406 uint32_t decode_utf8(const std::string& text, size_t& p)
407 {
408   uint32_t c1 = (unsigned char) text[p+0];
409
410   if (has_multibyte_mark(c1)) std::runtime_error("Malformed utf-8 sequence");
411
412   if ((c1 & 0200) == 0000) {
413     // 0xxx.xxxx: 1 byte sequence
414     p+=1;
415     return c1;
416   }
417   else if ((c1 & 0340) == 0300) {
418     // 110x.xxxx: 2 byte sequence
419     if(p+1 >= text.size()) throw std::range_error("Malformed utf-8 sequence");
420     uint32_t c2 = (unsigned char) text[p+1];
421     if (!has_multibyte_mark(c2)) throw std::runtime_error("Malformed utf-8 sequence");
422     p+=2;
423     return (c1 & 0037) << 6 | (c2 & 0077);
424   }
425   else if ((c1 & 0360) == 0340) {
426     // 1110.xxxx: 3 byte sequence
427     if(p+2 >= text.size()) throw std::range_error("Malformed utf-8 sequence");
428     uint32_t c2 = (unsigned char) text[p+1];
429     uint32_t c3 = (unsigned char) text[p+2];
430     if (!has_multibyte_mark(c2)) throw std::runtime_error("Malformed utf-8 sequence");
431     if (!has_multibyte_mark(c3)) throw std::runtime_error("Malformed utf-8 sequence");
432     p+=3;
433     return (c1 & 0017) << 12 | (c2 & 0077) << 6 | (c3 & 0077);
434   }
435   else if ((c1 & 0370) == 0360) {
436     // 1111.0xxx: 4 byte sequence
437     if(p+3 >= text.size()) throw std::range_error("Malformed utf-8 sequence");
438     uint32_t c2 = (unsigned char) text[p+1];
439     uint32_t c3 = (unsigned char) text[p+2];
440     uint32_t c4 = (unsigned char) text[p+4];
441     if (!has_multibyte_mark(c2)) throw std::runtime_error("Malformed utf-8 sequence");
442     if (!has_multibyte_mark(c3)) throw std::runtime_error("Malformed utf-8 sequence");
443     if (!has_multibyte_mark(c4)) throw std::runtime_error("Malformed utf-8 sequence");
444     p+=4;
445     return (c1 & 0007) << 18 | (c2 & 0077) << 12 | (c3 & 0077) << 6 | (c4 & 0077);
446   }
447   throw std::runtime_error("Malformed utf-8 sequence");
448 }
449
450 } // namespace