text.cpp

Go to the documentation of this file.
00001 /* $Id: text.cpp 54037 2012-04-30 19:37:20Z mordante $ */
00002 /*
00003    Copyright (C) 2008 - 2012 by Mark de Wever <koraq@xs4all.nl>
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 #define GETTEXT_DOMAIN "wesnoth-lib"
00017 
00018 #include "text.hpp"
00019 
00020 #include "gettext.hpp"
00021 #include "gui/widgets/helper.hpp"
00022 #include "gui/auxiliary/log.hpp"
00023 #include "font.hpp"
00024 #include "serialization/string_utils.hpp"
00025 #include "tstring.hpp"
00026 
00027 #include <boost/foreach.hpp>
00028 
00029 #include <cassert>
00030 #include <cstring>
00031 
00032 #ifndef PANGO_VERSION_CHECK
00033 #define PANGO_VERSION_CHECK(a,b,c) 0
00034 #endif
00035 
00036 namespace font {
00037 
00038 namespace {
00039 
00040 /**
00041  * Small helper wrapper for PangoLayoutIter*.
00042  *
00043  * Needed to make sure it gets freed properly.
00044  */
00045 class titor
00046     : private boost::noncopyable
00047 {
00048 public:
00049 
00050     explicit titor(PangoLayout* layout_) :
00051         itor_(pango_layout_get_iter(layout_))
00052     {
00053     }
00054 
00055     ~titor() { pango_layout_iter_free(itor_); }
00056 
00057     operator PangoLayoutIter*() { return itor_; }
00058 
00059 private:
00060 
00061     PangoLayoutIter* itor_;
00062 };
00063 
00064 } // namespace
00065 
00066 const unsigned ttext::STYLE_NORMAL = TTF_STYLE_NORMAL;
00067 const unsigned ttext::STYLE_BOLD = TTF_STYLE_BOLD;
00068 const unsigned ttext::STYLE_ITALIC = TTF_STYLE_ITALIC;
00069 const unsigned ttext::STYLE_UNDERLINE = TTF_STYLE_UNDERLINE;
00070 
00071 std::string escape_text(const std::string& text)
00072 {
00073     std::string result;
00074     BOOST_FOREACH(const char c, text) {
00075         switch(c) {
00076             case '&':  result += "&amp;";  break;
00077             case '<':  result += "&lt;";   break;
00078             case '>':  result += "&gt;";   break;
00079             case '\'': result += "&apos;"; break;
00080             case '"':  result += "&quot;"; break;
00081             default:   result += c;
00082         }
00083     }
00084     return result;
00085 }
00086 
00087 ttext::ttext() :
00088 #if PANGO_VERSION_CHECK(1,22,0)
00089     context_(pango_font_map_create_context(pango_cairo_font_map_get_default())),
00090 #else
00091     context_(pango_cairo_font_map_create_context((
00092         reinterpret_cast<PangoCairoFontMap*>(pango_cairo_font_map_get_default())))),
00093 #endif
00094     layout_(pango_layout_new(context_)),
00095     rect_(),
00096     surface_(),
00097     text_(),
00098     markedup_text_(false),
00099     font_size_(14),
00100     font_style_(STYLE_NORMAL),
00101     foreground_color_(0xFFFFFFFF), // solid white
00102     maximum_width_(-1),
00103     characters_per_line_(0),
00104     maximum_height_(-1),
00105     ellipse_mode_(PANGO_ELLIPSIZE_END),
00106     alignment_(PANGO_ALIGN_LEFT),
00107     maximum_length_(std::string::npos),
00108     calculation_dirty_(true),
00109     length_(0),
00110     surface_dirty_(true),
00111     surface_buffer_(NULL)
00112 {
00113     // With 72 dpi the sizes are the same as with SDL_TTF so hardcoded.
00114     pango_cairo_context_set_resolution(context_, 72.0);
00115 
00116     pango_layout_set_ellipsize(layout_, ellipse_mode_);
00117     pango_layout_set_alignment(layout_, alignment_);
00118     pango_layout_set_wrap(layout_, PANGO_WRAP_WORD_CHAR);
00119 
00120     /*
00121      * Set the pango spacing a bit bigger since the default is deemed to small
00122      * http://www.wesnoth.org/forum/viewtopic.php?p=358832#p358832
00123      */
00124     pango_layout_set_spacing(layout_, 2 * PANGO_SCALE);
00125 
00126     cairo_font_options_t *fo = cairo_font_options_create();
00127     cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL);
00128     cairo_font_options_set_hint_metrics(fo, CAIRO_HINT_METRICS_ON);
00129     pango_cairo_context_set_font_options(context_, fo);
00130     cairo_font_options_destroy(fo);
00131 }
00132 
00133 ttext::~ttext()
00134 {
00135     if(context_) {
00136         g_object_unref(context_);
00137     }
00138     if(layout_) {
00139         g_object_unref(layout_);
00140     }
00141     if(surface_buffer_) {
00142         surface_.assign(NULL);
00143         delete[] surface_buffer_;
00144     }
00145 }
00146 
00147 surface ttext::render() const
00148 {
00149     rerender();
00150     return surface_;
00151 }
00152 
00153 int ttext::get_width() const
00154 {
00155     return get_size().x;
00156 }
00157 
00158 int ttext::get_height() const
00159 {
00160     return get_size().y;
00161 }
00162 
00163 gui2::tpoint ttext::get_size() const
00164 {
00165     recalculate();
00166 
00167     return gui2::tpoint(rect_.width, rect_.height);
00168 }
00169 
00170 bool ttext::is_truncated() const
00171 {
00172     recalculate();
00173 
00174 #if PANGO_VERSION_CHECK(1,16,0)
00175     return (pango_layout_is_ellipsized(layout_) != 0);
00176 #else
00177     return false;
00178 #endif
00179 }
00180 
00181 unsigned ttext::insert_text(const unsigned offset, const std::string& text)
00182 {
00183     if(text.empty()) {
00184         return 0;
00185     }
00186 
00187     return insert_unicode(offset, utils::string_to_wstring(text));
00188 }
00189 
00190 bool ttext::insert_unicode(const unsigned offset, const wchar_t unicode)
00191 {
00192     return (insert_unicode(offset, wide_string(1, unicode)) == 1);
00193 }
00194 
00195 unsigned ttext::insert_unicode(const unsigned offset, const wide_string& unicode)
00196 {
00197     assert(offset <= length_);
00198 
00199     if(length_ == maximum_length_) {
00200         return 0;
00201     }
00202 
00203     const unsigned len = length_ + unicode.size() > maximum_length_
00204         ? maximum_length_ - length_  : unicode.size();
00205 
00206     wide_string tmp = utils::string_to_wstring(text_);
00207     tmp.insert(tmp.begin() + offset, unicode.begin(), unicode.begin() + len);
00208 
00209     set_text(utils::wstring_to_string(tmp), false);
00210 
00211     return len;
00212 }
00213 
00214 gui2::tpoint ttext::get_cursor_position(
00215         const unsigned column, const unsigned line) const
00216 {
00217     recalculate();
00218 
00219     // First we need to determine the byte offset, if more routines need it it
00220     // would be a good idea to make it a separate function.
00221     titor itor(layout_);
00222 
00223     // Go the the wanted line.
00224     if(line != 0) {
00225         if(pango_layout_get_line_count(layout_) >= static_cast<int>(line)) {
00226             return gui2::tpoint(0, 0);
00227         }
00228 
00229         for(size_t i = 0; i < line; ++i) {
00230             pango_layout_iter_next_line(itor);
00231         }
00232     }
00233 
00234     // Go the wanted column.
00235     for(size_t i = 0; i < column; ++i) {
00236         if(!pango_layout_iter_next_char(itor)) {
00237             // It seems that the documentation is wrong and causes and off by
00238             // one error... the result should be false if already at the end of
00239             // the data when started.
00240             if(i + 1 == column) {
00241                 break;
00242             }
00243             // beyound data.
00244             return gui2::tpoint(0, 0);
00245         }
00246     }
00247 
00248     // Get the byte offset
00249     const int offset = pango_layout_iter_get_index(itor);
00250 
00251     // Convert the byte offset in a position.
00252     PangoRectangle rect;
00253     pango_layout_get_cursor_pos(layout_, offset, &rect, NULL);
00254 
00255     return gui2::tpoint(PANGO_PIXELS(rect.x), PANGO_PIXELS(rect.y));
00256 }
00257 
00258 gui2::tpoint ttext::get_column_line(const gui2::tpoint& position) const
00259 {
00260     recalculate();
00261 
00262     // Get the index of the character.
00263     int index, trailing;
00264     pango_layout_xy_to_index(layout_, position.x * PANGO_SCALE,
00265         position.y * PANGO_SCALE, &index, &trailing);
00266 
00267     // Extract the line and the offset in pixels in that line.
00268     int line, offset;
00269     pango_layout_index_to_line_x(layout_, index, trailing, &line, &offset);
00270     offset = PANGO_PIXELS(offset);
00271 
00272     // Now convert this offset to a column, this way is a bit hacky but haven't
00273     // found a better solution yet.
00274 
00275     /**
00276      * @todo There's still a bug left. When you select a text which is in the
00277      * ellipses on the right side the text gets reformatted with ellipses on
00278      * the left and the selected character is not the one under the cursor.
00279      * Other widget toolkits don't show ellipses and have no indication more
00280      * text is available. Haven't found what the best thing to do would be.
00281      * Until that time leave it as is.
00282      */
00283     for(size_t i = 0; ; ++i) {
00284         const int pos = get_cursor_position(i, line).x;
00285 
00286         if(pos == offset) {
00287             return  gui2::tpoint(i, line);
00288         }
00289     }
00290 }
00291 
00292 bool ttext::set_text(const std::string& text, const bool markedup)
00293 {
00294     if(markedup != markedup_text_ || text != text_) {
00295         assert(layout_);
00296 
00297         const wide_string wide = utils::string_to_wstring(text);
00298         const std::string narrow = utils::wstring_to_string(wide);
00299         if(text != narrow) {
00300             ERR_GUI_L << "ttext::" << __func__
00301                     << " text '" << text
00302                     << "' contains invalid utf-8, trimmed the invalid parts.\n";
00303         }
00304         if(markedup) {
00305             if(!set_markup(narrow)) {
00306                 return false;
00307             }
00308         } else {
00309             /*
00310              * pango_layout_set_text after pango_layout_set_markup might
00311              * leave the layout in an undefined state regarding markup so
00312              * clear it unconditionally.
00313              */
00314             pango_layout_set_attributes(layout_, NULL);
00315             pango_layout_set_text(layout_, narrow.c_str(), narrow.size());
00316         }
00317         text_ = narrow;
00318         length_ = wide.size();
00319         markedup_text_ = markedup;
00320         calculation_dirty_ = true;
00321         surface_dirty_ = true;
00322     }
00323 
00324     return true;
00325 }
00326 
00327 ttext& ttext::set_font_size(const unsigned font_size)
00328 {
00329     if(font_size != font_size_) {
00330         font_size_ = font_size;
00331         calculation_dirty_ = true;
00332         surface_dirty_ = true;
00333     }
00334 
00335     return *this;
00336 }
00337 
00338 ttext& ttext::set_font_style(const unsigned font_style)
00339 {
00340     if(font_style != font_style_) {
00341         font_style_ = font_style;
00342         calculation_dirty_ = true;
00343         surface_dirty_ = true;
00344     }
00345 
00346     return *this;
00347 }
00348 
00349 ttext& ttext::set_foreground_color(const Uint32 color)
00350 {
00351     if(color != foreground_color_) {
00352         foreground_color_ = color;
00353         surface_dirty_ = true;
00354     }
00355 
00356     return *this;
00357 }
00358 
00359 ttext& ttext::set_maximum_width(int width)
00360 {
00361     if(width <= 0) {
00362         width = -1;
00363     }
00364 
00365     if(width != maximum_width_) {
00366         assert(context_);
00367 #if 0
00368         /**
00369          * todo Adding 4 extra pixels feels a bit hacky.
00370          *
00371          * For some reason it's needed since the following scenario fails:
00372          * - pango_layout_set_width(value)
00373          * - pango_layout_get_pixel_extents() -> max_width_1
00374          * - pango_layout_set_width(max_width_1)
00375          * - pango_layout_get_pixel_extents() -> max_width_2
00376          *
00377          * Now it can happen max_width_2 < max_width_1. Adding the 4 seems to
00378          * "fix" the problem.
00379          */
00380         pango_layout_set_width(layout_, width == -1
00381                 ? -1
00382                 : (width + 4) * PANGO_SCALE);
00383 #endif
00384         maximum_width_ = width;
00385         calculation_dirty_ = true;
00386         surface_dirty_ = true;
00387     }
00388 
00389     return *this;
00390 }
00391 
00392 ttext& ttext::set_characters_per_line(const unsigned characters_per_line)
00393 {
00394     if(characters_per_line != characters_per_line_) {
00395         characters_per_line_ = characters_per_line;
00396 
00397         calculation_dirty_ = true;
00398         surface_dirty_ = true;
00399     }
00400 
00401     return *this;
00402 }
00403 
00404 ttext& ttext::set_maximum_height(int height, bool multiline)
00405 {
00406     if(height <= 0) {
00407         height = -1;
00408         multiline = false;
00409     }
00410 
00411     if(height != maximum_height_) {
00412         assert(context_);
00413 
00414 /**
00415  * @todo See whether we can make pango 1.20 mandatory before Wesnoth 1.6 is
00416  * released.
00417  */
00418 #if PANGO_VERSION_CHECK(1,20,0)
00419         pango_layout_set_height(layout_, !multiline ? -1 : height * PANGO_SCALE);
00420 #endif
00421         maximum_height_ = height;
00422         calculation_dirty_ = true;
00423         surface_dirty_ = true;
00424     }
00425 
00426     return *this;
00427 }
00428 
00429 ttext& ttext::set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
00430 {
00431     if(ellipse_mode != ellipse_mode_) {
00432         assert(context_);
00433 
00434         pango_layout_set_ellipsize(layout_, ellipse_mode);
00435         ellipse_mode_ = ellipse_mode;
00436         calculation_dirty_ = true;
00437         surface_dirty_ = true;
00438     }
00439 
00440     return *this;
00441 }
00442 
00443 ttext &ttext::set_alignment(const PangoAlignment alignment)
00444 {
00445     if (alignment == alignment_) return *this;
00446     pango_layout_set_alignment(layout_, alignment);
00447     alignment_ = alignment;
00448     surface_dirty_ = true;
00449     return *this;
00450 }
00451 
00452 ttext& ttext::set_maximum_length(const size_t maximum_length)
00453 {
00454     if(maximum_length != maximum_length_) {
00455         maximum_length_ = maximum_length;
00456         if(length_ > maximum_length_) {
00457 
00458             wide_string tmp = utils::string_to_wstring(text_);
00459             tmp.resize(maximum_length_);
00460             set_text(utils::wstring_to_string(tmp), false);
00461         }
00462     }
00463 
00464     return *this;
00465 }
00466 
00467 namespace {
00468 
00469 /** Small helper class to make sure the font object is destroyed properly. */
00470 class tfont
00471     : private boost::noncopyable
00472 {
00473 public:
00474     tfont(const std::string& name, const unsigned size, const unsigned style) :
00475         font_(pango_font_description_new())
00476     {
00477         pango_font_description_set_family(font_, name.c_str());
00478         pango_font_description_set_size(font_, size * PANGO_SCALE);
00479 
00480         if(style != ttext::STYLE_NORMAL) {
00481             if(style & ttext::STYLE_ITALIC) {
00482                 pango_font_description_set_style(font_, PANGO_STYLE_ITALIC);
00483             }
00484             if(style & ttext::STYLE_BOLD) {
00485                 pango_font_description_set_weight(font_, PANGO_WEIGHT_BOLD);
00486             }
00487             if(style & ttext::STYLE_UNDERLINE) {
00488                 /* Do nothing here, underline is a property of the layout. */
00489             }
00490         }
00491     }
00492 
00493     ~tfont() { pango_font_description_free(font_); }
00494 
00495     PangoFontDescription* get() { return font_; }
00496 
00497 private:
00498     PangoFontDescription *font_;
00499 };
00500 
00501 std::ostream& operator<<(std::ostream& s, const PangoRectangle &rect)
00502 {
00503     s << rect.x << ',' << rect.y << " x " << rect.width << ',' << rect.height;
00504     return s;
00505 }
00506 
00507 } // namespace
00508 
00509 void ttext::recalculate(const bool force) const
00510 {
00511     if(calculation_dirty_ || force) {
00512         assert(layout_);
00513 
00514         calculation_dirty_ = false;
00515         surface_dirty_ = true;
00516 
00517         tfont font(get_font_families(), font_size_, font_style_);
00518         pango_layout_set_font_description(layout_, font.get());
00519 
00520         if(font_style_ & ttext::STYLE_UNDERLINE) {
00521             PangoAttrList *attribute_list = pango_attr_list_new();
00522             pango_attr_list_insert(attribute_list
00523                     , pango_attr_underline_new(PANGO_UNDERLINE_SINGLE));
00524 
00525             pango_layout_set_attributes (layout_, attribute_list);
00526             pango_attr_list_unref(attribute_list);
00527         }
00528 
00529         int maximum_width = 0;
00530         if(characters_per_line_ != 0) {
00531             PangoFont* f = pango_font_map_load_font(
00532                       pango_cairo_font_map_get_default()
00533                     , context_
00534                     , font.get());
00535 
00536             PangoFontMetrics* m = pango_font_get_metrics(f, NULL);
00537 
00538             int w = pango_font_metrics_get_approximate_char_width(m);
00539             w *= characters_per_line_;
00540 
00541             maximum_width = ceil(pango_units_to_double(w));
00542         } else {
00543             maximum_width = maximum_width_;
00544         }
00545 
00546         if(maximum_width_ != -1) {
00547             maximum_width = std::min(maximum_width, maximum_width_);
00548         }
00549 
00550         /*
00551          * See set_maximum_width for some more background info as well.
00552          * In order to fix the problem first set a width which seems to render
00553          * correctly then lower it to fit. For the campaigns the 4 does "the
00554          * right thing" for the terrain labels it should use the value 0 to set
00555          * the ellipse properly. Need to see whether this is a bug in pango or
00556          * a bug in my understanding of the pango api.
00557          */
00558         int hack = 4;
00559         do {
00560             pango_layout_set_width(layout_, maximum_width == -1
00561                     ? -1
00562                     : (maximum_width + hack) * PANGO_SCALE);
00563             pango_layout_get_pixel_extents(layout_, NULL, &rect_);
00564 
00565             DBG_GUI_L << "ttext::" << __func__
00566                     << " text '" << gui2::debug_truncate(text_)
00567                     << "' maximum_width " << maximum_width
00568                     << " hack " << hack
00569                     << " width " << rect_.x + rect_.width
00570                     << ".\n";
00571 
00572             --hack;
00573         } while(maximum_width != -1
00574                 && hack >= 0 && rect_.x + rect_.width > maximum_width);
00575 
00576         DBG_GUI_L << "ttext::" << __func__
00577                 << " text '" << gui2::debug_truncate(text_)
00578                 << "' font_size " << font_size_
00579                 << " markedup_text " << markedup_text_
00580                 << " font_style " << std::hex << font_style_ << std::dec
00581                 << " maximum_width " << maximum_width
00582                 << " maximum_height " << maximum_height_
00583                 << " result " <<  rect_
00584                 << ".\n";
00585         if(maximum_width != -1 && rect_.x + rect_.width > maximum_width) {
00586             DBG_GUI_L << "ttext::" << __func__
00587                     << " text '" << gui2::debug_truncate(text_)
00588                     << " ' width " << rect_.x + rect_.width
00589                     << " greater as the wanted maximum of " << maximum_width
00590                     << ".\n";
00591         }
00592     }
00593 }
00594 
00595 struct decode_table
00596 {
00597     // 1-based, from 1 to 255.
00598     unsigned values[255];
00599     decode_table()
00600         : values()
00601     {
00602         for (int i = 1; i < 256; ++i) values[i - 1] = (255 * 256) / i;
00603     }
00604 };
00605 
00606 static decode_table decode_table;
00607 
00608 
00609 #ifndef _WIN32
00610 /**
00611  * Converts from premultiplied alpha to plain alpha.
00612  * @param p pointer to a 4-byte endian-dependent color.
00613  */
00614 static void decode_pixel(unsigned char *p)
00615 {
00616 // Assume everything not compiled with gcc to be on a little endian platform.
00617 #if defined(__GNUC__) && defined(__BIG_ENDIAN__)
00618     int alpha = p[0];
00619 #else
00620     int alpha = p[3];
00621 #endif
00622     if (alpha == 0) return;
00623 
00624     int div = decode_table.values[alpha - 1];
00625 
00626 #define DECODE(i) \
00627     do { \
00628         unsigned color = p[i]; \
00629         color = color * div / 256; \
00630         if (color > 255) color = 255; \
00631         p[i] = color; \
00632     } while (0)
00633 
00634 #if defined(__GNUC__) && defined(__BIG_ENDIAN__)
00635     DECODE(3);
00636 #else
00637     DECODE(0);
00638 #endif
00639     DECODE(1);
00640     DECODE(2);
00641 }
00642 #endif
00643 
00644 
00645 void ttext::rerender(const bool force) const
00646 {
00647     if(surface_dirty_ || force) {
00648         assert(layout_);
00649 
00650         recalculate(force);
00651         surface_dirty_ = false;
00652 
00653         int width = rect_.x + rect_.width;
00654         int height = rect_.y + rect_.height;
00655         if (maximum_width_  > 0 && width  > maximum_width_ ) width  = maximum_width_;
00656         if (maximum_height_ > 0 && height > maximum_height_) height = maximum_height_;
00657         const unsigned stride = width * 4;
00658         create_surface_buffer(stride * height);
00659 
00660         cairo_surface_t *cairo_surface =
00661             cairo_image_surface_create_for_data(surface_buffer_,
00662                 CAIRO_FORMAT_ARGB32, width, height, stride);
00663         cairo_t *cr = cairo_create(cairo_surface);
00664 
00665         /* set color (used for foreground). */
00666         cairo_set_source_rgba(cr,
00667              (foreground_color_ >> 24)         / 256.0,
00668             ((foreground_color_ >> 16) & 0xFF) / 256.0,
00669             ((foreground_color_ >> 8)  & 0xFF) / 256.0,
00670             (foreground_color_         & 0xFF) / 256.0);
00671 
00672         pango_cairo_show_layout(cr, layout_);
00673 
00674 #ifndef _WIN32
00675 
00676         // The cairo surface is in CAIRO_FORMAT_ARGB32 which uses
00677         // pre-multiplied alpha. SDL doesn't use that so the pixels need to be
00678         // decoded again.
00679         for (int y = 0; y < height; ++y) {
00680             for (int x = 0; x < width; ++x)
00681             {
00682                 unsigned char *pixel = &surface_buffer_[(y * width + x) * 4];
00683                 decode_pixel(pixel);
00684             }
00685         }
00686 #else
00687         // The solution code above doesn't seem to work properly on windows so
00688         // use the old trick of drawing the same text a few times.
00689         pango_cairo_show_layout(cr, layout_);
00690         pango_cairo_show_layout(cr, layout_);
00691         pango_cairo_show_layout(cr, layout_);
00692 #endif
00693         surface_.assign(SDL_CreateRGBSurfaceFrom(
00694             surface_buffer_, width, height, 32, stride,
00695             0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000));
00696         cairo_destroy(cr);
00697         cairo_surface_destroy(cairo_surface);
00698     }
00699 }
00700 
00701 void ttext::create_surface_buffer(const size_t size) const
00702 {
00703     // clear old buffer
00704     if(surface_buffer_) {
00705         surface_.assign(NULL);
00706         delete[] surface_buffer_;
00707     }
00708 
00709     surface_buffer_ = new unsigned char [size];
00710     memset(surface_buffer_, 0, size);
00711 }
00712 
00713 bool ttext::set_markup(const std::string& text)
00714 {
00715     if(pango_parse_markup(text.c_str(), text.size()
00716             , 0, NULL, NULL, NULL, NULL)) {
00717 
00718         /* Markup is valid so set it. */
00719         pango_layout_set_markup(layout_, text.c_str(), text.size());
00720         return true;
00721     }
00722 
00723     /*
00724      * The markup is invalid. Try to recover.
00725      *
00726      * The pango engine tested seems to accept stray single quotes »'« and
00727      * double quotes »"«. Stray ampersands »&« seem to give troubles.
00728      * So only try to recover from broken ampersands, by simply replacing them
00729      * with the escaped version.
00730      */
00731     std::string semi_escaped;
00732     BOOST_FOREACH(const char c, text) {
00733         if(c == '&') {
00734             semi_escaped += "&amp;";
00735         } else {
00736             semi_escaped += c;
00737         }
00738     }
00739 
00740     /*
00741      * If at least one ampersand is replaced the semi-escaped string
00742      * is longer than the original.
00743      */
00744     if(text.size() != semi_escaped.size()
00745             && !pango_parse_markup(semi_escaped.c_str(), semi_escaped.size()
00746                 , 0, NULL, NULL, NULL, NULL)) {
00747 
00748         /* Fixing the ampersands didn't work. */
00749         ERR_GUI_L << "ttext::" << __func__
00750                 << " text '" << text
00751                 << "' has broken markup, set to normal text.\n";
00752 
00753         set_text(_("The text contains invalid markup: ") + text, false);
00754         return false;
00755     }
00756 
00757     /* Replacement worked, still warn the user about the error. */
00758     ERR_GUI_L << "ttext::" << __func__
00759             << " text '" << text
00760             << "' has unescaped ampersands '&', escaped them.\n";
00761 
00762     pango_layout_set_markup(layout_, semi_escaped.c_str(), semi_escaped.size());
00763     return true;
00764 }
00765 
00766 } // namespace font
00767 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

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