- added support for variable width fonts (not fully finished, needs some more cleanup...
[supertux.git] / src / video / font.cpp
1 //  $Id$
2 //
3 //  SuperTux
4 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19
20 #include <config.h>
21
22 #include <cstdlib>
23 #include <cstring>
24 #include <stdexcept>
25
26 #include <SDL_image.h>
27 #include "physfs/physfs_sdl.hpp"
28
29 #include "lisp/parser.hpp"
30 #include "lisp/lisp.hpp"
31 #include "screen.hpp"
32 #include "font.hpp"
33 #include "drawing_context.hpp"
34 #include "log.hpp"
35
36 namespace {
37 bool     has_multibyte_mark(unsigned char c);
38 uint32_t decode_utf8(const std::string& text, size_t& p);
39
40 bool vline_empty(SDL_Surface* surface, int x, int start_y, int end_y, Uint8 threshold)
41 {
42   Uint8* pixels = (Uint8*)surface->pixels;
43
44   for(int y = start_y; y < end_y; ++y)
45     {
46       const Uint8& p = pixels[surface->pitch*y + x*surface->format->BytesPerPixel + 3];
47       if (p > threshold)
48         {
49           return false;
50         }
51     }
52   return true;
53 }
54 } // namespace
55
56 Font::Font(const std::string& file, const std::string& shadowfile,
57            int w, int h, int shadowsize)
58   : glyph_surface(0), shadow_chars(0),
59     char_width(w), char_height(h), 
60     shadowsize(shadowsize)
61 {
62   glyph_surface = new Surface(file);
63   shadow_chars  = new Surface(shadowfile);
64
65   first_char = 32;
66   char_count = ((int) glyph_surface->get_height() / char_height) * 16;
67
68   for(uint32_t i = 0; i < char_count; ++i)
69     {
70       float x = (i % 16) * char_width;
71       float y = (i / 16) * char_height;
72       glyphs.push_back(Rect(x, y,
73                             x + char_width, y + char_height));
74     }
75 }
76
77 Font::Font(const std::string& filename, int char_width_, int char_height_)
78   : glyph_surface(0), shadow_chars(0), 
79     char_width(char_width_), char_height(char_height_), 
80     shadowsize(0)
81 {
82   glyph_surface = new Surface(filename);
83
84   first_char = 32;
85   char_count = ((int) glyph_surface->get_height() / char_height) * 16;
86
87   // Load the surface into RAM and scan the pixel data for characters
88   SDL_Surface* surface = IMG_Load_RW(get_physfs_SDLRWops(filename), 1);
89   if(surface == NULL) {
90     std::ostringstream msg;
91     msg << "Couldn't load image '" << filename << "' :" << SDL_GetError();
92     throw std::runtime_error(msg.str());
93   }
94
95   SDL_LockSurface(surface);
96
97   for(uint32_t i = 0; i < char_count; ++i)
98     {
99       int x = (i % 16) * char_width;
100       int y = (i / 16) * char_height;
101
102       int left = x;
103       while (left < x + char_width &&
104              vline_empty(surface, left, y, y + char_height, 0))
105         left += 1;
106
107       int right = x + char_width - 1;
108       while (right > left && 
109              vline_empty(surface, right, y, y + char_height, 0))
110         right -= 1;
111
112       if (left <= right)
113         glyphs.push_back(Rect(left,  y,
114                               right+1, y + char_height));
115       else // glyph is completly transparent
116         glyphs.push_back(Rect(x,  y,
117                               x + char_width, y + char_height));
118
119     }
120   
121   SDL_UnlockSurface(surface);
122
123   SDL_FreeSurface(surface);
124 }
125
126 Font::~Font()
127 {
128   delete glyph_surface;
129   delete shadow_chars;
130 }
131
132 float
133 Font::get_text_width(const std::string& text) const
134 {
135   float curr_width = 0;
136   float last_width = 0;
137
138  // FIXME: add UTF8 decode here
139   for(std::string::size_type i = 0; i < text.size(); ++i)
140     {
141       uint32_t chr = text[i];
142       if (chr == '\n')
143         {
144           last_width = std::max(last_width, curr_width);
145           curr_width = 0;
146         }
147       else
148         {
149           curr_width += glyphs[static_cast<unsigned char>(text[i])].get_width() + 1;
150         }
151     }
152
153   return std::max(curr_width, last_width);
154 }
155
156 float
157 Font::get_text_height(const std::string& text) const
158 {
159   std::string::size_type text_height = char_height;
160   
161   for(std::string::size_type i = 0; i < text.size(); ++i)
162     {
163       if (i == '\n')
164         text_height += char_height + 2;
165     }
166
167   return text_height;
168 }
169
170 float
171 Font::get_height() const
172 {
173   return char_height; 
174 }
175
176 std::string
177 Font::wrap_to_chars(const std::string& s, int line_length, std::string* overflow)
178 {
179   // if text is already smaller, return full text
180   if ((int)s.length() <= line_length) {
181     if (overflow) *overflow = "";
182     return s;
183   }
184
185   // if we can find a whitespace character to break at, return text up to this character
186   int i = line_length;
187   while ((i > 0) && (s[i] != ' ')) i--;
188   if (i > 0) {
189     if (overflow) *overflow = s.substr(i+1);
190     return s.substr(0, i);
191   }
192
193   // FIXME: wrap at line_length, taking care of multibyte characters
194   if (overflow) *overflow = "";
195   return s;
196 }
197
198 void
199 Font::draw(const std::string& text, const Vector& pos_, FontAlignment alignment,
200            DrawingEffect drawing_effect, float alpha) const
201 {
202   /* Cut lines changes into seperate strings, needed to support center/right text
203      alignments with break lines.
204      Feel free to replace this hack with a more elegant solution
205   */
206   char temp[1024];
207   std::string::size_type l, i, y;
208   bool done = false;
209   i = y = 0;
210
211   while(!done) {
212     l = text.find("\n", i);
213     if(l == std::string::npos) {
214       l = text.size();
215       done = true;
216     }
217
218     if(l > sizeof(temp)-1)
219       l = sizeof(temp)-1;
220
221     temp[text.copy(temp, l - i, i)] = '\0';
222
223     // calculate X positions based on the alignment type
224     Vector pos = Vector(pos_);
225     if(alignment == CENTER_ALLIGN)
226       pos.x -= get_text_width(temp) / 2;
227     else if(alignment == RIGHT_ALLIGN)
228       pos.x -= get_text_width(temp);
229
230     draw_text(temp, pos + Vector(0,y), drawing_effect, alpha);
231
232     i = l+1;
233     y += char_height + 2;
234   }
235 }
236
237 void
238 Font::draw_text(const std::string& text, const Vector& pos,
239                 DrawingEffect drawing_effect, float alpha) const
240 {
241   if(shadowsize > 0)
242     draw_chars(shadow_chars, text, pos + Vector(shadowsize, shadowsize),
243                drawing_effect, alpha);
244
245   draw_chars(glyph_surface, text, pos, drawing_effect, alpha);
246 }
247
248 void
249 Font::draw_chars(Surface* pchars, const std::string& text, const Vector& pos,
250                  DrawingEffect drawing_effect, float alpha) const
251 {
252   Vector p = pos;
253   size_t i = 0;
254   while(i < text.size()) {
255     uint32_t c;
256     try {
257       c = decode_utf8(text, i); // FIXME: this seems wrong, since when incrementing i by 
258     }
259     catch (std::runtime_error) {
260      log_debug << "Malformed utf-8 sequence beginning with " << *((uint32_t*)(text.c_str() + i)) << " found " << std::endl;
261      c = 0;
262      i++;
263     }
264
265     int font_index = c - first_char;
266
267     // a non-printable character?
268     if(c == '\n') {
269       p.x = pos.x;
270       p.y += char_height + 2;
271       continue;
272     }
273     if(c == ' ') {
274       p.x += glyphs[font_index].get_width();
275       continue;
276     }
277
278     // we don't have the control chars 0x80-0xa0 in the font
279     if (c >= 0x80) {
280       font_index -= 32;
281       if(c <= 0xa0) {
282         log_debug << "Unsupported utf-8 character '" << c << "' found" << std::endl;
283         font_index = 0;
284       }
285     }
286
287     if(font_index < 0 || font_index >= (int) char_count) {
288       log_debug << "Unsupported utf-8 character found" << std::endl;
289       font_index = 0;
290     }
291
292     const Rect& glyph = glyphs[font_index];
293     pchars->draw_part(glyph.get_left(), glyph.get_top(),
294                       p.x, p.y,
295                       glyph.get_width(), glyph.get_height(),
296                       alpha, drawing_effect);
297     p.x += glyphs[font_index].get_width();
298   }
299 }
300
301
302 namespace {
303
304 /**
305  * 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
306  */
307 bool has_multibyte_mark(unsigned char c) {
308   return ((c & 0300) == 0200);
309 }
310
311 /**
312  * gets unicode character at byte position @a p of UTF-8 encoded @a text, then advances @a p to the next character.
313  * @throws std::runtime_error if decoding fails.
314  * See unicode standard section 3.10 table 3-5 and 3-6 for details.
315  */
316 uint32_t decode_utf8(const std::string& text, size_t& p)
317 {
318   uint32_t c1 = (unsigned char) text[p+0];
319
320   if (has_multibyte_mark(c1)) std::runtime_error("Malformed utf-8 sequence");
321
322   if ((c1 & 0200) == 0000) {
323     // 0xxx.xxxx: 1 byte sequence
324     p+=1;
325     return c1;
326   }
327   else if ((c1 & 0340) == 0300) {
328     // 110x.xxxx: 2 byte sequence
329     if(p+1 >= text.size()) throw std::range_error("Malformed utf-8 sequence");
330     uint32_t c2 = (unsigned char) text[p+1];
331     if (!has_multibyte_mark(c2)) throw std::runtime_error("Malformed utf-8 sequence");
332     p+=2;
333     return (c1 & 0037) << 6 | (c2 & 0077);
334   }
335   else if ((c1 & 0360) == 0340) {
336     // 1110.xxxx: 3 byte sequence
337     if(p+2 >= text.size()) throw std::range_error("Malformed utf-8 sequence");
338     uint32_t c2 = (unsigned char) text[p+1];
339     uint32_t c3 = (unsigned char) text[p+2];
340     if (!has_multibyte_mark(c2)) throw std::runtime_error("Malformed utf-8 sequence");
341     if (!has_multibyte_mark(c3)) throw std::runtime_error("Malformed utf-8 sequence");
342     p+=3;
343     return (c1 & 0017) << 12 | (c2 & 0077) << 6 | (c3 & 0077);
344   }
345   else if ((c1 & 0370) == 0360) {
346     // 1111.0xxx: 4 byte sequence
347     if(p+3 >= text.size()) throw std::range_error("Malformed utf-8 sequence");
348     uint32_t c2 = (unsigned char) text[p+1];
349     uint32_t c3 = (unsigned char) text[p+2];
350     uint32_t c4 = (unsigned char) text[p+4];
351     if (!has_multibyte_mark(c2)) throw std::runtime_error("Malformed utf-8 sequence");
352     if (!has_multibyte_mark(c3)) throw std::runtime_error("Malformed utf-8 sequence");
353     if (!has_multibyte_mark(c4)) throw std::runtime_error("Malformed utf-8 sequence");
354     p+=4;
355     return (c1 & 0007) << 18 | (c2 & 0077) << 12 | (c3 & 0077) << 6 | (c4 & 0077);
356   }
357   throw std::runtime_error("Malformed utf-8 sequence");
358 }
359
360 } // namespace