The Battle for Wesnoth  1.13.10+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 - 2017 by Mark de Wever <koraq@xs4all.nl>
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 "font/text.hpp"
18 
19 #include "font/font_config.hpp"
20 
21 #include "font/pango/escape.hpp"
22 #include "font/pango/font.hpp"
23 #include "font/pango/hyperlink.hpp"
25 
26 #include "gettext.hpp"
27 #include "gui/widgets/helper.hpp"
28 #include "gui/core/log.hpp"
29 #include "gui/core/point.hpp"
30 #include "sdl/utils.hpp"
33 #include "preferences/general.hpp"
34 
35 #include <cassert>
36 #include <cstring>
37 #include <stdexcept>
38 
39 namespace font {
40 
42 #if PANGO_VERSION_CHECK(1,22,0)
43  : context_(pango_font_map_create_context(pango_cairo_font_map_get_default()), g_object_unref)
44 #else
45  : context_(pango_cairo_font_map_create_context((
46  reinterpret_cast<PangoCairoFontMap*>(pango_cairo_font_map_get_default()))), g_object_unref)
47 #endif
48  , layout_(pango_layout_new(context_.get()), g_object_unref)
49  , rect_()
50  , sublayouts_()
51  , surface_()
52  , text_()
53  , markedup_text_(false)
54  , link_aware_(false)
55  , link_color_()
56  , font_class_(font::FONT_SANS_SERIF)
57  , font_size_(14)
58  , font_style_(STYLE_NORMAL)
59  , foreground_color_() // solid white
60  , maximum_width_(-1)
61  , characters_per_line_(0)
62  , maximum_height_(-1)
63  , ellipse_mode_(PANGO_ELLIPSIZE_END)
64  , alignment_(PANGO_ALIGN_LEFT)
65  , maximum_length_(std::string::npos)
66  , calculation_dirty_(true)
67  , length_(0)
68  , surface_dirty_(true)
69  , surface_buffer_()
70 {
71  // With 72 dpi the sizes are the same as with SDL_TTF so hardcoded.
72  pango_cairo_context_set_resolution(context_.get(), 72.0);
73 
74  pango_layout_set_ellipsize(layout_.get(), ellipse_mode_);
75  pango_layout_set_alignment(layout_.get(), alignment_);
76  pango_layout_set_wrap(layout_.get(), PANGO_WRAP_WORD_CHAR);
77 
78  /*
79  * Set the pango spacing a bit bigger since the default is deemed to small
80  * http://www.wesnoth.org/forum/viewtopic.php?p=358832#p358832
81  */
82  pango_layout_set_spacing(layout_.get(), 4 * PANGO_SCALE);
83 
84  cairo_font_options_t *fo = cairo_font_options_create();
85  cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL);
86  cairo_font_options_set_hint_metrics(fo, CAIRO_HINT_METRICS_ON);
87  cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_DEFAULT);
88 
89  pango_cairo_context_set_font_options(context_.get(), fo);
90  cairo_font_options_destroy(fo);
91 }
92 
94 {
95  this->rerender();
96  return surface_;
97 }
98 
99 
101 {
102  return this->get_size().x;
103 }
104 
106 {
107  return this->get_size().y;
108 }
109 
111 {
112  this->recalculate();
113 
114  return gui2::point(rect_.width, rect_.height);
115 }
116 
118 {
119  this->recalculate();
120 
121  return (pango_layout_is_ellipsized(layout_.get()) != 0);
122 }
123 
124 unsigned pango_text::insert_text(const unsigned offset, const std::string& text)
125 {
126  if (text.empty() || length_ == maximum_length_) {
127  return 0;
128  }
129 
130  // do we really need that assert? utf8::insert will just append in this case, which seems fine
131  assert(offset <= length_);
132 
133  unsigned len = utf8::size(text);
134  if (length_ + len > maximum_length_) {
135  len = maximum_length_ - length_;
136  }
137  const utf8::string insert = text.substr(0, utf8::index(text, len));
138  utf8::string tmp = text_;
139  this->set_text(utf8::insert(tmp, offset, insert), false);
140  // report back how many characters were actually inserted (e.g. to move the cursor selection)
141  return len;
142 }
143 
144 bool pango_text::insert_unicode(const unsigned offset, ucs4::char_t unicode)
145 {
146  return this->insert_unicode(offset, ucs4::string(1, unicode)) == 1;
147 }
148 
149 unsigned pango_text::insert_unicode(const unsigned offset, const ucs4::string& unicode)
150 {
151  const utf8::string insert = unicode_cast<utf8::string>(unicode);
152  return this->insert_text(offset, insert);
153 }
154 
156  const unsigned column, const unsigned line) const
157 {
158  this->recalculate();
159 
160  // First we need to determine the byte offset, if more routines need it it
161  // would be a good idea to make it a separate function.
162  std::unique_ptr<PangoLayoutIter, std::function<void(PangoLayoutIter*)>> itor(
163  pango_layout_get_iter(layout_.get()), pango_layout_iter_free);
164 
165  // Go the wanted line.
166  if(line != 0) {
167  if(pango_layout_get_line_count(layout_.get()) >= static_cast<int>(line)) {
168  return gui2::point(0, 0);
169  }
170 
171  for(size_t i = 0; i < line; ++i) {
172  pango_layout_iter_next_line(itor.get());
173  }
174  }
175 
176  // Go the wanted column.
177  for(size_t i = 0; i < column; ++i) {
178  if(!pango_layout_iter_next_char(itor.get())) {
179  // It seems that the documentation is wrong and causes and off by
180  // one error... the result should be false if already at the end of
181  // the data when started.
182  if(i + 1 == column) {
183  break;
184  }
185  // We are beyond data.
186  return gui2::point(0, 0);
187  }
188  }
189 
190  // Get the byte offset
191  const int offset = pango_layout_iter_get_index(itor.get());
192 
193  // Convert the byte offset in a position.
194  PangoRectangle rect;
195  pango_layout_get_cursor_pos(layout_.get(), offset, &rect, nullptr);
196 
197  return gui2::point(PANGO_PIXELS(rect.x), PANGO_PIXELS(rect.y));
198 }
199 
200 std::string pango_text::get_token(const gui2::point & position, const char * delim) const
201 {
202  this->recalculate();
203 
204  // Get the index of the character.
205  int index, trailing;
206  if (!pango_layout_xy_to_index(layout_.get(), position.x * PANGO_SCALE,
207  position.y * PANGO_SCALE, &index, &trailing)) {
208  return "";
209  }
210 
211  std::string txt = pango_layout_get_text(layout_.get());
212 
213  std::string d(delim);
214 
215  if (index < 0 || (static_cast<size_t>(index) >= txt.size()) || d.find(txt.at(index)) != std::string::npos) {
216  return ""; // if the index is out of bounds, or the index character is a delimiter, return nothing
217  }
218 
219  size_t l = index;
220  while (l > 0 && (d.find(txt.at(l-1)) == std::string::npos)) {
221  --l;
222  }
223 
224  size_t r = index + 1;
225  while (r < txt.size() && (d.find(txt.at(r)) == std::string::npos)) {
226  ++r;
227  }
228 
229  return txt.substr(l,r-l);
230 }
231 
233 {
234  if (!link_aware_) {
235  return "";
236  }
237 
238  std::string tok = this->get_token(position, " \n\r\t");
239 
240  if (looks_like_url(tok)) {
241  return tok;
242  } else {
243  return "";
244  }
245 }
246 
248 {
249  this->recalculate();
250 
251  // Get the index of the character.
252  int index, trailing;
253  pango_layout_xy_to_index(layout_.get(), position.x * PANGO_SCALE,
254  position.y * PANGO_SCALE, &index, &trailing);
255 
256  // Extract the line and the offset in pixels in that line.
257  int line, offset;
258  pango_layout_index_to_line_x(layout_.get(), index, trailing, &line, &offset);
259  offset = PANGO_PIXELS(offset);
260 
261  // Now convert this offset to a column, this way is a bit hacky but haven't
262  // found a better solution yet.
263 
264  /**
265  * @todo There's still a bug left. When you select a text which is in the
266  * ellipses on the right side the text gets reformatted with ellipses on
267  * the left and the selected character is not the one under the cursor.
268  * Other widget toolkits don't show ellipses and have no indication more
269  * text is available. Haven't found what the best thing to do would be.
270  * Until that time leave it as is.
271  */
272  for(size_t i = 0; ; ++i) {
273  const int pos = this->get_cursor_position(i, line).x;
274 
275  if(pos == offset) {
276  return gui2::point(i, line);
277  }
278  }
279 }
280 
281 bool pango_text::set_text(const std::string& text, const bool markedup)
282 {
283  if(markedup != markedup_text_ || text != text_) {
284  sublayouts_.clear();
285  if(layout_ == nullptr) {
286  layout_.reset(pango_layout_new(context_.get()));
287  }
288 
290  const std::string narrow = unicode_cast<utf8::string>(wide);
291  if(text != narrow) {
292  ERR_GUI_L << "pango_text::" << __func__
293  << " text '" << text
294  << "' contains invalid utf-8, trimmed the invalid parts.\n";
295  }
296  if(markedup) {
297  if(!this->set_markup(narrow, *layout_)) {
298  return false;
299  }
300  } else {
301  /*
302  * pango_layout_set_text after pango_layout_set_markup might
303  * leave the layout in an undefined state regarding markup so
304  * clear it unconditionally.
305  */
306  pango_layout_set_attributes(layout_.get(), nullptr);
307  pango_layout_set_text(layout_.get(), narrow.c_str(), narrow.size());
308  }
309  text_ = narrow;
310  length_ = wide.size();
311  markedup_text_ = markedup;
312  calculation_dirty_ = true;
313  surface_dirty_ = true;
314  }
315 
316  return true;
317 }
318 
320 {
321  if(fclass != font_class_) {
322  font_class_ = fclass;
323  calculation_dirty_ = true;
324  surface_dirty_ = true;
325  }
326 
327  return *this;
328 }
329 
331 {
332  unsigned int actual_size = preferences::font_scaled(font_size);
333  if(actual_size != font_size_) {
334  font_size_ = actual_size;
335  calculation_dirty_ = true;
336  surface_dirty_ = true;
337  }
338 
339  return *this;
340 }
341 
343 {
344  if(font_style != font_style_) {
345  font_style_ = font_style;
346  calculation_dirty_ = true;
347  surface_dirty_ = true;
348  }
349 
350  return *this;
351 }
352 
354 {
355  if(color != foreground_color_) {
356  foreground_color_ = color;
357  surface_dirty_ = true;
358  }
359 
360  return *this;
361 }
362 
364 {
365  if(width <= 0) {
366  width = -1;
367  }
368 
369  if(width != maximum_width_) {
370  // assert(context_);
371 #if 0
372  /**
373  * todo Adding 4 extra pixels feels a bit hacky.
374  *
375  * For some reason it's needed since the following scenario fails:
376  * - pango_layout_set_width(value)
377  * - pango_layout_get_pixel_extents() -> max_width_1
378  * - pango_layout_set_width(max_width_1)
379  * - pango_layout_get_pixel_extents() -> max_width_2
380  *
381  * Now it can happen max_width_2 < max_width_1. Adding the 4 seems to
382  * "fix" the problem.
383  */
384  pango_layout_set_width(layout_, width == -1
385  ? -1
386  : (width + 4) * PANGO_SCALE);
387 #endif
388  maximum_width_ = width;
389  calculation_dirty_ = true;
390  surface_dirty_ = true;
391  }
392 
393  return *this;
394 }
395 
396 pango_text& pango_text::set_characters_per_line(const unsigned characters_per_line)
397 {
398  if(characters_per_line != characters_per_line_) {
399  characters_per_line_ = characters_per_line;
400 
401  calculation_dirty_ = true;
402  surface_dirty_ = true;
403  }
404 
405  return *this;
406 }
407 
408 pango_text& pango_text::set_maximum_height(int height, bool multiline)
409 {
410  if(height <= 0) {
411  height = -1;
412  multiline = false;
413  }
414 
415  if(height != maximum_height_) {
416  // assert(context_);
417 
418  pango_layout_set_height(layout_.get(), !multiline ? -1 : height * PANGO_SCALE);
419  maximum_height_ = height;
420  calculation_dirty_ = true;
421  surface_dirty_ = true;
422  }
423 
424  return *this;
425 }
426 
427 pango_text& pango_text::set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
428 {
429  if(ellipse_mode != ellipse_mode_) {
430  // assert(context_);
431 
432  pango_layout_set_ellipsize(layout_.get(), ellipse_mode);
433  ellipse_mode_ = ellipse_mode;
434  calculation_dirty_ = true;
435  surface_dirty_ = true;
436  }
437 
438  return *this;
439 }
440 
441 pango_text &pango_text::set_alignment(const PangoAlignment alignment)
442 {
443  if (alignment != alignment_) {
444  pango_layout_set_alignment(layout_.get(), alignment);
445  alignment_ = alignment;
446  surface_dirty_ = true;
447  }
448 
449  return *this;
450 }
451 
452 pango_text& pango_text::set_maximum_length(const size_t maximum_length)
453 {
454  if(maximum_length != maximum_length_) {
455  maximum_length_ = maximum_length;
456  if(length_ > maximum_length_) {
457  utf8::string tmp = text_;
458  this->set_text(utf8::truncate(tmp, maximum_length_), false);
459  }
460  }
461 
462  return *this;
463 }
464 
466 {
467  if (link_aware_ != b) {
468  calculation_dirty_ = true;
469  surface_dirty_ = true;
470  link_aware_ = b;
471  }
472  return *this;
473 }
474 
476 {
477  if(color != link_color_) {
478  link_color_ = color;
479  calculation_dirty_ = true;
480  surface_dirty_ = true;
481  }
482 
483  return *this;
484 }
485 
486 
487 void pango_text::recalculate(const bool force) const
488 {
489  if(calculation_dirty_ || force) {
490  assert(layout_ != nullptr);
491 
492  calculation_dirty_ = false;
493  surface_dirty_ = true;
494 
496  }
497 }
498 
499 PangoRectangle pango_text::calculate_size(PangoLayout& layout) const
500 {
501  PangoRectangle size;
502 
504  pango_layout_set_font_description(&layout, font.get());
505 
507  PangoAttrList *attribute_list = pango_attr_list_new();
508  pango_attr_list_insert(attribute_list
509  , pango_attr_underline_new(PANGO_UNDERLINE_SINGLE));
510 
511  pango_layout_set_attributes(&layout, attribute_list);
512  pango_attr_list_unref(attribute_list);
513  }
514 
515  int maximum_width = 0;
516  if(characters_per_line_ != 0) {
517  PangoFont* f = pango_font_map_load_font(
518  pango_cairo_font_map_get_default(),
519  context_.get(),
520  font.get());
521 
522  PangoFontMetrics* m = pango_font_get_metrics(f, nullptr);
523 
524  int w = pango_font_metrics_get_approximate_char_width(m);
526 
527  maximum_width = ceil(pango_units_to_double(w));
528  } else {
529  maximum_width = maximum_width_;
530  }
531 
532  if(maximum_width_ != -1) {
533  maximum_width = std::min(maximum_width, maximum_width_);
534  }
535 
536  /*
537  * See set_maximum_width for some more background info as well.
538  * In order to fix the problem first set a width which seems to render
539  * correctly then lower it to fit. For the campaigns the 4 does "the
540  * right thing" for the terrain labels it should use the value 0 to set
541  * the ellipse properly. Need to see whether this is a bug in pango or
542  * a bug in my understanding of the pango api.
543  */
544  int hack = 4;
545  do {
546  pango_layout_set_width(&layout, maximum_width == -1
547  ? -1
548  : (maximum_width + hack) * PANGO_SCALE);
549  pango_layout_get_pixel_extents(&layout, nullptr, &size);
550 
551  DBG_GUI_L << "pango_text::" << __func__
552  << " text '" << gui2::debug_truncate(text_)
553  << "' maximum_width " << maximum_width
554  << " hack " << hack
555  << " width " << size.x + size.width
556  << ".\n";
557 
558  --hack;
559  } while(maximum_width != -1
560  && hack >= 0 && size.x + size.width > maximum_width);
561 
562  DBG_GUI_L << "pango_text::" << __func__
563  << " text '" << gui2::debug_truncate(text_)
564  << "' font_size " << font_size_
565  << " markedup_text " << markedup_text_
566  << " font_style " << std::hex << font_style_ << std::dec
567  << " maximum_width " << maximum_width
568  << " maximum_height " << maximum_height_
569  << " result " << size
570  << ".\n";
571  if(maximum_width != -1 && size.x + size.width > maximum_width) {
572  DBG_GUI_L << "pango_text::" << __func__
573  << " text '" << gui2::debug_truncate(text_)
574  << " ' width " << size.x + size.width
575  << " greater as the wanted maximum of " << maximum_width
576  << ".\n";
577  }
578 
579  return size;
580 }
581 
582 /***
583  * Inverse table
584  *
585  * Holds a high-precision inverse for each number i, that is, a number x such that x * i / 256 is close to 255.
586  */
588 {
589  unsigned values[256];
590 
592  {
593  values[0] = 0;
594  for (int i = 1; i < 256; ++i) {
595  values[i] = (255 * 256) / i;
596  }
597  }
598 
599  unsigned operator[](Uint8 i) const { return values[i]; }
600 };
601 
603 
604 /***
605  * Helper function for un-premultiplying alpha
606  * Div should be the high-precision inverse for the alpha value.
607  */
608 static void unpremultiply(Uint8 & value, const unsigned div) {
609  unsigned temp = (value * div) / 256u;
610  // Note: It's always the case that alpha * div < 256 if div is the inverse
611  // for alpha, so if cairo is computing premultiplied alpha by rounding down,
612  // this min is not necessary. However, if cairo generates illegal output,
613  // the min may be selected.
614  // It's probably not worth removing the min, since branch prediction will
615  // make it essentially free if one of the branches is never actually
616  // selected.
617  value = std::min(255u, temp);
618 }
619 
620 /**
621  * Converts from cairo-format ARGB32 premultiplied alpha to plain alpha.
622  * @param c a uint32 representing the color
623  */
624 static void from_cairo_format(Uint32 & c)
625 {
626  Uint8 a = (c >> 24) & 0xff;
627  Uint8 r = (c >> 16) & 0xff;
628  Uint8 g = (c >> 8) & 0xff;
629  Uint8 b = c & 0xff;
630 
631  const unsigned div = inverse_table_[a];
632  unpremultiply(r, div);
633  unpremultiply(g, div);
634  unpremultiply(b, div);
635 
636  c = (static_cast<Uint32>(a) << 24) | (static_cast<Uint32>(r) << 16) | (static_cast<Uint32>(g) << 8) | static_cast<Uint32>(b);
637 }
638 
639 void pango_text::render(PangoLayout& layout, const PangoRectangle& rect, const size_t surface_buffer_offset, const unsigned stride)
640 {
641  int width = rect.x + rect.width;
642  int height = rect.y + rect.height;
643  if(maximum_width_ > 0) { width = std::min(width, maximum_width_); }
644  if(maximum_height_ > 0) { height = std::min(height, maximum_height_); }
645 
646  cairo_format_t format = CAIRO_FORMAT_ARGB32;
647 
648  uint8_t* buffer = &surface_buffer_[surface_buffer_offset];
649 
650  std::unique_ptr<cairo_surface_t, std::function<void(cairo_surface_t*)>> cairo_surface(
651  cairo_image_surface_create_for_data(buffer, format, width, height, stride), cairo_surface_destroy);
652  std::unique_ptr<cairo_t, std::function<void(cairo_t*)>> cr(cairo_create(cairo_surface.get()), cairo_destroy);
653 
654  if(cairo_status(cr.get()) == CAIRO_STATUS_INVALID_SIZE) {
655  if(!is_surface_split()) {
656  split_surface();
657 
658  PangoRectangle upper_rect = calculate_size(*sublayouts_[0]);
659  PangoRectangle lower_rect = calculate_size(*sublayouts_[1]);
660 
661  render(*sublayouts_[0], upper_rect, 0u, stride);
662  render(*sublayouts_[1], lower_rect, upper_rect.height * stride, stride);
663 
664  return;
665  } else {
666  // If this occurs in practice, it can be fixed by implementing recursive splitting.
667  throw std::length_error("Text is too long to render");
668  }
669  }
670 
671  /* set color (used for foreground). */
672  cairo_set_source_rgba(cr.get(),
673  foreground_color_.r / 255.0,
674  foreground_color_.g / 255.0,
675  foreground_color_.b / 255.0,
676  foreground_color_.a / 255.0
677  );
678 
679  pango_cairo_show_layout(cr.get(), &layout);
680 }
681 
682 void pango_text::rerender(const bool force)
683 {
684  if(surface_dirty_ || force) {
685  assert(layout_.get());
686 
687  this->recalculate(force);
688  surface_dirty_ = false;
689 
690  int width = rect_.x + rect_.width;
691  int height = rect_.y + rect_.height;
692  if(maximum_width_ > 0) { width = std::min(width, maximum_width_); }
693  if(maximum_height_ > 0) { height = std::min(height, maximum_height_); }
694 
695  cairo_format_t format = CAIRO_FORMAT_ARGB32;
696  const unsigned stride = cairo_format_stride_for_width(format, width);
697 
698  this->create_surface_buffer(stride * height);
699 
700  if (surface_buffer_.empty()) {
702  return;
703  }
704 
705  render(*layout_, rect_, 0u, stride);
706 
707  static_assert(sizeof(Uint32) == 4, "Something is wrong with our typedefs");
708 
709  // The cairo surface is in CAIRO_FORMAT_ARGB32 which uses
710  // pre-multiplied alpha. SDL doesn't use that so the pixels need to be
711  // decoded again.
712  Uint32 * pixels = reinterpret_cast<Uint32 *>(&surface_buffer_[0]);
713 
714  for(int y = 0; y < height; ++y) {
715  for(int x = 0; x < width; ++x) {
716  from_cairo_format(pixels[y * width + x]);
717  }
718  }
719 
720  surface_.assign(SDL_CreateRGBSurfaceFrom(
721  &surface_buffer_[0], width, height, 32, stride, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000));
722  }
723 }
724 
725 void pango_text::create_surface_buffer(const size_t size) const
726 {
727  // Clear surface.
728  surface_.assign(nullptr);
729 
730  // Resize buffer appropriately and clear all existing data (essentially sets all pixel values to 0).
731  surface_buffer_.assign(size, 0);
732 }
733 
734 bool pango_text::set_markup(utils::string_view text, PangoLayout& layout) {
735  return this->set_markup_helper(link_aware_ ? this->format_link_tokens(text.to_string()) : text, layout);
736 }
737 
739  std::string delim = " \n\r\t";
740  // Tokenize according to these delimiters, and stream the results of `handle_token` on each token to get the new text.
741 
742  std::string result;
743 
744  int last_delim = -1;
745  for (size_t index = 0; index < text.size(); ++index) {
746  if (delim.find(text.at(index)) != std::string::npos) {
747  // want to include chars from range since last token, dont want to include any delimiters
748  result += this->handle_token(text.substr(last_delim + 1, index - last_delim - 1));
749  result += text.at(index);
750  last_delim = index;
751  }
752  }
753  if (last_delim < static_cast<int>(text.size()) - 1) {
754  result += this->handle_token(text.substr(last_delim + 1, text.size() - last_delim - 1));
755  }
756 
757  return result;
758 }
759 
761 {
762  if (looks_like_url(token)) {
763  return format_as_link(token, link_color_.to_hex_string());
764  } else {
765  return token;
766  }
767 }
768 
769 bool pango_text::set_markup_helper(utils::string_view text, PangoLayout& layout)
770 {
771  if(pango_parse_markup(text.data(), text.size(),
772  0, nullptr, nullptr, nullptr, nullptr)) {
773 
774  /* Markup is valid so set it. */
775  pango_layout_set_markup(&layout, text.data(), text.size());
776  return true;
777  }
778 
779  /*
780  * The markup is invalid. Try to recover.
781  *
782  * The pango engine tested seems to accept stray single quotes »'« and
783  * double quotes »"«. Stray ampersands »&« seem to give troubles.
784  * So only try to recover from broken ampersands, by simply replacing them
785  * with the escaped version.
786  */
787  std::string semi_escaped{semi_escape_text(text.to_string())};
788 
789  /*
790  * If at least one ampersand is replaced the semi-escaped string
791  * is longer than the original. If this isn't the case then the
792  * markup wasn't (only) broken by ampersands in the first place.
793  */
794  if(text.size() == semi_escaped.size()
795  || !pango_parse_markup(semi_escaped.c_str(), semi_escaped.size()
796  , 0, nullptr, nullptr, nullptr, nullptr)) {
797 
798  /* Fixing the ampersands didn't work. */
799  ERR_GUI_L << "pango_text::" << __func__
800  << " text '" << text
801  << "' has broken markup, set to normal text.\n";
802 
803  this->set_text(_("The text contains invalid markup: ") + text.to_string(), false);
804  return false;
805  }
806 
807  /* Replacement worked, still warn the user about the error. */
808  WRN_GUI_L << "pango_text::" << __func__
809  << " text '" << text
810  << "' has unescaped ampersands '&', escaped them.\n";
811 
812  pango_layout_set_markup(&layout, semi_escaped.c_str(), semi_escaped.size());
813  return true;
814 }
815 
817 {
818  auto text_parts = utils::vertical_split(text_);
819 
820  PangoLayout* upper_layout = pango_layout_new(context_.get());
821  PangoLayout* lower_layout = pango_layout_new(context_.get());
822 
823  set_markup(text_parts.first, *upper_layout);
824  set_markup(text_parts.second, *lower_layout);
825 
826  copy_layout_properties(*layout_, *upper_layout);
827  copy_layout_properties(*layout_, *lower_layout);
828 
829  sublayouts_.emplace_back(upper_layout, g_object_unref);
830  sublayouts_.emplace_back(lower_layout, g_object_unref);
831 
832  layout_.reset(nullptr);
833 }
834 
835 void pango_text::copy_layout_properties(PangoLayout& src, PangoLayout& dst)
836 {
837  pango_layout_set_alignment(&dst, pango_layout_get_alignment(&src));
838  pango_layout_set_height(&dst, pango_layout_get_height(&src));
839  pango_layout_set_ellipsize(&dst, pango_layout_get_ellipsize(&src));
840 }
841 
842 } // namespace font
Define the common log macros for the gui toolkit.
gui2::point get_size() const
Returns the pixel size needed for the text.
Definition: text.cpp:110
surface create_neutral_surface(int w, int h)
Definition: utils.cpp:77
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:71
unsigned font_size_
The font size to draw.
Definition: text.hpp:283
#define DBG_GUI_L
Definition: log.hpp:57
std::string format_as_link(const std::string &link, const std::string &color)
Definition: hyperlink.hpp:28
family_class
Font classes for get_font_families().
bool insert_unicode(const unsigned offset, ucs4::char_t unicode)
Inserts a unicode char.
Definition: text.cpp:144
bool set_text(const std::string &text, const bool markedup)
Sets the text to render.
Definition: text.cpp:281
const std::string & text() const
Definition: text.hpp:220
Note: Specific to sdl_ttf.
std::pair< string_view, string_view > vertical_split(const std::string &val)
Splits a string into two parts as evenly as possible based on lines.
int maximum_height_
The maximum height of the text.
Definition: text.hpp:325
#define a
int x
x coordinate.
Definition: point.hpp:34
std::string handle_token(const std::string &token) const
Definition: text.cpp:760
unsigned characters_per_line_
The number of characters per line.
Definition: text.hpp:317
ucs4_convert_impl::enableif< TD, typename TS::value_type >::type unicode_cast(const TS &source)
pango_text & set_maximum_length(const size_t maximum_length)
Definition: text.cpp:452
static void from_cairo_format(Uint32 &c)
Converts from cairo-format ARGB32 premultiplied alpha to plain alpha.
Definition: text.cpp:624
#define WRN_GUI_L
Definition: log.hpp:59
pango_text & set_link_aware(bool b)
Definition: text.cpp:465
int get_width() const
Returns the width needed for the text.
Definition: text.cpp:100
Holds a 2D point.
Definition: point.hpp:23
int y
y coordinate.
Definition: point.hpp:37
static void unpremultiply(Uint8 &value, const unsigned div)
Definition: text.cpp:608
gui2::point get_column_line(const gui2::point &position) const
Gets the column of line of the character at the position.
Definition: text.cpp:247
std::basic_string< charT, traits, Allocator > to_string(const Allocator &a=Allocator()) const
#define d
font::family_class font_class_
The font family class used.
Definition: text.hpp:280
pango_text & set_font_style(const FONT_STYLE font_style)
Definition: text.cpp:342
uint32_t char_t
gui2::point get_cursor_position(const unsigned column, const unsigned line=0) const
Gets the location for the cursor.
Definition: text.cpp:155
pango_text & set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
Definition: text.cpp:427
std::vector< uint8_t > surface_buffer_
Buffer to store the image on.
Definition: text.hpp:383
Small helper class to make sure the pango font object is destroyed properly.
Definition: font.hpp:23
pango_text & set_alignment(const PangoAlignment alignment)
Definition: text.cpp:441
bool set_markup(utils::string_view text, PangoLayout &layout)
Sets the markup'ed text.
Definition: text.cpp:734
#define b
int get_height() const
Returns the height needed for the text.
Definition: text.cpp:105
#define ERR_GUI_L
Definition: log.hpp:60
const t_string & get_font_families(family_class fclass)
Returns the currently defined fonts.
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
std::string to_hex_string() const
Returns the stored color in rrggbb hex format.
Definition: color.cpp:97
std::unique_ptr< PangoLayout, std::function< void(void *)> > layout_
Definition: text.hpp:251
void recalculate(const bool force=false) const
Recalculates the text layout.
Definition: text.cpp:487
unsigned values[256]
Definition: text.cpp:589
int maximum_width_
The maximum width of the text.
Definition: text.hpp:299
std::string semi_escape_text(const std::string &text)
Definition: escape.hpp:48
void rerender(const bool force=false)
Renders the text.
Definition: text.cpp:682
color_t foreground_color_
The foreground color.
Definition: text.hpp:289
pango_text & set_font_size(const unsigned font_size)
Definition: text.cpp:330
int font_scaled(int size)
Definition: general.cpp:449
static const inverse_table inverse_table_
Definition: text.cpp:602
PangoEllipsizeMode ellipse_mode_
The way too long text is shown depends on this mode.
Definition: text.hpp:328
pango_text & set_characters_per_line(const unsigned characters_per_line)
Definition: text.cpp:396
uint8_t r
Red value.
Definition: color.hpp:177
uint8_t a
Alpha value.
Definition: color.hpp:186
bool markedup_text_
Does the text contain pango markup? If different render routines must be used.
Definition: text.hpp:265
std::string format_link_tokens(const std::string &text) const
Definition: text.cpp:738
utf8::string & truncate(utf8::string &str, const size_t size)
Truncates a UTF-8 string to the specified number of characters.
Definition: unicode.cpp:117
bool surface_dirty_
The dirty state of the surface.
Definition: text.hpp:361
PangoRectangle rect_
Definition: text.hpp:252
bool calculation_dirty_
The text has two dirty states:
Definition: text.hpp:343
unsigned insert_text(const unsigned offset, const std::string &text)
Inserts UTF-8 text.
Definition: text.cpp:124
pango_text & set_family_class(font::family_class fclass)
Definition: text.cpp:319
BOOST_CONSTEXPR size_type size() const BOOST_NOEXCEPT
size_t size(const utf8::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:86
std::unique_ptr< PangoContext, std::function< void(void *)> > context_
Definition: text.hpp:250
bool link_aware_
Are hyperlinks in the text marked-up, and will get_link return them.
Definition: text.hpp:268
bool is_truncated() const
Has the text been truncated? This happens if it exceeds max width or height.
Definition: text.cpp:117
std::string debug_truncate(const std::string &text)
Returns a truncated version of the text.
Definition: helper.cpp:139
double g
Definition: astarsearch.cpp:64
PangoRectangle calculate_size(PangoLayout &layout) const
Calculates surface size.
Definition: text.cpp:499
std::string get_link(const gui2::point &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:232
#define i
FONT_STYLE font_style_
The style of the font, this is an orred mask of the font flags.
Definition: text.hpp:286
size_t maximum_length_
The maximum length of the text.
Definition: text.hpp:334
pango_text & set_link_color(const color_t &color)
Definition: text.cpp:475
int w
void create_surface_buffer(const size_t size) const
Creates a new buffer.
Definition: text.cpp:725
color_t link_color_
The color to render links in.
Definition: text.hpp:277
Text class.
Definition: text.hpp:78
pango_text & set_maximum_width(int width)
Definition: text.cpp:363
void assign(SDL_Surface *surf)
Definition: surface.hpp:41
#define f
bool looks_like_url(const std::string &str)
Definition: hyperlink.hpp:23
bool is_surface_split() const
Definition: text.hpp:429
bool set_markup_helper(utils::string_view text, PangoLayout &layout)
Definition: text.cpp:769
size_t length_
Length of the text.
Definition: text.hpp:346
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:99
std::vector< std::unique_ptr< PangoLayout, std::function< void(void *)> > > sublayouts_
Definition: text.hpp:255
surface surface_
The SDL surface to render upon used as a cache.
Definition: text.hpp:258
surface & render()
Returns the rendered text.
Definition: text.cpp:93
pango_text & set_maximum_height(int height, bool multiline)
Definition: text.cpp:408
void split_surface()
Splits the text to two Cairo surfaces.
Definition: text.cpp:816
uint8_t g
Green value.
Definition: color.hpp:180
uint8_t b
Blue value.
Definition: color.hpp:183
mock_char c
std::string text_
The text to draw (stored as UTF-8).
Definition: text.hpp:262
pango_text & set_foreground_color(const color_t &color)
Definition: text.cpp:353
const int font_size
unsigned operator[](Uint8 i) const
Definition: text.cpp:599
BOOST_CONSTEXPR const_pointer data() const BOOST_NOEXCEPT
PangoAlignment alignment_
The alignment of the text.
Definition: text.hpp:331
std::string get_token(const gui2::point &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:200
std::string string
static void copy_layout_properties(PangoLayout &src, PangoLayout &dst)
Definition: text.cpp:835