Fixed svn:keywords... (Why aren't people using the feature to make svn autoset proper...
[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 "drawing_context.hpp"
35 #include "log.hpp"
36
37 namespace {
38 bool     has_multibyte_mark(unsigned char c);
39 uint32_t decode_utf8(const std::string& text, size_t& p);
40
41 struct UTF8Iterator
42 {
43   const std::string&     text;
44   std::string::size_type pos;
45   uint32_t chr;
46
47   UTF8Iterator(const std::string& text_)
48     : text(text_),
49       pos(0)
50   {
51     chr = decode_utf8(text, pos);
52   }
53
54   bool done() const
55   {
56     return pos > text.size();
57   }
58
59   UTF8Iterator& operator++() {
60     try {
61       chr = decode_utf8(text, pos);
62     } catch (std::runtime_error) {
63       log_debug << "Malformed utf-8 sequence beginning with " << *((uint32_t*)(text.c_str() + pos)) << " found " << std::endl;
64       chr = 0;
65       ++pos;
66     }
67
68     return *this;
69   }
70
71   uint32_t operator*() const {
72     return chr;
73   }
74 };
75
76 bool vline_empty(SDL_Surface* surface, int x, int start_y, int end_y, Uint8 threshold)
77 {
78   Uint8* pixels = (Uint8*)surface->pixels;
79
80   for(int y = start_y; y < end_y; ++y)
81     {
82       const Uint8& p = pixels[surface->pitch*y + x*surface->format->BytesPerPixel + 3];
83       if (p > threshold)
84         {
85           return false;
86         }
87     }
88   return true;
89 }
90 } // namespace
91
92 Font::Font(GlyphWidth glyph_width_,
93            const std::string& filename,
94            const std::string& shadowfile,
95            int char_width, int char_height_,
96            int shadowsize_)
97   : glyph_width(glyph_width_),
98     glyph_surface(0), shadow_glyph_surface(0),
99     char_height(char_height_),
100     shadowsize(shadowsize_)
101 {
102   glyph_surface = new Surface(filename);
103   shadow_glyph_surface  = new Surface(shadowfile);
104
105   first_char = 32;
106   char_count = ((int) glyph_surface->get_height() / char_height) * 16;
107
108   if (glyph_width == FIXED)
109     {
110       for(uint32_t i = 0; i < char_count; ++i)
111         {
112           float x = (i % 16) * char_width;
113           float y = (i / 16) * char_height;
114
115           Glyph glyph;
116           glyph.advance = char_width;
117           glyph.offset  = Vector(0, 0);
118           glyph.rect    = Rect(x, y, x + char_width, y + char_height);
119
120           glyphs.push_back(glyph);
121           shadow_glyphs.push_back(glyph);
122         }
123     }
124   else // glyph_width == VARIABLE
125     {
126       // Load the surface into RAM and scan the pixel data for characters
127       SDL_Surface* surface = IMG_Load_RW(get_physfs_SDLRWops(filename), 1);
128       if(surface == NULL) {
129         std::ostringstream msg;
130         msg << "Couldn't load image '" << filename << "' :" << SDL_GetError();
131         throw std::runtime_error(msg.str());
132       }
133
134       SDL_LockSurface(surface);
135
136       for(uint32_t i = 0; i < char_count; ++i)
137         {
138           int x = (i % 16) * char_width;
139           int y = (i / 16) * char_height;
140
141           int left = x;
142           while (left < x + char_width &&
143                  vline_empty(surface, left, y, y + char_height, 64))
144             left += 1;
145
146           int right = x + char_width - 1;
147           while (right > left &&
148                  vline_empty(surface, right, y, y + char_height, 64))
149             right -= 1;
150
151           Glyph glyph;
152           glyph.offset = Vector(0, 0);
153
154           if (left <= right)
155             glyph.rect = Rect(left,  y, right+1, y + char_height);
156           else // glyph is completly transparent
157             glyph.rect = Rect(x,  y, x + char_width, y + char_height);
158
159           glyph.advance = glyph.rect.get_width() + 1; // FIXME: might be usefull to make spacing configurable
160
161           glyphs.push_back(glyph);
162           shadow_glyphs.push_back(glyph);
163         }
164
165       SDL_UnlockSurface(surface);
166
167       SDL_FreeSurface(surface);
168     }
169 }
170
171 Font::~Font()
172 {
173   delete glyph_surface;
174   delete shadow_glyph_surface;
175 }
176
177 float
178 Font::get_text_width(const std::string& text) const
179 {
180   float curr_width = 0;
181   float last_width = 0;
182
183   for(UTF8Iterator it(text); !it.done(); ++it)
184     {
185       if (*it == '\n')
186         {
187           last_width = std::max(last_width, curr_width);
188           curr_width = 0;
189         }
190       else
191         {
192           int idx = chr2glyph(*it);
193           curr_width += glyphs[idx].advance;
194         }
195     }
196
197   return std::max(curr_width, last_width);
198 }
199
200 float
201 Font::get_text_height(const std::string& text) const
202 {
203   std::string::size_type text_height = char_height;
204
205   for(std::string::const_iterator it = text.begin(); it != text.end(); ++it)
206     { // since UTF8 multibyte characters are decoded with values
207       // outside the ASCII range there is no risk of overlapping and
208       // thus we don't need to decode the utf-8 string
209       if (*it == '\n')
210         text_height += char_height + 2;
211     }
212
213   return text_height;
214 }
215
216 float
217 Font::get_height() const
218 {
219   return char_height;
220 }
221
222 std::string
223 Font::wrap_to_chars(const std::string& s, int line_length, std::string* overflow)
224 {
225   // if text is already smaller, return full text
226   if ((int)s.length() <= line_length) {
227     if (overflow) *overflow = "";
228     return s;
229   }
230
231   // if we can find a whitespace character to break at, return text up to this character
232   int i = line_length;
233   while ((i > 0) && (s[i] != ' ')) i--;
234   if (i > 0) {
235     if (overflow) *overflow = s.substr(i+1);
236     return s.substr(0, i);
237   }
238
239   // FIXME: wrap at line_length, taking care of multibyte characters
240   if (overflow) *overflow = "";
241   return s;
242 }
243
244 void
245 Font::draw(const std::string& text, const Vector& pos_, FontAlignment alignment,
246            DrawingEffect drawing_effect, float alpha) const
247 {
248   float x = pos_.x;
249   float y = pos_.y;
250
251   std::string::size_type last = 0;
252   for(std::string::size_type i = 0;; ++i)
253     {
254       if (text[i] == '\n' || i == text.size())
255         {
256           std::string temp = text.substr(last, i - last);
257
258           // calculate X positions based on the alignment type
259           Vector pos = Vector(x, y);
260
261           if(alignment == ALIGN_CENTER)
262             pos.x -= get_text_width(temp) / 2;
263           else if(alignment == ALIGN_RIGHT)
264             pos.x -= get_text_width(temp);
265
266           // Cast font position to integer to get a clean drawing result and
267           // no bluring as we would get with subpixel positions
268           pos.x = static_cast<int>(pos.x);
269
270           draw_text(temp, pos, drawing_effect, alpha);
271
272           if (i == text.size())
273             break;
274
275           y += char_height + 2;
276           last = i + 1;
277         }
278     }
279 }
280
281 void
282 Font::draw_text(const std::string& text, const Vector& pos,
283                 DrawingEffect drawing_effect, float alpha) const
284 {
285   if(shadowsize > 0)
286     {
287       // FIXME: shadow_glyph_surface and glyph_surface do currently
288       // share the same glyph array, this is incorrect and should be
289       // fixed, it is however hardly noticable
290       draw_chars(shadow_glyph_surface, text, pos + Vector(shadowsize, shadowsize),
291                  drawing_effect, alpha);
292     }
293
294   draw_chars(glyph_surface, text, pos, drawing_effect, alpha);
295 }
296
297 int
298 Font::chr2glyph(uint32_t chr) const
299 {
300   int glyph_index = chr - first_char;
301
302   // we don't have the control chars 0x80-0xa0 in the font
303   if (chr >= 0x80) { // non-ascii character
304     glyph_index -= 32;
305     if(chr <= 0xa0) {
306       log_debug << "Unsupported utf-8 character '" << chr << "' found" << std::endl;
307       glyph_index = 0;
308     }
309   }
310
311   if(glyph_index < 0 || glyph_index >= (int) char_count) {
312     log_debug << "Unsupported utf-8 character found" << std::endl;
313     glyph_index = 0;
314   }
315
316   return glyph_index;
317 }
318
319 void
320 Font::draw_chars(Surface* pchars, const std::string& text, const Vector& pos,
321                  DrawingEffect drawing_effect, float alpha) const
322 {
323   Vector p = pos;
324
325   for(UTF8Iterator it(text); !it.done(); ++it)
326     {
327       int font_index = chr2glyph(*it);
328
329       if(*it == '\n')
330         {
331           p.x = pos.x;
332           p.y += char_height + 2;
333         }
334       else if(*it == ' ')
335         {
336           p.x += glyphs[font_index].advance;
337         }
338       else
339         {
340           const Glyph& glyph = glyphs[font_index];
341           pchars->draw_part(glyph.rect.get_left(),
342                             glyph.rect.get_top(),
343                             p.x + glyph.offset.y,
344                             p.y + glyph.offset.y,
345                             glyph.rect.get_width(), glyph.rect.get_height(),
346                             alpha, drawing_effect);
347           p.x += glyphs[font_index].advance;
348         }
349     }
350 }
351
352
353 namespace {
354
355 /**
356  * 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
357  */
358 bool has_multibyte_mark(unsigned char c) {
359   return ((c & 0300) == 0200);
360 }
361
362 /**
363  * gets unicode character at byte position @a p of UTF-8 encoded @a
364  * text, then advances @a p to the next character.
365  *
366  * @throws std::runtime_error if decoding fails.
367  * See unicode standard section 3.10 table 3-5 and 3-6 for details.
368  */
369 uint32_t decode_utf8(const std::string& text, size_t& p)
370 {
371   uint32_t c1 = (unsigned char) text[p+0];
372
373   if (has_multibyte_mark(c1)) std::runtime_error("Malformed utf-8 sequence");
374
375   if ((c1 & 0200) == 0000) {
376     // 0xxx.xxxx: 1 byte sequence
377     p+=1;
378     return c1;
379   }
380   else if ((c1 & 0340) == 0300) {
381     // 110x.xxxx: 2 byte sequence
382     if(p+1 >= text.size()) throw std::range_error("Malformed utf-8 sequence");
383     uint32_t c2 = (unsigned char) text[p+1];
384     if (!has_multibyte_mark(c2)) throw std::runtime_error("Malformed utf-8 sequence");
385     p+=2;
386     return (c1 & 0037) << 6 | (c2 & 0077);
387   }
388   else if ((c1 & 0360) == 0340) {
389     // 1110.xxxx: 3 byte sequence
390     if(p+2 >= text.size()) throw std::range_error("Malformed utf-8 sequence");
391     uint32_t c2 = (unsigned char) text[p+1];
392     uint32_t c3 = (unsigned char) text[p+2];
393     if (!has_multibyte_mark(c2)) throw std::runtime_error("Malformed utf-8 sequence");
394     if (!has_multibyte_mark(c3)) throw std::runtime_error("Malformed utf-8 sequence");
395     p+=3;
396     return (c1 & 0017) << 12 | (c2 & 0077) << 6 | (c3 & 0077);
397   }
398   else if ((c1 & 0370) == 0360) {
399     // 1111.0xxx: 4 byte sequence
400     if(p+3 >= text.size()) throw std::range_error("Malformed utf-8 sequence");
401     uint32_t c2 = (unsigned char) text[p+1];
402     uint32_t c3 = (unsigned char) text[p+2];
403     uint32_t c4 = (unsigned char) text[p+4];
404     if (!has_multibyte_mark(c2)) throw std::runtime_error("Malformed utf-8 sequence");
405     if (!has_multibyte_mark(c3)) throw std::runtime_error("Malformed utf-8 sequence");
406     if (!has_multibyte_mark(c4)) throw std::runtime_error("Malformed utf-8 sequence");
407     p+=4;
408     return (c1 & 0007) << 18 | (c2 & 0077) << 12 | (c3 & 0077) << 6 | (c4 & 0077);
409   }
410   throw std::runtime_error("Malformed utf-8 sequence");
411 }
412
413 } // namespace