marked-up_text.cpp

Go to the documentation of this file.
00001 /* $Id: marked-up_text.cpp 53965 2012-04-22 13:52:23Z mordante $ */
00002 /*
00003    Copyright (C) 2003 - 2012 by David White <dave@whitevine.net>
00004    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License as published by
00008    the Free Software Foundation; either version 2 of the License, or
00009    (at your option) any later version.
00010    This program is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY.
00012 
00013    See the COPYING file for more details.
00014 */
00015 
00016 /**
00017  * @file
00018  * Support for simple markup in text (fonts, colors, images).
00019  * E.g. "@Victory" will be shown in green.
00020  */
00021 
00022 #define GETTEXT_DOMAIN "wesnoth-lib"
00023 
00024 #include "global.hpp"
00025 
00026 #include "font.hpp"
00027 #include "gettext.hpp"
00028 #include "marked-up_text.hpp"
00029 #include "serialization/string_utils.hpp"
00030 #include "video.hpp"
00031 #include "wml_exception.hpp"
00032 
00033 namespace font {
00034 
00035 // NOTE: if you add more markup characters below, you'll need to update
00036 // the list in campaign_server.cpp (illegal_markup_chars) to blacklist
00037 // them for add-on names and titles.
00038 
00039 const char LARGE_TEXT='*', SMALL_TEXT='`',
00040            BOLD_TEXT='~',  NORMAL_TEXT='{',
00041            NULL_MARKUP='^',
00042            BLACK_TEXT='}', GRAY_TEXT='|',
00043            GOOD_TEXT='@',  BAD_TEXT='#',
00044            GREEN_TEXT='@', RED_TEXT='#',
00045            COLOR_TEXT='<', IMAGE='&';
00046 
00047 const std::string weapon = "<245,230,193>",
00048         weapon_details = "<166,146,117>",
00049         unit_type = "<245,230,193>",
00050         race = "<166,146,117>";
00051 
00052 const SDL_Color
00053     weapon_color = { 245, 230, 193, 255 },
00054     good_dmg_color = { 130, 240, 50, 255 },
00055     bad_dmg_color = { 250, 140, 80, 255 },
00056     weapon_details_color = { 166, 146, 117, 255 },
00057     unit_type_color = { 245, 230, 193, 255 },
00058     race_color = { 166, 146, 117, 255 };
00059 
00060 const std::string weapon_numbers_sep = "–", weapon_details_sep = "–";
00061 
00062 std::string::const_iterator parse_markup(std::string::const_iterator i1,
00063                                                 std::string::const_iterator i2,
00064                                                 int* font_size,
00065                                                 SDL_Color* color, int* style)
00066 {
00067     while(i1 != i2) {
00068         switch(*i1) {
00069         case '\\':
00070             // This must either be a quoted special character or a
00071             // quoted backslash - either way, remove leading backslash
00072             break;
00073         case BAD_TEXT:
00074             if (color) *color = BAD_COLOR;
00075             break;
00076         case GOOD_TEXT:
00077             if (color) *color = GOOD_COLOR;
00078             break;
00079         case NORMAL_TEXT:
00080             if (color) *color = NORMAL_COLOR;
00081             break;
00082         case BLACK_TEXT:
00083             if (color) *color = BLACK_COLOR;
00084             break;
00085         case GRAY_TEXT:
00086             if (color) *color = GRAY_COLOR;
00087             break;
00088         case LARGE_TEXT:
00089             if (font_size) *font_size += 2;
00090             break;
00091         case SMALL_TEXT:
00092             if (font_size) *font_size -= 2;
00093             break;
00094         case BOLD_TEXT:
00095             if (style) *style |= TTF_STYLE_BOLD;
00096             break;
00097         case NULL_MARKUP:
00098             return i1+1;
00099         case COLOR_TEXT:
00100             {
00101                 std::string::const_iterator start = i1;
00102                 // Very primitive parsing for rgb value
00103                 // should look like <213,14,151>
00104                 ++i1;
00105                 Uint8 red=0, green=0, blue=0, temp=0;
00106                 while (i1 != i2 && *i1 >= '0' && *i1<='9') {
00107                     temp*=10;
00108                     temp += lexical_cast<int, char>(*i1);
00109                     ++i1;
00110                 }
00111                 red=temp;
00112                 temp=0;
00113                 if (i1 != i2 && ',' == (*i1)) {
00114                     ++i1;
00115                     while(i1 != i2 && *i1 >= '0' && *i1<='9'){
00116                         temp*=10;
00117                         temp += lexical_cast<int, char>(*i1);
00118                         ++i1;
00119                     }
00120                     green=temp;
00121                     temp=0;
00122                 }
00123                 if (i1 != i2 && ',' == (*i1)) {
00124                     ++i1;
00125                     while(i1 != i2 && *i1 >= '0' && *i1<='9'){
00126                         temp*=10;
00127                         temp += lexical_cast<int, char>(*i1);
00128                         ++i1;
00129                     }
00130                 }
00131                 blue=temp;
00132                 if (i1 != i2 && '>' == (*i1)) {
00133                     SDL_Color temp_color = {red, green, blue, 0};
00134                     if (color) *color = temp_color;
00135                 } else {
00136                     // stop parsing and do not consume any chars
00137                     return start;
00138                 }
00139                 if (i1 == i2) return i1;
00140                 break;
00141             }
00142         default:
00143             return i1;
00144         }
00145         ++i1;
00146     }
00147     return i1;
00148 }
00149 
00150 std::string del_tags(const std::string& text){
00151     int ignore_int;
00152     SDL_Color ignore_color;
00153     std::vector<std::string> lines = utils::split(text, '\n', 0);
00154     std::vector<std::string>::iterator line;
00155     for(line = lines.begin(); line != lines.end(); ++line) {
00156         std::string::const_iterator i1 = line->begin(),
00157             i2 = line->end();
00158         *line = std::string(parse_markup(i1,i2,&ignore_int,&ignore_color,&ignore_int),i2);
00159     }
00160     return utils::join(lines, "\n");
00161 }
00162 
00163 std::string color2markup(const SDL_Color &color)
00164 {
00165     std::stringstream markup;
00166     // The RGB of SDL_Color are Uint8, we need to cast them to int.
00167     // Without cast, it gives their char equivalent.
00168     markup << "<"
00169            << static_cast<int>(color.r) << ","
00170            << static_cast<int>(color.g) << ","
00171            << static_cast<int>(color.b) << ">";
00172     return markup.str();
00173 }
00174 
00175 std::string color2hexa(const SDL_Color &color)
00176 {
00177     char buf[7];
00178     sprintf(buf, "%02x%02x%02x", color.r, color.g, color.b);
00179     return buf;
00180 }
00181 
00182 std::string span_color(const SDL_Color &color)
00183 {
00184     return "<span foreground=\"#" + font::color2hexa(color) + "\">";
00185 }
00186 
00187 SDL_Rect text_area(const std::string& text, int size, int style)
00188 {
00189     const SDL_Rect area = {0,0,10000,10000};
00190     return draw_text(NULL, area, size, font::NORMAL_COLOR, text, 0, 0, false, style);
00191 }
00192 
00193 SDL_Rect draw_text(surface dst, const SDL_Rect& area, int size,
00194                    const SDL_Color& color, const std::string& txt,
00195                    int x, int y, bool use_tooltips, int style)
00196 {
00197     // Make sure there's always at least a space,
00198     // so we can ensure that we can return a rectangle for height
00199     static const std::string blank_text(" ");
00200     const std::string& text = txt.empty() ? blank_text : txt;
00201 
00202     SDL_Rect res;
00203     res.x = x;
00204     res.y = y;
00205     res.w = 0;
00206     res.h = 0;
00207 
00208     std::string::const_iterator i1 = text.begin();
00209     std::string::const_iterator i2 = std::find(i1,text.end(),'\n');
00210     for(;;) {
00211         SDL_Color col = color;
00212         int sz = size;
00213         int text_style = style;
00214 
00215         i1 = parse_markup(i1,i2,&sz,&col,&text_style);
00216 
00217         if(i1 != i2) {
00218             std::string new_string = utils::unescape(std::string(i1, i2));
00219 
00220             const SDL_Rect rect = draw_text_line(dst, area, sz, col, new_string, x, y, use_tooltips, text_style);
00221             if(rect.w > res.w) {
00222                 res.w = rect.w;
00223             }
00224 
00225             res.h += rect.h;
00226             y += rect.h;
00227         }
00228 
00229         if(i2 == text.end()) {
00230             break;
00231         }
00232 
00233         i1 = i2+1;
00234         i2 = std::find(i1,text.end(),'\n');
00235     }
00236 
00237     return res;
00238 }
00239 
00240 SDL_Rect draw_text(CVideo* gui, const SDL_Rect& area, int size,
00241                    const SDL_Color& color, const std::string& txt,
00242                    int x, int y, bool use_tooltips, int style)
00243 {
00244     return draw_text(gui != NULL ? gui->getSurface() : NULL, area, size, color, txt, x, y, use_tooltips, style);
00245 }
00246 
00247 bool is_format_char(char c)
00248 {
00249     switch(c) {
00250     case LARGE_TEXT:
00251     case SMALL_TEXT:
00252     case GOOD_TEXT:
00253     case BAD_TEXT:
00254     case NORMAL_TEXT:
00255     case BLACK_TEXT:
00256     case GRAY_TEXT:
00257     case BOLD_TEXT:
00258     case NULL_MARKUP:
00259         return true;
00260     default:
00261         return false;
00262     }
00263 }
00264 
00265 bool is_cjk_char(const wchar_t c)
00266 {
00267     /**
00268      * You can check these range at http://unicode.org/charts/
00269      * see the "East Asian Scripts" part.
00270      * Notice that not all characters in that part is still in use today, so don't list them all here.
00271      * Below are characters that I guess may be used in wesnoth translations.
00272      */
00273 
00274     // cast to silence a windows warning (uses only 16bit for wchar_t)
00275     const unsigned int ch = static_cast<unsigned int>(c);
00276 
00277     //FIXME add range from Japanese-specific and Korean-specific section if you know the characters are used today.
00278 
00279     if (ch < 0x2e80) return false; // shorcut for common non-CJK
00280 
00281     return
00282         //Han Ideographs: all except Supplement
00283         (ch >= 0x4e00 && ch < 0x9fcf) ||
00284         (ch >= 0x3400 && ch < 0x4dbf) ||
00285         (ch >= 0x20000 && ch < 0x2a6df) ||
00286         (ch >= 0xf900 && ch < 0xfaff) ||
00287         (ch >= 0x3190 && ch < 0x319f) ||
00288 
00289         //Radicals: all except Ideographic Description
00290         (ch >= 0x2e80 && ch < 0x2eff) ||
00291         (ch >= 0x2f00 && ch < 0x2fdf) ||
00292         (ch >= 0x31c0 && ch < 0x31ef) ||
00293 
00294         //Chinese-specific: Bopomofo and Bopomofo Extended
00295         (ch >= 0x3104 && ch < 0x312e) ||
00296         (ch >= 0x31a0 && ch < 0x31bb) ||
00297 
00298         //Yi-specific: Yi Radicals, Yi Syllables
00299         (ch >= 0xa490 && ch < 0xa4c7) ||
00300         (ch >= 0xa000 && ch < 0xa48d) ||
00301 
00302         //Japanese-specific: Hiragana, Katakana, Kana Supplement
00303         (ch >= 0x3040 && ch <= 0x309f) ||
00304         (ch >= 0x30a0 && ch <= 0x30ff) ||
00305         (ch >= 0x1b000 && ch <= 0x1b001) ||
00306 
00307         //Ainu-specific: Katakana Phonetic Extensions
00308         (ch >= 0x31f0 && ch <= 0x31ff) ||
00309 
00310         //Korean-specific: Hangul Syllables, Hangul Jamo, Hangul Jamo Extended-A, Hangul Jamo Extended-B
00311         (ch >= 0xac00 && ch < 0xd7af) ||
00312         (ch >= 0x1100 && ch <= 0x11ff) ||
00313         (ch >= 0xa960 && ch <= 0xa97c) ||
00314         (ch >= 0xd7b0 && ch <= 0xd7fb) ||
00315 
00316         //CJK Symbols and Punctuation
00317         (ch >= 0x3000 && ch < 0x303f) ||
00318 
00319         //Halfwidth and Fullwidth Forms
00320         (ch >= 0xff00 && ch < 0xffef);
00321 }
00322 static void cut_word(std::string& line, std::string& word, int font_size, int style, int max_width)
00323 {
00324     std::string tmp = line;
00325     utils::utf8_iterator tc(word);
00326     bool first = true;
00327 
00328     for(;tc != utils::utf8_iterator::end(word); ++tc) {
00329         tmp.append(tc.substr().first, tc.substr().second);
00330         SDL_Rect tsize = line_size(tmp, font_size, style);
00331         if(tsize.w > max_width) {
00332             const std::string& w = word;
00333             if(line.empty() && first) {
00334                 line += std::string(w.begin(), tc.substr().second);
00335                 word = std::string(tc.substr().second, w.end());
00336             } else {
00337                 line += std::string(w.begin(), tc.substr().first);
00338                 word = std::string(tc.substr().first, w.end());
00339             }
00340             break;
00341         }
00342         first = false;
00343     }
00344 }
00345 
00346 namespace {
00347 
00348 /*
00349  * According to Kinsoku-Shori, Japanese rules about line-breaking:
00350  *
00351  * * the following characters cannot begin a line (so we will never break before them):
00352  * 、。,.)〕]}〉》」』】’”ゝゞヽヾ々?!:;ぁぃぅぇぉゃゅょゎァィゥェォャュョヮっヵッヶ・…ー
00353  *
00354  * * the following characters cannot end a line (so we will never break after them):
00355  * (〔[{〈《「『【‘“
00356  *
00357  * Unicode range that concerns word wrap for Chinese:
00358  *   全角ASCII、全角中英文标点 (Fullwidth Character for ASCII, English punctuations and part of Chinese punctuations)
00359  *   http://www.unicode.org/charts/PDF/UFF00.pdf
00360  *   CJK 标点符号 (CJK punctuations)
00361  *   http://www.unicode.org/charts/PDF/U3000.pdf
00362  */
00363 inline bool no_break_after(const wchar_t ch)
00364 {
00365     return
00366         /**
00367          * don't break after these Japanese characters
00368          */
00369         ch == 0x2018 || ch == 0x201c || ch == 0x3008 || ch == 0x300a || ch == 0x300c ||
00370         ch == 0x300e || ch == 0x3010 || ch == 0x3014 || ch == 0xff08 || ch == 0xff3b ||
00371         ch == 0xff5b ||
00372 
00373         /**
00374          * FIXME don't break after these Korean characters
00375          */
00376 
00377         /**
00378          * don't break after these Chinese characters
00379          * contains left side of different kinds of brackets and quotes
00380          */
00381         ch == 0x3016 || ch == 0x301a || ch == 0x301d;
00382 }
00383 
00384 inline bool no_break_before(const wchar_t ch)
00385 {
00386     return
00387         /**
00388          * don't break before these Japanese characters
00389          */
00390         ch == 0x2019 || ch == 0x201d || ch == 0x2026 || ch == 0x3001 || ch == 0x3002 ||
00391         ch == 0x3005 || ch == 0x3009 || ch == 0x300b || ch == 0x300d || ch == 0x300f ||
00392         ch == 0x3011 || ch == 0x3015 || ch == 0x3041 || ch == 0x3043 || ch == 0x3045 ||
00393         ch == 0x3047 || ch == 0x3049 || ch == 0x3063 || ch == 0x3083 || ch == 0x3085 ||
00394         ch == 0x3087 || ch == 0x308e || ch == 0x309d || ch == 0x309e || ch == 0x30a1 ||
00395         ch == 0x30a3 || ch == 0x30a5 || ch == 0x30a7 || ch == 0x30a9 || ch == 0x30c3 ||
00396         ch == 0x30e3 || ch == 0x30e5 || ch == 0x30e7 || ch == 0x30ee || ch == 0x30f5 ||
00397         ch == 0x30f6 || ch == 0x30fb || ch == 0x30fc || ch == 0x30fd || ch == 0x30fe ||
00398         ch == 0xff01 || ch == 0xff09 || ch == 0xff0c || ch == 0xff0e || ch == 0xff1a ||
00399         ch == 0xff1b || ch == 0xff1f || ch == 0xff3d || ch == 0xff5d ||
00400 
00401         // Small katakana used in Ainu:
00402         ch == 0x31f0 || ch == 0x31f1 || ch == 0x31f2 || ch == 0x31f3 || ch == 0x31f4 ||
00403         ch == 0x31f5 || ch == 0x31f6 || ch == 0x31f7 || ch == 0x31f8 || ch == 0x31f9 ||
00404         ch == 0x31fa || ch == 0x31fb || ch == 0x31fc || ch == 0x31fd || ch == 0x31fe ||
00405         ch == 0x31ff ||
00406 
00407         /**
00408          * FIXME don't break before these Korean characters
00409          */
00410 
00411         /**
00412          * don't break before these Chinese characters
00413          * contains
00414          *   many Chinese punctuations that should not start a line
00415          *   and right side of different kinds of brackets, quotes
00416          */
00417         ch == 0x301c || ch == 0xff0d || ch == 0xff64 || ch == 0xff65 || ch == 0x3017 ||
00418         ch == 0x301b || ch == 0x301e;
00419 }
00420 
00421 inline bool break_before(const wchar_t ch)
00422 {
00423     if(no_break_before(ch))
00424         return false;
00425 
00426     return is_cjk_char(ch);
00427 }
00428 
00429 inline bool break_after(const wchar_t ch)
00430 {
00431     if(no_break_after(ch))
00432         return false;
00433 
00434     return is_cjk_char(ch);
00435 }
00436 
00437 } // end of anon namespace
00438 
00439 std::string word_wrap_text(const std::string& unwrapped_text, int font_size,
00440     int max_width, int max_height, int max_lines, bool partial_line)
00441 {
00442     VALIDATE(max_width > 0, _("The maximum text width is less than 1."));
00443 
00444     utils::utf8_iterator ch(unwrapped_text);
00445     std::string current_word;
00446     std::string current_line;
00447     size_t line_width = 0;
00448     size_t current_height = 0;
00449     bool line_break = false;
00450     bool first = true;
00451     bool start_of_line = true;
00452     std::string wrapped_text;
00453     std::string format_string;
00454     SDL_Color color;
00455     int font_sz = font_size;
00456     int style = TTF_STYLE_NORMAL;
00457     utils::utf8_iterator end = utils::utf8_iterator::end(unwrapped_text);
00458 
00459     while(1) {
00460         if(start_of_line) {
00461             line_width = 0;
00462             format_string.clear();
00463             while(ch != end && *ch < static_cast<wchar_t>(0x100)
00464                     && is_format_char(*ch) && !ch.next_is_end()) {
00465 
00466                 format_string.append(ch.substr().first, ch.substr().second);
00467                 ++ch;
00468             }
00469             // We need to parse the special format characters
00470             // to give the proper font_size and style to line_size()
00471             font_sz = font_size;
00472             style = TTF_STYLE_NORMAL;
00473             parse_markup(format_string.begin(),format_string.end(),&font_sz,&color,&style);
00474             current_line.clear();
00475             start_of_line = false;
00476         }
00477 
00478         // If there is no current word, get one
00479         if(current_word.empty() && ch == end) {
00480             break;
00481         } else if(current_word.empty()) {
00482             if(*ch == ' ' || *ch == '\n') {
00483                 current_word = *ch;
00484                 ++ch;
00485             } else {
00486                 wchar_t previous = 0;
00487                 for(;ch != utils::utf8_iterator::end(unwrapped_text) &&
00488                         *ch != ' ' && *ch != '\n'; ++ch) {
00489 
00490                     if(!current_word.empty() &&
00491                             break_before(*ch) &&
00492                             !no_break_after(previous))
00493                         break;
00494 
00495                     if(!current_word.empty() &&
00496                             break_after(previous) &&
00497                             !no_break_before(*ch))
00498                         break;
00499 
00500                     current_word.append(ch.substr().first, ch.substr().second);
00501 
00502                     previous = *ch;
00503                 }
00504             }
00505         }
00506 
00507         if(current_word == "\n") {
00508             line_break = true;
00509             current_word.clear();
00510             start_of_line = true;
00511         } else {
00512 
00513             const size_t word_width = line_size(current_word, font_sz, style).w;
00514 
00515             line_width += word_width;
00516 
00517             if(static_cast<long>(line_width) > max_width) {
00518                 if (!partial_line && static_cast<long>(word_width) > max_width) {
00519                     cut_word(current_line,
00520                         current_word, font_sz, style, max_width);
00521                 }
00522                 if(current_word == " ")
00523                     current_word = "";
00524                 line_break = true;
00525             } else {
00526                 current_line += current_word;
00527                 current_word = "";
00528             }
00529         }
00530 
00531         if(line_break || (current_word.empty() && ch == end)) {
00532             SDL_Rect size = line_size(current_line, font_sz, style);
00533             if(max_height > 0 && current_height + size.h >= size_t(max_height)) {
00534                 return wrapped_text;
00535             }
00536 
00537             if(!first) {
00538                 wrapped_text += '\n';
00539             }
00540 
00541             wrapped_text += format_string + current_line;
00542             current_line.clear();
00543             line_width = 0;
00544             current_height += size.h;
00545             line_break = false;
00546             first = false;
00547 
00548             if(--max_lines == 0) {
00549                 return wrapped_text;
00550             }
00551         }
00552     }
00553     return wrapped_text;
00554 }
00555 
00556 SDL_Rect draw_wrapped_text(CVideo* gui, const SDL_Rect& area, int font_size,
00557              const SDL_Color& color, const std::string& text,
00558              int x, int y, int max_width)
00559 {
00560     std::string wrapped_text = word_wrap_text(text, font_size, max_width);
00561     return font::draw_text(gui, area, font_size, color, wrapped_text, x, y, false);
00562 }
00563 
00564 } // end namespace font
00565 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

Generated by doxygen 1.7.1 on Fri May 25 2012 01:03:05 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs