marked-up_text.cpp

Go to the documentation of this file.
00001 /* $Id: marked-up_text.cpp 24829 2008-03-19 15:57:14Z brunowolff $ */
00002 /*
00003    Copyright (C) 2003 - 2008 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 version 2
00008    or at your option any later version.
00009    This program is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY.
00011 
00012    See the COPYING file for more details.
00013 */
00014 
00015 //! @file marked-up_text.cpp
00016 //! Support for simple markup in text (fonts, colors, images).
00017 //! E.g. "@Victory" will be shown in green.
00018 
00019 #include "global.hpp"
00020 
00021 #include "font.hpp"
00022 #include "gettext.hpp"
00023 #include "marked-up_text.hpp"
00024 #include "team.hpp"
00025 #include "video.hpp"
00026 #include "wml_exception.hpp"
00027 
00028 namespace font {
00029 
00030 const char LARGE_TEXT='*', SMALL_TEXT='`',
00031            BOLD_TEXT='~',  NORMAL_TEXT='{',
00032            NULL_MARKUP='^',
00033            BLACK_TEXT='}', GRAY_TEXT='|',
00034            GOOD_TEXT='@',  BAD_TEXT='#',
00035            GREEN_TEXT='@', RED_TEXT='#',
00036            COLOR_TEXT='<', IMAGE='&';
00037 
00038 //! Parses the markup-tags at the front of a string.
00039 static std::string::const_iterator parse_markup(std::string::const_iterator i1,
00040                                                 std::string::const_iterator i2,
00041                                                 int* font_size,
00042                                                 SDL_Color* colour, int* style)
00043 {
00044     if(font_size == NULL || colour == NULL) {
00045         return i1;
00046     }
00047 
00048     std::string::const_iterator i_start=i1;
00049     while(i1 != i2) {
00050         switch(*i1) {
00051         case '\\':
00052             // This must either be a quoted special character or a
00053             // quoted backslash - either way, remove leading backslash
00054             break;
00055         case BAD_TEXT:
00056             *colour = BAD_COLOUR;
00057             break;
00058         case GOOD_TEXT:
00059             *colour = GOOD_COLOUR;
00060             break;
00061         case NORMAL_TEXT:
00062             *colour = NORMAL_COLOUR;
00063             break;
00064         case BLACK_TEXT:
00065             *colour = BLACK_COLOUR;
00066             break;
00067         case GRAY_TEXT:
00068             *colour = GRAY_COLOUR;
00069             break;
00070         case LARGE_TEXT:
00071             *font_size += 2;
00072             break;
00073         case SMALL_TEXT:
00074             *font_size -= 2;
00075             break;
00076         case BOLD_TEXT:
00077             *style |= TTF_STYLE_BOLD;
00078             break;
00079         case NULL_MARKUP:
00080             return i1+1;
00081         case COLOR_TEXT:
00082           {
00083             //Very primitive parsing for rgb value
00084             //should look like <213,14,151>
00085             //but no checking on commas or end '>',
00086             //could be any non-# char
00087             ++i1;
00088             Uint8 red=0, green=0, blue=0, temp=0;
00089             while(i1 != i2 && *i1 >= '0' && *i1<='9'){
00090               temp*=10;
00091               temp += lexical_cast<int, char>(*i1);
00092               ++i1;
00093             }
00094             red=temp;
00095             temp=0;
00096             if(i1 != i2 && '>' != (*i1)){
00097               ++i1;
00098               while(i1 != i2 && *i1 >= '0' && *i1<='9'){
00099             temp*=10;
00100             temp += lexical_cast<int, char>(*i1);
00101             ++i1;
00102               }
00103               green=temp;
00104               temp=0;
00105             }
00106             if(i1 != i2 && '>' != (*i1)){
00107               ++i1;
00108               while(i1 != i2 && *i1 >= '0' && *i1<='9'){
00109             temp*=10;
00110             temp += lexical_cast<int, char>(*i1);
00111             ++i1;
00112               }
00113             }
00114             blue=temp;
00115             if(i1 != i2 && '>'==(*i1)){
00116               SDL_Color temp_color = {red,green,blue,0};
00117               (*colour) = temp_color;
00118             }
00119             if(i1 == i2) return i1;
00120             break;
00121           }
00122         default:
00123           return i1;
00124         }
00125         ++i1;
00126     }
00127     return i1;
00128 }
00129 
00130 
00131 //! Copy string, but without tags at the beginning
00132 std::string del_tags(const std::string& text){
00133     int ignore_int;
00134     SDL_Color ignore_color;
00135     std::vector<std::string> lines = utils::split(text, '\n', 0);
00136     std::vector<std::string>::iterator line;
00137     for(line = lines.begin(); line != lines.end(); ++line) {
00138         std::string::const_iterator i1 = line->begin(),
00139             i2 = line->end();
00140         *line = std::string(parse_markup(i1,i2,&ignore_int,&ignore_color,&ignore_int),i2); 
00141     }
00142     return utils::join(lines, '\n');
00143 }
00144 
00145 //! Copy string, but with NULL MARKUP tag at the beginning of each line
00146 std::string nullify_markup(const std::string& text) {
00147     std::vector<std::string> lines = utils::split(text, '\n', 0);
00148     std::vector<std::string>::iterator line;
00149     for(line = lines.begin(); line != lines.end(); ++line) {
00150         *line = std::string() + NULL_MARKUP + *line; 
00151     }
00152     return utils::join(lines, '\n');
00153 }
00154 
00155 
00156 //! Create string of color-markup, such as "<255,255,0>" for yellow.
00157 std::string color2markup(const SDL_Color color) {
00158     std::stringstream markup;
00159     // The RGB of SDL_Color are Uint8, we need to cast them to int.
00160     // Without cast, it gives their char equivalent.
00161     markup << "<" 
00162            << static_cast<int>(color.r) << "," 
00163            << static_cast<int>(color.g) << "," 
00164            << static_cast<int>(color.b) << ">";
00165     return markup.str();
00166 }
00167 
00168 //! Calculate the size of a text (in pixels) if it were to be drawn.
00169 SDL_Rect text_area(const std::string& text, int size, int style)
00170 {
00171     const SDL_Rect area = {0,0,10000,10000};
00172     return draw_text(NULL, area, size, font::NORMAL_COLOUR, text, 0, 0, false, style);
00173 }
00174 
00175 //! Draw text on the screen, clip text to area. Supports simple markup.
00176 //!
00177 //! If the text runs outside of area horizontally, 
00178 //! an ellipsis will be displayed at the end of it.
00179 //! If use_tooltips is true, then text with an ellipsis will have a 
00180 //! tooltip set for it equivalent to the entire contents of the text.
00181 //!
00182 //! Some very basic 'markup' will be done on the text:
00183 //! - any line beginning in # will be displayed in BAD_COLOUR  (red)
00184 //! - any line beginning in @ will be displayed in GOOD_COLOUR (green)
00185 //! - any line beginning in + will be displayed with size increased by 2
00186 //! - any line beginning in - will be displayed with size decreased by 2
00187 //! - any line beginning with 0x0n will be displayed in the colour of side n
00188 //!
00189 //! The above special characters can be quoted using a C-style backslash.
00190 //!
00191 //! A bounding rectangle of the text is returned. 
00192 //! If gui is NULL, then the text will not be drawn, 
00193 //! and only a bounding rectangle will be returned.
00194 //! 
00195 SDL_Rect draw_text(CVideo* gui, const SDL_Rect& area, int size,
00196                    const SDL_Color& colour, const std::string& txt,
00197                    int x, int y, bool use_tooltips, int style)
00198 {
00199     // Make sure there's always at least a space,
00200     // so we can ensure that we can return a rectangle for height
00201     static const std::string blank_text(" ");
00202     const std::string& text = txt.empty() ? blank_text : txt;
00203 
00204     SDL_Rect res;
00205     res.x = x;
00206     res.y = y;
00207     res.w = 0;
00208     res.h = 0;
00209 
00210     std::string::const_iterator i1 = text.begin();
00211     std::string::const_iterator i2 = std::find(i1,text.end(),'\n');
00212     for(;;) {
00213         SDL_Color col = colour;
00214         int sz = size;
00215         int text_style = style;
00216 
00217         i1 = parse_markup(i1,i2,&sz,&col,&text_style);
00218 
00219         if(i1 != i2) {
00220             std::string new_string(i1,i2);
00221 
00222             utils::unescape(new_string);
00223 
00224             const SDL_Rect rect = draw_text_line(gui, area, sz, col, new_string, x, y, use_tooltips, text_style);
00225             if(rect.w > res.w) {
00226                 res.w = rect.w;
00227             }
00228 
00229             res.h += rect.h;
00230             y += rect.h;
00231         }
00232 
00233         if(i2 == text.end()) {
00234             break;
00235         }
00236 
00237         i1 = i2+1;
00238         i2 = std::find(i1,text.end(),'\n');
00239     }
00240 
00241     return res;
00242 }
00243 
00244 //! Determine if char is one of the special chars used as markup.
00245 //! @retval true    input-char is a markup-char
00246 //! @retval false   input-char is a normal char
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 static void cut_word(std::string& line, std::string& word, int font_size, int style, int max_width)
00266 {
00267     std::string tmp = line;
00268     utils::utf8_iterator tc(word);
00269     bool first = true;
00270 
00271     for(;tc != utils::utf8_iterator::end(word); ++tc) {
00272         tmp.append(tc.substr().first, tc.substr().second);
00273         SDL_Rect tsize = line_size(tmp, font_size, style);
00274         if(tsize.w > max_width) {
00275             const std::string& w = word;
00276             if(line.empty() && first) {
00277                 line += std::string(w.begin(), tc.substr().second);
00278                 word = std::string(tc.substr().second, w.end());
00279             } else {
00280                 line += std::string(w.begin(), tc.substr().first);
00281                 word = std::string(tc.substr().first, w.end());
00282             }
00283             break;
00284         }
00285         first = false;
00286     }
00287 }
00288 
00289 namespace {
00290 
00291 /*
00292  * According to Kinsoku-Shori, Japanese rules about line-breaking:
00293  *
00294  * * the following characters cannot begin a line (so we will never break before them):
00295  * 、。,.)〕]}〉》」』】’”ゝゞヽヾ々?!:;ぁぃぅぇぉゃゅょゎァィゥェォャュョヮっヵッヶ・…ー
00296  *
00297  * * the following characters cannot end a line (so we will never break after them):
00298  * (〔[{〈《「『【‘“
00299  */
00300 inline bool no_break_after(wchar_t ch)
00301 {
00302     return
00303         ch == 0x2018 || ch == 0x201c || ch == 0x3008 || ch == 0x300a || ch == 0x300c ||
00304         ch == 0x300e || ch == 0x3010 || ch == 0x3014 || ch == 0xff08 || ch == 0xff3b ||
00305         ch == 0xff5b;
00306 
00307 }
00308 
00309 inline bool no_break_before(wchar_t ch)
00310 {
00311     return
00312         ch == 0x2019 || ch == 0x201d || ch == 0x2026 || ch == 0x3001 || ch == 0x3002 ||
00313         ch == 0x3005 || ch == 0x3009 || ch == 0x300b || ch == 0x300d || ch == 0x300f ||
00314         ch == 0x3011 || ch == 0x3015 || ch == 0x3041 || ch == 0x3043 || ch == 0x3045 ||
00315         ch == 0x3047 || ch == 0x3049 || ch == 0x3063 || ch == 0x3083 || ch == 0x3085 ||
00316         ch == 0x3087 || ch == 0x308e || ch == 0x309d || ch == 0x309e || ch == 0x30a1 ||
00317         ch == 0x30a3 || ch == 0x30a5 || ch == 0x30a7 || ch == 0x30a9 || ch == 0x30c3 ||
00318         ch == 0x30e3 || ch == 0x30e5 || ch == 0x30e7 || ch == 0x30ee || ch == 0x30f5 ||
00319         ch == 0x30f6 || ch == 0x30fb || ch == 0x30fc || ch == 0x30fd || ch == 0x30fe ||
00320         ch == 0xff01 || ch == 0xff09 || ch == 0xff0c || ch == 0xff0e || ch == 0xff1a ||
00321         ch == 0xff1b || ch == 0xff1f || ch == 0xff3d || ch == 0xff5d;
00322 }
00323 
00324 inline bool break_before(wchar_t ch)
00325 {
00326     if(no_break_before(ch))
00327         return false;
00328 
00329     return ch == ' ' ||
00330         // CKJ characters
00331         (ch >= 0x3000 && ch < 0xa000) ||
00332         (ch >= 0xf900 && ch < 0xfb00) ||
00333         (ch >= 0xff00 && ch < 0xfff0);
00334 }
00335 
00336 inline bool break_after(wchar_t ch)
00337 {
00338     if(no_break_after(ch))
00339         return false;
00340 
00341     return ch == ' ' ||
00342         // CKJ characters
00343         (ch >= 0x3000 && ch < 0xa000) ||
00344         (ch >= 0xf900 && ch < 0xfb00) ||
00345         (ch >= 0xff00 && ch < 0xfff0);
00346 }
00347 
00348 } // end of anon namespace
00349 
00350 //! Wrap text.
00351 //!
00352 //! If the text exceedes the specified max width, wrap it one a word basis.
00353 //! If this is not possible, e.g. the word is too big to fit, 
00354 //! wrap it on a char basis.
00355 std::string word_wrap_text(const std::string& unwrapped_text, int font_size, int max_width, int max_height, int max_lines)
00356 {
00357     VALIDATE(max_width > 0, _("The maximum text width is less than 1."));
00358 
00359     utils::utf8_iterator ch(unwrapped_text);
00360     std::string current_word;
00361     std::string current_line;
00362     size_t line_width = 0;
00363     size_t current_height = 0;
00364     bool line_break = false;
00365     bool first = true;
00366     bool start_of_line = true;
00367     std::string wrapped_text;
00368     std::string format_string;
00369     SDL_Color color;
00370     int font_sz = font_size;
00371     int style = TTF_STYLE_NORMAL;
00372     utils::utf8_iterator end = utils::utf8_iterator::end(unwrapped_text);
00373 
00374     while(1) {
00375         if(start_of_line) {
00376             line_width = 0;
00377             format_string = "";
00378             while(ch != end && *ch < static_cast<wchar_t>(0x100)
00379                     && is_format_char(*ch) && !ch.next_is_end()) {
00380 
00381                 format_string.append(ch.substr().first, ch.substr().second);
00382                 ++ch;
00383             }
00384             // We need to parse the special format characters
00385             // to give the proper font_size and style to line_size()
00386             font_sz = font_size;
00387             style = TTF_STYLE_NORMAL;
00388             parse_markup(format_string.begin(),format_string.end(),&font_sz,&color,&style);
00389             current_line = format_string;
00390             start_of_line = false;
00391         }
00392 
00393         // If there is no current word, get one
00394         if(current_word.empty() && ch == end) {
00395             break;
00396         } else if(current_word.empty()) {
00397             if(*ch == ' ' || *ch == '\n') {
00398                 current_word = *ch;
00399                 ++ch;
00400             } else {
00401                 wchar_t previous = 0;
00402                 for(;ch != utils::utf8_iterator::end(unwrapped_text) &&
00403                         *ch != ' ' && *ch != '\n'; ++ch) {
00404 
00405                     if(!current_word.empty() &&
00406                             break_before(*ch) &&
00407                             !no_break_after(previous))
00408                         break;
00409 
00410                     if(!current_word.empty() &&
00411                             break_after(previous) &&
00412                             !no_break_before(*ch))
00413                         break;
00414 
00415                     current_word.append(ch.substr().first, ch.substr().second);
00416 
00417                     previous = *ch;
00418                 }
00419             }
00420         }
00421 
00422         if(current_word == "\n") {
00423             line_break = true;
00424             current_word = "";
00425             start_of_line = true;
00426         } else {
00427 
00428             const size_t word_width = line_size(current_word, font_sz, style).w;
00429 
00430             line_width += word_width;
00431 
00432             if(static_cast<long>(line_width) > max_width) {
00433                 if(static_cast<long>(word_width) > max_width) {
00434                     cut_word(current_line,
00435                         current_word, font_sz, style, max_width);
00436                 }
00437                 if(current_word == " ")
00438                     current_word = "";
00439                 line_break = true;
00440             } else {
00441                 current_line += current_word;
00442                 current_word = "";
00443             }
00444         }
00445 
00446         if(line_break || (current_word.empty() && ch == end)) {
00447             SDL_Rect size = line_size(current_line, font_sz, style);
00448             if(max_height > 0 && current_height + size.h >= size_t(max_height)) {
00449                 return wrapped_text;
00450             }
00451 
00452             if(!first) {
00453                 wrapped_text += '\n';
00454             }
00455 
00456             wrapped_text += current_line;
00457             current_line = format_string;
00458             line_width = 0;
00459             current_height += size.h;
00460             line_break = false;
00461             first = false;
00462 
00463             if(--max_lines == 0) {
00464                 return wrapped_text;
00465             }
00466         }
00467     }
00468     return wrapped_text;
00469 }
00470 
00471 //! Draw text on the screen, fit text to maximum width, no markup, no tooltips.
00472 //!
00473 //! This method makes sure that the text fits within a given maximum width. 
00474 //! If a line exceedes this width, it will be wrapped 
00475 //! on a word basis if possible, otherwise on a char basis.
00476 //! This method is otherwise similar to the draw_text method,
00477 //! but it doesn't support special markup or tooltips.
00478 //!
00479 //! @return     a bounding rectangle of the text.
00480 //!
00481 SDL_Rect draw_wrapped_text(CVideo* gui, const SDL_Rect& area, int font_size,
00482              const SDL_Color& colour, const std::string& text,
00483              int x, int y, int max_width)
00484 {
00485     std::string wrapped_text = word_wrap_text(text, font_size, max_width);
00486     return font::draw_text(gui, area, font_size, colour, wrapped_text, x, y, false);
00487 }
00488 
00489 
00490 //! Chop up one long string of text into lines.
00491 size_t text_to_lines(std::string& message, size_t max_length)
00492 {
00493     std::string starting_markup;
00494     bool at_start = true;
00495 
00496     size_t cur_line = 0, longest_line = 0;
00497     for(std::string::iterator i = message.begin(); i != message.end(); ++i) {
00498         if(at_start) {
00499             if(font::is_format_char(*i)) {
00500                 push_back(starting_markup,*i);
00501             } else {
00502                 at_start = false;
00503             }
00504         }
00505 
00506         if(*i == '\n') {
00507             at_start = true;
00508             starting_markup = "";
00509         }
00510 
00511         if(*i == ' ' && cur_line > max_length) {
00512             *i = '\n';
00513             const size_t index = i - message.begin();
00514             message.insert(index+1,starting_markup);
00515             i = message.begin() + index + starting_markup.size();
00516 
00517             if(cur_line > longest_line)
00518                 longest_line = cur_line;
00519 
00520             cur_line = 0;
00521         }
00522 
00523         if(*i == '\n' || i+1 == message.end()) {
00524             if(cur_line > longest_line)
00525                 longest_line = cur_line;
00526 
00527             cur_line = 0;
00528 
00529         } else {
00530             ++cur_line;
00531         }
00532     }
00533 
00534     return longest_line;
00535 }
00536 
00537 
00538 } // end namespace font
00539 

Generated by doxygen 1.5.5 on 23 May 2008 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs