The Battle for Wesnoth  1.13.4+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
text.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2016 by Mark de Wever <[email protected]>
3  Part of the Battle for Wesnoth Project http://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #define GETTEXT_DOMAIN "wesnoth-lib"
16 
17 #include "text.hpp"
18 
19 #include "gettext.hpp"
20 #include "gui/widgets/helper.hpp"
21 #include "gui/core/log.hpp"
22 #include "gui/core/point.hpp"
23 #include "font.hpp"
26 #include "tstring.hpp"
27 #include "preferences.hpp"
28 
29 #include <cassert>
30 #include <cstring>
31 
32 #include "video.hpp"
33 
34 #ifdef __GNUC__
35 # pragma GCC diagnostic ignored "-Wold-style-cast"
36 #endif
37 
38 namespace font {
39 
40 namespace {
41 
42 /**
43  * Small helper wrapper for PangoLayoutIter*.
44  *
45  * Needed to make sure it gets freed properly.
46  */
47 class titor
48  : private boost::noncopyable
49 {
50 public:
51 
52  explicit titor(PangoLayout* layout_) :
53  itor_(pango_layout_get_iter(layout_))
54  {
55  }
56 
57  ~titor() { pango_layout_iter_free(itor_); }
58 
59  operator PangoLayoutIter*() { return itor_; }
60 
61 private:
62 
63  PangoLayoutIter* itor_;
64 };
65 
66 } // namespace
67 
68 const unsigned ttext::STYLE_NORMAL = TTF_STYLE_NORMAL;
69 const unsigned ttext::STYLE_BOLD = TTF_STYLE_BOLD;
70 const unsigned ttext::STYLE_ITALIC = TTF_STYLE_ITALIC;
71 const unsigned ttext::STYLE_UNDERLINE = TTF_STYLE_UNDERLINE;
72 
73 static bool looks_like_url(const std::string & token);
74 
76 {
78  for(const char c : text) {
79  switch(c) {
80  case '&': result += "&amp;"; break;
81  case '<': result += "&lt;"; break;
82  case '>': result += "&gt;"; break;
83  case '\'': result += "&apos;"; break;
84  case '"': result += "&quot;"; break;
85  default: result += c;
86  }
87  }
88  return result;
89 }
90 
92 #if PANGO_VERSION_CHECK(1,22,0)
93  context_(pango_font_map_create_context(pango_cairo_font_map_get_default())),
94 #else
95  context_(pango_cairo_font_map_create_context((
96  reinterpret_cast<PangoCairoFontMap*>(pango_cairo_font_map_get_default())))),
97 #endif
98  layout_(pango_layout_new(context_)),
99  rect_(),
100  surface_(),
101 #ifdef SDL_GPU
102  texture_(),
103 #endif
104  text_(),
105  markedup_text_(false),
106  link_aware_(false),
107  link_color_(),
108  font_class_(font::FONT_SANS_SERIF),
109  font_size_(14),
110  font_style_(STYLE_NORMAL),
111  foreground_color_(0xFFFFFFFF), // solid white
112  maximum_width_(-1),
114  maximum_height_(-1),
115  ellipse_mode_(PANGO_ELLIPSIZE_END),
116  alignment_(PANGO_ALIGN_LEFT),
117  maximum_length_(std::string::npos),
118  calculation_dirty_(true),
119  length_(0),
120  surface_dirty_(true),
121  surface_buffer_(nullptr)
122 {
123  // With 72 dpi the sizes are the same as with SDL_TTF so hardcoded.
124  pango_cairo_context_set_resolution(context_, 72.0);
125 
126  pango_layout_set_ellipsize(layout_, ellipse_mode_);
127  pango_layout_set_alignment(layout_, alignment_);
128  pango_layout_set_wrap(layout_, PANGO_WRAP_WORD_CHAR);
129 
130  /*
131  * Set the pango spacing a bit bigger since the default is deemed to small
132  * http://www.wesnoth.org/forum/viewtopic.php?p=358832#p358832
133  */
134  pango_layout_set_spacing(layout_, 2 * PANGO_SCALE);
135 
136  cairo_font_options_t *fo = cairo_font_options_create();
137  cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL);
138  cairo_font_options_set_hint_metrics(fo, CAIRO_HINT_METRICS_ON);
139 #ifdef _WIN32
140  // Cairo on Windows (at least the latest available version from gtk.org
141  // as of 2014-02-22, version 1.10.2) has issues with ClearType resulting
142  // in glitchy anti-aliasing with CAIRO_ANTIALIAS_SUBPIXEL or
143  // CAIRO_ANTIALIAS_DEFAULT, but not CAIRO_ANTIALIAS_GRAY, so we use that
144  // as a workaround until the Windows package is updated to use a newer
145  // version of Cairo (see Wesnoth bug #21648).
146  cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_GRAY);
147 #endif
148  pango_cairo_context_set_font_options(context_, fo);
149  cairo_font_options_destroy(fo);
150 }
151 
153 {
154  if(context_) {
155  g_object_unref(context_);
156  }
157  if(layout_) {
158  g_object_unref(layout_);
159  }
160  if(surface_buffer_) {
161  surface_.assign(nullptr);
162  delete[] surface_buffer_;
163  }
164 }
165 
167 {
168  rerender();
169  return surface_;
170 }
171 
172 #ifdef SDL_GPU
173 sdl::timage ttext::render_as_texture() const
174 {
175  rerender();
176  return texture_;
177 }
178 #endif
179 
180 int ttext::get_width() const
181 {
182  return get_size().x;
183 }
184 
185 int ttext::get_height() const
186 {
187  return get_size().y;
188 }
189 
191 {
192  recalculate();
193 
194  return gui2::tpoint(rect_.width, rect_.height);
195 }
196 
198 {
199  recalculate();
200 
201  return (pango_layout_is_ellipsized(layout_) != 0);
202 }
203 
204 unsigned ttext::insert_text(const unsigned offset, const std::string& text)
205 {
206  if (text.empty() || length_ == maximum_length_) {
207  return 0;
208  }
209 
210  // do we really need that assert? utf8::insert will just append in this case, which seems fine
211  assert(offset <= length_);
212 
213  unsigned len = utf8::size(text);
214  if (length_ + len > maximum_length_) {
215  len = maximum_length_ - length_;
216  }
217  const utf8::string insert = text.substr(0, utf8::index(text, len));
218  utf8::string tmp = text_;
219  set_text(utf8::insert(tmp, offset, insert), false);
220  // report back how many characters were actually inserted (e.g. to move the cursor selection)
221  return len;
222 }
223 
224 bool ttext::insert_unicode(const unsigned offset, ucs4::char_t unicode)
225 {
226  return (insert_unicode(offset, ucs4::string(1, unicode)) == 1);
227 }
228 
229 unsigned ttext::insert_unicode(const unsigned offset, const ucs4::string& unicode)
230 {
231  const utf8::string insert = unicode_cast<utf8::string>(unicode);
232  return insert_text(offset, insert);
233 }
234 
236  const unsigned column, const unsigned line) const
237 {
238  recalculate();
239 
240  // First we need to determine the byte offset, if more routines need it it
241  // would be a good idea to make it a separate function.
242  titor itor(layout_);
243 
244  // Go the wanted line.
245  if(line != 0) {
246  if(pango_layout_get_line_count(layout_) >= static_cast<int>(line)) {
247  return gui2::tpoint(0, 0);
248  }
249 
250  for(size_t i = 0; i < line; ++i) {
251  pango_layout_iter_next_line(itor);
252  }
253  }
254 
255  // Go the wanted column.
256  for(size_t i = 0; i < column; ++i) {
257  if(!pango_layout_iter_next_char(itor)) {
258  // It seems that the documentation is wrong and causes and off by
259  // one error... the result should be false if already at the end of
260  // the data when started.
261  if(i + 1 == column) {
262  break;
263  }
264  // We are beyond data.
265  return gui2::tpoint(0, 0);
266  }
267  }
268 
269  // Get the byte offset
270  const int offset = pango_layout_iter_get_index(itor);
271 
272  // Convert the byte offset in a position.
273  PangoRectangle rect;
274  pango_layout_get_cursor_pos(layout_, offset, &rect, nullptr);
275 
276  return gui2::tpoint(PANGO_PIXELS(rect.x), PANGO_PIXELS(rect.y));
277 }
278 
279 std::string ttext::get_token(const gui2::tpoint & position, const char * delim) const
280 {
281  recalculate();
282 
283  // Get the index of the character.
284  int index, trailing;
285  if (!pango_layout_xy_to_index(layout_, position.x * PANGO_SCALE,
286  position.y * PANGO_SCALE, &index, &trailing)) {
287  return "";
288  }
289 
290  std::string txt = pango_layout_get_text(layout_);
291 
292  std::string d(delim);
293 
294  if (index < 0 || (static_cast<size_t>(index) >= txt.size()) || d.find(txt.at(index)) != std::string::npos) {
295  return ""; // if the index is out of bounds, or the index character is a delimiter, return nothing
296  }
297 
298  size_t l = index;
299  while (l > 0 && (d.find(txt.at(l-1)) == std::string::npos)) {
300  --l;
301  }
302 
303  size_t r = index + 1;
304  while (r < txt.size() && (d.find(txt.at(r)) == std::string::npos)) {
305  ++r;
306  }
307 
308  return txt.substr(l,r-l);
309 }
310 
311 std::string ttext::get_link(const gui2::tpoint & position) const
312 {
313  if (!link_aware_) {
314  return "";
315  }
316 
317  std::string tok = get_token(position, " \n\r\t");
318 
319  if (looks_like_url(tok)) {
320  return tok;
321  } else {
322  return "";
323  }
324 }
325 
327 {
328  recalculate();
329 
330  // Get the index of the character.
331  int index, trailing;
332  pango_layout_xy_to_index(layout_, position.x * PANGO_SCALE,
333  position.y * PANGO_SCALE, &index, &trailing);
334 
335  // Extract the line and the offset in pixels in that line.
336  int line, offset;
337  pango_layout_index_to_line_x(layout_, index, trailing, &line, &offset);
338  offset = PANGO_PIXELS(offset);
339 
340  // Now convert this offset to a column, this way is a bit hacky but haven't
341  // found a better solution yet.
342 
343  /**
344  * @todo There's still a bug left. When you select a text which is in the
345  * ellipses on the right side the text gets reformatted with ellipses on
346  * the left and the selected character is not the one under the cursor.
347  * Other widget toolkits don't show ellipses and have no indication more
348  * text is available. Haven't found what the best thing to do would be.
349  * Until that time leave it as is.
350  */
351  for(size_t i = 0; ; ++i) {
352  const int pos = get_cursor_position(i, line).x;
353 
354  if(pos == offset) {
355  return gui2::tpoint(i, line);
356  }
357  }
358 }
359 
360 bool ttext::set_text(const std::string& text, const bool markedup)
361 {
362  if(markedup != markedup_text_ || text != text_) {
363  assert(layout_);
364 
366  const std::string narrow = unicode_cast<utf8::string>(wide);
367  if(text != narrow) {
368  ERR_GUI_L << "ttext::" << __func__
369  << " text '" << text
370  << "' contains invalid utf-8, trimmed the invalid parts.\n";
371  }
372  if(markedup) {
373  if(!set_markup(narrow)) {
374  return false;
375  }
376  } else {
377  /*
378  * pango_layout_set_text after pango_layout_set_markup might
379  * leave the layout in an undefined state regarding markup so
380  * clear it unconditionally.
381  */
382  pango_layout_set_attributes(layout_, nullptr);
383  pango_layout_set_text(layout_, narrow.c_str(), narrow.size());
384  }
385  text_ = narrow;
386  length_ = wide.size();
387  markedup_text_ = markedup;
388  calculation_dirty_ = true;
389  surface_dirty_ = true;
390  }
391 
392  return true;
393 }
394 
396 {
397  if(fclass != font_class_) {
398  font_class_ = fclass;
399  calculation_dirty_ = true;
400  surface_dirty_ = true;
401  }
402 
403  return *this;
404 }
405 
407 {
408  unsigned int actual_size = preferences::font_scaled(font_size);
409  if(actual_size != font_size_) {
410  font_size_ = actual_size;
411  calculation_dirty_ = true;
412  surface_dirty_ = true;
413  }
414 
415  return *this;
416 }
417 
418 ttext& ttext::set_font_style(const unsigned font_style)
419 {
420  if(font_style != font_style_) {
421  font_style_ = font_style;
422  calculation_dirty_ = true;
423  surface_dirty_ = true;
424  }
425 
426  return *this;
427 }
428 
430 {
431  if(color != foreground_color_) {
433  surface_dirty_ = true;
434  }
435 
436  return *this;
437 }
438 
440 {
441  set_foreground_color((color.r << 16) + (color.g << 8) + color.b);
442 
443  return *this;
444 }
445 
447 {
448  if(width <= 0) {
449  width = -1;
450  }
451 
452  if(width != maximum_width_) {
453  assert(context_);
454 #if 0
455  /**
456  * todo Adding 4 extra pixels feels a bit hacky.
457  *
458  * For some reason it's needed since the following scenario fails:
459  * - pango_layout_set_width(value)
460  * - pango_layout_get_pixel_extents() -> max_width_1
461  * - pango_layout_set_width(max_width_1)
462  * - pango_layout_get_pixel_extents() -> max_width_2
463  *
464  * Now it can happen max_width_2 < max_width_1. Adding the 4 seems to
465  * "fix" the problem.
466  */
467  pango_layout_set_width(layout_, width == -1
468  ? -1
469  : (width + 4) * PANGO_SCALE);
470 #endif
472  calculation_dirty_ = true;
473  surface_dirty_ = true;
474  }
475 
476  return *this;
477 }
478 
479 ttext& ttext::set_characters_per_line(const unsigned characters_per_line)
480 {
481  if(characters_per_line != characters_per_line_) {
482  characters_per_line_ = characters_per_line;
483 
484  calculation_dirty_ = true;
485  surface_dirty_ = true;
486  }
487 
488  return *this;
489 }
490 
492 {
493  if(height <= 0) {
494  height = -1;
495  multiline = false;
496  }
497 
498  if(height != maximum_height_) {
499  assert(context_);
500 
501  pango_layout_set_height(layout_, !multiline ? -1 : height * PANGO_SCALE);
503  calculation_dirty_ = true;
504  surface_dirty_ = true;
505  }
506 
507  return *this;
508 }
509 
510 ttext& ttext::set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
511 {
512  if(ellipse_mode != ellipse_mode_) {
513  assert(context_);
514 
515  pango_layout_set_ellipsize(layout_, ellipse_mode);
516  ellipse_mode_ = ellipse_mode;
517  calculation_dirty_ = true;
518  surface_dirty_ = true;
519  }
520 
521  return *this;
522 }
523 
524 ttext &ttext::set_alignment(const PangoAlignment alignment)
525 {
526  if (alignment == alignment_) return *this;
527  pango_layout_set_alignment(layout_, alignment);
528  alignment_ = alignment;
529  surface_dirty_ = true;
530  return *this;
531 }
532 
533 ttext& ttext::set_maximum_length(const size_t maximum_length)
534 {
535  if(maximum_length != maximum_length_) {
536  maximum_length_ = maximum_length;
537  if(length_ > maximum_length_) {
538  utf8::string tmp = text_;
540  }
541  }
542 
543  return *this;
544 }
545 
547 {
548  if (link_aware_ != b) {
549  calculation_dirty_ = true;
550  surface_dirty_ = true;
551  link_aware_ = b;
552  }
553  return *this;
554 }
555 
557 {
558  if(color != link_color_) {
559  link_color_ = color;
560  calculation_dirty_ = true;
561  surface_dirty_ = true;
562  }
563 
564  return *this;
565 }
566 
567 namespace {
568 
569 /** Small helper class to make sure the font object is destroyed properly. */
570 class tfont
571  : private boost::noncopyable
572 {
573 public:
574  tfont(const std::string& name, const unsigned size, const unsigned style) :
575  font_(pango_font_description_new())
576  {
577  pango_font_description_set_family(font_, name.c_str());
578  pango_font_description_set_size(font_, size * PANGO_SCALE);
579 
580  if(style != ttext::STYLE_NORMAL) {
581  if(style & ttext::STYLE_ITALIC) {
582  pango_font_description_set_style(font_, PANGO_STYLE_ITALIC);
583  }
584  if(style & ttext::STYLE_BOLD) {
585  pango_font_description_set_weight(font_, PANGO_WEIGHT_BOLD);
586  }
587  if(style & ttext::STYLE_UNDERLINE) {
588  /* Do nothing here, underline is a property of the layout. */
589  }
590  }
591  }
592 
593  ~tfont() { pango_font_description_free(font_); }
594 
595  PangoFontDescription* get() { return font_; }
596 
597 private:
598  PangoFontDescription *font_;
599 };
600 
601 std::ostream& operator<<(std::ostream& s, const PangoRectangle &rect)
602 {
603  s << rect.x << ',' << rect.y << " x " << rect.width << ',' << rect.height;
604  return s;
605 }
606 
607 } // namespace
608 
609 void ttext::recalculate(const bool force) const
610 {
611  if(calculation_dirty_ || force) {
612  assert(layout_);
613 
614  calculation_dirty_ = false;
615  surface_dirty_ = true;
616 
618  pango_layout_set_font_description(layout_, font.get());
619 
620  if(font_style_ & ttext::STYLE_UNDERLINE) {
621  PangoAttrList *attribute_list = pango_attr_list_new();
622  pango_attr_list_insert(attribute_list
623  , pango_attr_underline_new(PANGO_UNDERLINE_SINGLE));
624 
625  pango_layout_set_attributes (layout_, attribute_list);
626  pango_attr_list_unref(attribute_list);
627  }
628 
629  int maximum_width = 0;
630  if(characters_per_line_ != 0) {
631  PangoFont* f = pango_font_map_load_font(
632  pango_cairo_font_map_get_default()
633  , context_
634  , font.get());
635 
636  PangoFontMetrics* m = pango_font_get_metrics(f, nullptr);
637 
638  int w = pango_font_metrics_get_approximate_char_width(m);
640 
641  maximum_width = ceil(pango_units_to_double(w));
642  } else {
643  maximum_width = maximum_width_;
644  }
645 
646  if(maximum_width_ != -1) {
647  maximum_width = std::min(maximum_width, maximum_width_);
648  }
649 
650  /*
651  * See set_maximum_width for some more background info as well.
652  * In order to fix the problem first set a width which seems to render
653  * correctly then lower it to fit. For the campaigns the 4 does "the
654  * right thing" for the terrain labels it should use the value 0 to set
655  * the ellipse properly. Need to see whether this is a bug in pango or
656  * a bug in my understanding of the pango api.
657  */
658  int hack = 4;
659  do {
660  pango_layout_set_width(layout_, maximum_width == -1
661  ? -1
662  : (maximum_width + hack) * PANGO_SCALE);
663  pango_layout_get_pixel_extents(layout_, nullptr, &rect_);
664 
665  DBG_GUI_L << "ttext::" << __func__
666  << " text '" << gui2::debug_truncate(text_)
667  << "' maximum_width " << maximum_width
668  << " hack " << hack
669  << " width " << rect_.x + rect_.width
670  << ".\n";
671 
672  --hack;
673  } while(maximum_width != -1
674  && hack >= 0 && rect_.x + rect_.width > maximum_width);
675 
676  DBG_GUI_L << "ttext::" << __func__
677  << " text '" << gui2::debug_truncate(text_)
678  << "' font_size " << font_size_
679  << " markedup_text " << markedup_text_
680  << " font_style " << std::hex << font_style_ << std::dec
681  << " maximum_width " << maximum_width
682  << " maximum_height " << maximum_height_
683  << " result " << rect_
684  << ".\n";
685  if(maximum_width != -1 && rect_.x + rect_.width > maximum_width) {
686  DBG_GUI_L << "ttext::" << __func__
687  << " text '" << gui2::debug_truncate(text_)
688  << " ' width " << rect_.x + rect_.width
689  << " greater as the wanted maximum of " << maximum_width
690  << ".\n";
691  }
692  }
693 }
694 
696 {
697  // 1-based, from 1 to 255.
698  unsigned values[255];
700  : values()
701  {
702  for (int i = 1; i < 256; ++i) values[i - 1] = (255 * 256) / i;
703  }
704 };
705 
707 
708 
709 #ifndef _WIN32
710 /**
711  * Converts from premultiplied alpha to plain alpha.
712  * @param p pointer to a 4-byte endian-dependent color.
713  */
714 static void decode_pixel(unsigned char *p)
715 {
716 // Assume everything not compiled with gcc to be on a little endian platform.
717 #if defined(__GNUC__) && defined(__BIG_ENDIAN__)
718  int alpha = p[0];
719 #else
720  int alpha = p[3];
721 #endif
722  if (alpha == 0) return;
723 
724  int div = decode_table.values[alpha - 1];
725 
726 #define DECODE(i) \
727  do { \
728  unsigned color = p[i]; \
729  color = color * div / 256; \
730  if (color > 255) color = 255; \
731  p[i] = color; \
732  } while (0)
733 
734 #if defined(__GNUC__) && defined(__BIG_ENDIAN__)
735  DECODE(3);
736 #else
737  DECODE(0);
738 #endif
739  DECODE(1);
740  DECODE(2);
741 }
742 #endif
743 
744 
745 void ttext::rerender(const bool force) const
746 {
747  if(surface_dirty_ || force) {
748  assert(layout_);
749 
750  recalculate(force);
751  surface_dirty_ = false;
752 
753  int width = rect_.x + rect_.width;
754  int height = rect_.y + rect_.height;
755  if (maximum_width_ > 0 && width > maximum_width_ ) width = maximum_width_;
756  if (maximum_height_ > 0 && height > maximum_height_) height = maximum_height_;
757  const unsigned stride = width * 4;
758  create_surface_buffer(stride * height);
759 
760  cairo_surface_t *cairo_surface =
761  cairo_image_surface_create_for_data(surface_buffer_,
762  CAIRO_FORMAT_ARGB32, width, height, stride);
763  cairo_t *cr = cairo_create(cairo_surface);
764 
765  /* set color (used for foreground). */
766  cairo_set_source_rgba(cr,
767  (foreground_color_ >> 24) / 256.0,
768  ((foreground_color_ >> 16) & 0xFF) / 256.0,
769  ((foreground_color_ >> 8) & 0xFF) / 256.0,
770  (foreground_color_ & 0xFF) / 256.0);
771 
772  pango_cairo_show_layout(cr, layout_);
773 
774 #ifndef _WIN32
775 
776  // The cairo surface is in CAIRO_FORMAT_ARGB32 which uses
777  // pre-multiplied alpha. SDL doesn't use that so the pixels need to be
778  // decoded again.
779  for (int y = 0; y < height; ++y) {
780  for (int x = 0; x < width; ++x)
781  {
782  unsigned char *pixel = &surface_buffer_[(y * width + x) * 4];
783  decode_pixel(pixel);
784  }
785  }
786 #else
787  // The solution code above doesn't seem to work properly on windows so
788  // use the old trick of drawing the same text a few times.
789  pango_cairo_show_layout(cr, layout_);
790  pango_cairo_show_layout(cr, layout_);
791  pango_cairo_show_layout(cr, layout_);
792 #endif
793  surface_.assign(SDL_CreateRGBSurfaceFrom(
794  surface_buffer_, width, height, 32, stride,
795  0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000));
796 #ifdef SDL_GPU
797  texture_ = sdl::timage(surface_);
798 #endif
799  cairo_destroy(cr);
800  cairo_surface_destroy(cairo_surface);
801  }
802 }
803 
804 void ttext::create_surface_buffer(const size_t size) const
805 {
806  // clear old buffer
807  if(surface_buffer_) {
808  surface_.assign(nullptr);
809  delete[] surface_buffer_;
810  }
811 
812  surface_buffer_ = new unsigned char [size];
813  memset(surface_buffer_, 0, size);
814 }
815 
816 bool ttext::set_markup(const std::string & text) {
817  if (!link_aware_) {
818  return set_markup_helper(text);
819  } else {
820  std::string delim = " \n\r\t";
821 
822  // Tokenize according to these delimiters, and stream the results of `handle_token` on each token to get the new text.
823 
824  std::stringstream ss;
825 
826  int last_delim = -1;
827  for (size_t index = 0; index < text.size(); ++index) {
828  if (delim.find(text.at(index)) != std::string::npos) {
829  ss << handle_token(text.substr(last_delim + 1, index - last_delim - 1)); // want to include chars from range since last token, dont want to include any delimiters
830  ss << text.at(index);
831  last_delim = index;
832  }
833  }
834  if (last_delim < static_cast<int>(text.size()) - 1) {
835  ss << handle_token(text.substr(last_delim + 1, text.size() - last_delim - 1));
836  }
837 
838  return set_markup_helper(ss.str());
839  }
840 }
841 
842 static bool looks_like_url(const std::string & str)
843 {
844  return (str.size() >= 8) && ((str.substr(0,7) == "http://") || (str.substr(0,8) == "https://"));
845 }
846 
848 {
849  if (looks_like_url(token)) {
850  return "<span underline=\'single\' color=\'" + link_color_ + "\'>" + token + "</span>";
851  } else {
852  return token;
853  }
854 }
855 
857 {
858  if(pango_parse_markup(text.c_str(), text.size()
859  , 0, nullptr, nullptr, nullptr, nullptr)) {
860 
861  /* Markup is valid so set it. */
862  pango_layout_set_markup(layout_, text.c_str(), text.size());
863  return true;
864  }
865 
866  /*
867  * The markup is invalid. Try to recover.
868  *
869  * The pango engine tested seems to accept stray single quotes »'« and
870  * double quotes »"«. Stray ampersands »&« seem to give troubles.
871  * So only try to recover from broken ampersands, by simply replacing them
872  * with the escaped version.
873  */
874  std::string semi_escaped;
875  for(const char c : text) {
876  if(c == '&') {
877  semi_escaped += "&amp;";
878  } else {
879  semi_escaped += c;
880  }
881  }
882 
883  /*
884  * If at least one ampersand is replaced the semi-escaped string
885  * is longer than the original. If this isn't the case then the
886  * markup wasn't (only) broken by ampersands in the first place.
887  */
888  if(text.size() == semi_escaped.size()
889  || !pango_parse_markup(semi_escaped.c_str(), semi_escaped.size()
890  , 0, nullptr, nullptr, nullptr, nullptr)) {
891 
892  /* Fixing the ampersands didn't work. */
893  ERR_GUI_L << "ttext::" << __func__
894  << " text '" << text
895  << "' has broken markup, set to normal text.\n";
896 
897  set_text(_("The text contains invalid markup: ") + text, false);
898  return false;
899  }
900 
901  /* Replacement worked, still warn the user about the error. */
902  ERR_GUI_L << "ttext::" << __func__
903  << " text '" << text
904  << "' has unescaped ampersands '&', escaped them.\n";
905 
906  pango_layout_set_markup(layout_, semi_escaped.c_str(), semi_escaped.size());
907  return true;
908 }
909 
910 } // namespace font
911 
Define the common log macros for the gui toolkit.
Uint32 foreground_color_
The foreground color.
Definition: text.hpp:285
void create_surface_buffer(const size_t size) const
Creates a new buffer.
Definition: text.cpp:804
static bool looks_like_url(const std::string &token)
Definition: text.cpp:842
std::vector< char_t > string
size_t index(const utf8::string &str, const size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:73
ttext & set_alignment(const PangoAlignment alignment)
Definition: text.cpp:524
tformula< int > maximum_height_
The maximum height for the text.
Definition: canvas.cpp:1286
#define DBG_GUI_L
Definition: log.hpp:58
family_class
Font classes for get_font_families().
ttext & set_link_aware(bool b)
Definition: text.cpp:546
void recalculate(const bool force=false) const
Recalculates the text.
Definition: text.cpp:609
const std::string & text() const
Definition: text.hpp:220
Graphical text output.
const GLfloat * c
Definition: glew.h:12741
int pos
Definition: formula.cpp:800
ttext & set_font_style(const unsigned font_style)
Definition: text.cpp:418
ttext & set_maximum_length(const size_t maximum_length)
Definition: text.cpp:533
GLsizei stride
Definition: glew.h:1491
bool calculation_dirty_
The text has two dirty states:
Definition: text.hpp:339
unsigned insert_text(const unsigned offset, const std::string &text)
Inserts UTF-8 text.
Definition: text.cpp:204
ucs4_convert_impl::enableif< TD, typename TS::value_type >::type unicode_cast(const TS &source)
bool is_truncated() const
Has the text been truncated?
Definition: text.cpp:197
ttext & set_font_size(const unsigned font_size)
Definition: text.cpp:406
PangoLayout * layout_
Definition: text.hpp:253
STL namespace.
gui2::tpoint get_column_line(const gui2::tpoint &position) const
Gets the column of line of the character at the position.
Definition: text.cpp:326
GLint GLint GLint GLint GLint GLint y
Definition: glew.h:1220
bool markedup_text_
Is the text markedup if so the markedup render routines need to be used.
Definition: text.hpp:267
ttext & set_maximum_width(int width)
Definition: text.cpp:446
#define d
ttext & set_link_color(const std::string &color)
Definition: text.cpp:556
bool surface_dirty_
The dirty state of the surface.
Definition: text.hpp:354
tformula< t_string > text_
The text to draw.
Definition: canvas.cpp:1268
-file util.hpp
PangoAlignment alignment_
The alignment of the text.
Definition: text.hpp:327
GLboolean GLenum GLenum GLvoid * values
Definition: glew.h:3799
GLdouble l
Definition: glew.h:6966
GLdouble GLdouble GLdouble b
Definition: glew.h:6966
GLintptr offset
Definition: glew.h:1650
std::string link_color_
The color to render links in.
Definition: text.hpp:273
unsigned characters_per_line_
The number of characters per line.
Definition: text.hpp:313
#define ERR_GUI_L
Definition: log.hpp:61
int get_height() const
Returns the height needed for the text.
Definition: text.cpp:185
const t_string & get_font_families(family_class fclass)
Returns the currently defined fonts.
Definition: font.cpp:1110
bool set_markup(const std::string &text)
Sets the markup'ed text.
Definition: text.cpp:816
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:82
GLuint64EXT * result
Definition: glew.h:10727
std::ostream & operator<<(std::ostream &s, ai::attack_result const &r)
Definition: actions.cpp:1127
std::string get_link(const gui2::tpoint &position) const
Checks if position points to a character in a link in the text, returns it if so, empty string otherw...
Definition: text.cpp:311
unsigned font_style_
The style of the text.
Definition: canvas.cpp:1259
int y
y coordinate.
Definition: point.hpp:34
ttext & set_maximum_height(int height, bool multiline)
Definition: text.cpp:491
GLubyte GLubyte GLubyte GLubyte w
Definition: glew.h:1858
GLenum GLsizei len
Definition: glew.h:5662
gui2::tpoint get_size() const
Returns the size needed for the text.
Definition: text.cpp:190
int font_scaled(int size)
font::family_class font_class_
The font family class used.
Definition: text.hpp:276
static const unsigned STYLE_BOLD
Bold text.
Definition: text.hpp:145
gui2::tpoint get_cursor_position(const unsigned column, const unsigned line=0) const
Gets the location for the cursor.
Definition: text.cpp:235
GLclampf GLclampf GLclampf alpha
Definition: glew.h:1488
int maximum_height_
The maximum height of the text.
Definition: text.hpp:321
ttext & set_characters_per_line(const unsigned characters_per_line)
Definition: text.cpp:479
void rerender(const bool force=false) const
Renders the text.
Definition: text.cpp:745
bool link_aware_
Are hyperlinks in the text marked-up, and will get_link return them.
Definition: text.hpp:270
static const unsigned STYLE_ITALIC
Italicized text.
Definition: text.hpp:146
unsigned font_style_
The style of the font, this is an orred mask of the font flags.
Definition: text.hpp:282
GLfloat GLfloat p
Definition: glew.h:12766
utf8::string & truncate(utf8::string &str, const size_t size)
Truncates a UTF-8 string to the specified number of characters.
Definition: unicode.cpp:119
PangoRectangle rect_
Definition: text.hpp:254
GLuint color
Definition: glew.h:5801
static const unsigned STYLE_NORMAL
The flags have the same values as the ones in SDL_TTF so it's easy to mix them for now...
Definition: text.hpp:144
int x
x coordinate.
Definition: point.hpp:31
GLenum GLenum GLvoid GLvoid * column
Definition: glew.h:3805
size_t size(const utf8::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:88
std::string escape_text(const std::string &text)
Escapes the markup characters in a text.
Definition: text.cpp:75
#define DECODE(i)
std::string get_token(const gui2::tpoint &position, const char *delimiters=" \n\r\t") const
Gets the largest collection of characters, including the token at position, and not including any cha...
Definition: text.cpp:279
std::map< std::string, tfilter >::iterator itor
Definition: filter.cpp:199
static void decode_pixel(unsigned char *p)
Converts from premultiplied alpha to plain alpha.
Definition: text.cpp:714
surface render() const
Returns the rendered text.
Definition: text.cpp:166
std::string handle_token(const std::string &token) const
Definition: text.cpp:847
surface surface_
The surface to render upon used as a cache.
Definition: text.hpp:257
std::string debug_truncate(const std::string &text)
Returns a truncated version of the text.
Definition: helper.cpp:157
GLuint index
Definition: glew.h:1782
boost::uint32_t char_t
size_t i
Definition: function.cpp:1057
tformula< bool > link_aware_
The link aware switch of the text.
Definition: canvas.cpp:1274
Holds a 2D point.
Definition: point.hpp:24
bool insert_unicode(const unsigned offset, ucs4::char_t unicode)
Inserts a unicode char.
Definition: text.cpp:224
GLint GLint GLint GLint GLint x
Definition: glew.h:1220
PangoContext * context_
Definition: text.hpp:252
GLdouble GLdouble GLdouble r
Definition: glew.h:1374
int font_size_
Definition: font.cpp:604
GLint GLint GLint GLint GLint GLint GLsizei GLsizei height
Definition: glew.h:1220
GLuint const GLchar * name
Definition: glew.h:1782
unsigned characters_per_line_
The number of characters per line.
Definition: canvas.cpp:1283
tformula< int > maximum_width_
The maximum width for the text.
Definition: canvas.cpp:1280
unsigned char * surface_buffer_
Buffer to store the image on.
Definition: text.hpp:373
GLsizeiptr size
Definition: glew.h:1649
const GLdouble * m
Definition: glew.h:6968
tformula< std::string > link_color_
The link color of the text.
Definition: canvas.cpp:1277
unsigned font_size_
The font size to draw.
Definition: text.hpp:279
ttext & set_family_class(font::family_class fclass)
Definition: text.cpp:395
CALLABLE_WRAPPER_INPUT_END if(key=="terrain")
int get_width() const
Returns the width needed for the text.
Definition: text.cpp:180
int maximum_width_
The maximum width of the text.
Definition: text.hpp:295
void assign(const surface &o)
Definition: utils.hpp:83
#define c
Definition: glew.h:12743
bool set_text(const std::string &text, const bool markedup)
Sets the text to render.
Definition: text.cpp:360
GLint GLint GLint GLint GLint GLint GLsizei width
Definition: glew.h:1220
utf8::string & insert(utf8::string &str, const size_t pos, const utf8::string &insert)
Insert a UTF-8 string at the specified position.
Definition: unicode.cpp:101
PangoEllipsizeMode ellipse_mode_
The way too long text is shown depends on this mode.
Definition: text.hpp:324
std::string text_
The text to draw (stored as UTF-8).
Definition: text.hpp:264
bool set_markup_helper(const std::string &text)
Definition: text.cpp:856
GLdouble s
Definition: glew.h:1358
const int font_size
size_t maximum_length_
The maximum length of the text.
Definition: text.hpp:330
static const unsigned STYLE_UNDERLINE
Underlined text.
Definition: text.hpp:147
GLsizei const GLcharARB ** string
Definition: glew.h:4503
Text class.
Definition: text.hpp:66
unsigned values[255]
Definition: text.cpp:698
size_t length_
Length of the text.
Definition: text.hpp:342
ttext & set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
Definition: text.cpp:510
std::string string
GLclampf f
Definition: glew.h:3024
ttext & set_foreground_color(const Uint32 color)
Definition: text.cpp:429