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