00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
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
00042
00043
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 }
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 += "&"; break;
00077 case '<': result += "<"; break;
00078 case '>': result += ">"; break;
00079 case '\'': result += "'"; break;
00080 case '"': result += """; 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),
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
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
00122
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
00220
00221 titor itor(layout_);
00222
00223
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
00235 for(size_t i = 0; i < column; ++i) {
00236 if(!pango_layout_iter_next_char(itor)) {
00237
00238
00239
00240 if(i + 1 == column) {
00241 break;
00242 }
00243
00244 return gui2::tpoint(0, 0);
00245 }
00246 }
00247
00248
00249 const int offset = pango_layout_iter_get_index(itor);
00250
00251
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
00263 int index, trailing;
00264 pango_layout_xy_to_index(layout_, position.x * PANGO_SCALE,
00265 position.y * PANGO_SCALE, &index, &trailing);
00266
00267
00268 int line, offset;
00269 pango_layout_index_to_line_x(layout_, index, trailing, &line, &offset);
00270 offset = PANGO_PIXELS(offset);
00271
00272
00273
00274
00275
00276
00277
00278
00279
00280
00281
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
00311
00312
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
00370
00371
00372
00373
00374
00375
00376
00377
00378
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
00416
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
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
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 }
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
00552
00553
00554
00555
00556
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
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
00612
00613
00614 static void decode_pixel(unsigned char *p)
00615 {
00616
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
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
00677
00678
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
00688
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
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
00719 pango_layout_set_markup(layout_, text.c_str(), text.size());
00720 return true;
00721 }
00722
00723
00724
00725
00726
00727
00728
00729
00730
00731 std::string semi_escaped;
00732 BOOST_FOREACH(const char c, text) {
00733 if(c == '&') {
00734 semi_escaped += "&";
00735 } else {
00736 semi_escaped += c;
00737 }
00738 }
00739
00740
00741
00742
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
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
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 }
00767